Rewrite continues
parent
455e447999
commit
a991c3bb91
473
lamb/__main__.py
473
lamb/__main__.py
|
@ -46,6 +46,8 @@ r = runner.Runner(
|
||||||
)
|
)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
macro_table = {}
|
||||||
|
|
||||||
class Direction(enum.Enum):
|
class Direction(enum.Enum):
|
||||||
UP = enum.auto()
|
UP = enum.auto()
|
||||||
LEFT = enum.auto()
|
LEFT = enum.auto()
|
||||||
|
@ -74,7 +76,11 @@ class Node:
|
||||||
self.left: Node | None = None
|
self.left: Node | None = None
|
||||||
self.right: Node | None = None
|
self.right: Node | None = None
|
||||||
|
|
||||||
def set_parent(self, parent, side: Direction):
|
def set_parent(self, parent, side):
|
||||||
|
if (parent is not None) and (side is None):
|
||||||
|
raise Exception("If a node has a parent, it must have a direction.")
|
||||||
|
if (parent is None) and (side is not None):
|
||||||
|
raise Exception("If a node has no parent, it cannot have a direction.")
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
self.parent_side = side
|
self.parent_side = side
|
||||||
|
|
||||||
|
@ -89,15 +95,33 @@ class Node:
|
||||||
return Direction.UP, self.right
|
return Direction.UP, self.right
|
||||||
|
|
||||||
def go_up(self):
|
def go_up(self):
|
||||||
if self.parent is None:
|
|
||||||
raise Exception("Can't go up when parent is None")
|
|
||||||
return self.parent_side, self.parent
|
return self.parent_side, self.parent
|
||||||
|
|
||||||
class EndNode:
|
def clone(self):
|
||||||
def print_value(self):
|
raise NotImplementedError("Nodes MUST provide a `clone` method!")
|
||||||
raise NotImplementedError("EndNodes MUST have a print_value method!")
|
|
||||||
|
|
||||||
class Macro(Node, EndNode):
|
class EndNode(Node):
|
||||||
|
def print_value(self):
|
||||||
|
raise NotImplementedError("EndNodes MUST provide a `print_value` method!")
|
||||||
|
|
||||||
|
class ExpandableEndNode(EndNode):
|
||||||
|
def expand(self):
|
||||||
|
raise NotImplementedError("ExpandableEndNodes MUST provide an `expand` method!")
|
||||||
|
|
||||||
|
class FreeVar(EndNode):
|
||||||
|
def __init__(self, name: str):
|
||||||
|
self.name = name
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"<freevar {self.name}>"
|
||||||
|
|
||||||
|
def print_value(self):
|
||||||
|
return f"{self.name}"
|
||||||
|
|
||||||
|
def clone(self):
|
||||||
|
return FreeVar(self.name)
|
||||||
|
|
||||||
|
class Macro(ExpandableEndNode):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def from_parse(results):
|
def from_parse(results):
|
||||||
return Macro(results[0])
|
return Macro(results[0])
|
||||||
|
@ -114,10 +138,21 @@ class Macro(Node, EndNode):
|
||||||
def print_value(self):
|
def print_value(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
class Church(Node, EndNode):
|
def expand(self):
|
||||||
|
if self.name in macro_table:
|
||||||
|
return macro_table[self.name]
|
||||||
|
else:
|
||||||
|
f = FreeVar(self.name)
|
||||||
|
f.set_parent(self.parent, self.parent_side) # type: ignore
|
||||||
|
return f
|
||||||
|
|
||||||
|
def clone(self):
|
||||||
|
return Macro(self.name)
|
||||||
|
|
||||||
|
class Church(ExpandableEndNode):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def from_parse(results):
|
def from_parse(results):
|
||||||
return Church(results[0])
|
return Church(int(results[0]))
|
||||||
|
|
||||||
def __init__(self, value: int) -> None:
|
def __init__(self, value: int) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
@ -131,10 +166,7 @@ class Church(Node, EndNode):
|
||||||
def print_value(self):
|
def print_value(self):
|
||||||
return str(self.value)
|
return str(self.value)
|
||||||
|
|
||||||
def to_church(self):
|
def expand(self):
|
||||||
"""
|
|
||||||
Return this number as an expanded church numeral.
|
|
||||||
"""
|
|
||||||
f = Bound("f")
|
f = Bound("f")
|
||||||
a = Bound("a")
|
a = Bound("a")
|
||||||
chain = a
|
chain = a
|
||||||
|
@ -147,9 +179,11 @@ class Church(Node, EndNode):
|
||||||
Func(a, chain)
|
Func(a, chain)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def clone(self):
|
||||||
|
return Church(self.value)
|
||||||
|
|
||||||
bound_counter = 0
|
bound_counter = 0
|
||||||
class Bound(Node, EndNode):
|
class Bound(EndNode):
|
||||||
def __init__(self, name: str, *, forced_id = None):
|
def __init__(self, name: str, *, forced_id = None):
|
||||||
self.name = name
|
self.name = name
|
||||||
global bound_counter
|
global bound_counter
|
||||||
|
@ -161,13 +195,7 @@ class Bound(Node, EndNode):
|
||||||
self.identifier = forced_id
|
self.identifier = forced_id
|
||||||
|
|
||||||
def clone(self):
|
def clone(self):
|
||||||
"""
|
return Bound(self.name, forced_id = self.identifier)
|
||||||
Return a new bound variable equivalent to this one.
|
|
||||||
"""
|
|
||||||
return Bound(
|
|
||||||
self.name,
|
|
||||||
forced_id = self.identifier
|
|
||||||
)
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
if not isinstance(other, Bound):
|
if not isinstance(other, Bound):
|
||||||
|
@ -207,6 +235,9 @@ class Func(Node):
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"<func {self.input!r} {self.left!r}>"
|
return f"<func {self.input!r} {self.left!r}>"
|
||||||
|
|
||||||
|
def clone(self):
|
||||||
|
return Func(self.input, None) # type: ignore
|
||||||
|
|
||||||
class Call(Node):
|
class Call(Node):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def from_parse(results):
|
def from_parse(results):
|
||||||
|
@ -237,6 +268,9 @@ class Call(Node):
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"<call {self.left!r} {self.right!r}>"
|
return f"<call {self.left!r} {self.right!r}>"
|
||||||
|
|
||||||
|
def clone(self):
|
||||||
|
return Call(None, None) # type: ignore
|
||||||
|
|
||||||
class MacroDef:
|
class MacroDef:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def from_parse(result):
|
def from_parse(result):
|
||||||
|
@ -278,137 +312,285 @@ p = lamb.parser.LambdaParser(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def print_expr(expr) -> str:
|
|
||||||
|
|
||||||
if isinstance(expr, MacroDef):
|
def clone_one(ptr, out):
|
||||||
return f"{expr.label} = {print_expr(expr.expr)}"
|
if ptr.parent_side == Direction.LEFT:
|
||||||
|
out.left = ptr.clone()
|
||||||
|
out.left.set_parent(out, Direction.LEFT)
|
||||||
|
else:
|
||||||
|
out.right = ptr.clone()
|
||||||
|
out.right.set_parent(out, Direction.RIGHT)
|
||||||
|
|
||||||
elif isinstance(expr, Node):
|
def clone(expr: Node):
|
||||||
ptr = expr
|
if not isinstance(expr, Node):
|
||||||
from_side = Direction.UP
|
raise TypeError(f"I don't know what to do with a {type(expr)}")
|
||||||
|
|
||||||
out = ""
|
# Disconnect parent while cloning
|
||||||
|
old_parent = expr.parent
|
||||||
|
expr.parent = None
|
||||||
|
|
||||||
while True:
|
out = expr.clone()
|
||||||
if isinstance(ptr, EndNode):
|
out_ptr = out # Stays one step behind ptr, in the new tree.
|
||||||
out += ptr.print_value()
|
ptr = expr
|
||||||
if ptr.parent is not None:
|
from_side = Direction.UP
|
||||||
from_side, ptr = ptr.go_up()
|
|
||||||
|
|
||||||
elif isinstance(ptr, Func):
|
if isinstance(expr, EndNode):
|
||||||
if from_side == Direction.UP:
|
|
||||||
if isinstance(ptr.parent, Func):
|
|
||||||
out += ptr.input.name
|
|
||||||
else:
|
|
||||||
out += "λ" + ptr.input.name
|
|
||||||
if not isinstance(ptr.left, Func):
|
|
||||||
out += "."
|
|
||||||
from_side, ptr = ptr.go_left()
|
|
||||||
elif from_side == Direction.LEFT:
|
|
||||||
if ptr.parent is not None:
|
|
||||||
from_side, ptr = ptr.go_up()
|
|
||||||
|
|
||||||
elif isinstance(ptr, Call):
|
|
||||||
if from_side == Direction.UP:
|
|
||||||
out += "("
|
|
||||||
from_side, ptr = ptr.go_left()
|
|
||||||
elif from_side == Direction.LEFT:
|
|
||||||
out += " "
|
|
||||||
from_side, ptr = ptr.go_right()
|
|
||||||
elif from_side == Direction.RIGHT:
|
|
||||||
out += ")"
|
|
||||||
if ptr.parent is not None:
|
|
||||||
from_side, ptr = ptr.go_up()
|
|
||||||
|
|
||||||
if ptr.parent is None:
|
|
||||||
break
|
|
||||||
return out
|
return out
|
||||||
|
|
||||||
else:
|
while True:
|
||||||
|
if isinstance(ptr, EndNode):
|
||||||
|
from_side, ptr = ptr.go_up()
|
||||||
|
_, out_ptr = out_ptr.go_up()
|
||||||
|
elif isinstance(ptr, Func):
|
||||||
|
if from_side == Direction.UP:
|
||||||
|
from_side, ptr = ptr.go_left()
|
||||||
|
clone_one(ptr, out_ptr)
|
||||||
|
_, out_ptr = out_ptr.go_left()
|
||||||
|
elif from_side == Direction.LEFT:
|
||||||
|
from_side, ptr = ptr.go_up()
|
||||||
|
_, out_ptr = out_ptr.go_up()
|
||||||
|
elif isinstance(ptr, Call):
|
||||||
|
if from_side == Direction.UP:
|
||||||
|
from_side, ptr = ptr.go_left()
|
||||||
|
clone_one(ptr, out_ptr)
|
||||||
|
_, out_ptr = out_ptr.go_left()
|
||||||
|
elif from_side == Direction.LEFT:
|
||||||
|
from_side, ptr = ptr.go_right()
|
||||||
|
clone_one(ptr, out_ptr)
|
||||||
|
_, out_ptr = out_ptr.go_right()
|
||||||
|
elif from_side == Direction.RIGHT:
|
||||||
|
from_side, ptr = ptr.go_up()
|
||||||
|
_, out_ptr = out_ptr.go_up()
|
||||||
|
if ptr is None:
|
||||||
|
break
|
||||||
|
|
||||||
|
expr.parent = old_parent
|
||||||
|
return out
|
||||||
|
|
||||||
|
def print_expr(expr) -> str:
|
||||||
|
|
||||||
|
out = ""
|
||||||
|
|
||||||
|
# Type check
|
||||||
|
if isinstance(expr, MacroDef):
|
||||||
|
out = expr.label + " = "
|
||||||
|
expr = expr.expr
|
||||||
|
elif not isinstance(expr, Node):
|
||||||
raise TypeError(f"I don't know what to do with a {type(expr)}")
|
raise TypeError(f"I don't know what to do with a {type(expr)}")
|
||||||
|
|
||||||
|
ptr = expr
|
||||||
|
from_side = Direction.UP
|
||||||
|
|
||||||
|
while True:
|
||||||
|
print(ptr)
|
||||||
|
if isinstance(ptr, EndNode):
|
||||||
|
out += ptr.print_value()
|
||||||
|
from_side, ptr = ptr.go_up()
|
||||||
|
|
||||||
|
elif isinstance(ptr, Func):
|
||||||
|
if from_side == Direction.UP:
|
||||||
|
if isinstance(ptr.parent, Func):
|
||||||
|
out += ptr.input.name
|
||||||
|
else:
|
||||||
|
out += "λ" + ptr.input.name
|
||||||
|
if not isinstance(ptr.left, Func):
|
||||||
|
out += "."
|
||||||
|
from_side, ptr = ptr.go_left()
|
||||||
|
elif from_side == Direction.LEFT:
|
||||||
|
from_side, ptr = ptr.go_up()
|
||||||
|
|
||||||
|
elif isinstance(ptr, Call):
|
||||||
|
if from_side == Direction.UP:
|
||||||
|
out += "("
|
||||||
|
from_side, ptr = ptr.go_left()
|
||||||
|
elif from_side == Direction.LEFT:
|
||||||
|
out += " "
|
||||||
|
from_side, ptr = ptr.go_right()
|
||||||
|
elif from_side == Direction.RIGHT:
|
||||||
|
out += ")"
|
||||||
|
from_side, ptr = ptr.go_up()
|
||||||
|
|
||||||
|
if ptr is None:
|
||||||
|
break
|
||||||
|
return out
|
||||||
|
|
||||||
def bind_variables(expr) -> None:
|
def bind_variables(expr) -> None:
|
||||||
|
|
||||||
|
# Type check
|
||||||
if isinstance(expr, MacroDef):
|
if isinstance(expr, MacroDef):
|
||||||
bind_variables(expr.expr)
|
expr = expr.expr
|
||||||
|
elif not isinstance(expr, Node):
|
||||||
elif isinstance(expr, Node):
|
|
||||||
ptr = expr
|
|
||||||
from_side = Direction.UP
|
|
||||||
|
|
||||||
bound_variables = {}
|
|
||||||
|
|
||||||
while True:
|
|
||||||
if isinstance(ptr, Func):
|
|
||||||
if from_side == Direction.UP:
|
|
||||||
# Add this function's input to the table of bound variables.
|
|
||||||
# If it is already there, raise an error.
|
|
||||||
if (ptr.input.name in bound_variables):
|
|
||||||
raise ReductionError(f"Bound variable name conflict: \"{ptr.input.name}\"")
|
|
||||||
else:
|
|
||||||
bound_variables[ptr.input.name] = Bound(ptr.input.name)
|
|
||||||
ptr.input = bound_variables[ptr.input.name]
|
|
||||||
|
|
||||||
# If output is a macro, swap it with a bound variable.
|
|
||||||
if isinstance(ptr.left, Macro):
|
|
||||||
if ptr.left.name in bound_variables:
|
|
||||||
ptr.left = bound_variables[ptr.left.name].clone()
|
|
||||||
ptr.left.set_parent(ptr, Direction.LEFT)
|
|
||||||
|
|
||||||
# If we can't move down the tree, move up.
|
|
||||||
if isinstance(ptr.left, EndNode):
|
|
||||||
del bound_variables[ptr.input.name]
|
|
||||||
if ptr.parent is not None:
|
|
||||||
from_side, ptr = ptr.go_up()
|
|
||||||
else:
|
|
||||||
from_side, ptr = ptr.go_left()
|
|
||||||
|
|
||||||
elif from_side == Direction.LEFT:
|
|
||||||
del bound_variables[ptr.input.name]
|
|
||||||
if ptr.parent is not None:
|
|
||||||
from_side, ptr = ptr.go_up()
|
|
||||||
|
|
||||||
elif isinstance(ptr, Call):
|
|
||||||
if from_side == Direction.UP:
|
|
||||||
# Bind macros
|
|
||||||
if isinstance(ptr.left, Macro):
|
|
||||||
if ptr.left.name in bound_variables:
|
|
||||||
ptr.left = bound_variables[ptr.left.name].clone()
|
|
||||||
ptr.left.set_parent(ptr, Direction.LEFT)
|
|
||||||
if isinstance(ptr.right, Macro):
|
|
||||||
if ptr.right.name in bound_variables:
|
|
||||||
ptr.right = bound_variables[ptr.right.name].clone()
|
|
||||||
ptr.right.set_parent(ptr, Direction.RIGHT)
|
|
||||||
|
|
||||||
if not isinstance(ptr.left, EndNode):
|
|
||||||
from_side, ptr = ptr.go_left()
|
|
||||||
elif not isinstance(ptr.right, EndNode):
|
|
||||||
from_side, ptr = ptr.go_right()
|
|
||||||
elif ptr.parent is not None:
|
|
||||||
from_side, ptr = ptr.go_up()
|
|
||||||
|
|
||||||
elif from_side == Direction.LEFT:
|
|
||||||
if isinstance(ptr.right, Macro):
|
|
||||||
if ptr.right.name in bound_variables:
|
|
||||||
ptr.right = bound_variables[ptr.right.name].clone()
|
|
||||||
ptr.right.set_parent(ptr, Direction.RIGHT)
|
|
||||||
|
|
||||||
if not isinstance(ptr.right, EndNode):
|
|
||||||
from_side, ptr = ptr.go_right()
|
|
||||||
elif ptr.parent is not None:
|
|
||||||
from_side, ptr = ptr.go_up()
|
|
||||||
|
|
||||||
elif from_side == Direction.RIGHT:
|
|
||||||
if ptr.parent is not None:
|
|
||||||
from_side, ptr = ptr.go_up()
|
|
||||||
|
|
||||||
if ptr.parent is None:
|
|
||||||
break
|
|
||||||
|
|
||||||
else:
|
|
||||||
raise TypeError(f"I don't know what to do with a {type(expr)}")
|
raise TypeError(f"I don't know what to do with a {type(expr)}")
|
||||||
|
|
||||||
|
ptr = expr
|
||||||
|
from_side = Direction.UP
|
||||||
|
bound_variables = {}
|
||||||
|
|
||||||
|
while True:
|
||||||
|
if isinstance(ptr, Func):
|
||||||
|
if from_side == Direction.UP:
|
||||||
|
# Add this function's input to the table of bound variables.
|
||||||
|
# If it is already there, raise an error.
|
||||||
|
if (ptr.input.name in bound_variables):
|
||||||
|
raise ReductionError(f"Bound variable name conflict: \"{ptr.input.name}\"")
|
||||||
|
else:
|
||||||
|
bound_variables[ptr.input.name] = Bound(ptr.input.name)
|
||||||
|
ptr.input = bound_variables[ptr.input.name]
|
||||||
|
|
||||||
|
# If output is a macro, swap it with a bound variable.
|
||||||
|
if isinstance(ptr.left, Macro):
|
||||||
|
if ptr.left.name in bound_variables:
|
||||||
|
ptr.left = bound_variables[ptr.left.name].clone()
|
||||||
|
ptr.left.set_parent(ptr, Direction.LEFT)
|
||||||
|
|
||||||
|
# If we can't move down the tree, move up.
|
||||||
|
if isinstance(ptr.left, EndNode):
|
||||||
|
del bound_variables[ptr.input.name]
|
||||||
|
from_side, ptr = ptr.go_up()
|
||||||
|
else:
|
||||||
|
from_side, ptr = ptr.go_left()
|
||||||
|
|
||||||
|
elif from_side == Direction.LEFT:
|
||||||
|
del bound_variables[ptr.input.name]
|
||||||
|
from_side, ptr = ptr.go_up()
|
||||||
|
|
||||||
|
elif isinstance(ptr, Call):
|
||||||
|
if from_side == Direction.UP:
|
||||||
|
# Bind macros
|
||||||
|
if isinstance(ptr.left, Macro):
|
||||||
|
if ptr.left.name in bound_variables:
|
||||||
|
ptr.left = bound_variables[ptr.left.name].clone()
|
||||||
|
ptr.left.set_parent(ptr, Direction.LEFT)
|
||||||
|
if isinstance(ptr.right, Macro):
|
||||||
|
if ptr.right.name in bound_variables:
|
||||||
|
ptr.right = bound_variables[ptr.right.name].clone()
|
||||||
|
ptr.right.set_parent(ptr, Direction.RIGHT)
|
||||||
|
|
||||||
|
if not isinstance(ptr.left, EndNode):
|
||||||
|
from_side, ptr = ptr.go_left()
|
||||||
|
elif not isinstance(ptr.right, EndNode):
|
||||||
|
from_side, ptr = ptr.go_right()
|
||||||
|
else:
|
||||||
|
from_side, ptr = ptr.go_up()
|
||||||
|
|
||||||
|
elif from_side == Direction.LEFT:
|
||||||
|
if not isinstance(ptr.right, EndNode):
|
||||||
|
from_side, ptr = ptr.go_right()
|
||||||
|
else:
|
||||||
|
from_side, ptr = ptr.go_up()
|
||||||
|
|
||||||
|
elif from_side == Direction.RIGHT:
|
||||||
|
from_side, ptr = ptr.go_up()
|
||||||
|
|
||||||
|
if ptr is None:
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
|
# Apply a function.
|
||||||
|
# Returns the function's output.
|
||||||
|
def call_func(fn: Func, arg: Node):
|
||||||
|
ptr = fn
|
||||||
|
|
||||||
|
# Temporarily disconnect this function's
|
||||||
|
# parent to keep our pointer inside this
|
||||||
|
# subtree.
|
||||||
|
old_parent = fn.parent
|
||||||
|
fn.parent = None
|
||||||
|
|
||||||
|
from_side = Direction.UP
|
||||||
|
|
||||||
|
while True:
|
||||||
|
if isinstance(ptr, Bound):
|
||||||
|
if ptr == fn.input:
|
||||||
|
if ptr.parent is None:
|
||||||
|
raise Exception("Tried to substitute a None bound variable.")
|
||||||
|
|
||||||
|
if ptr.parent_side == Direction.LEFT:
|
||||||
|
ptr.parent.left = clone(arg)
|
||||||
|
ptr.parent.left.set_parent(ptr, Direction.LEFT)
|
||||||
|
else:
|
||||||
|
ptr.parent.right = clone(arg)
|
||||||
|
ptr.parent.right.set_parent(ptr, Direction.RIGHT)
|
||||||
|
|
||||||
|
from_side, ptr = ptr.go_up()
|
||||||
|
|
||||||
|
elif isinstance(ptr, Func):
|
||||||
|
if from_side == Direction.UP:
|
||||||
|
from_side, ptr = ptr.go_left()
|
||||||
|
elif from_side == Direction.LEFT:
|
||||||
|
from_side, ptr = ptr.go_up()
|
||||||
|
|
||||||
|
elif isinstance(ptr, Call):
|
||||||
|
if from_side == Direction.UP:
|
||||||
|
from_side, ptr = ptr.go_left()
|
||||||
|
elif from_side == Direction.LEFT:
|
||||||
|
from_side, ptr = ptr.go_right()
|
||||||
|
elif from_side == Direction.RIGHT:
|
||||||
|
from_side, ptr = ptr.go_up()
|
||||||
|
else:
|
||||||
|
from_side, ptr = ptr.go_up()
|
||||||
|
|
||||||
|
if ptr is None:
|
||||||
|
break
|
||||||
|
|
||||||
|
fn.parent = old_parent
|
||||||
|
return fn.left
|
||||||
|
|
||||||
|
|
||||||
|
# Do a single reduction step
|
||||||
|
def reduce(expr) -> tuple[bool, Node]:
|
||||||
|
|
||||||
|
if not isinstance(expr, Node):
|
||||||
|
raise TypeError(f"I can't reduce a {type(expr)}")
|
||||||
|
|
||||||
|
ptr = expr
|
||||||
|
from_side = Direction.UP
|
||||||
|
reduced = False
|
||||||
|
|
||||||
|
while True:
|
||||||
|
print("redu", ptr)
|
||||||
|
|
||||||
|
if isinstance(ptr, Call):
|
||||||
|
if from_side == Direction.UP:
|
||||||
|
if isinstance(ptr.left, Func):
|
||||||
|
if ptr.parent is None:
|
||||||
|
expr = call_func(ptr.left, ptr.right)
|
||||||
|
expr.set_parent(None, None)
|
||||||
|
else:
|
||||||
|
ptr.parent.left = call_func(ptr.left, ptr.right)
|
||||||
|
ptr.parent.left.set_parent(ptr.parent, Direction.LEFT)
|
||||||
|
reduced = True
|
||||||
|
break
|
||||||
|
elif isinstance(ptr.left, ExpandableEndNode):
|
||||||
|
ptr.left = ptr.left.expand()
|
||||||
|
reduced = True
|
||||||
|
break
|
||||||
|
elif isinstance(ptr.left, Call):
|
||||||
|
from_side, ptr = ptr.go_left()
|
||||||
|
else:
|
||||||
|
from_side, ptr = ptr.go_right()
|
||||||
|
|
||||||
|
else:
|
||||||
|
from_side, ptr = ptr.go_up()
|
||||||
|
|
||||||
|
elif isinstance(ptr, Func):
|
||||||
|
if from_side == Direction.UP:
|
||||||
|
from_side, ptr = ptr.go_left()
|
||||||
|
else:
|
||||||
|
from_side, ptr = ptr.go_up()
|
||||||
|
|
||||||
|
elif isinstance(ptr, EndNode):
|
||||||
|
from_side, ptr = ptr.go_up()
|
||||||
|
|
||||||
|
if ptr is None:
|
||||||
|
break
|
||||||
|
|
||||||
|
return reduced, expr
|
||||||
|
|
||||||
|
|
||||||
for l in [
|
for l in [
|
||||||
|
"T = λab.a",
|
||||||
|
"F = λab.b",
|
||||||
"NOT = λa.(a F T)",
|
"NOT = λa.(a F T)",
|
||||||
"AND = λab.(a F b)",
|
"AND = λab.(a F b)",
|
||||||
"OR = λab.(a T b)",
|
"OR = λab.(a T b)",
|
||||||
|
@ -420,8 +602,21 @@ for l in [
|
||||||
"S = λnfa.(f (n f a))",
|
"S = λnfa.(f (n f a))",
|
||||||
"Z = λn.n (λa.F) T",
|
"Z = λn.n (λa.F) T",
|
||||||
"MULT = λnmf.n (m f)",
|
"MULT = λnmf.n (m f)",
|
||||||
"H = λp.((PAIR (p F)) (S (p F)))"
|
"H = λp.((PAIR (p F)) (S (p F)))",
|
||||||
|
"D = λn.n H (PAIR 0 0) T",
|
||||||
|
"FAC = λyn.(Z n)(1)(MULT n (y (D n)))",
|
||||||
|
"S (λfa.f a)"
|
||||||
]:
|
]:
|
||||||
n = p.parse_line(l)
|
n = p.parse_line(l)
|
||||||
bind_variables(n)
|
bind_variables(n)
|
||||||
print(print_expr(n))
|
|
||||||
|
if isinstance(n, MacroDef):
|
||||||
|
macro_table[n.label] = n.expr
|
||||||
|
print(print_expr(n))
|
||||||
|
else:
|
||||||
|
for i in range(10):
|
||||||
|
r, n = reduce(n)
|
||||||
|
if not r:
|
||||||
|
break
|
||||||
|
print(print_expr(n))
|
||||||
|
#print(print_expr(clone(n)))
|
|
@ -47,7 +47,7 @@ class LambdaParser:
|
||||||
)
|
)
|
||||||
|
|
||||||
self.pp_expr <<= (
|
self.pp_expr <<= (
|
||||||
#self.pp_church ^
|
self.pp_church ^
|
||||||
self.pp_lambda_fun ^
|
self.pp_lambda_fun ^
|
||||||
self.pp_name ^
|
self.pp_name ^
|
||||||
(self.lp + self.pp_expr + self.rp) ^
|
(self.lp + self.pp_expr + self.rp) ^
|
||||||
|
|
239
lamb/tokens.py
239
lamb/tokens.py
|
@ -1,239 +0,0 @@
|
||||||
import enum
|
|
||||||
import lamb.utils as utils
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class ReductionType(enum.Enum):
|
|
||||||
MACRO_EXPAND = enum.auto()
|
|
||||||
MACRO_TO_FREE = enum.auto()
|
|
||||||
FUNCTION_APPLY = enum.auto()
|
|
||||||
AUTOCHURCH = enum.auto()
|
|
||||||
|
|
||||||
|
|
||||||
class ReductionStatus:
|
|
||||||
"""
|
|
||||||
This object helps organize reduction output.
|
|
||||||
An instance is returned after every reduction step.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
*,
|
|
||||||
output,
|
|
||||||
was_reduced: bool,
|
|
||||||
reduction_type: ReductionType | None = None
|
|
||||||
):
|
|
||||||
# The new expression
|
|
||||||
self.output = output
|
|
||||||
|
|
||||||
# What did we do?
|
|
||||||
# Will be None if was_reduced is false.
|
|
||||||
self.reduction_type = reduction_type
|
|
||||||
|
|
||||||
# Did this reduction change anything?
|
|
||||||
# If we try to reduce an irreducible expression,
|
|
||||||
# this will be false.
|
|
||||||
self.was_reduced = was_reduced
|
|
||||||
|
|
||||||
|
|
||||||
class church_num(LambdaToken):
|
|
||||||
def reduce(self, *, force_substitute = False) -> ReductionStatus:
|
|
||||||
if force_substitute: # Only expand macros if we NEED to
|
|
||||||
return ReductionStatus(
|
|
||||||
output = self.to_church(),
|
|
||||||
was_reduced = True,
|
|
||||||
reduction_type = ReductionType.AUTOCHURCH
|
|
||||||
)
|
|
||||||
else: # Otherwise, do nothing.
|
|
||||||
return ReductionStatus(
|
|
||||||
output = self,
|
|
||||||
was_reduced = False
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class free_variable(LambdaToken):
|
|
||||||
"""
|
|
||||||
Represents a free variable.
|
|
||||||
|
|
||||||
This object does not reduce to
|
|
||||||
anything, since it has no meaning.
|
|
||||||
|
|
||||||
Any name in an expression that isn't
|
|
||||||
a macro or a bound variable is assumed
|
|
||||||
to be a free variable.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, label: str):
|
|
||||||
self.label = label
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return f"<freevar {self.label}>"
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return f"{self.label}"
|
|
||||||
|
|
||||||
class macro(LambdaToken):
|
|
||||||
def reduce(
|
|
||||||
self,
|
|
||||||
*,
|
|
||||||
# To keep output readable, we avoid expanding macros as often as possible.
|
|
||||||
# Macros are irreducible if force_substitute is false.
|
|
||||||
force_substitute = False,
|
|
||||||
|
|
||||||
# If this is false, error when macros aren't defined instead of
|
|
||||||
# invisibly making a free variable.
|
|
||||||
auto_free_vars = True
|
|
||||||
) -> ReductionStatus:
|
|
||||||
|
|
||||||
if (self.name in self.runner.macro_table) and force_substitute:
|
|
||||||
if force_substitute: # Only expand macros if we NEED to
|
|
||||||
return ReductionStatus(
|
|
||||||
output = self.runner.macro_table[self.name],
|
|
||||||
reduction_type = ReductionType.MACRO_EXPAND,
|
|
||||||
was_reduced = True
|
|
||||||
)
|
|
||||||
else: # Otherwise, do nothing.
|
|
||||||
return ReductionStatus(
|
|
||||||
output = self,
|
|
||||||
was_reduced = False
|
|
||||||
)
|
|
||||||
|
|
||||||
elif not auto_free_vars:
|
|
||||||
raise ReductionError(f"Macro {self.name} is not defined")
|
|
||||||
|
|
||||||
else:
|
|
||||||
return ReductionStatus(
|
|
||||||
output = free_variable(self.name),
|
|
||||||
reduction_type = ReductionType.MACRO_TO_FREE,
|
|
||||||
was_reduced = True
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class lambda_func(LambdaToken):
|
|
||||||
def reduce(self) -> ReductionStatus:
|
|
||||||
|
|
||||||
r = self.output.reduce()
|
|
||||||
|
|
||||||
return ReductionStatus(
|
|
||||||
was_reduced = r.was_reduced,
|
|
||||||
reduction_type = r.reduction_type,
|
|
||||||
output = lambda_func(
|
|
||||||
self.input,
|
|
||||||
r.output
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def apply(
|
|
||||||
self,
|
|
||||||
val,
|
|
||||||
*,
|
|
||||||
bound_var: bound_variable | None = None
|
|
||||||
):
|
|
||||||
"""
|
|
||||||
Substitute `bound_var` into all instances of a bound variable `var`.
|
|
||||||
If `bound_var` is none, use this functions bound variable.
|
|
||||||
Returns a new object.
|
|
||||||
"""
|
|
||||||
|
|
||||||
calling_self = False
|
|
||||||
if bound_var is None:
|
|
||||||
calling_self = True
|
|
||||||
bound_var = self.input # type: ignore
|
|
||||||
new_out = self.output
|
|
||||||
if isinstance(self.output, bound_variable):
|
|
||||||
if self.output == bound_var:
|
|
||||||
new_out = val
|
|
||||||
elif isinstance(self.output, lambda_func):
|
|
||||||
new_out = self.output.apply(val, bound_var = bound_var)
|
|
||||||
elif isinstance(self.output, lambda_apply):
|
|
||||||
new_out = self.output.sub_bound_var(val, bound_var = bound_var) # type: ignore
|
|
||||||
|
|
||||||
# If we're applying THIS function,
|
|
||||||
# just give the output
|
|
||||||
if calling_self:
|
|
||||||
return new_out
|
|
||||||
|
|
||||||
# If we're applying another function,
|
|
||||||
# return this one with substitutions
|
|
||||||
else:
|
|
||||||
return lambda_func(
|
|
||||||
self.input,
|
|
||||||
new_out
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class lambda_apply(LambdaToken):
|
|
||||||
def sub_bound_var(
|
|
||||||
self,
|
|
||||||
val,
|
|
||||||
*,
|
|
||||||
bound_var: bound_variable
|
|
||||||
):
|
|
||||||
|
|
||||||
new_fn = self.fn
|
|
||||||
if isinstance(self.fn, bound_variable):
|
|
||||||
if self.fn == bound_var:
|
|
||||||
new_fn = val
|
|
||||||
elif isinstance(self.fn, lambda_func):
|
|
||||||
new_fn = self.fn.apply(val, bound_var = bound_var)
|
|
||||||
elif isinstance(self.fn, lambda_apply):
|
|
||||||
new_fn = self.fn.sub_bound_var(val, bound_var = bound_var)
|
|
||||||
|
|
||||||
new_arg = self.arg
|
|
||||||
if isinstance(self.arg, bound_variable):
|
|
||||||
if self.arg == bound_var:
|
|
||||||
new_arg = val
|
|
||||||
elif isinstance(self.arg, lambda_func):
|
|
||||||
new_arg = self.arg.apply(val, bound_var = bound_var)
|
|
||||||
elif isinstance(self.arg, lambda_apply):
|
|
||||||
new_arg = self.arg.sub_bound_var(val, bound_var = bound_var)
|
|
||||||
|
|
||||||
return lambda_apply(
|
|
||||||
new_fn,
|
|
||||||
new_arg
|
|
||||||
)
|
|
||||||
|
|
||||||
def reduce(self) -> ReductionStatus:
|
|
||||||
|
|
||||||
# If we can directly apply self.fn, do so.
|
|
||||||
if isinstance(self.fn, lambda_func):
|
|
||||||
return ReductionStatus(
|
|
||||||
was_reduced = True,
|
|
||||||
reduction_type = ReductionType.FUNCTION_APPLY,
|
|
||||||
output = self.fn.apply(self.arg)
|
|
||||||
)
|
|
||||||
|
|
||||||
# Otherwise, try to reduce self.fn.
|
|
||||||
# If that is impossible, try to reduce self.arg.
|
|
||||||
else:
|
|
||||||
if isinstance(self.fn, macro) or isinstance(self.fn, church_num):
|
|
||||||
# Macros must be reduced before we apply them as functions.
|
|
||||||
# This is the only place we force substitution.
|
|
||||||
r = self.fn.reduce(
|
|
||||||
force_substitute = True
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
r = self.fn.reduce()
|
|
||||||
|
|
||||||
if r.was_reduced:
|
|
||||||
return ReductionStatus(
|
|
||||||
was_reduced = True,
|
|
||||||
reduction_type = r.reduction_type,
|
|
||||||
output = lambda_apply(
|
|
||||||
r.output,
|
|
||||||
self.arg
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
else:
|
|
||||||
r = self.arg.reduce()
|
|
||||||
|
|
||||||
return ReductionStatus(
|
|
||||||
was_reduced = r.was_reduced,
|
|
||||||
reduction_type = r.reduction_type,
|
|
||||||
output = lambda_apply(
|
|
||||||
self.fn,
|
|
||||||
r.output
|
|
||||||
)
|
|
||||||
)
|
|
Reference in New Issue