0

我有一个简单的文件格式,我想用 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-parser-guru,所以请让你的答案尽可能简单。

4

1 回答 1

3

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

此外,您的expressions产品与 没有任何关系$2,因此即使您解决了第一个问题,您仍然只会e在结果中看到一个问题。

您的expressions生产也是不正确的,因为除了基本案例中的 EOF 之外,EOF每个 都需要一个令牌。e考虑产品的工作原理:

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 将首先使用\\.to match \",但一旦这样做,它将找不到任何结束引号。然后它将回溯并尝试与 匹配[^"],这将匹配\,然后将引用识别为结束引用。

于 2015-11-27T06:41:02.733 回答