Added RecursionError handling and macro name check
This commit is contained in:
		@ -7,9 +7,7 @@
 | 
			
		||||
 - Write a nice README
 | 
			
		||||
 - Handle or avoid recursion errors
 | 
			
		||||
 - Fix colors
 | 
			
		||||
 - Clean up files
 | 
			
		||||
 - Print macro content if only a macro is typed
 | 
			
		||||
 - Don't expand numbers until you have to
 | 
			
		||||
 | 
			
		||||
## Todo:
 | 
			
		||||
 - live syntax check
 | 
			
		||||
 | 
			
		||||
@ -1,3 +1,4 @@
 | 
			
		||||
from tkinter import E
 | 
			
		||||
from prompt_toolkit import PromptSession
 | 
			
		||||
from prompt_toolkit.formatted_text import FormattedText
 | 
			
		||||
from prompt_toolkit import print_formatted_text as printf
 | 
			
		||||
@ -12,9 +13,10 @@ import lamb.utils as utils
 | 
			
		||||
 | 
			
		||||
class StopReason(enum.Enum):
 | 
			
		||||
	BETA_NORMAL		= ("class:text", "β-normal form")
 | 
			
		||||
	LOOP_DETECTED	= ("class:warn", "loop detected")
 | 
			
		||||
	MAX_EXCEEDED	= ("class:err", "too many reductions")
 | 
			
		||||
	INTERRUPT		= ("class:warn", "user interrupt")
 | 
			
		||||
	LOOP_DETECTED	= ("class:warn", "Loop detected")
 | 
			
		||||
	MAX_EXCEEDED	= ("class:err", "Too many reductions")
 | 
			
		||||
	INTERRUPT		= ("class:warn", "User interrupt")
 | 
			
		||||
	RECURSION		= ("class:err", "Python Recursion Error")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Runner:
 | 
			
		||||
@ -50,7 +52,10 @@ class Runner:
 | 
			
		||||
		# Give the elements of this expression access to the runner.
 | 
			
		||||
		# Runner must be set BEFORE variables are bound.
 | 
			
		||||
		e.set_runner(self)
 | 
			
		||||
		e.bind_variables()
 | 
			
		||||
		if isinstance(e, tokens.macro_expression):
 | 
			
		||||
			e.bind_variables(ban_macro_name = e.label)
 | 
			
		||||
		else:
 | 
			
		||||
			e.bind_variables()
 | 
			
		||||
		return e
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -63,8 +68,14 @@ class Runner:
 | 
			
		||||
		macro_expansions = 0
 | 
			
		||||
 | 
			
		||||
		stop_reason = StopReason.MAX_EXCEEDED
 | 
			
		||||
 | 
			
		||||
		while (self.reduction_limit is None) or (i < self.reduction_limit):
 | 
			
		||||
			r = expr.reduce()
 | 
			
		||||
 | 
			
		||||
			try:
 | 
			
		||||
				r = expr.reduce()
 | 
			
		||||
			except RecursionError:
 | 
			
		||||
				stop_reason = StopReason.RECURSION
 | 
			
		||||
				break
 | 
			
		||||
			expr = r.output
 | 
			
		||||
 | 
			
		||||
			#print(expr)
 | 
			
		||||
@ -86,19 +97,37 @@ class Runner:
 | 
			
		||||
			else:
 | 
			
		||||
				i += 1
 | 
			
		||||
 | 
			
		||||
		out_str = str(r.output) # type: ignore
 | 
			
		||||
		if (
 | 
			
		||||
			stop_reason == StopReason.BETA_NORMAL or
 | 
			
		||||
			stop_reason == StopReason.LOOP_DETECTED
 | 
			
		||||
			):
 | 
			
		||||
			out_str = str(r.output) # type: ignore
 | 
			
		||||
 | 
			
		||||
		printf(FormattedText([
 | 
			
		||||
			("class:result_header", f"\nExit reason: "),
 | 
			
		||||
			stop_reason.value,
 | 
			
		||||
			printf(FormattedText([
 | 
			
		||||
				("class:result_header", f"\nExit reason: "),
 | 
			
		||||
				stop_reason.value,
 | 
			
		||||
 | 
			
		||||
			("class:result_header", f"\nReduction count: "),
 | 
			
		||||
			("class:text", str(i)),
 | 
			
		||||
				("class:result_header", f"\nMacro expansions: "),
 | 
			
		||||
				("class:text", str(macro_expansions)),
 | 
			
		||||
 | 
			
		||||
				("class:result_header", f"\nReductions: "),
 | 
			
		||||
				("class:text", str(i)),
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
			("class:result_header", "\n\n    => "),
 | 
			
		||||
			("class:text", out_str),
 | 
			
		||||
		]), style = utils.style)
 | 
			
		||||
				("class:result_header", "\n\n    => "),
 | 
			
		||||
				("class:text", out_str),
 | 
			
		||||
			]), style = utils.style)
 | 
			
		||||
		else:
 | 
			
		||||
			printf(FormattedText([
 | 
			
		||||
				("class:result_header", f"\nExit reason: "),
 | 
			
		||||
				stop_reason.value,
 | 
			
		||||
 | 
			
		||||
				("class:result_header", f"\nMacro expansions: "),
 | 
			
		||||
				("class:text", str(macro_expansions)),
 | 
			
		||||
 | 
			
		||||
				("class:result_header", f"\nReductions: "),
 | 
			
		||||
				("class:text", str(i)),
 | 
			
		||||
			]), style = utils.style)
 | 
			
		||||
 | 
			
		||||
	def save_macro(self, macro: tokens.macro_expression, *, silent = False) -> None:
 | 
			
		||||
		was_rewritten = macro.label in self.macro_table
 | 
			
		||||
 | 
			
		||||
