diff --git a/checker/terms.py b/checker/terms.py index 4257bef..cdab253 100644 --- a/checker/terms.py +++ b/checker/terms.py @@ -4,7 +4,7 @@ """ from __future__ import annotations -from typing import Callable +from typing import Callable, Generator import copy @@ -24,6 +24,24 @@ USABLE_VAR_NAMES = [ 'μ', 'ν', 'ξ', 'ο', 'π', 'ρ', 'σ', 'τ', 'υ', 'φ', 'χ', 'ψ', 'ω' ] +def contains(haystack : Term, needle : Term) -> bool: + """ + Determine whether a given small term value is contained in a bigger + term. + + :param haystack: The term to search through. + :type haystack: Term + :param needle: The term to find. + :type needle: Term + :return: Whether the needle was found in the haystack. + :rtype: bool + """ + for term in haystack.rec_iter(): + if term == needle: + return True + else: + return False + def normalize(term : Term) -> Term: """ Create a normalization function. This function simplifies functions as @@ -78,6 +96,21 @@ def std_norm_iter(term : Term) -> Term: return term _known_norms.append(std_norm_iter) +def replace(term : Term, old : Term, new : Term) -> Term: + """ + Find and replace occurrences of a sub-term in a given term. + + :param term: The original term to update. + :type term: Term + :param old: The term to find and replace. + :type old: Term + :param new: The new term to replace the old one with. + :type new: Term + :return: The replaced term result. + :rtype: Term + """ + return traverse_bottom_up(f=lambda t : new if t == old else t, term=term) + def traverse(f : Callable[[Term], Term], term : Term) -> Term: """ Traverse through the tree back-and-forth, aiming to run the given @@ -96,7 +129,7 @@ def traverse(f : Callable[[Term], Term], term : Term) -> Term: a, b = Term(), term while a != b: - a, b = b, traverse_bottom_up(f=f, term=traverse_top_down(f=f, term=term)) + a, b = b, traverse_bottom_up(f=f, term=traverse_top_down(f=f, term=b)) return b @@ -171,12 +204,6 @@ class Term: Base class for all terms. """ - def __eq__(self, other) -> bool: - """ - Determine whether two terms are equal. - """ - return str(self) == str(other) - def __repr__(self) -> str: """ Create a representation of a term. @@ -185,6 +212,12 @@ class Term: def normalize(self) -> Term: return normalize(term=self) + + def replace(self, old : Term, new : Term) -> Term: + return replace(term=self, old=old, new=new) + + def rec_iter(self) -> Generator[Term, None, None]: + yield self def to_str(self, vars : dict[str, Var]) -> str: return "" @@ -204,6 +237,11 @@ class App(Term): Create a representation of a term. """ return self.to_str(vars={}) + + def rec_iter(self) -> Generator[Term, None, None]: + yield self + yield from self.f.rec_iter() + yield from self.x.rec_iter() def to_str(self, vars: dict[str, Var]) -> str: f_str = self.f.to_str(vars=vars) @@ -246,6 +284,18 @@ class Lambda(Term): out : Term var : Var + def __eq__(self, value: object) -> bool: + if not isinstance(value, Lambda): + return False + + for c in USABLE_VAR_NAMES: + if not contains(self.out, Const(c)): + return self.substitute(Const(c)) == value.substitute((Const(c))) + else: + raise ValueError( + "Ran out of usable variables for comparing lambda structures" + ) + def __init__(self, f : Callable[[Var], Term]) -> None: self.var = Var() self.out = f(self.var) @@ -256,6 +306,10 @@ class Lambda(Term): """ return self.to_str(vars={}) + def rec_iter(self) -> Generator[Term, None, None]: + yield self + yield from self.out.rec_iter() + def replace_content(self, term : Term) -> Lambda: """ Replace the content of the lambda function with the given term. @@ -427,3 +481,15 @@ if __name__ == "__main__": g = L1(lambda b : A2(b, bool_f, bool_t)) print(g) print(g.normalize()) + + print("\nFIND & REPLACE") + print(30 * "=") + + h = A3(Const("f"), Const("x"), Const("y"), Const("z")) + i = A3(Const("f"), Const("x"), Const("b"), Const("z")).replace( + old=Const("b"), new=Const("y"), + ) + + print(h) + print(i) + assert h == i