-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathstlc.py
274 lines (238 loc) · 9.43 KB
/
stlc.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
270
271
272
import re
from typing import *
from fphack import pipefy, pipefy_builtins, ExceptionMonad, Data, map, reduce, filter, list as list_
from miniparser import pregex, pand, por # type: ignore
# Hacky setup
pipefy_builtins(__name__)
Term, App, Var, Lamb, Arrow, Int, Bool = Data("Term") \
| "App f arg" \
| "Var name" \
| "Lamb var typ body" \
| "Arrow arg ret" \
| "Int" \
> "Bool"
class TypeEnv:
def __init__(self, **data):
self._data = data
def extend(self, d):
return TypeEnv(**{**self._data, **d})
def __add__(self, d):
return self.extend(d)
def __getitem__(self, k):
return self._data[k]
@pipefy
def typecheck(term, env=TypeEnv()):
assert type(env) is TypeEnv
assert isinstance(term, Term)
if type(term) is Var:
return env[term.name]
elif type(term) is App:
tf = typecheck(term.f, env)
if type(tf) is not Arrow:
raise TypeError(f"Expected a function type found a {tf}")
ta = typecheck(term.arg, env)
if ta != tf.arg:
raise TypeError(f"Expected a {tf.arg}, found a {ta}")
return tf.ret
elif type(term) is Lamb:
newenv = env + {term.var: term.typ}
tbody = typecheck(term.body, newenv)
return Arrow(term.typ, tbody)
else:
assert False
def freevars(t):
assert isinstance(t, Term)
if type(t) is App:
return freevars(t.f) | freevars(t.arg)
elif type(t) is Var:
return {t.name}
elif type(t) is Lamb:
return freevars(t.body) - {t.var}
else:
assert False, f"invalid value {t}"
def test_freevars():
assert freevars(App(Var("x"), Var("y"))) == {"x", "y"}
assert freevars(Var("x")) == {"x"}
assert freevars(Lamb("x", Int(), Var("x"))) == set()
assert freevars(Lamb("x", Int(), App(Var("y"), Var("x")))) == {"y"}
def whnf(t):
def spine(t, args=[]):
if type(t) is App:
return spine(t.f, [a, *args])
elif type(t) is Lamb:
assert len(args) > 1
a, *args = args
return spine(subst(t1.var, a, t1.body), args)
else:
return reduce(App, f, args)
return spine(t)
@pipefy
def subst(var, replacement, term):
assert type(var) is str
assert isinstance(replacement, Term)
assert isinstance(term, Term)
if type(term) is Var:
if term.name == var:
return replacement
else:
return term
elif type(term) is App:
return App(subst(var, replacement, term.f),
subst(var,replacement, term.arg))
elif type(term) is Lamb:
if term.var == var:
return term
elif term.var in freevars(replacement):
new_termvar = freshvar(term.var, freevars(term.body) | freevars(replacement))
new_body = subst(term.var, Var(new_termvar), term.body) @ subst(var, replacement, ...)
return Lamb(new_termvar, term.typ, new_body)
else:
return Lamb(term.var, term.typ, subst(var, replacement, term.body))
else:
assert False
def test_subst():
assert subst("x", Var("replacement"), Var("x")) == Var("replacement")
assert subst("x", Var("replacement"), Lamb("x", Int(), Var("x"))) == Lamb("x", Int(), Var("x"))
assert subst("x", Var("replacement"), Lamb("y", Int(), Var("x"))) == Lamb("y", Int(), Var("replacement"))
assert subst("x", Var("replacement"), Lamb("replacement", Int(), Var("x"))) == Lamb("replacement0", Int(), Var("replacement"))
assert subst("x", Var("replacement"), App(Var("x"), Var("x"))) == App(Var("replacement"), Var("replacement"))
def freshvar(var, freevarset, i = 0):
assert type(var) is str
assert type(freevarset) is set
if var in freevarset:
if i > 0:
var = re.search(r"[a-zA-Z]+", var).group(0)
return freshvar(f"{var}{i}", freevarset, i + 1)
else:
return freshvar(f"{var}{i}", freevarset, i + 1)
else:
return var
def test_freshvar():
assert freshvar("x", set()) == "x"
assert freshvar("x", {"x"}) == "x0"
assert freshvar("x", {"x", "x0"}) == "x1"
s = {"x"} | {f"x{i}" for i in range(0, 100)}
assert freshvar("x", s) == "x100"
def alpha_eq(term1, term2):
assert isinstance(term1, Term)
assert isinstance(term1, Term)
if type(term1) is not type(term2):
return False
elif type(term1) is Var:
return term1 == term2
elif type(term1) is App:
return alpha_eq(term1.f, term2.f) and alpha_eq(term1.arg, term2.arg)
elif type(term1) is Lamb:
return term1.typ == term2.typ and alpha_eq(term1.body, subst(term2.var, Var(term1.var), term2.body))
else:
assert False
def test_alpha_eq():
assert alpha_eq(Var("x"), Var("x"))
assert not alpha_eq(Var("x"), Var("y"))
assert alpha_eq(Lamb("x", Int(), Var("x")), Lamb("y", Int(), Var("y")))
assert not alpha_eq(Lamb("x", Int(), Var("y")), Lamb("y", Int(), Var("y")))
assert alpha_eq(App(Lamb("x", Int(), Var("x")), Var("z")), App(Lamb("y", Int(), Var("y")), Var("z")))
assert not alpha_eq(Lamb("x", Int(), Var("x")), Lamb("y", Bool(), Var("y")))
def normal_form(term):
assert isinstance(term, Term)
def spine(term, args):
if type(term) is App:
return spine(term.f, [term.arg, *args])
elif type(term) is Lamb:
if not args:
return Lamb(term.var, term.typ, normal_form(term.body))
else:
arg, *args = args
return spine(subst(term.var, arg, term.body), args)
else:
return reduce(App, map(normal_form, args), term)
return spine(term, [])
def beta_eq(term1, term2):
assert isinstance(term1, Term)
assert isinstance(term2, Term)
return alpha_eq(normal_form(term1), normal_form(term2))
def tokenize_type(s):
return s.split("(") @ map(lambda x : x.split(")"), ...) @ list_(...)
LPAR = pregex(r"\(")
RPAR = pregex(r"\)")
ARROW = pregex(r"->")
VAR = pregex(r"\w+")
def pvar(inp):
v, inp = VAR(inp)
if v == "int":
return Int(), inp
elif v == "bool":
return Bool(), inp
else:
assert False, "unexpected type {inp}"
def parrow(inp):
(t1, _, t2), inp = pand(patom, ARROW, pexpr)(inp)
return Arrow(t1, t2), inp
def ppar(inp):
(_, e, _), inp = pand(LPAR, pexpr, RPAR)(inp)
return e, inp
def patom(inp):
return por(ppar, pvar)(inp)
def pexpr(inp):
return por(parrow, patom)(inp)
def typ(inp):
return pexpr(inp)[0]
def test_typ_parser():
assert typ("bool") == Bool()
assert typ("int") == Int()
assert typ("int -> bool") == Arrow(Int(), Bool())
assert typ("int -> int -> bool") == Arrow(Int(), Arrow(Int(), Bool()))
assert typ("(int -> int) -> bool") == Arrow(Arrow(Int(), Int()), Bool())
def lamb(*args):
"construct mutli arguments lambdas"
*args, body = args
body = Var(body) if type(body) is str else body
def fold_f(body, arg):
assert ":" in arg, f"syntax error, missing the type annotaion (\": some_type\") in {arg}"
arg, typ_ = arg.split(":", 1) @ map(str.strip, ...) @ list_(...)
return Lamb(arg, typ(typ_), body)
return reduce(fold_f, reversed(args), body)
def app(*args):
"construct multi arguments applications"
f, *args = (Var(arg) if type(arg) is str else arg for arg in args)
return reduce(lambda acc, arg: App(acc, arg), args, f)
def test_typecheck():
def typchk(t, env=None):
env = TypeEnv() if env is None else env
return ExceptionMonad.ret(t) @ typecheck(..., env)
assert typchk(Var("x"), TypeEnv(x=Int())) == typ("int")
assert typchk(lamb("x : int", "x")) == typ("int -> int")
assert typchk(app(lamb("b : bool", "b"), "i"), TypeEnv(i=typ("int"))) == TypeError("Expected a Bool(), found a Int()")
id_int = lamb("x : int", "x")
apply_f = lamb("f : int -> int", "x : int", app("f", "x"))
assert typchk(app(apply_f, id_int, "i"), TypeEnv(i=Int())) == typ("int")
def test_beta_eq():
assert lamb("a : int", "a") == Lamb("a", Int(), Var("a"))
assert lamb("a : int", "b : bool", "a") == Lamb("a", Int(), Lamb("b", Bool(), Var("a")))
assert app("a", "b") == App(Var("a"), Var("b"))
assert app("a", "b", "c") == App(App(Var("a"), Var("b")), Var("c"))
assert beta_eq(app(lamb("a : int", "a"), "b"), Var("b"))
assert beta_eq(app(lamb("a : int", "b"), "c"), Var("b"))
true = lamb("a : bool", "b : bool", "a")
false = lamb("a : bool", "b : bool", "b")
assert beta_eq(app(true, "x", "y"), Var("x"))
assert beta_eq(app(false, "x", "y"), Var("y"))
assert beta_eq(app(lamb("x : int", "y : bool", app("x", "y")), "y"),
lamb("y0 : bool", app("y", "y0")))
id_int = lamb("x : int", "x")
apply_f = lamb("f : int -> int", "x : int", app("f", "x"))
assert beta_eq(app(apply_f, id_int, "i"), Var("i"))
# @TODO type annotate these functions
# zero = lamb("s", "z", "z")
# one = lamb("s", "z", app("s", "z"))
# two = lamb("s", "z", app("s", app("s", "z")))
# tree = lamb("s", "z", app("s", app("s", app("s", "z"))))
# plus = lamb("m", "n", "s", "z", app("m", "s", app("n", "s", "z")))
# assert normal_form(app(plus, zero, zero)) == zero
# assert normal_form(app(plus, zero, one)) == one
# assert normal_form(app(plus, one, zero)) == one
# assert normal_form(app(plus, one, one)) == two
# assert normal_form(app(plus, one, two)) == tree
# six = app(plus, tree, tree)
# four = app(plus, two, two)
# assert beta_eq(app(plus, four, two), six)