Added basic history recall

master
Mark 2022-10-29 15:44:17 -07:00
parent c58819a7d6
commit 01e542f88c
Signed by: Mark
GPG Key ID: AD62BB059C2AAEE4
3 changed files with 145 additions and 54 deletions

View File

@ -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

View File

@ -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(

View File

@ -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,12 +165,8 @@ 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
)
# Expand all remaining macros
m, node = lamb.node.finalize_macros(node, force = full_reduce)
macro_expansions += m
if i >= self.iter_update:
@ -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: