词法分析器对象在解析器操作中可用yy.lexer
,因此您可以使用 更改开始条件yy.lexer.begin('expression');
并返回到旧条件yy.lexer.popState()
。那部分没有问题。
但是,您需要考虑新的启动条件何时生效。LALR(1) 解析器,例如由 jison(或 bison)实现的解析器,使用单个前瞻标记来决定采取什么行动。(LALR(1) 中的“1”是可能的前瞻长度。)这意味着当解析器操作被执行时——当它所附加的规则被减少时——下一个标记可能已经被读取。
情况并非总是如此;jison 和 bison 有时都可以在不使用前瞻令牌的情况下进行归约,在这种情况下他们还没有读取它。
简而言之,一个动作中对词法分析器状态的更改可能会在读取下一个标记之前生效,但大多数情况下它会在读取第二个下一个标记时生效。由于这种模糊性,通常最好在不受词法分析器状态更改影响的标记之前进行词法分析器状态更改。
例如,考虑标准计算器。以下示例改编自 jison 手册:
%lex
%%
\s+ /* skip whitespace */
[0-9]+\b yytext=parseInt(yytext); return 'NUMBER'
[*/+%()-] return yytext[0]
<<EOF>> return 'EOF'
. return 'INVALID'
/lex
%left '+' '-'
%left '*' '/' '%'
%left UMINUS
%start expressions
%% /* language grammar */
expressions: e EOF {return $1;};
e : e '+' e {$$ = $1+$3;}
| e '-' e {$$ = $1-$3;}
| e '*' e {$$ = $1*$3;}
| e '/' e {$$ = $1/$3;}
| e '%' e {$$ = $1%$3;}
| '-' e %prec UMINUS {$$ = -$2;}
| '(' e ')' {$$ = $2;}
| NUMBER {$$ = $1;}
;
[现在,让我们对其进行修改,以便将]所有数字解释为十六进制。HEX
我们使用称为;的非排他性开始条件。当它被启用时,十六进制数字被识别并相应地转换。
%lex
%s HEX
%%
\s+ /* skip whitespace */
<INITIAL>[0-9]+("."[0-9]+)?\b yytext=parseInt(yytext); return 'NUMBER'
<HEX>[0-9a-fA-F]+\b yytext=parseInt(yytext, 16); return 'NUMBER'
[*/+%()[\]-] return yytext[0]
<<EOF>> return 'EOF'
. return 'INVALID'
/lex
%left '+' '-'
%left '*' '/' '%'
%left UMINUS
%start expressions
%% /* language grammar */
expressions: e EOF {return $1;};
e : e '+' e {$$ = $1+$3;}
| e '-' e {$$ = $1-$3;}
| e '*' e {$$ = $1*$3;}
| e '/' e {$$ = $1/$3;}
| e '%' e {$$ = $1%$3;}
| '-' e %prec UMINUS {$$ = -$2;}
| '(' e ')' {$$ = $2;}
| hex '[' e unhex ']' {$$ = $3;}
| NUMBER {$$ = $1;}
;
hex : { yy.lexer.begin('HEX'); } ;
unhex: { yy.lexer.popState(); } ;
在这里,我们使用空的非终结hex
符unhex
来改变词法分析器的状态。(在 bison 中,我会使用中间规则动作,这非常相似,但 jison 似乎没有实现它们。)关键是状态更改在[和]标记之前完成,不受状态变化。因此,状态更改发生在当前前瞻令牌之前还是之后都无关紧要,因为我们不需要它在第二个下一个令牌之前生效,这可能是一个数字。
26
给定输入,此语法将正确输出[10+a]
。如果我们将hex
标记非终结符移动到括号内:
/* NOT CORRECT */
| '[' hex e unhex ']' {$$ = $3;}
然后开始条件更改发生在前瞻标记之后,因此[10+a]
产生20
.