Compare commits
No commits in common. "8558c484c54e989750a798c42535ca5a82586cf3" and "7a1077e3715e03a107a610347350c469ce06196c" have entirely different histories.
8558c484c5
...
7a1077e371
|
@ -1,9 +1,7 @@
|
|||
{
|
||||
"cSpell.words": [
|
||||
"autochurch",
|
||||
"freevar",
|
||||
"pyparsing",
|
||||
"runstatus",
|
||||
"subvar"
|
||||
],
|
||||
"python.analysis.typeCheckingMode": "basic"
|
||||
|
|
|
@ -2,12 +2,15 @@
|
|||
|
||||
|
||||
## Todo (pre-release):
|
||||
- Fix parser (call parentheses)
|
||||
- Good command parsing (`:save`, `:load`, are a bare minimum)
|
||||
- Python files: installable, package list, etc
|
||||
- $\alpha$-equivalence check
|
||||
- Versioning
|
||||
- Automatic church numerals
|
||||
- Prettyprint functions (combine args, rename bound variables)
|
||||
- Documentation in README
|
||||
- Don't crash on errors
|
||||
|
||||
## Todo:
|
||||
- live syntax check
|
||||
|
@ -17,8 +20,6 @@
|
|||
- Warn when overwriting macro
|
||||
- Syntax highlighting: parenthesis, bound variables, macros, etc
|
||||
- Pin header to top of screen
|
||||
- Parser is a bit slow. Maybe we can do better?
|
||||
|
||||
## Mention in Docs
|
||||
- lambda functions only work with single-letter arguments
|
||||
- church numerals
|
10
main.py
10
main.py
|
@ -43,7 +43,7 @@ r.run_lines([
|
|||
"OR = λab.(a T b)",
|
||||
"XOR = λab.(a (NOT a b) b)",
|
||||
"w = λx.(x x)",
|
||||
"W = w w",
|
||||
"W = (w w)",
|
||||
"Y = λf.( (λx.(f (x x))) (λx.(f (x x))) )",
|
||||
"PAIR = λabi.( i a b )",
|
||||
"inc = λnfa.(f (n f a))",
|
||||
|
@ -90,13 +90,7 @@ while True:
|
|||
|
||||
# If this line defined a macro, print nothing.
|
||||
if isinstance(x, runner.MacroStatus):
|
||||
printf(FormattedText([
|
||||
("#FFFFFF", "Set "),
|
||||
("#FF00FF", x.macro_label),
|
||||
("#FFFFFF", " to "),
|
||||
("#FFFFFF", str(x.macro_expr))
|
||||
]))
|
||||
|
||||
pass
|
||||
|
||||
if isinstance(x, runner.CommandStatus):
|
||||
printf(x.formatted_text)
|
||||
|
|
70
parser.py
70
parser.py
|
@ -1,8 +1,17 @@
|
|||
import pyparsing as pp
|
||||
import tokens
|
||||
import utils
|
||||
|
||||
class Parser:
|
||||
"""
|
||||
Macro_def must be on its own line.
|
||||
macro_def :: var = expr
|
||||
|
||||
var :: word
|
||||
lambda_fun :: var -> expr
|
||||
call :: '(' (var | expr) ')' +
|
||||
expr :: define | var | call | '(' expr ')'
|
||||
"""
|
||||
|
||||
lp = pp.Suppress("(")
|
||||
rp = pp.Suppress(")")
|
||||
|
||||
|
@ -11,18 +20,6 @@ class Parser:
|
|||
pp_macro = pp.Word(pp.alphas + "_")
|
||||
pp_macro.set_parse_action(tokens.macro.from_parse)
|
||||
|
||||
pp_church = pp.Word(pp.nums)
|
||||
pp_church.set_parse_action(utils.autochurch)
|
||||
|
||||
# Function calls.
|
||||
# `tokens.lambda_apply.from_parse` handles chained calls.
|
||||
#
|
||||
# <exp> <exp>
|
||||
# <exp> <exp> <exp>
|
||||
pp_call = pp.Forward()
|
||||
pp_call <<= pp_expr[2, ...]
|
||||
pp_call.set_parse_action(tokens.lambda_apply.from_parse)
|
||||
|
||||
# Function definitions.
|
||||
# Right associative.
|
||||
#
|
||||
|
@ -31,44 +28,41 @@ class Parser:
|
|||
(pp.Suppress("λ") | pp.Suppress("\\")) +
|
||||
pp.Group(pp.Char(pp.alphas)[1, ...]) +
|
||||
pp.Suppress(".") +
|
||||
(pp_expr ^ pp_call)
|
||||
pp_expr
|
||||
)
|
||||
pp_lambda_fun.set_parse_action(tokens.lambda_func.from_parse)
|
||||
|
||||
# Assignment.
|
||||
# Can only be found at the start of a line.
|
||||
#
|
||||
# <name> = <exp>
|
||||
pp_macro_def = (
|
||||
pp.line_start() +
|
||||
pp_macro +
|
||||
pp.Suppress("=") +
|
||||
(pp_expr ^ pp_call)
|
||||
)
|
||||
# <var> = <exp>
|
||||
pp_macro_def = pp.line_start() + pp_macro + pp.Suppress("=") + pp_expr
|
||||
pp_macro_def.set_parse_action(tokens.macro_expression.from_parse)
|
||||
|
||||
pp_expr <<= (
|
||||
pp_church ^
|
||||
pp_lambda_fun ^
|
||||
pp_macro ^
|
||||
(lp + pp_expr + rp) ^
|
||||
(lp + pp_call + rp)
|
||||
)
|
||||
# Function calls.
|
||||
# `tokens.lambda_func.from_parse` handles chained calls.
|
||||
#
|
||||
# <var>(<exp>)
|
||||
# <var>(<exp>)(<exp>)(<exp>)
|
||||
# (<exp>)(<exp>)
|
||||
# (<exp>)(<exp>)(<exp>)(<exp>)
|
||||
pp_call = pp.Forward()
|
||||
pp_call <<= pp_expr[2, ...]
|
||||
pp_call.set_parse_action(tokens.lambda_apply.from_parse)
|
||||
|
||||
pp_expr <<= pp_lambda_fun ^ (lp + pp_expr + rp) ^ pp_macro ^ (lp + pp_call + rp)
|
||||
pp_all = pp_expr | pp_macro_def
|
||||
|
||||
pp_command = pp.Suppress(":") + pp.Word(pp.alphas + "_")
|
||||
pp_command.set_parse_action(tokens.command.from_parse)
|
||||
|
||||
|
||||
pp_all = (
|
||||
pp_expr ^
|
||||
pp_macro_def ^
|
||||
pp_command ^
|
||||
pp_call
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def parse_line(line):
|
||||
k = Parser.pp_all.parse_string(
|
||||
k = (
|
||||
Parser.pp_expr ^
|
||||
Parser.pp_macro_def ^
|
||||
Parser.pp_command ^ Parser.pp_call
|
||||
).parse_string(
|
||||
line,
|
||||
parse_all = True
|
||||
)[0]
|
||||
|
@ -77,4 +71,4 @@ class Parser:
|
|||
|
||||
@staticmethod
|
||||
def run_tests(lines):
|
||||
return Parser.pp_all.run_tests(lines)
|
||||
return Parser.pp_macro_def.run_tests(lines)
|
15
runner.py
15
runner.py
|
@ -10,6 +10,10 @@ from runstatus import ReduceStatus
|
|||
from runstatus import CommandStatus
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class Runner:
|
||||
def __init__(self):
|
||||
self.macro_table = {}
|
||||
|
@ -39,7 +43,7 @@ class Runner:
|
|||
i = 0
|
||||
macro_expansions = 0
|
||||
|
||||
while (self.reduction_limit is None) or (i < self.reduction_limit):
|
||||
while i < self.reduction_limit:
|
||||
r = expr.reduce(self.macro_table)
|
||||
expr = r.output
|
||||
|
||||
|
@ -60,7 +64,7 @@ class Runner:
|
|||
return ReduceStatus(
|
||||
reduction_count = i - macro_expansions,
|
||||
stop_reason = StopReason.MAX_EXCEEDED,
|
||||
result = r.output # type: ignore
|
||||
result = r.output
|
||||
)
|
||||
|
||||
|
||||
|
@ -77,8 +81,7 @@ class Runner:
|
|||
|
||||
return MacroStatus(
|
||||
was_rewritten = was_rewritten,
|
||||
macro_label = e.label,
|
||||
macro_expr = e.exp
|
||||
macro_label = e.label
|
||||
)
|
||||
|
||||
# If this line is a command, do the command.
|
||||
|
@ -86,11 +89,9 @@ class Runner:
|
|||
return self.exec_command(e.name)
|
||||
|
||||
# If this line is a plain expression, reduce it.
|
||||
elif isinstance(e, tokens.LambdaToken):
|
||||
else:
|
||||
e.bind_variables()
|
||||
return self.reduce_expression(e)
|
||||
else:
|
||||
raise TypeError(f"I don't know what to do with a {type(e)}")
|
||||
|
||||
|
||||
def run_lines(self, lines: list[str]):
|
||||
|
|
|
@ -17,19 +17,16 @@ class MacroStatus(RunStatus):
|
|||
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
|
||||
macro_label: str
|
||||
):
|
||||
self.was_rewritten = was_rewritten
|
||||
self.macro_label = macro_label
|
||||
self.macro_expr = macro_expr
|
||||
|
||||
|
||||
class StopReason(enum.Enum):
|
||||
|
|
12
tokens.py
12
tokens.py
|
@ -302,9 +302,9 @@ class lambda_func(LambdaToken):
|
|||
|
||||
|
||||
# Bind variables inside this function.
|
||||
if isinstance(self.output, macro) and placeholder is not None:
|
||||
if isinstance(self.output, macro):
|
||||
if self.output == placeholder:
|
||||
self.output = val # type: ignore
|
||||
self.output = val
|
||||
elif isinstance(self.output, lambda_func):
|
||||
self.output.bind_variables(placeholder, val)
|
||||
elif isinstance(self.output, lambda_apply):
|
||||
|
@ -345,7 +345,7 @@ class lambda_func(LambdaToken):
|
|||
calling_self = False
|
||||
if bound_var is None:
|
||||
calling_self = True
|
||||
bound_var = self.input # type: ignore
|
||||
bound_var = self.input
|
||||
new_out = self.output
|
||||
if isinstance(self.output, bound_variable):
|
||||
if self.output == bound_var:
|
||||
|
@ -353,7 +353,7 @@ class lambda_func(LambdaToken):
|
|||
elif isinstance(self.output, lambda_func):
|
||||
new_out = self.output.apply(val, bound_var = bound_var)
|
||||
elif isinstance(self.output, lambda_apply):
|
||||
new_out = self.output.sub_bound_var(val, bound_var = bound_var) # type: ignore
|
||||
new_out = self.output.sub_bound_var(val, bound_var = bound_var)
|
||||
|
||||
# If we're applying THIS function,
|
||||
# just give the output
|
||||
|
@ -430,7 +430,7 @@ class lambda_apply(LambdaToken):
|
|||
# everything below should still work as expected.
|
||||
if isinstance(self.fn, macro) and placeholder is not None:
|
||||
if self.fn == placeholder:
|
||||
self.fn = val # type: ignore
|
||||
self.fn = val
|
||||
elif isinstance(self.fn, lambda_func):
|
||||
self.fn.bind_variables(placeholder, val)
|
||||
elif isinstance(self.fn, lambda_apply):
|
||||
|
@ -438,7 +438,7 @@ class lambda_apply(LambdaToken):
|
|||
|
||||
if isinstance(self.arg, macro) and placeholder is not None:
|
||||
if self.arg == placeholder:
|
||||
self.arg = val # type: ignore
|
||||
self.arg = val
|
||||
elif isinstance(self.arg, lambda_func):
|
||||
self.arg.bind_variables(placeholder, val)
|
||||
elif isinstance(self.arg, lambda_apply):
|
||||
|
|
24
utils.py
24
utils.py
|
@ -1,24 +0,0 @@
|
|||
import tokens
|
||||
|
||||
def autochurch(results):
|
||||
"""
|
||||
Makes a church numeral from an integer.
|
||||
"""
|
||||
|
||||
num = int(results[0])
|
||||
|
||||
f = tokens.bound_variable()
|
||||
a = tokens.bound_variable()
|
||||
|
||||
chain = a
|
||||
|
||||
for i in range(num):
|
||||
chain = tokens.lambda_apply(f, chain)
|
||||
|
||||
return tokens.lambda_func(
|
||||
f,
|
||||
tokens.lambda_func(
|
||||
a,
|
||||
chain
|
||||
)
|
||||
)
|
Reference in New Issue