antlr java
在我以前的文章中,有一段时间我写了关于使用Java的内部DSL的文章。 在Martin Fowler撰写的《 领域特定语言 》一书中,他讨论了另一种称为外部DSL的DSL,其中DSL是用另一种语言编写的,然后由宿主语言进行解析以填充语义模型。
在前面的示例中,我讨论了有关创建用于定义图形的DSL的问题。 使用外部dsl的好处是,图形数据中的任何更改都不需要重新编译程序,而是程序可以仅加载外部dsl,创建解析树然后填充语义模型。 语义模型将保持不变,并且使用语义模型的优点是无需更改语义模型就可以对DSL进行修改。 在内部DSL和外部DSL之间的示例中,我没有修改语义模型。 为了创建外部DSL,我使用了ANTLR 。
什么是ANTLR?
官方网站上给出的定义是:
ANTLR(另一种语言识别工具)是功能强大的解析器生成器,用于读取,处理,执行或翻译结构化文本或二进制文件。 它被广泛用于构建语言,工具和框架。 ANTLR通过语法生成可以构建和遍历语法树的语法分析器。
根据以上定义,ANTLR的显着特征是:
- 用于结构化文本或二进制文件的解析器生成器
- 可以建造和行走解析树
语义模型
在此示例中,我将利用ANTLR的上述功能来解析DSL,然后遍历解析树以填充语义模型。 概括地说,语义模型由Graph
, Edge
和Vertex
类组成,它们分别表示Graph和Graph的Edge和Vertex。 下面的代码显示了类定义:
public class Graph {
private List<Edge> edges;
private Set<Vertex> vertices;
public Graph() {
edges = new ArrayList<>();
vertices = new TreeSet<>();
}
public void addEdge(Edge edge){
getEdges().add(edge);
getVertices().add(edge.getFromVertex());
getVertices().add(edge.getToVertex());
}
public void addVertice(Vertex v){
getVertices().add(v);
}
public List<Edge> getEdges() {
return edges;
}
public Set<Vertex> getVertices() {
return vertices;
}
public static void printGraph(Graph g){
System.out.println("Vertices...");
for (Vertex v : g.getVertices()) {
System.out.print(v.getLabel() + " ");
}
System.out.println("");
System.out.println("Edges...");
for (Edge e : g.getEdges()) {
System.out.println(e);
}
}
}
public class Edge {
private Vertex fromVertex;
private Vertex toVertex;
private Double weight;
public Edge() {
}
public Edge(Vertex fromVertex,
Vertex toVertex,
Double weight) {
this.fromVertex = fromVertex;
this.toVertex = toVertex;
this.weight = weight;
}
@Override
public String toString() {
return fromVertex.getLabel() +
" to " + toVertex.getLabel() +
" with weight " + getWeight();
}
public Vertex getFromVertex() {
return fromVertex;
}
public void setFromVertex(Vertex fromVertex) {
this.fromVertex = fromVertex;
}
public Vertex getToVertex() {
return toVertex;
}
public void setToVertex(Vertex toVertex) {
this.toVertex = toVertex;
}
public Double getWeight() {
return weight;
}
public void setWeight(Double weight) {
this.weight = weight;
}
}
public class Vertex implements Comparable<Vertex> {
private String label;
public Vertex(String label) {
this.label = label.toUpperCase();
}
@Override
public int compareTo(Vertex o) {
return (this.getLabel().compareTo(o.getLabel()));
}
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
}
创建DSL
在创建语法规则之前,让我们先提出语言的结构。 我打算提出的结构是这样的:
Graph {
A -> B (10)
B -> C (20)
D -> E (30)
}
Graph块中的每条线代表一条边,并且该边所涉及的顶点以及括号中的值代表该边的权重。 我要强制执行的一个限制是,图不能具有悬空的顶点,即不属于任何边线的顶点。 可以通过稍微更改语法来消除此限制,但是我将其留给本帖子的读者练习。
创建DSL的首要任务是定义语法规则。 这些是词法分析器和解析器用来将DSL转换为抽象语法树 / 解析树的规则 。
然后,ANTLR使用此语法生成解析器,Lexer和侦听器,它们不过是Java类,用于扩展/实现ANTLR库中的某些类。 DSL的创建者必须利用这些Java类来加载外部DSL,对其进行解析,然后在解析器遇到某些节点时使用侦听器填充语义模型(将其视为XML的SAX解析器的变体)。
现在,我们已经非常简短地了解了ANTLR可以做什么以及使用ANTLR的步骤,我们将需要设置ANTLR,即下载ANTLR API jar并设置一些脚本来生成解析器和词法分析器,然后通过命令行尝试使用该语言。工具。 对于请访问这个从ANTLR官方教程,显示了如何设置ANTLR和一个简单的Hello World例子。
DSL语法
现在您已经设置了ANTLR,让我深入了解DSL的语法:
grammar Graph;
graph: 'Graph {' edge+ '}';
vertex: ID;
edge: vertex '->' vertex '(' NUM ')' ;
ID: [a-zA-Z]+;
NUM: [0-9]+;
WS: [ \t\r\n]+ -> skip;
让我们通过以下规则:
graph: 'Graph {' edge+ '}';
上面的语法规则(即开始规则)说,该语言应以“ Graph {”开头,以“}”结尾,并且必须至少包含一个边或多个边。
vertex: ID;
edge: vertex '->' vertex '(' NUM ')' ;
ID: [a-zA-Z]+;
NUM: [0-9]+;
以上四个规则说一个顶点至少应具有一个字符或多个字符。 边定义为两个顶点的集合,两个顶点之间用“->”分隔,并且在“()”中包含一些数字。
我将语法语言命名为“ Graph”,因此一旦使用ANTLR生成Java类(即解析器和词法分析器),我们最终将看到以下类:GraphParser,GraphLexer,GraphListener和GraphBaseListener。 前两个类处理解析树的生成,后两个类处理解析树的遍历。 GraphListener是一个接口,其中包含处理解析树的所有方法,即处理事件(例如,输入规则,退出规则,访问终端节点),此外,还包含用于处理与输入图相关的事件的方法规则,输入边缘规则并输入顶点规则。 我们将利用这些方法来拦截dsl中存在的数据,然后填充语义模型。
填充语义模型
我在资源包中创建了一个文件graph.gr,其中包含用于填充图形的DSL。 由于资源包中的文件在运行时可供ClassLoader使用,因此我们可以使用ClassLoader读取DSL脚本,然后将其传递给Lexer和解析器类。 使用的DSL脚本是:
Graph {
A -> B (10)
B -> C (20)
D -> E (30)
A -> E (12)
B -> D (8)
}
以及加载DSL并填充语义模型的代码:
//Please resolve the imports for the classes used.
public class GraphDslAntlrSample {
public static void main(String[] args) throws IOException {
//Reading the DSL script
InputStream is =
ClassLoader.getSystemResourceAsStream("resources/graph.gr");
//Loading the DSL script into the ANTLR stream.
CharStream cs = new ANTLRInputStream(is);
//Passing the input to the lexer to create tokens
GraphLexer lexer = new GraphLexer(cs);
CommonTokenStream tokens = new CommonTokenStream(lexer);
//Passing the tokens to the parser to create the parse trea.
GraphParser parser = new GraphParser(tokens);
//Semantic model to be populated
Graph g = new Graph();
//Adding the listener to facilitate walking through parse tree.
parser.addParseListener(new MyGraphBaseListener(g));
//invoking the parser.
parser.graph();
Graph.printGraph(g);
}
}
/**
* Listener used for walking through the parse tree.
*/
class MyGraphBaseListener extends GraphBaseListener {
Graph g;
public MyGraphBaseListener(Graph g) {
this.g = g;
}
@Override
public void exitEdge(GraphParser.EdgeContext ctx) {
//Once the edge rule is exited the data required for the edge i.e
//vertices and the weight would be available in the EdgeContext
//and the same can be used to populate the semantic model
Vertex fromVertex = new Vertex(ctx.vertex(0).ID().getText());
Vertex toVertex = new Vertex(ctx.vertex(1).ID().getText());
double weight = Double.parseDouble(ctx.NUM().getText());
Edge e = new Edge(fromVertex, toVertex, weight);
g.addEdge(e);
}
}
执行上述操作时的输出为:
Vertices...
A B C D E
Edges...
A to B with weight 10.0
B to C with weight 20.0
D to E with weight 30.0
A to E with weight 12.0
B to D with weight 8.0
总而言之,本文创建了一个外部DSL,用于通过使用ANTLR填充图形数据。 我将增强这种简单的DSL,并将其公开为实用程序,供从事图形工作的程序员使用。
这篇文章非常讲究概念和代码,您可以随意提出任何疑问,以便我也可以尝试解决这些问题,以使他人受益。
翻译自: https://www.javacodegeeks.com/2013/07/creating-external-dsls-using-antlr-and-java.html
antlr java
所有评论(0)