Source code for parce.target

# -*- coding: utf-8 -*-
#
# This file is part of the parce Python package.
#
# Copyright © 2019-2020 by Wilbert Berendsen <info@wilbertberendsen.nl>
#
# This module is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This module is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <https://www.gnu.org/licenses/>.


"""
A Target describes where to go.

In a lexicon rule, you can specify destinations using integers or lexicons.
A Target generalizes this in two values/attributes: ``pop`` and ``push``.

``pop`` is zero or a negative integer, determining how many lexicons to
pop off the current state/context.

``push`` is a list of zero or more lexicons, determining which lexicons to
add to the current state.

You can sort of "add" targets using a TargetFactory, which can create single
Target objects combining multiple targets in once.

"""

import collections
import itertools


#: Used by the :mod:`.lexer` to describe lexicon changes.
Target = collections.namedtuple("Target", "pop push")
Target.pop.__doc__ = "A negative integer or 0, describing how many lexicons to leave."
Target.push.__doc__ = "A list of zero or more Lexicons to enter."


[docs]class TargetFactory: """Maintains a current target and allows you to store changes. Call get() to get the final Target, and to reset the factory's internal state. """ __slots__ = '_pop', '_push' def __init__(self): self._pop = 0 self._push = []
[docs] def add(self, target): """Add a Target to this factory.""" if target: if target.pop == 0: self._push.extend(target.push) elif -target.pop <= len(self._push): self._push[target.pop:] = target.push else: self._pop += len(self._push) + target.pop self._push[:] = target.push
[docs] def get(self): """Return the current :class:`Target`. Returns None if there is nothing to pop and push. After this the current target is reset. """ if self._pop or self._push: t = Target(self._pop, self._push) self.__init__() return t
[docs] def push(self, *lexicons): """Enter one or more lexicon(s).""" self._push.extend(lexicons)
[docs] def pop(self, pop=-1): """Pop off one (or more) lexicon(s).""" if pop: if -pop <= len(self._push): del self._push[pop:] else: self._pop += len(self._push) + pop self._push.clear()
[docs] @classmethod def make(cls, lexicon, rule): """Create a Target of a rule.""" if rule: f = cls() for t in rule: if isinstance(t, int): if t < 0: f.pop(t) elif t: f.push(*itertools.repeat(lexicon, t)) else: f.push(t) return f.get()