作为一名程序员,不知道您有没有也想过自己开发自己的语言呢?我以前就想过,但是只限于想想而已,因为开发一门语言所需掌握的知识量是巨大的,且语言也是要一直维护升级的。不过如果您只是想学习了解一下的话。那就接着往下看吧,我给您介绍一个简单开发一门编程语言的方式 ——
Antlr
Antlr是什么?
Antlr
是指可以根据输入自动生成语法树并可视化的显示出来的开源语法分析器。ANTLR—Another Tool for Language Recognition
,它为包括Java,C++,C#
在内的语言提供了一个通过语法描述来自动构造自定义语言的识别器(recognizer
),编译器(parser
)和解释器(translator
)的框架
- 词法分析器(
Lexer
)
词法分析器又称为Scanner
,Lexical analyser和Tokenizer
。程序设计语言通常由关键字和严格定义的语法结构组成。编译的最终目的是将程序设计语言的高层指令翻译成物理机器或虚拟机可以执行的指令。词法分析器的工作是分析量化那些本来毫无意义的字符流,将他们翻译成离散的字符组(也就是一个一个的Token
),包括关键字,标识符,符号(symbols
)和操作符供语法分析器使用。 - 语法分析器(
Parser
)
编译器又称为Syntactical analyser
。在分析字符流的时候,Lexer
不关心所生成的单个Token
的语法意义及其与上下文之间的关系,而这就是Parser
的工作。语法分析器将收到的Tokens
组织起来,并转换成为目标语言语法定义所允许的序列。
无论是Lexer
还是Parser
都是一种识别器,Lexer
是字符序列识别器而Parser
是Token
序列识别器。他们在本质上是类似的东西,而只是在分工上有所不同而已。如下图所示:
- 树分析器 (
tree parser
)
树分析器可以用于对语法分析生成的抽象语法树进行遍历,并能执行一些相关的操作
ANTLR
它允许我们定义识别字符流的词法规则和用于解释Token
流的语法分析规则。然后,ANTLR
将根据用户提供的语法文件自动生成相应的词法/语法分析器。用户可以利用他们将输入的文本进行编译,并转换成其他形式(如AST—Abstract Syntax Tree
,抽象的语法树)。
介绍一下与语法相关的概念(简单了解即可)
术语 | 含义 |
---|---|
语言 |
一门语言是一个有效语句的集合。语句 由词组 组成,词组 由子词组 组成,子词组 又由更小的子词组组 成,依此类推。 |
语法 |
语法定义了语言的语义规则。语法中的每条规则定义了一种词组结构。 |
语义树或语法分析树 |
代表了语句的结构,其中的每个子树的根节点都使用一个抽象的名字给其包含的元素命名。即子树的根节点对应了语法规则的名字。树的叶子节点是语句中的符号或者词法符号。 |
词法符号 |
词法符号就是一门语言的基本词汇符号,它们可以代表像是“标识符”这样的一类符号,也可以代表一个单一的运算符,或者代表一个关键字。 |
Antlr使用
下面我就以IDEA为例,来创建这个Antlr项目吧(如果您使用的是eclipse,不用担心,步骤不会差太多)
- 首先创建一个Maven工程
以下是配置文件的信息pom.xml
4.0.0
top.itreatment
antlr
1.0
/*我使用的是当前的最新版本,但是当您看到这篇文章后可能已有更新,可以下载更新的版本*/
org.antlr
antlr4
4.7.2
- 导好包后,我们就要安装一个IDEA插件了
下面就是要填入AntlrTest
的“代码”
grammar AntlrTest;
prog:stat+;
stat: expr NEWLINE # print
|ID '=' expr NEWLINE # assign
|NEWLINE # blank
;
expr:expr (MULTIPLY|DIVIDE) expr # MulDiv
|expr (PLUS|MINUS) expr # AddSub
|'('expr')' # Private
|value # IdInt
;
value:INT
|ID
;
MULTIPLY:'*';
DIVIDE:'/';
PLUS:'+';
MINUS:'-';
ID:[a-z]+;
INT:[1-9]+;
NEWLINE:'\r'?'\n';
WS:[ \t\r\n] -> skip;
打开后缀为g4的文件,点击鼠标右键,就可以看到下面的样子
点击
Configure ANTLR
,就可以看到下面的样子
这里我就先用默认的方式,执行命令
Cenerate ANTLR Recognizer
,生成目录及文件如下:
如果您已经生成了这些文件,说明基础的现在已经全部准备就绪,那我们开始进行编写计算器吧,GO
-
首先创建一个执行检验我们生成的java文件的启动类
-
然后回到
AntlrTest.g4
文件,配置Antlr,重新生成文件
-
勾选
generate parse tree visitor
后,重新生成文件如下:
这里简单介绍这几个文件吧
文件名 | 作用 |
---|---|
AntlrTestParser |
语法分析器,在该类中我们定义的规则都将生成对应的方法,同时还包含其他的一些辅助代码 |
AntlrTestLexer |
ANTLR 能够自动识别出我们的语法中的文法规则和词法规则。这个类包含的是词法分析器的类定义 |
-
我们将生成的文件中以
java
为后缀
的文件复制到,我们刚刚创建的Main
类同包下
-
这里我就通过替换删除的方式,删除两个类的@Override
接下来我们创建一个EvalVisitor 类继承AntlrTestBaseVisitor,代码如下
import java.util.concurrent.ConcurrentHashMap;
public class EvalVisitor extends AntlrTestBaseVisitor {
ConcurrentHashMap memory = new ConcurrentHashMap();
@Override
public Integer visitPrint(AntlrTestParser.PrintContext ctx) {
Integer value = visit(ctx.expr());
System.out.println(value);
return 0;
}
@Override
public Integer visitAssign(AntlrTestParser.AssignContext ctx) {
String id = ctx.ID().getText();
Integer value = visit(ctx.expr());
memory.put(id, value);
return value;
}
@Override
public Integer visitMulDiv(AntlrTestParser.MulDivContext ctx) {
Integer left = visit(ctx.expr(0));
Integer right = visit(ctx.expr(1));
if (ctx.MULTIPLY() != null) {
return left * right;
} else {
return left / right;
}
}
@Override
public Integer visitAddSub(AntlrTestParser.AddSubContext ctx) {
Integer left = visit(ctx.expr(0));
Integer right = visit(ctx.expr(1));
if (ctx.PLUS() != null) {
return left + right;
} else {
return left - right;
}
}
@Override
public Integer visitIdInt(AntlrTestParser.IdIntContext ctx) {
String text = ctx.getText();
if (text.matches("[a-z]+")) {
String id = text;
return memory.containsKey(id) ? memory.get(id) : 0;
}
return Integer.valueOf(text);
}
@Override
public Integer visitPrivate(AntlrTestParser.PrivateContext ctx) {
return visit(ctx.expr());
}
}
- 在Main类中填入一下代码,我们的计算器就算是完成了。
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.CodePointCharStream;
import org.antlr.v4.runtime.CommonTokenStream;
public class Main {
public static void run(String expr) throws Exception {
//对每一个输入的字符串,构造一个 CodePointCharStream
CodePointCharStream cpcs = CharStreams.fromString(expr);
//用 cpcs 构造词法分析器 lexer,词法分析的作用是产生记号
AntlrTestLexer lexer = new AntlrTestLexer(cpcs);
//用词法分析器 lexer 构造一个记号流 tokens
CommonTokenStream tokens = new CommonTokenStream(lexer);
//再使用 tokens 构造语法分析器 parser,至此已经完成词法分析和语法分析的准备工作
AntlrTestParser parser = new AntlrTestParser(tokens);
//最终调用语法分析器的规则 prog,完成对表达式的验证
AntlrTestParser.ProgContext pcontext = parser.prog();
// 通过访问者模式,执行我们的程序
EvalVisitor evalVisitor = new EvalVisitor();
evalVisitor.visit(pcontext);
}
public static void main(String[] args) throws Exception {
run("a=5\nb=3\nc=a*b+3\nc*c\n");
}
}
- 执行结果为:
package top.coolsite;
import org.antlr.runtime.ANTLRInputStream;
import org.antlr.v4.runtime.CharStream;
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.CodePointCharStream;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.tree.ParseTree;
import org.antlr.v4.runtime.tree.ParseTreeWalker;
import java.io.IOException;
import java.util.Scanner;
/**
* StudyAntlr
*
* @author DELL
* @Date 2019/10/11
*/
public class Main {
public static void main(String[] args) throws IOException {
Scanner scanner = new Scanner(System.in);
StringBuilder sb = new StringBuilder();
while (scanner.hasNext()) {
String next = scanner.nextLine();
if ("test".equals(next)){
String s = sb.toString();
sb.delete(0, s.length());
test(s);
}else{
sb.append(next);
}
}
}
private static void test(String msg) {
System.out.println("msg = " + msg);
//对每一个输入的字符串,构造一个 CodePointCharStream
CodePointCharStream cpcs = CharStreams.fromString(msg);
//用 cpcs 构造词法分析器 lexer,词法分析的作用是产生记号
jsonLexer lexer = new jsonLexer(cpcs);
//用词法分析器 lexer 构造一个记号流 tokens
CommonTokenStream tokens = new CommonTokenStream(lexer);
//再使用 tokens 构造语法分析器 parser,至此已经完成词法分析和语法分析的准备工作
jsonParser parser = new jsonParser(tokens);
//最终调用语法分析器的规则 prog,完成对表达式的验证
jsonParser.FileContext fileContext = parser.file();
ParseTreeWalker parseTreeWalker = new ParseTreeWalker();
parseTreeWalker.walk(new jsonBaseListener(), fileContext);
}
}
grammar json;
file:row?(NEWLINE row)*NEWLINE?;
row: field(','field)*;
field:INT;
INT:[0-9]+;
NEWLINE:('\r'?'\n')+;
WS:[ \t\r\n] -> skip;
本人水平有限,如果文章有误,欢迎指正