Improved printing
parent
81c78d7921
commit
09f78a7642
|
@ -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
|
|
@ -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(
|
||||||
|
|
44
lamb/node.py
44
lamb/node.py
|
@ -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
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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)]
|
||||||
|
)
|
|
@ -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",
|
||||||
|
|
Reference in New Issue