超简单易懂的编译原理词法分析——python

编译原理词法分析——python

1.词法分析

词法分析是计算机科学中将字符序列转换为单词序列的过程,进行词法分析的程序或者函数叫作词法分析器,也叫扫描器。在本程序中,我通过python实现了一个简单的词法分析器,该程序并不包含完整的词法分析,因为给的例子并不是很难,所以就没有添加很多,但是道理就是这样的道理。

2.需要分析的代码段

/*example*/
      b=1\
00
101:a=2*(1+3)
       IF(b>10) THEN
             a=1
       ELSE  IF(b>=5) THEN
             a=2
       ELSE
             GOTO 101

3.函数

#coding=utf-8
import sys
import string
key_word = ['IF','THEN','ELSE','GOTO'] #定义关键词
operator  = ['=','+','>=','*','>']             #定义运算符
delimiters = [':','(',')']                            #定义界符
#预处理函数,对例程进行预处理,去掉多余的空格、注释
def preprocess(file_name):
          try: #try except 使程序不会因为异常而中断
                fp_read = open(file_name,'r')              #打开需要分析的文件
                fp_write = open('preprocess.txt','w')
                sign = 0  #每当上一个字符为空格时,将sign变为1,通过这样来保证每个需要空格的地方不会有多余的空格
                while True:
                      read = fp_read.readline()   #循环读取每一行字符 
                if not read:
                      break         #如果没有读取到任何东西直接结束循环
                length = len(read)      #获得当前字符串的长度
                i = -1
                while i < length - 1:       #循环读取每个字符
                         i+=1
                if read[i] == ' ':    
                    if sign == 1:         #读取到空格,如果sign为1说明上一个字符也是空格,那个可以忽略当前空格
                        continue
                    else:                    #如果sign不为1,说明上一个字符不是空格,那么就输出当前空格
                        sign = 1
                        fp_write.write(' ')
                elif read[i] == '\t':    #同上读取到空格的操作,对于水平制表符(tab)同样输出为一个空格,如果已经输出过一个空格了那就选择跳过
                    if sign == 1:
                        continue
                    else:
                        sign = 1
                        fp_write.write('')
                elif read[i] == '\n':   #同上读取到水平制表符(tab)的操作
                    if sign == 1:
                        continue
                    else:
                        sign = 1
                        fp_write.write(' ')
                elif read[i] == '/' and read[i+1] == '*':    #如果读取到/* 即注释
                        i = i + 2                               #从星号之后的第一个字符开始遍历直到读取到下一对注释
                        while True:
                            if read[i] == '*' and read[i+1] == '/':
                                break
                            i = i + 1
                        i = i + 1      #遍历完之后i停留在左斜杠,所以需要+1达到下一个字符
                elif read[i] == '\\':      #如果读取到续行符,之所以是双斜杠因为右斜杠是转义字符,双右斜杠等于单右斜杠,即续行符
                    break
                else:       #除了以上这些就直接输出字符就好
                    fp_write.write(read[i])
                    sign = 0
                fp_write.write('#')#在最后输出一个井号
            except Exception:
                print('Errors')

以上为预处理函数,该函数先打开需要分析的文件(代码段),然后对多余的空格进行删除,去掉续行符,去掉注释,得到一段精简的代码。

