diff --git a/README.md b/README.md index 9617388..e020b90 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/lamb/runner.py b/lamb/runner.py index 8576409..2109674 100644 --- a/lamb/runner.py +++ b/lamb/runner.py @@ -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 diff --git a/lamb/tokens.py b/lamb/tokens.py index f1a9034..8189761 100755 --- a/lamb/tokens.py +++ b/lamb/tokens.py @@ -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 = @@ -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,