词法分析器-python

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

前言

使用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版本及之后才可以使用
注:文章中的代码不不全的,但是具体的思路都在,你可以选择看看我的思路,自己写代码
或者看这里 有我的源码 :代码链接

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

星星星星星星……

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值