每次在命令行里输入文本有点麻烦,可以将hello slb保存于hello.txt文本文件中,然后运行命令:
antlr4-parse Hello.g4 r -tokens hello.txt
出现如下内容:
[@0,0:4='hello',<'hello'>,1:0]
[@1,6:8='slb',<ID>,1:6]
[@2,9:8='<EOF>',<EOF>,1:9]
输出的是词法分析结果。几个术语:
token 词法符号
tokenizing 词法符号化
lexer 词法分析器
lexical analysis 词法分析
[@0,0:4='hello',<'hello'>,1:0]表示:
第0个词法符号,是'hello',从第0个字符到第4个字符,位于输入文本的第1行的第0个字符开始。
[@1,6:8='slb',<ID>,1:6] 表示:
第1个词法符号,是'slb',从第6个字符到第8个字符,类型是<ID>,位于输入文本的第1行的第6个字符开始。
识别一门语言,需要经过二个阶段,第一个阶段就是词法分析,第二个阶段是语法分析,生成一棵语法分析树。
下面是一个数组初始化的语法规则:
// file name: ArrayInit.g4
grammar ArrayInit;
init : '{' value (',' value)* '}' ; // 必须匹配至少一个value
value : init
| INT
;
INT : [0-9]+ ; // 整数
WS : [ \t\r\n]+ -> skip ;
运行下面的命令行,生成几个文件:
antlr4 -Dlanguage=Python3 ArrayInit.g4
1)ArrayInitParser.py
语法分析器类,每一条规则都对应一个方法,还有一些辅助代码。
2)ArrayInitLexer.py
词法分析器类
3)ArrayInitLexer.tokens
每个词法符号对应于一个数字形式的类型。
4)ArrayInitListener.py
在遍历语法分析树的时候,遍历器能够触发一系列“事件”,可以称为“回调函数”,通知我们提供的监听器对象。
试着解析一下所有的词法符号。
pygrun ArrayInit init --tokens
{99, 3, 451}
^Z
[@0,0:0='{',<1>,1:0]
[@1,1:2='99',<4>,1:1]
[@2,3:3=',',<2>,1:3]
[@3,5:5='3',<4>,1:5]
[@4,6:6=',',<2>,1:6]
[@5,8:10='451',<4>,1:8]
[@6,11:11='}',<3>,1:11]
[@7,13:12='<EOF>',<-1>,2:0]
打印语法分析树:
pygrun ArrayInit init --tree
{99, 3, 451}
^Z
(init {
(value 99) ,
(value 3) ,
(value 451) })
用antlr4-parse也是类似的:
antlr4-parse ArrayInit.g4 init -tree
{99, 3, 451}
^Z
(init:1 { (value:2 99) , (value:2 3) , (value:2 451) })
它还支持-gui的命令行参数。
可以生成更复杂的嵌套数组的表示:
antlr4-parse ArrayInit.g4 init -gui
{1, {2, 3}, 4}
^Z
antlr4生成了一些python源代码,但要让python环境里能够解释它们,还需要安装一个东西:
pip install antlr4-python3-runtime
前面生成了一些类,但没有主程序,下面的链接里有书中相配套的Python源代码。
https://github.com/jszheng/py3antlr4book
我稍微修改了一下:
import sys
from antlr4 import *
from ArrayInitLexer import ArrayInitLexer
from ArrayInitParser import ArrayInitParser
istream = FileStream('input.txt')
lexer = ArrayInitLexer(istream)
stream = CommonTokenStream(lexer)
parser = ArrayInitParser(stream)
tree = parser.init()
print(tree.toStringTree(recog=parser))
现在,使用纯Python实现,我们也可以打印刚才的语法解析树了。
一个简单的翻译器
下面需要构造一个Listener来实现数字的翻译任务。
只需实现一个Listener,实现enter/exit方法,就可以实现轻松实现这种转换。
from ArrayInitListener import ArrayInitListener
class RewriteListener(ArrayInitListener):
def enterInit(self, ctx):
print('"', end='')
def exitInit(self, ctx):
print('"', end='')
def enterValue(self, ctx):
pass
def exitValue(self, ctx):
v = int(ctx.INT().getText())
print(f'\\u{v:04x}', end='')
主程序也稍微增加了几行语句:
import sys
from antlr4 import *
from ArrayInitLexer import ArrayInitLexer
from ArrayInitParser import ArrayInitParser
from rewriter import RewriteListener
istream = FileStream("input.txt")
lexer = ArrayInitLexer(istream)
stream = CommonTokenStream(lexer)
parser = ArrayInitParser(stream)
tree = parser.init()
print(tree.toStringTree(recog=parser))
walker = ParseTreeWalker()
walker.walk(RewriteListener(), tree)
print()