【问题标题】:Jison parser stops after first ruleJison 解析器在第一条规则后停止
【发布时间】:2015-11-27 05:52:25
【问题描述】:

我有一个简单的文件格式,我想用 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. 我还缺少其他一些野牛/吉森魔法

我不是那个 ebnf-parser-guru,所以请让你的答案尽可能简单。

【问题讨论】:

    标签: javascript regex parsing ebnf jison


    【解决方案1】:

    直接的问题是你return 来自FILEID 生产。 return 返回,因此解析以返回值终止。通常,语义规则应该通过分配给变量$$ 来提供它们的结果。 (对于右侧只有一个符号的“单元规则”而言,这不是必需的;在执行操作之前,解析器会执行 $$ = $1,所以如果这是您想要的,您可以将操作保留为你在你的两个FILEID 规则中做。)

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

    您的expressions 产生式也不正确,因为除了基本情况的EOF 之外,每个e 都需要一个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 正则表达式交替运算符 |ordered;如果第一个替代方案匹配,则永远不会尝试第二个替代方案,除非模式的其余部分无法匹配(在这种情况下,将触发错误)。

    但是在 (f)lex 中,交替操作符会注意所有匹配的备选方案,并最终选择可能最长的匹配项。结果是当匹配"\\"..."时,flex会匹配token直到第三个​​引用,通过使用[^"]匹配第一个\然后\\.到匹配 \"。这让它继续寻找结束引号。

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

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

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

    【讨论】:

    • 哇,非常感谢您提供如此清晰而全面的答案!
    猜你喜欢
    • 2012-07-16
    • 2015-08-09
    • 1970-01-01
    • 2022-01-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-10-04
    • 1970-01-01
    相关资源
    最近更新 更多