Lichen

Annotated lib/__builtins__/sequence.py

928:acf2b78e9ee5
2021-06-27 Paul Boddie Added a ValueError-raising convenience function.
paul@6 1
#!/usr/bin/env python
paul@6 2
paul@6 3
"""
paul@6 4
Sequence operations.
paul@6 5
paul@835 6
Copyright (C) 2015, 2016, 2017, 2018 Paul Boddie <paul@boddie.org.uk>
paul@6 7
paul@6 8
This program is free software; you can redistribute it and/or modify it under
paul@6 9
the terms of the GNU General Public License as published by the Free Software
paul@6 10
Foundation; either version 3 of the License, or (at your option) any later
paul@6 11
version.
paul@6 12
paul@6 13
This program is distributed in the hope that it will be useful, but WITHOUT
paul@6 14
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
paul@6 15
FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
paul@6 16
details.
paul@6 17
paul@6 18
You should have received a copy of the GNU General Public License along with
paul@6 19
this program.  If not, see <http://www.gnu.org/licenses/>.
paul@6 20
"""
paul@6 21
paul@459 22
from __builtins__.int import maxint
paul@837 23
from native import isinstance as _isinstance, is_int, list_element
paul@6 24
paul@292 25
class itemaccess:
paul@224 26
paul@292 27
    "An abstract class providing item access."
paul@6 28
paul@287 29
    def _check_index(self, index):
paul@287 30
paul@287 31
        """
paul@287 32
        Check the given absolute 'index', raising an IndexError if out of
paul@287 33
        bounds.
paul@287 34
        """
paul@287 35
paul@402 36
        if index < 0 or index >= self.__len__():
paul@287 37
            raise IndexError(index)
paul@287 38
paul@224 39
    def __getitem__(self, index):
paul@224 40
paul@224 41
        "Return the item or slice specified by 'index'."
paul@224 42
paul@224 43
        # Normalise any integer indexes, converting negative indexes to positive
paul@224 44
        # ones.
paul@6 45
paul@777 46
        if is_int(index):
paul@265 47
            index = _get_absolute_index(index, self.__len__())
paul@224 48
            return self.__get_single_item__(index)
paul@224 49
paul@224 50
        # Handle slices separately.
paul@6 51
paul@356 52
        elif _isinstance(index, slice):
paul@295 53
            return self.__getslice__(index.start, index.end, index.step)
paul@224 54
paul@224 55
        # No other kinds of objects are supported as indexes.
paul@6 56
paul@224 57
        else:
paul@282 58
            raise TypeError()
paul@224 59
paul@227 60
    def __setitem__(self, index, value):
paul@227 61
paul@227 62
        "Set at 'index' the given 'value'."
paul@227 63
paul@227 64
        # Normalise any integer indexes, converting negative indexes to positive
paul@227 65
        # ones.
paul@227 66
paul@777 67
        if is_int(index):
paul@265 68
            index = _get_absolute_index(index, self.__len__())
paul@227 69
            return self.__set_single_item__(index, value)
paul@227 70
paul@227 71
        # Handle slices separately.
paul@227 72
paul@356 73
        elif _isinstance(index, slice):
paul@227 74
            return self.__setslice__(index.start, index.end, value)
paul@227 75
paul@227 76
        # No other kinds of objects are supported as indexes.
paul@227 77
paul@227 78
        else:
paul@282 79
            raise TypeError()
paul@227 80
paul@295 81
    def __getslice__(self, start, end=None, step=1):
paul@6 82
paul@295 83
        """
paul@295 84
        Return a slice of the sequence starting from the 'start' index, ending
paul@295 85
        before the optional 'end' (or at the end of the sequence), and providing
paul@295 86
        items at the frequency given by 'step' (with a default step of 1).
paul@295 87
        """
paul@295 88
paul@295 89
        if step == 0:
paul@295 90
            raise ValueError(step)
paul@224 91
paul@224 92
        length = self.__len__()
paul@6 93
paul@224 94
        # Handle a null start as the first position, otherwise normalising any
paul@224 95
        # start index.
paul@224 96
paul@224 97
        if start is None:
paul@367 98
            if step > 0:
paul@367 99
                start = 0
paul@367 100
            else:
paul@367 101
                start = length - 1
paul@224 102
        else:
paul@265 103
            start = _get_absolute_index(start, length)
paul@6 104
paul@224 105
        # Handle a null end as the first position after the end of the sequence,
paul@224 106
        # otherwise normalising any end index.
paul@224 107
paul@224 108
        if end is None:
paul@367 109
            if step > 0:
paul@367 110
                end = length
paul@367 111
            else:
paul@367 112
                end = -1
paul@224 113
        else:
paul@265 114
            end = _get_absolute_index(end, length)
paul@6 115
paul@384 116
        return self.__get_multiple_items__(start, end, step)
paul@6 117
paul@384 118
    def __get_multiple_items__(self, start, end, step):
paul@384 119
paul@520 120
        """
paul@520 121
        Return items from 'start' until (but excluding) 'end', at 'step'
paul@520 122
        intervals.
paul@520 123
        """
paul@384 124
paul@520 125
        result = []
paul@520 126
paul@520 127
        while step > 0 and start < end or step < 0 and start > end:
paul@520 128
            result.append(self.__get_single_item__(start))
paul@520 129
            start += step
paul@520 130
paul@520 131
        return result
paul@384 132
paul@816 133
    # Methods implemented by subclasses:
paul@351 134
paul@816 135
    # __setslice__(self, start, end, value)
paul@816 136
    # __get_single_item__(self, index)
paul@816 137
    # __set_single_item__(self, index, value)
