【小白打造编译器系列6】认识编译器前端工具:Antlr

通过【小白打造编译器系列】我们已经完成了一个简单的编译器,它仅支持简单的运算和识别几个关键字。而在实际应用中,这些简单的词法和语法规则根本不能满足我们的需要。因此我们需要借助现成的前端工具。当然,编译器的前端工具有很多,而我们选择 Antlr 的原因是它能支持更广泛的目标语言,以及它的语法更加简单

Antlr

Antlr 是一个开源的工具,支持根据规则文件生成词法分析器和语法分析器,它自身是用 Java 实现的。‘

下载网站:https://www.antlr.org

参考配置博客: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 生成的语法分析器还是很靠谱的。以后,我们只需要专注写语法规则就行了,可以把精力放在语言的设计和应用上。

发布了62 篇原创文章 · 获赞 34 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/weixin_41960890/article/details/105238379