• 主页
  • 相册
  • 随笔
  • 目录
  • 存档
Total 244
Search AboutMe

  • 主页
  • 相册
  • 随笔
  • 目录
  • 存档

四则运算及LL1语法检测器

2019-11-06

主要实现:

  • 从零实现语法分析first、follow集
  • 检测四则运算语法正确性
  • 对任意输入语法,检测是否符合LL(1)语法
  • tkinter实现GUI

不完全实现警告:

  • 不支持带空格的四则运算,但你可以 很轻易地 改为支持

1. lex对输入四则运算处理

主要是用了ply库(如果你不想重新实现ll(1)的话应该是直接用这个),这里只用其中的lex库来实现分割输入字符串

轮子:ply.lex介绍

  • _Lex.py
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
import ply.lex as lex


tokens = (
'NUMBER',
'PLUS',
'MINUS',
'TIMES',
'DIVIDE',
'LPAREN',
'RPAREN',
)


t_PLUS = r'\+'
t_MINUS = r'-'
t_TIMES = r'\*'
t_DIVIDE = r'/'
t_LPAREN = r'\('
t_RPAREN = r'\)'


def t_NUMBER(t):
r'\d+'
t.value = int(t.value)
return t


def t_newline(t):
r'\n+'
t.lexer.lineno += len(t.value)


t_ignore = ' \t'


def t_error(t):
print("Illegal character '%s'" % t.value[0])
t.lexer.skip(1)

2. 主体实现

  • _LL1.py
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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
from collections import defaultdict, deque
import tkinter as tk
from tkinter import ttk, messagebox
import string
import os
from _Lex import *


f = defaultdict(list)
Fn = list()
Ft = set()
_ll1 = dict()
s = ""
punc = string.punctuation

def tlex(data):
p = deque()
q = str()
tbl = None
lexer = lex.lex()
lexer.input(data)
while True:
tok = lexer.token()
if not tok:
break
p.append((tok.type, str(tok.value)))
q += str(tok.value) # 放弃空格处理
p.append(('#', '#'))
q += '#'
return p, q


def gfirst(i, g, Vn, Vt, first):
if i not in first.keys():
for j in g[i]: # j=[]
if(j[0]==i):
tk.messagebox.showinfo(title='错误', message="左递归")
os._exit()
gfirst(j[0], g, Vn, Vt, first)
first[i] |= first[j[0]]
if (set(Vt).isdisjoint(set(j))):
for k in j:
gfirst(k, g, Vn, Vt, first)
first[i] |= first[k]
if 'null' in first[k]:
if(k != j[-1]):
first[i].remove('null')
continue
break


def gfollow(i, g, Vn,Vt,follow, first):
if i not in follow.keys() or i == Vn[0]:
p = set((a, v) for a in Vn for v, b in enumerate(
g[a]) if len(b) >= 2 and i == b[1]) # a -> +i- k[0]=a k[1]=v 去重
for k in p:
j = [x for x in g[k[0]]][k[1]] # j=['+','i','-']
if len(j) == 2 :
if(k[0] != i):
gfollow(k[0], g, Vn,Vt, follow, first)
if k[0] in follow.keys():
follow[i] |= follow[k[0]]
elif len(j) == 3 :
if(k[0] != i):
tp = first[j[2]].copy()
if 'null' in tp:
gfollow(k[0], g, Vn, Vt,follow, first)
if k[0] in follow.keys():
follow[i] |= follow[k[0]]
tp.remove('null')
follow[j[1]] |= tp


def deal(g, Vn, Vt, first, follow):
for i in Vt:
first[i].add(i)
first['null'].add('null')
for i in Vn:
gfirst(i, g, Vn, Vt, first)
for i in g.values():
for j in i:
first[tuple(j)] |= first[j[0]] # ('null',) 'null'
follow[Vn[0]].add('#')
for i in Vn:
gfollow(i, g, Vn,Vt, follow, first)


def gmar():
Vn = ['Expr', 'Factor', 'TermTail', 'Term', 'ExprTail']
Vt = {'PLUS', 'MINUS', 'TIMES', 'DIVIDE', 'LPAREN', 'RPAREN', 'NUMBER'}
g = defaultdict(list)
first = defaultdict(set)
follow = defaultdict(set)
ll1 = dict()
g['Expr'].append(['Term', 'ExprTail'])
g['ExprTail'].append(['PLUS', 'Term', 'ExprTail'])
g['ExprTail'].append(['MINUS', 'Term', 'ExprTail'])
g['ExprTail'].append(['null'])
g['Term'].append(['Factor', 'TermTail'])
g['TermTail'].append(['TIMES', 'Factor', 'TermTail'])
g['TermTail'].append(['DIVIDE', 'Factor', 'TermTail'])
g['TermTail'].append(['null'])
g['Factor'].append(['LPAREN', 'Expr', 'RPAREN'])
g['Factor'].append(['NUMBER'])
deal(g, Vn, Vt, first, follow)
for k, _ in g.items():
for v in _: # k -> v
flg = False
for p in first[tuple(v)]:
if p != 'null':
ll1[(k, p)] = (k, v)
continue
flg = True
if(flg):
for p in follow[k]:
ll1[(k, p)] = (k, v)
return ll1


def isgmar():
first = defaultdict(set)
follow = defaultdict(set)
_ll1 = dict()
deal(f, Fn, Ft, first, follow)
for k, _ in f.items():
for v in _: # k -> v
flg = False
for p in first[tuple(v)]:
if p != 'null':
if((k, p) in _ll1.keys()):
return False
_ll1[(k, p)] = (k, v)
continue
flg = True
if(flg):
for p in follow[k]:
if((k, p) in _ll1.keys()):
return False
_ll1[(k, p)] = (k, v)
return _ll1


