Repository navigation
Expand file tree
/
Copy pathAST.py
More file actions
287 lines (243 loc) · 8.61 KB
/
Copy pathAST.py
File metadata and controls
287 lines (243 loc) · 8.61 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
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
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
from enum import Enum
from Exceptions import EvalException
############################################################################################
# These classes represent the different types of propositional logic expressions.
#
# Remember each expression can be represented as a Tree.
# So (a || b) && ~c is really the tree:
# &&
# / \
# / \
# || ~
# / \ |
# / \ c
# a b
#
# We can construct this tree in python with:
# And(Or(Var("a"), Var("b")), Not(Var("c")))
#
# Since variables make this code harder to read
# for the rest of this file I'll use the variables
# a = Var("a")
# b = Var("b")
# c = Var("c")
# Now the above expression can be rewritten as
# And(Or(a,b), Not(c))
############################################################################################
# This tells us what type of Node we've created
# You don't need to worry about this part.
class Node(Enum):
ARROW = 1
OR = 2
AND = 3
NOT = 4
LIT = 5
VAR = 6
############################################################################################
# An And node represents the expression a && b
# Each and node has a
# 1. left hand side (lhs)
# 2. right hand side (rhs)
#
# In this case the lhs = "a", and the rhs = "b"
#
# The And node (along with every other type of expression)
# has several methods that we can call
#
# __init__() is the constructor, this is what is called when we write And(a,b)
#
# __str__() returns a string representing the expression (this is what str() calls)
# example:
# str(And(a,b)) returns the string "(a && b)"
#
# __eq__() returns a True if both expressions are identical (this is what a == b calls)
# example:
# And(a,b) == And(b,a) returns False.
# Even though these two expressions are equivalent,
# they are not literally the same expression.
#
# vars() returns the set of all variables is the expression
# Example:
# And(a,b).vars() returns {"a","b"}
#
# type() returns the type of the node (this isn't used)
# Example:
# And(a,b).type() returns Node.AND
#
# eval() given the variables in env, return if the expression evaluates to True of False
# env is the environment, it is a dictionary containing all of the values for variables.
# if env = {"a" : True, "b" : False, "c" : False}
# then env["a"] == True
# Example:
# And(a,b).eval({"a" : True, "b" : False}) returns False
# because env["b"] == False, and True && False == False
#
# Or(And(a,b), Not(c)).eval({"a" : True, "b" : False, "c" : False}) returns True
#
# We can see this by splitting it up
#
# And(a,b).eval({"a" : True, "b" : False, "c" : False}) returns False
# but
# Not(c).eval({"a" : True, "b" : False, "c" : False}) returns True
#
# and False || True == True
#
############################################################################################
class And():
def __init__(self, l, r):
self.lhs = l
self.rhs = r
def __str__(self):
return "(" + str(self.lhs) + " ∧ " + str(self.rhs) + ")"
def __eq__(self, other):
return self.type() == other.type() and \
self.lhs == other.lhs and \
self.rhs == other.rhs
def vars(self):
return self.lhs.vars() | self.rhs.vars()
def type(self):
return Node.AND
def eval(self, env):
raise EvalException("And")
############################################################################################
# An Or node represents the expression a || b
# We can construct one with Or(a,b)
#
# You'll notice that this is very similar to the And node
############################################################################################
class Or():
def __init__(self, l, r):
self.lhs = l
self.rhs = r
def __str__(self):
return "(" + str(self.lhs) + " ∨ " + str(self.rhs) + ")"
def __eq__(self, other):
return self.type() == other.type() and \
self.lhs == other.lhs and \
self.rhs == other.rhs
def vars(self):
return self.lhs.vars() | self.rhs.vars()
def type(self):
return Node.OR
def eval(self, env):
raise EvalException("Or")
############################################################################################
# An Arrow node represents the expression a -> B
# We can construct one with Arrow(a,b)
#
# You'll notice that this is very similar to the And node
############################################################################################
class Arrow():
def __init__(self, l, r):
self.lhs = l
self.rhs = r
def __str__(self):
return "(" + str(self.lhs) + " → " + str(self.rhs) + ")"
def __eq__(self, other):
return self.type() == other.type() and \
self.lhs == other.lhs and \
self.rhs == other.rhs
def vars(self):
return self.lhs.vars() | self.rhs.vars()
def type(self):
return Node.ARROW
def eval(self, env):
raise EvalException("Arrow")
############################################################################################
# A Not node represents the expression ~a
# We can construct one with Not(a)
#
# This is different from the And node in that it only has 1 child the lhs
############################################################################################
class Not():
def __init__(self, l):
self.lhs = l
def __str__(self):
return "(¬ " + str(self.lhs) + ")"
def __eq__(self, other):
return self.type() == other.type() and \
self.lhs == other.lhs
def vars(self):
return self.lhs.vars()
def type(self):
return Node.NOT
def eval(self, env):
raise EvalException("Not")
############################################################################################
# A Lit node represents a literal value (T or F)
# We can construct one with true() or false()
#
# Lit nodes don't have any children, but they do have a value (True or False)
############################################################################################
class Lit():
def __init__(self, val):
self.val = val
def __str__(self):
if self.val:
return "T"
else:
return "⊥ "
def __eq__(self, other):
return self.type() == other.type() and \
self.val == other.val
def vars(self):
return set()
def type(self):
return Node.LIT
def eval(self, env):
raise EvalException("Lit")
# these are for convenience
# Don't call Lit(True) directly, just use true()
def true():
return Lit(True)
def false():
return Lit(False)
############################################################################################
# A Var node represents a variable
# We can construct one with Var("a")
# The string can be any string we want, but you should maksure it only consists of letter
# also, try to avoid T, F, true, True, False, false
# It won't break anything, but it will look confusing with printing the output.
#
# Var nodes don't have any children, but they have a name
# to evaluate a Var we need to look it up in the environment.
############################################################################################
class Var:
def __init__(self, name):
self.name = name
def __str__(self):
return self.name
def __eq__(self, other):
return self.type() == other.type() and \
self.name == other.name
def vars(self):
return {self.name}
def type(self):
return Node.VAR
def eval(self, env):
raise EvalException("Var")
############################################################################################
# Prints out a truth table for an expression
# The specifics of how this works aren't too important
# The idea is that we make every possible environment,
# and then we evaulate the expression with each environment.
#
# Since I don't know how many variables an expression might have,
# I need to construct the environment recursively.
############################################################################################
def truth_table(e):
vs = sorted(list(e.vars()))
print("%s | %s" % (" ".join(vs), str(e)))
print("%s+-%s" % ("--"*len(vs), "-"*len(str(e))))
rec_eval(e,vs,0,{})
def rec_eval(e, vs, i, env):
if i < len(vs):
for val in [True,False]:
env[vs[i]] = val
rec_eval(e,vs,i+1,env)
else:
print(" ".join([TorF(env[v]) for v in vs]), end=" ")
print("| ", TorF(e.eval(env)))
def TorF(b):
if b: return "T"
else: return "⊥"