1

是否可以从 Jison 的语法规则中更改词汇状态(又名“开始条件”)?

我正在解析一种计算机语言,当满足某些语法规则时,词汇状态会明显改变(至少对我的人类思维方式而言),即使在词法分析器中没有我可以准确指出的标记。

(我认为这是因为某些关键字在一种状态下是保留/可保留的,而在另一种状态下则不是。)

绝对可以从词法分析器中更改词法状态,例如:

%lex
%x expression

%% 
{id}               {  return 'ID';
"="                { this.begin('expression'); return '='; }
<expression>";"    { this.popState(); return ';'; }

但是当某些语法规则匹配时,有没有办法改变词法状态?

%% /* language grammar */

something :  pattern1 pattern2 { this.beginState('expression'); $$ = [$1,$2]; };
pattern1 : some stuff { $$ = [$1, $2]; }
pattern2 : other stuff { $$ = [$1, $2]; }

如果我试试这个,我得到

TypeError: this.popState is not a function
      at Object.anonymous (eval at createParser (/Users/me/Exp/stats/node_modules/jison/lib/jison.js:1327:23), <anonymous>:47:67)
      at Object.parse (eval at createParser (/Users/me/Exp/stats/node_modules/jison/lib/jison.js:1327:23), <anonymous>:329:36)

我不确定我所要求的在理论上是不可能的还是在概念上是幼稚的(例如,这是 的真正含义context free grammar吗?),或者它就在那里,我只是没有正确阅读文档。

4

1 回答 1

5

词法分析器对象在解析器操作中可用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(); } ;

在这里,我们使用空的非终结hexunhex来改变词法分析器的状态。(在 bison 中,我会使用中间规则动作,这非常相似,但 jison 似乎没有实现它们。)关键是状态更改在[]标记之前完成,不受状态变化。因此,状态更改发生在当前前瞻令牌之前还是之后都无关紧要,因为我们不需要它在第二个下一个令牌之前生效,这可能是一个数字。

26给定输入,此语法将正确输出[10+a]。如果我们将hex标记非终结符移动到括号内:

      /* NOT CORRECT */
    | '[' hex e unhex ']'       {$$ = $3;}

然后开始条件更改发生在前瞻标记之后,因此[10+a]产生20.

于 2015-10-20T06:16:57.353 回答