使用PLY 构建一个汇编器(1)
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 类型大概有下面这些.
- MIPS 基本指令 (课程设计的要求是31条)
- 若干伪指令 (很少, 主要有ORG_DATA , ORG_CODE 等)
- 符号地址 / 标号
- 注释
- 立即数
- 地址
- 其他的符号 (逗号,括号, 冒号等)
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 函数)