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
- 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

View File

@ -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

View File

@ -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,