-
Notifications
You must be signed in to change notification settings - Fork 0
/
cat.py
142 lines (117 loc) · 3.49 KB
/
cat.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
def parse_expr(s: str, idx: int):
idx = skip_space(s, idx)
if s[idx] == '(':
# a list
idx += 1
l = []
while True:
idx = skip_space(s, idx)
if idx >= len(s):
raise Exception('unbalanced parenthesis')
if s[idx] == ')':
idx += 1
break
idx, v = parse_expr(s, idx)
l.append(v)
return idx, l
elif s[idx] == ')':
raise Exception('bad parenthesis')
else:
# an atom
start = idx
while idx < len(s) and (not s[idx].isspace()) and s[idx] not in '()':
idx += 1
if start == idx:
raise Exception('empty program')
return idx, parse_atom(s[start:idx])
def skip_space(s, idx):
while 1:
save = idx
while idx < len(s) and s[idx].isspace():
idx += 1
if idx < len(s) and s[idx] == ';':
idx += 1
while idx < len(s) and s[idx] != '\n':
idx += 1
if idx == save:
break
return idx
def parse_atom(s):
import json
try:
return ['val', json.loads(s)]
except json.JSONDecodeError:
return s
def pl_parse(s):
idx, node = parse_expr(s, 0)
idx = skip_space(s, idx)
if idx < len(s):
raise ValueError('trailing garbage')
return node
def name_lookup(env, key):
while env:
if len(env) != 2:
raise ValueError('Invalid environment structure')
current, env = env
if key in current:
return current[key]
raise ValueError(f'undefined name: {key}')
def pl_eval(env, node):
if not isinstance(node, list):
assert isinstance(node, str)
return name_lookup(env, node)
if len(node) == 0:
raise ValueError('empty list')
# bool, number, string, and etc.
if len(node) == 2 and node[0] == 'val':
return node[1]
# binary operators
import operator
binops = {
'+': operator.add,
'-': operator.sub,
'*': operator.mul,
'/': operator.truediv,
'=': operator.eq,
'!=': operator.ne,
'>=': operator.ge,
'>': operator.gt,
'<=': operator.le,
'<': operator.lt,
'and': operator.and_,
'or': operator.or_,
'xor': operator.xor,
}
if len(node) == 3 and node[0] in binops:
op = binops[node[0]]
return op(pl_eval(env, node[1]), pl_eval(env, node[2]))
# unary operators
unops = {
'-': operator.neg,
'not': operator.not_,
'~': operator.not_,
}
if len(node) == 2 and node[0] in unops:
op = unops[node[0]]
return op(pl_eval(env, node[1]))
# Handle if expression
if len(node) == 4 and node[0] == 'if':
_, condition, then_clause, else_clause = node
if pl_eval(env, condition):
return pl_eval(env, then_clause)
else:
return pl_eval(env, else_clause)
# If-Else, do, then, else
if node[0] in ('pounce', 'watch', 'purr') and len(node) > 1:
new_env = (dict(), env)
for val in node[1:]:
val = pl_eval(new_env, val)
return val
# Print statement
if node[0] == 'meow':
return print(*(pl_eval(env, val) for val in node[1:]))
raise ValueError('unknown expression')
def evaluate(s):
return pl_eval(({},), pl_parse(s))
string = input("Enter your expression (Lisp-like format, e.g., '(meow 1)'): ")
print(evaluate(string))