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/functions.py

287 lines
7.6 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-11-07 20:15:04 -08:00
def print_node(node: lbn.Node, *, export: bool = False) -> str:
if not isinstance(node, lbn.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, lbn.EndNode):
if isinstance(n, lbn.Bound):
out += bound_subs[n.identifier]
else:
out += n.print_value(export = export)
elif isinstance(n, lbn.Func):
2022-11-10 08:02:28 -08:00
# This should never be true, but
# keep this here to silence type checker.
if not isinstance(n.input, lbn.Bound):
raise Exception("input is macro, something is wrong.")
2022-11-07 20:15:04 -08:00
if s == lbn.Direction.UP:
2022-11-10 08:02:28 -08:00
o = n.input.print_value(export = export)
if o in bound_subs.values():
i = -1
p = o
while o in bound_subs.values():
2022-11-11 17:20:59 -08:00
o = p + lamb_engine.utils.subscript(i := i + 1)
2022-11-10 08:02:28 -08:00
bound_subs[n.input.identifier] = o
else:
bound_subs[n.input.identifier] = n.input.print_value()
2022-11-07 20:15:04 -08:00
if isinstance(n.parent, lbn.Call):
out += "("
if isinstance(n.parent, lbn.Func):
2022-11-10 08:02:28 -08:00
out += bound_subs[n.input.identifier]
2022-11-07 20:15:04 -08:00
else:
2022-11-10 08:02:28 -08:00
out += "λ" + bound_subs[n.input.identifier]
2022-11-07 20:15:04 -08:00
if not isinstance(n.left, lbn.Func):
out += "."
2022-11-10 08:02:28 -08:00
2022-11-07 20:15:04 -08:00
elif s == lbn.Direction.LEFT:
if isinstance(n.parent, lbn.Call):
out += ")"
2022-11-10 08:02:28 -08:00
del bound_subs[n.input.identifier]
2022-11-07 20:15:04 -08:00
elif isinstance(n, lbn.Call):
if s == lbn.Direction.UP:
out += "("
elif s == lbn.Direction.LEFT:
out += " "
elif s == lbn.Direction.RIGHT:
out += ")"
return out
2022-11-11 15:30:21 -08:00
2022-11-07 20:15:04 -08:00
def clone(node: lbn.Node):
if not isinstance(node, lbn.Node):
raise TypeError(f"I don't know what to do with a {type(node)}")
2022-11-11 15:30:21 -08:00
macro_map = {}
if isinstance(node, lbn.Func):
c = node.copy()
macro_map[node.input.identifier] = c.input.identifier # type: ignore
else:
c = node.copy()
out = c
2022-11-07 20:15:04 -08:00
out_ptr = out # Stays one step behind ptr, in the new tree.
ptr = node
from_side = lbn.Direction.UP
2022-11-11 15:30:21 -08:00
2022-11-07 20:15:04 -08:00
if isinstance(node, lbn.EndNode):
return out
# We're not using a TreeWalker here because
# we need more control over our pointer when cloning.
while True:
if isinstance(ptr, lbn.EndNode):
from_side, ptr = ptr.go_up()
_, out_ptr = out_ptr.go_up()
elif isinstance(ptr, lbn.Func) or isinstance(ptr, lbn.Root):
if from_side == lbn.Direction.UP:
from_side, ptr = ptr.go_left()
2022-11-11 15:30:21 -08:00
if isinstance(ptr, lbn.Func):
c = ptr.copy()
macro_map[ptr.input.identifier] = c.input.identifier # type: ignore
elif isinstance(ptr, lbn.Bound):
c = ptr.copy()
if c.identifier in macro_map:
c.identifier = macro_map[c.identifier]
else:
c = ptr.copy()
out_ptr.set_side(ptr.parent_side, c)
2022-11-07 20:15:04 -08:00
_, out_ptr = out_ptr.go_left()
elif from_side == lbn.Direction.LEFT:
from_side, ptr = ptr.go_up()
_, out_ptr = out_ptr.go_up()
elif isinstance(ptr, lbn.Call):
if from_side == lbn.Direction.UP:
from_side, ptr = ptr.go_left()
2022-11-11 15:30:21 -08:00
if isinstance(ptr, lbn.Func):
c = ptr.copy()
macro_map[ptr.input.identifier] = c.input.identifier # type: ignore
elif isinstance(ptr, lbn.Bound):
c = ptr.copy()
if c.identifier in macro_map:
c.identifier = macro_map[c.identifier]
else:
c = ptr.copy()
out_ptr.set_side(ptr.parent_side, c)
2022-11-07 20:15:04 -08:00
_, out_ptr = out_ptr.go_left()
elif from_side == lbn.Direction.LEFT:
from_side, ptr = ptr.go_right()
2022-11-11 15:30:21 -08:00
if isinstance(ptr, lbn.Func):
c = ptr.copy()
macro_map[ptr.input.identifier] = c.input.identifier # type: ignore
elif isinstance(ptr, lbn.Bound):
c = ptr.copy()
if c.identifier in macro_map:
c.identifier = macro_map[c.identifier]
else:
c = ptr.copy()
out_ptr.set_side(ptr.parent_side, c)
2022-11-07 20:15:04 -08:00
_, out_ptr = out_ptr.go_right()
elif from_side == lbn.Direction.RIGHT:
from_side, ptr = ptr.go_up()
_, out_ptr = out_ptr.go_up()
if ptr is node.parent:
break
return out
2022-11-09 21:47:55 -08:00
def prepare(root: lbn.Root, *, ban_macro_name = None) -> list:
2022-11-07 20:15:04 -08:00
"""
Prepare an expression for expansion.
This will does the following:
- Binds variables
- Turns unbound macros into free variables
- Generates warnings
"""
if not isinstance(root, lbn.Root):
raise TypeError(f"I don't know what to do with a {type(root)}")
bound_variables = {}
2022-11-09 21:47:55 -08:00
warnings = []
2022-11-07 20:15:04 -08:00
it = iter(root)
for s, n in it:
if isinstance(n, lbn.History):
2022-11-11 17:04:05 -08:00
if root.runner.history[0] == None:
2022-11-09 21:47:55 -08:00
raise lbn.ReductionError("There isn't any history to reference.")
else:
warnings += [
("class:code", "$"),
2022-11-11 17:04:05 -08:00
("class:warn", " will be expanded to ")
2022-11-11 17:20:59 -08:00
] + lamb_engine.utils.lex_str(str(n.expand()[1]))
2022-11-07 20:15:04 -08:00
# If this expression is part of a macro,
# make sure we don't reference it inside itself.
elif isinstance(n, lbn.Macro):
if (n.name == ban_macro_name) and (ban_macro_name is not None):
raise lbn.ReductionError("Macro cannot reference self")
# Bind variables
if n.name in bound_variables:
n.parent.set_side(
n.parent_side,
clone(bound_variables[n.name])
)
it.ptr = n.parent.get_side(n.parent_side)
# Turn undefined macros into free variables
elif n.name not in root.runner.macro_table:
2022-11-09 21:47:55 -08:00
warnings += [
("class:warn", "Name "),
("class:code", n.name),
("class:warn", " is a free variable\n"),
]
2022-11-07 20:15:04 -08:00
n.parent.set_side(
n.parent_side,
n.to_freevar()
)
it.ptr = n.parent.get_side(n.parent_side)
# Save bound variables when we enter a function's sub-tree,
# delete them when we exit it.
elif isinstance(n, lbn.Func):
if s == lbn.Direction.UP:
# Add this function's input to the table of bound variables.
# If it is already there, raise an error.
if (n.input.name in bound_variables):
raise lbn.ReductionError(f"Bound variable name conflict: \"{n.input.name}\"")
else:
2022-11-10 09:08:58 -08:00
bound_variables[n.input.name] = lbn.Bound(
2022-11-11 17:20:59 -08:00
lamb_engine.utils.remove_sub(n.input.name),
2022-11-10 09:08:58 -08:00
macro_name = n.input.name
)
2022-11-07 20:15:04 -08:00
n.input = bound_variables[n.input.name]
elif s == lbn.Direction.LEFT:
2022-11-10 09:08:58 -08:00
del bound_variables[n.input.macro_name] # type: ignore
2022-11-07 20:15:04 -08:00
2022-11-09 21:47:55 -08:00
return warnings
2022-11-07 20:15:04 -08:00
# Apply a function.
# Returns the function's output.
def call_func(fn: lbn.Func, arg: lbn.Node):
for s, n in fn:
if isinstance(n, lbn.Bound) and (s == lbn.Direction.UP):
if n == fn.input:
if n.parent is None:
raise Exception("Tried to substitute a None bound variable.")
n.parent.set_side(n.parent_side, clone(arg)) # type: ignore
return fn.left
# Do a single reduction step
def reduce(root: lbn.Root) -> tuple[lbn.ReductionType, lbn.Root]:
if not isinstance(root, lbn.Root):
raise TypeError(f"I can't reduce a {type(root)}")
out = root
for s, n in out:
if isinstance(n, lbn.Call) and (s == lbn.Direction.UP):
if isinstance(n.left, lbn.Func):
n.parent.set_side(
n.parent_side, # type: ignore
call_func(n.left, n.right)
)
return lbn.ReductionType.FUNCTION_APPLY, out
elif isinstance(n.left, lbn.ExpandableEndNode):
r, n.left = n.left.expand()
return r, out
return lbn.ReductionType.NOTHING, out
def expand(root: lbn.Root, *, force_all = False) -> tuple[int, lbn.Root]:
"""
Expands expandable nodes in the given tree.
If force_all is false, this only expands
ExpandableEndnodes that have "always_expand" set to True.
If force_all is True, this expands ALL
ExpandableEndnodes.
"""
if not isinstance(root, lbn.Root):
raise TypeError(f"I don't know what to do with a {type(root)}")
out = root
macro_expansions = 0
it = iter(root)
for s, n in it:
if (
isinstance(n, lbn.ExpandableEndNode) and
(force_all or n.always_expand)
):
n.parent.set_side(
n.parent_side, # type: ignore
n.expand()[1]
)
it.ptr = n.parent.get_side(
n.parent_side # type: ignore
)
macro_expansions += 1
return macro_expansions, out