Improved printing

master
Mark 2022-10-29 13:25:37 -07:00
parent 81c78d7921
commit 09f78a7642
Signed by: Mark
GPG Key ID: AD62BB059C2AAEE4
6 changed files with 71 additions and 17 deletions

View File

@ -30,7 +30,7 @@ To define macros, use `=`. For example,
Note that there are spaces in `λa.a F T`. With no spaces, `aFT` will be parsed as one variable. \
Lambda functions can only take single-letter, lowercase arguments. `λA.A` is not valid syntax. \
Unbound variables (upper and lower case) that aren't macros will become free variables.
Unbound variables (upper and lower case) that aren't macros will become free variables. Free variables will be shown with a `'`, like `a'`.
Be careful, macros are case-sensitive. If you define a macro `MAC` and accidentally write `mac` in the prompt, `mac` will become a free variable.
@ -76,7 +76,6 @@ Lamb treats each λ expression as a binary tree. Variable binding and reduction
## Todo (pre-release):
- Make command output accessible in prompt
- Prettyprint functions and rename bound variables
- Prettier colors
- Prevent macro-chaining recursion
- step-by-step reduction
@ -86,11 +85,9 @@ Lamb treats each λ expression as a binary tree. Variable binding and reduction
## Todo:
- Optimization: clone only if absolutely necessary
- Better class mutation: when is a node no longer valid?
- Loop detection
- Command-line options (load a file, run a set of commands)
- $\alpha$-equivalence check
- Unchurch macro: make church numerals human-readable
- Smart alignment in all printouts
- Syntax highlighting: parenthesis, bound variables, macros, etc

View File

@ -62,7 +62,7 @@ def cmd_save(command, runner) -> None:
with open(target, "w") as f:
f.write("\n".join(
[f"{n} = {e}" for n, e in runner.macro_table.items()]
[f"{n} = {e.export()}" for n, e in runner.macro_table.items()]
))
printf(

View File

@ -1,4 +1,5 @@
import enum
import lamb
class Direction(enum.Enum):
UP = enum.auto()
@ -207,6 +208,12 @@ class Node:
def __str__(self) -> str:
return print_node(self)
def export(self) -> str:
"""
Convert this tree to a parsable string.
"""
return print_node(self, export = True)
def bind_variables(self, *, ban_macro_name = None):
return bind_variables(
self,
@ -214,7 +221,7 @@ class Node:
)
class EndNode(Node):
def print_value(self):
def print_value(self, *, export: bool = False) -> str:
raise NotImplementedError("EndNodes MUST provide a `print_value` method!")
class ExpandableEndNode(EndNode):
@ -228,8 +235,11 @@ class FreeVar(EndNode):
def __repr__(self):
return f"<freevar {self.name}>"
def print_value(self):
return f"{self.name}"
def print_value(self, *, export: bool = False) -> str:
if export:
return f"{self.name}'"
else:
return f"{self.name}'"
def copy(self):
return FreeVar(self.name)
@ -248,7 +258,7 @@ class Macro(ExpandableEndNode):
def __repr__(self):
return f"<macro {self.name}>"
def print_value(self):
def print_value(self, *, export: bool = False) -> str:
return self.name
def expand(self, *, macro_table = {}) -> tuple[ReductionType, Node]:
@ -274,7 +284,7 @@ class Church(ExpandableEndNode):
def __repr__(self):
return f"<church {self.value}>"
def print_value(self):
def print_value(self, *, export: bool = False) -> str:
return str(self.value)
def expand(self, *, macro_table = {}) -> tuple[ReductionType, Node]:
@ -316,7 +326,7 @@ class Bound(EndNode):
def __repr__(self):
return f"<{self.name} {self.identifier}>"
def print_value(self):
def print_value(self, *, export: bool = False) -> str:
return self.name
class Func(Node):
@ -378,15 +388,31 @@ class Call(Node):
return Call(None, None) # type: ignore
def print_node(node: Node) -> str:
def print_node(node: Node, *, export: bool = False) -> str:
if not isinstance(node, Node):
raise TypeError(f"I don't know how to print a {type(node)}")
out = ""
bound_subs = {}
for s, n in node:
if isinstance(n, EndNode):
out += n.print_value()
if isinstance(n, Bound):
if n.identifier not in bound_subs.keys():
o = n.print_value(export = export)
if o in bound_subs.items():
i = 1
while o in bound_subs.items():
o = lamb.utils.subscript(i := i + 1)
bound_subs[n.identifier] = o
else:
bound_subs[n.identifier] = n.print_value()
out += bound_subs[n.identifier]
else:
out += n.print_value(export = export)
elif isinstance(n, Func):
if s == Direction.UP:
@ -502,7 +528,7 @@ def call_func(fn: Func, arg: Node):
raise Exception("Tried to substitute a None bound variable.")
n.parent.set_side(n.parent_side, clone(arg)) # type: ignore
return clone(fn.left)
return fn.left
# Do a single reduction step

View File

@ -116,7 +116,7 @@ class Runner:
# Show reduction count
if (i >= self.iter_update) and (i % self.iter_update == 0):
print(f" Reducing... {i}", end = "\r")
print(f" Reducing... {i:,}", end = "\r")
try:
red_type, node = lamb.node.reduce(

View File

@ -70,4 +70,35 @@ def show_greeting():
"<_s> A λ calculus engine</_s>",
"<_p> Type :help for help</_p>",
""
])), style = style)
])), style = style)
def subscript(num: int):
sub = {
"0": "",
"1": "",
"2": "",
"3": "",
"4": "",
"5": "",
"6": "",
"7": "",
"8": "",
"9": ""
}
sup = {
"0": "",
"1": "¹",
"2": "²",
"3": "³",
"4": "",
"5": "",
"6": "",
"7": "",
"8": "",
"9": ""
}
return "".join(
[sup[x] for x in str(num)]
)

View File

@ -14,7 +14,7 @@ description = "A lambda calculus engine"
#
# Patch release:
# Small, compatible fixes.
version = "0.1.0"
version = "0.1.1"
dependencies = [
"prompt-toolkit==3.0.31",