编译原理——编译器前端

写在前面

终于写完了最后一个必做实验,编译原理课程也几乎要告一段落了,在最后一次实验完成之际,我想简单陈述一下我个人对于这门课程的感悟和想法。

之前我常常疑惑,计科和别的专业的区别是什么,为什么小专业学我们学过的课,也学我们不学的课呢,这么看来计科怎么都好像是他们的一个“子集”。

到大三下,我发现了一个“不合群”的课程——编译原理。这是一个只有计科的才有植入的专业核心课,也是计科专业区别于其他专业的唯一标准。

于是责任心鞭策着我要认真对待这门课。争取课代表、每节课坐前排、手写作业认真完成、课程实验撰写博客、选做题积极响应等是我对这门课的态度。

课程涵盖了文法定义、词法分析、符号表处理、各式各样的语法分析、到最后的目标代码生成,从内而外的介绍了编程语言被编译识别的过程,也让我体会到了计算机程序语言被机器所识别的内涵和精髓,这种上下文无关语言的识别过程为我探索自然语言处理等上下文有关语言的研究铺垫了道路,不论是知识开荒还是兴趣培养,这门课对我来说受益匪浅。

最后引用一句实验指导书的片头语:

编译原理课程中蕴含着计算机科学中解决问题的思路、抽象问题和解决问题的方法,其内容可让计算机专业学生“享用一辈子”。

这门课的知识应该是一个计科专业的人贯穿自始至终的课程。

目标任务

实验项目

将词法分析程序设计原理与实现(专题1)和基于SLR(1)分析法的语法制导翻译及中间代码生成程序设计原理与实现(专题5)形成一个程序,即程序的输入为符号串源程序,输出为中间代码四元式序列。

设计要求

(1)词法分析的输入为符号串,词法分析的输出为二元式形式的中间文件;
(2)语法制导翻译读取二元式形式的中间文件并生成中间代码四元式形式的中间文件;
(3)设计两个测试用例(尽可能完备),并给出程序执行结果。

实验1&5封装

由于我的实验一采用了C++所编写,所以首要解决的一个问题就是:

如何利用python调用C++程序?

词法分析器输出重定向

因为需要将词法分析器与SLR语法分析器链接成一个整体,所以需要首先修改词法分析器的输出中间文件,即通过一定方法令词法分析器实现输出重定向功能。

由于词法分析器的输出全部由函数out(int c,string tok)所控制,函数实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void out(int c,string tok){
// 保留字只输出编码
if(tok == "reserved")
cout<<c<<" "<<reserved.at(c)<<endl;
//ID和int输出
else if(c==10||c==11){
cout<<"i"<<" "<<tok<<endl;
}
//注释(不)输出
else if(c==25){
return;
//cout<<"COMMENT "<<tok<<endl;
}
else{
cout<<tok<<endl;
}
}

所以只需要针对其修改输出定向即可,实现语句如下:

1
FILE *fp1 = freopen("/Users/jiahaonan/Desktop/大三下/编译原理/前端展示/out.txt", "w+", stdout);

再此执行词法分析器,可以观察到词法分析器的输出结果均被定向至out.txt文件中
Alt text

封装词法分析器

由于不是同一语言,解决输出重定向问题后,在保证词法分析器功能无误的前提下,需要对词法分析器进行功能封装,即转变为可执行程序

可以通过通过终端下的g++编译器实现,指令如下:

1
g++ -o word_analyse main.cpp

将编译生成的可执行文件word_analyse放入与SLR语法分析类相同目录的文件夹下。

到此,词法分析器的修改封装已成功完成。

若需要在python中调用此可执行文件,需要使用如下代码:

1
2
3
import os

os.system("word_analyse")

语法分析器

由于实验5在设计时采用了面向对象的思想,故在实验6中通过如下代码可以直接引入SLR类来使用,无需修改!

1
2
3
import SLR

SLR.test()

这里的test即为实验5中的主测试函数,代码如下:

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
def test():
# 终结符号集/非终结符号集
Vn = ['A', 'V', 'E', 'T', 'F']
Vt = ['+', '-', '*', '/', '(', ')', 'i', '=']

# 规则集合
rule = {
'A': ["V=E"],
'E': ["E+T", "E-T", "T"],
'T': ["T*F", "T/F", "F"],
'F': ["(E)", "i"],
'V': ["i"]
}

slr = SLR(start='A', Vt=Vt, Vn=Vn, rule=rule)

test_words = ""
test_symbol = ""
f = open("out.txt", 'r')
while (True):
t = f.readline().strip('\n')
if (t == ''): break
test_symbol += t[0]
test_words += t[-1]

slr.compile(symbol=test_symbol, word=test_words, log=True)

数据流

在封装好词法分析器后、引入语法分析器后,下一步需要做的就是如何设计文件I/O流,以衔接词法分析器和SLR语法分析器。

我的设计思想如下:
Alt text

