Did the following:
- Added Root node - Created node.prepare() method - Added "macro show" warning - Rewrote TreeWalker & simplified a few methodsmaster
parent
ecbb8661ce
commit
04880b7724
15
README.md
15
README.md
|
@ -76,20 +76,21 @@ Lamb treats each λ expression as a binary tree. Variable binding and reduction
|
||||||
|
|
||||||
## Todo (pre-release):
|
## Todo (pre-release):
|
||||||
- Prettier colors
|
- Prettier colors
|
||||||
- Prevent macro-chaining recursion
|
|
||||||
- step-by-step reduction
|
|
||||||
- Full-reduce option (expand all macros)
|
|
||||||
- PyPi package
|
|
||||||
- Cleanup warnings
|
- Cleanup warnings
|
||||||
- Preprocess method: bind, macros to free, etc
|
|
||||||
- History queue
|
|
||||||
- Truncate long expressions in warnings
|
- Truncate long expressions in warnings
|
||||||
|
- Prevent macro-chaining recursion
|
||||||
|
- Full-reduce option (expand all macros)
|
||||||
|
- step-by-step reduction
|
||||||
|
- Cleanup files
|
||||||
|
- PyPi package
|
||||||
|
|
||||||
|
|
||||||
## Todo:
|
## Todo:
|
||||||
|
- History queue + command indexing
|
||||||
|
- Show history command
|
||||||
- Better class mutation: when is a node no longer valid?
|
- Better class mutation: when is a node no longer valid?
|
||||||
- Loop detection
|
- Loop detection
|
||||||
- Command-line options (load a file, run a set of commands)
|
|
||||||
- $\alpha$-equivalence check
|
- $\alpha$-equivalence check
|
||||||
|
- Command-line options (load a file, run a set of commands)
|
||||||
- Unchurch macro: make church numerals human-readable
|
- Unchurch macro: make church numerals human-readable
|
||||||
- Syntax highlighting: parenthesis, bound variables, macros, etc
|
- Syntax highlighting: parenthesis, bound variables, macros, etc
|
208
lamb/node.py
208
lamb/node.py
|
@ -20,9 +20,6 @@ class ReductionType(enum.Enum):
|
||||||
# We turned a church numeral into an expression
|
# We turned a church numeral into an expression
|
||||||
AUTOCHURCH = enum.auto()
|
AUTOCHURCH = enum.auto()
|
||||||
|
|
||||||
# We replaced a macro with a free variable.
|
|
||||||
MACRO_TO_FREE = enum.auto()
|
|
||||||
|
|
||||||
# We applied a function.
|
# We applied a function.
|
||||||
# This is the only type of "formal" reduction step.
|
# This is the only type of "formal" reduction step.
|
||||||
FUNCTION_APPLY = enum.auto()
|
FUNCTION_APPLY = enum.auto()
|
||||||
|
@ -50,27 +47,32 @@ class TreeWalker:
|
||||||
def __init__(self, expr):
|
def __init__(self, expr):
|
||||||
self.expr = expr
|
self.expr = expr
|
||||||
self.ptr = expr
|
self.ptr = expr
|
||||||
|
self.first_step = True
|
||||||
self.from_side = Direction.UP
|
self.from_side = Direction.UP
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return self
|
||||||
|
|
||||||
def __next__(self):
|
def __next__(self):
|
||||||
# This could be implemented without checking the node type,
|
# This could be implemented without checking the node type,
|
||||||
# but there's no reason to do that.
|
# but there's no reason to do that.
|
||||||
# Maybe later?
|
# Maybe later?
|
||||||
|
|
||||||
if self.ptr is self.expr.parent:
|
|
||||||
raise StopIteration
|
|
||||||
|
|
||||||
out = self.ptr
|
if self.first_step:
|
||||||
out_side = self.from_side
|
self.first_step = False
|
||||||
if isinstance(self.ptr, EndNode):
|
return self.from_side, self.ptr
|
||||||
|
|
||||||
|
if isinstance(self.ptr, Root):
|
||||||
|
if self.from_side == Direction.UP:
|
||||||
|
self.from_side, self.ptr = self.ptr.go_left()
|
||||||
|
elif isinstance(self.ptr, EndNode):
|
||||||
self.from_side, self.ptr = self.ptr.go_up()
|
self.from_side, self.ptr = self.ptr.go_up()
|
||||||
|
|
||||||
elif isinstance(self.ptr, Func):
|
elif isinstance(self.ptr, Func):
|
||||||
if self.from_side == Direction.UP:
|
if self.from_side == Direction.UP:
|
||||||
self.from_side, self.ptr = self.ptr.go_left()
|
self.from_side, self.ptr = self.ptr.go_left()
|
||||||
elif self.from_side == Direction.LEFT:
|
elif self.from_side == Direction.LEFT:
|
||||||
self.from_side, self.ptr = self.ptr.go_up()
|
self.from_side, self.ptr = self.ptr.go_up()
|
||||||
|
|
||||||
elif isinstance(self.ptr, Call):
|
elif isinstance(self.ptr, Call):
|
||||||
if self.from_side == Direction.UP:
|
if self.from_side == Direction.UP:
|
||||||
self.from_side, self.ptr = self.ptr.go_left()
|
self.from_side, self.ptr = self.ptr.go_left()
|
||||||
|
@ -78,11 +80,18 @@ class TreeWalker:
|
||||||
self.from_side, self.ptr = self.ptr.go_right()
|
self.from_side, self.ptr = self.ptr.go_right()
|
||||||
elif self.from_side == Direction.RIGHT:
|
elif self.from_side == Direction.RIGHT:
|
||||||
self.from_side, self.ptr = self.ptr.go_up()
|
self.from_side, self.ptr = self.ptr.go_up()
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise TypeError(f"I don't know how to iterate a {type(self.ptr)}")
|
raise TypeError(f"I don't know how to iterate a {type(self.ptr)}")
|
||||||
|
|
||||||
return out_side, out
|
# Stop conditions
|
||||||
|
if isinstance(self.expr, Root):
|
||||||
|
if self.ptr is self.expr:
|
||||||
|
raise StopIteration
|
||||||
|
else:
|
||||||
|
if self.ptr is self.expr.parent:
|
||||||
|
raise StopIteration
|
||||||
|
|
||||||
|
return self.from_side, self.ptr
|
||||||
|
|
||||||
class Node:
|
class Node:
|
||||||
"""
|
"""
|
||||||
|
@ -93,11 +102,11 @@ class Node:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
# The node this one is connected to.
|
# The node this one is connected to.
|
||||||
# None if this is the top objects.
|
# None if this is the top objects.
|
||||||
self.parent: Node | None = None
|
self.parent: Node = None # type: ignore
|
||||||
|
|
||||||
# What direction this is relative to the parent.
|
# What direction this is relative to the parent.
|
||||||
# Left of Right.
|
# Left of Right.
|
||||||
self.parent_side: Direction | None = None
|
self.parent_side: Direction = None # type: ignore
|
||||||
|
|
||||||
# Left and right nodes, None if empty
|
# Left and right nodes, None if empty
|
||||||
self._left: Node | None = None
|
self._left: Node | None = None
|
||||||
|
@ -220,12 +229,6 @@ class Node:
|
||||||
"""
|
"""
|
||||||
return print_node(self, export = True)
|
return print_node(self, export = True)
|
||||||
|
|
||||||
def bind_variables(self, *, ban_macro_name = None):
|
|
||||||
return bind_variables(
|
|
||||||
self,
|
|
||||||
ban_macro_name = ban_macro_name
|
|
||||||
)
|
|
||||||
|
|
||||||
def set_runner(self, runner):
|
def set_runner(self, runner):
|
||||||
for s, n in self:
|
for s, n in self:
|
||||||
if s == Direction.UP:
|
if s == Direction.UP:
|
||||||
|
@ -279,9 +282,17 @@ class Macro(ExpandableEndNode):
|
||||||
|
|
||||||
def expand(self) -> tuple[ReductionType, Node]:
|
def expand(self) -> tuple[ReductionType, Node]:
|
||||||
if self.name in self.runner.macro_table:
|
if self.name in self.runner.macro_table:
|
||||||
return ReductionType.MACRO_EXPAND, clone(self.runner.macro_table[self.name])
|
# The element in the macro table will be a Root node,
|
||||||
|
# so we clone its left element.
|
||||||
|
return (
|
||||||
|
ReductionType.MACRO_EXPAND,
|
||||||
|
clone(self.runner.macro_table[self.name].left)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
return ReductionType.MACRO_TO_FREE, FreeVar(self.name, runner = self.runner)
|
raise Exception(f"Macro {self.name} is not defined")
|
||||||
|
|
||||||
|
def to_freevar(self):
|
||||||
|
return FreeVar(self.name, runner = self.runner)
|
||||||
|
|
||||||
def copy(self):
|
def copy(self):
|
||||||
return Macro(self.name, runner = self.runner)
|
return Macro(self.name, runner = self.runner)
|
||||||
|
@ -342,7 +353,10 @@ class History(ExpandableEndNode):
|
||||||
def expand(self) -> tuple[ReductionType, Node]:
|
def expand(self) -> tuple[ReductionType, Node]:
|
||||||
if len(self.runner.history) == 0:
|
if len(self.runner.history) == 0:
|
||||||
raise ReductionError(f"There isn't any history to reference.")
|
raise ReductionError(f"There isn't any history to reference.")
|
||||||
return ReductionType.HIST_EXPAND, clone(self.runner.history[-1])
|
# .left is VERY important!
|
||||||
|
# self.runner.history will contain Root nodes,
|
||||||
|
# and we don't want those *inside* our tree.
|
||||||
|
return ReductionType.HIST_EXPAND, clone(self.runner.history[-1].left)
|
||||||
|
|
||||||
def copy(self):
|
def copy(self):
|
||||||
return History(runner = self.runner)
|
return History(runner = self.runner)
|
||||||
|
@ -401,6 +415,23 @@ class Func(Node):
|
||||||
def copy(self):
|
def copy(self):
|
||||||
return Func(self.input, None, runner = self.runner) # type: ignore
|
return Func(self.input, None, runner = self.runner) # type: ignore
|
||||||
|
|
||||||
|
class Root(Node):
|
||||||
|
"""
|
||||||
|
Root node.
|
||||||
|
Used at the top of an expression.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, left: Node, *, runner = None) -> None:
|
||||||
|
super().__init__()
|
||||||
|
self.left: Node = left
|
||||||
|
self.runner = runner # type: ignore
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"<Root {self.left!r}>"
|
||||||
|
|
||||||
|
def copy(self):
|
||||||
|
return Root(None, runner = self.runner) # type: ignore
|
||||||
|
|
||||||
class Call(Node):
|
class Call(Node):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def from_parse(results):
|
def from_parse(results):
|
||||||
|
@ -498,7 +529,7 @@ def clone(node: Node):
|
||||||
if isinstance(ptr, EndNode):
|
if isinstance(ptr, EndNode):
|
||||||
from_side, ptr = ptr.go_up()
|
from_side, ptr = ptr.go_up()
|
||||||
_, out_ptr = out_ptr.go_up()
|
_, out_ptr = out_ptr.go_up()
|
||||||
elif isinstance(ptr, Func):
|
elif isinstance(ptr, Func) or isinstance(ptr, Root):
|
||||||
if from_side == Direction.UP:
|
if from_side == Direction.UP:
|
||||||
from_side, ptr = ptr.go_left()
|
from_side, ptr = ptr.go_left()
|
||||||
out_ptr.set_side(ptr.parent_side, ptr.copy())
|
out_ptr.set_side(ptr.parent_side, ptr.copy())
|
||||||
|
@ -523,9 +554,17 @@ def clone(node: Node):
|
||||||
break
|
break
|
||||||
return out
|
return out
|
||||||
|
|
||||||
def bind_variables(node: Node, *, ban_macro_name = None) -> dict:
|
def prepare(root: Root, *, ban_macro_name = None) -> dict:
|
||||||
if not isinstance(node, Node):
|
"""
|
||||||
raise TypeError(f"I don't know what to do with a {type(node)}")
|
Prepare an expression for expansion.
|
||||||
|
This will does the following:
|
||||||
|
- Binds variables
|
||||||
|
- Turns unbound macros into free variables
|
||||||
|
- Generates warnings
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not isinstance(root, Root):
|
||||||
|
raise TypeError(f"I don't know what to do with a {type(root)}")
|
||||||
|
|
||||||
bound_variables = {}
|
bound_variables = {}
|
||||||
|
|
||||||
|
@ -534,7 +573,8 @@ def bind_variables(node: Node, *, ban_macro_name = None) -> dict:
|
||||||
"free_variables": set()
|
"free_variables": set()
|
||||||
}
|
}
|
||||||
|
|
||||||
for s, n in node:
|
it = iter(root)
|
||||||
|
for s, n in it:
|
||||||
if isinstance(n, History):
|
if isinstance(n, History):
|
||||||
output["has_history"] = True
|
output["has_history"] = True
|
||||||
|
|
||||||
|
@ -544,9 +584,26 @@ def bind_variables(node: Node, *, ban_macro_name = None) -> dict:
|
||||||
if (n.name == ban_macro_name) and (ban_macro_name is not None):
|
if (n.name == ban_macro_name) and (ban_macro_name is not None):
|
||||||
raise ReductionError("Macro cannot reference self")
|
raise ReductionError("Macro cannot reference self")
|
||||||
|
|
||||||
if n.name not in node.runner.macro_table:
|
# Bind variables
|
||||||
output["free_variables"].add(n.name)
|
if n.name in bound_variables:
|
||||||
|
n.parent.set_side(
|
||||||
|
n.parent_side,
|
||||||
|
clone(bound_variables[n.name])
|
||||||
|
)
|
||||||
|
it.ptr = n.parent.get_side(n.parent_side)
|
||||||
|
|
||||||
|
# Turn undefined macros into free variables
|
||||||
|
elif n.name not in root.runner.macro_table:
|
||||||
|
output["free_variables"].add(n.name)
|
||||||
|
n.parent.set_side(
|
||||||
|
n.parent_side,
|
||||||
|
n.to_freevar()
|
||||||
|
)
|
||||||
|
it.ptr = n.parent.get_side(n.parent_side)
|
||||||
|
|
||||||
|
|
||||||
|
# Save bound variables when we enter a function's sub-tree,
|
||||||
|
# delete them when we exit it.
|
||||||
elif isinstance(n, Func):
|
elif isinstance(n, Func):
|
||||||
if s == Direction.UP:
|
if s == Direction.UP:
|
||||||
# Add this function's input to the table of bound variables.
|
# Add this function's input to the table of bound variables.
|
||||||
|
@ -557,23 +614,9 @@ def bind_variables(node: Node, *, ban_macro_name = None) -> dict:
|
||||||
bound_variables[n.input.name] = Bound(n.input.name)
|
bound_variables[n.input.name] = Bound(n.input.name)
|
||||||
n.input = bound_variables[n.input.name]
|
n.input = bound_variables[n.input.name]
|
||||||
|
|
||||||
# If output is a macro, swap it with a bound variable.
|
|
||||||
if isinstance(n.left, Macro):
|
|
||||||
if n.left.name in bound_variables:
|
|
||||||
n.left = clone(bound_variables[n.left.name])
|
|
||||||
|
|
||||||
elif s == Direction.LEFT:
|
elif s == Direction.LEFT:
|
||||||
del bound_variables[n.input.name]
|
del bound_variables[n.input.name]
|
||||||
|
|
||||||
elif isinstance(n, Call):
|
|
||||||
if s == Direction.UP:
|
|
||||||
# Bind macros
|
|
||||||
if isinstance(n.left, Macro):
|
|
||||||
if n.left.name in bound_variables:
|
|
||||||
n.left = clone(bound_variables[n.left.name])
|
|
||||||
if isinstance(n.right, Macro):
|
|
||||||
if n.right.name in bound_variables:
|
|
||||||
n.right = clone(bound_variables[n.right.name])
|
|
||||||
return output
|
return output
|
||||||
|
|
||||||
# Apply a function.
|
# Apply a function.
|
||||||
|
@ -589,22 +632,18 @@ def call_func(fn: Func, arg: Node):
|
||||||
return fn.left
|
return fn.left
|
||||||
|
|
||||||
# Do a single reduction step
|
# Do a single reduction step
|
||||||
def reduce(node: Node) -> tuple[ReductionType, Node]:
|
def reduce(root: Root) -> tuple[ReductionType, Root]:
|
||||||
if not isinstance(node, Node):
|
if not isinstance(root, Root):
|
||||||
raise TypeError(f"I can't reduce a {type(node)}")
|
raise TypeError(f"I can't reduce a {type(root)}")
|
||||||
|
|
||||||
out = node
|
out = root
|
||||||
for s, n in out:
|
for s, n in out:
|
||||||
if isinstance(n, Call) and (s == Direction.UP):
|
if isinstance(n, Call) and (s == Direction.UP):
|
||||||
if isinstance(n.left, Func):
|
if isinstance(n.left, Func):
|
||||||
if n.parent is None:
|
n.parent.set_side(
|
||||||
out = call_func(n.left, n.right)
|
n.parent_side, # type: ignore
|
||||||
out._set_parent(None, None)
|
call_func(n.left, n.right)
|
||||||
else:
|
)
|
||||||
n.parent.set_side(
|
|
||||||
n.parent_side, # type: ignore
|
|
||||||
call_func(n.left, n.right)
|
|
||||||
)
|
|
||||||
|
|
||||||
return ReductionType.FUNCTION_APPLY, out
|
return ReductionType.FUNCTION_APPLY, out
|
||||||
|
|
||||||
|
@ -614,7 +653,7 @@ def reduce(node: Node) -> tuple[ReductionType, Node]:
|
||||||
return ReductionType.NOTHING, out
|
return ReductionType.NOTHING, out
|
||||||
|
|
||||||
|
|
||||||
def expand(node: Node, *, force_all = False) -> tuple[int, Node]:
|
def expand(root: Root, *, force_all = False) -> tuple[int, Root]:
|
||||||
"""
|
"""
|
||||||
Expands expandable nodes in the given tree.
|
Expands expandable nodes in the given tree.
|
||||||
|
|
||||||
|
@ -625,50 +664,25 @@ def expand(node: Node, *, force_all = False) -> tuple[int, Node]:
|
||||||
ExpandableEndnodes.
|
ExpandableEndnodes.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not isinstance(node, Node):
|
if not isinstance(root, Root):
|
||||||
raise TypeError(f"I don't know what to do with a {type(node)}")
|
raise TypeError(f"I don't know what to do with a {type(root)}")
|
||||||
|
|
||||||
out = clone(node)
|
out = root
|
||||||
ptr = out
|
|
||||||
from_side = Direction.UP
|
|
||||||
macro_expansions = 0
|
macro_expansions = 0
|
||||||
|
|
||||||
while True:
|
it = iter(root)
|
||||||
|
for s, n in it:
|
||||||
if (
|
if (
|
||||||
isinstance(ptr, ExpandableEndNode) and
|
isinstance(n, ExpandableEndNode) and
|
||||||
(force_all or ptr.always_expand)
|
(force_all or n.always_expand)
|
||||||
):
|
):
|
||||||
if ptr.parent is None:
|
|
||||||
ptr = ptr.expand()[1]
|
n.parent.set_side(
|
||||||
out = ptr
|
n.parent_side, # type: ignore
|
||||||
ptr._set_parent(None, None)
|
n.expand()[1]
|
||||||
else:
|
)
|
||||||
ptr.parent.set_side(
|
it.ptr = n.parent.get_side(
|
||||||
ptr.parent_side, # type: ignore
|
n.parent_side # type: ignore
|
||||||
ptr.expand()[1]
|
)
|
||||||
)
|
|
||||||
ptr = ptr.parent.get_side(
|
|
||||||
ptr.parent_side # type: ignore
|
|
||||||
)
|
|
||||||
macro_expansions += 1
|
macro_expansions += 1
|
||||||
|
|
||||||
|
|
||||||
# Tree walk logic
|
|
||||||
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
|
return macro_expansions, out
|
|
@ -13,6 +13,7 @@ class StopReason(enum.Enum):
|
||||||
LOOP_DETECTED = ("class:warn", "Loop detected")
|
LOOP_DETECTED = ("class:warn", "Loop detected")
|
||||||
MAX_EXCEEDED = ("class:err", "Too many reductions")
|
MAX_EXCEEDED = ("class:err", "Too many reductions")
|
||||||
INTERRUPT = ("class:warn", "User interrupt")
|
INTERRUPT = ("class:warn", "User interrupt")
|
||||||
|
SHOW_MACRO = ("class:text", "Displaying macro content")
|
||||||
|
|
||||||
class MacroDef:
|
class MacroDef:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -32,11 +33,6 @@ class MacroDef:
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.label} := {self.expr}"
|
return f"{self.label} := {self.expr}"
|
||||||
|
|
||||||
def bind_variables(self, *, ban_macro_name = None):
|
|
||||||
return self.expr.bind_variables(
|
|
||||||
ban_macro_name = ban_macro_name
|
|
||||||
)
|
|
||||||
|
|
||||||
def set_runner(self, runner):
|
def set_runner(self, runner):
|
||||||
return self.expr.set_runner(runner)
|
return self.expr.set_runner(runner)
|
||||||
|
|
||||||
|
@ -88,27 +84,30 @@ class Runner:
|
||||||
# so that all digits appear to be changing.
|
# so that all digits appear to be changing.
|
||||||
self.iter_update = 231
|
self.iter_update = 231
|
||||||
|
|
||||||
self.history = []
|
self.history: list[lamb.node.Root] = []
|
||||||
|
|
||||||
def prompt(self):
|
def prompt(self):
|
||||||
return self.prompt_session.prompt(
|
return self.prompt_session.prompt(
|
||||||
message = self.prompt_message
|
message = self.prompt_message
|
||||||
)
|
)
|
||||||
|
|
||||||
def parse(self, line) -> tuple[lamb.node.Node | MacroDef | Command, dict]:
|
def parse(self, line) -> tuple[lamb.node.Root | MacroDef | Command, dict]:
|
||||||
e = self.parser.parse_line(line)
|
e = self.parser.parse_line(line)
|
||||||
|
|
||||||
o = {}
|
o = {}
|
||||||
if isinstance(e, MacroDef):
|
if isinstance(e, MacroDef):
|
||||||
|
e.expr = lamb.node.Root(e.expr)
|
||||||
e.set_runner(self)
|
e.set_runner(self)
|
||||||
o = e.bind_variables(ban_macro_name = e.label)
|
o = lamb.node.prepare(e.expr, ban_macro_name = e.label)
|
||||||
elif isinstance(e, lamb.node.Node):
|
elif isinstance(e, lamb.node.Node):
|
||||||
|
e = lamb.node.Root(e)
|
||||||
e.set_runner(self)
|
e.set_runner(self)
|
||||||
o = e.bind_variables()
|
o = lamb.node.prepare(e)
|
||||||
|
|
||||||
return e, o
|
return e, o
|
||||||
|
|
||||||
|
|
||||||
def reduce(self, node: lamb.node.Node, *, status = {}) -> None:
|
def reduce(self, node: lamb.node.Root, *, status = {}) -> None:
|
||||||
|
|
||||||
warning_text = []
|
warning_text = []
|
||||||
|
|
||||||
|
@ -130,12 +129,9 @@ class Runner:
|
||||||
("class:warn", "\n")
|
("class:warn", "\n")
|
||||||
]
|
]
|
||||||
|
|
||||||
only_macro = isinstance(node, lamb.node.ExpandableEndNode)
|
only_macro = isinstance(node.left, lamb.node.Macro)
|
||||||
if only_macro:
|
if only_macro:
|
||||||
warning_text += [
|
stop_reason = StopReason.SHOW_MACRO
|
||||||
("class:warn", "All macros will be expanded"),
|
|
||||||
("class:warn", "\n")
|
|
||||||
]
|
|
||||||
m, node = lamb.node.expand(node, force_all = only_macro)
|
m, node = lamb.node.expand(node, force_all = only_macro)
|
||||||
macro_expansions += m
|
macro_expansions += m
|
||||||
|
|
||||||
|
@ -147,10 +143,16 @@ class Runner:
|
||||||
("class:warn", " is a free variable\n"),
|
("class:warn", " is a free variable\n"),
|
||||||
]
|
]
|
||||||
|
|
||||||
printf(FormattedText(warning_text), style = lamb.utils.style)
|
if len(warning_text) != 0:
|
||||||
|
printf(FormattedText(warning_text), style = lamb.utils.style)
|
||||||
|
|
||||||
|
|
||||||
while (self.reduction_limit is None) or (k < self.reduction_limit):
|
while (
|
||||||
|
(
|
||||||
|
(self.reduction_limit is None) or
|
||||||
|
(k < self.reduction_limit)
|
||||||
|
) and not only_macro
|
||||||
|
):
|
||||||
|
|
||||||
# Show reduction count
|
# Show reduction count
|
||||||
if (k >= self.iter_update) and (k % self.iter_update == 0):
|
if (k >= self.iter_update) and (k % self.iter_update == 0):
|
||||||
|
@ -177,22 +179,32 @@ class Runner:
|
||||||
# Clear reduction counter if it was printed
|
# Clear reduction counter if it was printed
|
||||||
print(" " * round(14 + math.log10(k)), end = "\r")
|
print(" " * round(14 + math.log10(k)), end = "\r")
|
||||||
|
|
||||||
out_text += [
|
if only_macro:
|
||||||
("class:result_header", f"Runtime: "),
|
out_text += [
|
||||||
("class:text", f"{time.time() - start_time:.03f} seconds"),
|
("class:result_header", f"Displaying macro content")
|
||||||
|
]
|
||||||
|
|
||||||
("class:result_header", f"\nExit reason: "),
|
else:
|
||||||
stop_reason.value,
|
out_text += [
|
||||||
|
("class:result_header", f"Runtime: "),
|
||||||
|
("class:text", f"{time.time() - start_time:.03f} seconds"),
|
||||||
|
|
||||||
("class:result_header", f"\nMacro expansions: "),
|
("class:result_header", f"\nExit reason: "),
|
||||||
("class:text", f"{macro_expansions:,}"),
|
stop_reason.value,
|
||||||
|
|
||||||
("class:result_header", f"\nReductions: "),
|
("class:result_header", f"\nMacro expansions: "),
|
||||||
("class:text", f"{k:,}\t"),
|
("class:text", f"{macro_expansions:,}"),
|
||||||
("class:muted", f"(Limit: {self.reduction_limit:,})")
|
|
||||||
]
|
|
||||||
|
|
||||||
if (stop_reason == StopReason.BETA_NORMAL or stop_reason == StopReason.LOOP_DETECTED):
|
("class:result_header", f"\nReductions: "),
|
||||||
|
("class:text", f"{k:,}\t"),
|
||||||
|
("class:muted", f"(Limit: {self.reduction_limit:,})")
|
||||||
|
]
|
||||||
|
|
||||||
|
if (
|
||||||
|
stop_reason == StopReason.BETA_NORMAL or
|
||||||
|
stop_reason == StopReason.LOOP_DETECTED or
|
||||||
|
only_macro
|
||||||
|
):
|
||||||
out_text += [
|
out_text += [
|
||||||
("class:result_header", "\n\n => "),
|
("class:result_header", "\n\n => "),
|
||||||
("class:text", str(node)), # type: ignore
|
("class:text", str(node)), # type: ignore
|
||||||
|
|
Reference in New Issue