def draw():
window = tk.Tk()
window.title('四则运算--LL(1)')
window.geometry('600x400')
l1 = tk.Label(window,
text='输入四则运算', # 标签的文字
bg='pink', # 背景颜色
font=('Arial', 12), # 字体和字体大小
height=2) # 标签长宽
l1.pack() # 固定窗口位置
e1 = tk.Entry(window)
e1.insert(40, '[0-9+-*/()]')
e1.pack()

def inputdata():
data = e1.get()
p, q = tlex(data)
tb = gmar()

tr = ttk.Treeview(window, height=10, column=('col0', 'col1', 'col2'))
tr.column('col0', width=100, anchor='center')
tr.column('col1', width=100, anchor='center')
tr.column('col2', width=200, anchor='center')
tr.heading('col0', text='stack')
tr.heading('col1', text='input')
tr.heading('col2', text='output')
stk = ['#', 'Expr']
idq = p # [('NUMBER','1')]
ipt = q
cnt = 0
while True:
opt = str()
if(stk[-1] == idq[0][0] == '#'):
tk.messagebox.showinfo(title='成功', message="匹配成功,语法正确")
break

elif(stk[-1] == idq[0][0]):
stk.pop()
idq.popleft()
_ = list(ipt)
_.pop(0)
ipt = "".join(_)

elif((stk[-1], idq[0][0]) in tb.keys()):
k = tb[(stk[-1], idq[0][0])]
_ = list()
opt = k[0]+'->'
for i in k[1]:
opt += ' '+i
_.append(i)
stk.pop()
if(_[0] != 'null'):
_.reverse()
stk += _
else:
tk.messagebox.showinfo(title='失败', message="匹配失败,不符合文法")
break
cnt += 1
tr.insert('', cnt, values=(stk[-1], ipt, opt))
tr.pack()

b1 = tk.Button(window, text='确认', width=15,
height=2, command=inputdata)
b1.pack()
l2 = tk.Label(window,
text='输入文法',
bg='pink',
font=('Arial', 12),
height=2)
l2.pack()
e2 = tk.Entry(window)
e2.insert(40, '[A]->a')
e2.pack()
e3 = tk.Entry(window)
e3.insert(40, 'A->[a]')
e3.pack()

txt2 = tk.Text(window, width=55, height=7)
txt2.pack()

def inputDict():
A = str(e2.get())
if(A not in Fn):
Fn.append(A)
rstr = str(e3.get())
rstr = rstr.split('|')
for ch in rstr:
a = list()
if(ch == 'ε'):
a.append('null')
else:
for i in ch:
a.append(i)
if(i in punc or i.islower()):
Ft.add(i)
else:
if(i not in Fn):
Fn.append(i)
s = (A+"->"+ch+'\n')
txt2.insert(tk.END, s)
txt2.update()
f[A].append(a)

b2 = tk.Button(window, text='确认', width=15,
height=2, command=inputDict)
b2.pack()

def isLL1():
t = isgmar()
if(t == False):
tk.messagebox.showinfo(title='失败', message="语法不为LL(1)")
else:
tk.messagebox.showinfo(title='成功', message="语法为LL(1)")
txt = tk.Text(window, width=55, height=7)

for k, v in t.items():
txt.insert(tk.END, 'key: '+str(k)+'\t'+'value: '+str(v)+'\n')
txt.pack()
b3 = tk.Button(window, text='确认输入全部文法', width=15,
height=2, command=isLL1)
b3.pack()
window.mainloop()


if __name__ == "__main__":
draw()

LL(1):

  • gfollow

    • 在实现时发现网上对求follow集方法的描述,与《编译原理》(清华第三版)有偏差。具体来说,对对$A->aBb$:
      • $a$时$V_t$的闭包
      • $b$是$V$的闭包
  • 没有实现select集合

    • 原因是写的时候参照了这篇文章

具体实现:

  • 检测是否存在左递归

    • 如存在则弹出提示框并结束程序
  • 处理四则运算输入字符串时末尾加上了#

  • ttk.Treeview显示表格

    • 最长10行,剩下被隐藏的需要向下滚动
      • 吐槽:按着网上的轮子实现滑块,但失败了,貌似和引入tkinter的方式有关…
  • 解决tkinter触发函数返回值问题——全局变量

  • deque

    • 考虑到input队列(idq)需要从队头出
  • punc = string.punctuation

    • 匹配符号
  • set、list

    • 考虑到set无法下标访问
      • 吐槽:list不仅可以做动态修改,还可以下标访问,分片。特别地,在不清楚将来是否需要这些特性的情况下,最好先用list。唯一不好的是list效率没set高(参见某篇12年的文章)

额外:

  • 调试工具:beeprint
  • 打包工具:pyinstaller

3. 结果展示

  • 初始化
* 结果
# 参考文章
  • Lex - Python Lex Yacc手册 - 极客学院Wiki

一些LL(1)的资料,熟习LL(1)则可无视👇

  • [图文]自顶向下语法分析方法 - 百度文库

  • 简述自顶向下的语法分析 -- 博客园

  • 编译原理之–FIRST集、FOLLOW集 和 SELECT集 - CSDN博客

  • 来点儿编译原理(1)实现一个小型四则运算编译器 - 知乎

  • Lab
  • Compiler Construction
  • Lab
Scapy扫描及隐藏SSID扫描
编译原理备忘录-语法分析与词法分析
© 2024 何决云 载入天数...