总体来说,共需要使用到3个操作文件:

  1. in.txt:词法分析器的接受输入文件
  2. out.txt:词法分析器的二元组输出文件,同时供语法分析器输入
  3. out1.txt:语法分析器的语法制导翻译过程输出文件

文件的预览如下:
Alt text
Alt text
Alt text

需要说明的是,out1文件为了观测的直观性输出了一些辅助性信息,可以通过控制输出来取消,无大影响。

图形化前端设计

虽然本次实验的前端含义不代表web/图形化前端,但既然已经将工程转移到python下了,就一不做二不休。

根据题目的需求,前端应该具有如下功能:

  1. 接受待分析的代码输入
  2. 对代码进行词法分析,并输出二元式中间文件
  3. 对二元组中间文件进行语法分析,并输出四元式中间文件

这里通过tkinter库来依次完成以上需求。

接受输入

通过一个Entry类型的控件实现输入功能,代码如下:

1
2
3
4
5
tip = Label(root,text="你可以在此输入代码",font=("手扎体-简",18))
tip.place(x=100,y=110)

code = Entry(root,width=18)
code.place(x=100,y=135)

如果要调用code控件的输入内容,只需使用如下函数即可:

1
code.get()

词法分析

对输入的代码进行词法分析应该属于功能选择类,所以我们需要通过两个Button类型的控件实现选择并打印,通过一个Text类型的控件实现词法分析结果的输出

代码如下:

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
# 选择词法分析
def compile_confirm(event):
if(len(code.get())==0):
a = messagebox.showinfo("","您没有输入任何语句")
return

f = open("in.txt", 'w')
var = code.get()
f.write(code.get())
f.write('\n')

word_show = Text(root,height=6,width=30)
word_show.place(x=75,y=235)

# 选择打印结果
def print_confirm1(event):
word_ans = ""
os.system("word_analyse")
with open("out.txt",'r') as f:
word_ans += f.read()

word_show.delete('1.0', 'end')
word_show.insert(INSERT,word_ans)

btn_compile = Button(root, text=" 词法分析 ")
btn_compile.place(x=140,y=180)
btn_compile.bind("<Button-1>",compile_confirm)

btn_print1 = Button(root, text=" 打印结果 ")
btn_print1.place(x=140,y=205)
btn_print1.bind("<Button-1>",print_confirm1)

执行效果如下:
Alt text

语法分析

词法分析成功之后,接下来实现语法分析功能。

语法分析的实现机理与词法分析类似,需要通过一个Button类型的控件实现语法分析功能选择,通过一个Text类型的控件实现语法分析结果的输出

代码如下;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
langu_show = Text(root,height=10,width=30)
langu_show.place(x=75,y=365)
def print_confirm2(event):
langu_ans = ""
SLR.test()
with open("out1.txt",'r') as f:
langu_ans += f.read()

langu_show.delete('1.0', 'end')
langu_show.insert(INSERT,langu_ans)

btn_compile1 = Button(root, text=" 语法分析 ")
btn_compile1.place(x=140,y=335)
btn_compile1.bind("<Button-1>",print_confirm2)

执行效果如下:
Alt text
Alt text
Alt text

到此,前端的设计圆满成功!

附录

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
import os
from tkinter import *
from tkinter import messagebox
import SLR

root = Tk()
root.title("前端展示")
root.geometry('400x600')

title = Label(root,text="编译前端",font=("手扎体-简",30))
title.place(x=120,y=30)


tip = Label(root,text="你可以在此输入代码",font=("手扎体-简",18))
tip.place(x=100,y=110)

code = Entry(root,width=18)
code.place(x=100,y=135)

def compile_confirm(event):
if(len(code.get())==0):
a = messagebox.showinfo("","您没有输入任何语句")
return

f = open("in.txt", 'w')
var = code.get()
f.write(code.get())
f.write('\n')

word_show = Text(root,height=6,width=30)
word_show.place(x=75,y=235)
def print_confirm1(event):
word_ans = ""
os.system("word_analyse")
with open("out.txt",'r') as f:
word_ans += f.read()

word_show.delete('1.0', 'end')
word_show.insert(INSERT,word_ans)

langu_show = Text(root,height=10,width=30)
langu_show.place(x=75,y=365)
def print_confirm2(event):
langu_ans = ""
SLR.test()
with open("out1.txt",'r') as f:
langu_ans += f.read()

langu_show.delete('1.0', 'end')
langu_show.insert(INSERT,langu_ans)


btn_compile = Button(root, text=" 词法分析 ")
btn_compile.place(x=140,y=180)
btn_compile.bind("<Button-1>",compile_confirm)

btn_print1 = Button(root, text=" 打印结果 ")
btn_print1.place(x=140,y=205)
btn_print1.bind("<Button-1>",print_confirm1)

btn_compile1 = Button(root, text=" 语法分析 ")
btn_compile1.place(x=140,y=335)
btn_compile1.bind("<Button-1>",print_confirm2)

root.mainloop()
小手一抖⬇️