我正在写Antlr/Xtext parser for coffeescript grammar。它刚刚开始,我只是移动了original grammar的一个子集,我被表达式所困扰。这是可怕的“规则表达式有非LL(*)决定”错误。我在这里找到了一些相关问题,Help with left factoring a grammar to remove left recursion和ANTLR Grammar for expressions。我也试过How to remove global backtracking from your grammar,但它只是演示了一个我在现实生活中无法使用的非常简单的案例。关于ANTLR Grammar Tip: LL() and Left Factoring的帖子给了我更多的见解,但我仍然无法掌握。
我的问题是如何修复以下语法(抱歉,我无法简化它并仍然保留错误)。我猜麻烦制造者是term
规则,所以我很欣赏本地修复它,而不是改变整个事情(我试图接近原始语法的规则)。我们也欢迎指针提示如何“调试”这种错误的语法。
grammar CoffeeScript;
options {
output=AST;
}
tokens {
AT_SIGIL; BOOL; BOUND_FUNC_ARROW; BY; CALL_END; CALL_START; CATCH; CLASS; COLON; COLON_SLASH; COMMA; COMPARE; COMPOUND_ASSIGN; DOT; DOT_DOT; DOUBLE_COLON; ELLIPSIS; ELSE; EQUAL; EXTENDS; FINALLY; FOR; FORIN; FOROF; FUNC_ARROW; FUNC_EXIST; HERECOMMENT; IDENTIFIER; IF; INDENT; INDEX_END; INDEX_PROTO; INDEX_SOAK; INDEX_START; JS; LBRACKET; LCURLY; LEADING_WHEN; LOGIC; LOOP; LPAREN; MATH; MINUS; MINUS; MINUS_MINUS; NEW; NUMBER; OUTDENT; OWN; PARAM_END; PARAM_START; PLUS; PLUS_PLUS; POST_IF; QUESTION; QUESTION_DOT; RBRACKET; RCURLY; REGEX; RELATION; RETURN; RPAREN; SHIFT; STATEMENT; STRING; SUPER; SWITCH; TERMINATOR; THEN; THIS; THROW; TRY; UNARY; UNTIL; WHEN; WHILE;
}
COMPARE : '<' | '==' | '>';
COMPOUND_ASSIGN : '+=' | '-=';
EQUAL : '=';
LOGIC : '&&' | '||';
LPAREN : '(';
MATH : '*' | '/';
MINUS : '-';
MINUS_MINUS : '--';
NEW : 'new';
NUMBER : ('0'..'9')+;
PLUS : '+';
PLUS_PLUS : '++';
QUESTION : '?';
RELATION : 'in' | 'of' | 'instanceof';
RPAREN : ')';
SHIFT : '<<' | '>>';
STRING : '"' (('a'..'z') | ' ')* '"';
TERMINATOR : '\n';
UNARY : '!' | '~' | NEW;
// Put it at the end, so keywords will be matched earlier
IDENTIFIER : ('a'..'z' | 'A'..'Z')+;
WS : (' ')+ {skip();} ;
root
: body
;
body
: line
;
line
: expression
;
assign
: assignable EQUAL expression
;
expression
: value
| assign
| operation
;
identifier
: IDENTIFIER
;
simpleAssignable
: identifier
;
assignable
: simpleAssignable
;
value
: assignable
| literal
| parenthetical
;
literal
: alphaNumeric
;
alphaNumeric
: NUMBER
| STRING;
parenthetical
: LPAREN body RPAREN
;
// term should be the same as expression except operation to avoid left-recursion
term
: value
| assign
;
questionOp
: term QUESTION?
;
mathOp
: questionOp (MATH questionOp)*
;
additiveOp
: mathOp ((PLUS | MINUS) mathOp)*
;
shiftOp
: additiveOp (SHIFT additiveOp)*
;
relationOp
: shiftOp (RELATION shiftOp)*
;
compareOp
: relationOp (COMPARE relationOp)*
;
logicOp
: compareOp (LOGIC compareOp)*
;
operation
: UNARY expression
| MINUS expression
| PLUS expression
| MINUS_MINUS simpleAssignable
| PLUS_PLUS simpleAssignable
| simpleAssignable PLUS_PLUS
| simpleAssignable MINUS_MINUS
| simpleAssignable COMPOUND_ASSIGN expression
| logicOp
;
更新: 最终解决方案将使用带有外部词法分析器的Xtext来避免intricacies of handling significant whitespace。这是我的Xtext版本的片段:
CompareOp returns Operation:
AdditiveOp ({CompareOp.left=current} operator=COMPARE right=AdditiveOp)*;
我的策略是在没有可用AST的情况下首先创建一个有效的Antlr解析器。 (如果这是一个可行的方法,它应该得到一个单独的问题。)所以我现在不关心令牌,它们被包含在内以使开发更容易。
我知道原始语法是LR。我不知道在转换为LL时我能保持多久。
UPDATE2和解决方案:
我可以用Bart的答案中获得的见解来简化我的问题。这是一个有效的玩具语法来处理带有函数调用的简单表达式来说明它。 expression
之前的评论显示了我的洞察力。
grammar FunExp;
ID: ('a'..'z'|'A'..'Z'|'_') ('a'..'z'|'A'..'Z'|'0'..'9'|'_')*;
NUMBER: '0'..'9'+;
WS: (' ')+ {skip();};
root
: expression
;
// atom and functionCall would go here,
// but they are reachable via operation -> term
// so they are omitted here
expression
: operation
;
atom
: NUMBER
| ID
;
functionCall
: ID '(' expression (',' expression)* ')'
;
operation
: multiOp
;
multiOp
: additiveOp (('*' | '/') additiveOp)*
;
additiveOp
: term (('+' | '-') term)*
;
term
: atom
| functionCall
| '(' expression ')'
;
答案 0 :(得分:4)
当您从语法中生成词法分析器和解析器时,您会在控制台上看到以下错误:
错误(211):CoffeeScript.g:52:3:[致命] 规则表达式 具有非LL(*)决策,因为递归规则调用可以从alts 1,3。通过左因子分解或使用句法谓词或使用backtrack = true选项来解析。
警告(200):CoffeeScript.g:52:3: 决策可以使用多种替代方式匹配输入,例如“{NUMBER,STRING}”:1,3
因此,该输入 备选方案3被禁用
(我强调了重点)
这只是第一个错误,但是你从第一个错误开始,运气不错,当你修复第一个错误时,第一个错误也会消失。
上面发布的错误意味着当您尝试使用语法生成的解析器解析NUMBER
或STRING
时,解析器最终可以通过两种方式解析expression
规则:
expression : value // choice 1 | assign // choice 2 | operation // choice 3 ;
即,选择1和选择3都可以解析NUMBER
或STRING
,您可以通过解析器可以遵循的“路径”来查看这两个选项:
expression value literal alphaNumeric : {NUMBER, STRING}
expression operation logicOp relationOp shiftOp additiveOp mathOp questionOp term value literal alphaNumeric : {NUMBER, STRING}
在警告的最后部分,ANTLR通知您,无论何时解析NUMBER
或STRING
,它都会忽略选择3,从而导致选择1匹配此类输入(因为它已定义)在选择之前3)。
因此,CoffeeScript语法在这方面是不明确的(并以某种方式处理这种歧义),或者你的实现是错误的(我猜测后者:))。你需要在你的语法中解决这种歧义:即不要让expression
的选择1和3都匹配相同的输入。
我注意到你的语法还有其他三件事:
采用以下词法分析器规则:
NEW : 'new'; ... UNARY : '!' | '~' | NEW;
请注意,令牌UNARY
永远不会与文本'new'
匹配,因为在其之前定义了令牌NEW
。如果您想让UNARY
macth,请删除NEW
规则并执行以下操作:
UNARY : '!' | '~' | 'new';
在某些情况下,您可以在一个徽标中收集多种类型的令牌,例如LOGIC
:
LOGIC : '&&' | '||';
然后在这样的解析器规则中使用该标记:
logicOp : compareOp (LOGIC compareOp)* ;
但如果您要在稍后阶段评估此类表达式,则您不知道这个LOGIC
令牌匹配的内容('&&'
或'||'
)并且您将会必须检查令牌的内部文本才能找到它。你最好做这样的事情(至少,如果你在后期进行某种评估):
AND : '&&'; OR : '||'; ... logicOp : compareOp ( AND compareOp // easier to evaluate, you know it's an AND expression | OR compareOp // easier to evaluate, you know it's an OR expression )* ;
您正在使用以下内容跳过空格(并且没有标签?)
WS : (' ')+ {skip();} ;
但是,CoffeeScript是否像Python一样用空格(和制表符)缩进它的代码块?但也许你会在以后的阶段做到这一点?
我刚看到the grammar you're looking at是一个jison语法(在JavaScript中或多或少是一个bison实现)。但野牛和其他jison产生LR parsers,而ANTLR产生LL parsers。因此,试图接近原始语法的规则只会导致更多问题。