This repository has been archived on 2024-11-05. You can view files and clone it, but cannot push or open issues/pull-requests.
lamb/lamb_engine/nodes/nodes.py

452 lines
11 KiB
Python
Raw Permalink Normal View History

2022-11-11 17:20:59 -08:00
import lamb_engine
import lamb_engine.nodes as lbn
2022-10-28 08:33:52 -07:00
class TreeWalker:
2022-10-28 16:01:58 -07:00
"""
An iterator that walks the "outline" of a tree
defined by a chain of nodes.
It returns a tuple: (out_side, out)
out is the node we moved to,
out_side is the direction we came to the node from.
"""
2022-10-28 08:33:52 -07:00
def __init__(self, expr):
self.expr = expr
self.ptr = expr
self.first_step = True
2022-11-07 20:15:04 -08:00
self.from_side = lbn.Direction.UP
2022-10-28 08:33:52 -07:00
def __iter__(self):
return self
2022-10-28 08:33:52 -07:00
def __next__(self):
2022-10-28 16:01:58 -07:00
# This could be implemented without checking the node type,
# but there's no reason to do that.
# Maybe later?
2022-10-28 08:33:52 -07:00
if self.first_step:
self.first_step = False
return self.from_side, self.ptr
2022-10-28 08:33:52 -07:00
if isinstance(self.ptr, Root):
2022-11-07 20:15:04 -08:00
if self.from_side == lbn.Direction.UP:
self.from_side, self.ptr = self.ptr.go_left()
elif isinstance(self.ptr, EndNode):
self.from_side, self.ptr = self.ptr.go_up()
2022-10-28 08:33:52 -07:00
elif isinstance(self.ptr, Func):
2022-11-07 20:15:04 -08:00
if self.from_side == lbn.Direction.UP:
2022-10-28 08:33:52 -07:00
self.from_side, self.ptr = self.ptr.go_left()
2022-11-07 20:15:04 -08:00
elif self.from_side == lbn.Direction.LEFT:
2022-10-28 08:33:52 -07:00
self.from_side, self.ptr = self.ptr.go_up()
elif isinstance(self.ptr, Call):
2022-11-07 20:15:04 -08:00
if self.from_side == lbn.Direction.UP:
2022-10-28 08:33:52 -07:00
self.from_side, self.ptr = self.ptr.go_left()
2022-11-07 20:15:04 -08:00
elif self.from_side == lbn.Direction.LEFT:
2022-10-28 08:33:52 -07:00
self.from_side, self.ptr = self.ptr.go_right()
2022-11-07 20:15:04 -08:00
elif self.from_side == lbn.Direction.RIGHT:
2022-10-28 08:33:52 -07:00
self.from_side, self.ptr = self.ptr.go_up()
else:
raise TypeError(f"I don't know how to iterate a {type(self.ptr)}")
# Stop conditions
if isinstance(self.expr, Root):
if self.ptr is self.expr:
raise StopIteration
else:
if self.ptr is self.expr.parent:
raise StopIteration
return self.from_side, self.ptr
2022-10-28 08:33:52 -07:00
class Node:
2022-10-28 16:01:58 -07:00
"""
Generic class for an element of an expression tree.
2022-10-28 20:45:26 -07:00
All nodes are subclasses of this.
2022-10-28 16:01:58 -07:00
"""
2022-10-28 08:33:52 -07:00
def __init__(self):
# The node this one is connected to.
# None if this is the top objects.
self.parent: Node = None # type: ignore
2022-10-28 08:33:52 -07:00
# What direction this is relative to the parent.
# Left of Right.
self.parent_side: Direction = None # type: ignore
2022-10-28 08:33:52 -07:00
# Left and right nodes, None if empty
self._left = None
self._right = None
2022-10-28 08:33:52 -07:00
2022-10-29 15:44:17 -07:00
# The runner this node is attached to.
# Set by Node.set_runner()
2022-11-11 17:20:59 -08:00
self.runner: lamb_engine.runner.Runner = None # type: ignore
2022-10-29 15:44:17 -07:00
2022-10-28 08:33:52 -07:00
def __iter__(self):
return TreeWalker(self)
def _set_parent(self, parent, side):
2022-10-28 16:01:58 -07:00
"""
Set this node's parent and parent side.
This method shouldn't be called explicitly unless
there's no other option. Use self.left and self.right instead.
"""
2022-10-28 08:33:52 -07:00
if (parent is not None) and (side is None):
2022-11-07 20:15:04 -08:00
raise Exception("If a node has a parent, it must have a lbn.direction.")
2022-10-28 08:33:52 -07:00
if (parent is None) and (side is not None):
2022-11-07 20:15:04 -08:00
raise Exception("If a node has no parent, it cannot have a lbn.direction.")
2022-10-28 08:33:52 -07:00
self.parent = parent
self.parent_side = side
return self
@property
def left(self):
return self._left
@left.setter
def left(self, node):
if node is not None:
2022-11-07 20:15:04 -08:00
node._set_parent(self, lbn.Direction.LEFT)
2022-10-28 08:33:52 -07:00
self._left = node
@property
def right(self):
return self._right
@right.setter
def right(self, node):
if node is not None:
2022-11-07 20:15:04 -08:00
node._set_parent(self, lbn.Direction.RIGHT)
2022-10-28 08:33:52 -07:00
self._right = node
2022-11-07 20:15:04 -08:00
def set_side(self, side: lbn.Direction, node):
2022-10-28 16:01:58 -07:00
"""
A wrapper around Node.left and Node.right that
automatically selects a side.
"""
2022-11-07 20:15:04 -08:00
if side == lbn.Direction.LEFT:
2022-10-28 08:33:52 -07:00
self.left = node
2022-11-07 20:15:04 -08:00
elif side == lbn.Direction.RIGHT:
2022-10-28 08:33:52 -07:00
self.right = node
else:
raise TypeError("Can only set left or right side.")
2022-11-07 20:15:04 -08:00
def get_side(self, side: lbn.Direction):
if side == lbn.Direction.LEFT:
2022-10-29 10:25:06 -07:00
return self.left
2022-11-07 20:15:04 -08:00
elif side == lbn.Direction.RIGHT:
2022-10-29 10:25:06 -07:00
return self.right
else:
raise TypeError("Can only get left or right side.")
2022-10-28 08:33:52 -07:00
def go_left(self):
2022-10-28 16:01:58 -07:00
"""
Go down the left branch of this node.
Returns a tuple (from_dir, node)
from_dir is the direction from which we came INTO the next node.
node is the node on the left of this one.
"""
2022-10-28 08:33:52 -07:00
if self._left is None:
raise Exception("Can't go left when left is None")
2022-11-07 20:15:04 -08:00
return lbn.Direction.UP, self._left
2022-10-28 08:33:52 -07:00
def go_right(self):
2022-10-28 16:01:58 -07:00
"""
Go down the right branch of this node.
Returns a tuple (from_dir, node)
from_dir is the direction from which we came INTO the next node.
node is the node on the right of this one.
"""
2022-10-28 08:33:52 -07:00
if self._right is None:
raise Exception("Can't go right when right is None")
2022-11-07 20:15:04 -08:00
return lbn.Direction.UP, self._right
2022-10-28 08:33:52 -07:00
def go_up(self):
2022-10-28 16:01:58 -07:00
"""
Go up th the parent of this node.
Returns a tuple (from_dir, node)
from_dir is the direction from which we came INTO the parent.
node is the node above of this one.
"""
2022-10-28 08:33:52 -07:00
return self.parent_side, self.parent
2022-10-28 16:01:58 -07:00
def copy(self):
"""
Return a copy of this node.
parent, parent_side, left, and right should be left
as None, and will be filled later.
"""
raise NotImplementedError("Nodes MUST provide a `copy` method!")
2022-10-28 08:33:52 -07:00
def __str__(self) -> str:
2022-11-07 20:15:04 -08:00
return lbn.print_node(self)
2022-10-28 08:33:52 -07:00
2022-10-29 13:25:37 -07:00
def export(self) -> str:
"""
Convert this tree to a parsable string.
"""
2022-11-07 20:15:04 -08:00
return lbn.print_node(self, export = True)
2022-10-29 13:25:37 -07:00
2022-10-29 15:44:17 -07:00
def set_runner(self, runner):
for s, n in self:
2022-11-07 20:15:04 -08:00
if s == lbn.Direction.UP:
2022-10-29 15:44:17 -07:00
n.runner = runner # type: ignore
return self
2022-10-28 08:33:52 -07:00
class EndNode(Node):
2022-10-29 13:25:37 -07:00
def print_value(self, *, export: bool = False) -> str:
2022-10-28 08:33:52 -07:00
raise NotImplementedError("EndNodes MUST provide a `print_value` method!")
class ExpandableEndNode(EndNode):
2022-10-29 15:44:17 -07:00
always_expand = False
2022-11-07 20:15:04 -08:00
def expand(self) -> tuple[lbn.ReductionType, Node]:
2022-10-28 08:33:52 -07:00
raise NotImplementedError("ExpandableEndNodes MUST provide an `expand` method!")
class FreeVar(EndNode):
2022-10-29 15:44:17 -07:00
def __init__(self, name: str, *, runner = None):
2022-10-31 08:20:27 -07:00
super().__init__()
2022-10-28 08:33:52 -07:00
self.name = name
2022-10-29 15:44:17 -07:00
self.runner = runner # type: ignore
2022-10-28 08:33:52 -07:00
def __repr__(self):
return f"<freevar {self.name}>"
2022-10-29 13:25:37 -07:00
def print_value(self, *, export: bool = False) -> str:
if export:
return f"{self.name}'"
else:
return f"{self.name}'"
2022-10-28 08:33:52 -07:00
2022-10-28 16:01:58 -07:00
def copy(self):
2022-10-28 08:33:52 -07:00
return FreeVar(self.name)
class Macro(ExpandableEndNode):
@staticmethod
def from_parse(results):
return Macro(results[0])
2022-10-29 15:44:17 -07:00
def __init__(self, name: str, *, runner = None) -> None:
2022-10-28 08:33:52 -07:00
super().__init__()
self.name = name
self.left = None
self.right = None
2022-10-29 15:44:17 -07:00
self.runner = runner # type: ignore
2022-10-28 08:33:52 -07:00
def __repr__(self):
return f"<macro {self.name}>"
2022-10-29 13:25:37 -07:00
def print_value(self, *, export: bool = False) -> str:
2022-10-28 08:33:52 -07:00
return self.name
2022-11-07 20:15:04 -08:00
def expand(self) -> tuple[lbn.ReductionType, Node]:
2022-10-29 15:44:17 -07:00
if self.name in self.runner.macro_table:
# The element in the macro table will be a Root node,
# so we clone its left element.
return (
2022-11-07 20:15:04 -08:00
lbn.ReductionType.MACRO_EXPAND,
lbn.clone(self.runner.macro_table[self.name].left)
)
2022-10-28 08:33:52 -07:00
else:
raise Exception(f"Macro {self.name} is not defined")
def to_freevar(self):
return FreeVar(self.name, runner = self.runner)
2022-10-28 08:33:52 -07:00
2022-10-28 16:01:58 -07:00
def copy(self):
2022-10-29 15:44:17 -07:00
return Macro(self.name, runner = self.runner)
2022-10-28 08:33:52 -07:00
class Church(ExpandableEndNode):
@staticmethod
def from_parse(results):
return Church(int(results[0]))
2022-10-29 15:44:17 -07:00
def __init__(self, value: int, *, runner = None) -> None:
2022-10-28 08:33:52 -07:00
super().__init__()
self.value = value
self.left = None
self.right = None
2022-10-29 15:44:17 -07:00
self.runner = runner # type: ignore
2022-10-28 08:33:52 -07:00
def __repr__(self):
return f"<church {self.value}>"
2022-10-29 13:25:37 -07:00
def print_value(self, *, export: bool = False) -> str:
2022-10-28 08:33:52 -07:00
return str(self.value)
2022-11-07 20:15:04 -08:00
def expand(self) -> tuple[lbn.ReductionType, Node]:
2022-10-28 08:33:52 -07:00
f = Bound("f")
a = Bound("a")
chain = a
for i in range(self.value):
2022-11-07 20:15:04 -08:00
chain = Call(lbn.clone(f), lbn.clone(chain))
2022-10-28 08:33:52 -07:00
2022-10-28 16:01:58 -07:00
return (
2022-11-07 20:15:04 -08:00
lbn.ReductionType.AUTOCHURCH,
2022-10-29 15:44:17 -07:00
Func(f, Func(a, chain)).set_runner(self.runner)
2022-10-28 08:33:52 -07:00
)
2022-10-28 16:01:58 -07:00
def copy(self):
2022-10-29 15:44:17 -07:00
return Church(self.value, runner = self.runner)
class History(ExpandableEndNode):
always_expand = True
@staticmethod
def from_parse(results):
return History()
def __init__(self, *, runner = None) -> None:
super().__init__()
self.left = None
self.right = None
self.runner = runner # type: ignore
def __repr__(self):
return f"<$>"
def print_value(self, *, export: bool = False) -> str:
return "$"
2022-11-07 20:15:04 -08:00
def expand(self) -> tuple[lbn.ReductionType, Node]:
2022-11-09 21:47:55 -08:00
# We shouldn't ever get here, prepare()
# catches empty history.
2022-11-11 17:04:05 -08:00
if self.runner.history[0] == None:
2022-11-09 21:47:55 -08:00
raise Exception(f"Tried to expand empty history.")
# .left is VERY important!
# self.runner.history will contain Root nodes,
# and we don't want those *inside* our tree.
2022-11-11 17:04:05 -08:00
return lbn.ReductionType.HIST_EXPAND, lbn.clone(self.runner.history[0].left)
2022-10-29 15:44:17 -07:00
def copy(self):
return History(runner = self.runner)
2022-10-28 08:33:52 -07:00
bound_counter = 0
class Bound(EndNode):
2022-11-10 09:08:58 -08:00
def __init__(self, name: str, *, forced_id = None, runner = None, macro_name = None):
2022-10-28 08:33:52 -07:00
self.name = name
global bound_counter
2022-10-29 15:44:17 -07:00
self.runner = runner # type: ignore
2022-10-28 08:33:52 -07:00
2022-11-10 09:08:58 -08:00
# The name of the macro this bound came from.
# Always equal to self.name, unless the macro
# this came from had a subscript.
self.macro_name = macro_name
2022-11-10 09:08:58 -08:00
2022-10-28 08:33:52 -07:00
if forced_id is None:
self.identifier = bound_counter
bound_counter += 1
else:
self.identifier = forced_id
2022-10-28 16:01:58 -07:00
def copy(self):
2022-11-11 15:30:21 -08:00
return Bound(
self.name,
forced_id = self.identifier,
runner = self.runner
)
2022-10-28 08:33:52 -07:00
def __eq__(self, other):
if not isinstance(other, Bound):
raise TypeError(f"Cannot compare bound_variable with {type(other)}")
return self.identifier == other.identifier
def __repr__(self):
return f"<{self.name} {self.identifier}>"
2022-10-29 13:25:37 -07:00
def print_value(self, *, export: bool = False) -> str:
2022-10-28 08:33:52 -07:00
return self.name
class Func(Node):
@staticmethod
def from_parse(result):
if len(result[0]) == 1:
return Func(
result[0][0],
result[1]
)
else:
return Func(
result[0].pop(0),
Func.from_parse(result)
)
def __init__(self, input, output: Node, *, runner = None) -> None:
2022-10-28 08:33:52 -07:00
super().__init__()
self.input = input
2022-10-28 08:33:52 -07:00
self.left: Node = output
self.right: None = None
2022-10-29 15:44:17 -07:00
self.runner = runner # type: ignore
2022-10-28 08:33:52 -07:00
def __repr__(self):
return f"<func {self.input!r} {self.left!r}>"
2022-10-28 16:01:58 -07:00
def copy(self):
2022-11-11 15:30:21 -08:00
return Func(
Bound(
self.input.name,
runner = self.runner
),
None, # type: ignore
runner = self.runner
)
2022-10-28 08:33:52 -07:00
class Root(Node):
"""
Root node.
Used at the top of an expression.
"""
def __init__(self, left: Node, *, runner = None) -> None:
super().__init__()
self.left: Node = left
self.runner = runner # type: ignore
def __repr__(self):
return f"<Root {self.left!r}>"
def copy(self):
return Root(None, runner = self.runner) # type: ignore
2022-10-28 08:33:52 -07:00
class Call(Node):
@staticmethod
def from_parse(results):
if len(results) == 2:
return Call(
results[0],
results[1]
)
else:
this = Call(
results[0],
results[1]
)
return Call.from_parse(
[Call(
results[0],
results[1]
)] + results[2:]
)
2022-10-29 15:44:17 -07:00
def __init__(self, fn: Node, arg: Node, *, runner = None) -> None:
2022-10-28 08:33:52 -07:00
super().__init__()
self.left: Node = fn
self.right: Node = arg
2022-10-29 15:44:17 -07:00
self.runner = runner # type: ignore
2022-10-28 08:33:52 -07:00
def __repr__(self):
return f"<call {self.left!r} {self.right!r}>"
2022-10-28 16:01:58 -07:00
def copy(self):
2022-11-07 20:15:04 -08:00
return Call(None, None, runner = self.runner) # type: ignore