我最近发现了项目jison,并从其网站上修改了计算器示例。(http://zaach.github.io/jison/demos/calc/)
/* lexical grammar */
%lex
%%
"a" return 'TOKEN1'
"b" return 'TOKEN2'
<<EOF>> return 'EOF'
. return 'INVALID'
/lex
%start letters
%% /* language grammar */
letters
:
| letters letter
;
letter
: 'TOKEN1'
| 'TOKEN2'
;
使用由上述语法定义生成的解析器解析字符串"aaabbbababa"会导致
Parse error on line 1:
aaabbbaaba
^
Expecting 'TOKEN1', 'TOKEN2', got 'INVALID'
不幸的是,我不知道为什么TOKEN1
没有被正确找到。删除了令牌INVALID后,我得到了解析错误
Unrecognized text.
我在Issue with a Jison Grammar,Strange error from generate dparser中发现了一个关联错误的描述,导致了类似的错误消息,但我在代码中找不到类似的东西。
这个问题的解决方案是什么?
好问题。
jison
的lexer生成器有两种模式:默认模式和稍微兼容flex
的模式。通过将%options flex
放置在%lex
行之后来选择后者。
在默认模式下:
-
第一个匹配模式获胜,即使后面的模式将匹配更长的令牌;和
-
以字母或数字结尾的模式会添加一个隐含的
b
,这将匹配限制在单词边界上。
在flex
模式中,模式不会改变,并且应用正常的flex第一最长规则。然而,生成的lexer会更慢(见下文)。
因此,在lexer定义中,"a"
将与输入字符串中的第一个a不匹配,因为生成的lexer实际上是在尝试匹配ab
——也就是说,一个a后面跟着一个单词边界。
你可以通过简单地用括号包围模式来解决这个问题:
("a") { return 'TOKEN1'; }
或者使用字符类而不是引号:
[a] { return 'TOKEN1'; }
或者将%options flex
添加到您的%lex
部分。
作为一种解释
与flex
不同,jison
不构建单个DFA lexer。相反,它将每个lex模式转换为一个锚定的javascript正则表达式,在每次请求令牌时,它都会尝试所有模式,直到找到正确的匹配。
为了实现flex
第一个最长匹配规则,jison
生成的lexer需要为每个令牌尝试每个正则表达式,因为在尝试所有正则表达式之前,它无法知道哪一个是最长匹配。"第一个匹配"规则可能会快得多,特别是如果将常见的令牌模式放在文件开头附近。
不幸的是,在令牌可能是关键字或标识符的常见情况下,第一个匹配规则要难得多,而碰巧以关键字开头的标识符需要作为标识符进行匹配。对于第一个最长匹配,将关键字放在第一位就足够了,因为带有关键字前缀的标识符会更长。但对于第一个匹配,有必要限制关键字或标识符或两者都以单词边界结束。
因此,上述两个规则的组合意味着在Identifier
模式之前列出关键字的正常模式仍然有效。单词边界测试可以防止关键字模式出现虚假的前缀匹配。
但是,如果你有很多关键词,那仍然是很多模式,即使大多数都会很快失败。因此,与其使用flex
约定:
"do" { return DO; }
"end" { return END; }
/* ... */
[[:alpha:]][[:alnum:]_]* { return "ID"; }
你最好让关键字表示它们自己(以及其他固定的标记,如运算符),因为这样可以将所有关键字和多字符运算符模式组合成一个正则表达式:
/* Keywords and multicharacter operators in a single enormous pattern */
/* For jison mode, I added a manual b because it won't be added
* automatically. In flex mode, that won't hurt, but it could be
* removed.
*/
("do"|"else"|"end"|"if"|"then"|"while")b|[<>!=]"=" { return yytext; }
[[:alpha:]][[:alnum:]_]* { return "ID"; }
[[:digit:]]+("."[[:digit:]]*)? { return "NUMBER"; }
[[:space:]]+ ;
/* All single character tokens use a fallback rule */
. { return yytext; }
<<EOF>>
规则
许多jison
语法都有一个显式的<<EOF>>
词法规则,它返回一些像"EOF"
或"END_OF_FILE"
这样的标记。该令牌由显式增强的开始生产识别,该生产在其操作中具有return
:
%lex
%%
// ...
<<EOF>> { return "EOF"; }
/lex
%start input
%%
input: start EOF { return $1; }
start: /* The real start token */
这是一个特定于jison
的习语,许多人会认为它在flex/bison
中的风格很差。它允许生成的语法返回一个实际值,该值是解析的结果。
不要只是在词法规则中添加<<EOF>>
规则。如果您提供自己的EOF
令牌,那么您有责任在解析器中识别它。如果解析器没有与EOF
令牌匹配的规则,则解析将失败。