Improved reduction process
parent
c671fc6f9a
commit
5f4f3db48f
210
tokens.py
210
tokens.py
|
@ -1,7 +1,53 @@
|
||||||
from typing import Type
|
from ast import Lambda
|
||||||
|
import enum
|
||||||
|
|
||||||
|
class ReductionType(enum.Enum):
|
||||||
|
MACRO_EXPAND = enum.auto()
|
||||||
|
MACRO_TO_FREE = enum.auto()
|
||||||
|
FUNCTION_APPLY = enum.auto()
|
||||||
|
|
||||||
|
|
||||||
class free_variable:
|
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 LambdaToken:
|
||||||
|
"""
|
||||||
|
Base class for all lambda tokens.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def bind_variables(self) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def reduce(self, macro_table) -> ReductionStatus:
|
||||||
|
return ReductionStatus(
|
||||||
|
was_reduced = False,
|
||||||
|
output = self
|
||||||
|
)
|
||||||
|
|
||||||
|
class free_variable(LambdaToken):
|
||||||
"""
|
"""
|
||||||
Represents a free variable.
|
Represents a free variable.
|
||||||
|
|
||||||
|
@ -22,13 +68,22 @@ class free_variable:
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.label}"
|
return f"{self.label}"
|
||||||
|
|
||||||
|
class command:
|
||||||
|
@staticmethod
|
||||||
|
def from_parse(result):
|
||||||
|
return command(
|
||||||
|
result[0],
|
||||||
|
)
|
||||||
|
|
||||||
class macro:
|
def __init__(self, name):
|
||||||
|
self.name = name
|
||||||
|
|
||||||
|
class macro(LambdaToken):
|
||||||
"""
|
"""
|
||||||
Represents a "macro" in lambda calculus,
|
Represents a "macro" in lambda calculus,
|
||||||
a variable that expands to an expression.
|
a variable that reduces to an expression.
|
||||||
|
|
||||||
These don't have inherent logic, they
|
These don't have any inherent logic, they
|
||||||
just make writing and reading expressions
|
just make writing and reading expressions
|
||||||
easier.
|
easier.
|
||||||
|
|
||||||
|
@ -54,14 +109,21 @@ class macro:
|
||||||
raise TypeError("Can only compare macro with macro")
|
raise TypeError("Can only compare macro with macro")
|
||||||
return self.name == other.name
|
return self.name == other.name
|
||||||
|
|
||||||
def expand(self, macro_table = {}, *, auto_free_vars = True):
|
def reduce(self, macro_table = {}, *, auto_free_vars = True) -> ReductionStatus:
|
||||||
if self.name in macro_table:
|
if self.name in macro_table:
|
||||||
return macro_table[self.name]
|
return ReductionStatus(
|
||||||
|
output = macro_table[self.name],
|
||||||
|
reduction_type = ReductionType.MACRO_EXPAND,
|
||||||
|
was_reduced = True
|
||||||
|
)
|
||||||
elif not auto_free_vars:
|
elif not auto_free_vars:
|
||||||
raise NameError(f"Name {self.name} is not defined!")
|
raise NameError(f"Name {self.name} is not defined!")
|
||||||
else:
|
else:
|
||||||
return free_variable(self.name)
|
return ReductionStatus(
|
||||||
|
output = free_variable(self.name),
|
||||||
|
reduction_type = ReductionType.MACRO_TO_FREE,
|
||||||
|
was_reduced = True
|
||||||
|
)
|
||||||
|
|
||||||
class macro_expression:
|
class macro_expression:
|
||||||
"""
|
"""
|
||||||
|
@ -80,7 +142,7 @@ class macro_expression:
|
||||||
result[1]
|
result[1]
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, label, exp):
|
def __init__(self, label: str, exp: LambdaToken):
|
||||||
self.label = label
|
self.label = label
|
||||||
self.exp = exp
|
self.exp = exp
|
||||||
|
|
||||||
|
@ -91,11 +153,8 @@ class macro_expression:
|
||||||
return f"{self.label} := {self.exp}"
|
return f"{self.label} := {self.exp}"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
bound_variable_counter = 0
|
bound_variable_counter = 0
|
||||||
class bound_variable:
|
class bound_variable(LambdaToken):
|
||||||
def __init__(self, forced_id = None):
|
def __init__(self, forced_id = None):
|
||||||
global bound_variable_counter
|
global bound_variable_counter
|
||||||
|
|
||||||
|
@ -113,7 +172,7 @@ class bound_variable:
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"<in {self.identifier}>"
|
return f"<in {self.identifier}>"
|
||||||
|
|
||||||
class lambda_func:
|
class lambda_func(LambdaToken):
|
||||||
"""
|
"""
|
||||||
Represents a function.
|
Represents a function.
|
||||||
Defined like λa.aa
|
Defined like λa.aa
|
||||||
|
@ -132,9 +191,13 @@ class lambda_func:
|
||||||
result[1]
|
result[1]
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, input_var, output):
|
def __init__(
|
||||||
self.input = input_var
|
self,
|
||||||
self.output = output
|
input_var: macro | bound_variable,
|
||||||
|
output: LambdaToken
|
||||||
|
):
|
||||||
|
self.input: macro | bound_variable = input_var
|
||||||
|
self.output: LambdaToken = output
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"<{self.input!r} → {self.output!r}>"
|
return f"<{self.input!r} → {self.output!r}>"
|
||||||
|
@ -142,7 +205,6 @@ class lambda_func:
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return f"λ{self.input}.{self.output}"
|
return f"λ{self.input}.{self.output}"
|
||||||
|
|
||||||
|
|
||||||
def bind_variables(
|
def bind_variables(
|
||||||
self,
|
self,
|
||||||
placeholder: macro | None = None,
|
placeholder: macro | None = None,
|
||||||
|
@ -211,29 +273,25 @@ class lambda_func:
|
||||||
elif isinstance(self.output, lambda_apply):
|
elif isinstance(self.output, lambda_apply):
|
||||||
self.output.bind_variables(placeholder, val)
|
self.output.bind_variables(placeholder, val)
|
||||||
|
|
||||||
# Expand this function's output.
|
def reduce(self, macro_table = {}) -> ReductionStatus:
|
||||||
# For functions, this isn't done unless
|
|
||||||
# its explicitly asked for.
|
|
||||||
def expand(self, macro_table = {}):
|
|
||||||
new_out = self.output
|
|
||||||
if isinstance(self.output, macro):
|
|
||||||
new_out = self.output.expand(macro_table)
|
|
||||||
|
|
||||||
# If the macro becomes a free variable, expand again.
|
r = self.output.reduce(macro_table)
|
||||||
if isinstance(new_out, free_variable):
|
|
||||||
lambda_func(
|
|
||||||
self.input,
|
|
||||||
new_out
|
|
||||||
).expand(macro_table)
|
|
||||||
|
|
||||||
elif isinstance(self.output, lambda_func):
|
# If a macro becomes a free variable,
|
||||||
new_out = self.output.expand(macro_table)
|
# reduce twice.
|
||||||
elif isinstance(self.output, lambda_apply):
|
if r.reduction_type == ReductionType.MACRO_TO_FREE:
|
||||||
new_out = self.output.expand(macro_table)
|
self.output = r.output
|
||||||
return lambda_func(
|
return self.reduce(macro_table)
|
||||||
|
|
||||||
|
return ReductionStatus(
|
||||||
|
was_reduced = r.was_reduced,
|
||||||
|
reduction_type = r.reduction_type,
|
||||||
|
output = lambda_func(
|
||||||
self.input,
|
self.input,
|
||||||
new_out
|
r.output
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def apply(
|
def apply(
|
||||||
self,
|
self,
|
||||||
|
@ -259,8 +317,6 @@ class lambda_func:
|
||||||
new_out = self.output.apply(val, bound_var = bound_var)
|
new_out = self.output.apply(val, bound_var = bound_var)
|
||||||
elif isinstance(self.output, lambda_apply):
|
elif isinstance(self.output, lambda_apply):
|
||||||
new_out = self.output.sub_bound_var(val, bound_var = bound_var)
|
new_out = self.output.sub_bound_var(val, bound_var = bound_var)
|
||||||
else:
|
|
||||||
raise TypeError("Cannot apply a function to {self.output!r}")
|
|
||||||
|
|
||||||
# If we're applying THIS function,
|
# If we're applying THIS function,
|
||||||
# just give the output
|
# just give the output
|
||||||
|
@ -276,7 +332,7 @@ class lambda_func:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class lambda_apply:
|
class lambda_apply(LambdaToken):
|
||||||
"""
|
"""
|
||||||
Represents a function application.
|
Represents a function application.
|
||||||
Has two elements: fn, the function,
|
Has two elements: fn, the function,
|
||||||
|
@ -303,11 +359,11 @@ class lambda_apply:
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
fn,
|
fn: LambdaToken,
|
||||||
arg
|
arg: LambdaToken
|
||||||
):
|
):
|
||||||
self.fn = fn
|
self.fn: LambdaToken = fn
|
||||||
self.arg = arg
|
self.arg: LambdaToken = arg
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"<{self.fn!r} | {self.arg!r}>"
|
return f"<{self.fn!r} | {self.arg!r}>"
|
||||||
|
@ -381,40 +437,48 @@ class lambda_apply:
|
||||||
new_arg
|
new_arg
|
||||||
)
|
)
|
||||||
|
|
||||||
def expand(self, macro_table = {}):
|
def reduce(self, macro_table = {}) -> ReductionStatus:
|
||||||
# If fn is a function, apply it.
|
|
||||||
|
# If we can directly apply self.fn, do so.
|
||||||
if isinstance(self.fn, lambda_func):
|
if isinstance(self.fn, lambda_func):
|
||||||
return self.fn.apply(self.arg)
|
return ReductionStatus(
|
||||||
# If fn is an application or macro, expand it.
|
was_reduced = True,
|
||||||
elif isinstance(self.fn, macro):
|
reduction_type = ReductionType.FUNCTION_APPLY,
|
||||||
f = lambda_apply(
|
output = self.fn.apply(self.arg)
|
||||||
m := self.fn.expand(macro_table),
|
|
||||||
self.arg
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# If a macro becomes a free variable,
|
# Otherwise, try to reduce self.fn.
|
||||||
# expand twice.
|
# If that is impossible, try to reduce self.arg.
|
||||||
if isinstance(m, free_variable):
|
|
||||||
return f.expand(macro_table)
|
|
||||||
else:
|
else:
|
||||||
return f
|
r = self.fn.reduce(macro_table)
|
||||||
|
# If a macro becomes a free variable,
|
||||||
|
# reduce twice.
|
||||||
|
if r.reduction_type == ReductionType.MACRO_TO_FREE:
|
||||||
|
self.fn = r.output
|
||||||
|
return self.reduce(macro_table)
|
||||||
|
|
||||||
elif isinstance(self.fn, lambda_apply):
|
if r.was_reduced:
|
||||||
return lambda_apply(
|
return ReductionStatus(
|
||||||
self.fn.expand(macro_table),
|
was_reduced = True,
|
||||||
|
reduction_type = r.reduction_type,
|
||||||
|
output = lambda_apply(
|
||||||
|
r.output,
|
||||||
self.arg
|
self.arg
|
||||||
)
|
)
|
||||||
|
|
||||||
# If we get to this point, the function we're applying
|
|
||||||
# can't be expanded. That means it's a free or bound
|
|
||||||
# variable. If that happens, expand the arg instead.
|
|
||||||
elif (
|
|
||||||
isinstance(self.arg, lambda_apply) or
|
|
||||||
isinstance(self.arg, lambda_func)
|
|
||||||
):
|
|
||||||
return lambda_apply(
|
|
||||||
self.fn,
|
|
||||||
self.arg.expand(macro_table)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return self
|
else:
|
||||||
|
r = self.arg.reduce(macro_table)
|
||||||
|
|
||||||
|
if r.reduction_type == ReductionType.MACRO_TO_FREE:
|
||||||
|
self.arg = r.output
|
||||||
|
return self.reduce(macro_table)
|
||||||
|
|
||||||
|
return ReductionStatus(
|
||||||
|
was_reduced = r.was_reduced,
|
||||||
|
reduction_type = r.reduction_type,
|
||||||
|
output = lambda_apply(
|
||||||
|
self.fn,
|
||||||
|
r.output
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
Reference in New Issue