diff --git a/lamb/__main__.py b/lamb/__main__.py
index d9b98d8..6a1f6d9 100755
--- a/lamb/__main__.py
+++ b/lamb/__main__.py
@@ -9,7 +9,6 @@ from prompt_toolkit.lexers import Lexer
from pyparsing import exceptions as ppx
import lamb.runner as runner
-import lamb.runstatus as rs
import lamb.tokens as tokens
import lamb.utils as utils
@@ -100,31 +99,4 @@ while True:
]), style = utils.style)
continue
- # If this line defined a macro, print nothing.
- if isinstance(x, rs.MacroStatus):
- printf(FormattedText([
- ("class:text", "Set "),
- ("class:syn_macro", x.macro_label),
- ("class:text", " to "),
- ("class:text", str(x.macro_expr))
- ]), style = utils.style)
-
-
- if isinstance(x, rs.CommandStatus):
- pass
-
- # If this line was an expression, print reduction status
- elif isinstance(x, rs.ReduceStatus):
- printf(FormattedText([
- ("class:result_header", f"\nExit reason: "),
- x.stop_reason.value,
-
- ("class:result_header", f"\nReduction count: "),
- ("class:text", str(x.reduction_count)),
-
-
- ("class:result_header", "\n\n => "),
- ("class:text", str(x.result)),
- ]), style = utils.style)
-
printf("")
diff --git a/lamb/commands.py b/lamb/commands.py
index b1f25b9..321253c 100644
--- a/lamb/commands.py
+++ b/lamb/commands.py
@@ -6,9 +6,9 @@ from prompt_toolkit.shortcuts import clear as clear_screen
import os.path
from pyparsing import exceptions as ppx
-import lamb.runstatus as rs
-import lamb.utils as utils
+import lamb.tokens
+import lamb.utils
commands = {}
@@ -26,7 +26,7 @@ def run(command, runner) -> None:
FormattedText([
("class:warn", f"Unknown command \"{command.name}\"")
]),
- style = utils.style
+ style = lamb.utils.style
)
else:
commands[command.name](command, runner)
@@ -39,7 +39,7 @@ def save(command, runner) -> None:
HTML(
"Command :save takes exactly one argument."
),
- style = utils.style
+ style = lamb.utils.style
)
return
@@ -57,7 +57,7 @@ def save(command, runner) -> None:
HTML(
"Cancelled."
),
- style = utils.style
+ style = lamb.utils.style
)
return
@@ -70,7 +70,7 @@ def save(command, runner) -> None:
HTML(
f"Wrote {len(runner.macro_table)} macros to {target}"
),
- style = utils.style
+ style = lamb.utils.style
)
@@ -81,7 +81,7 @@ def load(command, runner):
HTML(
"Command :load takes exactly one argument."
),
- style = utils.style
+ style = lamb.utils.style
)
return
@@ -91,7 +91,7 @@ def load(command, runner):
HTML(
f"File {target} doesn't exist."
),
- style = utils.style
+ style = lamb.utils.style
)
return
@@ -101,7 +101,7 @@ def load(command, runner):
for i in range(len(lines)):
l = lines[i]
try:
- x = runner.run(l, macro_only = True)
+ x = runner.parse(l)
except ppx.ParseException as e:
printf(
FormattedText([
@@ -110,25 +110,30 @@ def load(command, runner):
("class:err", l[e.loc]),
("class:cmd_code", l[e.loc+1:])
]),
- style = utils.style
+ style = lamb.utils.style
)
- except rs.NotAMacro:
+ return
+
+ if not isinstance(x, lamb.tokens.macro_expression):
printf(
FormattedText([
("class:warn", f"Skipping line {i+1:02}: "),
("class:cmd_code", l),
("class:warn", f" is not a macro definition.")
]),
- style = utils.style
- )
- else:
- printf(
- FormattedText([
- ("class:ok", f"Loaded {x.macro_label}: "),
- ("class:cmd_code", str(x.macro_expr))
- ]),
- style = utils.style
+ style = lamb.utils.style
)
+ return
+
+ runner.save_macro(x, silent = True)
+
+ printf(
+ FormattedText([
+ ("class:ok", f"Loaded {x.label}: "),
+ ("class:cmd_code", str(x.expr))
+ ]),
+ style = lamb.utils.style
+ )
@@ -139,7 +144,7 @@ def mdel(command, runner) -> None:
HTML(
"Command :mdel takes exactly one argument."
),
- style = utils.style
+ style = lamb.utils.style
)
return
@@ -149,7 +154,7 @@ def mdel(command, runner) -> None:
HTML(
f"Macro \"{target}\" is not defined"
),
- style = utils.style
+ style = lamb.utils.style
)
return
@@ -166,13 +171,13 @@ def macros(command, runner) -> None:
("class:cmd_text", f"\t{name} \t {exp}\n")
for name, exp in runner.macro_table.items()
]),
- style = utils.style
+ style = lamb.utils.style
)
@lamb_command(help_text = "Clear the screen")
def clear(command, runner) -> None:
clear_screen()
- utils.show_greeting()
+ lamb.utils.show_greeting()
@lamb_command(help_text = "Print this help")
@@ -196,5 +201,5 @@ def help(command, runner) -> None:
]) +
""
),
- style = utils.style
+ style = lamb.utils.style
)
\ No newline at end of file
diff --git a/lamb/runner.py b/lamb/runner.py
index c36d619..8576409 100644
--- a/lamb/runner.py
+++ b/lamb/runner.py
@@ -1,10 +1,20 @@
from prompt_toolkit import PromptSession
+from prompt_toolkit.formatted_text import FormattedText
+from prompt_toolkit import print_formatted_text as printf
+
+import enum
import lamb.commands as commands
from lamb.parser import LambdaParser
import lamb.tokens as tokens
import lamb.utils as utils
-import lamb.runstatus as rs
+
+
+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")
class Runner:
@@ -24,6 +34,7 @@ class Runner:
# 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.
@@ -34,8 +45,16 @@ class Runner:
def prompt(self):
return self.prompt_session.prompt(message = self.prompt_message)
+ def parse(self, line):
+ e = self.parser.parse_line(line)
+ # 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()
+ return e
- def reduce_expression(self, expr: tokens.LambdaToken) -> rs.ReduceStatus:
+
+ def reduce_expression(self, expr: tokens.LambdaToken) -> None:
# Reduction Counter.
# We also count macro (and church) expansions,
@@ -43,7 +62,7 @@ class Runner:
i = 0
macro_expansions = 0
-
+ stop_reason = StopReason.MAX_EXCEEDED
while (self.reduction_limit is None) or (i < self.reduction_limit):
r = expr.reduce()
expr = r.output
@@ -54,11 +73,8 @@ class Runner:
# If we can't reduce this expression anymore,
# it's in beta-normal form.
if not r.was_reduced:
- return rs.ReduceStatus(
- reduction_count = i - macro_expansions,
- stop_reason = rs.StopReason.BETA_NORMAL,
- result = r.output
- )
+ stop_reason = StopReason.BETA_NORMAL
+ break
# Count reductions
#i += 1
@@ -70,43 +86,47 @@ class Runner:
else:
i += 1
- return rs.ReduceStatus(
- reduction_count = i, # - macro_expansions,
- stop_reason = rs.StopReason.MAX_EXCEEDED,
- result = r.output # type: ignore
- )
+ out_str = str(r.output) # type: ignore
+ printf(FormattedText([
+ ("class:result_header", f"\nExit reason: "),
+ stop_reason.value,
+
+ ("class:result_header", f"\nReduction count: "),
+ ("class:text", str(i)),
+
+
+ ("class:result_header", "\n\n => "),
+ ("class:text", out_str),
+ ]), style = utils.style)
+
+ def save_macro(self, macro: tokens.macro_expression, *, 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:syn_macro", macro.label),
+ ("class:text", " to "),
+ ("class:text", str(macro.expr))
+ ]), style = utils.style)
# Apply a list of definitions
- def run(self, line: str, *, macro_only = False) -> rs.RunStatus:
- e = self.parser.parse_line(line)
- # 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()
+ def run(self, line: str, *, silent = False) -> None:
+ e = self.parse(line)
# If this line is a macro definition, save the macro.
if isinstance(e, tokens.macro_expression):
- was_rewritten = e.label in self.macro_table
- self.macro_table[e.label] = e.exp
-
- return rs.MacroStatus(
- was_rewritten = was_rewritten,
- macro_label = e.label,
- macro_expr = e.exp
- )
-
- elif macro_only:
- raise rs.NotAMacro()
+ self.save_macro(e, silent = silent)
# If this line is a command, do the command.
elif isinstance(e, tokens.command):
commands.run(e, self)
- return rs.CommandStatus(cmd = e.name)
# If this line is a plain expression, reduce it.
elif isinstance(e, tokens.LambdaToken):
- return self.reduce_expression(e)
+ self.reduce_expression(e)
# We shouldn't ever get here.
else:
@@ -115,4 +135,4 @@ class Runner:
def run_lines(self, lines: list[str]):
for l in lines:
- self.run(l)
+ self.run(l, silent = True)
diff --git a/lamb/runstatus.py b/lamb/runstatus.py
deleted file mode 100644
index f307e6c..0000000
--- a/lamb/runstatus.py
+++ /dev/null
@@ -1,84 +0,0 @@
-from prompt_toolkit.formatted_text import FormattedText
-from prompt_toolkit.formatted_text import HTML
-import enum
-
-import lamb.tokens as tokens
-
-
-class NotAMacro(Exception):
- """
- Raised when we try to run a non-macro line
- while enforcing macro_only in Runner.run().
-
- This should be caught and elegantly presented to the user.
- """
- pass
-
-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.
- `macro_expr`: The expr of the macro we just made.
- """
-
- def __init__(
- self,
- *,
- was_rewritten: bool,
- macro_label: str,
- macro_expr
- ):
- self.was_rewritten = was_rewritten
- self.macro_label = macro_label
- self.macro_expr = macro_expr
-
-
-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")
-
-
-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.
- Doesn't do anything interesting.
-
- Values:
- `cmd`: The command that was run, without a colon.
- """
-
- def __init__(self, *, cmd: str):
- self.cmd = cmd
\ No newline at end of file
diff --git a/lamb/tokens.py b/lamb/tokens.py
index a6cd961..f1a9034 100755
--- a/lamb/tokens.py
+++ b/lamb/tokens.py
@@ -75,13 +75,27 @@ class church_num(LambdaToken):
return f"<{self.val}>"
def __str__(self):
return f"{self.val}"
+
+ def to_church(self):
+ """
+ Return this number as an expanded church numeral.
+ """
+ f = bound_variable("f", runner = self.runner)
+ a = bound_variable("a", runner = self.runner)
+ chain = a
+
+ for i in range(self.val):
+ chain = lambda_apply(f, chain)
+
+ return lambda_func(
+ f,
+ lambda_func(a, chain)
+ )
+
def reduce(self, *, force_substitute = False) -> ReductionStatus:
if force_substitute: # Only expand macros if we NEED to
return ReductionStatus(
- output = utils.autochurch(
- self.runner,
- self.val
- ),
+ output = self.to_church(),
was_reduced = True,
reduction_type = ReductionType.AUTOCHURCH
)
@@ -214,19 +228,19 @@ class macro_expression:
)
def set_runner(self, runner):
- self.exp.set_runner(runner)
+ self.expr.set_runner(runner)
def bind_variables(self):
- self.exp.bind_variables()
+ self.expr.bind_variables()
- def __init__(self, label: str, exp: LambdaToken):
+ def __init__(self, label: str, expr: LambdaToken):
self.label = label
- self.exp = exp
+ self.expr = expr
def __repr__(self):
- return f"<{self.label} := {self.exp!r}>"
+ return f"<{self.label} := {self.expr!r}>"
def __str__(self):
- return f"{self.label} := {self.exp}"
+ return f"{self.label} := {self.expr}"
class bound_variable(LambdaToken):
diff --git a/lamb/utils.py b/lamb/utils.py
index 6e2895a..276070d 100644
--- a/lamb/utils.py
+++ b/lamb/utils.py
@@ -3,30 +3,6 @@ from prompt_toolkit.formatted_text import HTML
from prompt_toolkit import print_formatted_text as printf
from importlib.metadata import version
-import lamb.tokens as tokens
-
-
-def autochurch(runner, num):
- """
- Makes a church numeral from an integer.
- """
-
- f = tokens.bound_variable("f", runner = runner)
- a = tokens.bound_variable("a", runner = runner)
-
- chain = a
-
- for i in range(num):
- chain = tokens.lambda_apply(f, chain)
-
- return tokens.lambda_func(
- f,
- tokens.lambda_func(
- a,
- chain
- )
- )
-
style = Style.from_dict({ # type: ignore
# Basic formatting