-
Notifications
You must be signed in to change notification settings - Fork 0
/
pylambda.py
249 lines (201 loc) · 7.3 KB
/
pylambda.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
"""
This is a lambda reducer (α and β) in Python
It is mostly just a copy of the Haskell-Code
but also does alpha-reduction which the Haskell-version cannot provide
"""
import re
from itertools import chain
from string import ascii_letters, whitespace
from dataclasses import dataclass
Char = str
letters = set(ascii_letters)
whitespace_pattern = re.compile(r"^\s+")
class Expression:
def all_vars(self)->set[Char]:
...
def free_vars(self, bound_vars: set[Char])->set[Char]:
...
def rename_bound(self, var: Char, into: Char):
...
def rename_all(self, var: Char, into: Char):
...
def areduce(self):
r"""
We need to alpha reduce when an expression
has a free variable and a bound variable at the same time
(\ha.ha)a -> (\hb.hb)a
Because beta reducing the first term yields (\a.aa) which is different
to (\b.ab) which is the result of beta reducing the latter term.
"""
free = self.free_vars(set())
# _all = all_vars(expr)
for f in free:
rest = letters - self.all_vars()
try:
self.rename_bound(f, rest.pop())
except:
RuntimeError("Ran out of letters")
def breduce(self):
...
def replace_var(self, var: str, wth: "Expression")->"Expression":
return self
@dataclass
class Var(Expression):
char: Char
def __str__(self):
return self.char
def all_vars(self)->set[Char]:
return {self.char}
def free_vars(self, bound_vars: set[Char])->set[Char]:
return {self.char} - bound_vars
def rename_bound(self, var: Char, into: Char):
pass
def rename_all(self, var: Char, into: Char):
self.char = self.char.replace(var, into)
def replace_var(self, var: str, wth: Expression)->Expression:
return wth if self.char == var else self
@dataclass
class Func(Expression):
head: str
body: Expression
def __str__(self):
return rf"(\{self.head}.{self.body})"
def all_vars(self)->set[Char]:
return set(self.head) | self.body.all_vars()
def free_vars(self, bound_vars: set[Char])->set[Char]:
return self.body.free_vars(bound_vars | set(self.head)) - bound_vars
def rename_bound(self, var: Char, into: Char):
if var in self.head:
self.head = self.head.replace(var, into)
self.body.rename_all(var, into)
else:
self.body.rename_bound(var, into)
def rename_all(self, var: Char, into: Char):
self.head = self.head.replace(var, into)
self.body.rename_all(var, into)
def replace_var(self, var: str, wth: Expression)->Expression:
# Don't replace bound variables inside the functions body
if not var in self.head:
return Func(self.head, self.body.replace_var(var, wth))
return self
def breduce(self):
self.body.breduce()
@dataclass
class Appl(Expression):
xs: list[Expression]
def __str__(self):
return "".join(str(x) for x in self.xs)
def all_vars(self)->set[Char]:
return set(chain.from_iterable(x.all_vars() for x in self.xs))
def free_vars(self, bound_vars: set[Char])->set[Char]:
return set(chain.from_iterable(x.free_vars(bound_vars) for x in self.xs)) - bound_vars
def rename_bound(self, var: Char, into: Char):
for expr in self.xs:
expr.rename_bound(var, into)
def rename_all(self, var: Char, into: Char):
for expr in self.xs:
expr.rename_all(var, into)
def replace_var(self, var: str, wth: Expression)->Expression:
return Appl([x.replace_var(var, wth) for x in self.xs])
def breduce(self):
if not self.xs:
return self
elif len(self.xs)>=2 and type(func:=self.xs[0]) is Func and func.head:
param, func.head = func.head[0], func.head[1:]
func.body = func.body.replace_var(param, self.xs.pop(1))
else:
for expr in self.xs:
expr.breduce()
class ParseError(ValueError, SyntaxError):
pass
def find_closing_paren(text: str):
count = 1
for i,c in enumerate(text):
if c == ")":
count -= 1
if count == 0:
return i
elif c == "(":
count += 1
return -1
def _parse_expr(rest: str) -> list[Expression]:
lambda_chars = r"\λ"
# can't extend these below
dot_chars = r"."
paren_chars = r"()"
special_chars = lambda_chars + dot_chars + paren_chars
result = []
while rest:
first, rest = rest[0], rest[1:]
# function
if first in lambda_chars:
if dot_chars not in rest:
raise ParseError("Found a function without a dot")
head, body = rest.split(".", 1)
if any(c in special_chars for c in head):
raise ParseError("Found a special character in the head of a function")
rest = ""
if " " in body:
count = 0
for i,c in enumerate(body):
if c == ")":
count -= 1
elif c == "(":
count += 1
elif whitespace_pattern.match(body[i:]) and not count:
body, rest = body[:i], body[i:].lstrip()
break
result.append(Func(head, parse_expr(body)))
elif first == "(":
closing = find_closing_paren(rest)
if closing == -1:
raise ParseError("Found opening parentheses without matching closing parentheses")
inside, rest = rest[:closing], rest[closing+1:]
result.append(parse_expr(inside))
elif first in dot_chars:
raise ParseError("Found a stray dot")
elif first == ")":
raise ParseError("Found stray closing parantheses")
elif first in whitespace:
continue
else:
result.append(Var(first))
return result
def parse_expr(text: str) -> Expression:
if not text:
raise ValueError("Can't parse empty input")
return Appl(_parse_expr(text))
def reduce(expr: Expression)->Expression:
while True:
old_expr = str(expr)
expr = simple_reduce(expr)
reduction_type = "alpha"
expr.areduce()
if old_expr == str(expr):
reduction_type = "beta"
expr.breduce()
expr = simple_reduce(expr)
if old_expr == str(expr):
return expr
print(f"Step ({reduction_type}): {old_expr}->{expr}")
def simple_reduce(expr: Expression)->Expression:
match expr:
case Func("", x):
return simple_reduce(x)
case Func(head, body):
return Func(head, simple_reduce(body))
case Appl([x]):
return simple_reduce(x)
case Appl(xs):
new_xs = []
for x in xs:
if type(x) is Appl:
new_xs.extend(x.xs)
else:
new_xs.append(x)
expr.xs = [simple_reduce(x) for x in new_xs]
return expr
return expr
if __name__ == "__main__":
text = input("Please provide your expression: ")
print(reduce(parse_expr(text)))