paul@816 138
    # __len__(self)
paul@351 139
paul@459 140
class hashable(itemaccess):
paul@459 141
paul@459 142
    "An abstract class providing support for hashable sequences."
paul@459 143
paul@459 144
    _p = maxint / 32
paul@459 145
    _a = 31
paul@459 146
paul@459 147
    def _hashvalue(self, fn):
paul@459 148
paul@459 149
        """
paul@459 150
        Return a value for hashing purposes for the sequence using the given
paul@459 151
        'fn' on each item.
paul@459 152
        """
paul@459 153
paul@459 154
        result = 0
paul@459 155
        l = self.__len__()
paul@459 156
        i = 0
paul@459 157
paul@459 158
        while i < l:
paul@459 159
            result = (result * self._a + fn(self.__get_single_item__(i))) % self._p
paul@459 160
            i += 1
paul@459 161
paul@459 162
        return result
paul@459 163
paul@292 164
class sequence(itemaccess):
paul@292 165
paul@292 166
    "A common base class for sequence types."
paul@292 167
paul@292 168
    def _str(self, opening, closing):
paul@292 169
paul@292 170
        "Serialise this object with the given 'opening' and 'closing' strings."
paul@292 171
paul@292 172
        b = buffer()
paul@292 173
        i = 0
paul@292 174
        l = self.__len__()
paul@292 175
        first = True
paul@292 176
paul@292 177
        b.append(opening)
paul@292 178
        while i < l:
paul@292 179
            if first:
paul@292 180
                first = False
paul@292 181
            else:
paul@292 182
                b.append(", ")
paul@292 183
            b.append(repr(self.__get_single_item__(i)))
paul@292 184
            i += 1
paul@292 185
        b.append(closing)
paul@292 186
paul@292 187
        return str(b)
paul@292 188
paul@292 189
    def __contains__(self, value):
paul@292 190
paul@292 191
        "Return whether the list contains 'value'."
paul@292 192
paul@292 193
        # Perform a linear search of the sequence contents.
paul@292 194
paul@292 195
        for v in self:
paul@292 196
paul@292 197
            # Return True if the current value is equal to the specified one.
paul@292 198
            # Note that this is not an identity test, but an equality test.
paul@292 199
paul@292 200
            if v == value:
paul@292 201
                return True
paul@292 202
paul@292 203
        return False
paul@292 204
paul@292 205
    def index(self, value):
paul@292 206
paul@292 207
        "Return the index of 'value' or raise ValueError."
paul@292 208
paul@292 209
        i = 0
paul@402 210
        l = self.__len__()
paul@292 211
        while i < l:
paul@292 212
            if self[i] == value:
paul@292 213
                return i
paul@292 214
            i += 1
paul@292 215
paul@292 216
        raise ValueError(value)
paul@292 217
paul@287 218
    def __eq__(self, other):
paul@287 219
paul@287 220
        "Return whether this sequence is equal to 'other'."
paul@287 221
paul@459 222
        try:
paul@459 223
            return self._eq(other)
paul@459 224
        except TypeError:
paul@459 225
            return NotImplemented
paul@459 226
paul@459 227
    def _eq(self, other):
paul@459 228
paul@459 229
        """
paul@459 230
        Return whether this sequence is equal to 'other' sequence. Note that
paul@459 231
        this method will raise a TypeError if 'other' is not a sequence.
paul@459 232
        """
paul@459 233
paul@287 234
        # Sequences must have equal lengths to be equal.
paul@287 235
paul@287 236
        n = self.__len__()
paul@402 237
        if other.__len__() != n:
paul@287 238
            return False
paul@265 239
paul@287 240
        i = 0
paul@287 241
        while i < n:
paul@287 242
            if self.__getitem__(i) != other.__getitem__(i):
paul@287 243
                return False
paul@287 244
            i += 1
paul@265 245
paul@287 246
        return True
paul@287 247
paul@287 248
    def __ne__(self, other):
paul@287 249
paul@287 250
        "Return whether this sequence is not equal to 'other'."
paul@287 251
paul@287 252
        return not self.__eq__(other)
paul@265 253
paul@816 254
    # Methods implemented by subclasses:
paul@351 255
paul@816 256
    # __iter__(self)
paul@351 257
paul@837 258
class unpackable(sequence):
paul@837 259
paul@837 260
    "Class for list and tuple unpacking."
paul@837 261
paul@837 262
    def __get_single_item_unchecked__(self, index):
paul@837 263
paul@837 264
        """
paul@846 265
        This method is provided by a privileged attribute recorded in the common
paul@846 266
        module in the toolchain. Normal programs should not be able to access
paul@846 267
        it.
paul@837 268
        """
paul@837 269
paul@846 270
        # NOTE: This uses implementation-specific access.
paul@846 271
paul@837 272
        return list_element(self.__data__, index)
paul@837 273
paul@6 274
def _get_absolute_index(index, length):
paul@6 275
paul@6 276
    """
paul@6 277
    Return the absolute index for 'index' given a collection having the
paul@6 278
    specified 'length'.
paul@6 279
    """
paul@6 280
paul@6 281
    if index < 0:
paul@6 282
        return length + index
paul@6 283
    else:
paul@6 284
        return index
paul@6 285
paul@835 286
def _test_length(seq, length):
paul@835 287
paul@835 288
    "Helper function for asserting that 'seq' is of the given 'length'."
paul@835 289
paul@835 290
    if len(seq) != length:
paul@835 291
        raise ValueError, length
paul@835 292
paul@6 293
# vim: tabstop=4 expandtab shiftwidth=4