Added macro full-expansion
parent
f67d1e2730
commit
81c78d7921
|
@ -64,7 +64,7 @@ Lamb comes with a few commands. Prefix them with a `:`
|
||||||
|
|
||||||
## Internals
|
## Internals
|
||||||
|
|
||||||
Lamb treats each λ expression as a binary tree. Variable binding and reduction are all simple operations on that tree. All the magic happens inside [`nodes.py`](./lamb/nodes.py).
|
Lamb treats each λ expression as a binary tree. Variable binding and reduction are all simple operations on that tree. All this magic happens in [`nodes.py`](./lamb/nodes.py).
|
||||||
|
|
||||||
**Highlights:**
|
**Highlights:**
|
||||||
- `TreeWalker` is the iterator we (usually) use to traverse our tree. It walks the "perimeter" of the tree, visiting some nodes multiple times.
|
- `TreeWalker` is the iterator we (usually) use to traverse our tree. It walks the "perimeter" of the tree, visiting some nodes multiple times.
|
||||||
|
@ -80,16 +80,17 @@ Lamb treats each λ expression as a binary tree. Variable binding and reduction
|
||||||
- Prettier colors
|
- Prettier colors
|
||||||
- Prevent macro-chaining recursion
|
- Prevent macro-chaining recursion
|
||||||
- step-by-step reduction
|
- step-by-step reduction
|
||||||
|
- Full-reduce option (expand all macros)
|
||||||
- Show a warning when a free variable is created
|
- Show a warning when a free variable is created
|
||||||
- PyPi package
|
- PyPi package
|
||||||
|
|
||||||
|
|
||||||
## Todo:
|
## Todo:
|
||||||
|
- Optimization: clone only if absolutely necessary
|
||||||
|
- Better class mutation: when is a node no longer valid?
|
||||||
|
- Loop detection
|
||||||
- Command-line options (load a file, run a set of commands)
|
- Command-line options (load a file, run a set of commands)
|
||||||
- $\alpha$-equivalence check
|
- $\alpha$-equivalence check
|
||||||
- Unchurch macro: make church numerals human-readable
|
- Unchurch macro: make church numerals human-readable
|
||||||
- Full-reduce option (expand all macros)
|
|
||||||
- Print macro content if only a macro is typed
|
|
||||||
- Smart alignment in all printouts
|
- Smart alignment in all printouts
|
||||||
- Syntax highlighting: parenthesis, bound variables, macros, etc
|
- Syntax highlighting: parenthesis, bound variables, macros, etc
|
||||||
|
|
||||||
|
|
65
lamb/node.py
65
lamb/node.py
|
@ -152,6 +152,14 @@ class Node:
|
||||||
else:
|
else:
|
||||||
raise TypeError("Can only set left or right side.")
|
raise TypeError("Can only set left or right side.")
|
||||||
|
|
||||||
|
def get_side(self, side: Direction):
|
||||||
|
if side == Direction.LEFT:
|
||||||
|
return self.left
|
||||||
|
elif side == Direction.RIGHT:
|
||||||
|
return self.right
|
||||||
|
else:
|
||||||
|
raise TypeError("Can only get left or right side.")
|
||||||
|
|
||||||
|
|
||||||
def go_left(self):
|
def go_left(self):
|
||||||
"""
|
"""
|
||||||
|
@ -210,7 +218,7 @@ class EndNode(Node):
|
||||||
raise NotImplementedError("EndNodes MUST provide a `print_value` method!")
|
raise NotImplementedError("EndNodes MUST provide a `print_value` method!")
|
||||||
|
|
||||||
class ExpandableEndNode(EndNode):
|
class ExpandableEndNode(EndNode):
|
||||||
def expand(self) -> tuple[ReductionType, Node]:
|
def expand(self, *, macro_table = {}) -> tuple[ReductionType, Node]:
|
||||||
raise NotImplementedError("ExpandableEndNodes MUST provide an `expand` method!")
|
raise NotImplementedError("ExpandableEndNodes MUST provide an `expand` method!")
|
||||||
|
|
||||||
class FreeVar(EndNode):
|
class FreeVar(EndNode):
|
||||||
|
@ -269,7 +277,7 @@ class Church(ExpandableEndNode):
|
||||||
def print_value(self):
|
def print_value(self):
|
||||||
return str(self.value)
|
return str(self.value)
|
||||||
|
|
||||||
def expand(self) -> tuple[ReductionType, Node]:
|
def expand(self, *, macro_table = {}) -> tuple[ReductionType, Node]:
|
||||||
f = Bound("f")
|
f = Bound("f")
|
||||||
a = Bound("a")
|
a = Bound("a")
|
||||||
chain = a
|
chain = a
|
||||||
|
@ -518,12 +526,57 @@ def reduce(node: Node, *, macro_table = {}) -> tuple[ReductionType, Node]:
|
||||||
return ReductionType.FUNCTION_APPLY, out
|
return ReductionType.FUNCTION_APPLY, out
|
||||||
|
|
||||||
elif isinstance(n.left, ExpandableEndNode):
|
elif isinstance(n.left, ExpandableEndNode):
|
||||||
if isinstance(n.left, Macro):
|
|
||||||
r, n.left = n.left.expand(
|
r, n.left = n.left.expand(
|
||||||
macro_table = macro_table
|
macro_table = macro_table
|
||||||
)
|
)
|
||||||
else:
|
|
||||||
r, n.left = n.left.expand()
|
|
||||||
return r, out
|
return r, out
|
||||||
|
|
||||||
return ReductionType.NOTHING, out
|
return ReductionType.NOTHING, out
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Expand all expandable end nodes.
|
||||||
|
def force_expand_macros(node: Node, *, macro_table = {}) -> tuple[int, Node]:
|
||||||
|
if not isinstance(node, Node):
|
||||||
|
raise TypeError(f"I can't reduce a {type(node)}")
|
||||||
|
|
||||||
|
|
||||||
|
out = clone(node)
|
||||||
|
ptr = out
|
||||||
|
from_side = Direction.UP
|
||||||
|
macro_expansions = 0
|
||||||
|
|
||||||
|
while True:
|
||||||
|
if isinstance(ptr, ExpandableEndNode):
|
||||||
|
if ptr.parent is None:
|
||||||
|
ptr = ptr.expand(macro_table = macro_table)[1]
|
||||||
|
out = ptr
|
||||||
|
ptr._set_parent(None, None)
|
||||||
|
else:
|
||||||
|
ptr.parent.set_side(
|
||||||
|
ptr.parent_side, # type: ignore
|
||||||
|
ptr.expand(macro_table = macro_table)[1]
|
||||||
|
)
|
||||||
|
ptr = ptr.parent.get_side(
|
||||||
|
ptr.parent_side # type: ignore
|
||||||
|
)
|
||||||
|
macro_expansions += 1
|
||||||
|
|
||||||
|
|
||||||
|
if isinstance(ptr, EndNode):
|
||||||
|
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()
|
||||||
|
if ptr is node.parent:
|
||||||
|
break
|
||||||
|
|
||||||
|
return macro_expansions, out # type: ignore
|
|
@ -108,6 +108,9 @@ class Runner:
|
||||||
|
|
||||||
stop_reason = StopReason.MAX_EXCEEDED
|
stop_reason = StopReason.MAX_EXCEEDED
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
|
full_reduce = isinstance(node, lamb.node.ExpandableEndNode)
|
||||||
|
out_text = []
|
||||||
|
|
||||||
|
|
||||||
while (self.reduction_limit is None) or (i < self.reduction_limit):
|
while (self.reduction_limit is None) or (i < self.reduction_limit):
|
||||||
|
|
||||||
|
@ -116,7 +119,7 @@ class Runner:
|
||||||
print(f" Reducing... {i}", end = "\r")
|
print(f" Reducing... {i}", end = "\r")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
red_type, new_node = lamb.node.reduce(
|
red_type, node = lamb.node.reduce(
|
||||||
node,
|
node,
|
||||||
macro_table = self.macro_table
|
macro_table = self.macro_table
|
||||||
)
|
)
|
||||||
|
@ -124,8 +127,6 @@ class Runner:
|
||||||
stop_reason = StopReason.INTERRUPT
|
stop_reason = StopReason.INTERRUPT
|
||||||
break
|
break
|
||||||
|
|
||||||
node = new_node
|
|
||||||
|
|
||||||
# If we can't reduce this expression anymore,
|
# If we can't reduce this expression anymore,
|
||||||
# it's in beta-normal form.
|
# it's in beta-normal form.
|
||||||
if red_type == lamb.node.ReductionType.NOTHING:
|
if red_type == lamb.node.ReductionType.NOTHING:
|
||||||
|
@ -137,12 +138,20 @@ class Runner:
|
||||||
if red_type == lamb.node.ReductionType.FUNCTION_APPLY:
|
if red_type == lamb.node.ReductionType.FUNCTION_APPLY:
|
||||||
macro_expansions += 1
|
macro_expansions += 1
|
||||||
|
|
||||||
|
# Expand all macros if we need to
|
||||||
|
if full_reduce:
|
||||||
|
m, node = lamb.node.force_expand_macros(
|
||||||
|
node,
|
||||||
|
macro_table = self.macro_table
|
||||||
|
)
|
||||||
|
macro_expansions += m
|
||||||
|
|
||||||
if i >= self.iter_update:
|
if i >= self.iter_update:
|
||||||
# Clear reduction counter
|
# Clear reduction counter
|
||||||
print(" " * round(14 + math.log10(i)), end = "\r")
|
print(" " * round(14 + math.log10(i)), end = "\r")
|
||||||
|
|
||||||
out_text = [
|
out_text += [
|
||||||
("class:result_header", f"\nRuntime: "),
|
("class:result_header", f"Runtime: "),
|
||||||
("class:text", f"{time.time() - start_time:.03f} seconds"),
|
("class:text", f"{time.time() - start_time:.03f} seconds"),
|
||||||
|
|
||||||
("class:result_header", f"\nExit reason: "),
|
("class:result_header", f"\nExit reason: "),
|
||||||
|
@ -152,14 +161,19 @@ class Runner:
|
||||||
("class:text", f"{macro_expansions:,}"),
|
("class:text", f"{macro_expansions:,}"),
|
||||||
|
|
||||||
("class:result_header", f"\nReductions: "),
|
("class:result_header", f"\nReductions: "),
|
||||||
("class:text", f"{i:,} "),
|
("class:text", f"{i:,}\t"),
|
||||||
("class:muted", f"(Limit: {self.reduction_limit:,})")
|
("class:muted", f"(Limit: {self.reduction_limit:,})")
|
||||||
]
|
]
|
||||||
|
|
||||||
|
if full_reduce:
|
||||||
|
out_text += [
|
||||||
|
("class:warn", "\nAll macros have been expanded")
|
||||||
|
]
|
||||||
|
|
||||||
if (stop_reason == StopReason.BETA_NORMAL or stop_reason == StopReason.LOOP_DETECTED):
|
if (stop_reason == StopReason.BETA_NORMAL or stop_reason == StopReason.LOOP_DETECTED):
|
||||||
out_text += [
|
out_text += [
|
||||||
("class:result_header", "\n\n => "),
|
("class:result_header", "\n\n => "),
|
||||||
("class:text", str(new_node)), # type: ignore
|
("class:text", str(node)), # type: ignore
|
||||||
]
|
]
|
||||||
|
|
||||||
printf(
|
printf(
|
||||||
|
|
Reference in New Issue