使用ANTLR和Java创建外部DSL
在以前的一段時間里,我曾寫過有關使用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;}@Overridepublic 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();}@Overridepublic 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 scriptInputStream 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 tokensGraphLexer 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 populatedGraph 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;}@Overridepublic 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 modelVertex 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创建外部DSL的全部內容,希望文章能夠幫你解決所遇到的問題。
                            
                        - 上一篇: WPS AI正式面向社会开放 率先应用在
 - 下一篇: 部分小米手机被安装恶意软件!可篡改游览器