#K:关键字 I:标识符 C:常数 O:运算符 P:界符 L:标号
def process(file_name):
    try:
        fp_read=open(file_name,'r')        #打开预处理之后得到的文件
        fp_write=open('result.txt','w')     #打开一个新的文件,为了将得到的二元式表输入进去
        lines = fp_read.readlines()          #依旧是按行读取
        i = -1
        for line in lines:                         #逐行遍历
            length = len(line)                 #获得当前行字符长度为了遍历每个字符
            while  i < length - 1:
                i+=1
                if line[i] == ' ':              #当前字符为空格,直接跳过
                    continue
                if line[i] in delimiters:       #当前字符在最开始定义的界符集中,那么就直接输出(P,当前字符)
                    print('(P,',line[i],')',file=fp_write)
                    continue
                if line[i] in operator:        #当前字符在运算符集中
                    if line[i-1] in operator:    #判断运算符,祥见程序之后注释(1)
                        continue
                    print('(O,',end='',file=fp_write)   #end=''实现print不换行,file=fp_write实现将print的内容输出到文件中
                    if line[i+1] in operator:
                        print(line[i],line[i+1],')',sep='',file=fp_write) #sep=''实现print连续输出不加空格,为了输出>=而不是> =
                    else:
                        print(line[i],')',file=fp_write)
                    continue
                if line[i-1].isdigit():        #判断数字,祥见程序之后注释(2)
                    continue
                if line[i].isdigit():
                    t = i
                    while line[i].isdigit():
                        i+=1
                    if line[i] == ':' or line[i] == '#':
                        print('(L,',end='',file=fp_write)
                    else:
                        print('(C,',end='',file=fp_write)
                    i = t
                    while line[i].isdigit():
                        print(line[i],end='',file=fp_write)
                        i+=1
                    print(')',file=fp_write)
                    i-=1
                    continue
                j = 0
                temp = ''              #判断关键词及标识符,详见程序之后注释(3)
                while True:
                    if  line[i] == ' ' or line[i] in operator or line[i] in delimiters:
                        break
                    else:
                        j+=1
                    i+=1
                    if i > length-1:
                        break
                temp = line[i-j:i]
                if temp in key_word:
                    print('(K,',temp,')',file=fp_write)
                else:
                    if temp == '#':
                       break
                    print('(I,',temp,')',file=fp_write)
                i-=1
    except Exception:
        print('Error')

该函数分析预处理之后的代码,并将得到的二元式表输出到文件中。

def main():
      preprocess('source.txt')
      process('preprocess.txt')
if __name__=='__main__':
      main()

4.注释

(1). 判断运算符,在本程序中我只考虑了两种情况的运算符,一种是单独一个的运算符,例如“+”,“-”,另一种是两个运算符,例如">=","<="。整个思路是当识别到当前字符是运算符的时候,判断下一个字符是不是也是运算符,如果是运算符的话,那就连续输出两个运算符,如果不是就输出单独一个运算符,如果以为这样就结束了,那就会发现在结果中多了一些二元式,这是因为当连续输出两个运算符的时候,如果不加处理,当遍历到下一个运算符,还会再输出一次,我的解决办法就是再加一个if判断上一个字符是不是运算符,如果是,那就直接跳过,因为如果是的话,那么当前运算符肯定已经输出过了。
(2). 判断数字,在本程序中,数字分为两种,一种是给变量赋的值,另一种就是程序段之前的标号(或者GOTO之后的),通过观察发现区分两种数字的关键是后面紧跟着的是不是“:”以及前面有没有“GOTO”(在本问题中我采用的是后面紧跟着#,这样简单一些,但仅适用于本次处理的程序段简单,如果复杂的还需要在此处完善)。当读取到数字的时候,程序会先保存一下当前的i值,然后循环一次得到当前数字是几位数,以便得到最后一位数之后是冒号或者井号或者是其他来判断当前数字是标号还是常数,之所以保存了i的值,就是为了在判断完成之后,让i的值复位,然后重新循环读取整个数字。记得在读取完整个数字之后,让i-1,因为在循环遍历的时候会+1。
(3). 判断关键词和标识符,定义j来表示关键词或者标识符的长度,temp为空的字符串,当遇到空格、运算符、界符就跳出循环或者当i超出了length的范围,line[i-j:i]即为从i-j到i对应的字符串,也就是遍历遇到要么是关键词要么是标识符的字符串,如果这个字符串在key_word数组中,那么就是关键词,否则就是标识符。

5.运行得到的结果

(I, b )
(O,= )
(C,100)
(L,101)
(P, : )
(I, a )
(O,= )
(C,2)
(O,* )
(P, ( )
(C,1)
(O,+ )
(C,3)
(P, ) )
(K, IF )
(P, ( )
(I, b )
(O,> )
(C,10)
(P, ) )
(K, THEN )
(I, a )
(O,= )
(C,1)
(K, ELSE )
(K, IF )
(P, ( )
(I, b )
(O,>=)
(C,5)
(P, ) )
(K, THEN )
(I, a )
(O,= )
(C,2)
(K, ELSE )
(K, GOTO )
(L,101)

如有错误,希望多加指正,如果你看到了这里,非常感谢。
如果有相关疑问,可以留言也可以加我qq(707390684)我们交流一下。

你可能感兴趣的:(code)