Added RecursionError handling and macro name check
parent
123d885adf
commit
044ec60a49
|
@ -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,6 +52,9 @@ class Runner:
|
|||
# Give the elements of this expression access to the runner.
|
||||
# Runner must be set BEFORE variables are bound.
|
||||
e.set_runner(self)
|
||||
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):
|
||||
|
||||
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
|
||||
|
||||
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,
|
||||
|
||||
("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: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