词法和语法分析器构建
ANTLR简介
ANTLR全称ANother Tool for Languate Recognition,是基于LL(*)算法实现的语法分析器生成器和词法分析器生成器,由旧金山大学的Terence Parr博士等人创建。截止到目前,ANTLR已经支持生成适用于Ada95、C、C#、JavaScript、Objective-C、Perl、Python、Ruby、C++和Standard ML等多种编程语言的词法和语法分析器了。
ANTLR安装
$ cd /usr/local/lib
$ wget https://www.antlr.org/download/antlr-4.7.1-complete.jar
$ export CLASSPATH=".:/usr/local/lib/antlr-4.7.1-complete.jar:$CLASSPATH"
$ alias antlr4='java -jar /usr/local/lib/antlr-4.7.1-complete.jar'
$ alias grun='java org.antlr.v4.gui.TestRig'
词法分析和Antlr词法定义
词法分析就是讲字符序列转换为单词序列的过程。单词是构成源代码的最小单位,它由一个或多个连续字符组成。比如对代码int year=2018
进行词法分析,源代码将被转换成由5个单词组成的序列:int
,\s
(空格),year
,=
和2018
。
antlr的词法定义比较简单,大部分的词法都可以使用简单的正则表达式表示。我们来看一个简单的例子DemoLexer.g4
:
lexer grammer Demo;
PLUS:'+';
MINUS:'-';
MULTIPLE:'*';
DIV:'/';
LPAREN:'(';
RPAREN:')';
NUMBER:[0-9]+;
第1行声明这是一个词法定义文件,第2-7定义4个运算符关键字和左右两个括号,第8行则是正整数的定义。
语法分析和antlr语法定义
语法分析就是根据特定的形式文法对由单词序列构成的输入进行分析并确定其语法结构的过程。比如int
,\s
(空格),year
,=
和2018
这5个单词序列按顺序输入到语法分析器,语法分析器就能识别出这是一条变量声明和初始化的语句。
antlr的语法定义和词法定义类似,列出所有可能的单词组合情况。我们还是从一个简单的四则运算语法定义作为例子。
DemoParser.g4
parser grammar DemoParser;
options { tokenVocab=DemoLexer; }
expr:
'(' expr ')'
| NUMBER ( MULTIPLE | DIV ) NUMBER
| NUMBER ( PLUS | MINUS ) NUMBER
;
第1行声明这是一个语法定义文件,第2行设置词法选项,说明使用哪个词法,第3-7行是运算表达式的文法定义。这里简单介绍一下,expr为我们为文法取的名称,此文法有三种情况:由括号包起来的表达式、乘除表达式和加减表达式。需要注意的是乘除表达式和加减表达式定义的顺序不能随便调换,不然会影响运算符的优先级,得到错误的语法解析树。高优先级的表达式需要先定义。
生成词法分析器和语法分析器
上面只是antlr词法和语法的定义,无法在java里直接使用,因为它不是合法的java源代码,我们需要将其转换成java代码,这个转换工作还是由antlr为我们完成。在终端运行如下命令:
antlr4 DemoLexer.g4 -package demo.antlr
antlr4 DemoParser.g4 -package demo.antlr -visitor
命令执行后,将会自动生成如下文件:
DemoLexer.java
DemoLexer.tokens
DemoParser.java
DemoParser.tokens
DemoParserBaseListener.java
DemoParserBaseVisitor.java
DemoParserListener.java
DemoParserVisitor.java
这些java文件就是我们能在程序里直接调用的词法分析器和语法分析器。
词法分析器和语法分析器的使用
生成的词法分析器和语法分析器拷贝到源码目录后就可以直接使用了,我们来看一下最常用的调用方法:
String source = "1+2+3";//我们需要解析的表达式
CharStream charStream = new ANTLRInputStream(source);
DemoLexer lexer = new DemoLexer(charStrem);//词法分析器
CommonTokenStream tokenStream = new CommonTokenStream(lexer);
DemoParser parser = new DemoParser(tokenStream);//语法分析器
ExprContext exprContext = parser.expr();//以expr规则为起始规矩开始解析,得到语法解析树
通过以上步骤,表达式字符串最终被转换成了语法解析树,有了语法解析树,对表达式求值就变得很简单了。首先,我们先定义一个解析树访问(遍历)器MainDemoParserVisitor.java
:
public MainDemoParserVisitor extends DemoParserBaseVisitor {
@override
public Integer visitExpr(ExprContext ctx) {
if (ctx.MULTIPLE()!=null) {//乘法
return Integer.parseInt(ctx.NUMBER(0)) * Integer.parseInt(ctx.NUMBER(1));
} else if (ctx.DIV()!=null) {//除法
return Integer.parseInt(ctx.NUMBER(0)) / Integer.parseInt(ctx.NUMBER(1));
} else if (ctx.PLUS()!=null) {//加法
return Integer.parseInt(ctx.NUMBER(0)) + Integer.parseInt(ctx.NUMBER(1));
} else if (ctx.MINUS()!=null) {//减法
return Integer.parseInt(ctx.NUMBER(0)) - Integer.parseInt(ctx.NUMBER(1));
} else {//对括号内表达式求值
return visitExpr(ctx.expr());
}
}
}
使用访问器对解析树进行遍历并求值:
MainDemoParserVisitor visitor = new MainDemoParserVisitor();//创建访问者
Integer result = (Integer) vistor.visit(exprContext);//开始遍历语法解析树对表达式求值
System.out.println(result);
参考链接
- Antlr文档:https://github.com/antlr/antlr4/blob/master/doc/index.md
- Antlr词法语法示例:https://github.com/antlr/grammars-v4
- 使用Antlr构建的编程语言Kalang:https://github.com/kasonyang/kalang