PLY
你好! 如果你从事编译器或解析器的开发工作,你可能对lex和yacc不会陌生,PLY是David Beazley实现的基于Python的lex和yacc。作者最著名的成就可能是其撰写的Python Cookbook, 3rd Edition。一个基于python实现的Lex和Yacc的编译器构建工具。包括支持LALR(1)分析法、提供丰富的输入验证、错误报告和诊断。因此,如果你曾经在其他编程语言下使用过yacc,你应该能够很容易的迁移到PLY上。
编译器与解析器
根据他们的定义,编译器和解释器之间的区别貌似十分明显:
解释器:直接执行用编程语言编写的指令的程序
编译器:把源代码转换成(翻译)低级语言的程序
了解定义之后我们把重点放在编译器上,根据定义简单来说就是把我没写的源代码转换成机器可以识别的机器码(汇编码)
具体的转换过程如下
以c代码为案例
源程序到目标程序执行的四个阶段如图1所示,GCC编译C源代码有四个步骤:预处理—->编译—->汇编—->链接。
预处理
编译器将C程序的头文件编译进来,还有宏的替换,可以用gcc的参数-E来参看。
命令:unix>gcc –o hello hello.c
作用:将hello.c预处理输出hello.i
编译(.i—.s)转换为汇编语言文件
这个阶段编译器主要做词法分析、语法分析、语义分析等,在检查无错误后后,把代码翻译成汇编语言。可用gcc的参数-S来参看。
编译器(ccl)将文本文件hello.i 翻译成文本文件hello.s, 它包含一个汇编语言程序。
一条低级机器语言指令。
命令:gcc -S hello.i -o hello.s
作用:将预处理输出文件hello.i汇编成hello.s文件
汇编阶段(.s—.o)得到机器语言
汇编器as 将hello.s 翻译成机器语言保存在hello.o 中(二进制文本形式)。
链接阶段
printf函数存在于一个名为printf.o的单独预编译目标文件中。必须得将其并入到hello.o的程序中,链接器就是负责处理这两个的并入,结果得到hello文件,它就是一个可执行的目标文件。
重点 编译
Lex
lex.py通过向外部提供token()方法作为接口,方法每次会从输入中返回下一个有效的标记。yacc.py将会不断的调用这个方法来获取标记并匹配语法规则。yacc.py的的功能通常是生成抽象语法树(AST),不过,这完全取决于用户,如果需要,yacc.py可以直接用来完成简单的翻译。
lex.py是用来将输入字符串标记化。例如,假设你正在设计一个编程语言,用户的输入字符串如下:
x = 3 + 42 * (s - t)
标记器将字符串分割成独立的标记:
'x','=', '3', '+', '42', '*', '(', 's', '-', 't', ')'
标记通常用一组名字来命名和表示:
'ID','EQUALS','NUMBER','PLUS','NUMBER','TIMES','LPAREN','ID','MINUS','ID','RPAREN'
将标记名和标记值本身组合起来:
('ID','x'), ('EQUALS','='), ('NUMBER','3'),('PLUS','+'), ('NUMBER','42), ('TIMES','*'),('LPAREN','('), ('ID','s'),('MINUS','-'),('ID','t'), ('RPAREN',')
正则表达式是描述标记规则的典型方法,下面展示如何用lex.py实现。
# -----------------------------------------------------------
# calclex.py
#
# tokenizer for a simple expression evaluator for
# numbers and +,-,*,/
# ------------------------------------------------------------
import ply.lex as lex
# List of token names. This is always required
tokens = (
'NUMBER',
'PLUS',
'MINUS',
'TIMES',
'DIVIDE',
'LPAREN',
'RPAREN',
)
# Regular expression rules for simple tokens
t_PLUS = r'\+'
t_MINUS = r'-'
t_TIMES = r'\*'
t_DIVIDE = r'/'
t_LPAREN = r'\('
t_RPAREN = r'\)'
# A regular expression rule with some action code
def t_NUMBER(t):
r'\d+'
t.value = int(t.value)
return t
# Define a rule so we can track line numbers
def t_newline(t):
r'\n+'
t.lexer.lineno += len(t.value)
# A string containing ignored characters (spaces and tabs)
t_ignore = ' \t'
# Error handling rule
def t_error(t):
print "Illegal character '%s'" % t.value[0]
t.lexer.skip(1)
# Build the lexer
lexer = lex.lex()
为了使lexer工作,你需要给定一个输入,并传递给input()方法。然后,重复调用token()方法来获取标记序列,下面的代码展示了这种用法:
# Test it out
data = '''
3 + 4 * 10
+ -20 *2
'''
# Give the lexer some input
lexer.input(data)
# Tokenize
while True:
tok = lexer.token()
if not tok: break # No more input
print tok
程序执行,将给出如下输出:
$ python example.py
LexToken(NUMBER,3,2,1)
LexToken(PLUS,'+',2,3)
LexToken(NUMBER,4,2,5)
LexToken(TIMES,'*',2,7)
LexToken(NUMBER,10,2,10)
LexToken(PLUS,'+',3,14)
LexToken(MINUS,'-',3,16)
LexToken(NUMBER,20,3,18)
LexToken(TIMES,'*',3,20)
LexToken(NUMBER,2,3,21)
结束
当然这只是PLY的简单功能,后期我们来了解语法部分Yacc
ANSI C grammar