Passed
Push — master ( c07ac7...918a2a )
by Max
55s
created

DestructurerList.names()   B

Complexity

Conditions 6

Size

Total Lines 23
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 22
nop 2
dl 0
loc 23
rs 8.4186
c 0
b 0
f 0
1
from ._adt_constructor import ADTConstructor
2
from ._match_failure import MatchFailure
3
from ._not_in import not_in
4
from ._patterns import AsPattern
5
from ._patterns import AttrPattern
6
from ._patterns import Bind
7
from ._patterns import DictPattern
8
from ._patterns import Pattern
9
from ._unpack import unpack
10
11
12
class Destructurer:
13
    def __init__(self, target):
14
        self.target = target
15
16
    def __call__(self, value):
17
        return self.destructure(value)
18
19
    def destructure(self, value):
20
        raise NotImplementedError
21
22
    type = None
23
24
25
class AsPatternDestructurer(Destructurer):
26
    def destructure(self, value):
27
        if isinstance(value, AsPattern):
28
            if value is self.target:
29
                return reversed(value)
30
            return (value.match, value)
31
        return (value, value)
32
33
    type = AsPattern
34
35
36
class ADTDestructurer(Destructurer):
37
    def destructure(self, value):
38
        if value.__class__ is not self.target.__class__:
39
            raise MatchFailure
40
        return reversed(unpack(value))
41
42
    type = ADTConstructor
43
44
45
def guarded_getattr(value, target_key):
46
    try:
47
        return getattr(value, target_key)
48
    except AttributeError:
49
        raise MatchFailure
50
51
52
def value_cant_be_smaller(target_match_dict, value_match_dict):
53
    if len(value_match_dict) < len(target_match_dict):
54
        raise MatchFailure
55
56
57
def key_destructure(value, match_dict, getter):
58
    return [getter(value, target_key) for (target_key, _) in reversed(match_dict)]
59
60
61
def same_type_destructure(target_match_dict, value_match_dict):
62
    for (target_key, _), (value_key, value_value) in zip(
63
        target_match_dict, value_match_dict
64
    ):
65
        if target_key != value_key:
66
            raise MatchFailure
67
        yield value_value
68
69
70
class AttrPatternDestructurer(Destructurer):
71
    def destructure(self, value):
72
        if isinstance(value, AttrPattern):
73
            value_cant_be_smaller(self.target.match_dict, value.match_dict)
74
            return reversed(
75
                list(same_type_destructure(self.target.match_dict, value.match_dict))
76
            )
77
        return key_destructure(value, self.target.match_dict, guarded_getattr)
78
79
    type = AttrPattern
80
81
82
def guarded_getitem(value, target_key):
83
    try:
84
        return value[target_key]
85
    except KeyError:
86
        raise MatchFailure
87
88
89
def exhaustive_length_must_match(target, value_match_dict):
90
    if target.exhaustive and len(value_match_dict) != len(target.match_dict):
91
        raise MatchFailure
92
93
94
class DictPatternDestructurer(Destructurer):
95
    def destructure(self, value):
96
        if isinstance(value, DictPattern):
97
            value_cant_be_smaller(self.target.match_dict, value.match_dict)
98
            exhaustive_length_must_match(self.target, value.match_dict)
99
            return reversed(
100
                list(same_type_destructure(self.target.match_dict, value.match_dict))
101
            )
102
        exhaustive_length_must_match(self.target, value)
103
        return key_destructure(value, self.target.match_dict, guarded_getitem)
104
105
    type = DictPattern
106
107
108
class TupleDestructurer(Destructurer):
109
    def destructure(self, value):
110
        if isinstance(value, self.target.__class__) and len(self.target) == len(value):
111
            return reversed(value)
112
        raise MatchFailure
113
114
    type = tuple
115
116
117
class DestructurerList(tuple):
118
119
    __slots__ = ()
120
121
    def __new__(cls, *destructurers):
122
        return super().__new__(cls, destructurers)
123
124
    def get_destructurer(self, item):
125
        for destructurer in self:
126
            if isinstance(item, destructurer.type):
127
                return destructurer(item)
128
        return None
129
130
    @classmethod
131
    def custom(cls, *destructurers):
132
        return cls(
133
            *destructurers,
134
            AsPatternDestructurer,
135
            ADTDestructurer,
136
            AttrPatternDestructurer,
137
            DictPatternDestructurer,
138
            TupleDestructurer
139
        )
140
141
    def names(self, target):
142
        name_list = []
143
        names_seen = set()
144
        extra_names = ()
145
        if isinstance(target, Bind):
146
            extra_names = target.bindings
147
            target = target.structure
148
        to_process = [target]
149
        while to_process:
150
            item = to_process.pop()
151
            if isinstance(item, Pattern):
152
                not_in(names_seen, item.name)
153
                names_seen.add(item.name)
154
                name_list.append(item.name)
155
            else:
156
                destructurer = self.get_destructurer(item)
157
                if destructurer:
158
                    to_process.extend(destructurer(item))
159
        for (name, _) in extra_names:
160
            not_in(names_seen, name)
161
            names_seen.add(name)
162
            name_list.append(name)
163
        return name_list
164
165
166
DESTRUCTURERS = DestructurerList.custom()
167