-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.py
More file actions
203 lines (166 loc) · 6.38 KB
/
main.py
File metadata and controls
203 lines (166 loc) · 6.38 KB
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
from grammar import origin_productions, null
terminals: list[str] = []
non_terminals: list[str] = []
start_symbol: list[str] = []
other_symbol: list[str] = []
first: dict[str, set[str]] = {}
follow: dict[str, set[str]] = {}
productions: dict[str, list[list[str]]] = {}
table: dict[tuple[str, str], str] = {}
end_symbol = "$"
def init():
global terminals, non_terminals
global start_symbol, other_symbol
global first, follow
global productions
# init_production
def to_list(strs: list[str]):
return [str.split(" ") for str in strs]
for key, value in origin_productions.items():
productions[key] = to_list(value)
# init symbols
non_terminals = list(productions.keys())
start_symbol = non_terminals[:1]
other_symbol = non_terminals[1:]
terminals = list(
set(
[
symbol
for list_list_symbol in productions.values()
for list_symbol in list_list_symbol
for symbol in list_symbol
if symbol not in non_terminals
]
)
)
terminals.append(end_symbol)
# init first set and follow set
for symbol in terminals:
# if a symbol is a final symbol, then FIRST[symbol] = [symbol]
first[symbol] = set([symbol])
follow[symbol] = set([])
for symbol in start_symbol:
first[symbol] = set([])
# put end char to FOLLOW[start symbol]
follow[symbol] = set([end_symbol])
for symbol in other_symbol:
first[symbol] = set([])
follow[symbol] = set([])
def isFinal(x: str):
return x in terminals
def dislocation(x: list[str]):
"""
turn
1, 2, 3, 4
into
([], 1), ([1], 2), ([1, 2], 3), ([1, 2, 3], 4)
"""
return [(x[: i], x[i]) for i in range(len(x))]
def allHasNull(symbol_list: list[str]):
all_null = True
for symbol in symbol_list:
if null not in first[symbol]:
all_null = False
break
return all_null
def discardNull(tk_set: set[str]):
tmp = set(tk_set)
tmp.discard(null)
return set(tmp)
def getFirst(tk_list: list[str]):
"""
get the first set of list[token]
"""
l = [set(first[tk_list[0]])] if len(tk_list) > 0 else []
if len(tk_list) >= 2:
for prev, this in dislocation(tk_list):
if allHasNull(prev) is True:
first_set = set(first[this])
l.append(set(first_set))
else:
break
ret: set[str] = set()
for i in l:
ret.update(i)
return ret
def set_first():
while True:
add = False
for non_terminal, production_list in productions.items():
if isFinal(non_terminal) is not True:
for production in production_list:
# if X -> null is a production, then add null to first[X]
if production == [null]:
add |= False if null in first[non_terminal] else True
first[non_terminal].add(null)
else:
# for X -> abc, if a -> null, then add first[b] to first[X] exclude null
for prev, this in dislocation(production):
if allHasNull(prev) is True:
first_set = discardNull(first[this])
add |= False if first_set.issubset(first[non_terminal]) else True
first[non_terminal].update(first_set)
else:
break
# for X -> abc, if a, b, c -> null, then add null to first[X]
if allHasNull(production) is True:
add |= True if null not in first[non_terminal] else False
first[non_terminal].add(null)
if add is False:
break
def set_follow():
while True:
add = False
for non_terminal, production_list in productions.items():
for production in production_list:
if len(production[:-1]) > 0:
for i in range(0, len(production[:-1])):
# for X -> aBc, add first[c] to follow[B] exclude null
follow_set = discardNull(getFirst(production[i + 1 :]))
add |= False if follow_set.issubset(follow[production[i]]) else True
follow[production[i]].update(follow_set)
# if c includes null, then add follow[X] to follow[B]
if allHasNull(production[i + 1 :]):
add |= (
False
if follow[non_terminal].issubset(follow[production[i]])
else True
)
follow[production[i]].update(follow[non_terminal])
# for X -> aB, add follow[X] to follow[B]
add |= False if follow[non_terminal].issubset(follow[production[-1]]) else True
follow[production[-1]].update(follow[non_terminal])
if add == False:
break
def gen_table():
for terminal in discardNull(set(terminals)):
for non_terminal in non_terminals:
table[(non_terminal, terminal)] = ""
for symbol, production_list in productions.items():
for production in production_list:
for x in discardNull(getFirst(production)):
table[(symbol, x)] += f"{symbol} -> {' '.join(production)}"
if null in getFirst(production):
for x in follow[symbol]:
table[(symbol, x)] += f"{symbol} -> {' '.join(production)}"
def save_table():
input_symbol = [terminal for terminal in terminals]
input_symbol.remove(null)
input_symbol.sort()
filename = f"./table.csv"
with open(filename, "w", encoding="utf-8") as file:
line1 = f"non-terminal,"
for terminal in input_symbol:
line1 += terminal + ","
file.write(line1 + "\n")
for terminal in start_symbol + other_symbol:
line = terminal + ","
for non_terminal in input_symbol:
line += table[(terminal, non_terminal)] + ","
file.write(line + "\n")
if __name__ == "__main__":
init()
set_first()
set_follow()
gen_table()
save_table()