Rearranged code

master
Mark 2022-10-22 18:53:40 -07:00
parent 239aa210c5
commit 123d885adf
Signed by: Mark
GPG Key ID: AD62BB059C2AAEE4
6 changed files with 107 additions and 204 deletions

View File

@ -9,7 +9,6 @@ from prompt_toolkit.lexers import Lexer
from pyparsing import exceptions as ppx from pyparsing import exceptions as ppx
import lamb.runner as runner import lamb.runner as runner
import lamb.runstatus as rs
import lamb.tokens as tokens import lamb.tokens as tokens
import lamb.utils as utils import lamb.utils as utils
@ -100,31 +99,4 @@ while True:
]), style = utils.style) ]), style = utils.style)
continue 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("") printf("")

View File

@ -6,9 +6,9 @@ from prompt_toolkit.shortcuts import clear as clear_screen
import os.path import os.path
from pyparsing import exceptions as ppx from pyparsing import exceptions as ppx
import lamb.runstatus as rs
import lamb.utils as utils
import lamb.tokens
import lamb.utils
commands = {} commands = {}
@ -26,7 +26,7 @@ def run(command, runner) -> None:
FormattedText([ FormattedText([
("class:warn", f"Unknown command \"{command.name}\"") ("class:warn", f"Unknown command \"{command.name}\"")
]), ]),
style = utils.style style = lamb.utils.style
) )
else: else:
commands[command.name](command, runner) commands[command.name](command, runner)
@ -39,7 +39,7 @@ def save(command, runner) -> None:
HTML( HTML(
"<err>Command <cmd_code>:save</cmd_code> takes exactly one argument.</err>" "<err>Command <cmd_code>:save</cmd_code> takes exactly one argument.</err>"
), ),
style = utils.style style = lamb.utils.style
) )
return return
@ -57,7 +57,7 @@ def save(command, runner) -> None:
HTML( HTML(
"<err>Cancelled.</err>" "<err>Cancelled.</err>"
), ),
style = utils.style style = lamb.utils.style
) )
return return
@ -70,7 +70,7 @@ def save(command, runner) -> None:
HTML( HTML(
f"Wrote {len(runner.macro_table)} macros to <cmd_code>{target}</cmd_code>" f"Wrote {len(runner.macro_table)} macros to <cmd_code>{target}</cmd_code>"
), ),
style = utils.style style = lamb.utils.style
) )
@ -81,7 +81,7 @@ def load(command, runner):
HTML( HTML(
"<err>Command <cmd_code>:load</cmd_code> takes exactly one argument.</err>" "<err>Command <cmd_code>:load</cmd_code> takes exactly one argument.</err>"
), ),
style = utils.style style = lamb.utils.style
) )
return return
@ -91,7 +91,7 @@ def load(command, runner):
HTML( HTML(
f"<err>File {target} doesn't exist.</err>" f"<err>File {target} doesn't exist.</err>"
), ),
style = utils.style style = lamb.utils.style
) )
return return
@ -101,7 +101,7 @@ def load(command, runner):
for i in range(len(lines)): for i in range(len(lines)):
l = lines[i] l = lines[i]
try: try:
x = runner.run(l, macro_only = True) x = runner.parse(l)
except ppx.ParseException as e: except ppx.ParseException as e:
printf( printf(
FormattedText([ FormattedText([
@ -110,25 +110,30 @@ def load(command, runner):
("class:err", l[e.loc]), ("class:err", l[e.loc]),
("class:cmd_code", l[e.loc+1:]) ("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( printf(
FormattedText([ FormattedText([
("class:warn", f"Skipping line {i+1:02}: "), ("class:warn", f"Skipping line {i+1:02}: "),
("class:cmd_code", l), ("class:cmd_code", l),
("class:warn", f" is not a macro definition.") ("class:warn", f" is not a macro definition.")
]), ]),
style = utils.style style = lamb.utils.style
)
else:
printf(
FormattedText([
("class:ok", f"Loaded {x.macro_label}: "),
("class:cmd_code", str(x.macro_expr))
]),
style = 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( HTML(
"<err>Command <cmd_code>:mdel</cmd_code> takes exactly one argument.</err>" "<err>Command <cmd_code>:mdel</cmd_code> takes exactly one argument.</err>"
), ),
style = utils.style style = lamb.utils.style
) )
return return
@ -149,7 +154,7 @@ def mdel(command, runner) -> None:
HTML( HTML(
f"<warn>Macro \"{target}\" is not defined</warn>" f"<warn>Macro \"{target}\" is not defined</warn>"
), ),
style = utils.style style = lamb.utils.style
) )
return return
@ -166,13 +171,13 @@ def macros(command, runner) -> None:
("class:cmd_text", f"\t{name} \t {exp}\n") ("class:cmd_text", f"\t{name} \t {exp}\n")
for name, exp in runner.macro_table.items() for name, exp in runner.macro_table.items()
]), ]),
style = utils.style style = lamb.utils.style
) )
@lamb_command(help_text = "Clear the screen") @lamb_command(help_text = "Clear the screen")
def clear(command, runner) -> None: def clear(command, runner) -> None:
clear_screen() clear_screen()
utils.show_greeting() lamb.utils.show_greeting()
@lamb_command(help_text = "Print this help") @lamb_command(help_text = "Print this help")
@ -196,5 +201,5 @@ def help(command, runner) -> None:
]) + ]) +
"</cmd_text>" "</cmd_text>"
), ),
style = utils.style style = lamb.utils.style
) )

