Added basic history recall
This commit is contained in:
		
							
								
								
									
										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
	
	Block a user