]>
Commit | Line | Data |
---|---|---|
53e6db90 DC |
1 | from contextlib import contextmanager |
2 | from typing import Dict, List | |
3 | ||
4 | ||
5 | class _NormalizerMeta(type): | |
6 | def __new__(cls, name, bases, dct): | |
7 | new_cls = type.__new__(cls, name, bases, dct) | |
8 | new_cls.rule_value_classes = {} | |
9 | new_cls.rule_type_classes = {} | |
10 | return new_cls | |
11 | ||
12 | ||
13 | class Normalizer(metaclass=_NormalizerMeta): | |
14 | _rule_type_instances: Dict[str, List[type]] = {} | |
15 | _rule_value_instances: Dict[str, List[type]] = {} | |
16 | ||
17 | def __init__(self, grammar, config): | |
18 | self.grammar = grammar | |
19 | self._config = config | |
20 | self.issues = [] | |
21 | ||
22 | self._rule_type_instances = self._instantiate_rules('rule_type_classes') | |
23 | self._rule_value_instances = self._instantiate_rules('rule_value_classes') | |
24 | ||
25 | def _instantiate_rules(self, attr): | |
26 | dct = {} | |
27 | for base in type(self).mro(): | |
28 | rules_map = getattr(base, attr, {}) | |
29 | for type_, rule_classes in rules_map.items(): | |
30 | new = [rule_cls(self) for rule_cls in rule_classes] | |
31 | dct.setdefault(type_, []).extend(new) | |
32 | return dct | |
33 | ||
34 | def walk(self, node): | |
35 | self.initialize(node) | |
36 | value = self.visit(node) | |
37 | self.finalize() | |
38 | return value | |
39 | ||
40 | def visit(self, node): | |
41 | try: | |
42 | children = node.children | |
43 | except AttributeError: | |
44 | return self.visit_leaf(node) | |
45 | else: | |
46 | with self.visit_node(node): | |
47 | return ''.join(self.visit(child) for child in children) | |
48 | ||
49 | @contextmanager | |
50 | def visit_node(self, node): | |
51 | self._check_type_rules(node) | |
52 | yield | |
53 | ||
54 | def _check_type_rules(self, node): | |
55 | for rule in self._rule_type_instances.get(node.type, []): | |
56 | rule.feed_node(node) | |
57 | ||
58 | def visit_leaf(self, leaf): | |
59 | self._check_type_rules(leaf) | |
60 | ||
61 | for rule in self._rule_value_instances.get(leaf.value, []): | |
62 | rule.feed_node(leaf) | |
63 | ||
64 | return leaf.prefix + leaf.value | |
65 | ||
66 | def initialize(self, node): | |
67 | pass | |
68 | ||
69 | def finalize(self): | |
70 | pass | |
71 | ||
72 | def add_issue(self, node, code, message): | |
73 | issue = Issue(node, code, message) | |
74 | if issue not in self.issues: | |
75 | self.issues.append(issue) | |
76 | return True | |
77 | ||
78 | @classmethod | |
79 | def register_rule(cls, *, value=None, values=(), type=None, types=()): | |
80 | """ | |
81 | Use it as a class decorator:: | |
82 | ||
83 | normalizer = Normalizer('grammar', 'config') | |
84 | @normalizer.register_rule(value='foo') | |
85 | class MyRule(Rule): | |
86 | error_code = 42 | |
87 | """ | |
88 | values = list(values) | |
89 | types = list(types) | |
90 | if value is not None: | |
91 | values.append(value) | |
92 | if type is not None: | |
93 | types.append(type) | |
94 | ||
95 | if not values and not types: | |
96 | raise ValueError("You must register at least something.") | |
97 | ||
98 | def decorator(rule_cls): | |
99 | for v in values: | |
100 | cls.rule_value_classes.setdefault(v, []).append(rule_cls) | |
101 | for t in types: | |
102 | cls.rule_type_classes.setdefault(t, []).append(rule_cls) | |
103 | return rule_cls | |
104 | ||
105 | return decorator | |
106 | ||
107 | ||
108 | class NormalizerConfig: | |
109 | normalizer_class = Normalizer | |
110 | ||
111 | def create_normalizer(self, grammar): | |
112 | if self.normalizer_class is None: | |
113 | return None | |
114 | ||
115 | return self.normalizer_class(grammar, self) | |
116 | ||
117 | ||
118 | class Issue: | |
119 | def __init__(self, node, code, message): | |
120 | self.code = code | |
121 | """ | |
122 | An integer code that stands for the type of error. | |
123 | """ | |
124 | self.message = message | |
125 | """ | |
126 | A message (string) for the issue. | |
127 | """ | |
128 | self.start_pos = node.start_pos | |
129 | """ | |
130 | The start position position of the error as a tuple (line, column). As | |
131 | always in |parso| the first line is 1 and the first column 0. | |
132 | """ | |
133 | self.end_pos = node.end_pos | |
134 | ||
135 | def __eq__(self, other): | |
136 | return self.start_pos == other.start_pos and self.code == other.code | |
137 | ||
138 | def __ne__(self, other): | |
139 | return not self.__eq__(other) | |
140 | ||
141 | def __hash__(self): | |
142 | return hash((self.code, self.start_pos)) | |
143 | ||
144 | def __repr__(self): | |
145 | return '<%s: %s>' % (self.__class__.__name__, self.code) | |
146 | ||
147 | ||
148 | class Rule: | |
149 | code: int | |
150 | message: str | |
151 | ||
152 | def __init__(self, normalizer): | |
153 | self._normalizer = normalizer | |
154 | ||
155 | def is_issue(self, node): | |
156 | raise NotImplementedError() | |
157 | ||
158 | def get_node(self, node): | |
159 | return node | |
160 | ||
161 | def _get_message(self, message, node): | |
162 | if message is None: | |
163 | message = self.message | |
164 | if message is None: | |
165 | raise ValueError("The message on the class is not set.") | |
166 | return message | |
167 | ||
168 | def add_issue(self, node, code=None, message=None): | |
169 | if code is None: | |
170 | code = self.code | |
171 | if code is None: | |
172 | raise ValueError("The error code on the class is not set.") | |
173 | ||
174 | message = self._get_message(message, node) | |
175 | ||
176 | self._normalizer.add_issue(node, code, message) | |
177 | ||
178 | def feed_node(self, node): | |
179 | if self.is_issue(node): | |
180 | issue_node = self.get_node(node) | |
181 | self.add_issue(issue_node) | |
182 | ||
183 | ||
184 | class RefactoringNormalizer(Normalizer): | |
185 | def __init__(self, node_to_str_map): | |
186 | self._node_to_str_map = node_to_str_map | |
187 | ||
188 | def visit(self, node): | |
189 | try: | |
190 | return self._node_to_str_map[node] | |
191 | except KeyError: | |
192 | return super().visit(node) | |
193 | ||
194 | def visit_leaf(self, leaf): | |
195 | try: | |
196 | return self._node_to_str_map[leaf] | |
197 | except KeyError: | |
198 | return super().visit_leaf(leaf) |