Compare commits
No commits in common. "78cc118bfc3a0950d85064a9af30f8ea538d385e" and "bd13b10f761d6103fae3d1988cd15efdcb227b17" have entirely different histories.
78cc118bfc
...
bd13b10f76
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"cSpell.words": [
|
"cSpell.words": [
|
||||||
"autochurch",
|
"autochurch",
|
||||||
"delmac",
|
"clearmacros",
|
||||||
"Endnodes",
|
"Endnodes",
|
||||||
"freevar",
|
"freevar",
|
||||||
"mdel",
|
"mdel",
|
||||||
|
12
README.md
12
README.md
@ -66,11 +66,7 @@ Lamb understands many commands. Prefix them with a `:` in the prompt.
|
|||||||
|
|
||||||
`:mdel [macro]` Delete a macro
|
`:mdel [macro]` Delete a macro
|
||||||
|
|
||||||
`:step [yes | no]` Enable or disable step-by-step reduction. Toggle if no argument is given.
|
`:clearmacros` Delete all macros
|
||||||
|
|
||||||
`:expand [yes | no]` Enable or disable full expansion. Toggle if no argument is given. If full expansion is enabled, ALL macros will be expanded when printing output.
|
|
||||||
|
|
||||||
`:delmac` Delete all macros
|
|
||||||
|
|
||||||
`:save [filename]` \
|
`:save [filename]` \
|
||||||
`:load [filename]` \
|
`:load [filename]` \
|
||||||
@ -81,8 +77,9 @@ The lines in a file look exactly the same as regular entries in the prompt, but
|
|||||||
|
|
||||||
|
|
||||||
## Todo (pre-release, in this order):
|
## Todo (pre-release, in this order):
|
||||||
- History queue
|
|
||||||
- Prevent macro-chaining recursion
|
- Prevent macro-chaining recursion
|
||||||
|
- Full-reduce option (expand all macros)
|
||||||
|
- step-by-step reduction
|
||||||
- Update screenshot
|
- Update screenshot
|
||||||
- Update documentation
|
- Update documentation
|
||||||
- Write "how it works"
|
- Write "how it works"
|
||||||
@ -93,9 +90,10 @@ The lines in a file look exactly the same as regular entries in the prompt, but
|
|||||||
- Cleanup warnings
|
- Cleanup warnings
|
||||||
- Syntax highlight printouts
|
- Syntax highlight printouts
|
||||||
- Truncate long expressions in warnings
|
- Truncate long expressions in warnings
|
||||||
- History indexing
|
- History queue + indexing
|
||||||
- Show history command
|
- Show history command
|
||||||
- Loop detection
|
- Loop detection
|
||||||
|
- Optimization: reduction can be slow with large trees.
|
||||||
- $\alpha$-equivalence check
|
- $\alpha$-equivalence check
|
||||||
- Command-line options (load a file)
|
- Command-line options (load a file)
|
||||||
- Unchurch command: make church numerals human-readable
|
- Unchurch command: make church numerals human-readable
|
||||||
|
@ -58,24 +58,15 @@ def print_node(node: lbn.Node, *, export: bool = False) -> str:
|
|||||||
|
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
def clone(node: lbn.Node):
|
def clone(node: lbn.Node):
|
||||||
if not isinstance(node, lbn.Node):
|
if not isinstance(node, lbn.Node):
|
||||||
raise TypeError(f"I don't know what to do with a {type(node)}")
|
raise TypeError(f"I don't know what to do with a {type(node)}")
|
||||||
|
|
||||||
macro_map = {}
|
out = node.copy()
|
||||||
if isinstance(node, lbn.Func):
|
|
||||||
c = node.copy()
|
|
||||||
macro_map[node.input.identifier] = c.input.identifier # type: ignore
|
|
||||||
else:
|
|
||||||
c = node.copy()
|
|
||||||
|
|
||||||
out = c
|
|
||||||
out_ptr = out # Stays one step behind ptr, in the new tree.
|
out_ptr = out # Stays one step behind ptr, in the new tree.
|
||||||
ptr = node
|
ptr = node
|
||||||
from_side = lbn.Direction.UP
|
from_side = lbn.Direction.UP
|
||||||
|
|
||||||
|
|
||||||
if isinstance(node, lbn.EndNode):
|
if isinstance(node, lbn.EndNode):
|
||||||
return out
|
return out
|
||||||
|
|
||||||
@ -88,18 +79,7 @@ def clone(node: lbn.Node):
|
|||||||
elif isinstance(ptr, lbn.Func) or isinstance(ptr, lbn.Root):
|
elif isinstance(ptr, lbn.Func) or isinstance(ptr, lbn.Root):
|
||||||
if from_side == lbn.Direction.UP:
|
if from_side == lbn.Direction.UP:
|
||||||
from_side, ptr = ptr.go_left()
|
from_side, ptr = ptr.go_left()
|
||||||
|
out_ptr.set_side(ptr.parent_side, ptr.copy())
|
||||||
if isinstance(ptr, lbn.Func):
|
|
||||||
c = ptr.copy()
|
|
||||||
macro_map[ptr.input.identifier] = c.input.identifier # type: ignore
|
|
||||||
elif isinstance(ptr, lbn.Bound):
|
|
||||||
c = ptr.copy()
|
|
||||||
if c.identifier in macro_map:
|
|
||||||
c.identifier = macro_map[c.identifier]
|
|
||||||
else:
|
|
||||||
c = ptr.copy()
|
|
||||||
out_ptr.set_side(ptr.parent_side, c)
|
|
||||||
|
|
||||||
_, out_ptr = out_ptr.go_left()
|
_, out_ptr = out_ptr.go_left()
|
||||||
elif from_side == lbn.Direction.LEFT:
|
elif from_side == lbn.Direction.LEFT:
|
||||||
from_side, ptr = ptr.go_up()
|
from_side, ptr = ptr.go_up()
|
||||||
@ -107,33 +87,11 @@ def clone(node: lbn.Node):
|
|||||||
elif isinstance(ptr, lbn.Call):
|
elif isinstance(ptr, lbn.Call):
|
||||||
if from_side == lbn.Direction.UP:
|
if from_side == lbn.Direction.UP:
|
||||||
from_side, ptr = ptr.go_left()
|
from_side, ptr = ptr.go_left()
|
||||||
|
out_ptr.set_side(ptr.parent_side, ptr.copy())
|
||||||
if isinstance(ptr, lbn.Func):
|
|
||||||
c = ptr.copy()
|
|
||||||
macro_map[ptr.input.identifier] = c.input.identifier # type: ignore
|
|
||||||
elif isinstance(ptr, lbn.Bound):
|
|
||||||
c = ptr.copy()
|
|
||||||
if c.identifier in macro_map:
|
|
||||||
c.identifier = macro_map[c.identifier]
|
|
||||||
else:
|
|
||||||
c = ptr.copy()
|
|
||||||
out_ptr.set_side(ptr.parent_side, c)
|
|
||||||
|
|
||||||
_, out_ptr = out_ptr.go_left()
|
_, out_ptr = out_ptr.go_left()
|
||||||
elif from_side == lbn.Direction.LEFT:
|
elif from_side == lbn.Direction.LEFT:
|
||||||
from_side, ptr = ptr.go_right()
|
from_side, ptr = ptr.go_right()
|
||||||
|
out_ptr.set_side(ptr.parent_side, ptr.copy())
|
||||||
if isinstance(ptr, lbn.Func):
|
|
||||||
c = ptr.copy()
|
|
||||||
macro_map[ptr.input.identifier] = c.input.identifier # type: ignore
|
|
||||||
elif isinstance(ptr, lbn.Bound):
|
|
||||||
c = ptr.copy()
|
|
||||||
if c.identifier in macro_map:
|
|
||||||
c.identifier = macro_map[c.identifier]
|
|
||||||
else:
|
|
||||||
c = ptr.copy()
|
|
||||||
out_ptr.set_side(ptr.parent_side, c)
|
|
||||||
|
|
||||||
_, out_ptr = out_ptr.go_right()
|
_, out_ptr = out_ptr.go_right()
|
||||||
elif from_side == lbn.Direction.RIGHT:
|
elif from_side == lbn.Direction.RIGHT:
|
||||||
from_side, ptr = ptr.go_up()
|
from_side, ptr = ptr.go_up()
|
||||||
|
@ -5,7 +5,6 @@ class Direction(enum.Enum):
|
|||||||
LEFT = enum.auto()
|
LEFT = enum.auto()
|
||||||
RIGHT = enum.auto()
|
RIGHT = enum.auto()
|
||||||
|
|
||||||
|
|
||||||
class ReductionType(enum.Enum):
|
class ReductionType(enum.Enum):
|
||||||
# Nothing happened. This implies that
|
# Nothing happened. This implies that
|
||||||
# an expression cannot be reduced further.
|
# an expression cannot be reduced further.
|
||||||
@ -24,16 +23,6 @@ class ReductionType(enum.Enum):
|
|||||||
# This is the only type of "formal" reduction step.
|
# This is the only type of "formal" reduction step.
|
||||||
FUNCTION_APPLY = enum.auto()
|
FUNCTION_APPLY = enum.auto()
|
||||||
|
|
||||||
# Pretty, short names for each reduction type.
|
|
||||||
# These should all have the same length.
|
|
||||||
reduction_text = {
|
|
||||||
ReductionType.NOTHING: "N",
|
|
||||||
ReductionType.MACRO_EXPAND: "M",
|
|
||||||
ReductionType.HIST_EXPAND: "H",
|
|
||||||
ReductionType.AUTOCHURCH: "C",
|
|
||||||
ReductionType.FUNCTION_APPLY: "F",
|
|
||||||
}
|
|
||||||
|
|
||||||
class ReductionError(Exception):
|
class ReductionError(Exception):
|
||||||
"""
|
"""
|
||||||
Raised when we encounter an error while reducing.
|
Raised when we encounter an error while reducing.
|
||||||
|
@ -350,11 +350,7 @@ class Bound(EndNode):
|
|||||||
self.identifier = forced_id
|
self.identifier = forced_id
|
||||||
|
|
||||||
def copy(self):
|
def copy(self):
|
||||||
return Bound(
|
return Bound(self.name, forced_id = self.identifier, runner = self.runner)
|
||||||
self.name,
|
|
||||||
forced_id = self.identifier,
|
|
||||||
runner = self.runner
|
|
||||||
)
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
if not isinstance(other, Bound):
|
if not isinstance(other, Bound):
|
||||||
@ -392,14 +388,7 @@ class Func(Node):
|
|||||||
return f"<func {self.input!r} {self.left!r}>"
|
return f"<func {self.input!r} {self.left!r}>"
|
||||||
|
|
||||||
def copy(self):
|
def copy(self):
|
||||||
return Func(
|
return Func(self.input, None, runner = self.runner) # type: ignore
|
||||||
Bound(
|
|
||||||
self.input.name,
|
|
||||||
runner = self.runner
|
|
||||||
),
|
|
||||||
None, # type: ignore
|
|
||||||
runner = self.runner
|
|
||||||
)
|
|
||||||
|
|
||||||
class Root(Node):
|
class Root(Node):
|
||||||
"""
|
"""
|
||||||
|
@ -2,7 +2,6 @@ from prompt_toolkit.formatted_text import FormattedText
|
|||||||
from prompt_toolkit.formatted_text import HTML
|
from prompt_toolkit.formatted_text import HTML
|
||||||
from prompt_toolkit import print_formatted_text as printf
|
from prompt_toolkit import print_formatted_text as printf
|
||||||
from prompt_toolkit.shortcuts import clear as clear_screen
|
from prompt_toolkit.shortcuts import clear as clear_screen
|
||||||
from prompt_toolkit import prompt
|
|
||||||
|
|
||||||
import os.path
|
import os.path
|
||||||
from pyparsing import exceptions as ppx
|
from pyparsing import exceptions as ppx
|
||||||
@ -28,100 +27,6 @@ def lamb_command(
|
|||||||
help_texts[name] = help_text
|
help_texts[name] = help_text
|
||||||
return inner
|
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.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.utils.style
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
if target:
|
|
||||||
printf(
|
|
||||||
HTML(
|
|
||||||
f"<warn>Enabled step-by-step reduction.</warn>"
|
|
||||||
),
|
|
||||||
style = lamb.utils.style
|
|
||||||
)
|
|
||||||
runner.step_reduction = True
|
|
||||||
else:
|
|
||||||
printf(
|
|
||||||
HTML(
|
|
||||||
f"<warn>Disabled step-by-step reduction.</warn>"
|
|
||||||
),
|
|
||||||
style = lamb.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.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.utils.style
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
if target:
|
|
||||||
printf(
|
|
||||||
HTML(
|
|
||||||
f"<warn>Enabled complete expansion.</warn>"
|
|
||||||
),
|
|
||||||
style = lamb.utils.style
|
|
||||||
)
|
|
||||||
runner.full_expansion = True
|
|
||||||
else:
|
|
||||||
printf(
|
|
||||||
HTML(
|
|
||||||
f"<warn>Disabled complete expansion.</warn>"
|
|
||||||
),
|
|
||||||
style = lamb.utils.style
|
|
||||||
)
|
|
||||||
runner.full_expansion = False
|
|
||||||
|
|
||||||
|
|
||||||
@lamb_command(
|
@lamb_command(
|
||||||
command_name = "save",
|
command_name = "save",
|
||||||
@ -139,12 +44,11 @@ def cmd_save(command, runner) -> None:
|
|||||||
|
|
||||||
target = command.args[0]
|
target = command.args[0]
|
||||||
if os.path.exists(target):
|
if os.path.exists(target):
|
||||||
confirm = prompt(
|
confirm = runner.prompt_session.prompt(
|
||||||
message = FormattedText([
|
message = FormattedText([
|
||||||
("class:warn", "File exists. Overwrite? "),
|
("class:warn", "File exists. Overwrite? "),
|
||||||
("class:text", "[yes/no]: ")
|
("class:text", "[yes/no]: ")
|
||||||
]),
|
])
|
||||||
style = lamb.utils.style
|
|
||||||
).lower()
|
).lower()
|
||||||
|
|
||||||
if confirm != "yes":
|
if confirm != "yes":
|
||||||
@ -269,13 +173,12 @@ def mdel(command, runner) -> None:
|
|||||||
@lamb_command(
|
@lamb_command(
|
||||||
help_text = "Delete all macros"
|
help_text = "Delete all macros"
|
||||||
)
|
)
|
||||||
def delmac(command, runner) -> None:
|
def clearmacros(command, runner) -> None:
|
||||||
confirm = prompt(
|
confirm = runner.prompt_session.prompt(
|
||||||
message = FormattedText([
|
message = FormattedText([
|
||||||
("class:warn", "Are you sure? "),
|
("class:warn", "Are you sure? "),
|
||||||
("class:text", "[yes/no]: ")
|
("class:text", "[yes/no]: ")
|
||||||
]),
|
])
|
||||||
style = lamb.utils.style
|
|
||||||
).lower()
|
).lower()
|
||||||
|
|
||||||
if confirm != "yes":
|
if confirm != "yes":
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
from prompt_toolkit import PromptSession
|
from prompt_toolkit import PromptSession
|
||||||
from prompt_toolkit.formatted_text import FormattedText
|
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
|
from prompt_toolkit import print_formatted_text as printf
|
||||||
import enum
|
import enum
|
||||||
import math
|
import math
|
||||||
@ -15,13 +13,6 @@ from lamb.runner.misc import StopReason
|
|||||||
from lamb.runner import commands as cmd
|
from lamb.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:
|
class Runner:
|
||||||
def __init__(
|
def __init__(
|
||||||
@ -60,12 +51,6 @@ class Runner:
|
|||||||
|
|
||||||
self.history: list[lamb.nodes.Root] = []
|
self.history: list[lamb.nodes.Root] = []
|
||||||
|
|
||||||
# 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):
|
def prompt(self):
|
||||||
return self.prompt_session.prompt(
|
return self.prompt_session.prompt(
|
||||||
message = self.prompt_message
|
message = self.prompt_message
|
||||||
@ -111,19 +96,7 @@ class Runner:
|
|||||||
if len(warnings) != 0:
|
if len(warnings) != 0:
|
||||||
printf(FormattedText(warnings), style = lamb.utils.style)
|
printf(FormattedText(warnings), style = lamb.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.utils.style)
|
|
||||||
|
|
||||||
|
|
||||||
skip_to_end = False
|
|
||||||
while (
|
while (
|
||||||
(
|
(
|
||||||
(self.reduction_limit is None) or
|
(self.reduction_limit is None) or
|
||||||
@ -132,10 +105,7 @@ class Runner:
|
|||||||
):
|
):
|
||||||
|
|
||||||
# Show reduction count
|
# Show reduction count
|
||||||
if (
|
if (k >= self.iter_update) and (k % self.iter_update == 0):
|
||||||
( (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")
|
print(f" Reducing... {k:,}", end = "\r")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -155,37 +125,10 @@ class Runner:
|
|||||||
if red_type == lamb.nodes.ReductionType.FUNCTION_APPLY:
|
if red_type == lamb.nodes.ReductionType.FUNCTION_APPLY:
|
||||||
macro_expansions += 1
|
macro_expansions += 1
|
||||||
|
|
||||||
# Pause after step if necessary
|
|
||||||
if self.step_reduction and not skip_to_end:
|
|
||||||
try:
|
|
||||||
s = prompt(
|
|
||||||
message = FormattedText([
|
|
||||||
("class:muted", lamb.nodes.reduction_text[red_type]),
|
|
||||||
("class:muted", f":{k:03} "),
|
|
||||||
("class:text", str(node)),
|
|
||||||
]),
|
|
||||||
style = lamb.utils.style,
|
|
||||||
key_bindings = step_bindings
|
|
||||||
)
|
|
||||||
except KeyboardInterrupt or EOFError:
|
|
||||||
skip_to_end = True
|
|
||||||
printf(FormattedText([
|
|
||||||
("class:warn", "Skipping to end."),
|
|
||||||
]), style = lamb.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:
|
if k >= self.iter_update:
|
||||||
|
# Clear reduction counter if it was printed
|
||||||
print(" " * round(14 + math.log10(k)), end = "\r")
|
print(" " * round(14 + math.log10(k)), end = "\r")
|
||||||
|
|
||||||
# Expand fully if necessary
|
|
||||||
if self.full_expansion:
|
|
||||||
o, node = lamb.nodes.expand(node, force_all = True)
|
|
||||||
macro_expansions += o
|
|
||||||
|
|
||||||
if only_macro:
|
if only_macro:
|
||||||
out_text += [
|
out_text += [
|
||||||
("class:ok", f"Displaying macro content")
|
("class:ok", f"Displaying macro content")
|
||||||
@ -207,11 +150,6 @@ class Runner:
|
|||||||
("class:muted", f"(Limit: {self.reduction_limit:,})")
|
("class:muted", f"(Limit: {self.reduction_limit:,})")
|
||||||
]
|
]
|
||||||
|
|
||||||
if self.full_expansion:
|
|
||||||
out_text += [
|
|
||||||
("class:ok", "\nAll macros have been expanded")
|
|
||||||
]
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
stop_reason == StopReason.BETA_NORMAL or
|
stop_reason == StopReason.BETA_NORMAL or
|
||||||
stop_reason == StopReason.LOOP_DETECTED or
|
stop_reason == StopReason.LOOP_DETECTED or
|
||||||
@ -222,16 +160,14 @@ class Runner:
|
|||||||
("class:text", str(node)), # type: ignore
|
("class:text", str(node)), # type: ignore
|
||||||
]
|
]
|
||||||
|
|
||||||
|
self.history.append(lamb.nodes.expand(node, force_all = True)[1])
|
||||||
|
|
||||||
|
|
||||||
printf(
|
printf(
|
||||||
FormattedText(out_text),
|
FormattedText(out_text),
|
||||||
style = lamb.utils.style
|
style = lamb.utils.style
|
||||||
)
|
)
|
||||||
|
|
||||||
# Save to history
|
|
||||||
# Do this at the end so we don't always fully expand.
|
|
||||||
self.history.append(lamb.nodes.expand(node, force_all = True)[1])
|
|
||||||
|
|
||||||
def save_macro(
|
def save_macro(
|
||||||
self,
|
self,
|
||||||
macro: MacroDef,
|
macro: MacroDef,
|
||||||
|
Reference in New Issue
Block a user