Source code for fosf.syntax.terms

#!/usr/bin/env python3

from __future__ import annotations

from collections import Counter, defaultdict, deque
from typing import TYPE_CHECKING

from fosf.syntax.base import FrozenDisjunctiveSort, URIFeature, URISort
from fosf.syntax.constraints import DisjunctiveClause

if TYPE_CHECKING:
    from fosf.syntax.constraints import RootedClause, RootedSolvedClause

from fosf.syntax import DisjunctiveSort, Feature, Sort, Tag


[docs] class Term: """ Represent an OSF term X:s(f1 -> t1, ..., fn -> tn). """ def __init__( self, X: Tag, s: Sort | None = None, subterms: dict[Feature, list[Term]] | None = None, ): """ Parameters ---------- X : Tag The root tag of the term. s : Sort | None The root sort of the term. If None, when the OSF term is processes, it is assumed to be the top sort in a given Sort taxonomy. subterms: dict[Feature, list[Term]] | None The subterms of the OSF term. In a non-normal OSF term, the same feature may point to different subterms. Attributes ---------- X : Tag The root tag of the term. s : Sort The root sort of the term. subterms : dict[Feature, list[Term]] Possibly empty dict mapping each feature to a list of subterms. """ self.X = X self.s = s self.subterms = defaultdict(list) if subterms is None else subterms
[docs] def dfs(self): """ Generate subterms depth-first. """ stack = [self] while stack: yield (term := stack.pop()) for _, subterm in term.iter_subterms(): stack.append(subterm)
[docs] def bfs(self): """ Generate subterms breadth-first. """ queue = deque([self]) while queue: yield (term := queue.popleft()) for _, subterm in term.iter_subterms(): queue.append(subterm)
[docs] def to_clause(self) -> RootedClause: """ Transform the OSF term into an equivalent rooted clause. Returns ------- RootedClause The rooted clause corresponding to the OSF term. """ from fosf.syntax.constraints import RootedClause clause = RootedClause(self.X) for c in self.generate_constraints(): clause.add(c) return clause
[docs] def generate_constraints(self): """ Generate the OSF constraints expressed by the OSF term. """ from fosf.syntax.constraints import FeatureConstraint, SortConstraint if self.s is not None: yield SortConstraint(self.X, self.s) for term in self.dfs(): for f, subterm in term.iter_subterms(): yield FeatureConstraint(term.X, f, subterm.X) if subterm.s is not None: yield SortConstraint(subterm.X, subterm.s)
[docs] def tags(self) -> set[Tag]: """ Return the set of :class:`Tag`'s appearing in the OSF term. """ out = set() for term in self.dfs(): out.add(term.X) return out
[docs] def tag_counts(self) -> Counter: """ Return the counts of :class:`Tag`'s appearing in the OSF term. """ out = Counter() for term in self.dfs(): out[term.X] += 1 return out
[docs] def sorts(self) -> set[Sort]: """ Return the set of :class:`Sort`'s appearing in the OSF term. """ out = set() for term in self.dfs(): if term.s is not None: if isinstance(term.s, DisjunctiveSort): out.add(term.s.freeze()) else: out.add(term.s) return out
[docs] def prettify(self, spaces=0, feature="", namespace_manager=None): "Prettify the OSF Term for printing." out = " " * spaces if feature: if isinstance(feature, URIFeature) and namespace_manager: feature = feature.n3(namespace_manager) out += f"{feature} -> " out += f"{self.X}" if self.s is not None: s = self.s if isinstance(s, URISort) and namespace_manager: s = s.n3(namespace_manager) if isinstance(s, (DisjunctiveSort, FrozenDisjunctiveSort)) and namespace_manager: s = "{ " + ", ".join({ds.n3(namespace_manager) for ds in s}) + " }" out += f" : {s}" if self.subterms: out += "(" out += "\n" for feature, term in self.iter_subterms(): out += term.prettify(spaces + 4, feature, namespace_manager) if self.subterms: out += " " * spaces + ")\n" return out
[docs] def pretty_print(self, spaces=0, feature="", namespace_manager=None): "Pretty-print the OSF Term." print(self.prettify(spaces, feature, namespace_manager))
def __repr__(self): out = f"{self.__class__.__name__}(X={self.X!r}" if self.s is not None: out += f", s={self.s!r}" if self.subterms: out += f", subterms={dict(self.subterms)!r}" out += ")" return out def __str__(self): out = f"{self.X}" if self.s is not None: out += f" : {self.s}" if self.subterms: pairs = [f"{f} -> {str(subterm)}" for f, subterm in self.iter_subterms()] out += f"({', '.join(pairs)})" return out def __getitem__(self, key: Feature): return self.subterms.get(key, None) def __eq__(self, other: Term): return ( self.X == other.X and self.s == other.s and self.subterms == other.subterms )
[docs] def iter_subterms(self): """ Generate (feature, subterm) pairs. """ for feature, subterms in self.subterms.items(): for subterm in subterms: yield (feature, subterm)
[docs] def tag_to_sort(self) -> dict[Tag, set[Sort]]: """ Return a mapping from each :class:`Tag` to its set of :class:`Sort`'s. """ out = defaultdict(set) for term in self.dfs(): if term.s: out[term.X].add(term.s) return out
def to_sparql( self, vars=None, prefixes=None, base=None, include_prefixes: bool = True, simplify=False, ): from fosf.utils.sparql import term_to_query term = self if simplify: if not isinstance(term, (NormalTerm, DisjunctiveTerm)): print( "Skipping simplification step.", "Only NormalTerms and DisjunctiveTerm can be simplified.", f"Found {type(term)}.", ) else: from fosf.utils.terms import simplify_disjunctive_term term = simplify_disjunctive_term(term) return term_to_query(term, vars, prefixes, base, include_prefixes)
[docs] class NormalTerm(Term): """ Represent an OSF term in normal form. """ def __init__( self, X: Tag, s: Sort | None = None, subterms: dict[Feature, NormalTerm] | None = None, ): """ Parameters ---------- X : Tag The root tag. s : Sort | None The root sort. If None, when the OSF term is processes, it is assumed to be the top sort in a given Sort taxonomy. subterms: dict[Feature, NormalTerm] | None The subterms of the OSF term. In a normal OSF term, each feature may point to at most one (normal) subterm. Attributes ---------- X : Tag The root tag of the term. s : Sort The root sort of the term. subterms : dict[Feature, Term] Possibly empty dict mapping each feature to a unique subterm. """ self.X = X self.s = s self.subterms = {} if subterms is None else subterms
[docs] def to_clause(self) -> RootedSolvedClause: """ Transform the OSF term into an equivalent rooted solved clause. Returns ------- RootedSolvedClause The rooted solved clause corresponding to the OSF term. """ from fosf.syntax.constraints import ( FeatureConstraint, RootedSolvedClause, SortConstraint, ) clause = RootedSolvedClause(self.X) for term in self.dfs(): if term.s is not None: clause.add(SortConstraint(term.X, term.s)) for f, subterm in term.subterms.items(): clause.add(FeatureConstraint(term.X, f, subterm.X)) return clause
[docs] def equivalent_to(self, other: NormalTerm) -> bool: """ Return whether this Term is equivalent to another normal term. """ this_clause = self.to_clause() other_clause = other.to_clause() return this_clause.equivalent_to(other_clause)
[docs] def iter_subterms(self): """ Generate (feature, subterm) pairs. """ yield from self.subterms.items()
[docs] def tag_to_sort(self) -> dict[Tag, Sort]: """ Return a mapping from each :class:`Tag` to its :class:`Sort`. """ out = dict() for term in self.dfs(): if term.s: out[term.X] = term.s return out
[docs] class DisjunctiveTerm(Term): """ Represent a disjunctive OSF term. """ def __init__(self, X: Tag, terms: list[Term]): self.X = X self.terms = terms def __repr__(self): out = f"{self.__class__.__name__}(" out += f"X={self.X!r}, " out += f"terms={self.terms!r}" out += ")" return out def __str__(self): terms = " ; ".join(str(term) for term in self.terms) if self.X is None: out = f"{{ {terms} }}" else: var = f"{self.X}" out = f"{var} : {{ {terms} }}" return out
[docs] def iter_subterms(self): """ Generate (feature, subterm) pairs. """ for term in self.terms: yield from term.iter_subterms()
[docs] def prettify(self, spaces=0, feature="", namespace_manager=None): out = " " * spaces if feature: if isinstance(feature, URIFeature) and namespace_manager is not None: feature = feature.n3(namespace_manager) out += f"{feature} -> " if self.X is not None: out += f"{self.X} : {{" else: out += "{" out += "\n" for i, term in enumerate(self.terms): if i > 0: out += " " * spaces + " ;\n" out += term.prettify(spaces + 4, namespace_manager=namespace_manager) + "\n" out += " " * spaces + "}\n" return out
[docs] def pretty_print(self, spaces=0, feature="", namespace_manager=None): "Pretty-print the OSF Term." print(self.prettify(spaces, feature, namespace_manager))
def __iter__(self): return iter(self.terms)
[docs] def tags(self): out = {self.X} for term in self.terms: out.update(term.tags()) return out
[docs] def tag_counts(self): """ Return the counts of :class:`Tag`'s appearing in the OSF term. """ out = Counter([self.X]) for term in self.terms: out.update(term.tag_counts()) return out
[docs] def to_clause(self): clauses = [term.to_clause() for term in self.terms] return DisjunctiveClause(clauses)
[docs] def generate_constraints(self): msg = "The method 'generate_constraints' is currently not implemented for DisjunctiveTerms" return NotImplementedError(msg)
[docs] def sorts(self): msg = "The method 'sorts' is currently not implemented for DisjunctiveTerms" return NotImplementedError(msg)
def __getitem__(self, key: Feature): return NotImplementedError() def __eq__(self, other: Term): return NotImplementedError()
[docs] def tag_to_sort(self): return NotImplementedError()