Flex:处理类似多字符注释的分隔符

时间:2014-05-03 14:41:09

标签: flex-lexer

我试图解析一种具有某种"转义序列的语言"使用flex / bison。目前,我很难定义词法分析器。通过示例解释最简单:

Text    --> {if} test {literal} text 43.21 {if} foo {/literal} {if}
            ---- ---- --------- ------------------- ---------- ----   etc.
Desired -->  IF  TEXT (ignore)         TEXT          (ignore)   IF
  Token

如您所见,该语言包含一些终端符号,例如IFTEXT,这些符号非常简单。 但是,{literal}{/literal}之间的所有内容都是TEXT,即使它包含的字符串本来也是特殊的令牌。

到目前为止,我可以为词法分析器找到最好的,它使用Start Conditions在不同状态之间跳转:如果遇到{literal},它会激活LITERAL规则。

%{
#include <stdio.h>
#define YY_DECL int yylex()
%}
%x LITERAL
%%
[^{]+                 {printf("TEXT: %s\n", yytext);}
"{if}"                {printf("IF\n");}
"{literal}"           {BEGIN(LITERAL);}
<LITERAL>[^{]+        {printf("TEXT: %s\n", yytext);}
<LITERAL>"{"          {printf("TEXT: %s\n", yytext);}
<LITERAL>"{/literal}" {BEGIN(INITIAL);}
%%
main() {yylex();}

但是如何离开LITERAL州?将此定义与上面的示例一起使用

IF
TEXT:  test 
TEXT:  text 43.21 
TEXT: {
TEXT: if} foo 
IF

换句话说,TEXT标记中的{literal}令牌会在{处拆分。我怎么能避免这个?

1 个答案:

答案 0 :(得分:2)

{literal}内的文字在{分开,因为您匹配{;如果您不希望拆分文本,则需要使用LITERAL开始条件中的规则扩展匹配,而不是每个创建新匹配。这是一个相当常见的(f)lex习语,并且有一个专门为此目的而设计的功能:yymore

yymore()告诉扫描器下次匹配规则时,相应的令牌应该附加到yytext的当前值而不是替换它。
(来自flex manual。)

使用这个方便的功能,我们可以写:

"{literal}"           {BEGIN(LITERAL);}
<LITERAL>[^{]+        {yymore();}
<LITERAL>"{"          {yymore();}
<LITERAL>"{/literal}" {
                        /* Now we have to provide the token, but we've matched
                         * 10 extra characters, the close marker, and so the
                         * token is the text from yytext with length yyleng-10.
                         * Here we just print it out, but normally we'd copy
                         * yytext to a temporary for future processing.
                         * Most compilers will optimize out the call to strlen.
                         */
                        BEGIN(INITIAL);
                        printf("TEXT: %.*s\n",
                               (int)(yyleng - strlen("{/literal}")),
                               yytext);
                      }

以上假设LITERAL状态字面意思是字面意思:),也就是说,它仅以{/literal}标记终止,并始终识别{/literal}标记,无论上下文。但是,它并不依赖于此;您可以在文字扫描中进行更复杂的令牌识别,只要您在除关闭标记的操作之外的每个操作中始终使用yymore()

如果我的假设是正确的,可以使用另一种解决方案:只需将整个文字与正则表达式匹配即可。用非贪婪的匹配(或者甚至直接作为有限状态机)编写正则表达式会更容易,但不幸的是flex并没有实现那些,所以必须做很长的事情,而且它确实很长。幸运的是,结束标记以一个未包含在结束标记内的字符开始,因此可以相当容易地机械地生成正则表达式。在这里,我使用了flex定义来避免一个非常长的行,并使模式更明显:

l1             [{]
l2          "/"[{]
l3         "/l"[{]
l4        "/li"[{]
l5       "/lit"[{]
l6      "/lite"[{]
l7     "/liter"[{]
l8    "/litera"[{]
l9   "/literal"[{]
loop [{](l1|l2|l3|l4|l5|l6|l7|l8|l9)*

n1             [^{/]
n2          "/"[^{l]
n3         "/l"[^{i]
n4        "/li"[^{t]
n5       "/lit"[^{e]
n6      "/lite"[^{r]
n7     "/liter"[^{a]
n8    "/litera"[^{l]
n9   "/literal"[^{}]
next n1|n2|n3|n4|n5|n6|n7|n8|n9

prefix  "{literal}"
middle  ([^{]|{loop}{next})*
suffix  {loop}"/literal}"

literal {prefix}{middle}{suffix}

%%

{literal}  {
              /* The token includes both the {literal} opener and
               * the {/literal} closer, so we need to get rid of
               * both of them.
               */ 
              printf("TEXT: %.*s\n",
                     (int)(yyleng - strlen("{literal}") - strlen("{/literal}")),
                     yytext + strlen("{literal}"));
           }