提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
使用python语法对源程序进行词法分析,得到词法分析单元
一、词法分析主要做什么?
词法分析是编译程序时唯一与源程序打交道的部分,其主要工作如下:
1.扫描源程序,从源程序中读入字符流到输入缓冲区汇总。
2.按构词规则识别单词,输出单词本身及其类别码。
3.过滤掉源程序中无用成分,如注释、空格、回车换行等。
二、具体步骤
1.读取
首先将源程序读入到内存中
path= 'code.txt' //源文件
content=readFileAndPreDeal(path) //读文件并且预处理
content=scan(content) //词法分析(扫描)
print(content) //打印结果
同时,将你的词法分析规则或者正则表达式定义好。
#正则定义规则
D = r'[0-9]'
L = r'[a-zA-Z_]'
H = r'[a-fA-F0-9]'
E = r'[Ee][+-]?[0-9]+'
FS = r'(f|F|l|L)'
IS = r'(u|U|l|L)'
id = re.compile('[a-zA-Z_][a-zA-Z_0-9]*')
string = re.compile(f'{L}?"(\.|[^\\\"])*"')
num = re.compile(f'{D}*\.{D}+({E})?{FS}|{D}*\.{D}+({E})?{FS}?|0[xX]{H}+{IS}*|0{D}+{IS}*|{D}+{IS}*|{D}+{E}{FS}?|L?\'(\\.|[^\\\'])+\'')
#todo 预处理的正则表达式 将源程序中的多行注释,单行注释,换行符,制表符替换掉
pattern_line = re.compile(r'\n',re.S)
pattern_tab=re.compile(r'\t',re.S)
pattern_single_zhushi=re.compile(r'//\s*?',re.S)
pattern_multi_zhushi=re.compile('/\*(.|\r\n)*\*/',re.S)
2.预处理–readFileAndPreDeal(path)
def readFileAndPreDeal(path):
"""
:purpose: todo 读文件并且预处理
:param path: todo 源代码文件所在路径
:return: todo 从文件读出的源代码 同时去掉多行注释 单行注释 制表符 换行符
"""
with open(path, 'r',encoding = "utf-8") as file:
# todo 读取文件内容
content = file.read()
content = re.sub(pattern_tab, replacement, content)
content = re.sub(pattern_line,' ', content)
content = re.sub(pattern_single_zhushi,replacement,content)
content = re.sub(pattern_multi_zhushi,replacement,content)
return content
3.开始扫描
首先说明一下我的思路–1.顺次读取字符知道遇到空格(我默认遇到空格,空格后面的字符序列和前面的字符序就不是同一个“标识符”了)即停。
2.将此时读到的字符序列和事先准备好的正则表达式进行匹配,会出现两种情况,一是匹配,而是不匹配。首先说一下匹配的情况:
匹配,说明这个字符序列是一个“标识符”,他可以是常数,保留字,字符串等等均有可能,不一定。但有一点可以确定的是,他只可能是其中一种,所以只需要将他和正则表达式挨个匹配,得出结果即可。
在说一下不匹配的情况
不匹配的情况就要复杂的多,当字符序列不匹配时,可能这个字符序列是一个表达式,例如:a+b=c,我们的扫描规则是遇到空格才进行判断,所以这种情况下,会被程序认为是“一个标识符”,可事实上,这根本就不是!所以,这时候就需要回滚,a+b=c回滚一个字符等于a+b=,重新判断是否匹配,匹配则返回,不匹配则继续回滚,直到结束为止。
def scan(content):
"""
:purpose todo 扫描程序生成词法单元
:param content: todo 源程序字符串
"""
content=content[1:] #todo 在读取文件时,有一个文件的符号在源程序开头处,用这个过滤
length=len(content)
result=[]
token=''
i=0
temp=-1
start = 0
end = 0
#todo start 表示还未匹配(token中的)的第一个字符 end表示还未匹配的结尾的下一个
while(end<length):
while(end<length):
if(content[end]!=' '):#todo 如果不是空格就加到token上面去
if token=='': #todo 证明是第一个
start=end
token+=content[end]
end+=1
continue
#todo 实行最大匹配 程序能执行到这里证明是空格出现了
temp=-1
for i in range(0,len(list)): #todo 空格前面的单词恰好是id num string 运算符 界符
if(i<=2):
if ( re.match(list[i], token) and token==re.match(list[i], token).group()):
temp=i
break
else:
if(list[i]==token):
temp=i
break
match temp:
case -1: # todo 没有恰好匹配,那么就要回退
start,end=rollback(start,end,token,result,content)
case 0:
if (token in reserved_word.keys()):
result.append(find(token, reserved_word))
start+=len(token)
token = ''
temp = -1 #todo 每次匹配后,要将一切有状态和选择的变量 “初始化”
end+=1
break
else:
result.append(tuple(['ID', token]))
start += len(token)
end += 1
break
case 1:
result.append(tuple(['STRING', token]))
start += len(token)
token = ''
temp = -1
end += 1
break
case 2:
result.append(tuple(['NUM', token]))
start += len(token)
token = ''
temp= -1
end += 1
break
case 3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19:
value=subset.get(token)
result.append(tuple([value,token]))
start += len(token)
token=''
temp=-1
end+=1
break
case _:
err('主case_',token,result)
break
return result
回滚函数:
def rollback(start,end,token,result,content):
"""
:purpose: todo 主程序扫描到空格时,如果token中的字符不是一个词法单元,那么就回滚
:param start: todo token的第一位字符在content中的下标
:param end: todo 空格在content 中的下标
:param token: todo 待回滚的字符串
:param result: todo 词法单元列表
:param content: todo 源程序字符串
:return: todo 回滚后 新的
"""
i=0 #todo 选择正则
temp=-1 # todo 选择正则
# print('开始回退:')
while(start<end):
cursor=end
while(start<cursor):
token=content[start:cursor] # main()
# print('----------')
# print("token:",token)
# print("子cursor:",cursor)
# print("子start:",start,content[start])
# print("子end:",end,content[end])
# print('-----------')
for i in range(0, len(list)): #todo 空格前面的单词恰好是id num string 运算符 界符
if(i<=2):
if ( re.match(list[i], token) and token==re.match(list[i], token).group()):
temp=i
else:
if(list[i]==token):
temp=i
# print(temp)
match temp:
case -1: # todo 没有匹配,那么就要继续回退
cursor-=1
if(cursor==start):
if content[cursor]==' ':
start+=1
break
else:
err('子cursor',token,result)
# start+=1
break
case 0:
if (token in reserved_word.keys()):
result.append(find(token, reserved_word))
start+=len(token)
token = ''
temp = -1
else:
result.append(tuple(['ID', token]))
start += len(token)
token = ''
temp = -1
case 1:
result.append(tuple(['STRING', token]))
start += len(token)
token = ''
temp = -1
case 2:
result.append(tuple(['NUM', token]))
start += len(token)
token = ''
temp= -1
case 3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19:
value=subset.get(token)
result.append(tuple([value,token]))
start += len(token)
token=''
temp=-1
case _:
err('子case_',token,result)
break
return start+1,end+1
4.运行
源程序文件:
int main()
{
double W, b;
double Y_predicted;
int passenger_id, survived, pclass;
W = 0.0;
b = 0.005;
Y_predicted = 1;
passenger_id= 1000L;
survived = 505u;
pclass = L'\10a0cc';
if (passenger_id >= 100)
W += 5.6372e-10;
else
b = 9.78f -0.005 * W;
if (Y_predicted < 1)
passenger_id = 0X654E;
else
passenger_id = 0X054EL;
survived = 2^pclass;
print("end");
}
运行结果 (本结果是删除了survived =2^pclass
这行代码得来的,因为在最初的词法规则定义时,^
没有,程序运行到这里,应该要报错:没有这种规则(自己定义的)
。但是这样就无法看到最终的结果了,所以除此下策。)
('INT', 'int')
('ID', 'main')
('LR', '(')
('RR', ')')
('LB', '{')
('DOUBLE', 'double')
('ID', 'W')
('COMMA', ',')
('ID', 'b')
('SEMI', ';')
('DOUBLE', 'double')
('ID', 'Y_predicted')
('SEMI', ';')
('INT', 'int')
('ID', 'passenger_id')
('COMMA', ',')
('ID', 'survived')
('COMMA', ',')
('ID', 'pclass')
('SEMI', ';')
('ID', 'W')
('ASSING', '=')
('NUM', '0.0')
('SEMI', ';')
('ID', 'b')
('ASSING', '=')
('NUM', '0.005')
('SEMI', ';')
('ID', 'Y_predicted')
('ASSING', '=')
('NUM', '1')
('SEMI', ';')
('ID', 'passenger_id')
('ASSING', '=')
('NUM', '1000L')
('SEMI', ';')
('ID', 'survived')
('ASSING', '=')
('NUM', '505u')
('SEMI', ';')
('ID', 'pclass')
('ASSING', '=')
('NUM', "L'\\10a0cc'")
('SEMI', ';')
('IF', 'if')
('LR', '(')
('ID', 'passenger_id')
('GE', '>=')
('NUM', '100')
('RR', ')')
('ID', 'W')
('ADD', '+')
('ASSING', '=')
('NUM', '5.6372e-10')
('SEMI', ';')
('ELSE', 'else')
('ID', 'b')
('ASSING', '=')
('NUM', '9.78f')
('SUB', '-')
('NUM', '0.005')
('MUL', '*')
('ID', 'W')
('SEMI', ';')
('IF', 'if')
('LR', '(')
('ID', 'Y_predicted')
('LT', '<')
('NUM', '1')
('RR', ')')
('ID', 'passenger_id')
('ASSING', '=')
('NUM', '0X654E')
('SEMI', ';')
('ELSE', 'else')
('ID', 'passenger_id')
('ASSING', '=')
('NUM', '0X054EL')
('SEMI', ';')
('ID', 'print')
('LR', '(')
('STRING', '"end"')
('RR', ')')
('SEMI', ';')
('RB', '}')
总结
词法分析是编译过程的第一步,是将源程序转化为词法单元的过程。思路上一旦悟透,代码应该不难写。
注:我使用的python解释器的版本是3.10,因为match语法(类似于c语言的switch)在3.10版本及之后才可以使用
注:文章中的代码不不全的,但是具体的思路都在,你可以选择看看我的思路,自己写代码
或者看这里 有我的源码 :代码链接