Rewrite continues
parent
455e447999
commit
a991c3bb91
293
lamb/__main__.py
293
lamb/__main__.py
|
@ -46,6 +46,8 @@ r = runner.Runner(
|
|||
)
|
||||
"""
|
||||
|
||||
macro_table = {}
|
||||
|
||||
class Direction(enum.Enum):
|
||||
UP = enum.auto()
|
||||
LEFT = enum.auto()
|
||||
|
@ -74,7 +76,11 @@ class Node:
|
|||
self.left: Node | None = None
|
||||
self.right: Node | None = None
|
||||
|
||||
def set_parent(self, parent, side: Direction):
|
||||
def set_parent(self, parent, side):
|
||||
if (parent is not None) and (side is None):
|
||||
raise Exception("If a node has a parent, it must have a direction.")
|
||||
if (parent is None) and (side is not None):
|
||||
raise Exception("If a node has no parent, it cannot have a direction.")
|
||||
self.parent = parent
|
||||
self.parent_side = side
|
||||
|
||||
|
@ -89,15 +95,33 @@ class Node:
|
|||
return Direction.UP, self.right
|
||||
|
||||
def go_up(self):
|
||||
if self.parent is None:
|
||||
raise Exception("Can't go up when parent is None")
|
||||
return self.parent_side, self.parent
|
||||
|
||||
class EndNode:
|
||||
def print_value(self):
|
||||
raise NotImplementedError("EndNodes MUST have a print_value method!")
|
||||
def clone(self):
|
||||
raise NotImplementedError("Nodes MUST provide a `clone` method!")
|
||||
|
||||
class Macro(Node, EndNode):
|
||||
class EndNode(Node):
|
||||
def print_value(self):
|
||||
raise NotImplementedError("EndNodes MUST provide a `print_value` method!")
|
||||
|
||||
class ExpandableEndNode(EndNode):
|
||||
def expand(self):
|
||||
raise NotImplementedError("ExpandableEndNodes MUST provide an `expand` method!")
|
||||
|
||||
class FreeVar(EndNode):
|
||||
def __init__(self, name: str):
|
||||
self.name = name
|
||||
|
||||
def __repr__(self):
|
||||
return f"<freevar {self.name}>"
|
||||
|
||||
def print_value(self):
|
||||
return f"{self.name}"
|
||||
|
||||
def clone(self):
|
||||
return FreeVar(self.name)
|
||||
|
||||
class Macro(ExpandableEndNode):
|
||||
@staticmethod
|
||||
def from_parse(results):
|
||||
return Macro(results[0])
|
||||
|
@ -114,10 +138,21 @@ class Macro(Node, EndNode):
|
|||
def print_value(self):
|
||||
return self.name
|
||||
|
||||
class Church(Node, EndNode):
|
||||
def expand(self):
|
||||
if self.name in macro_table:
|
||||
return macro_table[self.name]
|
||||
else:
|
||||
f = FreeVar(self.name)
|
||||
f.set_parent(self.parent, self.parent_side) # type: ignore
|
||||
return f
|
||||
|
||||
def clone(self):
|
||||
return Macro(self.name)
|
||||
|
||||
class Church(ExpandableEndNode):
|
||||
@staticmethod
|
||||
def from_parse(results):
|
||||
return Church(results[0])
|
||||
return Church(int(results[0]))
|
||||
|
||||
def __init__(self, value: int) -> None:
|
||||
super().__init__()
|
||||
|
@ -131,10 +166,7 @@ class Church(Node, EndNode):
|
|||
def print_value(self):
|
||||
return str(self.value)
|
||||
|
||||
def to_church(self):
|
||||
"""
|
||||
Return this number as an expanded church numeral.
|
||||
"""
|
||||
def expand(self):
|
||||
f = Bound("f")
|
||||
a = Bound("a")
|
||||
chain = a
|
||||
|
@ -147,9 +179,11 @@ class Church(Node, EndNode):
|
|||
Func(a, chain)
|
||||
)
|
||||
|
||||
def clone(self):
|
||||
return Church(self.value)
|
||||
|
||||
bound_counter = 0
|
||||
class Bound(Node, EndNode):
|
||||
class Bound(EndNode):
|
||||
def __init__(self, name: str, *, forced_id = None):
|
||||
self.name = name
|
||||
global bound_counter
|
||||
|
@ -161,13 +195,7 @@ class Bound(Node, EndNode):
|
|||
self.identifier = forced_id
|
||||
|
||||
def clone(self):
|
||||
"""
|
||||
Return a new bound variable equivalent to this one.
|
||||
"""
|
||||
return Bound(
|
||||
self.name,
|
||||
forced_id = self.identifier
|
||||
)
|
||||
return Bound(self.name, forced_id = self.identifier)
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, Bound):
|
||||
|
@ -207,6 +235,9 @@ class Func(Node):
|
|||
def __repr__(self):
|
||||
return f"<func {self.input!r} {self.left!r}>"
|
||||
|
||||
def clone(self):
|
||||
return Func(self.input, None) # type: ignore
|
||||
|
||||
class Call(Node):
|
||||
@staticmethod
|
||||
def from_parse(results):
|
||||
|
@ -237,6 +268,9 @@ class Call(Node):
|
|||
def __repr__(self):
|
||||
return f"<call {self.left!r} {self.right!r}>"
|
||||
|
||||
def clone(self):
|
||||
return Call(None, None) # type: ignore
|
||||
|
||||
class MacroDef:
|
||||
@staticmethod
|
||||
def from_parse(result):
|
||||
|
@ -278,21 +312,79 @@ p = lamb.parser.LambdaParser(
|
|||
)
|
||||
|
||||
|
||||
def print_expr(expr) -> str:
|
||||
|
||||
if isinstance(expr, MacroDef):
|
||||
return f"{expr.label} = {print_expr(expr.expr)}"
|
||||
def clone_one(ptr, out):
|
||||
if ptr.parent_side == Direction.LEFT:
|
||||
out.left = ptr.clone()
|
||||
out.left.set_parent(out, Direction.LEFT)
|
||||
else:
|
||||
out.right = ptr.clone()
|
||||
out.right.set_parent(out, Direction.RIGHT)
|
||||
|
||||
elif isinstance(expr, Node):
|
||||
def clone(expr: Node):
|
||||
if not isinstance(expr, Node):
|
||||
raise TypeError(f"I don't know what to do with a {type(expr)}")
|
||||
|
||||
# Disconnect parent while cloning
|
||||
old_parent = expr.parent
|
||||
expr.parent = None
|
||||
|
||||
out = expr.clone()
|
||||
out_ptr = out # Stays one step behind ptr, in the new tree.
|
||||
ptr = expr
|
||||
from_side = Direction.UP
|
||||
|
||||
out = ""
|
||||
if isinstance(expr, EndNode):
|
||||
return out
|
||||
|
||||
while True:
|
||||
if isinstance(ptr, EndNode):
|
||||
from_side, ptr = ptr.go_up()
|
||||
_, out_ptr = out_ptr.go_up()
|
||||
elif isinstance(ptr, Func):
|
||||
if from_side == Direction.UP:
|
||||
from_side, ptr = ptr.go_left()
|
||||
clone_one(ptr, out_ptr)
|
||||
_, out_ptr = out_ptr.go_left()
|
||||
elif from_side == Direction.LEFT:
|
||||
from_side, ptr = ptr.go_up()
|
||||
_, out_ptr = out_ptr.go_up()
|
||||
elif isinstance(ptr, Call):
|
||||
if from_side == Direction.UP:
|
||||
from_side, ptr = ptr.go_left()
|
||||
clone_one(ptr, out_ptr)
|
||||
_, out_ptr = out_ptr.go_left()
|
||||
elif from_side == Direction.LEFT:
|
||||
from_side, ptr = ptr.go_right()
|
||||
clone_one(ptr, out_ptr)
|
||||
_, out_ptr = out_ptr.go_right()
|
||||
elif from_side == Direction.RIGHT:
|
||||
from_side, ptr = ptr.go_up()
|
||||
_, out_ptr = out_ptr.go_up()
|
||||
if ptr is None:
|
||||
break
|
||||
|
||||
expr.parent = old_parent
|
||||
return out
|
||||
|
||||
def print_expr(expr) -> str:
|
||||
|
||||
out = ""
|
||||
|
||||
# Type check
|
||||
if isinstance(expr, MacroDef):
|
||||
out = expr.label + " = "
|
||||
expr = expr.expr
|
||||
elif not isinstance(expr, Node):
|
||||
raise TypeError(f"I don't know what to do with a {type(expr)}")
|
||||
|
||||
ptr = expr
|
||||
from_side = Direction.UP
|
||||
|
||||
while True:
|
||||
print(ptr)
|
||||
if isinstance(ptr, EndNode):
|
||||
out += ptr.print_value()
|
||||
if ptr.parent is not None:
|
||||
from_side, ptr = ptr.go_up()
|
||||
|
||||
elif isinstance(ptr, Func):
|
||||
|
@ -305,7 +397,6 @@ def print_expr(expr) -> str:
|
|||
out += "."
|
||||
from_side, ptr = ptr.go_left()
|
||||
elif from_side == Direction.LEFT:
|
||||
if ptr.parent is not None:
|
||||
from_side, ptr = ptr.go_up()
|
||||
|
||||
elif isinstance(ptr, Call):
|
||||
|
@ -317,25 +408,22 @@ def print_expr(expr) -> str:
|
|||
from_side, ptr = ptr.go_right()
|
||||
elif from_side == Direction.RIGHT:
|
||||
out += ")"
|
||||
if ptr.parent is not None:
|
||||
from_side, ptr = ptr.go_up()
|
||||
|
||||
if ptr.parent is None:
|
||||
if ptr is None:
|
||||
break
|
||||
return out
|
||||
|
||||
else:
|
||||
raise TypeError(f"I don't know what to do with a {type(expr)}")
|
||||
|
||||
def bind_variables(expr) -> None:
|
||||
|
||||
# Type check
|
||||
if isinstance(expr, MacroDef):
|
||||
bind_variables(expr.expr)
|
||||
expr = expr.expr
|
||||
elif not isinstance(expr, Node):
|
||||
raise TypeError(f"I don't know what to do with a {type(expr)}")
|
||||
|
||||
elif isinstance(expr, Node):
|
||||
ptr = expr
|
||||
from_side = Direction.UP
|
||||
|
||||
bound_variables = {}
|
||||
|
||||
while True:
|
||||
|
@ -358,14 +446,12 @@ def bind_variables(expr) -> None:
|
|||
# If we can't move down the tree, move up.
|
||||
if isinstance(ptr.left, EndNode):
|
||||
del bound_variables[ptr.input.name]
|
||||
if ptr.parent is not None:
|
||||
from_side, ptr = ptr.go_up()
|
||||
else:
|
||||
from_side, ptr = ptr.go_left()
|
||||
|
||||
elif from_side == Direction.LEFT:
|
||||
del bound_variables[ptr.input.name]
|
||||
if ptr.parent is not None:
|
||||
from_side, ptr = ptr.go_up()
|
||||
|
||||
elif isinstance(ptr, Call):
|
||||
|
@ -384,31 +470,127 @@ def bind_variables(expr) -> None:
|
|||
from_side, ptr = ptr.go_left()
|
||||
elif not isinstance(ptr.right, EndNode):
|
||||
from_side, ptr = ptr.go_right()
|
||||
elif ptr.parent is not None:
|
||||
else:
|
||||
from_side, ptr = ptr.go_up()
|
||||
|
||||
elif from_side == Direction.LEFT:
|
||||
if isinstance(ptr.right, Macro):
|
||||
if ptr.right.name in bound_variables:
|
||||
ptr.right = bound_variables[ptr.right.name].clone()
|
||||
ptr.right.set_parent(ptr, Direction.RIGHT)
|
||||
|
||||
if not isinstance(ptr.right, EndNode):
|
||||
from_side, ptr = ptr.go_right()
|
||||
elif ptr.parent is not None:
|
||||
else:
|
||||
from_side, ptr = ptr.go_up()
|
||||
|
||||
elif from_side == Direction.RIGHT:
|
||||
if ptr.parent is not None:
|
||||
from_side, ptr = ptr.go_up()
|
||||
|
||||
if ptr.parent is None:
|
||||
if ptr is None:
|
||||
break
|
||||
|
||||
|
||||
# Apply a function.
|
||||
# Returns the function's output.
|
||||
def call_func(fn: Func, arg: Node):
|
||||
ptr = fn
|
||||
|
||||
# Temporarily disconnect this function's
|
||||
# parent to keep our pointer inside this
|
||||
# subtree.
|
||||
old_parent = fn.parent
|
||||
fn.parent = None
|
||||
|
||||
from_side = Direction.UP
|
||||
|
||||
while True:
|
||||
if isinstance(ptr, Bound):
|
||||
if ptr == fn.input:
|
||||
if ptr.parent is None:
|
||||
raise Exception("Tried to substitute a None bound variable.")
|
||||
|
||||
if ptr.parent_side == Direction.LEFT:
|
||||
ptr.parent.left = clone(arg)
|
||||
ptr.parent.left.set_parent(ptr, Direction.LEFT)
|
||||
else:
|
||||
raise TypeError(f"I don't know what to do with a {type(expr)}")
|
||||
ptr.parent.right = clone(arg)
|
||||
ptr.parent.right.set_parent(ptr, Direction.RIGHT)
|
||||
|
||||
from_side, ptr = ptr.go_up()
|
||||
|
||||
elif isinstance(ptr, Func):
|
||||
if from_side == Direction.UP:
|
||||
from_side, ptr = ptr.go_left()
|
||||
elif from_side == Direction.LEFT:
|
||||
from_side, ptr = ptr.go_up()
|
||||
|
||||
elif isinstance(ptr, Call):
|
||||
if from_side == Direction.UP:
|
||||
from_side, ptr = ptr.go_left()
|
||||
elif from_side == Direction.LEFT:
|
||||
from_side, ptr = ptr.go_right()
|
||||
elif from_side == Direction.RIGHT:
|
||||
from_side, ptr = ptr.go_up()
|
||||
else:
|
||||
from_side, ptr = ptr.go_up()
|
||||
|
||||
if ptr is None:
|
||||
break
|
||||
|
||||
fn.parent = old_parent
|
||||
return fn.left
|
||||
|
||||
|
||||
# Do a single reduction step
|
||||
def reduce(expr) -> tuple[bool, Node]:
|
||||
|
||||
if not isinstance(expr, Node):
|
||||
raise TypeError(f"I can't reduce a {type(expr)}")
|
||||
|
||||
ptr = expr
|
||||
from_side = Direction.UP
|
||||
reduced = False
|
||||
|
||||
while True:
|
||||
print("redu", ptr)
|
||||
|
||||
if isinstance(ptr, Call):
|
||||
if from_side == Direction.UP:
|
||||
if isinstance(ptr.left, Func):
|
||||
if ptr.parent is None:
|
||||
expr = call_func(ptr.left, ptr.right)
|
||||
expr.set_parent(None, None)
|
||||
else:
|
||||
ptr.parent.left = call_func(ptr.left, ptr.right)
|
||||
ptr.parent.left.set_parent(ptr.parent, Direction.LEFT)
|
||||
reduced = True
|
||||
break
|
||||
elif isinstance(ptr.left, ExpandableEndNode):
|
||||
ptr.left = ptr.left.expand()
|
||||
reduced = True
|
||||
break
|
||||
elif isinstance(ptr.left, Call):
|
||||
from_side, ptr = ptr.go_left()
|
||||
else:
|
||||
from_side, ptr = ptr.go_right()
|
||||
|
||||
else:
|
||||
from_side, ptr = ptr.go_up()
|
||||
|
||||
elif isinstance(ptr, Func):
|
||||
if from_side == Direction.UP:
|
||||
from_side, ptr = ptr.go_left()
|
||||
else:
|
||||
from_side, ptr = ptr.go_up()
|
||||
|
||||
elif isinstance(ptr, EndNode):
|
||||
from_side, ptr = ptr.go_up()
|
||||
|
||||
if ptr is None:
|
||||
break
|
||||
|
||||
return reduced, expr
|
||||
|
||||
|
||||
for l in [
|
||||
"T = λab.a",
|
||||
"F = λab.b",
|
||||
"NOT = λa.(a F T)",
|
||||
"AND = λab.(a F b)",
|
||||
"OR = λab.(a T b)",
|
||||
|
@ -420,8 +602,21 @@ for l in [
|
|||
"S = λnfa.(f (n f a))",
|
||||
"Z = λn.n (λa.F) T",
|
||||
"MULT = λnmf.n (m f)",
|
||||
"H = λp.((PAIR (p F)) (S (p F)))"
|
||||
"H = λp.((PAIR (p F)) (S (p F)))",
|
||||
"D = λn.n H (PAIR 0 0) T",
|
||||
"FAC = λyn.(Z n)(1)(MULT n (y (D n)))",
|
||||
"S (λfa.f a)"
|
||||
]:
|
||||
n = p.parse_line(l)
|
||||
bind_variables(n)
|
||||
|
||||
if isinstance(n, MacroDef):
|
||||
macro_table[n.label] = n.expr
|
||||
print(print_expr(n))
|
||||
else:
|
||||
for i in range(10):
|
||||
r, n = reduce(n)
|
||||
if not r:
|
||||
break
|
||||
print(print_expr(n))
|
||||
#print(print_expr(clone(n)))
|
|
@ -47,7 +47,7 @@ class LambdaParser:
|
|||
)
|
||||
|
||||
self.pp_expr <<= (
|
||||
#self.pp_church ^
|
||||
self.pp_church ^
|
||||
self.pp_lambda_fun ^
|
||||
self.pp_name ^
|
||||
(self.lp + self.pp_expr + self.rp) ^
|
||||
|
|
239
lamb/tokens.py
239
lamb/tokens.py
|
@ -1,239 +0,0 @@
|
|||
import enum
|
||||
import lamb.utils as utils
|
||||
|
||||
|
||||
|
||||
class ReductionType(enum.Enum):
|
||||
MACRO_EXPAND = enum.auto()
|
||||
MACRO_TO_FREE = enum.auto()
|
||||
FUNCTION_APPLY = enum.auto()
|
||||
AUTOCHURCH = enum.auto()
|
||||
|
||||
|
||||
class ReductionStatus:
|
||||
"""
|
||||
This object helps organize reduction output.
|
||||
An instance is returned after every reduction step.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
output,
|
||||
was_reduced: bool,
|
||||
reduction_type: ReductionType | None = None
|
||||
):
|
||||
# The new expression
|
||||
self.output = output
|
||||
|
||||
# What did we do?
|
||||
# Will be None if was_reduced is false.
|
||||
self.reduction_type = reduction_type
|
||||
|
||||
# Did this reduction change anything?
|
||||
# If we try to reduce an irreducible expression,
|
||||
# this will be false.
|
||||
self.was_reduced = was_reduced
|
||||
|
||||
|
||||
class church_num(LambdaToken):
|
||||
def reduce(self, *, force_substitute = False) -> ReductionStatus:
|
||||
if force_substitute: # Only expand macros if we NEED to
|
||||
return ReductionStatus(
|
||||
output = self.to_church(),
|
||||
was_reduced = True,
|
||||
reduction_type = ReductionType.AUTOCHURCH
|
||||
)
|
||||
else: # Otherwise, do nothing.
|
||||
return ReductionStatus(
|
||||
output = self,
|
||||
was_reduced = False
|
||||
)
|
||||
|
||||
|
||||
class free_variable(LambdaToken):
|
||||
"""
|
||||
Represents a free variable.
|
||||
|
||||
This object does not reduce to
|
||||
anything, since it has no meaning.
|
||||
|
||||
Any name in an expression that isn't
|
||||
a macro or a bound variable is assumed
|
||||
to be a free variable.
|
||||
"""
|
||||
|
||||
def __init__(self, label: str):
|
||||
self.label = label
|
||||
|
||||
def __repr__(self):
|
||||
return f"<freevar {self.label}>"
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.label}"
|
||||
|
||||
class macro(LambdaToken):
|
||||
def reduce(
|
||||
self,
|
||||
*,
|
||||
# To keep output readable, we avoid expanding macros as often as possible.
|
||||
# Macros are irreducible if force_substitute is false.
|
||||
force_substitute = False,
|
||||
|
||||
# If this is false, error when macros aren't defined instead of
|
||||
# invisibly making a free variable.
|
||||
auto_free_vars = True
|
||||
) -> ReductionStatus:
|
||||
|
||||
if (self.name in self.runner.macro_table) and force_substitute:
|
||||
if force_substitute: # Only expand macros if we NEED to
|
||||
return ReductionStatus(
|
||||
output = self.runner.macro_table[self.name],
|
||||
reduction_type = ReductionType.MACRO_EXPAND,
|
||||
was_reduced = True
|
||||
)
|
||||
else: # Otherwise, do nothing.
|
||||
return ReductionStatus(
|
||||
output = self,
|
||||
was_reduced = False
|
||||
)
|
||||
|
||||
elif not auto_free_vars:
|
||||
raise ReductionError(f"Macro {self.name} is not defined")
|
||||
|
||||
else:
|
||||
return ReductionStatus(
|
||||
output = free_variable(self.name),
|
||||
reduction_type = ReductionType.MACRO_TO_FREE,
|
||||
was_reduced = True
|
||||
)
|
||||
|
||||
|
||||
class lambda_func(LambdaToken):
|
||||
def reduce(self) -> ReductionStatus:
|
||||
|
||||
r = self.output.reduce()
|
||||
|
||||
return ReductionStatus(
|
||||
was_reduced = r.was_reduced,
|
||||
reduction_type = r.reduction_type,
|
||||
output = lambda_func(
|
||||
self.input,
|
||||
r.output
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def apply(
|
||||
self,
|
||||
val,
|
||||
*,
|
||||
bound_var: bound_variable | None = None
|
||||
):
|
||||
"""
|
||||
Substitute `bound_var` into all instances of a bound variable `var`.
|
||||
If `bound_var` is none, use this functions bound variable.
|
||||
Returns a new object.
|
||||
"""
|
||||
|
||||
calling_self = False
|
||||
if bound_var is None:
|
||||
calling_self = True
|
||||
bound_var = self.input # type: ignore
|
||||
new_out = self.output
|
||||
if isinstance(self.output, bound_variable):
|
||||
if self.output == bound_var:
|
||||
new_out = val
|
||||
elif isinstance(self.output, lambda_func):
|
||||
new_out = self.output.apply(val, bound_var = bound_var)
|
||||
elif isinstance(self.output, lambda_apply):
|
||||
new_out = self.output.sub_bound_var(val, bound_var = bound_var) # type: ignore
|
||||
|
||||
# If we're applying THIS function,
|
||||
# just give the output
|
||||
if calling_self:
|
||||
return new_out
|
||||
|
||||
# If we're applying another function,
|
||||
# return this one with substitutions
|
||||
else:
|
||||
return lambda_func(
|
||||
self.input,
|
||||
new_out
|
||||
)
|
||||
|
||||
|
||||
class lambda_apply(LambdaToken):
|
||||
def sub_bound_var(
|
||||
self,
|
||||
val,
|
||||
*,
|
||||
bound_var: bound_variable
|
||||
):
|
||||
|
||||
new_fn = self.fn
|
||||
if isinstance(self.fn, bound_variable):
|
||||
if self.fn == bound_var:
|
||||
new_fn = val
|
||||
elif isinstance(self.fn, lambda_func):
|
||||
new_fn = self.fn.apply(val, bound_var = bound_var)
|
||||
elif isinstance(self.fn, lambda_apply):
|
||||
new_fn = self.fn.sub_bound_var(val, bound_var = bound_var)
|
||||
|
||||
new_arg = self.arg
|
||||
if isinstance(self.arg, bound_variable):
|
||||
if self.arg == bound_var:
|
||||
new_arg = val
|
||||
elif isinstance(self.arg, lambda_func):
|
||||
new_arg = self.arg.apply(val, bound_var = bound_var)
|
||||
elif isinstance(self.arg, lambda_apply):
|
||||
new_arg = self.arg.sub_bound_var(val, bound_var = bound_var)
|
||||
|
||||
return lambda_apply(
|
||||
new_fn,
|
||||
new_arg
|
||||
)
|
||||
|
||||
def reduce(self) -> ReductionStatus:
|
||||
|
||||
# If we can directly apply self.fn, do so.
|
||||
if isinstance(self.fn, lambda_func):
|
||||
return ReductionStatus(
|
||||
was_reduced = True,
|
||||
reduction_type = ReductionType.FUNCTION_APPLY,
|
||||
output = self.fn.apply(self.arg)
|
||||
)
|
||||
|
||||
# Otherwise, try to reduce self.fn.
|
||||
# If that is impossible, try to reduce self.arg.
|
||||
else:
|
||||
if isinstance(self.fn, macro) or isinstance(self.fn, church_num):
|
||||
# Macros must be reduced before we apply them as functions.
|
||||
# This is the only place we force substitution.
|
||||
r = self.fn.reduce(
|
||||
force_substitute = True
|
||||
)
|
||||
else:
|
||||
r = self.fn.reduce()
|
||||
|
||||
if r.was_reduced:
|
||||
return ReductionStatus(
|
||||
was_reduced = True,
|
||||
reduction_type = r.reduction_type,
|
||||
output = lambda_apply(
|
||||
r.output,
|
||||
self.arg
|
||||
)
|
||||
)
|
||||
|
||||
else:
|
||||
r = self.arg.reduce()
|
||||
|
||||
return ReductionStatus(
|
||||
was_reduced = r.was_reduced,
|
||||
reduction_type = r.reduction_type,
|
||||
output = lambda_apply(
|
||||
self.fn,
|
||||
r.output
|
||||
)
|
||||
)
|
Reference in New Issue