Source code for kif_lib.model.term.term

# Copyright (C) 2024 IBM Corp.
# SPDX-License-Identifier: Apache-2.0

from __future__ import annotations

import abc
from typing import TYPE_CHECKING

from ... import itertools
from ...typing import (
    Any,
    cast,
    ClassVar,
    Iterator,
    Location,
    Mapping,
    Optional,
    Self,
    Set,
    TypeAlias,
)
from ..kif_object import KIF_Object

if TYPE_CHECKING:               # pragma: no cover
    from .template import Template
    from .variable import Variable


[docs] class Term(KIF_Object): """Abstract base class for terms.""" def __new__(cls, *args, **kwargs) -> Self: has_tpl_or_var_arg = any(map( cls.is_open, itertools.chain(args, kwargs.values()))) if cls._is_proper_subclass_of_closed_term(cls) and has_tpl_or_var_arg: return cast( Self, cls.template_class(*args, **kwargs)) # type:ignore elif (cls._is_proper_subclass_of_template(cls) and not has_tpl_or_var_arg): return cast( Self, cls.object_class(*args, **kwargs)) # type: ignore else: return super().__new__(cls) @classmethod def _is_proper_subclass_of_closed_term(cls, arg: Any) -> bool: return (isinstance(arg, type) and arg is not ClosedTerm and issubclass(arg, ClosedTerm)) @classmethod def _is_proper_subclass_of_template(cls, arg: Any) -> bool: from .template import Template return (isinstance(arg, type) and arg is not Template and issubclass(arg, Template))
[docs] @classmethod def is_closed(cls, arg: Any) -> bool: """Tests whether argument is a closed term. Returns: ``True`` if successful; ``False`` otherwise. """ return isinstance(arg, ClosedTerm)
[docs] @classmethod def is_open(cls, arg: Any) -> bool: """Tests whether argument is an open term. Returns: ``True`` if successful; ``False`` otherwise.""" return isinstance(arg, OpenTerm)
[docs] class ClosedTerm(Term): """Abstract base class for closed (ground) terms.""" #: Template class associated with this closed-term class. template_class: ClassVar[type[Template]] #: Variable class associated with this closed-term class. variable_class: ClassVar[type[Variable]] @classmethod def __init_subclass__(cls, **kwargs: Any) -> None: from .template import Template from .variable import Variable assert not issubclass(cls, OpenTerm) if 'template_class' in kwargs: cls.template_class = kwargs['template_class'] assert issubclass(cls.template_class, Template) cls.template_class.object_class = cls # pyright: ignore if 'variable_class' in kwargs: cls.variable_class = kwargs['variable_class'] assert issubclass(cls.variable_class, Variable) cls.variable_class.object_class = cls # pyright: ignore
#: The type of variable instantiations. Theta: TypeAlias = Mapping['Variable', Optional[Term]]
[docs] class OpenTerm(Term): """Abstract base class for open terms.""" #: Closed-term class associated with this open-term class. object_class: ClassVar[type[ClosedTerm]]
[docs] class InstantiationError(ValueError): """Bad instantiation attempt."""
@property def variables(self) -> Set[Variable]: """The set of variables occurring in open term.""" return self.get_variables()
[docs] def get_variables(self) -> Set[Variable]: """Gets the set of variables occurring in open term. Returns: Set of variables. """ return frozenset(self._iterate_variables())
@abc.abstractmethod def _iterate_variables(self) -> Iterator[Variable]: raise NotImplementedError
[docs] def instantiate( self, theta: Theta, coerce: bool = True ) -> Term | None: """Applies variable instantiation `theta` to open term. Parameters: theta: Variable instantiation. coerce: Whether to consider coercible variables equal. Returns: Term or ``None``. """ self._check_arg_isinstance( theta, Mapping, self.instantiate, 'theta', 1) return self._instantiate( theta, coerce, self.instantiate) if theta else self
@abc.abstractmethod def _instantiate( self, theta: Theta, coerce: bool, function: Location | None = None, name: str | None = None, position: int | None = None ) -> Term | None: raise NotImplementedError