我有一个相对简单的DSL,我想比一堆手动编码的java.util.regex.Pattern
语句+解析逻辑更强大地处理。
最引用的工具似乎是ANTLR。我不熟悉它,我愿意尝试一下。但是,当我查看示例时(例如ANTLR expression evaluator example或Martin Fowler的HelloAntlr或this other Q on stackoverflow),我有点怀疑。这样做的原因是语法文件似乎是一个语法定义的大杂烩,其中散布着实施语言(例如Java)的片段,这些片段本质上是必不可少的。
我真正喜欢的是分离出解析器的命令/评估部分。有没有办法使用ANTLR(或其他工具)来定义语法&生成一组Java源文件,以便它可以编译成类,我可以使用它来将输入解析为不依赖于该结构的结构?
例如,如果我只想使用+
和*
以及()
运算符进行表达式评估,并且我有输入
3 *(4 + 7 * 6)*(3 + 7 *(4 + 2))
那么我想要做的就是编写一个语法来将其转换为像
这样的层次结构Product
Term(3)
Sum
Term(4)
Product
Term(7)
Term(6)
Sum
Term(3)
Product
Term(7)
Sum
Term(4)
Term(2)
我可以使用像
这样的类interface Expression<T> {
public T evaluate();
}
class Term implements Expression<Double> {
final private double value;
@Override public Double evaluate() { return value; }
}
class Product implements Expression<Double> {
final private List<Expression<Double>> terms;
@Override public Double evaluate() {
double result = 1;
for (Expression<Double> ex : terms)
result *= ex.evaluate();
return result;
}
}
class Sum implements Expression<Double> {
final private List<Expression<Double>> terms;
@Override public Double evaluate() {
double result = 0;
for (Expression<Double> ex : terms)
result += ex.evaluate();
return result;
}
}
并使用ANTLR构造结构。有没有办法做到这一点?我真的更愿意采用这种方法,因为它让我(和其他软件工程师)编辑和可视化完整的Java类,而不必将这些类碎片化为ANTLR语法文件中的奇怪部分。
有办法做到这一点吗?
澄清:我希望以两种方式尽可能多地花费我的努力:定义语法本身,以及独立于ANTLR的Java(例如我的Product / Sum / Term类)。我想尽量减少花在学习ANTLR语法,怪癖和API上的时间/经验。我不知道如何从ANTLR语法创建和操作AST。因为这只是大型Java项目的一小部分,所以我的团队中的任何人都不得不审查或维护我的代码。
(我并不是说听起来很不礼貌:我愿意投入时间和精力来使用工具,但前提是该工具成为一种有用的工具并且不会继续成为绊脚石。)
答案 0 :(得分:11)
Jason S写道:
有办法做到这一点吗?
是
首先定义你的语法(我只用了+
和*
以及()
运算符来表达一个表达式解析器的例子:
grammar Exp;
// parser rules
parse
: additionExp
;
additionExp
: multiplyExp (Add multiplyExp)*
;
multiplyExp
: atomExp (Mult atomExp)*
;
atomExp
: Number
| LParen additionExp RParen
;
// lexer rules
Add : '+' ;
Mult : '*' ;
LParen : '(' ;
RParen : ')' ;
Number : ('0'..'9')+ ('.' ('0'..'9')+)? ;
Spaces : (' ' | '\t' | '\r'| '\n') {$channel=HIDDEN;} ;
如果你想让ANTLR从上面的语法中生成一个合适的AST,你必须将以下内容置于语法的顶部(在语法声明下):
options {
output=AST;
}
并且您必须指出每个解析规则的根应该是什么。这可以通过两种方式完成:
^
和!
:
^
表示:将此令牌设为根; !
表示:从AST 中排除此令牌。现在你的语法看起来像这样:
grammar Exp;
options {
output=AST;
}
// parser rules
parse
: additionExp
;
additionExp
: multiplyExp (Add^ multiplyExp)*
;
multiplyExp
: atomExp (Mult^ atomExp)*
;
atomExp
: Number
| LParen! additionExp RParen!
;
// lexer rules
Add : '+' ;
Mult : '*' ;
LParen : '(' ;
RParen : ')' ;
Number : ('0'..'9')+ ('.' ('0'..'9')+)? ;
Spaces : (' ' | '\t' | '\r'| '\n') {$channel=HIDDEN;} ;
如您所见,我创建了Add
和Mult
根,并排除了括号。
现在生成词法分析器&amp;解析器来自语法:
java -cp antlr-3.2.jar org.antlr.Tool Exp.g
创建一个小测试工具:
import org.antlr.runtime.*;
import org.antlr.runtime.tree.*;
import java.util.*;
public class Main {
private static void preOrder(CommonTree tree, int depth) {
for(int i = 0; i < depth; i++) {
System.out.print("- ");
}
System.out.println("> "+tree + " :: " + ExpParser.tokenNames[tree.getType()]);
List children = tree.getChildren();
if(children == null) return;
for(Object o : children) {
preOrder((CommonTree)o, depth+1);
}
}
public static void main(String[] args) throws Exception {
ANTLRStringStream in = new ANTLRStringStream("3 * (4 + 7 * 6) * (3 + 7 * (4 + 2))");
ExpLexer lexer = new ExpLexer(in);
CommonTokenStream tokens = new CommonTokenStream(lexer);
ExpParser parser = new ExpParser(tokens);
CommonTree tree = (CommonTree)parser.parse().getTree();
preOrder(tree, 0);
}
}
编译所有内容:
javac -cp antlr-3.2.jar *.java
并运行Main
类:
// *nix/Mac OS
java -cp .:antlr-3.2.jar Main
// Windows
java -cp .;antlr-3.2.jar Main
产生以下内容:
> * :: Mult
- > * :: Mult
- - > 3 :: Number
- - > + :: Add
- - - > 4 :: Number
- - - > * :: Mult
- - - - > 7 :: Number
- - - - > 6 :: Number
- > + :: Add
- - > 3 :: Number
- - > * :: Mult
- - - > 7 :: Number
- - - > + :: Add
- - - - > 4 :: Number
- - - - > 2 :: Number
正如您所看到的, HTH parse
规则(方法)会返回一个CommonTree
对象,您可以使用该对象创建自己的助手/访问者,而 。< / p>
答案 1 :(得分:3)
如何使用ANTLR AST(抽象语法树)并通过访问每个树节点为您的类构建镜像树。
@Giuseppe Cardone在这里发布了一些很棒的链接:
http://www.antlr.org/article/1100569809276/use.tree.grammars.tml
http://www.antlr.org/article/1170602723163/treewalkers.html
可以在以下网址找到一个例子:
http://sagarsunkle.spaces.live.com/blog/cns!E07F3B561597E4EE!664.entry?sa=97619042
答案 2 :(得分:2)
为了简明起见,您提到的示例在语法中嵌入了解析器操作。这适用于小型项目。对于较大的那些,你更喜欢首先制作AST,然后随意做任何事情。你可以通过嵌入创建树的动作来做到这一点,但是antlr提供了一种更好的声明方式:
http://www.antlr.org/wiki/display/ANTLR3/Tree+construction
然后,您可以使用树语法生成代码,例如使用StringTemplate。 我已经将这个工具链用于我的论文,它就像一个魅力。但我敢打赌,如果没有Anlr3参考书(http://pragprog.com/titles/tpantlr/the-definitive-antlr-reference),我会遭受很多苦难
我还发现在antlr页面上链接的讲义非常有用: http://www.antlr.org/wiki/display/CS652/CS652+Home
另外,利用AntlrWorks来测试你的语法。还有一个语法单元测试套件。此外,antlr邮件列表非常活跃,Terence Parr积极响应大多数帖子。此外,这很有趣。