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. # We replaced a macro with an expression.
MACRO_EXPAND = enum.auto() MACRO_EXPAND = enum.auto()
# We expanded a history reference
HIST_EXPAND = enum.auto()
# We turned a church numeral into an expression # We turned a church numeral into an expression
AUTOCHURCH = enum.auto() AUTOCHURCH = enum.auto()
@ -101,6 +104,10 @@ class Node:
self._left: Node | None = None self._left: Node | None = None
self._right: 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): def __iter__(self):
return TreeWalker(self) return TreeWalker(self)
@ -220,17 +227,25 @@ class Node:
ban_macro_name = ban_macro_name 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): class EndNode(Node):
def print_value(self, *, export: bool = False) -> str: def print_value(self, *, export: bool = False) -> str:
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, *, macro_table = {}) -> tuple[ReductionType, Node]: always_expand = False
def expand(self) -> 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):
def __init__(self, name: str): def __init__(self, name: str, *, runner = None):
self.name = name self.name = name
self.runner = runner # type: ignore
def __repr__(self): def __repr__(self):
return f"<freevar {self.name}>" return f"<freevar {self.name}>"
@ -249,11 +264,12 @@ class Macro(ExpandableEndNode):
def from_parse(results): def from_parse(results):
return Macro(results[0]) return Macro(results[0])
def __init__(self, name: str) -> None: def __init__(self, name: str, *, runner = None) -> None:
super().__init__() super().__init__()
self.name = name self.name = name
self.left = None self.left = None
self.right = None self.right = None
self.runner = runner # type: ignore
def __repr__(self): def __repr__(self):
return f"<macro {self.name}>" return f"<macro {self.name}>"
@ -261,25 +277,26 @@ class Macro(ExpandableEndNode):
def print_value(self, *, export: bool = False) -> str: def print_value(self, *, export: bool = False) -> str:
return self.name return self.name
def expand(self, *, macro_table = {}) -> tuple[ReductionType, Node]: def expand(self) -> tuple[ReductionType, Node]:
if self.name in macro_table: if self.name in self.runner.macro_table:
return ReductionType.MACRO_EXPAND, clone(macro_table[self.name]) return ReductionType.MACRO_EXPAND, clone(self.runner.macro_table[self.name])
else: else:
return ReductionType.MACRO_TO_FREE, FreeVar(self.name) return ReductionType.MACRO_TO_FREE, FreeVar(self.name, runner = self.runner)
def copy(self): def copy(self):
return Macro(self.name) return Macro(self.name, runner = self.runner)
class Church(ExpandableEndNode): class Church(ExpandableEndNode):
@staticmethod @staticmethod
def from_parse(results): def from_parse(results):
return Church(int(results[0])) return Church(int(results[0]))
def __init__(self, value: int) -> None: def __init__(self, value: int, *, runner = None) -> None:
super().__init__() super().__init__()
self.value = value self.value = value
self.left = None self.left = None
self.right = None self.right = None
self.runner = runner # type: ignore
def __repr__(self): def __repr__(self):
return f"<church {self.value}>" return f"<church {self.value}>"
@ -287,7 +304,7 @@ class Church(ExpandableEndNode):
def print_value(self, *, export: bool = False) -> str: def print_value(self, *, export: bool = False) -> str:
return str(self.value) return str(self.value)
def expand(self, *, macro_table = {}) -> tuple[ReductionType, Node]: def expand(self) -> tuple[ReductionType, Node]:
f = Bound("f") f = Bound("f")
a = Bound("a") a = Bound("a")
chain = a chain = a
@ -297,17 +314,46 @@ class Church(ExpandableEndNode):
return ( return (
ReductionType.AUTOCHURCH, ReductionType.AUTOCHURCH,
Func(f, Func(a, chain)) Func(f, Func(a, chain)).set_runner(self.runner)
) )
def copy(self): 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 bound_counter = 0
class Bound(EndNode): class Bound(EndNode):
def __init__(self, name: str, *, forced_id = None): def __init__(self, name: str, *, forced_id = None, runner = None):
self.name = name self.name = name
global bound_counter global bound_counter
self.runner = runner # type: ignore
if forced_id is None: if forced_id is None:
self.identifier = bound_counter self.identifier = bound_counter
@ -316,7 +362,7 @@ class Bound(EndNode):
self.identifier = forced_id self.identifier = forced_id
def copy(self): 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): def __eq__(self, other):
if not isinstance(other, Bound): if not isinstance(other, Bound):
@ -343,17 +389,18 @@ class Func(Node):
Func.from_parse(result) 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__() super().__init__()
self.input: Macro | Bound = input self.input: Macro | Bound = input
self.left: Node = output self.left: Node = output
self.right: None = None self.right: None = None
self.runner = runner # type: ignore
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 copy(self): def copy(self):
return Func(self.input, None) # type: ignore return Func(self.input, None, runner = self.runner) # type: ignore
class Call(Node): class Call(Node):
@staticmethod @staticmethod
@ -376,16 +423,17 @@ class Call(Node):
)] + results[2:] )] + results[2:]
) )
def __init__(self, fn: Node, arg: Node) -> None: def __init__(self, fn: Node, arg: Node, *, runner = None) -> None:
super().__init__() super().__init__()
self.left: Node = fn self.left: Node = fn
self.right: Node = arg self.right: Node = arg
self.runner = runner # type: ignore
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 copy(self): 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: def print_node(node: Node, *, export: bool = False) -> str:
@ -476,21 +524,31 @@ def clone(node: Node):
break break
return out 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): if not isinstance(node, Node):
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(node)}")
bound_variables = {} bound_variables = {}
output = {
"has_history": False,
"free_variables": set()
}
for s, n in node: for s, n in node:
if isinstance(n, History):
output["has_history"] = True
# If this expression is part of a macro, # If this expression is part of a macro,
# make sure we don't reference it inside itself. # make sure we don't reference it inside itself.
if isinstance(n, Macro) and ban_macro_name is not None: elif isinstance(n, Macro):
if n.name == ban_macro_name: 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 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: 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.
# If it is already there, raise an error. # 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 isinstance(n.right, Macro):
if n.right.name in bound_variables: if n.right.name in bound_variables:
n.right = clone(bound_variables[n.right.name]) n.right = clone(bound_variables[n.right.name])
return output
# Apply a function. # Apply a function.
# Returns the function's output. # Returns the function's output.
@ -532,7 +591,7 @@ def call_func(fn: Func, arg: Node):
# Do a single reduction step # 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): if not isinstance(node, Node):
raise TypeError(f"I can't reduce a {type(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 return ReductionType.FUNCTION_APPLY, out
elif isinstance(n.left, ExpandableEndNode): elif isinstance(n.left, ExpandableEndNode):
r, n.left = n.left.expand( r, n.left = n.left.expand()
macro_table = macro_table
)
return r, out return r, out
return ReductionType.NOTHING, out return ReductionType.NOTHING, out
# Expand all expandable end nodes. # 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): if not isinstance(node, Node):
raise TypeError(f"I can't reduce a {type(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 macro_expansions = 0
while True: while True:
if isinstance(ptr, ExpandableEndNode): if (
isinstance(ptr, ExpandableEndNode) and
(force or ptr.always_expand)
):
if ptr.parent is None: if ptr.parent is None:
ptr = ptr.expand(macro_table = macro_table)[1] ptr = ptr.expand()[1]
out = ptr out = ptr
ptr._set_parent(None, None) ptr._set_parent(None, None)
else: else:
ptr.parent.set_side( ptr.parent.set_side(
ptr.parent_side, # type: ignore ptr.parent_side, # type: ignore
ptr.expand(macro_table = macro_table)[1] ptr.expand()[1]
) )
ptr = ptr.parent.get_side( ptr = ptr.parent.get_side(
ptr.parent_side # type: ignore ptr.parent_side # type: ignore

View File

@ -16,13 +16,14 @@ class LambdaParser:
self.pp_bound = pp.Char(pp.srange("[a-z]")) self.pp_bound = pp.Char(pp.srange("[a-z]"))
self.pp_name = self.pp_bound ^ self.pp_macro self.pp_name = self.pp_bound ^ self.pp_macro
self.pp_church = pp.Word(pp.nums) self.pp_church = pp.Word(pp.nums)
self.pp_history = pp.Char("$")
# Function calls. # Function calls.
# #
# <exp> <exp> # <exp> <exp>
# <exp> <exp> <exp> # <exp> <exp> <exp>
self.pp_call = pp.Forward() 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 definitions, right associative.
# Function args MUST be lowercase. # Function args MUST be lowercase.
@ -43,7 +44,7 @@ class LambdaParser:
pp.line_start() + pp.line_start() +
self.pp_macro + self.pp_macro +
pp.Suppress("=") + pp.Suppress("=") +
(self.pp_expr ^ self.pp_call) (self.pp_expr ^ self.pp_call ^ self.pp_history)
) )
self.pp_expr <<= ( self.pp_expr <<= (
@ -51,7 +52,8 @@ class LambdaParser:
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) ^
(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, ...] 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_expr ^
self.pp_macro_def ^ self.pp_macro_def ^
self.pp_command ^ self.pp_command ^
self.pp_call self.pp_call ^
self.pp_history
) )
def __init__( def __init__(
@ -73,7 +76,8 @@ class LambdaParser:
action_func, action_func,
action_bound, action_bound,
action_macro, action_macro,
action_call action_call,
action_history
): ):
self.make_parser() self.make_parser()
@ -85,6 +89,7 @@ class LambdaParser:
self.pp_macro.set_parse_action(action_macro) self.pp_macro.set_parse_action(action_macro)
self.pp_bound.set_parse_action(action_bound) self.pp_bound.set_parse_action(action_bound)
self.pp_call.set_parse_action(action_call) self.pp_call.set_parse_action(action_call)
self.pp_history.set_parse_action(action_history)
def parse_line(self, line: str): def parse_line(self, line: str):
return self.pp_all.parse_string( return self.pp_all.parse_string(

View File

@ -37,6 +37,9 @@ class MacroDef:
ban_macro_name = ban_macro_name ban_macro_name = ban_macro_name
) )
def set_runner(self, runner):
return self.expr.set_runner(runner)
class Command: class Command:
@staticmethod @staticmethod
def from_parse(result): def from_parse(result):
@ -66,7 +69,8 @@ class Runner:
action_call = lamb.node.Call.from_parse, action_call = lamb.node.Call.from_parse,
action_church = lamb.node.Church.from_parse, action_church = lamb.node.Church.from_parse,
action_macro_def = MacroDef.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. # Maximum amount of reductions.
@ -84,22 +88,48 @@ 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 = []
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): def parse(self, line) -> tuple[lamb.node.Node | MacroDef | Command, dict]:
e = self.parser.parse_line(line) e = self.parser.parse_line(line)
o = {}
if isinstance(e, MacroDef): 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): elif isinstance(e, lamb.node.Node):
e.bind_variables() e.set_runner(self)
return e 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. # Reduction Counter.
# We also count macro (and church) expansions, # We also count macro (and church) expansions,
# and subtract those from the final count. # and subtract those from the final count.
@ -119,10 +149,7 @@ class Runner:
print(f" Reducing... {i:,}", end = "\r") print(f" Reducing... {i:,}", end = "\r")
try: try:
red_type, node = lamb.node.reduce( red_type, node = lamb.node.reduce(node)
node,
macro_table = self.macro_table
)
except KeyboardInterrupt: except KeyboardInterrupt:
stop_reason = StopReason.INTERRUPT stop_reason = StopReason.INTERRUPT
break break
@ -138,13 +165,9 @@ 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 # Expand all remaining macros
if full_reduce: m, node = lamb.node.finalize_macros(node, force = full_reduce)
m, node = lamb.node.force_expand_macros( macro_expansions += m
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
@ -176,6 +199,9 @@ class Runner:
("class:text", str(node)), # type: ignore ("class:text", str(node)), # type: ignore
] ]
self.history.append(lamb.node.finalize_macros(node, force = True)[1])
printf( printf(
FormattedText(out_text), FormattedText(out_text),
style = lamb.utils.style style = lamb.utils.style
@ -205,7 +231,7 @@ class Runner:
*, *,
silent = False silent = False
) -> None: ) -> None:
e = self.parse(line) e, o = self.parse(line)
# If this line is a macro definition, save the macro. # If this line is a macro definition, save the macro.
if isinstance(e, MacroDef): if isinstance(e, MacroDef):
@ -225,7 +251,7 @@ class Runner:
# If this line is a plain expression, reduce it. # If this line is a plain expression, reduce it.
elif isinstance(e, lamb.node.Node): elif isinstance(e, lamb.node.Node):
self.reduce(e) self.reduce(e, status = o)
# We shouldn't ever get here. # We shouldn't ever get here.
else: else: