antlr java_使用ANTLR和Java创建外部DSL

antlr java

在我以前的文章中,有一段时间我写了关于使用Java的内部DSL的文章。 在Martin Fowler撰写的《 领域特定语言 》一书中,他讨论了另一种称为外部DSL的DSL,其中DSL是用另一种语言编写的,然后由宿主语言进行解析以填充语义模型。

在前面的示例中,我讨论了有关创建用于定义图形的DSL的问题。 使用外部dsl的好处是,图形数据中的任何更改都不需要重新编译程序,而是程序可以仅加载外部dsl,创建解析树然后填充语义模型。 语义模型将保持不变,并且使用语义模型的优点是无需更改语义模型就可以对DSL进行修改。 在内部DSL和外部DSL之间的示例中,我没有修改语义模型。 为了创建外部DSL,我使用了ANTLR 。

什么是ANTLR?

官方网站上给出的定义是:

ANTLR(另一种语言识别工具)是功能强大的解析器生成器,用于读取,处理,执行或翻译结构化文本或二进制文件。 它被广泛用于构建语言,工具和框架。 ANTLR通过语法生成可以构建和遍历语法树的语法分析器。

根据以上定义,ANTLR的显着特征是:

  • 用于结构化文本或二进制文件的解析器生成器
  • 可以建造和行走解析树

语义模型

在此示例中,我将利用ANTLR的上述功能来解析DSL,然后遍历解析树以填充语义模型。 概括地说,语义模型由GraphEdgeVertex类组成,它们分别表示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,并将其公开为实用程序,供从事图形工作的程序员使用。

这篇文章非常讲究概念和代码,您可以随意提出任何疑问,以便我也可以尝试解决这些问题,以使他人受益。

参考:来自Experiences Unlimited博客的JCG合作伙伴 Mohamed Sanaulla 使用ANTLR和Java创建外部DSL 。

翻译自: https://www.javacodegeeks.com/2013/07/creating-external-dsls-using-antlr-and-java.html

antlr java

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/347000.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【MFC系列-第7天】MFC类库封装原理

关注公号【逆向通信猿】更精彩&#xff01;&#xff01;&#xff01; 运算符重载 operator RECT* () {return this; }CString类库 例1 CString str;int n str.GetLength();::GetSystemDirectory(str.GetBuffer(1000), 1000);n str.GetLength();str.ReleaseBuffer();//必须…

【MFC系列-第8天】小型软件项目开发

第8天 小型软件项目开发 8.1 记事本开发 小技巧&#xff1a;用VC6新建工程&#xff0c;以资源方式打开系统自带notepad.exe中的MENU资源&#xff0c;加入到自己新建的工程中&#xff1b;然后再添加到VS工程中&#xff0c;即可获取现有exe的菜单资源。 EndDialog中传入的参数…

Spring休眠教程

1.简介 在本文中&#xff0c;我们将演示如何利用最流行的ORM&#xff08;对象关系映射&#xff09;工具之一的Hibernate的功能 &#xff0c;该工具可将面向对象的域模型转换为传统的关系数据库。 Hibernate是目前最流行的Java框架之一。 由于这个原因&#xff0c;我们在Java Co…

【MFC系列-第9天】MFC消息映射机制的原理

关注公号【逆向通信猿】更精彩&#xff01;&#xff01;&#xff01; 第9天 MFC消息映射机制的原理 9.1 对话框常用的回调函数 a)窗口创建时的消息和虚函数包括&#xff1a;WM_CREATE&#xff0c;WM_INITDIALOG,和PreSubclassWindow等&#xff1b; b)窗口关闭时的消息和虚函…

无状态会话的ejb_Java EE状态会话Bean(EJB)示例

无状态会话的ejb在本文中&#xff0c;我们将了解如何在简单的Web应用程序中使用状态会话Bean来跟踪客户端会话中的状态。 1.简介 有状态会话Bean通常保存有关特定客户端会话的信息&#xff0c;并在整个会话中保留该信息&#xff08;与无状态会话Bean相对&#xff09;。 有状态…

ArrayList源码学习笔记(3)

时隔两年&#xff0c;重新读ArrayList源码&#xff0c;轻松了很多&#xff0c;以问题的方式记录一下收获 装饰器模式 注释中提到ArrayList本身不是线程安全的&#xff0c;注释如下&#xff1a; * <p><strong>Note that this implementation is not synchronized.&…

【MFC系列-第10天】非模式对话框开发

10.1 程序左上角图标设置 通过SendMessage发送WM_SETICON消息来设置 10.2 纯Win32程序开发和技巧&#xff08;借助MFC源码&#xff09; 10.3 非模式对话框的调用 a)调用CDialog::Create函数来创建&#xff0c;并且调用ShowWindow来显示&#xff1b; b)单例模式每次判断句柄…

Maven教程之春

1.简介 在本文中&#xff0c;我们将演示如何针对非常特定的用例对Spring使用Maven依赖项。 我们使用的所有库的最新版本都可以在Maven Central上找到。 对于一个有效的构建周期而言&#xff0c;了解Maven依赖项的工作方式以及如何对其进行管理非常重要&#xff0c;并且对于在我…

