通过【小白打造编译器系列】我们已经完成了一个简单的编译器,它仅支持简单的运算和识别几个关键字。而在实际应用中,这些简单的词法和语法规则根本不能满足我们的需要。因此我们需要借助现成的前端工具。当然,编译器的前端工具有很多,而我们选择 Antlr 的原因是它能支持更广泛的目标语言,以及它的语法更加简单。
Antlr
Antlr 是一个开源的工具,支持根据规则文件生成词法分析器和语法分析器,它自身是用 Java 实现的。‘
参考配置博客:https://blog.csdn.net/u014454538/article/details/86316794
用 Antlr 生成词法分析器
Antlr 通过解析规则文件来生成编译器。规则文件以 .g4 结尾,词法规则和语法规则可以放在同一个文件里。不过为了清晰起见,我们还是把它们分成两个文件,先用一个文件编写词法规则。
我们创建一个 Hello.g4 文件,用于保存词法规则,然后把之前用过的一些词法规则写进去。
lexer grammar Hello; //lexer关键字意味着这是一个词法规则文件,名称是Hello,要与文件名相同
//关键字
If : 'if';
Int : 'int';
//字面量
IntLiteral: [0-9]+;
StringLiteral: '"' .*? '"' ; //字符串字面量
//操作符
AssignmentOP: '=' ;
RelationalOP: '>'|'>='|'<' |'<=' ;
Star: '*';
Plus: '+';
Sharp: '#';
SemiColon: ';';
Dot: '.';
Comm: ',';
LeftBracket : '[';
RightBracket: ']';
LeftBrace: '{';
RightBrace: '}';
LeftParen: '(';
RightParen: ')';
//标识符
Id : [a-zA-Z_] ([a-zA-Z_] | [0-9])*;
//空白字符,抛弃
Whitespace: [ \t]+ -> skip;
Newline: ( '\r' '\n'?|'\n')-> skip;
可以很直观的看到,每个词法规则都是大写字母开头,这是 Antlr 对词法规则的约定。
下一步,在cmd中输入:
antlr4 Hello.g4
这个命令是让 Antlr 编译规则文件,并生成 Hello.java 文件和其他两个辅助文件。你可以打开看一看文件里面的内容。接着,我用下面的命令编译 Hello.java:
javac *.java
结果会生成 Hello.class 文件,这就是我们生成的词法分析器。接下来,我们来写个脚本文件,让生成的词法分析器解析一下:
int age = 45;
if (age >= 17+8+20){
printf("Hello old man!");
}
我们将上面的脚本存成 hello.play 文件,然后在终端输入下面的命令:
grun Hello tokens -tokens hello.play
grun 命令实际上是调用了我们刚才生成的词法分析器,即 Hello 类,打印出对 hello.play 词法分析的结果:
从结果中看到,我们的词法分析器把每个 Token 都识别了,还记录了它们在代码中的位置、文本值、类别。上面这些都是 Token 的属性。
以第二行 [@1, 4:6=‘age’,< Id >,1:4] 为例,其中 @1 是 Token 的流水编号,表明这是 1 号 Token;4:6 是 Token 在字符流中的开始和结束位置;age 是文本值,Id 是其 Token 类别;最后的 1:4 表示这个 Token 在源代码中位于第 1 行、第 4 列。
只要我们完善好我们的词法规则文件,我们就能做出一个实用的词法分析器了,是不是很简单?这里可以借鉴成熟的规则文件,让自己的规则文件更加完善。
用 Antlr 生成语法分析器
这一次的文件名叫做 PlayScript.g4。playscript 是为我们的脚本语言起的名称,文件开头是这样的:
grammar PlayScript;
import CommonLexer; //导入词法定义
/*下面的内容加到所生成的Java源文件的头部,如包名称,import语句等。*/
@header {
package antlrtest;
}
然后把之前做过的语法定义放进去。Antlr 内部有自动处理左递归的机制,你可以放心大胆地把语法规则写成下面的样子:
expression
: assignmentExpression
| expression ',' assignmentExpression
;
assignmentExpression
: additiveExpression
| Identifier assignmentOperator additiveExpression
;
assignmentOperator
: '='
| '*='
| '/='
| '%='
| '+='
| '-='
;
additiveExpression
: multiplicativeExpression
| additiveExpression '+' multiplicativeExpression
| additiveExpression '-' multiplicativeExpression
;
multiplicativeExpression
: primaryExpression
| multiplicativeExpression '*' primaryExpression
| multiplicativeExpression '/' primaryExpression
| multiplicativeExpression '%' primaryExpression
;
然后生成我们的语法分析器:
antlr PlayScript.g4
javac antlrtest/*.java
下一步,来测试一下:
grun antlrtest.PlayScript expression -gui
这个命令的意思是:测试 PlayScript 这个类的 expression 方法,也就是解析表达式的方法,结果用图形化界面显示。
我们在控制台界面中输入下面的内容:
age + 10 * 2 + 10
^Z
看得出来,AST 完全正确,优先级和结合性也都没错。所以,Antlr 生成的语法分析器还是很靠谱的。以后,我们只需要专注写语法规则就行了,可以把精力放在语言的设计和应用上。