Improved reduction process

master
Mark 2022-10-21 14:40:59 -07:00
parent c671fc6f9a
commit 5f4f3db48f
Signed by: Mark
GPG Key ID: AD62BB059C2AAEE4
1 changed files with 140 additions and 76 deletions

210
tokens.py
View File

@ -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
)
)