Passed
Push — master ( ead53e...a631b0 )
by Max
42s
created

structured_data.match.tuple_processor()   A

Complexity

Conditions 3

Size

Total Lines 7
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 6
nop 1
dl 0
loc 7
rs 10
c 0
b 0
f 0
1
import collections
2
3
from ._attribute_constructor import AttributeConstructor
4
from ._match_failure import MatchFailure
5
from ._patterns import DISCARD
6
from ._patterns import Pattern
7
from ._processors import PROCESSORS
8
9
10
def not_in(container, name):
11
    if name in container:
12
        raise ValueError
13
14
15
def names(target):
16
    """Return every name bound by a target."""
17
    name_list = []
18
    names_seen = set()
19
    to_process = [target]
20
    while to_process:
21
        item = to_process.pop()
22
        if isinstance(item, Pattern):
23
            not_in(names_seen, item.name)
24
            names_seen.add(item.name)
25
            name_list.append(item.name)
26
        else:
27
            processor = PROCESSORS.get_processor(item)
28
            if processor:
29
                to_process.extend(processor(item))
30
    return name_list
31
32
33
def _as_name(key):
34
    if isinstance(key, Pattern):
35
        return key.name
36
    return key
37
38
39
def _multi_index(dct, key):
40
    if isinstance(key, tuple):
41
        return tuple(dct[sub_key] for sub_key in key)
42
    if isinstance(key, dict):
43
        return {name: dct[value] for (name, value) in key.items()}
44
    raise KeyError(key)
45
46
47
class MatchDict(collections.abc.MutableMapping):
48
49
    def __init__(self):
50
        self.data = {}
51
52
    def __getitem__(self, key):
53
        key = _as_name(key)
54
        if isinstance(key, str):
55
            return self.data[key]
56
        return _multi_index(self, key)
57
58
    def __setitem__(self, key, value):
59
        key = _as_name(key)
60
        if not isinstance(key, str):
61
            raise TypeError
62
        self.data[key] = value
63
64
    def __delitem__(self, key):
65
        del self.data[_as_name(key)]
66
67
    def __iter__(self):
68
        yield from self.data
69
70
    def __len__(self):
71
        return len(self.data)
72
73
74
def _match_iteration(match_dict, target, value):
75
    if target is DISCARD:
76
        return
77
    if isinstance(target, Pattern):
78
        not_in(match_dict, target.name)
79
        match_dict[target.name] = value
80
        return
81
    processor = PROCESSORS.get_processor(target)
82
    if processor:
83
        yield from zip(list(processor(target)), list(processor(value)))
84
    elif target != value:
85
        raise MatchFailure
86
87
88
def _match(target, value):
89
    match_dict = MatchDict()
90
    to_process = [(target, value)]
91
    while to_process:
92
        to_process.extend(_match_iteration(match_dict, *to_process.pop()))
93
    return match_dict
94
95
96
class ValueMatcher:
97
    """Given a value, attempt to match against a target."""
98
99
    def __init__(self, value):
100
        self.value = value
101
        self.matches = None
102
103
    def match(self, target):
104
        """Match against target, generating a set of bindings."""
105
        try:
106
            self.matches = _match(target, self.value)
107
        except MatchFailure:
108
            self.matches = None
109
        return self.matches is not None
110
111
112
pat = AttributeConstructor(Pattern)
113
114
115
__all__ = ['Pattern', 'ValueMatcher', 'names', 'pat']
116