如何在不从缓冲区中删除它的情况下查看flex中的下一个标记

时间:2016-05-15 21:44:28

标签: regex bison flex-lexer lex ampl

我正在尝试为某些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

3 个答案:

答案 0 :(得分:2)

我认为您正在尝试检查符号表,了解您所看到的名称。

如果是这种情况,您应该通过符号表进行通信来完成此操作。那就是:

  1. 创建一个简单的&#34;符号&#34;规则。你原来的规则很好:

    {SYMBOLIC_NAME}   { yylval.string = (char*) strdup(yytext);
                    return SYMBOLIC_NAME;  
                  }
    
  2. 在解析器级别处理声明语法:

    var_decl : VAR SYMBOLIC_NAME
         { add name to symbol table }
    
  3. 现在返回并扩展您的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;
                 }
             }
    
  4. 现在,您有一个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开始条件中的其他条目:任何其他标记(大概)是语法错误,包括您碰巧需要的任何关键字标记(例如例如varsetparam

我认为这样可行,但我实际上并没有尝试编译它。但它不仅有点笨重。

恕我直言,更好的方法是在解析器和词法分析器之间共享符号表。 (野牛手册解释了如何为yylex提供额外的参数,并且flex手册解释了如何接收它们。)基本符号表将如上所示,除了它将属于解析器,或更多准确地调用解析器的程序。但是,它可能具有比符号种类更多的语义信息。映射的值可能是一个有区别的联合,boost::variant或其他任何被证明是方便的。

在这种情况下,扫描仪将如上所述,但没有开始条件。当它看到一个未定义的符号时(正如在扫描定义关键字后面的符号时那样),它将返回一个UNDEFINED_NAME标记,因此您的解析器规则将类似于:

param_definition: PARAM UNDEFINED_NAME ...

并且在定义的语义操作中,解析器将填充符号的种类以及任何其他有用信息。在这种情况下,yylval.symbol可能很方便成为指向符号表中值的指针(不是迭代器,它可能会失效,而是&*iterator这是稳定的)所以不需要重复查找。

在这种情况下,使用未定义的符号并尝试定义已定义的符号自然会出现语法错误,因为解析器没有处理这些情况的规则。要提供有意义的错误消息,您可能希望添加此类规则作为错误情况。

答案 2 :(得分:0)

你不应该关心语法。您的语法应该只处理IDENTIFIER,然后查找符号表本身以进行语义操作。

所有这些在词法分析器中捣乱当然是错误的做法。