Renamed package for pypi
This commit is contained in:
2
lamb_engine/runner/__init__.py
Normal file
2
lamb_engine/runner/__init__.py
Normal file
@ -0,0 +1,2 @@
|
||||
from .runner import Runner
|
||||
from .runner import StopReason
|
420
lamb_engine/runner/commands.py
Normal file
420
lamb_engine/runner/commands.py
Normal file
@ -0,0 +1,420 @@
|
||||
from prompt_toolkit.formatted_text import FormattedText
|
||||
from prompt_toolkit.formatted_text import HTML
|
||||
from prompt_toolkit import print_formatted_text as printf
|
||||
from prompt_toolkit.shortcuts import clear as clear_screen
|
||||
from prompt_toolkit import prompt
|
||||
|
||||
import os.path
|
||||
from pyparsing import exceptions as ppx
|
||||
|
||||
import lamb_engine
|
||||
|
||||
commands = {}
|
||||
help_texts = {}
|
||||
|
||||
def lamb_command(
|
||||
*,
|
||||
command_name: str | None = None,
|
||||
help_text: str
|
||||
):
|
||||
"""
|
||||
A decorator that allows us to easily make commands
|
||||
"""
|
||||
|
||||
def inner(func):
|
||||
name = func.__name__ if command_name is None else command_name
|
||||
|
||||
commands[name] = func
|
||||
help_texts[name] = help_text
|
||||
return inner
|
||||
|
||||
@lamb_command(
|
||||
command_name = "step",
|
||||
help_text = "Toggle step-by-step reduction"
|
||||
)
|
||||
def cmd_step(command, runner) -> None:
|
||||
if len(command.args) > 1:
|
||||
printf(
|
||||
HTML(
|
||||
f"<err>Command <code>:{command.name}</code> takes no more than one argument.</err>"
|
||||
),
|
||||
style = lamb_engine.utils.style
|
||||
)
|
||||
return
|
||||
|
||||
target = not runner.step_reduction
|
||||
if len(command.args) == 1:
|
||||
if command.args[0].lower() in ("y", "yes"):
|
||||
target = True
|
||||
elif command.args[0].lower() in ("n", "no"):
|
||||
target = False
|
||||
else:
|
||||
printf(
|
||||
HTML(
|
||||
f"<err>Usage: <code>:step [yes|no]</code></err>"
|
||||
),
|
||||
style = lamb_engine.utils.style
|
||||
)
|
||||
return
|
||||
|
||||
|
||||
if target:
|
||||
printf(
|
||||
HTML(
|
||||
f"<warn>Enabled step-by-step reduction.</warn>"
|
||||
),
|
||||
style = lamb_engine.utils.style
|
||||
)
|
||||
runner.step_reduction = True
|
||||
else:
|
||||
printf(
|
||||
HTML(
|
||||
f"<warn>Disabled step-by-step reduction.</warn>"
|
||||
),
|
||||
style = lamb_engine.utils.style
|
||||
)
|
||||
runner.step_reduction = False
|
||||
|
||||
@lamb_command(
|
||||
command_name = "expand",
|
||||
help_text = "Toggle full expansion"
|
||||
)
|
||||
def cmd_expand(command, runner) -> None:
|
||||
if len(command.args) > 1:
|
||||
printf(
|
||||
HTML(
|
||||
f"<err>Command <code>:{command.name}</code> takes no more than one argument.</err>"
|
||||
),
|
||||
style = lamb_engine.utils.style
|
||||
)
|
||||
return
|
||||
|
||||
target = not runner.full_expansion
|
||||
if len(command.args) == 1:
|
||||
if command.args[0].lower() in ("y", "yes"):
|
||||
target = True
|
||||
elif command.args[0].lower() in ("n", "no"):
|
||||
target = False
|
||||
else:
|
||||
printf(
|
||||
HTML(
|
||||
f"<err>Usage: <code>:expand [yes|no]</code></err>"
|
||||
),
|
||||
style = lamb_engine.utils.style
|
||||
)
|
||||
return
|
||||
|
||||
|
||||
if target:
|
||||
printf(
|
||||
HTML(
|
||||
f"<warn>Enabled complete expansion.</warn>"
|
||||
),
|
||||
style = lamb_engine.utils.style
|
||||
)
|
||||
runner.full_expansion = True
|
||||
else:
|
||||
printf(
|
||||
HTML(
|
||||
f"<warn>Disabled complete expansion.</warn>"
|
||||
),
|
||||
style = lamb_engine.utils.style
|
||||
)
|
||||
runner.full_expansion = False
|
||||
|
||||
|
||||
@lamb_command(
|
||||
command_name = "save",
|
||||
help_text = "Save macros to a file"
|
||||
)
|
||||
def cmd_save(command, runner) -> None:
|
||||
if len(command.args) != 1:
|
||||
printf(
|
||||
HTML(
|
||||
f"<err>Command <code>:{command.name}</code> takes exactly one argument.</err>"
|
||||
),
|
||||
style = lamb_engine.utils.style
|
||||
)
|
||||
return
|
||||
|
||||
target = command.args[0]
|
||||
if os.path.exists(target):
|
||||
confirm = prompt(
|
||||
message = FormattedText([
|
||||
("class:warn", "File exists. Overwrite? "),
|
||||
("class:text", "[yes/no]: ")
|
||||
]),
|
||||
style = lamb_engine.utils.style
|
||||
).lower()
|
||||
|
||||
if confirm != "yes":
|
||||
printf(
|
||||
HTML(
|
||||
"<err>Cancelled.</err>"
|
||||
),
|
||||
style = lamb_engine.utils.style
|
||||
)
|
||||
return
|
||||
|
||||
with open(target, "w") as f:
|
||||
f.write("\n".join(
|
||||
[f"{n} = {e.export()}" for n, e in runner.macro_table.items()]
|
||||
))
|
||||
|
||||
printf(
|
||||
HTML(
|
||||
f"Wrote {len(runner.macro_table)} macros to <code>{target}</code>"
|
||||
),
|
||||
style = lamb_engine.utils.style
|
||||
)
|
||||
|
||||
|
||||
@lamb_command(
|
||||
command_name = "load",
|
||||
help_text = "Load macros from a file"
|
||||
)
|
||||
def cmd_load(command, runner):
|
||||
if len(command.args) != 1:
|
||||
printf(
|
||||
HTML(
|
||||
f"<err>Command <code>:{command.name}</code> takes exactly one argument.</err>"
|
||||
),
|
||||
style = lamb_engine.utils.style
|
||||
)
|
||||
return
|
||||
|
||||
target = command.args[0]
|
||||
if not os.path.exists(target):
|
||||
printf(
|
||||
HTML(
|
||||
f"<err>File {target} doesn't exist.</err>"
|
||||
),
|
||||
style = lamb_engine.utils.style
|
||||
)
|
||||
return
|
||||
|
||||
with open(target, "r") as f:
|
||||
lines = [x.strip() for x in f.readlines()]
|
||||
|
||||
for i in range(len(lines)):
|
||||
l = lines[i].strip()
|
||||
|
||||
# Skip comments and empty lines
|
||||
if l.startswith("#"):
|
||||
continue
|
||||
if l == "":
|
||||
continue
|
||||
|
||||
try:
|
||||
x = runner.parse(l)[0]
|
||||
except ppx.ParseException as e:
|
||||
printf(
|
||||
FormattedText([
|
||||
("class:warn", f"Syntax error on line {i+1:02}: "),
|
||||
("class:code", l[:e.loc]),
|
||||
("class:err", l[e.loc]),
|
||||
("class:code", l[e.loc+1:])
|
||||
]),
|
||||
style = lamb_engine.utils.style
|
||||
)
|
||||
return
|
||||
|
||||
if not isinstance(x, lamb_engine.runner.runner.MacroDef):
|
||||
printf(
|
||||
FormattedText([
|
||||
("class:warn", f"Skipping line {i+1:02}: "),
|
||||
("class:code", l),
|
||||
("class:warn", f" is not a macro definition.")
|
||||
]),
|
||||
style = lamb_engine.utils.style
|
||||
)
|
||||
return
|
||||
|
||||
runner.save_macro(x, silent = True)
|
||||
|
||||
printf(
|
||||
FormattedText([
|
||||
("class:ok", f"Loaded {x.label}: ")
|
||||
] + lamb_engine.utils.lex_str(str(x.expr))),
|
||||
style = lamb_engine.utils.style
|
||||
)
|
||||
|
||||
|
||||
@lamb_command(
|
||||
help_text = "Delete a macro"
|
||||
)
|
||||
def mdel(command, runner) -> None:
|
||||
if len(command.args) != 1:
|
||||
printf(
|
||||
HTML(
|
||||
f"<err>Command <code>:{command.name}</code> takes exactly one argument.</err>"
|
||||
),
|
||||
style = lamb_engine.utils.style
|
||||
)
|
||||
return
|
||||
|
||||
target = command.args[0]
|
||||
if target not in runner.macro_table:
|
||||
printf(
|
||||
HTML(
|
||||
f"<warn>Macro \"{target}\" is not defined</warn>"
|
||||
),
|
||||
style = lamb_engine.utils.style
|
||||
)
|
||||
return
|
||||
|
||||
del runner.macro_table[target]
|
||||
|
||||
@lamb_command(
|
||||
help_text = "Delete all macros"
|
||||
)
|
||||
def delmac(command, runner) -> None:
|
||||
confirm = prompt(
|
||||
message = FormattedText([
|
||||
("class:warn", "Are you sure? "),
|
||||
("class:text", "[yes/no]: ")
|
||||
]),
|
||||
style = lamb_engine.utils.style
|
||||
).lower()
|
||||
|
||||
if confirm != "yes":
|
||||
printf(
|
||||
HTML(
|
||||
"<err>Cancelled.</err>"
|
||||
),
|
||||
style = lamb_engine.utils.style
|
||||
)
|
||||
return
|
||||
|
||||
runner.macro_table = {}
|
||||
|
||||
|
||||
@lamb_command(
|
||||
help_text = "Show macros"
|
||||
)
|
||||
def macros(command, runner) -> None:
|
||||
if len(runner.macro_table) == 0:
|
||||
printf(FormattedText([
|
||||
("class:warn", "No macros are defined."),
|
||||
]),
|
||||
style = lamb_engine.utils.style
|
||||
)
|
||||
else:
|
||||
printf(FormattedText([
|
||||
("class:cmd_h", "\nDefined Macros:\n"),
|
||||
] +
|
||||
[
|
||||
("class:text", f"\t{name} \t {exp}\n")
|
||||
for name, exp in runner.macro_table.items()
|
||||
]),
|
||||
style = lamb_engine.utils.style
|
||||
)
|
||||
|
||||
@lamb_command(
|
||||
help_text = "Clear the screen"
|
||||
)
|
||||
def clear(command, runner) -> None:
|
||||
clear_screen()
|
||||
lamb_engine.utils.show_greeting()
|
||||
|
||||
@lamb_command(
|
||||
help_text = "Get or set reduction limit"
|
||||
)
|
||||
def rlimit(command, runner) -> None:
|
||||
if len(command.args) == 0:
|
||||
if runner.reduction_limit is None:
|
||||
printf(
|
||||
HTML(
|
||||
"<ok>No reduction limit is set</ok>"
|
||||
),
|
||||
style = lamb_engine.utils.style
|
||||
)
|
||||
else:
|
||||
printf(
|
||||
HTML(
|
||||
f"<ok>Reduction limit is {runner.reduction_limit:,}</ok>"
|
||||
),
|
||||
style = lamb_engine.utils.style
|
||||
)
|
||||
return
|
||||
|
||||
elif len(command.args) != 1:
|
||||
printf(
|
||||
HTML(
|
||||
f"<err>Command <code>:{command.name}</code> takes exactly one argument.</err>"
|
||||
),
|
||||
style = lamb_engine.utils.style
|
||||
)
|
||||
return
|
||||
|
||||
t = command.args[0]
|
||||
if t.lower() == "none":
|
||||
runner.reduction_limit = None
|
||||
printf(
|
||||
HTML(
|
||||
f"<ok>Removed reduction limit</ok>"
|
||||
),
|
||||
style = lamb_engine.utils.style
|
||||
)
|
||||
return
|
||||
|
||||
try:
|
||||
t = int(t)
|
||||
except ValueError:
|
||||
printf(
|
||||
HTML(
|
||||
"<err>Reduction limit must be a positive integer or \"none\".</err>"
|
||||
),
|
||||
style = lamb_engine.utils.style
|
||||
)
|
||||
return
|
||||
|
||||
if 50 > t:
|
||||
printf(
|
||||
HTML(
|
||||
"<err>Reduction limit must be at least 50.</err>"
|
||||
),
|
||||
style = lamb_engine.utils.style
|
||||
)
|
||||
return
|
||||
|
||||
runner.reduction_limit = t
|
||||
printf(
|
||||
HTML(
|
||||
f"<ok>Set reduction limit to {t:,}</ok>"
|
||||
),
|
||||
style = lamb_engine.utils.style
|
||||
)
|
||||
|
||||
|
||||
|
||||
@lamb_command(
|
||||
help_text = "Print this help"
|
||||
)
|
||||
def help(command, runner) -> None:
|
||||
printf(
|
||||
HTML(
|
||||
"\n<text>" +
|
||||
|
||||
"<cmd_h>Usage:</cmd_h>" +
|
||||
"\n" +
|
||||
"\tWrite lambda expressions using your <cmd_key>\\</cmd_key> key." +
|
||||
"\n" +
|
||||
"\tMacros can be defined using <cmd_key>=</cmd_key>, as in <code>T = λab.a</code>" +
|
||||
"\n" +
|
||||
"\tRun commands using <cmd_key>:</cmd_key>, for example <code>:help</code>" +
|
||||
"\n" +
|
||||
"\tHistory can be accessed with <cmd_key>$</cmd_key>, which will expand to the result of the last successful reduction." +
|
||||
"\n\n" +
|
||||
"<cmd_h>Commands:</cmd_h>"+
|
||||
"\n" +
|
||||
"\n".join([
|
||||
f"\t<code>{name}</code> \t {text}"
|
||||
for name, text in help_texts.items()
|
||||
]) +
|
||||
"\n\n"
|
||||
"<muted>Detailed documentation can be found on this project's git page.</muted>" +
|
||||
"</text>"
|
||||
),
|
||||
style = lamb_engine.utils.style
|
||||
)
|
42
lamb_engine/runner/misc.py
Normal file
42
lamb_engine/runner/misc.py
Normal file
@ -0,0 +1,42 @@
|
||||
import enum
|
||||
import lamb_engine
|
||||
|
||||
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")
|
||||
SHOW_MACRO = ("class:text", "Displaying macro content")
|
||||
|
||||
class MacroDef:
|
||||
@staticmethod
|
||||
def from_parse(result):
|
||||
return MacroDef(
|
||||
result[0].name,
|
||||
result[1]
|
||||
)
|
||||
|
||||
def __init__(self, label: str, expr: lamb_engine.nodes.Node):
|
||||
self.label = label
|
||||
self.expr = expr
|
||||
|
||||
def __repr__(self):
|
||||
return f"<{self.label} := {self.expr!r}>"
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.label} := {self.expr}"
|
||||
|
||||
def set_runner(self, runner):
|
||||
return self.expr.set_runner(runner)
|
||||
|
||||
class Command:
|
||||
@staticmethod
|
||||
def from_parse(result):
|
||||
return Command(
|
||||
result[0],
|
||||
result[1:]
|
||||
)
|
||||
|
||||
def __init__(self, name, args):
|
||||
self.name = name
|
||||
self.args = args
|
294
lamb_engine/runner/runner.py
Normal file
294
lamb_engine/runner/runner.py
Normal file
@ -0,0 +1,294 @@
|
||||
from prompt_toolkit import PromptSession
|
||||
from prompt_toolkit.formatted_text import FormattedText
|
||||
from prompt_toolkit.key_binding import KeyBindings
|
||||
from prompt_toolkit import prompt
|
||||
from prompt_toolkit import print_formatted_text as printf
|
||||
import collections
|
||||
import math
|
||||
import time
|
||||
|
||||
import lamb_engine
|
||||
|
||||
from lamb_engine.runner.misc import MacroDef
|
||||
from lamb_engine.runner.misc import Command
|
||||
from lamb_engine.runner.misc import StopReason
|
||||
from lamb_engine.runner import commands as cmd
|
||||
|
||||
|
||||
# Keybindings for step prompt.
|
||||
# Prevents any text from being input.
|
||||
step_bindings = KeyBindings()
|
||||
@step_bindings.add("<any>")
|
||||
def _(event):
|
||||
pass
|
||||
|
||||
|
||||
class Runner:
|
||||
def __init__(
|
||||
self,
|
||||
prompt_session: PromptSession,
|
||||
prompt_message
|
||||
):
|
||||
self.macro_table = {}
|
||||
self.prompt_session = prompt_session
|
||||
self.prompt_message = prompt_message
|
||||
self.parser = lamb_engine.parser.LambdaParser(
|
||||
action_func = lamb_engine.nodes.Func.from_parse,
|
||||
action_bound = lamb_engine.nodes.Macro.from_parse,
|
||||
action_macro = lamb_engine.nodes.Macro.from_parse,
|
||||
action_call = lamb_engine.nodes.Call.from_parse,
|
||||
action_church = lamb_engine.nodes.Church.from_parse,
|
||||
action_macro_def = MacroDef.from_parse,
|
||||
action_command = Command.from_parse,
|
||||
action_history = lamb_engine.nodes.History.from_parse
|
||||
)
|
||||
|
||||
# Maximum amount of reductions.
|
||||
# If None, no maximum is enforced.
|
||||
# Must be at least 1.
|
||||
self.reduction_limit: int | None = 1_000_000
|
||||
|
||||
# Ensure bound variables are unique.
|
||||
# This is automatically incremented whenever we make
|
||||
# a bound variable.
|
||||
self.bound_variable_counter = 0
|
||||
|
||||
# Update iteration after this many iterations
|
||||
# Make sure every place value has a non-zero digit
|
||||
# so that all digits appear to be changing.
|
||||
self.iter_update = 231
|
||||
|
||||
self.history = collections.deque(
|
||||
[None] * 10,
|
||||
10)
|
||||
|
||||
|
||||
# If true, reduce step-by-step.
|
||||
self.step_reduction = False
|
||||
|
||||
# If true, expand ALL macros when printing output
|
||||
self.full_expansion = False
|
||||
|
||||
def prompt(self):
|
||||
return self.prompt_session.prompt(
|
||||
message = self.prompt_message
|
||||
)
|
||||
|
||||
def parse(self, line) -> tuple[lamb_engine.nodes.Root | MacroDef | Command, list]:
|
||||
e = self.parser.parse_line(line)
|
||||
|
||||
w = []
|
||||
if isinstance(e, MacroDef):
|
||||
e.expr = lamb_engine.nodes.Root(e.expr)
|
||||
e.set_runner(self)
|
||||
w = lamb_engine.nodes.prepare(e.expr, ban_macro_name = e.label)
|
||||
elif isinstance(e, lamb_engine.nodes.Node):
|
||||
e = lamb_engine.nodes.Root(e)
|
||||
e.set_runner(self)
|
||||
w = lamb_engine.nodes.prepare(e)
|
||||
|
||||
return e, w
|
||||
|
||||
|
||||
def reduce(self, node: lamb_engine.nodes.Root, *, warnings = []) -> None:
|
||||
|
||||
# Reduction Counter.
|
||||
# We also count macro (and church) expansions,
|
||||
# and subtract those from the final count.
|
||||
k = 0
|
||||
macro_expansions = 0
|
||||
|
||||
stop_reason = StopReason.MAX_EXCEEDED
|
||||
start_time = time.time()
|
||||
out_text = []
|
||||
|
||||
only_macro = (
|
||||
isinstance(node.left, lamb_engine.nodes.Macro) or
|
||||
isinstance(node.left, lamb_engine.nodes.Church)
|
||||
)
|
||||
if only_macro:
|
||||
stop_reason = StopReason.SHOW_MACRO
|
||||
m, node = lamb_engine.nodes.expand(node, force_all = only_macro)
|
||||
macro_expansions += m
|
||||
|
||||
if len(warnings) != 0:
|
||||
printf(FormattedText(warnings), style = lamb_engine.utils.style)
|
||||
|
||||
if self.step_reduction:
|
||||
printf(FormattedText([
|
||||
("class:warn", "Step-by-step reduction is enabled.\n"),
|
||||
("class:muted", "Press "),
|
||||
("class:cmd_key", "ctrl-c"),
|
||||
("class:muted", " to continue automatically.\n"),
|
||||
("class:muted", "Press "),
|
||||
("class:cmd_key", "enter"),
|
||||
("class:muted", " to step.\n"),
|
||||
]), style = lamb_engine.utils.style)
|
||||
|
||||
|
||||
skip_to_end = False
|
||||
while (
|
||||
(
|
||||
(self.reduction_limit is None) or
|
||||
(k < self.reduction_limit)
|
||||
) and not only_macro
|
||||
):
|
||||
|
||||
# Show reduction count
|
||||
if (
|
||||
( (k >= self.iter_update) and (k % self.iter_update == 0) )
|
||||
and not (self.step_reduction and not skip_to_end)
|
||||
):
|
||||
print(f" Reducing... {k:,}", end = "\r")
|
||||
|
||||
try:
|
||||
red_type, node = lamb_engine.nodes.reduce(node)
|
||||
except KeyboardInterrupt:
|
||||
stop_reason = StopReason.INTERRUPT
|
||||
break
|
||||
|
||||
# If we can't reduce this expression anymore,
|
||||
# it's in beta-normal form.
|
||||
if red_type == lamb_engine.nodes.ReductionType.NOTHING:
|
||||
stop_reason = StopReason.BETA_NORMAL
|
||||
break
|
||||
|
||||
# Count reductions
|
||||
k += 1
|
||||
if red_type == lamb_engine.nodes.ReductionType.FUNCTION_APPLY:
|
||||
macro_expansions += 1
|
||||
|
||||
# Pause after step if necessary
|
||||
if self.step_reduction and not skip_to_end:
|
||||
try:
|
||||
s = prompt(
|
||||
message = FormattedText([
|
||||
("class:prompt", lamb_engine.nodes.reduction_text[red_type]),
|
||||
("class:prompt", f":{k:03} ")
|
||||
] + lamb_engine.utils.lex_str(str(node))),
|
||||
style = lamb_engine.utils.style,
|
||||
key_bindings = step_bindings
|
||||
)
|
||||
except KeyboardInterrupt or EOFError:
|
||||
skip_to_end = True
|
||||
printf(FormattedText([
|
||||
("class:warn", "Skipping to end."),
|
||||
]), style = lamb_engine.utils.style)
|
||||
|
||||
# Print a space between step messages
|
||||
if self.step_reduction:
|
||||
print("")
|
||||
|
||||
# Clear reduction counter if it was printed
|
||||
if k >= self.iter_update:
|
||||
print(" " * round(14 + math.log10(k)), end = "\r")
|
||||
|
||||
# Expand fully if necessary
|
||||
if self.full_expansion:
|
||||
o, node = lamb_engine.nodes.expand(node, force_all = True)
|
||||
macro_expansions += o
|
||||
|
||||
if only_macro:
|
||||
out_text += [
|
||||
("class:ok", f"Displaying macro content")
|
||||
]
|
||||
|
||||
else:
|
||||
out_text += [
|
||||
("class:ok", f"Runtime: "),
|
||||
("class:text", f"{time.time() - start_time:.03f} seconds"),
|
||||
|
||||
("class:ok", f"\nExit reason: "),
|
||||
stop_reason.value,
|
||||
|
||||
("class:ok", f"\nMacro expansions: "),
|
||||
("class:text", f"{macro_expansions:,}"),
|
||||
|
||||
("class:ok", f"\nReductions: "),
|
||||
("class:text", f"{k:,}\t"),
|
||||
("class:muted", f"(Limit: {self.reduction_limit:,})")
|
||||
]
|
||||
|
||||
if self.full_expansion:
|
||||
out_text += [
|
||||
("class:ok", "\nAll macros have been expanded")
|
||||
]
|
||||
|
||||
if (
|
||||
stop_reason == StopReason.BETA_NORMAL or
|
||||
stop_reason == StopReason.LOOP_DETECTED or
|
||||
only_macro
|
||||
):
|
||||
out_text += [
|
||||
("class:ok", "\n\n => ")
|
||||
] + lamb_engine.utils.lex_str(str(node))
|
||||
|
||||
|
||||
printf(
|
||||
FormattedText(out_text),
|
||||
style = lamb_engine.utils.style
|
||||
)
|
||||
|
||||
# Save to history
|
||||
# Do this at the end so we don't always fully expand.
|
||||
self.history.appendleft(
|
||||
lamb_engine.nodes.expand( # type: ignore
|
||||
node,
|
||||
force_all = True
|
||||
)[1]
|
||||
)
|
||||
|
||||
def save_macro(
|
||||
self,
|
||||
macro: MacroDef,
|
||||
*,
|
||||
silent = False
|
||||
) -> None:
|
||||
was_rewritten = macro.label in self.macro_table
|
||||
self.macro_table[macro.label] = macro.expr
|
||||
|
||||
if not silent:
|
||||
printf(FormattedText([
|
||||
("class:text", "Set "),
|
||||
("class:code", macro.label),
|
||||
("class:text", " to "),
|
||||
("class:code", str(macro.expr))
|
||||
]), style = lamb_engine.utils.style)
|
||||
|
||||
# Apply a list of definitions
|
||||
def run(
|
||||
self,
|
||||
line: str,
|
||||
*,
|
||||
silent = False
|
||||
) -> None:
|
||||
e, w = self.parse(line)
|
||||
|
||||
# If this line is a macro definition, save the macro.
|
||||
if isinstance(e, MacroDef):
|
||||
self.save_macro(e, silent = silent)
|
||||
|
||||
# If this line is a command, do the command.
|
||||
elif isinstance(e, Command):
|
||||
if e.name not in cmd.commands:
|
||||
printf(
|
||||
FormattedText([
|
||||
("class:warn", f"Unknown command \"{e.name}\"")
|
||||
]),
|
||||
style = lamb_engine.utils.style
|
||||
)
|
||||
else:
|
||||
cmd.commands[e.name](e, self)
|
||||
|
||||
# If this line is a plain expression, reduce it.
|
||||
elif isinstance(e, lamb_engine.nodes.Node):
|
||||
self.reduce(e, warnings = w)
|
||||
|
||||
# We shouldn't ever get here.
|
||||
else:
|
||||
raise TypeError(f"I don't know what to do with a {type(e)}")
|
||||
|
||||
|
||||
def run_lines(self, lines: list[str]):
|
||||
for l in lines:
|
||||
self.run(l, silent = True)
|
Reference in New Issue
Block a user