View File

@ -1,10 +1,20 @@
from prompt_toolkit import PromptSession 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 import lamb.commands as commands
from lamb.parser import LambdaParser from lamb.parser import LambdaParser
import lamb.tokens as tokens import lamb.tokens as tokens
import lamb.utils as utils 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: class Runner:
@ -24,6 +34,7 @@ class Runner:
# Maximum amount of reductions. # Maximum amount of reductions.
# If None, no maximum is enforced. # If None, no maximum is enforced.
# Must be at least 1.
self.reduction_limit: int | None = 1_000_000 self.reduction_limit: int | None = 1_000_000
# Ensure bound variables are unique. # Ensure bound variables are unique.
@ -34,8 +45,16 @@ class Runner:
def prompt(self): def prompt(self):
return self.prompt_session.prompt(message = self.prompt_message) 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. # Reduction Counter.
# We also count macro (and church) expansions, # We also count macro (and church) expansions,
@ -43,7 +62,7 @@ class Runner:
i = 0 i = 0
macro_expansions = 0 macro_expansions = 0
stop_reason = StopReason.MAX_EXCEEDED
while (self.reduction_limit is None) or (i < self.reduction_limit): while (self.reduction_limit is None) or (i < self.reduction_limit):
r = expr.reduce() r = expr.reduce()
expr = r.output expr = r.output
@ -54,11 +73,8 @@ class Runner:
# If we can't reduce this expression anymore, # If we can't reduce this expression anymore,
# it's in beta-normal form. # it's in beta-normal form.
if not r.was_reduced: if not r.was_reduced:
return rs.ReduceStatus( stop_reason = StopReason.BETA_NORMAL
reduction_count = i - macro_expansions, break
stop_reason = rs.StopReason.BETA_NORMAL,
result = r.output
)
# Count reductions # Count reductions
#i += 1 #i += 1
@ -70,43 +86,47 @@ class Runner:
else: else:
i += 1 i += 1
return rs.ReduceStatus( out_str = str(r.output) # type: ignore
reduction_count = i, # - macro_expansions,
stop_reason = rs.StopReason.MAX_EXCEEDED,
result = 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 # Apply a list of definitions
def run(self, line: str, *, macro_only = False) -> rs.RunStatus: def run(self, line: str, *, silent = False) -> None:
e = self.parser.parse_line(line) e = self.parse(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()
# If this line is a macro definition, save the macro. # If this line is a macro definition, save the macro.
if isinstance(e, tokens.macro_expression): if isinstance(e, tokens.macro_expression):
was_rewritten = e.label in self.macro_table self.save_macro(e, silent = silent)
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()
# If this line is a command, do the command. # If this line is a command, do the command.
elif isinstance(e, tokens.command): elif isinstance(e, tokens.command):
commands.run(e, self) commands.run(e, self)
return rs.CommandStatus(cmd = e.name)
# If this line is a plain expression, reduce it. # If this line is a plain expression, reduce it.
elif isinstance(e, tokens.LambdaToken): elif isinstance(e, tokens.LambdaToken):
return self.reduce_expression(e) self.reduce_expression(e)
# We shouldn't ever get here. # We shouldn't ever get here.
else: else:
@ -115,4 +135,4 @@ class Runner:
def run_lines(self, lines: list[str]): def run_lines(self, lines: list[str]):
for l in lines: for l in lines:
self.run(l) self.run(l, silent = True)

View File

@ -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

View File

@ -75,13 +75,27 @@ class church_num(LambdaToken):
return f"<{self.val}>" return f"<{self.val}>"
def __str__(self): def __str__(self):
return f"{self.val}" 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: def reduce(self, *, force_substitute = False) -> ReductionStatus:
if force_substitute: # Only expand macros if we NEED to if force_substitute: # Only expand macros if we NEED to
return ReductionStatus( return ReductionStatus(
output = utils.autochurch( output = self.to_church(),
self.runner,
self.val
),
was_reduced = True, was_reduced = True,
reduction_type = ReductionType.AUTOCHURCH reduction_type = ReductionType.AUTOCHURCH
) )
@ -214,19 +228,19 @@ class macro_expression:
) )
def set_runner(self, runner): def set_runner(self, runner):
self.exp.set_runner(runner) self.expr.set_runner(runner)
def bind_variables(self): 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.label = label
self.exp = exp self.expr = expr
def __repr__(self): def __repr__(self):
return f"<{self.label} := {self.exp!r}>" return f"<{self.label} := {self.expr!r}>"
def __str__(self): def __str__(self):
return f"{self.label} := {self.exp}" return f"{self.label} := {self.expr}"
class bound_variable(LambdaToken): class bound_variable(LambdaToken):

View File

@ -3,30 +3,6 @@ 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 importlib.metadata import version 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 style = Style.from_dict({ # type: ignore
# Basic formatting # Basic formatting