Added :step and fixed cloning bug
parent
bd13b10f76
commit
dfbc5f3704
|
@ -79,7 +79,6 @@ The lines in a file look exactly the same as regular entries in the prompt, but
|
|||
## Todo (pre-release, in this order):
|
||||
- Prevent macro-chaining recursion
|
||||
- Full-reduce option (expand all macros)
|
||||
- step-by-step reduction
|
||||
- Update screenshot
|
||||
- Update documentation
|
||||
- Write "how it works"
|
||||
|
|
|
@ -58,15 +58,24 @@ def print_node(node: lbn.Node, *, export: bool = False) -> str:
|
|||
|
||||
return out
|
||||
|
||||
|
||||
def clone(node: lbn.Node):
|
||||
if not isinstance(node, lbn.Node):
|
||||
raise TypeError(f"I don't know what to do with a {type(node)}")
|
||||
|
||||
out = node.copy()
|
||||
macro_map = {}
|
||||
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.
|
||||
ptr = node
|
||||
from_side = lbn.Direction.UP
|
||||
|
||||
|
||||
if isinstance(node, lbn.EndNode):
|
||||
return out
|
||||
|
||||
|
@ -79,7 +88,18 @@ def clone(node: lbn.Node):
|
|||
elif isinstance(ptr, lbn.Func) or isinstance(ptr, lbn.Root):
|
||||
if from_side == lbn.Direction.UP:
|
||||
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()
|
||||
elif from_side == lbn.Direction.LEFT:
|
||||
from_side, ptr = ptr.go_up()
|
||||
|
@ -87,11 +107,33 @@ def clone(node: lbn.Node):
|
|||
elif isinstance(ptr, lbn.Call):
|
||||
if from_side == lbn.Direction.UP:
|
||||
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()
|
||||
elif from_side == lbn.Direction.LEFT:
|
||||
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()
|
||||
elif from_side == lbn.Direction.RIGHT:
|
||||
from_side, ptr = ptr.go_up()
|
||||
|
|
|
@ -5,6 +5,7 @@ class Direction(enum.Enum):
|
|||
LEFT = enum.auto()
|
||||
RIGHT = enum.auto()
|
||||
|
||||
|
||||
class ReductionType(enum.Enum):
|
||||
# Nothing happened. This implies that
|
||||
# an expression cannot be reduced further.
|
||||
|
@ -23,6 +24,16 @@ class ReductionType(enum.Enum):
|
|||
# This is the only type of "formal" reduction step.
|
||||
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):
|
||||
"""
|
||||
Raised when we encounter an error while reducing.
|
||||
|
|
|
@ -350,7 +350,11 @@ class Bound(EndNode):
|
|||
self.identifier = forced_id
|
||||
|
||||
def copy(self):
|
||||
return Bound(self.name, forced_id = self.identifier, runner = self.runner)
|
||||
return Bound(
|
||||
self.name,
|
||||
forced_id = self.identifier,
|
||||
runner = self.runner
|
||||
)
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, Bound):
|
||||
|
@ -388,7 +392,14 @@ class Func(Node):
|
|||
return f"<func {self.input!r} {self.left!r}>"
|
||||
|
||||
def copy(self):
|
||||
return Func(self.input, None, runner = self.runner) # type: ignore
|
||||
return Func(
|
||||
Bound(
|
||||
self.input.name,
|
||||
runner = self.runner
|
||||
),
|
||||
None, # type: ignore
|
||||
runner = self.runner
|
||||
)
|
||||
|
||||
class Root(Node):
|
||||
"""
|
||||
|
|
|
@ -2,6 +2,7 @@ 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
|
||||
|
@ -27,6 +28,53 @@ def lamb_command(
|
|||
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.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"<ok>Enabled step-by-step reduction.</ok>"
|
||||
),
|
||||
style = lamb.utils.style
|
||||
)
|
||||
runner.step_reduction = True
|
||||
else:
|
||||
printf(
|
||||
HTML(
|
||||
f"<ok>Disabled step-by-step reduction.</ok>"
|
||||
),
|
||||
style = lamb.utils.style
|
||||
)
|
||||
runner.step_reduction = False
|
||||
|
||||
|
||||
@lamb_command(
|
||||
command_name = "save",
|
||||
|
@ -44,7 +92,7 @@ def cmd_save(command, runner) -> None:
|
|||
|
||||
target = command.args[0]
|
||||
if os.path.exists(target):
|
||||
confirm = runner.prompt_session.prompt(
|
||||
confirm = prompt(
|
||||
message = FormattedText([
|
||||
("class:warn", "File exists. Overwrite? "),
|
||||
("class:text", "[yes/no]: ")
|
||||
|
@ -174,7 +222,7 @@ def mdel(command, runner) -> None:
|
|||
help_text = "Delete all macros"
|
||||
)
|
||||
def clearmacros(command, runner) -> None:
|
||||
confirm = runner.prompt_session.prompt(
|
||||
confirm = prompt(
|
||||
message = FormattedText([
|
||||
("class:warn", "Are you sure? "),
|
||||
("class:text", "[yes/no]: ")
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
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 enum
|
||||
import math
|
||||
|
@ -13,6 +15,13 @@ from lamb.runner.misc import StopReason
|
|||
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:
|
||||
def __init__(
|
||||
|
@ -51,6 +60,9 @@ class Runner:
|
|||
|
||||
self.history: list[lamb.nodes.Root] = []
|
||||
|
||||
# If true, reduce step-by-step.
|
||||
self.step_reduction = False
|
||||
|
||||
def prompt(self):
|
||||
return self.prompt_session.prompt(
|
||||
message = self.prompt_message
|
||||
|
@ -96,7 +108,19 @@ class Runner:
|
|||
if len(warnings) != 0:
|
||||
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 (
|
||||
(
|
||||
(self.reduction_limit is None) or
|
||||
|
@ -105,7 +129,10 @@ class Runner:
|
|||
):
|
||||
|
||||
# Show reduction count
|
||||
if (k >= self.iter_update) and (k % self.iter_update == 0):
|
||||
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:
|
||||
|
@ -125,6 +152,27 @@ class Runner:
|
|||
if red_type == lamb.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: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)
|
||||
|
||||
if self.step_reduction:
|
||||
print("")
|
||||
|
||||
if k >= self.iter_update:
|
||||
# Clear reduction counter if it was printed
|
||||
print(" " * round(14 + math.log10(k)), end = "\r")
|
||||
|
|
Reference in New Issue