Skip to content

yex.parse.Pushback

yex.parse.Pushback() #

A pushback stores items from a tokeniser which have been pushed back.

When you're reading from a tokeniser, you often read more than you actually wanted. So you can push things back into a pushback. Every parser has exactly one Pushback, which lives at e.pushback. Every tokeniser keeps track of a pushback, generally that of their parser, and while that pushback has things in it, the tokeniser will return those instead of its own data. Multiple tokenisers can point at the same pushback, and they usually do.

Pushbacks also keep count of group depth.

Attributes:

Name Type Description
group_depth int

the level of nesting of groups.

Source code in yex/parse/pushback.py
27
28
29
30
31
32
33
34
def __init__(self):
    self.items: List[Any] = []
    """
    The items stored. They will be returned last in, first out.
    The next item to be returned is the last one in the list.
    """

    self._group_depth = 0

items = [] instance-attribute #

The items stored. They will be returned last in, first out. The next item to be returned is the last one in the list.

__len__() #

The number of items on the pushback.

Source code in yex/parse/pushback.py
191
192
193
194
195
def __len__(self) -> int:
    """
    The number of items on the pushback.
    """
    return len(self.items)

adjust_group_depth(c, why='', reverse=False) #

Adjusts the group_depth parameter according to incoming or outgoing items.

When a Tokeniser or a Pushback produces any item, we want to keep track of group nesting. This is how we do it.

Pushbacks call this on push, but not pop. See the docstrings of those methods for the reasons.

Parameters:

Name Type Description Default
c typing.Any

an item which is coming or going. If the item is a Token, we adjust _group_depth for BeginningGroup and EndGroup. Otherwise, nothing happens.

required
why str

a message for logging

''
reverse bool

True if the item is being pushed back; False if it's being produced or popped.

False
Source code in yex/parse/pushback.py
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
def adjust_group_depth(self,
                       c: Any,
                       why:str = '',
                       reverse:bool=False,
                       ):
    """
    Adjusts the group_depth parameter according to incoming or outgoing items.

    When a Tokeniser or a Pushback produces any item, we want to
    keep track of group nesting. This is how we do it.

    Pushbacks call this on push, but not pop. See the docstrings of
    those methods for the reasons.

    Args:
        c: an item which is coming or going. If the item
            is a Token, we adjust _group_depth for BeginningGroup and
            EndGroup. Otherwise, nothing happens.
        why: a message for logging
        reverse: True if the item is being pushed back;
            False if it's being produced or popped.
    """

    if not isinstance(c, yex.parse.Token):
        return

    cat = c.category

    if cat==yex.parse.Token.BEGINNING_GROUP:
        delta = 1
    elif cat==yex.parse.Token.END_GROUP:
        delta = -1
    else:
        return

    if reverse:
        delta *= -1

    self._group_depth += delta

    where = f'{delta}'
    if delta>0:
        where = f'+{where}'

    logger.debug("%s: _group_depth %s %s; now %s",
            self, where, why, self._group_depth)

check_empty() #

Does some final checks.

You don't need to call this.

Raises:

Type Description
ValueError

if the checks fail.

Source code in yex/parse/pushback.py
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
def check_empty(self) -> None:
    """
    Does some final checks.

    You don't need to call this.

    Raises:
        ValueError: if the checks fail.
    """
    if self.items:
        raise ValueError(
                f'{self}: there are items still on the stack: '
                f'{self.items}'
                )

    if self._group_depth!=0:
        raise ValueError(
                f'{self}: group depth should be 0 on close, '
                f'and not {self._group_depth}'
                )

clear() #

Clears the pushback of items.

Source code in yex/parse/pushback.py
180
181
182
183
184
185
186
187
188
189
def clear(self) -> None:
    """
    Clears the pushback of items.
    """
    if self.items:
        logger.debug("%s: clearing; dropping %s", self, self.items)
    else:
        logger.debug("%s: clearing", self)

    self.items = []

pop() #

Returns the next item.

We don't adjust group_depth based on the item, because this method is usually used by Tokenisers which want to control the group depth themselves.

Returns:

Type Description
typing.Any

the next item (like I just told you). If there are

typing.Any

no more items, returns None. It can't return None

typing.Any

in any other circumstance.

Source code in yex/parse/pushback.py
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
def pop(self) -> Any:
    """
    Returns the next item.

    We don't adjust `group_depth` based on the item, because this
    method is usually used by Tokenisers which want to control the
    group depth themselves.

    Returns:
        the next item (like I just told you). If there are
        no more items, returns None. It can't return None
        in any other circumstance.
    """

    if self.items:
        result = self.items.pop()
        logger.debug("%s: popped: %s", self, repr(result))

        return result

    return None

push(thing) #

Pushes back a token or a character (or anything else).

Tokenisers which point to us will see the new thing first, before any of its regular input.

If the thing is a character, it will be parsed as usual by the tokeniser; if it's anything else, it will simply be yielded.

If you supply a list (not just any iterable!) the contents of the list will be pushed as if you'd pushed them individually. Multi-character strings work similarly.

Pushing None does nothing.

This method works even at end of file.

Parameters:

Name Type Description Default
thing typing.Any

what to push.

required
Source code in yex/parse/pushback.py
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
def push(self, thing: Any) -> None:
    """
    Pushes back a token or a character (or anything else).

    Tokenisers which point to us will see the new thing
    first, before any of its regular input.

    If the thing is a character, it will be parsed as usual
    by the tokeniser; if it's anything else, it will simply be yielded.

    If you supply a list (not just any iterable!) the
    contents of the list will be pushed as if you'd
    pushed them individually. Multi-character strings
    work similarly.

    Pushing `None` does nothing.

    This method works even at end of file.

    Args:
        thing: what to push.
    """
    if thing is None:
        logger.debug("%s: not pushing back eof",
                self)
        return

    if not isinstance(thing, (list, str)):
        thing = [thing]
    elif isinstance(thing, list):
        thing = [x for x in thing if x is not None]

    if not thing:
        logger.debug("%s: nothing to push", self)
        return

    for t in thing:
        self.adjust_group_depth(t,
                reverse=True,
                why='on push',
                )

    self.items.extend(reversed([c for c in thing]))

    logger.debug("%s: pushed: %s",
            self, thing)