diff --git a/README.md b/README.md index 39c638a..96ec63c 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ ## Todo (pre-release): - Fix parser (call parentheses) - - Good command parsing (`:help`, `:save`, `:load`, `:macros`, `:clear` are a bare minimum) + - Good command parsing (`:save`, `:load`, are a bare minimum) - Python files: installable, package list, etc - $\alpha$-equivalence check - Versioning @@ -15,4 +15,5 @@ - Documentation in README - Maybe a better icon? - Warn when overwriting macro - - Syntax highlighting: parenthesis, bound variables, macros, etc \ No newline at end of file + - Syntax highlighting: parenthesis, bound variables, macros, etc + - Pin header to top of screen \ No newline at end of file diff --git a/commands.py b/commands.py new file mode 100644 index 0000000..63b431d --- /dev/null +++ b/commands.py @@ -0,0 +1,96 @@ +from prompt_toolkit.formatted_text import FormattedText +from prompt_toolkit.shortcuts import clear as clear_screen + +from runstatus import CommandStatus +import greeting + + + +commands = {} +help_texts = {} + +def lamb_command(*, help_text: str): + def inner(func): + commands[func.__name__] = func + help_texts[func.__name__] = help_text + return inner + +def run(command, runner): + return commands[command](runner) + + +@lamb_command(help_text = "Show macros") +def macros(runner): + return CommandStatus( + formatted_text = FormattedText([ + ("#FF6600 bold", "\nDefined Macros:\n"), + ] + + [ + ("#FFFFFF", f"\t{name} \t {exp}\n") + for name, exp in runner.macro_table.items() + ] + ) + ) + +@lamb_command(help_text = "Clear the screen") +def clear(runner): + clear_screen() + greeting.show() + + +@lamb_command(help_text = "Print this help") +def help(runner): + return CommandStatus( + formatted_text = FormattedText([ + ("#FF6600 bold", "\nUsage:\n"), + ( + "#FFFFFF", + "\tWrite lambda expressions using your " + ), + ( + "#00FF00", + "\\" + ), + ( + "#FFFFFF", + " key.\n" + + "\tMacros can be defined using " + ), + + + ("#00FF00", "="), + ( + "#FFFFFF", + ", as in " + ), + ( + "#AAAAAA bold", + "T = λab.a\n" + ), + + + ( + "#FFFFFF", + "\tRun commands using " + ), + ( + "#00FF00", + ":" + ), + ( + "#FFFFFF", + ", for example " + ), + ( + "#AAAAAA bold", + ":help" + ), + + ("#FF6600 bold", "\n\nCommands:\n") + ] + + [ + ("#FFFFFF", f"\t{name} \t {text}\n") + for name, text in help_texts.items() + ] + ) + ) \ No newline at end of file diff --git a/greeting.py b/greeting.py index e8bb1a3..f95f2da 100644 --- a/greeting.py +++ b/greeting.py @@ -1,6 +1,6 @@ from prompt_toolkit.styles import Style from prompt_toolkit.formatted_text import HTML, to_formatted_text -from prompt_toolkit import print_formatted_text +from prompt_toolkit import print_formatted_text as printf @@ -30,7 +30,10 @@ style = Style.from_dict({ "_l": "#FF6600 bold", # Subtitle - "_s": "#B4EC85 bold" + "_s": "#B4EC85 bold", + + # :help message + "_p": "#AAAAAA" }) html = HTML(f""" @@ -46,8 +49,9 @@ html = HTML(f""" `'._.--._.' <_s> A λ calculus engine +<_p> Type :help for help """[1:-1]) def show(): - print_formatted_text(html, style = style) \ No newline at end of file + printf(html, style = style) \ No newline at end of file diff --git a/main.py b/main.py index 71042bc..8edf3bf 100755 --- a/main.py +++ b/main.py @@ -86,6 +86,9 @@ while True: if isinstance(x, runner.MacroStatus): pass + if isinstance(x, runner.CommandStatus): + printf(x.formatted_text) + # If this line was an expression, print reduction status elif isinstance(x, runner.ReduceStatus): printf(FormattedText([ diff --git a/runner.py b/runner.py index ac0e701..f67efe1 100644 --- a/runner.py +++ b/runner.py @@ -1,60 +1,17 @@ +from prompt_toolkit.formatted_text import FormattedText + import tokens from parser import Parser -import enum +import commands +from runstatus import RunStatus +from runstatus import MacroStatus +from runstatus import StopReason +from runstatus import ReduceStatus +from runstatus import CommandStatus -class RunStatus: - """ - Base class for run status. - These are returned whenever the runner does something. - """ - pass - -class MacroStatus(RunStatus): - """ - Returned when a macro is defined. - - Values: - `was_rewritten`: If true, an old macro was replaced. - `macro_label`: The name of the macro we just made. - """ - - def __init__( - self, - *, - was_rewritten: bool, - macro_label: str - ): - self.was_rewritten = was_rewritten - self.macro_label = macro_label -class StopReason(enum.Enum): - BETA_NORMAL = ("#FFFFFF", "β-normal form") - LOOP_DETECTED = ("#FFFF00", "loop detected") - MAX_EXCEEDED = ("#FFFF00", "too many reductions") - INTERRUPT = ("#FF0000", "user interrupt") - - -class ReduceStatus(RunStatus): - """ - Returned when an expression is reduced. - - Values: - `reduction_count`: How many reductions were made. - `stop_reason`: Why we stopped. See `StopReason`. - """ - - def __init__( - self, - *, - reduction_count: int, - stop_reason: StopReason, - result: tokens.LambdaToken - ): - self.reduction_count = reduction_count - self.stop_reason = stop_reason - self.result = result class Runner: @@ -65,9 +22,18 @@ class Runner: # If None, no maximum is enforced. self.reduction_limit: int | None = 300 - def exec_command(self, command: str): - if command == "help": - print("This is a help message.") + def exec_command(self, command: str) -> CommandStatus: + if command in commands.commands: + return commands.run(command, self) + + # Handle unknown commands + else: + return CommandStatus( + formatted_text = FormattedText([ + ("#FFFF00", f"Unknown command \"{command}\"") + ]) + ) + def reduce_expression(self, expr: tokens.LambdaToken) -> ReduceStatus: diff --git a/runstatus.py b/runstatus.py new file mode 100644 index 0000000..2a43af5 --- /dev/null +++ b/runstatus.py @@ -0,0 +1,73 @@ +import enum +import tokens +from prompt_toolkit.formatted_text import FormattedText + + +class RunStatus: + """ + Base class for run status. + These are returned whenever the runner does something. + """ + pass + +class MacroStatus(RunStatus): + """ + Returned when a macro is defined. + + Values: + `was_rewritten`: If true, an old macro was replaced. + `macro_label`: The name of the macro we just made. + """ + + def __init__( + self, + *, + was_rewritten: bool, + macro_label: str + ): + self.was_rewritten = was_rewritten + self.macro_label = macro_label + + +class StopReason(enum.Enum): + BETA_NORMAL = ("#FFFFFF", "β-normal form") + LOOP_DETECTED = ("#FFFF00", "loop detected") + MAX_EXCEEDED = ("#FFFF00", "too many reductions") + INTERRUPT = ("#FF0000", "user interrupt") + + +class ReduceStatus(RunStatus): + """ + Returned when an expression is reduced. + + Values: + `reduction_count`: How many reductions were made. + `stop_reason`: Why we stopped. See `StopReason`. + """ + + def __init__( + self, + *, + reduction_count: int, + stop_reason: StopReason, + result: tokens.LambdaToken + ): + self.reduction_count = reduction_count + self.stop_reason = stop_reason + self.result = result + + +class CommandStatus(RunStatus): + """ + Returned when a command is executed. + + Values: + `formatted_text`: What to print after this command is executed + """ + + def __init__( + self, + *, + formatted_text: FormattedText + ): + self.formatted_text = formatted_text \ No newline at end of file