这里是这个编译器如何使用的简单说明.
主程序为compiler.py, (目录中compiler_alpha.py为alpha版, 现在已经不能使用.)
输入为工作目录(windows下就是同目录)下的input.c,输入文件的对应目标语言为MiniC,简单来说是阉割版C语言,具体需要看最新版文档. (控制语句必须用大括号括起来)
编译器首先读入代码并格式化(包括去单行/多行注释, 加{}号, 去;号, 分行, 替换变量格式标识符,替换一元右结合运算符号), 格式化的结果输出为code1.txt
之后利用格式化过的代码进行翻译成汇编代码, 输出为code.asm. 中间检测到的错误会被抛出, 即error.txt记录了所有格式化过的错误代码行.(不保证能够正确处理及抛出) 所以如果要定位错误行, 需要将 源代码 — 格式化后的代码 — 错误代码行 三者匹配.
说在最前面.. 这次编译器整体的设计思路为: 抛弃lex和yacc, python直接翻译
这次的编译器的目标语言是MiniC, 与C区别在于精简了大量功能并规定了一些格式. (这也是敢用python硬解的底子) 于是再看一下我们需要处理的语言是什么模样. 这里是MiniC的一个示例代码:
int a;
void delay(int i, int j) {
int a[10];
short s;
char c;
unsigned char uc;
unsigned short us;
unsigned int ui;
char q;
short w;
int e;
int *p;
a[1] = 1000;
while(c>0){ c=c - 1;}
i = a[5] + 3;
}
int main(void)
{
int i,j,k[3];
int key;
int *LED;
key=0;
LED = 0xfffffc60;
while(1)
{
key=key+1;
*LED=key;
if(key>100) key=0;
else if(key>10)key = 1;
else key=key-3;
delay(a+5, k[2]);
}
}
大概就是这种样式. 重点在于它比C更注重规则化: 全局变量定义在所有函数前, 函数内部变量定义必须在代码之前; 定义和赋值必须分开. 另外MiniC支持(unsigned) byte/short/int.
首先, 看一下编译出的汇编语言中有哪几个部分:
.stack
.data
.code
.stack默认为空, 为满足汇编器需求, 需要在.code中生成一句初始化$sp的代码即可:
outputln('addi $29,$0,4000H')
(outputln函数为自定义的文件写入函数, 具体代码为:
outputcode = open('code.asm','w')
def outputln(s):
outputcode.write(str(s))
outputcode.write('\n')
outputcode.flush()
之后不再说明.)
.data需要存放非寄存器类型数据, 分为全局变量和局部变量(函数内定义的变量).
-
全局变量, 毫无疑问是定义在.data区. 所以只要扫描提取出变量名并列在.data就好. 首先定义一个名为Global的word类型数, 值为之后全局变量占用了多少字节(8位). 之后是全局变量, 为了和之后函数里的变量名区分, 全局变量在汇编.data里的名称定义都是 "Global_ + 原本的变量名". 另外为了支持对齐, 假设不足32位的变量, 会被强制补齐至足够32位的个数.
举例, 上面的实例代码中, 全局变量为
int a
, 最后生成的全局变量在.data中的代码是这个样子:.data Global .word 4 Global_a .word 0
-
函数的局部变量, 与全局变量不同的是, 前8个使用最频繁的局部变量需要分配的是寄存器, 其它才会分配到.data区. 所以先要提取出函数形参和最开始定义的变量, 并扫描整个函数记录变量出现的次数, 选取前8个分配寄存器, 之后的分配到.data区. 比如这个函数:
int delay(int n){ int a[10]; char b[3]; return 0; }
实际编译出的效果应为:
delay .word 44 delay_a .word 0 0 0 0 0 0 0 0 0 0 delay_b .byte 0 0 0 0
有一个叫delay(void)的函数, 里面定义了一个
int a[10]; char b[3];
的数组. 注意虽然b[3]定义里长度是3, 但为了32位对齐, 实际会补足32位长度, 即4个.
.code是程序代码. MiniC代码大致可以分类为三种:
- 变量定义
- 普通运算, 包括函数调用
- 控制语句, 包括if , while (未支持for)
赋值之类思路很简单, 关键是while 循环和if else条件控制. 这里, 编译器和汇编器之间做了一个约定: 编译器中绝不出现实际地址, 全部由标号(Label)代替. 在这里, while循环比较简单, 只要在while语句开始时加一个while_begin_number的Label, 最后加一个j while_begin_number的跳转语句和一个while_end_number 的结束Label, 内部开头加一个判断跳转语句即可. 但if else循环相当不好写. 大致的处理步骤如下:
- 首先, 不一定有{}号把if-else的语句括起来, 需要你自行处理. 所以第一步就是规则化, 把所有的if else语句变成if() {} else{ if-else }的形式, 这实际变成了一个递归的问题.
- 递归地生成Label. 这里我无法用语言去描述代码. 总之是根据第一步生成好了的左右{}号来生成Label, 较1.而言应该说是简单多了.
具体请看下面的 编译器的顺序处理流程
为了方便处理及OOP(Object-oriented programming), 定义了两种类:
顾名思义, 该类为函数中变量对应的类.
class Variable(object):
"""docstring for Variable
type: 0 : register; 1 : RAM,单独变量,占1单元 n(>1):数组,占n单元
"""
def __init__(self, name, vartp):
super(Variable, self).__init__()
self.name = name #name in function
self.vtype = vartp #variable_types = ['sint08','sint16','sint32','uint08','uint16','uint32']
self.sizeof = int(vartp[4:6]) # 8, 16, 32
self.corname = '' # name in .code or register name
self.type = 0 # 0 : register; 1 : RAM单独变量 n(>1):数组,占n单元
def generatecode(self):
if self.type == 1:
tmp = 32/self.sizeof
ret = self.corname + ' ' + sizetotype[self.sizeof]
for i in range(tmp):
ret += ' 0' #32对齐
return 1,ret
elif self.type > 1:
cursize = self.sizeof * self.type
while cursize % 32 != 0:
cursize += self.sizeof
num = cursize / 32
tmp = cursize / self.sizeof
ret = self.corname + ' ' + sizetotype[self.sizeof]
for i in range(tmp):
ret += ' 0' #32对齐
return num,ret
else:
return 0,''
类的有如下几个属性:
属性 | 含义 |
---|---|
name | 实际变量名 |
vtype | 变量类型. 包括sint08,sint16,sint32,uint08,uint16,uint32 |
sizeof | 类型所占空间大小. 包括8,16,32 |
corname | 对应在.data区的名称或对应的寄存器的名称 |
type | 0: 寄存器型变量 1: .data区单独变量 n(n>1): .data区数组型变量 |
除去全局变量, MiniC的代码是有一个或多个Funciton组成的. Main函数同样为Function. 故定义Function类将其规范化.
class Function(object):
def __init__(self, codes):
super(Function, self).__init__()
self.codes = []
for i in codes:
i = i.strip()
if i != '':
self.codes.append(i)
tmp = self.codes[0]
name = tmp[tmp.find(' ')+1:tmp.find('(')].strip()
if name == '':
throw_error(get_cur_info() + codes[0])
varstring = tmp[tmp.find('(')+1:tmp.rfind(')')].strip()
self.name = name
self.prefix = name + '_'
self.vtype = self.codes[0][:6] #variable_types = ['void00','sint08','sint16','sint32','uint08','uint16','uint32']
self.sizeof = int(self.vtype[4:6]) # 8, 16, 32
self.params = []
self.vardict = {} # varname -> varclass
if varstring != '' and varstring not in types:
for x in varstring.split(','):
x = x.strip()
tp = x[:6].strip()
name = x[6:].strip()
if name.find('[') != -1:
name = name[:name.find('[')].strip()
self.params.append((name,tp))
self.head = codes[0]
self.vardeclaration = []
self.realcode = []
for i in range(2,len(codes)):
s = self.codes[i]
if len(s) >= 8 and s[:6] in variable_types and s[6] == ' ':
continue
else:
self.vardeclaration = codes[2:i]
self.realcode = codes[i:-1]
break
def printcode(self):
outputln('\n\n' + self.name + '_begin:')
availableVars = self.vardict.copy()
for i,j in globalVarDict.items():
if i not in self.vardict:
availableVars[i] = j
print '\nBegin##################################### ' + self.name
print 'self.name = ',self.name
print 'self.vtype = ',self.codes[0][:6] #variable_types = ['void00','sint08','sint16','sint32','uint08','uint16','uint32']
print 'self.params = ',self.params
self.sizeof = int(self.vtype[4:6]) # 8, 16, 32
print '%s vardict len:%d'%(self.name,len(self.vardict))
print '%s availableVars num: %d'%(self.name,len(availableVars))
for i,j in availableVars.items():
print 'vars %s -- %s'%(i,j.corname)
dealCodes(self.name, self.realcode, availableVars, 0)
rassignr('$v0', '$zero')
outputln('jr $ra')
print '\nEnd##################################### ' + self.name
Function类中有如下属性:
属性 | 含义 |
---|---|
name | 函数名 |
codes | 全部代码行 |
prefix | 对应的.data区变量定义时加的前缀. 一般为 "函数名_" |
vtype | 函数返回类型 |
sizeof | 函数返回类型所占空间大小. 包括8,16,32 |
params | 函数参数列表. 存放的是 (参数名,类型) 的二元组 |
vardict | {参数名->参数类} 的字典 |
head | 函数定义行 |
vardeclaration | 参数定义行列表 |
realcode | 实际执行代码列表 |
函数通过传给__init__
全部的代码行进行初始化, 通过printcode
输出全部函数对应的汇编代码.
上面把编译器的约定大致说完了, 这里是具体的工作流程.
1460行之前的代码都是各种定义和函数, 之后才是main函数体. 其实应该用一个__main__函数括起来的, 不过当时还没有用那种代码风格.
""""""""""""""""""""" 读入数据并格式化 """""""""""""""""""""
codes = init_input()
sys.stdout = open('code1.txt', 'w')
for i in codes:
print i
def init_input():
sys.stdin = open('input.c', 'r')
ret = []
global raw
ss = ''
while True:
try:
s = raw_input().strip()
raw.append(s)
loc = s.find('//')
if loc != -1:
s = s[:loc].strip()
ss += s
except EOFError:
break
while True:
loc = ss.find('/*')
if loc == -1:
break
pre = ss[:loc]
aft = ss[loc + 2:]
loc = aft.find('*/')
if loc != -1:
aft = aft[loc + 2:]
else:
aft = []
ss = pre + aft
tmp = re.search('\\bvoid\\b',ss)
while tmp:
i,j = tmp.span()
ss = ss[:i] + 'void00' + ss[j:]
tmp = re.search('\\bvoid\\b',ss)
tmp = re.search('\\bunsigned char\\b',ss)
while tmp:
i,j = tmp.span()
ss = ss[:i] + 'uint08' + ss[j:]
tmp = re.search('\\bunsigned char\\b',ss)
tmp = re.search('\\bunsigned short\\b',ss)
while tmp:
i,j = tmp.span()
ss = ss[:i] + 'uint16' + ss[j:]
tmp = re.search('\\bunsigned short\\b',ss)
tmp = re.search('\\bunsigned int\\b',ss)
while tmp:
i,j = tmp.span()
ss = ss[:i] + 'uint32' + ss[j:]
tmp = re.search('\\bunsigned int\\b',ss)
tmp = re.search('\\bchar\\b',ss)
while tmp:
i,j = tmp.span()
ss = ss[:i] + 'sint08' + ss[j:]
tmp = re.search('\\bchar\\b',ss)
tmp = re.search('\\bshort\\b',ss)
while tmp:
i,j = tmp.span()
ss = ss[:i] + 'sint16' + ss[j:]
tmp = re.search('\\bshort\\b',ss)
tmp = re.search('\\bint\\b',ss)
while tmp:
i,j = tmp.span()
ss = ss[:i] + 'sint32' + ss[j:]
tmp = re.search('\\bint\\b',ss)
while len(ss) > 0:
# for while if else
ss = extract_a_part(ss, ret)
return ret
具体内容包括:
-
分行, 是以 ; 号和 \n来分行, 并删除;号. 即, 这之后的数据, 只有两种形式: 具体的运算/定义代码, 否则就是 {或} 号.
-
单行注释直接在读入每行时消除.
-
多行注释方面, 我犯了一个极其愚蠢的错误, 而且这个错误几乎无法修正: 为了支持多行注释的格式
/* xxx */
, 我采用了一个蠢办法: 将所有读入行合成一整行, 之后扫描寻找/*
子串, 找到后继续向后寻找*/
子串. 这种方法虽然可行, 但却严重破坏了代码的行信息, 以至于在之后的错误定位上越改越乱. (姑且, 现在错误定位是这样实现的: 存了一份原始的代码, 之后在代码处理过程中发现错误的时候, 直接将错误行抛出, 最后对所有抛出的代码行, 查找其在原始代码中的位置. 愚蠢的方法, 但别无选择了. ) -
类型名替换. 这是被坑过一次的事: 初版MiniC手册只要求支持int,所以alpha版代码也就只支持了int; 而后序更新的MiniC手册要求增加对各种长度类型的支持. 为了便利的处理不同类型, 将原本的名称(unsigned) char, short, int 统一替换为swift语言风格的int08, int16, int32. 并根据是否为unsigned类型而添加u和s. 最后类型会被编译器替换为
['sint08','sint16','sint32','uint08','uint16','uint32']
这个6种格式. 当然这些都是编译器做的, 对用户透明. 用户写的时候仍然是标准C风格的定义即可.
-
运算符替换. 关于指针类型, 直接按照*作为一种右结合的运算, 数据本身作为sint32处理. 同时为了不与乘运算混淆, 所有的指针运算符号
*
都被替换为了$
号; 作为单元运算符的负号-
也被替换为了`
号(以便和二元运算符减号区分). -
if控制语句的标准化. 因为if else的处理相当麻烦, 所以要统一处理为
if(){ //xxx }else{ if(){} else{} }
的形式. 其中, else if的处理是递归的, 具体是在这个函数里:
def extract_a_part(s, ret): if s[0] == '{' or s[0] == '}': ret.append(s[0]) s = s[1:].strip() return s if s == '': return '' if re.match('(unsigned )?char |(unsigned )?short |(unsigned )?int ',s): replace = '' if s[0] == 'u': replace += 'u' s = s[9:] else: replace += 's' replace += 'int' if s[:4] == 'char': replace += '08' s = s[4:] elif s[:5] == 'short': replace += '16' s = s[5:] elif s[:3] == 'int': replace += '32' s = s[3:] s = replace + s if re.match('\\bvoid\\b',s): s = 'void00' + s[4:] if len(s)>=8 and s[:6] in types: i = 6 while i < len(s) and ( s[i] == ' ' or s[i] == '\t' ): i += 1 if i < len(s) and s[i] == '*': s = s[:i] + s[i+1:] if s[:3] == 'for': s = s[3:].strip() tmp = get_parenthesis_content(s) ret.append('for' + tmp[0]) s = tmp[1] if s[0] != '{': ret.append('{') s = extract_a_part(s, ret) ret.append('}') return s if s[:5] == 'while': s = s[5:].strip() tmp = get_parenthesis_content(s) ret.append('while' + tmp[0]) s = tmp[1] if s[0] != '{': ret.append('{') s = extract_a_part(s, ret) ret.append('}') return s elif s[:2] == 'if': s = s[2:].strip() tmp = get_parenthesis_content(s) ret.append('if' + tmp[0]) s = tmp[1] if s[0] != '{': ret.append('{') s = extract_a_part(s, ret) ret.append('}') if re.match('else[ \\t]+if',s): tmp = s.find('if') s = s[tmp:] ret.append('else') ret.append('{') s = extract_a_part(s,ret) ret.append('}') elif s[:4] == 'else': s = s[4:].strip() ret.append('else') if s[0] != '{': ret.append('{') s = extract_a_part(s, ret) ret.append('}') return s i = 0 for c in s: if c == ';': if i: ret.append(s[:i]) s = s[i + 1:] break elif c == '{' or c == '}': if i: ret.append(s[:i]) ret.append(s[i]) s = s[i + 1:] break i += 1 return s
该函数递归处理一段程序代码, 将其分块并适当添加{}号, 参数ret存放所有处理过的代码.
""""""""""""""""""""""""""""""""" 分块,初始化function类 开始 """""""""""""""""""""""""""""""""
i = 0
for i in range(len(codes)):
if codes[i].find('(') != -1:
globalCodes = codes[:i]
codes = codes[i:]
break
while len(codes) > 0:
block = codes[0:2]
codes = codes[2:]
num = 1
while num != 0 and len(codes)>=1:
tmp = codes[0].strip()
if tmp == '':
continue
block.append(tmp)
if tmp == '{':
num += 1
elif tmp == '}':
num -= 1
codes = codes[1:]
func = Function(block)
functions.append(func)
while len(codes)>0 and codes[0].strip == '':
codes = codes[1:]
for i in range(len(functions)):
if functions[i].name == '':
functions[i],functions[len(functions)-1] = swap(functions[i],functions[len(functions)-1])
functions = functions[:-1]
else:
functionDict[functions[i].name] = functions[i]
if i >= len(functions)-1:
break
if functions[len(functions)-1].name != 'main':
for i in range(len(functions)):
if functions[i].name == 'name':
functions[i],functions[len(functions)-1] = swap(functions[i],functions[len(functions)-1])
break
for f in functions:
functionNameList.append(f.name)
functionReturnType[f.name] = f.vtype
for f in functions:
print f.name
for i,j in f.vardict.items():
print '\t',j.name
""""""""""""""""""""""""""""""""" 分块,初始化function类 结束 """""""""""""""""""""""""""""""""
##此时var还未填充
通过 ( 号来判断第一个函数行位置, 并认定之前的代码为全局变量定义. 之后通过左右大括号来确定每一个函数的函数体, 并将main函数放到所有函数的末尾. (只是强迫症) 类的初始化参数为类的全部代码行, 初始化时函数抽取出函数头, 参数列表和实际代码等数据. 至此输入和初始化完毕, 接下来是输出.
.data区数据, 包括全局变量和函数的临时变量. 全局变量的处理如之前所述, 为方便识别全体在.data区加上了前缀 Global_ .
函数的临时变量同样如之前所述, 计算了之后变量出现的次数并排序, 前8个分配给寄存器, 之后的再.data中开辟空间. 不过需要注意的是如果是数组类型则必定放在.data区, 无论数组大小是多少. 另外函数的形参同样是它自己 定义的 参数, 不能落下.
""""""""""""""""""""""""""""""""" .DATA输出开始 """""""""""""""""""""""""""""""""
outputln('.stack')
outputln('.data')
globalVarList,nouse,globalArray = scanVarible(globalCodes, globalCodes)
for name,tp in globalVarList:
tmp = Variable(name,tp)
tmp.corname = prefix_global + name
tmp.type = 1
globalVarDict[name] = tmp
for array,tp in globalArray:
name = array[:array.find('[')].strip()
num = int(array[ array.find('[') + 1 : array.rfind(']') ].strip())
tmp = Variable(name, tp)
tmp.corname = prefix_global + name
tmp.type = num
globalVarDict[name] = tmp
tmp = []
num = 0
for i,j in globalVarDict.items():
print '\t',j.name,j.vtype,j.sizeof,j.type
tmpnum,tmps = j.generatecode()
if tmpnum == 0:
continue
num += tmpnum
tmp.append(tmps)
outputln('Global .word ' + str(num * 4))
for s in tmp:
outputln(s)
""""""""""""""""""""" 全局变量输出完毕 """""""""""""""""""""
for f in functions:
print 'init functino %s'%(f.name)
f.varlist,numdict,arraylist = scanVarible([f.head] + f.vardeclaration, f.realcode)
print 'f.varlist size:%d, numdict size:%d'%(len(f.varlist),len(numdict))
for array,tp in arraylist:
name = array[:array.find('[')].strip()
num = int(array[array.find('[') + 1:array.rfind(']')].strip())
tmp = Variable(name, tp)
tmp.corname = f.prefix + name
tmp.type = num
f.vardict[name] = tmp
numlist = sorted(numdict.items(), key = lambda x:x[1], reverse = True)
if len(numlist)<= 8:
for i in range(len(numlist)):
name,tp = numlist[i][0]
tmp = Variable(name,tp)
tmp.corname = '$s' + str(i)
tmp.type = 0
f.vardict[name] = tmp
else:
for i in range(8):
name,tp = numlist[i][0]
tmp = Variable(name, tp)
tmp.corname = '$s' + str(i)
tmp.type = 0
f.vardict[name] = tmp
for (name,tp),num in numlist[8:]:
tmp = Variable(name, tp)
tmp.corname = f.prefix + name
tmp.type = 1
f.vardict[name] = tmp
tmp = []
num = 0
for i,j in f.vardict.items():
tmpnum,tmps = j.generatecode()
if tmpnum == 0:
continue
num += tmpnum
tmp.append(tmps)
outputln(f.name + ' .word ' + str(num * 4))
for s in tmp:
outputln(s)
outputln('')
print 'f.vardict size:%d\n'%(len(f.vardict))
""""""""""""""""""""""""""""""""" .DATA输出结束 """""""""""""""""""""""""""""""""
首先是对$ra和$sp(即$29)的初始化, 之后要求直接跳到main_begin, 即main函数开始标号处. 之后依序输出各个函数翻译过的代码, 结束.
""""""""""""""""""""""""""""""""" .CODE输出开始 """""""""""""""""""""""""""""""""
outputln('\n.code')
outputln('START:')
outputln('addi $ra,$zero,END')
outputln('addi $29,$0,4000H')
outputln('j main_begin\n')
for f in functions:
f.printcode()
outputln('\nEND:')
outputln('END START')
""""""""""""""""""""""""""""""""" .CODE输出结束 """""""""""""""""""""""""""""""""
其实printcode函数的具体代码核心部分如下:
def printcode(self):
outputln('\n\n' + self.name + '_begin:')
availableVars = self.vardict.copy()
for i,j in globalVarDict.items():
if i not in self.vardict:
availableVars[i] = j
self.sizeof = int(self.vtype[4:6]) # 8, 16, 32
dealCodes(self.name, self.realcode, availableVars, 0)
rassignr('$v0', '$zero')
outputln('jr $ra')
即, 首先输出 函数名_begin 标号, 之后初始化变量词典. 这个词典包括全部内部定义的变量+全局变量中变量名不重复的变量. (这里要注意必须用python的.copy()生成新的dict, 我就因为传指针搞得很惨.) 最后添加的
rassignr('$v0', '$zero')
outputln('jr $ra')
是为了防止函数本身没有写return函数而不跳回之前的调用位置, 这时候默认返回0.
中间所有的代码生成是由dealCodes函数完成.
dealCodes具体的工作就是判断下每条语句的类型, 是if while控制语句, 还是return语句, 还是普通的计算语句. 对于if/while这样的准函数 , 即本身也有一个 '体' 的控制语句, 同样是递归处理.
def dealCodes(funcname, codes, corvar, loopnum):
for i,j in corvar.items():
print i + ':' + j.corname
print ''
dealedLineNum = -1
for linenum in range(len(codes)):
outputln('')
if linenum <= dealedLineNum:
continue
s = codes[linenum].strip()
if s == '{' or s == '}' or s == '':
continue
if re.match('^return\\b',s):
tmp = s[6:].strip()
if tmp != '':
dealExpression(tmp, '$v0', funcname, corvar)
#throw_error(get_cur_info() + 'tmp:'+tmp)
outputln('jr $ra')
pass
elif re.match('^while(.+)$',s):
beginLabel = funcname + '_while_start_' + str(loopnum)
endLabel = funcname + '_while_end_' + str(loopnum)
loopnum += 1
state = s[s.find('(')+1:s.rfind(')')].strip()
body,curnum = findCurlyContent(codes[linenum:])
dealedLineNum = linenum + curnum
if state == '':
throw_error(get_cur_info() + s)
continue
outputln(beginLabel + ':')
dealExpression(state,'$t0', funcname, corvar)
outputln('beq $t0,$zero,' + endLabel)
dealCodes(funcname, body, corvar, loopnum)
outputln('j ' + beginLabel)
outputln(endLabel + ':')
outputln('')
pass
elif re.match('^if(.+)$',s):
ifbeginLabel = funcname + '_if_start_' + str(loopnum)
ifendLabel = funcname + '_if_end_' + str(loopnum)
allendLabel = funcname + '_ifelse_end_' + str(loopnum)
state = s[s.find('(')+1:s.rfind(')')].strip()
body,curnum = findCurlyContent(codes[linenum:])
dealedLineNum = linenum + curnum
if state == '':
throw_error(get_cur_info() + s)
continue
outputln(ifbeginLabel + ':')
dealExpression(state,'$t0', funcname, corvar)
outputln('beq $t0,$zero,' + ifendLabel)
dealCodes(funcname, body, corvar, loopnum)
outputln('j ' + allendLabel)
outputln(ifendLabel + ':')
outputln('')
if dealedLineNum + 1 < len(codes) and codes[dealedLineNum + 1].strip() == 'else':
elsebeginLabel = funcname + '_else_start_' + str(loopnum)
elseendLabel = funcname + '_else_end_' + str(loopnum)
body,curnum = findCurlyContent(codes[dealedLineNum + 1:])
dealedLineNum += curnum
if state == '':
throw_error(get_cur_info() + s)
continue
outputln(elsebeginLabel + ':')
dealCodes(funcname, body, corvar, loopnum + 1)
outputln(elseendLabel + ':')
outputln(allendLabel + ':')
outputln('')
loopnum += 1
pass
elif re.match('^for(.*;.*;.*)$',s):
pass
else:# 赋值语句或单条表达式或函数
dealExpression(s,'$v1', funcname, corvar)
pass
打个比方的话, dealCodes函数就是一台电脑的BIOS, 真正的大头, 也就是操作系统是之后调用的dealExpression函数.
dealExpression函数是整个编译器的准核心. 一切bug都产生与此并消灭于此. 现在这里的代码连作者本人都很难看懂.
整个代码的大意是根据其所在的函数 prefuncname, 及对应的变量表corvar, 处理运算代码exp, 并将值保存到saveto寄存器里. 注意, 即使是 a = b + 1这样的语句也是有返回值的, 返回值就是a的值. 所以本质上a = b + 1 和 b+1 是没有任何区别的. 等号也只是一个普通的二元运算符而已. (虽然处理的时候分开处理了)
具体代码如下:
def dealExpression(exp, saveto, prefuncname, corvar):
parts = toParts(exp, prefuncname, corvar)
suffix = midToSuffix(parts)
ansvtype = ''
if len(suffix) == 0:
print 'NoSuffix'
for i in suffix:
print 'suffix:'
print i[0],'#',i[1],'#',i[2]
print '\n'
##calc mid and save to saveto
stack = []
failFlag = 0
print 'dealE begin: ' + exp
for i,tp,vtype in suffix:
print 'i,tp,vtype:',i,tp,vtype
if tp == 'function' or tp == 'array' or tp == 'const' or tp == 'variable' or tp == 'port':
stack.append((i,tp,vtype))
elif tp == 'symbol' and operation_units[i] == 2:
r = stack.pop()
l = stack.pop()
print 'r:',r
print 'l:',l
rs = '$a3'
if r[1] == 'register': ##if the tp is register, it must be a value calculated and pushed before
##but the value in reg may be replaced, so we must pop from stack to get the value
outputln('POP ' + rs)
else:
if not assignr(r,rs,prefuncname, corvar):
print 'error: if not assignr(r,rs,prefuncname, corvar):'
failFlag = throw_error(get_cur_info() + exp)
break
if i in assign_operation:
ansvtype = rassign((rs, r[2]), l, prefuncname, corvar)
if ansvtype == '':
failFlag = throw_error(get_cur_info() + exp)
break
if not rassignrWithStyle(saveto, rs, l[2], r[2]):
failFlag = throw_error(get_cur_info() + exp)
break
else:
ls = '$a2'
if l[1] == 'register':
outputln('POP ' + ls)
else:
if not assignr(l,ls,prefuncname, corvar):
failFlag = throw_error(get_cur_info() + exp)
break
ansvtype = calc2((ls,l[2]),i,(rs,r[2]),saveto) ##这样是无法写回数据的. 只能把右值assign给寄存器. 左值不行
stack.append((saveto,'register',ansvtype))
outputln('PUSH ' + saveto)
elif i == '$':
#需要把所有指针类型转成对register的解析
l = stack.pop()
print '$l:',l
if l[1] == 'const':
if not assignr(l,saveto,prefuncname,corvar):
failFlag = throw_error(get_cur_info() + exp)
print get_cur_info() + 'pointer: const to reg failed'
pass
outputln('PUSH ' + saveto)
stack.append((saveto,'port','sint32'))
pass
elif tp == 'symbol' and operation_units[i] == 1:
l = stack.pop()
ls = '$a2'
if l[1] == 'register':
outputln('POP ' + ls)
else:
assignr(l,ls,prefuncname, corvar)
print 'calc1:',ls,l[2],i,saveto
ansvtype = calc1((ls,l[2]),i,saveto)
stack.append((saveto,'register',ansvtype))
outputln('PUSH ' + saveto)
else:
failFlag = throw_error(get_cur_info() + exp)
break
print 'dealE end len:%d'%(len(stack))
if not failFlag:
if len(stack) == 0:
for i,j in corvar.items():
print 'i,j:',i,j.corname
print 'exp:',exp
print 'stack[0]:',stack[0]
i,tp,vtype = stack[0]
if tp == 'register':
outputln('POP ' + saveto)
else:
assignr(stack[0], saveto, prefuncname, corvar)
return stack[0][2]
else:
for i in stack:
if i[1] == 'register':
outputln('POP $t0')
return 'NoVtype'
函数的工作流程为:
-
将exp分块. 利用了toParts函数
#@call string, prefuncname, corvar #@return [(a_part, left_string, part_type, part_vtype)] def toParts(s, prefuncname, corvar): ret = [] while len(s) > 0: tmp = readapart(s, prefuncname, corvar) if tmp[0] != '': ret.append((tmp[0],tmp[2],tmp[3])) s = tmp[1].strip() for i in range(len(ret)): if ret[i][0] == '-': if i == 0 or ret[i-1][0] == '(' or ret[i-1][1] == 'symbol': ret[i] = ('`',ret[i][1],ret[i][2]) if ret[i][0] == '+': if i == 0 or ret[i-1][0] == '(' or ret[i-1][1] == 'symbol': ret = ret[:i] + ret[i+1:] if ret[i][0] == '*': if i == 0 or ret[i-1][0] == '(' or ret[i-1][1] == 'symbol': ret[i] = ('$',ret[i][1],ret[i][2]) return ret
返回值类型为list, 内部是每一部分的( 代码, 类型, 值类型 ), 类型包括 function/array/const/variable/port, 值类型为[u/s]int[08|16|32].
-
中缀转后缀. 为了处理中缀运算, 利用逆波兰表达式, 转成后缀之后顺序处理.
逆波兰算法:
将中缀表达式转换成后缀表达式算法:
- 从左至右扫描一中缀表达式。
- 若读取的是操作数,则判断该操作数的类型,并将该操作数存入操作数堆栈
- 若读取的是运算符
- 该运算符为左括号"(",则直接存入运算符堆栈。
- 该运算符为右括号")",则输出运算符堆栈中的运算符到操作数堆栈,直到遇到左括号为止。
- 该运算符为非括号运算符:
- 若运算符堆栈栈顶的运算符为括号,则直接存入运算符堆栈。
- 若比运算符堆栈栈顶的运算符优先级高或相等,则直接存入运算符堆栈。
- 若比运算符堆栈栈顶的运算符优先级低,则输出栈顶运算符到操作数堆栈,并将当前运算符压入运算符堆栈。
- 当表达式读取完成后运算符堆栈中尚有运算符时,则依序取出运算符到操作数堆栈,直到运算符堆栈为空。
逆波兰表达式求值算法:
- 循环扫描语法单元的项目。
- 如果扫描的项目是操作数,则将其压入操作数堆栈,并扫描下一个项目。
- 如果扫描的项目是一个二元运算符,则对栈的顶上两个操作数执行该运算。
- 如果扫描的项目是一个一元运算符,则对栈的最顶上操作数执行该运算。
- 将运算结果重新压入堆栈。
- 重复步骤2-5,堆栈中即为结果值。
中缀转后缀代码如下:
def midToSuffix(l): ret = [] symbols = [('#','NoType','NoVtype')] for i,tp,vtype in l: if tp == 'function' or tp == 'array' or tp == 'const' or tp == 'variable' or tp == 'port': ret.append((i,tp,vtype)) elif i == '(': symbols.append((i,tp,vtype)) elif i == ')': tmp = symbols.pop() while tmp[0] != '(': ret.append(tmp) tmp = symbols.pop() else: if symbols[-1][0] == '(': symbols.append((i,tp,vtype)) elif priority[i] <= priority[symbols[-1][0]]: symbols.append((i,tp,vtype)) else: while priority[i] > priority[symbols[-1][0]]: ret.append(symbols.pop()) symbols.append((i,tp,vtype)) for tmp in reversed(symbols[1:]): ret.append(tmp) return ret
后面的部分是对后缀表达式的计算. 真正的计算只有碰到1元或2元运算符时才会出现. 运算的处理思路为: 将运算用到的 元 通过assignr()赋给临时寄存器, 临时寄存器运算并将结果写回saveto寄存器. 具体请看代码旁边的注释. 解释太麻烦.
关于函数的处理, 虽然它本身也是属于上面的那部分, 但因为很重要单独提出来另说. 函数的调用, 其实就是将一个函数的返回值赋给一个寄存器, 所以核心部分在assignr这么一个方法里.
#assign the value in x to reg
#@return successful
def assignr(x , reg, prefuncname, corvar):
# x[0] : name x[1] : type x[2] : variable_type (['void00','sint08','sint16','sint32','uint08','uint16','uint32'])
# tp == 'function' or tp == 'array' or tp == 'const' or tp == 'variable' or tp == 'port':
name = x[0]
tp = x[1]
vtype = x[2]
print 'assignr ',x,reg
if tp == 'function':
##prefuncname 调用 x[0] : funcName
##corvar --- functionDict[funcName].vardict
##params 传给 functionDict[funcName].params
funcName = name[:name.find('(')].strip()
params = name[name.find('(')+1:name.rfind(')')].strip()
if params == '':
params = []
else:
params = [x.strip() for x in params.split(',')]
if len(params) != len(functionDict[funcName].params):
throw_error(get_cur_info() + x)
return False
outputln('PUSHA ##'+prefuncname)
f = functionDict[funcName]
##开始传参数
for i in range(len(params)):
here = params[i]
aimName, aimVarType = f.params[i]
aimRealName = f.vardict[aimName].corname
if here in corvar and corvar[here].type > 1 and f.vardict[aimName].type > 1: #传递整个数组
hereRealName = corvar[here].corname
hereVarType = corvar[here].vtype
size = f.vardict[aimName].type
for i in range(size):
readArrayInData('$a0',hereRealName,hereVarType,i)
saveToArrayInData('$a0',aimRealName,aimVarType,i)
continue
elif here in corvar and corvar[here].type > 1 or f.vardict[aimName].type > 1:
throw_error(get_cur_info() + x)
rassignr(reg, '$zero')
return
else: ##aim 必定是0 - reg, 1 - 内存的单个变量两种形式;
ansvtype = dealExpression(here, '$a0', prefuncname, corvar)
var = f.vardict[aimName]
print 'func: aimName:' + aimName,'var:'+aimRealName, ansvtype
if var.type == 0:
rassignrWithStyle(aimRealName, '$a0', var.vtype, ansvtype)
elif var.type == 1:
saveToArrayInData('$a0', aimRealName, aimVarType, 0)
print 'func:var assign end'
#函数执行
outputln('jal ' + funcName + '_begin')
#执行结束, 结果存于$v0
outputln('POPA ##' + prefuncname) #PUSHA, POPA操作不能动$v的值
rassignr('$v0', reg)
elif tp == 'array':
arrayName = x[0][:name.find('[')].strip()
param = name[name.find('[')+1:name.rfind(']')].strip()
dealExpression(param, '$a0', prefuncname, corvar)
realArrayName = corvar[arrayName].corname
readArrayInData(reg, realArrayName, vtype, '$a0')
elif tp == 'const': # vtype must be sint32
exp = x[0]
realnum = 0
if exp.find('x') != -1:
realnum = int(exp,16)
else:
realnum = int(exp,10)
if abs(realnum) <= 65535:
outputln('ori %s,$zero,%s'%(reg,exp))
else:
pre = (realnum >> 16) & ((1 << 16) - 1)
aft = realnum & ((1 << 16) - 1)
pres = ''
afts = ''
for i in range(16):
pres += str(pre & 1)
pre >>= 1
afts += str(aft & 1)
aft >>= 1
pre = int(pres[::-1],2)
aft = int(afts[::-1],2)
outputln('ori %s,$zero,%d'%(reg,pre))
outputln('sll %s,%s,16'%(reg,reg))
outputln('ori %s,$zero,%d'%(reg,aft))
elif tp == 'variable':
var = corvar[name]
if var.type == 0:
rassignr(reg, var.corname)
elif var.type == 1:
if vtype == 'sint08':
outputln('lb %s,%s(%s)'%(reg, var.corname, '$zero'))
pass
elif vtype == 'uint08':
outputln('lbu %s,%s(%s)'%(reg, var.corname, '$zero' ))
pass
elif vtype == 'sint16':
outputln('lh %s,%s(%s)'%(reg, var.corname, '$zero' ))
pass
elif vtype == 'uint16':
outputln('lhu %s,%s(%s)'%(reg, var.corname, '$zero' ))
pass
elif vtype.strip() == 'sint32' or vtype.strip() == 'uint32':
outputln('lw %s,%s(%s)'%(reg, var.corname, '$zero'))
else:
throw_error(get_cur_info() + name)
rassignr(reg, '$zero')
pass
elif tp == 'port':
outputln('POP ' + reg)
outputln('lw %s,0(%s)'%(reg,name))
else:
throw_error(get_cur_info() + x[0])
rassignr(reg, '$zero')
return True
和汇编器协商, 自定义了PUSHA/POPA这么一个伪代码, 功能是将所有寄存器+所在函数所有变量(寄存器型+.data型)全部push/pop到栈里. 这样, 在调用一个函数时, 首先PUSHA, 之后传值, 跳转到函数开头, return到调用函数处, POPA恢复现场. 一切完美. 递归只是一种特殊的函数调用, 没有什么特别的.
注意, 在生成PUSHA命令的时候, 编译器不仅仅输出PUSHA, 同时在后面空了一个空格后, 输出这么一条注释: ##函数名. (函数名即为当前所在函数的函数名); POPA同理. 比如:
PUSHA ##main
这样, 汇编器在识别出PUSHA/POPA之后, 读取后面的注释就可以知道当前所在函数的函数名, 同理也就知道了.data中该函数的入口位置,(还记得我们的这次实现中最讨巧的地方么? .data区的变量都是按照函数簇集的, 在每块函数所属的变量前都加了一个新的变量, 变量名为函数名, 值为该函数之后所占.data区的字节(8位大小)个数. ) 读取该位置的值就知道了该函数在.data里占用的大小, 继续向后读取该大小的8位数并push到栈里即可. 这样函数的递归就可以实现了.
在本编译器中, 指针是一种特殊的运算. 指针类型的数据, 比如 int *a
这种, 仅仅是在定义时的特殊处理, 编译器内部其实将其解析成了int a
类型. 因为真正是端口操作时, 运算还需要*
操作符. 所以, *操作符可以看成是1元右结合运算符, 当它出现时, 提取星号和一个操作数, 并将其值赋给一个寄存器, 类型设为port, 重新压回堆栈. 之后出现赋值时直接sw|lw即可.
每条语句的处理, 最后都可以化简为两种运算: 单元运算或二元运算. 单元运算包括 非, 负, 否, 指针; 其它运算,包括赋值运算都归为2元运算. 所以通过两个函数来处理即可. 因为涉及不同位数和unsigned类型, 里面同样有各种坑, 比如一个未越界但满32位的uint32 减一个未越界的sint32. 这时候因为要把uint转为sint来处理, 所以会造成越界, 但语义上其实结果不应该越界. 所以必须仍然按照uint判断正负值并计算, 最后再根据正负值转为回sint.
alpha版历时一周, 每日从早9点写到早2点. 虽不健康, 自有其乐趣. 后发现被MiniC要求版本坑, 更新beta版, 大概3,4日左右. 其中两个calc就花了1天. 最后验收之前发现大量bug, 一路从1.0改到2.0, 直到验收完之后现在在更新手册时又发现了指针bug和传数组bug... 总之能改的都改了.
P.S. 写文档时发现, 陪伴了2年的RMP屏幕出现了亮斑..保修期已过..