Improved printing
This commit is contained in:
		| @ -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 | ||||
| @ -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( | ||||
|  | ||||
							
								
								
									
										44
									
								
								lamb/node.py
									
									
									
									
									
								
							
							
						
						
									
										44
									
								
								lamb/node.py
									
									
									
									
									
								
							| @ -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 | ||||
|  | ||||
| @ -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( | ||||
|  | ||||
| @ -71,3 +71,34 @@ def show_greeting(): | ||||
| 		"<_p> Type :help for help</_p>", | ||||
| 		"" | ||||
| 	])), 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: | ||||
| # Small, compatible fixes. | ||||
| version = "0.1.0" | ||||
| version = "0.1.1" | ||||
|  | ||||
| dependencies = [ | ||||
| 	"prompt-toolkit==3.0.31", | ||||
|  | ||||
		Reference in New Issue
	
	Block a user