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. \ 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. \ 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. 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): ## Todo (pre-release):
- Make command output accessible in prompt - Make command output accessible in prompt
- Prettyprint functions and rename bound variables
- Prettier colors - Prettier colors
- Prevent macro-chaining recursion - Prevent macro-chaining recursion
- step-by-step reduction - step-by-step reduction
@ -86,11 +85,9 @@ Lamb treats each λ expression as a binary tree. Variable binding and reduction
## Todo: ## Todo:
- Optimization: clone only if absolutely necessary
- Better class mutation: when is a node no longer valid? - Better class mutation: when is a node no longer valid?
- Loop detection - Loop detection
- Command-line options (load a file, run a set of commands) - Command-line options (load a file, run a set of commands)
- $\alpha$-equivalence check - $\alpha$-equivalence check
- Unchurch macro: make church numerals human-readable - Unchurch macro: make church numerals human-readable
- Smart alignment in all printouts
- Syntax highlighting: parenthesis, bound variables, macros, etc - 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: with open(target, "w") as f:
f.write("\n".join( 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( printf(

View File

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

View File

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

View File

@ -70,4 +70,35 @@ def show_greeting():
"<_s> A λ calculus engine</_s>", "<_s> A λ calculus engine</_s>",
"<_p> Type :help for help</_p>", "<_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: # Patch release:
# Small, compatible fixes. # Small, compatible fixes.
version = "0.1.0" version = "0.1.1"
dependencies = [ dependencies = [
"prompt-toolkit==3.0.31", "prompt-toolkit==3.0.31",