如何让 lex/flex 识别不以空格分隔的标记?

问题描述 投票:0回答:2

我正在学习编译器构建课程,我当前的任务是为我们正在实现的语言编写词法分析器。我不知道如何满足词法分析器必须识别连接标记的要求。也就是说,标记不被空格分隔。例如:字符串

39if
应该被识别为数字
39
和关键字
if
。同时,词法分析器在遇到无效输入时也必须
exit(1)

我的代码的简化版本:

%{
#include <stdio.h>
%}

%option main warn debug

%%

if      |
then    |
else    printf("keyword: %s\n", yytext);

[[:digit:]]+    printf("number: %s\n", yytext);

[[:alpha:]][[:alnum:]]*     printf("identifier: %s\n", yytext);

[[:space:]]+    // skip whitespace
[[:^space:]]+   { printf("ERROR: %s\n", yytext); exit(1); }

%%

当我运行这个(或我的完整版本)并向其传递输入

39if
时,错误规则会匹配并且输出为
ERROR: 39if
,当我希望它是:

number: 39
keyword: if

(即与我输入

39 if
作为输入相同。)

按照手册,我有预感,原因是错误规则匹配比数字和关键字规则更长的可能输入,而flex会更喜欢它。也就是说,我不知道如何解决这种情况。编写一个显式的正则表达式来拒绝所有非错误输入似乎是不可行的,而且我不知道如何编写一个“包罗万象”的规则来处理词法分析器错误。

更新:我想我可以将包罗万象的规则设为

. { exit(1); }
,但我想得到一些比“我在第1行感到困惑”更好的调试输出。

parsing compiler-construction lex lexer
2个回答
4
投票

你说得很对,你应该只匹配一个“任何”字符作为后备。获取有关解析所在行的信息的“标准”方法是使用

--bison-bridge
选项,但这可能有点麻烦,特别是如果您不使用
bison
的话。还有很多其他方法 - 例如,在手册中查找指定您自己的 i/o 函数的方法 - 但最简单的恕我直言是使用启动条件:

%x LEXING_ERROR
%%
// all your rules; the following *must* be at the end
.                 { BEGIN(LEXING_ERROR); yyless(1); }
<LEXING_ERROR>.+  { fprintf(stderr,
                            "Invalid character '%c' found at line %d,"
                            " just before '%s'\n",
                            *yytext, yylineno, yytext+1);
                    exit(1);
                  }

注意:确保您忽略了规则中的空格。模式

.+
匹配任何数字,但至少有一个非换行符,或者换句话说,直到当前行的末尾(它将强制 flex 读取那么远,这应该不是问题)。
yyless(n)
n
个字符备份读取指针,因此在
.
规则匹配后,它将重新扫描该字符,产生(希望)一条半合理的错误消息。 (如果您的输入是多字节,或者具有奇怪的控制字符,这实际上是不合理的,因此您可以编写更仔细的代码。由您决定。如果错误位于行尾,也可能不合理,所以您可能还想编写一个更仔细的正则表达式,它可以获得更多上下文,甚至可能限制读取的前向字符数。)

在 Flex 手册中查找 启动条件,了解有关

%x
BEGIN

的更多信息

0
投票

我相信问题是您单独使用 lex 并让扫描仪规则执行 printf 的操作。 THOMAS NIEMANN 的优秀教程 A GUIDE TO LEX & YACC 中建议采用这种方法。 他在那里展示了如何通过将 printf 语句放入 lex 规则中来使用 lex。此示例将行号添加到文件的行前面:

%{
       int yylineno;
%}
%%
^(.*)\n printf("%4d\t%s", ++yylineno, yytext); 
%%
int main(int argc, char *argv[]) {
       yyin = fopen(argv[1], "r");
       yylex();
       fclose(yyin);
}

凭借想象力和足智多谋,可以用 lex 编写有趣的小程序。但是,以这种方式使用时,只有由空格分隔的标记才会被识别。这是以这种方式使用 lex 的限制。这不是 lex 的预期使用方式。

为了允许查找不由空格分隔的标记,您需要为每个标记调用一次 lex 。这可以通过从 main 多次调用 yylex 并将 printf 调用放在那里来完成。当以这种方式调用时,lex 不需要空格分隔的标记。以下查找所有由小写字母组成的标记:

%{
    int nword, lexresult;
%}
%%
[a-z]+      { nword++; return 1;}
[^a-z]+     ;
%%
int yywrap(void) { return 1;}

int main(void) {
while ((lexresult = yylex()) > 0 ) {
    printf("\t%d:%d, %s\n", nword, lexresult, yytext);
    }   ;
return 0;
}

如果省略“[^a-z]+;”行你会发现 lex 的行为非常好。该程序将打印出每个数字标记,但此外,lex 会将不匹配的字符转储到标准输出。这非常有用。在制定识别令牌的规则时,任何无法识别的内容都会被吐出。您可以不断修改您的规则,直到没有输入丢失。

© www.soinside.com 2019 - 2024. All rights reserved.