我正在尝试为某些AMPL语言子集构建词法分析器。 我现在需要lexer正在处理什么类型的符号名称。 每个符号名称都是var或param或set。幸运的是,所有这些都必须在使用之前声明。所以我认为我可以在flex中使用先行运算符,只需更改词法分析器中的代码
SYMBOLIC_NAME [a-zA-Z_][a-zA-Z0-9_]*
%%
param { return PARAM; }
var { return VAR; }
set { return SET; }
{SYMBOLIC_NAME} { yylval.string = (char*) strdup(yytext);
return SYMBOLIC_NAME;
}
%%
这样的事情
SYMBOLIC_NAME [a-zA-Z_][a-zA-Z0-9_]*
%{
#include <vector>
#include <algorithm>
std::vector<std::string> paramNames;
std::vector<std::string> setNames;
std::vector<std::string> varNames;
%}
%%
param/(.|\n)+{SYMBOLIC_NAME} { paramNames.push_back(&yytext[5]);
return PARAM; }
var/(.|\n)+{SYMBOLIC_NAME} { varNames.push_back(&yytext[3]);
return VAR; }
set/(.|\n)+{SYMBOLIC_NAME} { setNames.push_back(&yytext[3]);
return SET; }
{SYMBOLIC_NAME} { if ( std::find(setNames.begin(), setNames.end(), yytext) != setNames.end() ) {
yylval.string = (char*) strdup(yytext);
return SET_NAME;
}
if ( std::find(paramNames.begin(), paramNames.end(), yytext) != paramNames.end() ){
yylval.string = (char*) strdup(yytext);
return PARAM_NAME;
}
if ( std::find(varNames.begin(), varNames.end(), yytext) != varNames.end() ){
yylval.string = (char*) strdup(yytext);
return VAR_NAME;
}
}
%%
我知道它不起作用,因为 yytext 不包含前三个正则表达式的第二部分。 问题出现了如何查看(。| \ n)+ {SYMBOLIC_NAME} 下的内容。
PS
我知道代码不是最优的,但这不是问题:D
答案 0 :(得分:2)
我认为您正在尝试检查符号表,了解您所看到的名称。
如果是这种情况,您应该通过符号表进行通信来完成此操作。那就是:
创建一个简单的&#34;符号&#34;规则。你原来的规则很好:
{SYMBOLIC_NAME} { yylval.string = (char*) strdup(yytext);
return SYMBOLIC_NAME;
}
在解析器级别处理声明语法:
var_decl : VAR SYMBOLIC_NAME
{ add name to symbol table }
现在返回并扩展您的SYMBOLIC_NAME规则以检查已定义的符号:
{SYMBOLIC_NAME} {
yylval.string = (char*) strdup(yytext);
if ( std::find(setNames.begin(), setNames.end(), yytext) != setNames.end() ) {
return SET_NAME;
}
else if (... varNames ...) {
return VAR_NAME;
else if (... paramNames ...) {
return PARAM_NAME;
}
else {
return SYMBOLIC_NAME;
}
}
现在,您有一个Flex目标返回四个可能的令牌,具体取决于已定义的令牌。但Flex不必担心记住哪个符号定义处于活动状态 - 您可以让解析器处理它。
在解析器方面,您编写了不同的规则:
var_decl: VAR SYMBOLIC_NAME
set_decl: SET SYMBOLIC_NAME
expr: atom '+' atom
atom: VAR_NAME | SET_NAME | PARAM_NAME
答案 1 :(得分:1)
你可以使用开始条件有效地“偷看”,但是如果你真正想要做的是维护一个符号表,并让词法分析器自动为每个符号返回正确的语义类别,那么有一个更好的解决方案(见下文)。
首先,不是使用三个std::vector
列表,而是直接搜索每个列表,直到找到符号,最好使用单个std::unordered_map
将每个名称与语义相关联类型。 (我知道你说不要考虑代码的低效率,但这种改变会使事情变得更加简单。)
如果您希望词法分析器负责维护符号表,虽然它有点强制,但由于解析器还需要存储与每个符号关联的语义信息,因此可以轻松完成。尽管如此,它并不太痛苦。下面我使用单个开始条件来收集定义关键字后的定义名称(这基本上是你的前瞻,但这样,词法分析器将实际名称隔离,而不是以可变数量的空格开头的字符串)。 / p>
这里我利用哈希表包含表示符号名称的std::string
这一事实,方法是将yylval.string
设置为哈希表条目中的内部C字符串。只要您不修改yylval.string
的内容,这就完全安全了,因为符号永远不会从符号表中删除,并且哈希表永远不会移动其元素。实际上,建立yylval
联盟成员可能更好:
%union {
std::string* string;
// ...
}
但这是一个小细节。这是扫描仪:
%{
#include <unordered_map>
namespace {
enum class Kind { UNDEFINED, PARAM, SET, VAR };
std::unordered_map<std::string, Kind> symbols;
}
%}
%x SC_DEFINE
id [[:alpha:]_][[:alnum:]_]*
%%
/* Up to the first unindented line is inserted at the beginning of yylex */
Kind to_define;
<*>[[:space:]] /* Ignore in all start conditions */
param { kind_to_define = Kind::PARAM; BEGIN(SC_DEFINE); }
set { kind_to_define = Kind::SET; BEGIN(SC_DEFINE); }
var { kind_to_define = Kind::VAR; BEGIN(SC_DEFINE); }
{id} { auto it = symbols.emplace(yytext, Kind::UNKNOWN).first;
yylval.string = it->first.c_str();
switch (it->second) {
case Kind::PARAM: return PARAM_NAME;
case Kind::SET: return SET_NAME;
case Kind::VAR: return VAR_NAME;
default: return UNDEFINED_NAME;
}
}
<SC_DEFINE>{id} { auto itbool = symbols.emplace(yytext, to_define);
if (!itbool.second) {
if (itbool.first->second != Kind::UNKNOWN) {
/* Redefinition: handle the error somehow */
} else {
/* Used previously, error presumably already issued */
itbool.first->second = to_define;
}
}
BEGIN(INITIAL);
yylval.string = itbool.first->first.c_str();
switch (to_define) {
case Kind::PARAM: return DEFINE_PARAM;
case Kind::SET: return DEFINE_SET;
case Kind::VAR: return DEFINE_VAR;
default: /* Logic error */
}
}
注意:以上内容会返回一个DEFINE_PARAM
(例如)令牌,已经指示了该符号的名称(在yylval.string
中),所以你的语法规则必须是param_definition: DEFINE_PARAM ...
而不是param_definition: PARAM SYMBOL ...
。
我上面没有做的一件事是填写SC_DEFINE
开始条件中的其他条目:任何其他标记(大概)是语法错误,包括您碰巧需要的任何关键字标记(例如例如var
,set
和param
。
我认为这样可行,但我实际上并没有尝试编译它。但它不仅有点笨重。
恕我直言,更好的方法是在解析器和词法分析器之间共享符号表。 (野牛手册解释了如何为yylex
提供额外的参数,并且flex手册解释了如何接收它们。)基本符号表将如上所示,除了它将属于解析器,或更多准确地调用解析器的程序。但是,它可能具有比符号种类更多的语义信息。映射的值可能是一个有区别的联合,boost::variant
或其他任何被证明是方便的。
在这种情况下,扫描仪将如上所述,但没有开始条件。当它看到一个未定义的符号时(正如在扫描定义关键字后面的符号时那样),它将返回一个UNDEFINED_NAME
标记,因此您的解析器规则将类似于:
param_definition: PARAM UNDEFINED_NAME ...
并且在定义的语义操作中,解析器将填充符号的种类以及任何其他有用信息。在这种情况下,yylval.symbol
可能很方便成为指向符号表中值的指针(不是迭代器,它可能会失效,而是&*iterator
这是稳定的)所以不需要重复查找。
在这种情况下,使用未定义的符号并尝试定义已定义的符号自然会出现语法错误,因为解析器没有处理这些情况的规则。要提供有意义的错误消息,您可能希望添加此类规则作为错误情况。
答案 2 :(得分:0)
你不应该关心语法。您的语法应该只处理IDENTIFIER
,然后查找符号表本身以进行语义操作。
所有这些在词法分析器中捣乱当然是错误的做法。