@ -50,7 +50,7 @@ class LambdaToken:
 | 
			
		||||
	def set_runner(self, runner):
 | 
			
		||||
		self.runner = runner
 | 
			
		||||
 | 
			
		||||
	def bind_variables(self) -> None:
 | 
			
		||||
	def bind_variables(self, *, ban_macro_name: str | None = None) -> None:
 | 
			
		||||
		pass
 | 
			
		||||
 | 
			
		||||
	def reduce(self) -> ReductionStatus:
 | 
			
		||||
@ -127,12 +127,7 @@ class free_variable(LambdaToken):
 | 
			
		||||
	def __str__(self):
 | 
			
		||||
		return f"{self.label}"
 | 
			
		||||
 | 
			
		||||
class command:
 | 
			
		||||
	def set_runner(self, runner):
 | 
			
		||||
		pass
 | 
			
		||||
	def bind_variables(self):
 | 
			
		||||
		pass
 | 
			
		||||
 | 
			
		||||
class command(LambdaToken):
 | 
			
		||||
	@staticmethod
 | 
			
		||||
	def from_parse(result):
 | 
			
		||||
		return command(
 | 
			
		||||
@ -175,6 +170,10 @@ class macro(LambdaToken):
 | 
			
		||||
			raise TypeError("Can only compare macro with macro")
 | 
			
		||||
		return self.name == other.name
 | 
			
		||||
 | 
			
		||||
	def bind_variables(self, *, ban_macro_name=None) -> None:
 | 
			
		||||
		if self.name == ban_macro_name:
 | 
			
		||||
			raise ReductionError(f"Cannot use macro \"{ban_macro_name}\" here.")
 | 
			
		||||
 | 
			
		||||
	def reduce(
 | 
			
		||||
			self,
 | 
			
		||||
			*,
 | 
			
		||||
@ -210,7 +209,7 @@ class macro(LambdaToken):
 | 
			
		||||
				was_reduced = True
 | 
			
		||||
			)
 | 
			
		||||
 | 
			
		||||
class macro_expression:
 | 
			
		||||
class macro_expression(LambdaToken):
 | 
			
		||||
	"""
 | 
			
		||||
	Represents a line that looks like
 | 
			
		||||
		<name> = <expression>
 | 
			
		||||
@ -229,8 +228,9 @@ class macro_expression:
 | 
			
		||||
 | 
			
		||||
	def set_runner(self, runner):
 | 
			
		||||
		self.expr.set_runner(runner)
 | 
			
		||||
	def bind_variables(self):
 | 
			
		||||
		self.expr.bind_variables()
 | 
			
		||||
 | 
			
		||||
	def bind_variables(self, *, ban_macro_name: str | None = None):
 | 
			
		||||
		self.expr.bind_variables(ban_macro_name = ban_macro_name)
 | 
			
		||||
 | 
			
		||||
	def __init__(self, label: str, expr: LambdaToken):
 | 
			
		||||
		self.label = label
 | 
			
		||||
@ -314,7 +314,8 @@ class lambda_func(LambdaToken):
 | 
			
		||||
			placeholder: macro | None = None,
 | 
			
		||||
			val: bound_variable | None = None,
 | 
			
		||||
			*,
 | 
			
		||||
			binding_self: bool = False
 | 
			
		||||
			binding_self: bool = False,
 | 
			
		||||
			ban_macro_name: str | None = None
 | 
			
		||||
		) -> None:
 | 
			
		||||
		"""
 | 
			
		||||
		Go through this function and all the functions inside it,
 | 
			
		||||
@ -370,19 +371,22 @@ class lambda_func(LambdaToken):
 | 
			
		||||
			self.bind_variables(
 | 
			
		||||
				self.input,
 | 
			
		||||
				new_bound_var,
 | 
			
		||||
				binding_self = True
 | 
			
		||||
				binding_self = True,
 | 
			
		||||
				ban_macro_name = ban_macro_name
 | 
			
		||||
			)
 | 
			
		||||
			self.input = new_bound_var
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		# Bind variables inside this function.
 | 
			
		||||
		if isinstance(self.output, macro) and placeholder is not None:
 | 
			
		||||
			if self.output.name == ban_macro_name:
 | 
			
		||||
				raise ReductionError(f"Cannot use macro \"{ban_macro_name}\" here.")
 | 
			
		||||
			if self.output == placeholder:
 | 
			
		||||
				self.output = val # type: ignore
 | 
			
		||||
		elif isinstance(self.output, lambda_func):
 | 
			
		||||
			self.output.bind_variables(placeholder, val)
 | 
			
		||||
			self.output.bind_variables(placeholder, val, ban_macro_name = ban_macro_name)
 | 
			
		||||
		elif isinstance(self.output, lambda_apply):
 | 
			
		||||
			self.output.bind_variables(placeholder, val)
 | 
			
		||||
			self.output.bind_variables(placeholder, val, ban_macro_name = ban_macro_name)
 | 
			
		||||
 | 
			
		||||
	def reduce(self) -> ReductionStatus:
 | 
			
		||||
 | 
			
		||||
@ -484,7 +488,9 @@ class lambda_apply(LambdaToken):
 | 
			
		||||
	def bind_variables(
 | 
			
		||||
			self,
 | 
			
		||||
			placeholder: macro | None = None,
 | 
			
		||||
			val: bound_variable | None = None
 | 
			
		||||
			val: bound_variable | None = None,
 | 
			
		||||
			*,
 | 
			
		||||
			ban_macro_name: str | None = None
 | 
			
		||||
		) -> None:
 | 
			
		||||
		"""
 | 
			
		||||
		Does exactly what lambda_func.bind_variables does,
 | 
			
		||||
@ -499,20 +505,24 @@ class lambda_apply(LambdaToken):
 | 
			
		||||
		# If val and placeholder are None,
 | 
			
		||||
		# everything below should still work as expected.
 | 
			
		||||
		if isinstance(self.fn, macro) and placeholder is not None:
 | 
			
		||||
			if self.fn.name == ban_macro_name:
 | 
			
		||||
				raise ReductionError(f"Cannot use macro \"{ban_macro_name}\" here.")
 | 
			
		||||
			if self.fn == placeholder:
 | 
			
		||||
				self.fn = val # type: ignore
 | 
			
		||||
		elif isinstance(self.fn, lambda_func):
 | 
			
		||||
			self.fn.bind_variables(placeholder, val)
 | 
			
		||||
			self.fn.bind_variables(placeholder, val, ban_macro_name = ban_macro_name)
 | 
			
		||||
		elif isinstance(self.fn, lambda_apply):
 | 
			
		||||
			self.fn.bind_variables(placeholder, val)
 | 
			
		||||
			self.fn.bind_variables(placeholder, val, ban_macro_name = ban_macro_name)
 | 
			
		||||
 | 
			
		||||
		if isinstance(self.arg, macro) and placeholder is not None:
 | 
			
		||||
			if self.arg.name == ban_macro_name:
 | 
			
		||||
				raise ReductionError(f"Cannot use macro \"{ban_macro_name}\" here.")
 | 
			
		||||
			if self.arg == placeholder:
 | 
			
		||||
				self.arg = val # type: ignore
 | 
			
		||||
		elif isinstance(self.arg, lambda_func):
 | 
			
		||||
			self.arg.bind_variables(placeholder, val)
 | 
			
		||||
			self.arg.bind_variables(placeholder, val, ban_macro_name = ban_macro_name)
 | 
			
		||||
		elif isinstance(self.arg, lambda_apply):
 | 
			
		||||
			self.arg.bind_variables(placeholder, val)
 | 
			
		||||
			self.arg.bind_variables(placeholder, val, ban_macro_name = ban_macro_name)
 | 
			
		||||
 | 
			
		||||
	def sub_bound_var(
 | 
			
		||||
		self,
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user