Added RecursionError handling and macro name check

master
Mark 2022-10-22 19:16:46 -07:00
parent 123d885adf
commit 044ec60a49
Signed by: Mark
GPG Key ID: AD62BB059C2AAEE4
3 changed files with 72 additions and 35 deletions

View File

@ -7,9 +7,7 @@
- Write a nice README - Write a nice README
- Handle or avoid recursion errors - Handle or avoid recursion errors
- Fix colors - Fix colors
- Clean up files
- Print macro content if only a macro is typed - Print macro content if only a macro is typed
- Don't expand numbers until you have to
## Todo: ## Todo:
- live syntax check - live syntax check

View File

@ -1,3 +1,4 @@
from tkinter import E
from prompt_toolkit import PromptSession from prompt_toolkit import PromptSession
from prompt_toolkit.formatted_text import FormattedText from prompt_toolkit.formatted_text import FormattedText
from prompt_toolkit import print_formatted_text as printf from prompt_toolkit import print_formatted_text as printf
@ -12,9 +13,10 @@ import lamb.utils as utils
class StopReason(enum.Enum): class StopReason(enum.Enum):
BETA_NORMAL = ("class:text", "β-normal form") BETA_NORMAL = ("class:text", "β-normal form")
LOOP_DETECTED = ("class:warn", "loop detected") LOOP_DETECTED = ("class:warn", "Loop detected")
MAX_EXCEEDED = ("class:err", "too many reductions") MAX_EXCEEDED = ("class:err", "Too many reductions")
INTERRUPT = ("class:warn", "user interrupt") INTERRUPT = ("class:warn", "User interrupt")
RECURSION = ("class:err", "Python Recursion Error")
class Runner: class Runner:
@ -50,6 +52,9 @@ class Runner:
# Give the elements of this expression access to the runner. # Give the elements of this expression access to the runner.
# Runner must be set BEFORE variables are bound. # Runner must be set BEFORE variables are bound.
e.set_runner(self) e.set_runner(self)
if isinstance(e, tokens.macro_expression):
e.bind_variables(ban_macro_name = e.label)
else:
e.bind_variables() e.bind_variables()
return e return e
@ -63,8 +68,14 @@ class Runner:
macro_expansions = 0 macro_expansions = 0
stop_reason = StopReason.MAX_EXCEEDED stop_reason = StopReason.MAX_EXCEEDED
while (self.reduction_limit is None) or (i < self.reduction_limit): while (self.reduction_limit is None) or (i < self.reduction_limit):
try:
r = expr.reduce() r = expr.reduce()
except RecursionError:
stop_reason = StopReason.RECURSION
break
expr = r.output expr = r.output
#print(expr) #print(expr)
@ -86,19 +97,37 @@ class Runner:
else: else:
i += 1 i += 1
if (
stop_reason == StopReason.BETA_NORMAL or
stop_reason == StopReason.LOOP_DETECTED
):
out_str = str(r.output) # type: ignore out_str = str(r.output) # type: ignore
printf(FormattedText([ printf(FormattedText([
("class:result_header", f"\nExit reason: "), ("class:result_header", f"\nExit reason: "),
stop_reason.value, stop_reason.value,
("class:result_header", f"\nReduction count: "), ("class:result_header", f"\nMacro expansions: "),
("class:text", str(macro_expansions)),
("class:result_header", f"\nReductions: "),
("class:text", str(i)), ("class:text", str(i)),
("class:result_header", "\n\n => "), ("class:result_header", "\n\n => "),
("class:text", out_str), ("class:text", out_str),
]), style = utils.style) ]), 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: def save_macro(self, macro: tokens.macro_expression, *, silent = False) -> None:
was_rewritten = macro.label in self.macro_table was_rewritten = macro.label in self.macro_table

View File

@ -50,7 +50,7 @@ class LambdaToken:
def set_runner(self, runner): def set_runner(self, runner):
self.runner = runner self.runner = runner
def bind_variables(self) -> None: def bind_variables(self, *, ban_macro_name: str | None = None) -> None:
pass pass
def reduce(self) -> ReductionStatus: def reduce(self) -> ReductionStatus:
@ -127,12 +127,7 @@ class free_variable(LambdaToken):
def __str__(self): def __str__(self):
return f"{self.label}" return f"{self.label}"
class command: class command(LambdaToken):
def set_runner(self, runner):
pass
def bind_variables(self):
pass
@staticmethod @staticmethod
def from_parse(result): def from_parse(result):
return command( return command(
@ -175,6 +170,10 @@ class macro(LambdaToken):
raise TypeError("Can only compare macro with macro") raise TypeError("Can only compare macro with macro")
return self.name == other.name 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( def reduce(
self, self,
*, *,
@ -210,7 +209,7 @@ class macro(LambdaToken):
was_reduced = True was_reduced = True
) )
class macro_expression: class macro_expression(LambdaToken):
""" """
Represents a line that looks like Represents a line that looks like
<name> = <expression> <name> = <expression>
@ -229,8 +228,9 @@ class macro_expression:
def set_runner(self, runner): def set_runner(self, runner):
self.expr.set_runner(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): def __init__(self, label: str, expr: LambdaToken):
self.label = label self.label = label
@ -314,7 +314,8 @@ class lambda_func(LambdaToken):
placeholder: macro | None = None, placeholder: macro | None = None,
val: bound_variable | None = None, val: bound_variable | None = None,
*, *,
binding_self: bool = False binding_self: bool = False,
ban_macro_name: str | None = None
) -> None: ) -> None:
""" """
Go through this function and all the functions inside it, Go through this function and all the functions inside it,
@ -370,19 +371,22 @@ class lambda_func(LambdaToken):
self.bind_variables( self.bind_variables(
self.input, self.input,
new_bound_var, new_bound_var,
binding_self = True binding_self = True,
ban_macro_name = ban_macro_name
) )
self.input = new_bound_var self.input = new_bound_var
# Bind variables inside this function. # Bind variables inside this function.
if isinstance(self.output, macro) and placeholder is not None: 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: if self.output == placeholder:
self.output = val # type: ignore self.output = val # type: ignore
elif isinstance(self.output, lambda_func): 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): 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: def reduce(self) -> ReductionStatus:
@ -484,7 +488,9 @@ class lambda_apply(LambdaToken):
def bind_variables( def bind_variables(
self, self,
placeholder: macro | None = None, placeholder: macro | None = None,
val: bound_variable | None = None val: bound_variable | None = None,
*,
ban_macro_name: str | None = None
) -> None: ) -> None:
""" """
Does exactly what lambda_func.bind_variables does, Does exactly what lambda_func.bind_variables does,
@ -499,20 +505,24 @@ class lambda_apply(LambdaToken):
# If val and placeholder are None, # If val and placeholder are None,
# everything below should still work as expected. # everything below should still work as expected.
if isinstance(self.fn, macro) and placeholder is not None: 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: if self.fn == placeholder:
self.fn = val # type: ignore self.fn = val # type: ignore
elif isinstance(self.fn, lambda_func): 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): 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 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: if self.arg == placeholder:
self.arg = val # type: ignore self.arg = val # type: ignore
elif isinstance(self.arg, lambda_func): 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): 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( def sub_bound_var(
self, self,