减少疯狂的flex lexer扩展?

时间:2016-02-28 06:34:19

标签: flex-lexer

我编写了一个flex lexer来处理BYOND的.dmi文件格式的文本。里面的内容是由'='分隔的(键,值)对。有效密钥本质上都是关键字(例如“宽度”),无效密钥不是错误:它们只是被忽略。

有趣的是,BYOND的.dmi解析器的当前状态在'='之前使用所有作为其关键字,并且只是忽略任何多余的垃圾。这意味着“\ twidth123”被识别为“宽度”。

我的问题的关键在于允许这种不规则性。在这样做时,我生成的词法分析器从~40-50KB扩展到~13-14MB。作为参考,我提出了以下人为的例子:

%option c++ noyywrap

fill    [^=#\n]*

%%
{fill}version{fill}     { return 0; }
{fill}width{fill}       { return 0; }
{fill}height{fill}      { return 0; }
{fill}state{fill}       { return 0; }
{fill}dirs{fill}        { return 0; }
{fill}frames{fill}      { return 0; }
{fill}delay{fill}       { return 0; }
{fill}loop{fill}        { return 0; }
{fill}rewind{fill}      { return 0; }
{fill}movement{fill}    { return 0; }
{fill}hotspot{fill}     { return 0; }
%%

fill是用于将关键字与“之前的任何内容”合并的规则。在上面运行flex会在我的计算机上产生~13MB lex.yy.cc。只需在填充规则中删除kleene星(*)即可生成45KB的lex.yy.cc文件;然而,显然,这会使词法分析器不正确。

是否有任何技巧,弹性选项或词法分析器黑客可以避免这种疯狂的扩张?我唯一能想到的是:

  1. 禁止“width123”表示“宽度”,这是不合需要的,因为无法解析技术上正确的文件。
  2. 制作一个简单的[^ = \ n] +规则来返回一些标识符标记,并在解析器中选择关键字。这对我来说似乎不是最理想的,特别是因为不同的关键字具有不同的值类型,并且在解析器中处理“'width''='INT”和“'version''='FLOAT”似乎最自然而不是“ID'='VALUE”,然后在标识符中选择关键字,确保值的类型正确,等等。
  3. 我可以制定规则{fill}(width | height | version | ...){fill},这确实使生成的文件保持较小。然而,虽然正则表达式解析器倾向于产生“捕获”,但flex只是给了我yytext并重新解析为了生成所需的令牌,关键字产生所需的令牌在算法复杂性方面似乎是非常不受欢迎的。

2 个答案:

答案 0 :(得分:1)

使fill成为一个单独的规则,不执行任何操作,并将其从所有其他规则中删除,并将其定义与空格分开以便清晰起见:

whitespace [ \t\f]
fill [^#=\n]
%%
{whitespace}+ ;
{fill}+ ;

我可能还会避免在词法分析器中构建关键字,只使用执行表查找的identifier [a-zA-Z]+规则。最后添加一条规则来捕获=

. return yytext[0];

让解析器处理所有特殊字符。

答案 1 :(得分:1)

这不是一个真正的问题,flex是"擅长",但如果精确定义它就可以解决。特别是,如果 = 之前的随机字符串包含多个关键字,则必须知道应返回哪些关键字。例如,假设输入为:

garbage_widtheight_moregarbage = 42

现在,是设置width还是height

请记住,弹性扫描程序将选择匹配最长的规则和具有相同长匹配的规则,这是词法描述中的第一个。

所以OP中提出的模型:

fill    [^=#\n]*

%%
{fill}width{fill}       { return 0; }
{fill}height{fill}      { return 0; }
  /* SNIP */

总是更喜欢widthheight,因为匹配的长度相同(都在 = 之前的最后一个字符处终止)和{{1模式在文件中排在第一位。如果规则以相反的顺序编写,则width将是首选。

另一方面,如果您删除了第二个height

{fill}

然后输入中的 last 关键字(在这种情况下,{fill}width{fill} { return 0; } {fill}height{fill} { return 0; } )将是首选,因为那个具有更长的匹配。

然而,最可能的要求是识别 first 关键字,因此前面的任何一个都不起作用。为了匹配第一个关键字,首先必须匹配{em>最短可能的height序列。由于flex不会实现非贪婪的重复,因此只能逐个字符地进行。

以下是一个使用开始条件的示例。请注意,我们会在实际找到{fill}之前保留关键字令牌,以防=找不到。

=

在转义转义代码中,您可能希望翻译 /* INITIAL: beginning of a line * FIND_EQUAL: keyword recognized, looking for the = * VALUE: = recognized, lexing the right-hand side * NEXT_LINE: find the next line and continue the scan */ %x FIND_EQUAL VALUE %% int keyword; "[#=]".* /* Skip comments and lines with no recognizable keyword */ version { keyword = KW_VERSION; BEGIN(FIND_EQUAL); } width { keyword = KW_WIDTH; BEGIN(FIND_EQUAL); } height { keyword = KW_HEIGHT; BEGIN(FIND_EQUAL); } /* etc. */ .|\n /* Skip any other single character, or newline */ <FIND_EQUAL>{ [^=#\n]*"=" { BEGIN(VALUE); return keyword; } "#".* { BEGIN(INITIAL); } \n { BEGIN(INITIAL); } } <VALUE>{ "#".* { BEGIN(INITIAL); } \n { BEGIN(INITIAL); } [[:blank:]]+ ; /* Ignore space and tab characters */ [[:digit:]]+ { yylval.ival = atoi(yytext); BEGIN(NEXT_LINE); return INTEGER; } [[:digit:]]+"."[[:digit:]]*|"."[[:digit:]]+ { yylval.fval = atod(yytext); BEGIN(NEXT_LINE); return FLOAT; } \"([^"]|\\.)*\" { char* s = malloc(yyleng - 1); yylval.sval = s; /* Remove quotes and escape characters */ yytext[yyleng - 1] = '\0'; do { if (*++yytext == '\\') ++yytext; *s++ = *yytext; } while (*yytext); BEGIN(NEXT_LINE); return STRING; } /* Other possible value token types */ . BEGIN(NEXT_LINE); /* bad character in value */ } <NEXT_LINE>.*\n? BEGIN(INITIAL); 之类的内容。您可能还希望避免使用物理换行符的字符串值。还有一堆etceteras。它仅用作模型。