- Java 双重比较
- java - 比较器与 Apache BeanComparator
- Objective-C 完成 block 导致额外的方法调用?
- database - RESTful URI 是否应该公开数据库主键?
我有一个相对简单的 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 项目的一小部分,所以不仅仅是我,我团队中的任何人都必须审查或维护我的代码。
(我并不是要显得无礼:我愿意投入时间和精力来使用工具,但前提是该工具成为有用的工具并且不会继续成为绊脚石。)
最佳答案
Jason S wrote:
Is there a way to do this?
是的。
首先定义你的语法(我以你的表达式解析器为例,只有 +
和 *
和 ()
运算符):
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
根,并排除了括号。
现在从语法生成词法分析器和解析器:
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
如您所见,parse
规则(方法)返回 CommonTree
您可以用来创建自己的 walker/visitor 的对象,按原样保留语法。
HTH
关于java - ANTLR(或替代): decoupling parsing from evaluation,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/3909957/
让我们考虑以下 ANTLR4 语法(最小示例): grammar Foo; expr : a? b? c? ; 我如何指定 a、b 或 c 中的至少一个 必须出现在表达式? 基本上我正在寻
我为字符串变量声明写了下面的语法。字符串的定义类似于单引号之间的任何内容,但必须有一种方法可以通过使用 $ 字母转义将单引号添加到字符串值。 grammar test; options {
我最近创建了一个 ANTLR3 解析器规则 options : foo bar; 它没有编译,我花了一些时间才发现 options是一个保留字(AntlrWorks 指出了错误,但没有指出原因)。 A
我正在从“The Definitive Antlr reference”一书中学习 Antlr。我还处于起步阶段。我喜欢动手做东西,所以我认为做一个好的示例项目会是一个很好的学习经验。 我正在寻找一个
我们有一个为 antlr V2 编写的语法,我想迁移到 antlr v4。有迁移指南吗?我还想知道对现有 V2 语法的修改,以便我们更好地利用 v4 功能。 最佳答案 我通过编写一个新的 Antlr
介绍 查看文档,ANTLR 2 曾经有一个叫做 predicated lexing 的东西。 ,有这样的例子(受 Pascal 启发): RANGE_OR_INT : ( INT ".."
我已经开始学习 ANTLR,并且拥有 2007 年的书《The Definitive ANTLR Reference》和 ANTLRWorks(用于创建语法的交互式工具)。而且,作为这样的人,我从第三
我正在开发 D 语言的解析器,当我尝试添加“切片”运算符规则时遇到了麻烦。你可以找到它的ANTLR语法here 。基本上问题是,如果词法分析器遇到这样的字符串:“1..2”,它就会完全丢失,并且最终成
在 ANTLR 语法中,我们如何区分变量名和标识符? VAR: ('A'..'Z')+ DIGIT* ; IDENT : ('a'..'z'|'A'..'Z'|'_') ('a'..'z'|'
我想在 ANTLR 语法中解析 ISO 8601 日期。 2001-05-03 我的语法文件中有以下条目: date : FOUR_DIGIT ('-')? TWO_DIGIT ('-')? T
我们有一个为 antlr V3 编写的语法,我想迁移到 antlr v4。有没有迁移指南。我还想知道对现有 V3 语法的修改,以便我们很好地利用 v4 的功能。 最佳答案 如果您在 v3 或更早版本中
我正在尝试在一台新计算机上使用 ANTLR v4 语法插件在 IntelliJ 中运行一个简单的语法文件。我已经按照在线步骤在 IntelliJ 中安装插件,插件看起来安装正确。我在 .g4 语法文件
我正在使用 ANTLR 为旧的专有报告规范编写一个解析器,目前我正在尝试实现生成的解析树的访问者,以扩展自动生成的抽象访问类。 我对 ANTLR(我最近才学会)和一般的访问者模式都没有什么经验,但是如
我知道插入符号后缀在 antlr 中的含义(即 make root)但是当插入符号是我一直在阅读的以下语法中的前缀时呢(该语法是全新的,由学习 antlr 的新团队完成)。 .. selectClau
我不知道这个问题是否有效,因为我对源代码解析不是很熟悉。我的目标是为一种现有的编程语言(语言“X”)编写一个源代码完成函数,以供学习之用。 Antlr(v4) 是否适合这样的任务,还是应该手动完成必要
请查看源代码:https://gist.github.com/1684022 . 我定义了两个 token : ID : ('a'..'z' | 'A'..'Z') ('0'..'9' | 'a
我知道“+”、“?”和 '*'。但是,如果我希望某事重复 5 次,该怎么办?例如,如果标识符必须是长度为 5 的十六进制数字符串? 更具体地说,我正在考虑定义一个无限长度的通用词法分析器规则,然后在解
如何控制切换Antlr来自解析器操作的词法分析器模式? 我扩展了生成的 Parser 和 Lexer,所以我可以调用 pushMode和 popMode从解析器女巫依次调用合适的pushMode和 p
我正在使用 ANTLR 来标记一个简单的语法,并且需要区分一个 ID: ID : LETTER (LETTER | DIGIT)* ; fragment DIGIT : '
我有一个这样的 ANTLR 规则 receive returns[Evaluator e,String message] : RECEIVE FILENAME {$e= new ReceiveEv
我是一名优秀的程序员,十分优秀!