PLY 是一个Python 的Lex/YACC 实现. 官方网站在这里http://www.dabeaz.com/ply/
这学期有一门课叫 ” 计算机综合课程设计-基于MIPS16的SOC设计实践” ,其中有一部分是实现一个汇编器. 将MIPS汇编程序转换成Quartus 的mif 文件.

这样的需求使用Lex/YACC 最合适不过, 本来是想使用Flex/Bison , 或上学期自己实现的lex和yacc. 但是在尝试了一下后,决定放弃.原因在于C/C++ 对文本的操纵比较麻烦,另外yacc 的语义动作限制比较大. (主要是数据类型问题)

而PLY 这个实现学习成本很低, 而且很Pythonic.加入符号表等等功能也很简单.
首先我们先来看我们要处理的汇编文件的格式.

1
2
3
4
5
6
7
8
9
10
11
12
13
ORG_DATA  0000           ;The start address of data
BUF   DW  00FF, 5500
NEXT  DW  0CFF, 5D00
ORG_CODE  0000		       ;The start address of code
start:  addi  $t0, $Zero,      ;A label for the first statement,$t0=0
 	lw    $v0, BUF($t0)      ;$v0=00FF  (BUF[0])
 	addi  $t0, $t0, 2
 	lw	  $v1, BUF($t0)	 ;$v1=5500  (BUF[2])
 	add   $v0, $v0, $v1	;$v0=$v0+$v1=55FF
 	addi  $t0, $t0, 2
 	sw    $v0, BUF($t0)    	;BUF[4]=55FF
 	j	  start
end  start

下面我们来书写汇编文件的lex 定义. token 类型大概有下面这些.

  1. MIPS 基本指令 (课程设计的要求是31条)
  2. 若干伪指令 (很少, 主要有ORG_DATA , ORG_CODE 等)
  3. 符号地址 / 标号
  4. 注释
  5. 立即数
  6. 地址
  7. 其他的符号 (逗号,括号, 冒号等)

PLY 的lex 文件格式其实就是一个标准的Python 文件, 不过使用一些规定的格式/名字来指定.
(下面的代码比较长, 所以我要用–MORE–了)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
tokens =  [
	'IDENTIFIER', 'NUMBER','REG',
	'ORG_DATA', 'ORG_CODE', 'DW', 'END',
	'ADD', 'ADDU', 'SUB', 'SUBU', 'AND', 'OR', 'XOR', 'NOR', 'SLT', 'SLTU',
	'SLL', 'SRL', 'SRA',
	'SLLV', 'SRLV', 'SRAV',
	'JR',
	'ADDI','ADDIU','ANDI','ORI', 'XORI',
	'LUI',
	'LW', 'SW',
	'BEQ', 'BNE', 'SLTI', 'SLTIU',
	'J', 'JAL']
 
literals = [ ':',',','(',')' ]
 
def t_NUMBER(t):
	r'[0-9][a-fA-F0-9]*'
	try:
		t.value = int(t.value,16);
	except ValueError:
		print "Number %s is bad!" % t.value
		t.value = 
	return t		
 
def t_COMMENT(t):
	r';.*'
	pass
 
reserved  = {
	"add"		:"ADD",
	"addu"		:"ADDU",
	"sub"		:"SUB",
	"subu"		:"SUBU",
	"and"		:"AND",
	"or"		:"OR",
	"xor"		:"XOR",
	"nor"		:"NOR",
	"slt"		:"SLT",
	"sltu"		:"SLTU",
	"sll"		:"SLL",
	"srl"		:"SRL",
	"sra"		:"SRA",
	"sllv"		:"SLLV",
	"srlv"		:"SRLV",
	"srav"		:"SRAV",
	"jr"		:"JR",
	"addi"		:"ADDI",
	"addiu"		:"ADDIU",
	"ori"		:"ORI",
	"xori"		:"XORI",
	"lui"		:"LUI",
	"lw"		:"LW",
	"sw"		:"SW",
	"beq"		:"BEQ",
	"bne"		:"BNE",
	"slti"		:"SLTI",
	"sltiu"		:"SLTIU",
	"j"			:"J",
	"jal"		:"JAL",
	"ORG_DATA" 	:"ORG_DATA",
	"ORG_CODE" 	:"ORG_CODE",
	"DW" 		:"DW",
	"end"		:"END",
}
 
def t_IDENTIFIER(t):
	r'\b[a-zA-Z_][a-zA-Z_0-9]*'
	t.type = reserved.get(t.value,'IDENTIFIER')    # Check for reserved words
	return t	
 
def t_REG(t):
	r'\$[a-zA-Z_0-9]*'
	return t
 
def t_newline(t):
	r'\n+'
	t.lexer.lineno += len(t.value)
 
t_ignore  = ' \t\v'
 
def t_error(t):
    print "Illegal character '%s' on line %d " % (t.value[],t.lexer.lineno)
    t.lexer.skip(1)
 
# Compute column.
#     input is the input text string
#     token is a token instance
def find_column(input,token):
    i = token.lexpos
    while i > :
        if input[i] == '\n': break
        i -= 1
    column = (token.lexpos - i)+1
    return column

所有以t_ 开头的函数/变量 都是 PLY 使用的token 定义, PLY 使用Python 文档字符串来表示token对应的正则表达式, 而函数的返回值就是这个token的值. (更对关于PLY 的信息,请查看文档)
需要注意的是PLY (直接或间接的)支持记录token 的行号/列号, 这样就可以很方便进行错误报告. (注意t_error 函数)