我最近开始使用ANTLR。我目前正在尝试使用+
,-
,*
和array[index]
以及一些其他构造来编码表达式语法。
这是所需的语法:
Exp -> Exp (+ | - | * | < | &&) Exp
| Exp [ Exp ]
| -Exp
| ( Exp )
| Exp.length
| true
| false
| Id
| this
| ! Exp
我首先将其重构为AndExp
,SumExp
,ProdExp
等等以解决优先级问题。大概是这样的:
Exp -> AndExp
AndExp -> CmpExp (&& CmpExp)*
CmpExp -> SumExp (< SumExp)*
SumExp -> ProdExp ((+|-) ProdExp)*
ProdExp -> UnaryExp (Times UnaryExp)*
UnaryExp -> Minus* PrimaryExp
PrimaryExp -> Exp.length
| Exp [ Exp ]
| ! Exp
| true
| false
| Id
| this
然后我意识到这使用左递归,而ANTLR不喜欢这样。我继续eliminate the left-recursion,我最终得到了这个语法:
grammar test;
options {
language=Java;
output=AST;
backtrack=true;
}
start : expression;
expression : andExp;
andExp : cmpExp (And^ cmpExp)*;
cmpExp : sumExp (LessThan^ sumExp)*;
sumExp : prodExp ((Plus | Minus)^ prodExp)*;
prodExp : unaryExp (Times^ unaryExp)*;
unaryExp : Minus* primaryExp;
primaryExp : INT primaryPrime
| 'true' primaryPrime
| 'false' primaryPrime
| 'this' primaryPrime
| ID primaryPrime
| '!' expression primaryPrime
| '('! expression ')'! primaryPrime
;
primaryPrime
: '[' expression ']' primaryPrime
| '.' ID '(' exprList ')' primaryPrime
| '.length' primaryPrime
| 'new' 'int' '[' expression ']' primaryPrime
| 'new' ID '(' ')' primaryPrime
|
;
exprList :
| expression (',' expression)*;
LessThan : '<';
Plus : '+';
Minus : '-';
Times : '*';
And : '&&';
Not : '!';
INT : '0' | ('1'..'9')('0'..'9')*;
ID : ('a'..'z'|'A'..'Z'|'_') ('a'..'z'|'A'..'Z'|'0'..'9'|'_')*;
WS : ('\t' | ' ' | '\r' | '\n'| '\u000C') { $channel=HIDDEN; } ;
Q1:这种语法的回溯是“必需的”(除非我激活它,否则无法通过ANTLR获取)或者我是否遗漏了一些简单的东西?
Q2:当添加一些^
和-> ^(TOKEN ...)
构造来刷新树时,我遇到了以下烦人的情况(因为{{ 1}}这是由于左分解):
primaryPrime
这会将primaryPrime
: '[' expression ']' primaryPrime -> ^(ARRAY_EXPR expression)
//...
变为
array[index]
虽然我真的想要
array
ARRAY_EXPR
index
解决此问题的最佳方法是什么?我在这里走在正确的轨道上,还是我应该采取其他方法?
答案 0 :(得分:4)
不,还不需要回溯。但是如果你确实需要一些回溯,建议不要立即使用backtrack=true
,而是在需要回溯的规则之前使用谓词。通过使用backtrack=true
,您可以启用所有规则的回溯,而可能只有一两个需要回溯。但是,如果您的语言相对较小,backtrack=true
比手动混合谓词更容易,并且可能不会对性能产生很大影响。但如果你可以避免它们,那就这样做。
您有几个匹配空字符串的解析器规则,这些规则会导致问题。您通常最好让规则匹配某些内容,并使规则可选。所以而不是:
foo : bar baz ;
bar : 'bar' ;
baz : 'baz' | /* epsilon */ ;
DO
foo : bar baz? ;
bar : 'bar' ;
baz : 'baz' ;
代替。
此外,如果保留关键字如true
,false
等,请不要在解析器规则中混用它们:始终在词法分析器规则的顶部明确定义它们。 Lexer规则从上到下进行匹配,因此最安全的方法是(至少)在像ID
这样的规则之前定义它们。我通常将它们作为第一个词法规则。
你可以通过将参数传递给你的解析器规则来做到这一点,尽管这会使你的语法(有点)可读性降低。
你的语法和我的评论:
grammar test;
options {
output=AST;
}
tokens {
ARRAY_EXPR;
}
start : expression;
expression : andExp;
andExp : cmpExp (And^ cmpExp)*;
cmpExp : sumExp (LessThan^ sumExp)*;
sumExp : prodExp ((Plus | Minus)^ prodExp)*;
prodExp : unaryExp (Times^ unaryExp)*;
unaryExp : '-' primaryExp
| '!' primaryExp // negation is really a `unaryExp`
| primaryExp
;
primaryExp : INT primaryPrime[null]?
| 'true' primaryPrime[null]?
| 'false' primaryPrime[null]?
| 'this' primaryPrime[null]?
| (ID -> ID) (primaryPrime[new CommonTree($ID)] -> primaryPrime)?
| '('! expression ')'! primaryPrime[null]?
;
// removed the matching of 'epsilon'
primaryPrime [CommonTree parent]
: '[' expression ']' primaryPrime[null]? -> ^(ARRAY_EXPR {parent} expression primaryPrime?)
| '.' ID '(' exprList? ')' primaryPrime[null]?
| '.length' primaryPrime[null]?
| 'new' 'int' '[' expression ']' primaryPrime[null]?
| 'new' ID '(' ')' primaryPrime[null]?
;
// removed the matching of 'epsilon'
exprList : expression (',' expression)*;
// be sure to create explicit tokens for keywords!
True : 'true';
False : 'false';
This : 'this';
LessThan : '<';
Plus : '+';
Minus : '-';
Times : '*';
And : '&&';
Not : '!';
INT : '0' | ('1'..'9')('0'..'9')*;
ID : ('a'..'z'|'A'..'Z'|'_') ('a'..'z'|'A'..'Z'|'0'..'9'|'_')*;
WS : ('\t' | ' ' | '\r' | '\n'| '\u000C') { $channel=HIDDEN; } ;
将输入"array[2*3]"
解析为以下AST:
通过运行以下测试类可以看到
:
import org.antlr.runtime.*;
import org.antlr.runtime.tree.*;
import org.antlr.stringtemplate.*;
public class Main {
public static void main(String[] args) throws Exception {
String source = "array[2*3]";
testLexer lexer = new testLexer(new ANTLRStringStream(source));
CommonTokenStream tokens = new CommonTokenStream(lexer);
testParser parser = new testParser(tokens);
testParser.start_return returnValue = parser.start();
CommonTree tree = (CommonTree)returnValue.getTree();
DOTTreeGenerator gen = new DOTTreeGenerator();
StringTemplate st = gen.toDOT(tree);
System.out.println(st);
}
}