我正在学习编译器构建课程,我当前的任务是为我们正在实现的语言编写词法分析器。我不知道如何满足词法分析器必须识别连接标记的要求。也就是说,标记不被空格分隔。例如:字符串
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行感到困惑”更好的调试输出。
你说得很对,你应该只匹配一个“任何”字符作为后备。获取有关解析所在行的信息的“标准”方法是使用
--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
的更多信息
我相信问题是您单独使用 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 会将不匹配的字符转储到标准输出。这非常有用。在制定识别令牌的规则时,任何无法识别的内容都会被吐出。您可以不断修改您的规则,直到没有输入丢失。