Added basic history recall
parent
c58819a7d6
commit
01e542f88c
120
lamb/node.py
120
lamb/node.py
|
@ -14,6 +14,9 @@ class ReductionType(enum.Enum):
|
|||
# We replaced a macro with an expression.
|
||||
MACRO_EXPAND = enum.auto()
|
||||
|
||||
# We expanded a history reference
|
||||
HIST_EXPAND = enum.auto()
|
||||
|
||||
# We turned a church numeral into an expression
|
||||
AUTOCHURCH = enum.auto()
|
||||
|
||||
|
@ -101,6 +104,10 @@ class Node:
|
|||
self._left: Node | None = None
|
||||
self._right: Node | None = None
|
||||
|
||||
# The runner this node is attached to.
|
||||
# Set by Node.set_runner()
|
||||
self.runner: lamb.runner.Runner = None # type: ignore
|
||||
|
||||
def __iter__(self):
|
||||
return TreeWalker(self)
|
||||
|
||||
|
@ -220,17 +227,25 @@ class Node:
|
|||
ban_macro_name = ban_macro_name
|
||||
)
|
||||
|
||||
def set_runner(self, runner):
|
||||
for s, n in self:
|
||||
if s == Direction.UP:
|
||||
n.runner = runner # type: ignore
|
||||
return self
|
||||
|
||||
class EndNode(Node):
|
||||
def print_value(self, *, export: bool = False) -> str:
|
||||
raise NotImplementedError("EndNodes MUST provide a `print_value` method!")
|
||||
|
||||
class ExpandableEndNode(EndNode):
|
||||
def expand(self, *, macro_table = {}) -> tuple[ReductionType, Node]:
|
||||
always_expand = False
|
||||
def expand(self) -> tuple[ReductionType, Node]:
|
||||
raise NotImplementedError("ExpandableEndNodes MUST provide an `expand` method!")
|
||||
|
||||
class FreeVar(EndNode):
|
||||
def __init__(self, name: str):
|
||||
def __init__(self, name: str, *, runner = None):
|
||||
self.name = name
|
||||
self.runner = runner # type: ignore
|
||||
|
||||
def __repr__(self):
|
||||
return f"<freevar {self.name}>"
|
||||
|
@ -249,11 +264,12 @@ class Macro(ExpandableEndNode):
|
|||
def from_parse(results):
|
||||
return Macro(results[0])
|
||||
|
||||
def __init__(self, name: str) -> None:
|
||||
def __init__(self, name: str, *, runner = None) -> None:
|
||||
super().__init__()
|
||||
self.name = name
|
||||
self.left = None
|
||||
self.right = None
|
||||
self.runner = runner # type: ignore
|
||||
|
||||
def __repr__(self):
|
||||
return f"<macro {self.name}>"
|
||||
|
@ -261,25 +277,26 @@ class Macro(ExpandableEndNode):
|
|||
def print_value(self, *, export: bool = False) -> str:
|
||||
return self.name
|
||||
|
||||
def expand(self, *, macro_table = {}) -> tuple[ReductionType, Node]:
|
||||
if self.name in macro_table:
|
||||
return ReductionType.MACRO_EXPAND, clone(macro_table[self.name])
|
||||
def expand(self) -> tuple[ReductionType, Node]:
|
||||
if self.name in self.runner.macro_table:
|
||||
return ReductionType.MACRO_EXPAND, clone(self.runner.macro_table[self.name])
|
||||
else:
|
||||
return ReductionType.MACRO_TO_FREE, FreeVar(self.name)
|
||||
return ReductionType.MACRO_TO_FREE, FreeVar(self.name, runner = self.runner)
|
||||
|
||||
def copy(self):
|
||||
return Macro(self.name)
|
||||
return Macro(self.name, runner = self.runner)
|
||||
|
||||
class Church(ExpandableEndNode):
|
||||
@staticmethod
|
||||
def from_parse(results):
|
||||
return Church(int(results[0]))
|
||||
|
||||
def __init__(self, value: int) -> None:
|
||||
def __init__(self, value: int, *, runner = None) -> None:
|
||||
super().__init__()
|
||||
self.value = value
|
||||
self.left = None
|
||||
self.right = None
|
||||
self.runner = runner # type: ignore
|
||||
|
||||
def __repr__(self):
|
||||
return f"<church {self.value}>"
|
||||
|
@ -287,7 +304,7 @@ class Church(ExpandableEndNode):
|
|||
def print_value(self, *, export: bool = False) -> str:
|
||||
return str(self.value)
|
||||
|
||||
def expand(self, *, macro_table = {}) -> tuple[ReductionType, Node]:
|
||||
def expand(self) -> tuple[ReductionType, Node]:
|
||||
f = Bound("f")
|
||||
a = Bound("a")
|
||||
chain = a
|
||||
|
@ -297,17 +314,46 @@ class Church(ExpandableEndNode):
|
|||
|
||||
return (
|
||||
ReductionType.AUTOCHURCH,
|
||||
Func(f, Func(a, chain))
|
||||
Func(f, Func(a, chain)).set_runner(self.runner)
|
||||
)
|
||||
|
||||
def copy(self):
|
||||
return Church(self.value)
|
||||
return Church(self.value, runner = self.runner)
|
||||
|
||||
class History(ExpandableEndNode):
|
||||
always_expand = True
|
||||
|
||||
@staticmethod
|
||||
def from_parse(results):
|
||||
return History()
|
||||
|
||||
def __init__(self, *, runner = None) -> None:
|
||||
super().__init__()
|
||||
self.left = None
|
||||
self.right = None
|
||||
self.runner = runner # type: ignore
|
||||
|
||||
def __repr__(self):
|
||||
return f"<$>"
|
||||
|
||||
def print_value(self, *, export: bool = False) -> str:
|
||||
return "$"
|
||||
|
||||
def expand(self) -> tuple[ReductionType, Node]:
|
||||
if len(self.runner.history) == 0:
|
||||
raise ReductionError(f"There isn't any history to reference.")
|
||||
return ReductionType.HIST_EXPAND, clone(self.runner.history[-1])
|
||||
|
||||
def copy(self):
|
||||
return History(runner = self.runner)
|
||||
|
||||
|
||||
bound_counter = 0
|
||||
class Bound(EndNode):
|
||||
def __init__(self, name: str, *, forced_id = None):
|
||||
def __init__(self, name: str, *, forced_id = None, runner = None):
|
||||
self.name = name
|
||||
global bound_counter
|
||||
self.runner = runner # type: ignore
|
||||
|
||||
if forced_id is None:
|
||||
self.identifier = bound_counter
|
||||
|
@ -316,7 +362,7 @@ class Bound(EndNode):
|
|||
self.identifier = forced_id
|
||||
|
||||
def copy(self):
|
||||
return Bound(self.name, forced_id = self.identifier)
|
||||
return Bound(self.name, forced_id = self.identifier, runner = self.runner)
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, Bound):
|
||||
|
@ -343,17 +389,18 @@ class Func(Node):
|
|||
Func.from_parse(result)
|
||||
)
|
||||
|
||||
def __init__(self, input: Macro | Bound, output: Node) -> None:
|
||||
def __init__(self, input: Macro | Bound, output: Node, *, runner = None) -> None:
|
||||
super().__init__()
|
||||
self.input: Macro | Bound = input
|
||||
self.left: Node = output
|
||||
self.right: None = None
|
||||
self.runner = runner # type: ignore
|
||||
|
||||
def __repr__(self):
|
||||
return f"<func {self.input!r} {self.left!r}>"
|
||||
|
||||
def copy(self):
|
||||
return Func(self.input, None) # type: ignore
|
||||
return Func(self.input, None, runner = self.runner) # type: ignore
|
||||
|
||||
class Call(Node):
|
||||
@staticmethod
|
||||
|
@ -376,16 +423,17 @@ class Call(Node):
|
|||
)] + results[2:]
|
||||
)
|
||||
|
||||
def __init__(self, fn: Node, arg: Node) -> None:
|
||||
def __init__(self, fn: Node, arg: Node, *, runner = None) -> None:
|
||||
super().__init__()
|
||||
self.left: Node = fn
|
||||
self.right: Node = arg
|
||||
self.runner = runner # type: ignore
|
||||
|
||||
def __repr__(self):
|
||||
return f"<call {self.left!r} {self.right!r}>"
|
||||
|
||||
def copy(self):
|
||||
return Call(None, None) # type: ignore
|
||||
return Call(None, None, runner = self.runner) # type: ignore
|
||||
|
||||
|
||||
def print_node(node: Node, *, export: bool = False) -> str:
|
||||
|
@ -476,21 +524,31 @@ def clone(node: Node):
|
|||
break
|
||||
return out
|
||||
|
||||
def bind_variables(node: Node, *, ban_macro_name = None) -> None:
|
||||
def bind_variables(node: Node, *, ban_macro_name = None) -> dict:
|
||||
if not isinstance(node, Node):
|
||||
raise TypeError(f"I don't know what to do with a {type(node)}")
|
||||
|
||||
bound_variables = {}
|
||||
|
||||
output = {
|
||||
"has_history": False,
|
||||
"free_variables": set()
|
||||
}
|
||||
|
||||
for s, n in node:
|
||||
if isinstance(n, History):
|
||||
output["has_history"] = True
|
||||
|
||||
# If this expression is part of a macro,
|
||||
# make sure we don't reference it inside itself.
|
||||
if isinstance(n, Macro) and ban_macro_name is not None:
|
||||
if n.name == ban_macro_name:
|
||||
elif isinstance(n, Macro):
|
||||
if (n.name == ban_macro_name) and (ban_macro_name is not None):
|
||||
raise ReductionError("Macro cannot reference self")
|
||||
|
||||
if isinstance(n, Func):
|
||||
if n.name not in node.runner.macro_table:
|
||||
output["free_variables"].add(n.name)
|
||||
|
||||
elif isinstance(n, Func):
|
||||
if s == Direction.UP:
|
||||
# Add this function's input to the table of bound variables.
|
||||
# If it is already there, raise an error.
|
||||
|
@ -517,6 +575,7 @@ def bind_variables(node: Node, *, ban_macro_name = None) -> None:
|
|||
if isinstance(n.right, Macro):
|
||||
if n.right.name in bound_variables:
|
||||
n.right = clone(bound_variables[n.right.name])
|
||||
return output
|
||||
|
||||
# Apply a function.
|
||||
# Returns the function's output.
|
||||
|
@ -532,7 +591,7 @@ def call_func(fn: Func, arg: Node):
|
|||
|
||||
|
||||
# Do a single reduction step
|
||||
def reduce(node: Node, *, macro_table = {}) -> tuple[ReductionType, Node]:
|
||||
def reduce(node: Node) -> tuple[ReductionType, Node]:
|
||||
if not isinstance(node, Node):
|
||||
raise TypeError(f"I can't reduce a {type(node)}")
|
||||
|
||||
|
@ -552,16 +611,14 @@ def reduce(node: Node, *, macro_table = {}) -> tuple[ReductionType, Node]:
|
|||
return ReductionType.FUNCTION_APPLY, out
|
||||
|
||||
elif isinstance(n.left, ExpandableEndNode):
|
||||
r, n.left = n.left.expand(
|
||||
macro_table = macro_table
|
||||
)
|
||||
r, n.left = n.left.expand()
|
||||
return r, out
|
||||
return ReductionType.NOTHING, out
|
||||
|
||||
|
||||
|
||||
# Expand all expandable end nodes.
|
||||
def force_expand_macros(node: Node, *, macro_table = {}) -> tuple[int, Node]:
|
||||
def finalize_macros(node: Node, *, force = False) -> tuple[int, Node]:
|
||||
if not isinstance(node, Node):
|
||||
raise TypeError(f"I can't reduce a {type(node)}")
|
||||
|
||||
|
@ -572,15 +629,18 @@ def force_expand_macros(node: Node, *, macro_table = {}) -> tuple[int, Node]:
|
|||
macro_expansions = 0
|
||||
|
||||
while True:
|
||||
if isinstance(ptr, ExpandableEndNode):
|
||||
if (
|
||||
isinstance(ptr, ExpandableEndNode) and
|
||||
(force or ptr.always_expand)
|
||||
):
|
||||
if ptr.parent is None:
|
||||
ptr = ptr.expand(macro_table = macro_table)[1]
|
||||
ptr = ptr.expand()[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.expand()[1]
|
||||
)
|
||||
ptr = ptr.parent.get_side(
|
||||
ptr.parent_side # type: ignore
|
||||
|
|
|
@ -16,13 +16,14 @@ class LambdaParser:
|
|||
self.pp_bound = pp.Char(pp.srange("[a-z]"))
|
||||
self.pp_name = self.pp_bound ^ self.pp_macro
|
||||
self.pp_church = pp.Word(pp.nums)
|
||||
self.pp_history = pp.Char("$")
|
||||
|
||||
# Function calls.
|
||||
#
|
||||
# <exp> <exp>
|
||||
# <exp> <exp> <exp>
|
||||
self.pp_call = pp.Forward()
|
||||
self.pp_call <<= (self.pp_expr | self.pp_bound)[2, ...]
|
||||
self.pp_call <<= (self.pp_expr | self.pp_bound | self.pp_history)[2, ...]
|
||||
|
||||
# Function definitions, right associative.
|
||||
# Function args MUST be lowercase.
|
||||
|
@ -43,7 +44,7 @@ class LambdaParser:
|
|||
pp.line_start() +
|
||||
self.pp_macro +
|
||||
pp.Suppress("=") +
|
||||
(self.pp_expr ^ self.pp_call)
|
||||
(self.pp_expr ^ self.pp_call ^ self.pp_history)
|
||||
)
|
||||
|
||||
self.pp_expr <<= (
|
||||
|
@ -51,7 +52,8 @@ class LambdaParser:
|
|||
self.pp_lambda_fun ^
|
||||
self.pp_name ^
|
||||
(self.lp + self.pp_expr + self.rp) ^
|
||||
(self.lp + self.pp_call + self.rp)
|
||||
(self.lp + self.pp_call + self.rp) ^
|
||||
(self.lp + self.pp_history + self.rp)
|
||||
)
|
||||
|
||||
self.pp_command = pp.Suppress(":") + pp.Word(pp.alphas + "_") + pp.Word(pp.alphas + pp.nums + "_")[0, ...]
|
||||
|
@ -61,7 +63,8 @@ class LambdaParser:
|
|||
self.pp_expr ^
|
||||
self.pp_macro_def ^
|
||||
self.pp_command ^
|
||||
self.pp_call
|
||||
self.pp_call ^
|
||||
self.pp_history
|
||||
)
|
||||
|
||||
def __init__(
|
||||
|
@ -73,7 +76,8 @@ class LambdaParser:
|
|||
action_func,
|
||||
action_bound,
|
||||
action_macro,
|
||||
action_call
|
||||
action_call,
|
||||
action_history
|
||||
):
|
||||
|
||||
self.make_parser()
|
||||
|
@ -85,6 +89,7 @@ class LambdaParser:
|
|||
self.pp_macro.set_parse_action(action_macro)
|
||||
self.pp_bound.set_parse_action(action_bound)
|
||||
self.pp_call.set_parse_action(action_call)
|
||||
self.pp_history.set_parse_action(action_history)
|
||||
|
||||
def parse_line(self, line: str):
|
||||
return self.pp_all.parse_string(
|
||||
|
|
|
@ -37,6 +37,9 @@ class MacroDef:
|
|||
ban_macro_name = ban_macro_name
|
||||
)
|
||||
|
||||
def set_runner(self, runner):
|
||||
return self.expr.set_runner(runner)
|
||||
|
||||
class Command:
|
||||
@staticmethod
|
||||
def from_parse(result):
|
||||
|
@ -66,7 +69,8 @@ class Runner:
|
|||
action_call = lamb.node.Call.from_parse,
|
||||
action_church = lamb.node.Church.from_parse,
|
||||
action_macro_def = MacroDef.from_parse,
|
||||
action_command = Command.from_parse
|
||||
action_command = Command.from_parse,
|
||||
action_history = lamb.node.History.from_parse
|
||||
)
|
||||
|
||||
# Maximum amount of reductions.
|
||||
|
@ -84,22 +88,48 @@ class Runner:
|
|||
# so that all digits appear to be changing.
|
||||
self.iter_update = 231
|
||||
|
||||
self.history = []
|
||||
|
||||
def prompt(self):
|
||||
return self.prompt_session.prompt(
|
||||
message = self.prompt_message
|
||||
)
|
||||
|
||||
def parse(self, line):
|
||||
def parse(self, line) -> tuple[lamb.node.Node | MacroDef | Command, dict]:
|
||||
e = self.parser.parse_line(line)
|
||||
|
||||
o = {}
|
||||
if isinstance(e, MacroDef):
|
||||
e.bind_variables(ban_macro_name = e.label)
|
||||
e.set_runner(self)
|
||||
o = e.bind_variables(ban_macro_name = e.label)
|
||||
elif isinstance(e, lamb.node.Node):
|
||||
e.bind_variables()
|
||||
return e
|
||||
e.set_runner(self)
|
||||
o = e.bind_variables()
|
||||
return e, o
|
||||
|
||||
|
||||
def reduce(self, node: lamb.node.Node) -> None:
|
||||
def reduce(self, node: lamb.node.Node, *, status = {}) -> None:
|
||||
|
||||
# Show warnings
|
||||
warning_text = []
|
||||
|
||||
if status["has_history"] and len(self.history) != 0:
|
||||
warning_text += [
|
||||
("class:code", "$"),
|
||||
("class:warn", " will be expanded to "),
|
||||
("class:code", str(self.history[-1])),
|
||||
("class:warn", "\n")
|
||||
]
|
||||
|
||||
for i in status["free_variables"]:
|
||||
warning_text += [
|
||||
("class:warn", "Macro "),
|
||||
("class:code", i),
|
||||
("class:warn", " will become a free variable.\n"),
|
||||
]
|
||||
|
||||
printf(FormattedText(warning_text), style = lamb.utils.style)
|
||||
|
||||
# Reduction Counter.
|
||||
# We also count macro (and church) expansions,
|
||||
# and subtract those from the final count.
|
||||
|
@ -119,10 +149,7 @@ class Runner:
|
|||
print(f" Reducing... {i:,}", end = "\r")
|
||||
|
||||
try:
|
||||
red_type, node = lamb.node.reduce(
|
||||
node,
|
||||
macro_table = self.macro_table
|
||||
)
|
||||
red_type, node = lamb.node.reduce(node)
|
||||
except KeyboardInterrupt:
|
||||
stop_reason = StopReason.INTERRUPT
|
||||
break
|
||||
|
@ -138,13 +165,9 @@ class Runner:
|
|||
if red_type == lamb.node.ReductionType.FUNCTION_APPLY:
|
||||
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
|
||||
# Expand all remaining macros
|
||||
m, node = lamb.node.finalize_macros(node, force = full_reduce)
|
||||
macro_expansions += m
|
||||
|
||||
if i >= self.iter_update:
|
||||
# Clear reduction counter
|
||||
|
@ -176,6 +199,9 @@ class Runner:
|
|||
("class:text", str(node)), # type: ignore
|
||||
]
|
||||
|
||||
self.history.append(lamb.node.finalize_macros(node, force = True)[1])
|
||||
|
||||
|
||||
printf(
|
||||
FormattedText(out_text),
|
||||
style = lamb.utils.style
|
||||
|
@ -205,7 +231,7 @@ class Runner:
|
|||
*,
|
||||
silent = False
|
||||
) -> None:
|
||||
e = self.parse(line)
|
||||
e, o = self.parse(line)
|
||||
|
||||
# If this line is a macro definition, save the macro.
|
||||
if isinstance(e, MacroDef):
|
||||
|
@ -225,7 +251,7 @@ class Runner:
|
|||
|
||||
# If this line is a plain expression, reduce it.
|
||||
elif isinstance(e, lamb.node.Node):
|
||||
self.reduce(e)
|
||||
self.reduce(e, status = o)
|
||||
|
||||
# We shouldn't ever get here.
|
||||
else:
|
||||
|
|
Reference in New Issue