Passed
Push — master ( 2149f7...ead53e )
by Max
44s
created

structured_data.match.MatchDict.__getitem__()   A

Complexity

Conditions 2

Size

Total Lines 5
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 5
nop 2
dl 0
loc 5
rs 10
c 0
b 0
f 0
1
import collections
2
3
from ._attribute_constructor import AttributeConstructor
4
from ._enum_constructor import EnumConstructor
5
from ._match_failure import MatchFailure
6
from ._patterns import DISCARD
7
from ._patterns import AsPattern
8
from ._patterns import Pattern
9
from ._unpack import unpack
10
11
12
def as_pattern_processor(target):
13
    def processor(value):
14
        if target is value:
15
            yield target.match
16
            yield target.matcher
17
        else:
18
            yield value
19
            yield value
20
    return processor
21
22
23
def enum_processor(target):
24
    def processor(value):
25
        if value.__class__ is not target.__class__:
26
            raise MatchFailure
27
        yield from reversed(unpack(value))
28
    return processor
29
30
31
def tuple_processor(target):
32
    def processor(value):
33
        if isinstance(value, target.__class__) and len(target) == len(value):
34
            yield from reversed(value)
35
        else:
36
            raise MatchFailure
37
    return processor
38
39
40
class ProcessorList:
41
42
    def __init__(self, processors=()):
43
        self.processors = list(processors)
44
45
    def get_processor(self, item):
46
        for typ, meta_processor in self.processors:
47
            if isinstance(item, typ):
48
                return meta_processor(item)
49
        return None
50
51
52
PROCESSORS = ProcessorList((
53
    (AsPattern, as_pattern_processor),
54
    (EnumConstructor, enum_processor),
55
    (tuple, tuple_processor),
56
))
57
58
59
def not_in(container, name):
60
    if name in container:
61
        raise ValueError
62
63
64
def names(target):
65
    """Return every name bound by a target."""
66
    name_list = []
67
    names_seen = set()
68
    to_process = [target]
69
    while to_process:
70
        item = to_process.pop()
71
        if isinstance(item, Pattern):
72
            not_in(names_seen, item.name)
73
            names_seen.add(item.name)
74
            name_list.append(item.name)
75
        else:
76
            processor = PROCESSORS.get_processor(item)
77
            if processor:
78
                to_process.extend(processor(item))
79
    return name_list
80
81
82
def _as_name(key):
83
    if isinstance(key, Pattern):
84
        return key.name
85
    return key
86
87
88
def _multi_index(dct, key):
89
    if isinstance(key, tuple):
90
        return tuple(dct[sub_key] for sub_key in key)
91
    if isinstance(key, dict):
92
        return {name: dct[value] for (name, value) in key.items()}
93
    raise KeyError(key)
94
95
96
class MatchDict(collections.abc.MutableMapping):
97
98
    def __init__(self):
99
        self.data = {}
100
101
    def __getitem__(self, key):
102
        key = _as_name(key)
103
        if isinstance(key, str):
104
            return self.data[key]
105
        return _multi_index(self, key)
106
107
    def __setitem__(self, key, value):
108
        key = _as_name(key)
109
        if not isinstance(key, str):
110
            raise TypeError
111
        self.data[key] = value
112
113
    def __delitem__(self, key):
114
        del self.data[_as_name(key)]
115
116
    def __iter__(self):
117
        yield from self.data
118
119
    def __len__(self):
120
        return len(self.data)
121
122
123
def _match_iteration(match_dict, target, value):
124
    if target is DISCARD:
125
        return
126
    if isinstance(target, Pattern):
127
        not_in(match_dict, target.name)
128
        match_dict[target.name] = value
129
        return
130
    processor = PROCESSORS.get_processor(target)
131
    if processor:
132
        yield from zip(processor(target), processor(value))
133
    elif target != value:
134
        raise MatchFailure
135
136
137
def _match(target, value):
138
    match_dict = MatchDict()
139
    to_process = [(target, value)]
140
    while to_process:
141
        to_process.extend(_match_iteration(match_dict, *to_process.pop()))
142
    return match_dict
143
144
145
class ValueMatcher:
146
    """Given a value, attempt to match against a target."""
147
148
    def __init__(self, value):
149
        self.value = value
150
        self.matches = None
151
152
    def match(self, target):
153
        """Match against target, generating a set of bindings."""
154
        try:
155
            self.matches = _match(target, self.value)
156
        except MatchFailure:
157
            self.matches = None
158
        return self.matches is not None
159
160
161
pat = AttributeConstructor(Pattern)
162
163
164
__all__ = ['Pattern', 'ValueMatcher', 'names', 'pat']
165