如果你可以在你的词法分析器中完美地预测状态序列,那么使用yacc 真的没有意义;它在这里确实没有提供任何有用的设施。 (请参阅下面的词法分析器中的错误恢复策略。)另一方面,如果语法比简单的模式序列更复杂,您可能需要yacc;在这种情况下,您应该提供一个更准确的示例。
无论如何,将状态压入堆栈并不是处理进程的一种非常有效的机制。使用BEGIN 宏通常更容易构建简单的状态机。
这是您示例的基本词法分析器:
%s NUMBER WORD2 ETC
%%
/* Any indented text before the first rule is inserted
* at the top of the yylex function.
*/
int error_count = 0;
<INITIAL>[A-Z]{6} BEGIN(NUMBER);
<NUMBER>[0-9]{5} BEGIN(WORD2);
<WORD2>[A-Z]{2} BEGIN(ETC);
<ETC>[+-/]{3} BEGIN(EOL);
<EOL>" "*\n BEGIN(INITIAL);
<RECOVER>.* BEGIN(EOL);
.|\n signal_error(); ++error_count; BEGIN(RECOVER);
<<EOF>> return error_count != 0;
(在 (f)lex 中,模式 . 不匹配换行符。在 RECOVER 开始条件中使用这一事实应该在下面变得明显。)
只要换行符不是模式的一部分,就很容易跟踪行和列信息,就像您的示例一样。所以让我们补充一下:
%s NUMBER WORD2 ETC
%%
int error_count = 0;
int line = 1, column = 1;
<INITIAL>[A-Z]{6} BEGIN(NUMBER); column += yyleng;
<NUMBER>[0-9]{5} BEGIN(WORD2); column += yyleng;
<WORD2>[A-Z]{2} BEGIN(ETC); column += yyleng;
<ETC>[+-/]{3} BEGIN(EOL); column += yyleng;
<EOL>" "*\n BEGIN(INITIAL); ++line; column = 0;
<RECOVER>.* BEGIN(EOL);
.|\n signal_error(); ++error_count; yyless(0); BEGIN(RECOVER);
<<EOF>> return error_count;
(注意在默认规则中使用yyless(0)。这会导致错误字符返回到输入源,以便在新的开始条件下重新扫描它,这避免了一些围绕换行符和获取的混乱逻辑行和列计数器正确。此外,我们将所有换行处理集中在EOL 开始条件的规则中,以防我们以后需要修改它。)
现在只需要编写错误报告器,我们需要将状态映射到字符串和 main 驱动程序,并添加必要的内容以避免编译器警告:
%{
# include <stdio.h>
void signal_error(int state, int line, int column);
%}
%option noyywrap nounput noinput
%s NUMBER WORD2 ETC EOL RECOVER
%%
int error_count = 0;
int line=1, column=1;
<INITIAL>[A-Z]{6} BEGIN(NUMBER); column += yyleng;
<NUMBER>[0-9]{5} BEGIN(WORD2); column += yyleng;
<WORD2>[A-Z]{2} BEGIN(ETC); column += yyleng;
<ETC>[+-/]{3} BEGIN(EOL); column += yyleng;
<EOL>" "*\n BEGIN(INITIAL); ++line; column = 1;
<RECOVER>.* BEGIN(EOL);
.|\n { signal_error(YY_START, line, column);
++error_count; yyless(0); BEGIN(RECOVER);
}
<<EOF>> return error_count != 0;
%%
typedef struct { int state; const char* name; } StateToName;
const StateToName state_to_name[] = {
{ INITIAL, "in WORD1" },
{ NUMBER, "in NUMBER"},
{ WORD2, "in WORD2" },
{ ETC, "in ETC" },
{ EOL, "at end of line"},
{ -1, NULL}
};
const char* find_name(int state) {
for (const StateToName* ent = state_to_name; ent->name; ++ent)
if (state == ent->state) return ent->name;
return "in unknown state";
}
void signal_error(int state, int line, int column) {
fprintf(stderr, "Bad character %s at line %d, column %d\n",
find_name(state), line, column);
}
int main(int argc, char** argv) {
return yylex();
}
这仍然不是很理想,因为报告的列计数是“令牌”的开始,而不是带有无效字符的实际列。不幸的是,flex 没有提供写“匹配此模式的初始前缀”的方法。在这种情况下,模式计算可以手动完成,但总的来说很烦人:
<INITIAL>[A-Z]{6} BEGIN(NUMBER); column += yyleng;
<INITIAL>[A-Z]{0,5} BEGIN(ERROR); column += yyleng;
<NUMBER>[0-9]{5} BEGIN(WORD2); column += yyleng;
<NUMBER>[0-9]{0,4} BEGIN(ERROR); column += yyleng;
<WORD2>[A-Z]{2} BEGIN(ETC); column += yyleng;
<WORD2>[A-Z]{0,1} BEGIN(ERROR); column += yyleng;
<ETC>[+-/]{3} BEGIN(EOL); column += yyleng;
<ETC>[+-/]{0,2} BEGIN(ERROR); column += yyleng;
根据上述情况,需要将ERROR 添加到开始条件列表中,但由于开始条件包含在内,因此无需为该条件显式标记任何规则。