from __future__ import annotations
from abc import ABC
from math import inf
from typing import Collection, Generator, TypeVar
from pathex.adts.collection_wrapper import CollectionWrapper
from pathex.generation.defaults import COMPLETE_WORDS, LANGUAGE_TYPE, WORD_TYPE
from pathex.machines.decomposers.decomposer import Decomposer
__doc__ = f"""
Expressions abstract base class
===============================
:Module: ``{__name__}``
.. sectionauthor:: Ernesto Soto Gómez <esto.yinyang@gmail.com>
.. codeauthor:: Ernesto Soto Gómez <esto.yinyang@gmail.com>
---------------------------------------------------------------
"""
# TODO: __str__ y __repr__ de todas las expresiones
# TODO: Refactory operators overloading
__all__ = ['Expression']
ellipsis = type(...)
T = TypeVar('T', bound=CollectionWrapper)
E = TypeVar('E', bound=CollectionWrapper)
def _get_non_optional_repetition_or_binary_op(binary_op):
def f(self, v):
import pathex
if v in (inf, Ellipsis):
return pathex.__dict__[f'{binary_op}Repetition'](self, 1, inf)
elif isinstance(v, int):
return pathex.__dict__[f'{binary_op}Repetition'](self, v, v)
else:
return pathex.__dict__[binary_op](self, v)
return f
def _get_optional_repetition_or_binary_op(binary_op):
def f(self, v):
import pathex
if isinstance(v, list):
return pathex.__dict__[f'{binary_op}Repetition'](self, *v)
elif v in (inf, Ellipsis) or isinstance(v, int):
return pathex.__dict__[f'{binary_op}Repetition'](self, 0, v)
else:
from pathex import optional
return pathex.__dict__[binary_op](optional(self), v)
return f
[docs]class Expression(ABC):
"""Expressions abstract base class.
In |pe| objects of any other kind different from :class:`Expression` are interpreted as an identity terminal expression. :class:`Expression` is meant to grouping those kind of expressions that has a special meaning and to provide general methods and Python operator overloading.
"""
[docs] def get_generator(self, decomposer: Decomposer | None = None):
"""get_generator(machine: pathex.generation.machines.machine.Machine | None = None) -> pathex.generation.words_generator.WordsGenerator
Gives a :class:`~.WordsGenerator` with default values for the actual expression.
This method is meant as a handy shortcut for :class:`WordsGenerator(expression, machine) <.WordsGenerator>`.
``machine`` is the machine to be used to interpret the expression. If it is :obj:`None` then an instance of :class:`~.ExtendedMachineCompalphabet` will be used. Defaults to :obj:`None`.
"""
if decomposer is None:
from pathex.machines.decomposers.extended_decomposer_compalphabet import \
ExtendedDecomposerCompalphabet
decomposer = ExtendedDecomposerCompalphabet()
from pathex.generation.words_generator import WordsGenerator
return WordsGenerator(self, decomposer)
[docs] def get_eager_generator(self, decomposer: Decomposer | None = None,
complete_words: bool = COMPLETE_WORDS) -> Generator[Collection, None, None]:
if decomposer is None:
from pathex.machines.decomposers.extended_decomposer_compalphabet import \
ExtendedDecomposerCompalphabet
decomposer = ExtendedDecomposerCompalphabet()
from pathex.generation.eager import words_generator
return words_generator(self, decomposer, complete_words)
get_eager_generator.__doc__ = f"""
get_eager_generator(self, machine: pathex.generation.machines.machine.Machine | None = None, word_type: type[T] = {WORD_TYPE.__name__}, complete_words: bool = {COMPLETE_WORDS}) -> typing.Generator[T, None, None]
Gives a generator of :class:`~.CollectionWrapper` object that represent the words generated by the expression.
This method is meant as a handy shortcut for :func:`words_generator(self, machine, word_type, complete_words) <.words_generator>`.
``machine`` is The machine to be used to interpret the expression. If it is :obj:`None` then an instance of :class:`~.ExtendedMachineCompalphabet` will be used. Defaults to :obj:`None`.
``word_type`` is a :class:`~.CollectionWrapper` subtype that will be the type of collection to be used to represent words. Defaults to :class:`{WORD_TYPE.__name__} <.CollectionWrapper>`.
``complete_words`` is a flag indicating if only complete words are to be given. Defaults to :obj:`{COMPLETE_WORDS}`.
"""
[docs] def get_language(self, language_type: type[T] = LANGUAGE_TYPE,
decomposer: Decomposer | None = None,
complete_words: bool = COMPLETE_WORDS) -> T:
if decomposer is None:
from pathex.machines.decomposers.extended_decomposer_compalphabet import \
ExtendedDecomposerCompalphabet
decomposer = ExtendedDecomposerCompalphabet()
from pathex.generation.eager import words_generator
language = language_type()
for w in words_generator(self, decomposer, complete_words):
language.put(w)
return language
get_language.__doc__ = f"""
get_language(language_type: type[T] = {LANGUAGE_TYPE.__name__}, machine: pathex.generation.machines.machine.Machine | None = None, word_type: type[pathex.adts.collection_wrapper.CollectionWrapper] = {WORD_TYPE.__name__}, complete_words: bool = {COMPLETE_WORDS}) -> T
Gives a :class:`~.CollectionWrapper` object that contains :class:`~.CollectionWrapper` objects that represent the words generated by the expression.
``language_type`` is a subtype of :class:`~.CollectionWrapper` that will be used as the type of the object to be returned. Defaults to :class:`{LANGUAGE_TYPE.__name__} <.CollectionWrapper>`.
``machine`` is the machine to be used to interpret the expression. If it is :obj:`None` then an instance of :class:`~.ExtendedMachineCompalphabet` will be used. Defaults to :obj:`None`.
``word_type`` is a :class:`~.CollectionWrapper` subtype that will be the type of collection to be used to represent words. Defaults to :class:`{WORD_TYPE.__name__} <.CollectionWrapper>`.
``complete_words`` is a flag indicating if only complete words are to be given. Defaults to :obj:`{COMPLETE_WORDS}`.
"""
# self | other
def __or__(self, other):
from pathex import Union
return Union(self, other)
# other | self
def __ror__(self, other):
from pathex import Union
return Union(other, self)
# self & other
def __and__(self, other):
from pathex import Intersection
return Intersection(self, other)
# other & self
def __rand__(self, other: object):
from pathex import Intersection
return Intersection(other, self)
# self - other
def __sub__(self, other):
from pathex import Difference
return Difference(self, other)
# other - self
def __rsub__(self, other):
from pathex import Difference
return Difference(other, self)
# self+...
# self+inf
# self+number
# self + other
__add__ = _get_non_optional_repetition_or_binary_op('Concatenation')
# other + self
def __radd__(self, v):
from pathex import Concatenation
return Concatenation(v, self)
# +self
def __pos__(self):
from pathex import ConcatenationRepetition
return ConcatenationRepetition(self, 1, inf)
# self*[lb,ub]
# self*number
# self * other
__mul__ = _get_optional_repetition_or_binary_op('Concatenation')
# other * self
def __rmul__(self, v):
from pathex import Concatenation, optional
return Concatenation(optional(v), self)
# self//...
# self//inf
# self//number
# self // other
__floordiv__ = _get_non_optional_repetition_or_binary_op('Shuffle')
# other // self
def __rfloordiv__(self, v):
from pathex import Shuffle
return Shuffle(v, self)
# self%[lb, ub]
# self%number
# self % other
__mod__ = _get_optional_repetition_or_binary_op('Shuffle')
# other % self
def __rmod__(self, v):
from pathex import Shuffle, optional
return Shuffle(optional(v), self)