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

时间:2015-11-27 05:52:26

标签: javascript regex parsing ebnf jison

我有一个简单的文件格式,我想用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 debuggerjison page本身尝试了此代码,但两个页面都显示相同的结果。

我对这个问题的建议是:

  1. 一些词法错误(正则表达式)
  2. 一些语法错误(左右递归)
  3. 解析器中缺少某些操作(类似于{$$ = $ 1;})
  4. 我缺少的其他一些野牛/野牛魔法
  5. 我不是那个ebnf-parser-guru所以请尽量保持你的答案。

1 个答案:

答案 0 :(得分:2)

当前问题是return制作中的FILEIDreturn返回,因此解析以返回的值终止。通常,语义规则应通过分配变量$$来提供结果。 (对于“单位规则”,右边只有一个符号,这不是必需的;在执行操作之前,解析器执行$$ = $1,所以如果这是你想要的,你可以离开行动和你的两个FILEID规则一样。)

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

您的expressions制作也不正确,因为除了基本案例中的EOF之外,每EOFe令牌需要一个expressions -> e expressions EOF -> e e expressions EOF EOF -> e e e expressions EOF EOF EOF -> e e e EOF EOF EOF EOF 令牌。考虑一下制作如何运作:

return

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

最后,您需要在实际到达输入结束时返回最终值。在jison中,通常需要一个明确的启动规则,其语义操作为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正则表达式交替运算符"\\"..." ordered ;如果第一个替代匹配,则永远不会尝试第二个替代,除非模式的其余部分无法匹配(在这种情况下,将触发错误)。

但在(f)lex中,交替运营商会注意到所有匹配的替代方案,并最终选择最长的匹配。结果是匹配[^"]时,flex将匹配令牌,直到第三个​​引用,方法是使用\\.匹配第一个 \ 然后[^"]匹配 \ 。这样可以继续寻找收盘价。

编写正则表达式很容易,它可以使用任何语义,我强烈建议你这样做,以防你想要迁移到不同的解析器生成器,只需确保 \ \"(\\.|[^\\"])*\" return 'STRING' 不匹配:

"\"

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