Jison 解析器在第一条规则后停止

Jison parser stops after first rule

本文关键字:规则 一条 Jison      更新时间:2023-09-26

我有一个简单的文件格式,我想用jison解析器生成器解析。此文件可以包含任意顺序和数量的多个表达式。这是解析器的 jison 文件:

/* lexical grammar */
%lex
%%
's+                   /* skip whitespace */
'"(''.|[^"])*'"          return 'STRING'
File's*Version's*':      return 'FILEVERSION'
[0-9]+("."[0-9]+)?'b     return 'NUMBER'
<<EOF>>                  return 'EOF'
.                        return 'INVALID'
/lex
%start expressions
%% /* language grammar */
expressions
    : EOF
    | e expressions EOF
    ;
e
    : STRING
    | FILEID 
    ;
FILEID
    : FILEVERSION NUMBER { return $1 + $2; }
    ;

为简单起见,我缩短了文件,使其仅包含字符串和文件 id 表达式。

我的问题是,如果第二个表达式仅包含一个像字符串这样的标记,则生成的解析器似乎只能识别一个或两个完整的表达式。例如:

文件版本: 1.0

将被解析,或

文件版本: 1.0 《我的弦》

也将被解析,但对于

文件版本: 1.0 《我的弦》 "未解析的字符串">

不会分析最后一个字符串。

我已经使用 jison 调试器和 jison 页面本身尝试了这段代码,但两个页面都显示相同的结果。

我对这个问题的建议是:

  1. 一些词法分析器错误(正则表达式(
  2. 一些语法错误(左右递归(
  3. 解析器中缺少某些操作(有点 { $$ = $1;} (
  4. 我缺少的其他一些野牛/吉森魔法

我不是那个ebnf解析器大师,所以请保持你的答案尽可能简单。

眼前的问题是你从FILEID生产中returnreturn返回,因此解析以返回值终止。通常,语义规则应通过分配给变量 $$ 来提供其结果。(对于右侧只有一个符号的"单元规则",这不是必需的;在执行操作之前,解析器会$$ = $1,所以如果这是你想要的,你可以像在两个FILEID规则中所做的那样省略操作。

此外,您的expressions生产不会对$2执行任何操作,因此即使您修复了第一个问题,您仍然只会在结果中看到一个e

您的expressions生产也是不正确的,因为它需要 每个e一个EOF令牌 ,除了基本情况下的 EOF。考虑作品是如何工作的:

expressions -> e expressions EOF
            -> e e expressions EOF EOF
            -> e e e expressions EOF EOF EOF
            -> e e e EOF EOF EOF EOF

就个人而言,我建议使用左递归而不是右递归。像 jison 这样的自下而上的解析器更喜欢左递归,它通常会导致更自然的语义规则,就像在这种情况下一样。

最后,您需要在实际到达输入末尾时返回最终值。在 jison 中,这通常需要一个明确的开始规则,其语义操作是return

因此,考虑到所有这些,让我们尝试一下:(我更改了一些非终端的名称,并FILEID小写,因为通常对非终端使用小写,对终端使用大写(

%start prog
%%
prog   : exprs EOF          { return $1; }
       ;
exprs  :                    { $$ = []; }
       | exprs expr         { $$.push($2); }
       ;
expr   : file_id
       | STRING
       ;
file_id: FILEVERSION NUMBER { $$ = $1 + $2; }
       ;

关于匹配字符串的正则表达式的一个注意事项:

'"(''.|[^"])*'"          return 'STRING'

虽然它显然可以在javascript中工作(主要是;见下文(,但它会在flex(或与Posix兼容的正则表达式库(中表现出错误。它主要在javascript中工作,因为javascript正则表达式交替运算符|有序的;如果第一个备选方案匹配,则除非模式的其余部分不匹配,否则永远不会尝试第二个备选方案(在这种情况下,将触发错误(。

但在 (f(lex 中,交替运算符会注意到所有匹配的备选方案,并最终选择最长的可能匹配项。结果是,当匹配"''"..."时,flex 将匹配令牌直到第三个报价,方法是使用 [^"] 匹配第一个 '',然后''.匹配 ''"。这让它继续寻找结束报价。

编写正则表达式很容易,以便它可以与任一语义一起使用,我强烈建议您在想要迁移到不同的解析器生成器时这样做,只需确保 '' 不匹配[^"]

'"(''.|[^''"])*'"        return 'STRING'

此更改还将修复一个微妙的错误,即使在 javascript 中,如果"'"是输入中的最后一个字符串,则将其视为有效的字符串标记。在这种情况下,javascript 将首先使用 ''. 来匹配 ''">,但是一旦这样做,它将找不到任何结束引号。然后它将回溯并尝试与 [^"] 匹配,这将匹配 '',允许报价被识别为结束报价。