【MFC系列-第11天】CWinApp类成员分析

11.1 资源管理器开发&#xff08;C语言&#xff09; 三种位运算 //#include <AtlBase.h> //混合 c_file.attrib | _A_HIDDEN|_A_RDONLY; //判断使用if(c_file.attrib & _A_HIDDEN) //删除属性c_file.attrib&~_A_HIDDENT;11.2 资源管理器开发&#xff08;API&a…

【MFC系列-第12天】Windows系统对话框

12.1 INI配置文件 UINT GetProfileInt( LPCTSTR lpszSection, LPCTSTR lpszEntry, int nDefault ); 从应用程序的配置文件&#xff08;.INI&#xff09;的一个配置项中获取一个整数 CString GetProfileString(LPCTSTR szSection, LPCTSTR szEntry, LPCTSTR szDefault NULL )…

【BCH码2】BCH码的快速BM迭代译码原理详解及MATLAB实现(不使用MATLAB库函数【全部代码需私信另外付费获取】)

理论基础 订阅《信道编码》专栏,首先查阅各子程序的详解 【有限域生成】本原多项式生成有限域的原理及MATLAB实现 【有限域除法】二元多项式除法电路原理及MATLAB详解 【有限域元素加法和乘法】有限域元素加法和乘法的原理及MATLAB实现 【多元域乘法】多项式乘法电路原理…

【MFC系列-第13天】Windows系统对话框(对话框记事本逻辑)

13.1 内存泄露问题 真正的内存泄露是有循环性反复申请而不释放内存&#xff1a;是指在软件运行时&#xff0c;比如点一下某按钮就申请一次堆空间&#xff0c;而在下次申请前或者适当的时机及时释放内存&#xff1b; Detected memory leaks! Dumping objects -> {225} norm…

js 实现轻量ps_简单轻量的池实现

js 实现轻量ps对象池是包含指定数量的对象的容器。 从池中获取对象时&#xff0c;在将对象放回之前&#xff0c;该对象在池中不可用。 池中的对象具有生命周期&#xff1a;创建&#xff0c;验证&#xff0c;销毁等。池有助于更好地管理可用资源。 有许多使用示例。 特别是在应用…

【MFC系列-第14天】MFC核心类库的成员介绍(记事本快捷键)

14.1 对话框快捷键的设置和加载 a) 插入一个新的Accelerator到资源里&#xff0c;把加速键和对应的响应控件(如一个按钮)关联 b) 在对话框头文件中声明 HACCEL m_hAccel;c) 在对话框的构造函数里初始化m_hAccel m_hAccel ::LoadAccelerators(AfxGetInstanceHandle(),MAKEI…

【MFC系列-第15天】关联变量的概念与用法

15.1 权限管理对话框的信息录入与保存 15.2 控件型关联变量&#xff1a; FromHandle和DeleteTempMap管理成员对象表&#xff0c;前者由HWND获取CWnd*&#xff0c;后者进行删除。 BOOL Attach( HWND hWndNew ); //关联 HWND Detach( ); //解除关联 BOOL SubclassWindow( HWND…

【MFC系列-第16天】企业信息管理软件开发

常见的两种类和类之间相互调用的方法。 16.1 用户权限信息在不同对话框之间共享 ①在CWokerApp类中定义变量&#xff1a; class CWorkerApp : public CWinApp { public:CWorkerApp();SAdmin m_admin;//登录信息 // 重写 public:virtual BOOL InitInstance(); // 实现DECLARE…

java微妙_编码Java时的10个微妙的最佳实践

java微妙这是10条最佳实践的列表&#xff0c;这些最佳实践比您的平均Josh Bloch有效Java规则要微妙得多。 尽管Josh Bloch的列表很容易学习&#xff0c;并且涉及日常情况&#xff0c;但此处的列表包含了涉及API / SPI设计的较不常见的情况&#xff0c;尽管这些情况可能会产生很…

【MFC系列-第17天】企业信息管理软件开发

关注公号【逆向通信猿】更精彩&#xff01;&#xff01;&#xff01; 17.1 数值型关联变量&#xff1a; a)在MFC中有部分控件支持数值型关联变量&#xff1a; 编辑控件、下拉控件、单选按钮、复选框以及日期控件&#xff1b; b)在类向导中为控件建立关联变量时&#xff0c;选…

GraphQL在Wildfly群上

“ GraphQL是API的查询语言&#xff0c;是用于使用现有数据完成这些查询的运行时。 GraphQL为您的API中的数据提供了一个完整且易于理解的描述&#xff0c;使客户能够准确地询问他们所需的内容&#xff0c;仅此而已&#xff0c;使随着时间的推移更容易开发API并启用强大的开发人…

【MFC系列-第18天】企业信息管理软件开发

关注公号【逆向通信猿】更精彩&#xff01;&#xff01;&#xff01; CWnd类中常用的成员函数 函数名称含义static CWnd* PASCAL GetActiveWindow( )&#xff08;进程内的&#xff09;获取活动窗口CWnd* SetActiveWindow( )&#xff08;进程内的&#xff09;将一个窗口设置为…