mapres 3.dev14__py3-none-any.whl → 3.dev16__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mapres/datamap.py +44 -14
- mapres/evaluator.py +112 -10
- mapres/exceptions.py +3 -1
- mapres/resolver.py +19 -2
- mapres/tokenizer.py +69 -23
- {mapres-3.dev14.dist-info → mapres-3.dev16.dist-info}/METADATA +1 -1
- mapres-3.dev16.dist-info/RECORD +18 -0
- mapres-3.dev14.dist-info/RECORD +0 -18
- {mapres-3.dev14.dist-info → mapres-3.dev16.dist-info}/WHEEL +0 -0
- {mapres-3.dev14.dist-info → mapres-3.dev16.dist-info}/licenses/LICENSE +0 -0
- {mapres-3.dev14.dist-info → mapres-3.dev16.dist-info}/top_level.txt +0 -0
mapres/datamap.py
CHANGED
|
@@ -35,7 +35,10 @@ class DataMap:
|
|
|
35
35
|
- "callable": map exposes callable methods for nested calls
|
|
36
36
|
'''
|
|
37
37
|
__syntax__: str = None
|
|
38
|
-
__mode__: str | None = None
|
|
38
|
+
__mode__: str | None = None # "dynamic", "config", "callable", or None
|
|
39
|
+
__recursive__: bool = False
|
|
40
|
+
__missing_key__: str = 'error' # 'error', 'silent', 'placeholder'
|
|
41
|
+
__ignore_delimiters__: bool = False
|
|
39
42
|
|
|
40
43
|
def as_map(self):
|
|
41
44
|
'''
|
|
@@ -47,8 +50,9 @@ class DataMap:
|
|
|
47
50
|
'''
|
|
48
51
|
result = {}
|
|
49
52
|
|
|
53
|
+
# dataclass fields
|
|
50
54
|
for f in fields(self):
|
|
51
|
-
if not f.init or f.name
|
|
55
|
+
if not f.init or f.name.startswith('__'):
|
|
52
56
|
continue
|
|
53
57
|
|
|
54
58
|
val = getattr(self, f.name)
|
|
@@ -63,24 +67,58 @@ class DataMap:
|
|
|
63
67
|
|
|
64
68
|
result[f.name] = val
|
|
65
69
|
|
|
70
|
+
# nested classes > nested dicts
|
|
71
|
+
for name, obj in self.__class__.__dict__.items():
|
|
72
|
+
if name.startswith('__'):
|
|
73
|
+
continue
|
|
74
|
+
if isinstance(obj, type):
|
|
75
|
+
result[name] = self._class_to_dict(obj)
|
|
76
|
+
|
|
66
77
|
return result
|
|
67
78
|
|
|
79
|
+
def _class_to_dict(self, cls):
|
|
80
|
+
out = {}
|
|
81
|
+
|
|
82
|
+
if is_dataclass(cls):
|
|
83
|
+
dummy = cls()
|
|
84
|
+
for f in fields(dummy):
|
|
85
|
+
if not f.init:
|
|
86
|
+
continue
|
|
87
|
+
out[f.name] = getattr(dummy, f.name)
|
|
88
|
+
|
|
89
|
+
for name, obj in cls.__dict__.items():
|
|
90
|
+
if name.startswith('__'):
|
|
91
|
+
continue
|
|
92
|
+
if isinstance(obj, type):
|
|
93
|
+
out[name] = self._class_to_dict(obj)
|
|
94
|
+
|
|
95
|
+
return out
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def datamap(_cls=None, *, syntax=None, mode=None, recursive=None,
|
|
99
|
+
missing_key=None, ignore_delimiters=None):
|
|
68
100
|
|
|
69
|
-
# main decorator factory
|
|
70
|
-
def datamap(_cls=None, *, syntax=None, mode=None):
|
|
71
|
-
'''@datamap decorator'''
|
|
72
101
|
def wrap(cls):
|
|
73
102
|
cls.__syntax__ = syntax
|
|
74
103
|
cls.__mode__ = mode
|
|
104
|
+
cls.__recursive__ = recursive
|
|
105
|
+
cls.__missing_key__ = missing_key or 'error'
|
|
106
|
+
cls.__ignore_delimiters__ = ignore_delimiters or False
|
|
107
|
+
|
|
75
108
|
namespace = dict(cls.__dict__)
|
|
76
109
|
namespace['__dict__'] = {}
|
|
77
110
|
rules_obj = namespace.get('rules', None)
|
|
111
|
+
|
|
78
112
|
cls = type(cls.__name__, (DataMap,), namespace)
|
|
113
|
+
|
|
79
114
|
if rules_obj is not None:
|
|
80
115
|
setattr(cls, 'rules', rules_obj)
|
|
116
|
+
|
|
81
117
|
return dataclass(frozen=False)(cls)
|
|
82
|
-
|
|
118
|
+
|
|
119
|
+
if _cls is not None and isinstance(_cls, type):
|
|
83
120
|
return wrap(_cls)
|
|
121
|
+
|
|
84
122
|
return wrap
|
|
85
123
|
|
|
86
124
|
|
|
@@ -98,30 +136,23 @@ class _SyntaxFactory:
|
|
|
98
136
|
self._syntax = syntax_name
|
|
99
137
|
|
|
100
138
|
def __call__(self, _cls=None, **kwargs):
|
|
101
|
-
# Case 1: bare decorator: @datamap.angles
|
|
102
139
|
if _cls is not None and isinstance(_cls, type):
|
|
103
140
|
return datamap(_cls, syntax=self._syntax)
|
|
104
|
-
|
|
105
|
-
# Case 2: decorator with args: @datamap.angles(mode='config')
|
|
106
141
|
return datamap(syntax=self._syntax, **kwargs)
|
|
107
142
|
|
|
108
143
|
@property
|
|
109
144
|
def config(self):
|
|
110
|
-
# Case 3: @datamap.angles.config
|
|
111
145
|
return datamap(syntax=self._syntax, mode='config')
|
|
112
146
|
|
|
113
147
|
@property
|
|
114
148
|
def dynamic(self):
|
|
115
|
-
# @datamap.angles.dynamic
|
|
116
149
|
return datamap(syntax=self._syntax, mode='dynamic')
|
|
117
150
|
|
|
118
151
|
@property
|
|
119
152
|
def callable(self):
|
|
120
|
-
# @datamap.angles.callable
|
|
121
153
|
return datamap(syntax=self._syntax, mode='callable')
|
|
122
154
|
|
|
123
155
|
|
|
124
|
-
# build syntax shortcuts
|
|
125
156
|
datamap.braces = _SyntaxFactory(syntax.braces)
|
|
126
157
|
datamap.dollars = _SyntaxFactory(syntax.dollars)
|
|
127
158
|
datamap.angles = _SyntaxFactory(syntax.angles)
|
|
@@ -132,7 +163,6 @@ datamap.at_tags = _SyntaxFactory(syntax.at_tags)
|
|
|
132
163
|
datamap.hash_tags = _SyntaxFactory(syntax.hash_tags)
|
|
133
164
|
datamap.paren_dollars = _SyntaxFactory(syntax.paren_dollars)
|
|
134
165
|
|
|
135
|
-
# global mode shortcuts
|
|
136
166
|
datamap.config = datamap(mode='config')
|
|
137
167
|
datamap.dynamic = datamap(mode='dynamic')
|
|
138
168
|
datamap.callable = datamap(mode='callable')
|
mapres/evaluator.py
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
from .ast import TemplateNode, TextNode, IdentNode, CallNode
|
|
2
|
+
from .tokenizer import Tokenizer
|
|
3
|
+
from .parser import Parser
|
|
4
|
+
from .exceptions import MapResError, MissingKeyError
|
|
2
5
|
|
|
3
6
|
|
|
4
|
-
class EvaluationError(
|
|
7
|
+
class EvaluationError(MapResError):
|
|
5
8
|
pass
|
|
6
9
|
|
|
7
10
|
|
|
@@ -29,25 +32,47 @@ class Evaluator:
|
|
|
29
32
|
if isinstance(node, CallNode):
|
|
30
33
|
return self._eval_call(node)
|
|
31
34
|
|
|
32
|
-
raise EvaluationError(f
|
|
35
|
+
raise EvaluationError(f'Unknown AST node: {node}')
|
|
33
36
|
|
|
34
37
|
# template
|
|
35
38
|
def _eval_template(self, node: TemplateNode):
|
|
36
39
|
parts = []
|
|
37
40
|
for child in node.children:
|
|
38
41
|
parts.append(self._eval(child))
|
|
39
|
-
|
|
42
|
+
out = ''.join(parts)
|
|
43
|
+
|
|
44
|
+
# recursion over full template output
|
|
45
|
+
if self._has_recursive_maps():
|
|
46
|
+
seen = set()
|
|
47
|
+
depth = 0
|
|
48
|
+
max_depth = self._get_max_depth()
|
|
49
|
+
|
|
50
|
+
while depth < max_depth:
|
|
51
|
+
new_out = self._re_resolve(out)
|
|
52
|
+
if new_out == out:
|
|
53
|
+
break
|
|
54
|
+
if new_out in seen:
|
|
55
|
+
raise EvaluationError('Cycle detected in template recursion')
|
|
56
|
+
seen.add(new_out)
|
|
57
|
+
out = new_out
|
|
58
|
+
depth += 1
|
|
59
|
+
|
|
60
|
+
return out
|
|
40
61
|
|
|
41
62
|
# identifier
|
|
42
63
|
def _eval_ident(self, node: IdentNode):
|
|
43
64
|
name = node.name
|
|
44
65
|
|
|
66
|
+
# ignore_delimiters: treat token as literal for this syntax
|
|
67
|
+
if self._syntax_ignores_delimiters(node.syntax):
|
|
68
|
+
return self._literal_from_syntax(name, node.syntax)
|
|
69
|
+
|
|
45
70
|
# cycle detection
|
|
46
71
|
if name in self._seen:
|
|
47
72
|
raise EvaluationError(f"Cycle detected in identifier '{name}'")
|
|
48
73
|
self._seen.add(name)
|
|
49
74
|
|
|
50
|
-
parts = name.split(
|
|
75
|
+
parts = name.split('.')
|
|
51
76
|
|
|
52
77
|
# 1. ctx first
|
|
53
78
|
val = self._lookup_hierarchical(self.ctx, parts)
|
|
@@ -57,7 +82,7 @@ class Evaluator:
|
|
|
57
82
|
|
|
58
83
|
# 2. syntax-bound maps
|
|
59
84
|
for m in self.layerstack.all_maps():
|
|
60
|
-
if getattr(m,
|
|
85
|
+
if getattr(m, '__syntax__', None) not in (None, node.syntax):
|
|
61
86
|
continue
|
|
62
87
|
|
|
63
88
|
d = self._map_to_dict(m)
|
|
@@ -67,12 +92,17 @@ class Evaluator:
|
|
|
67
92
|
return str(val)
|
|
68
93
|
|
|
69
94
|
self._seen.remove(name)
|
|
70
|
-
raise
|
|
95
|
+
raise MissingKeyError(name)
|
|
71
96
|
|
|
72
97
|
# call (nested token)
|
|
73
98
|
def _eval_call(self, node: CallNode):
|
|
74
99
|
outer = node.name
|
|
75
100
|
|
|
101
|
+
# ignore_delimiters: treat call token as literal
|
|
102
|
+
if self._syntax_ignores_delimiters(node.syntax):
|
|
103
|
+
arg_text = self._eval(node.arg)
|
|
104
|
+
return self._literal_from_syntax(outer, node.syntax, is_call=True, arg_text=arg_text)
|
|
105
|
+
|
|
76
106
|
if outer in self._seen:
|
|
77
107
|
raise EvaluationError(f"Cycle detected in call '{outer}'")
|
|
78
108
|
self._seen.add(outer)
|
|
@@ -88,7 +118,7 @@ class Evaluator:
|
|
|
88
118
|
|
|
89
119
|
# syntax-bound maps
|
|
90
120
|
for m in self.layerstack.all_maps():
|
|
91
|
-
if getattr(m,
|
|
121
|
+
if getattr(m, '__syntax__', None) not in (None, node.syntax):
|
|
92
122
|
continue
|
|
93
123
|
|
|
94
124
|
d = self._map_to_dict(m)
|
|
@@ -98,15 +128,15 @@ class Evaluator:
|
|
|
98
128
|
return str(val)
|
|
99
129
|
|
|
100
130
|
self._seen.remove(outer)
|
|
101
|
-
raise
|
|
131
|
+
raise MissingKeyError(f'{outer}({arg_value})')
|
|
102
132
|
|
|
103
133
|
# helpers
|
|
104
134
|
def _map_to_dict(self, m):
|
|
105
|
-
if hasattr(m,
|
|
135
|
+
if hasattr(m, 'as_map'):
|
|
106
136
|
return m.as_map()
|
|
107
137
|
if isinstance(m, dict):
|
|
108
138
|
return m
|
|
109
|
-
raise EvaluationError(f
|
|
139
|
+
raise EvaluationError(f'Invalid map object: {m}')
|
|
110
140
|
|
|
111
141
|
def _lookup_hierarchical(self, root, parts):
|
|
112
142
|
cur = root
|
|
@@ -117,3 +147,75 @@ class Evaluator:
|
|
|
117
147
|
return None
|
|
118
148
|
cur = cur[p]
|
|
119
149
|
return cur
|
|
150
|
+
|
|
151
|
+
def _has_recursive_maps(self):
|
|
152
|
+
for m in self.layerstack.all_maps():
|
|
153
|
+
if getattr(m, '__recursive__', False):
|
|
154
|
+
return True
|
|
155
|
+
return False
|
|
156
|
+
|
|
157
|
+
def _get_max_depth(self):
|
|
158
|
+
depths = [
|
|
159
|
+
getattr(m, '__max_depth__', 10)
|
|
160
|
+
for m in self.layerstack.all_maps()
|
|
161
|
+
if getattr(m, '__recursive__', False)
|
|
162
|
+
]
|
|
163
|
+
return max(depths) if depths else 0
|
|
164
|
+
|
|
165
|
+
def _re_resolve(self, text: str) -> str:
|
|
166
|
+
tokens = Tokenizer(text).tokenize()
|
|
167
|
+
ast = Parser(tokens).parse()
|
|
168
|
+
return self.evaluate(ast)
|
|
169
|
+
|
|
170
|
+
def _syntax_ignores_delimiters(self, syntax_name: str) -> bool:
|
|
171
|
+
for m in self.layerstack.all_maps():
|
|
172
|
+
if getattr(m, '__syntax__', None) == syntax_name:
|
|
173
|
+
if getattr(m, '__ignore_delimiters__', False):
|
|
174
|
+
return True
|
|
175
|
+
return False
|
|
176
|
+
|
|
177
|
+
def _literal_from_syntax(self, name: str, syntax_name: str, is_call: bool = False, arg_text: str | None = None) -> str:
|
|
178
|
+
if not is_call:
|
|
179
|
+
if syntax_name == 'colons':
|
|
180
|
+
return f':{name}:'
|
|
181
|
+
if syntax_name == 'braces':
|
|
182
|
+
return f'{{{{{name}}}}}'
|
|
183
|
+
if syntax_name == 'dollars':
|
|
184
|
+
return f'${{{name}}}'
|
|
185
|
+
if syntax_name == 'angles':
|
|
186
|
+
return f'<{name}>'
|
|
187
|
+
if syntax_name == 'pipes':
|
|
188
|
+
return f'|{name}|'
|
|
189
|
+
if syntax_name == 'percents':
|
|
190
|
+
return f'%{name}%'
|
|
191
|
+
if syntax_name == 'paren_dollars':
|
|
192
|
+
return f'$({name})'
|
|
193
|
+
if syntax_name == 'at_tags':
|
|
194
|
+
return f'@{name}@'
|
|
195
|
+
if syntax_name == 'hash_tags':
|
|
196
|
+
return f'#{name}#'
|
|
197
|
+
return name
|
|
198
|
+
|
|
199
|
+
# call literal: syntax-specific wrapping of name(arg_text)
|
|
200
|
+
arg = arg_text or ''
|
|
201
|
+
inner = f'{name}({arg})'
|
|
202
|
+
|
|
203
|
+
if syntax_name == 'colons':
|
|
204
|
+
return f':{inner}:'
|
|
205
|
+
if syntax_name == 'braces':
|
|
206
|
+
return f'{{{{{inner}}}}}'
|
|
207
|
+
if syntax_name == 'dollars':
|
|
208
|
+
return f'${{{inner}}}'
|
|
209
|
+
if syntax_name == 'angles':
|
|
210
|
+
return f'<{inner}>'
|
|
211
|
+
if syntax_name == 'pipes':
|
|
212
|
+
return f'|{inner}|'
|
|
213
|
+
if syntax_name == 'percents':
|
|
214
|
+
return f'%{inner}%'
|
|
215
|
+
if syntax_name == 'paren_dollars':
|
|
216
|
+
return f'$({inner})'
|
|
217
|
+
if syntax_name == 'at_tags':
|
|
218
|
+
return f'@{inner}@'
|
|
219
|
+
if syntax_name == 'hash_tags':
|
|
220
|
+
return f'#{inner}#'
|
|
221
|
+
return inner
|
mapres/exceptions.py
CHANGED
mapres/resolver.py
CHANGED
|
@@ -3,7 +3,7 @@ from .parser import Parser
|
|
|
3
3
|
from .evaluator import Evaluator
|
|
4
4
|
from .layers import LayerStack
|
|
5
5
|
from .cache import LRUCache
|
|
6
|
-
from .exceptions import MapResError
|
|
6
|
+
from .exceptions import MapResError, MissingKeyError
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
class MapResolver:
|
|
@@ -65,8 +65,18 @@ class MapResolver:
|
|
|
65
65
|
|
|
66
66
|
return result
|
|
67
67
|
|
|
68
|
+
except MissingKeyError as exc:
|
|
69
|
+
mode = self._resolve_missing_key_mode()
|
|
70
|
+
if mode == 'error':
|
|
71
|
+
raise MapResError(f'Resolver error: {exc}') from exc
|
|
72
|
+
if mode == 'silent':
|
|
73
|
+
return text
|
|
74
|
+
if mode == 'placeholder':
|
|
75
|
+
return f'<missing:{exc.key}>'
|
|
76
|
+
raise MapResError(f'Resolver error: {exc}') from exc
|
|
77
|
+
|
|
68
78
|
except Exception as exc:
|
|
69
|
-
raise MapResError(f
|
|
79
|
+
raise MapResError(f'Resolver error: {exc}') from exc
|
|
70
80
|
|
|
71
81
|
# ---------------------------------------
|
|
72
82
|
# LAYERSTACK BUILDING
|
|
@@ -92,6 +102,13 @@ class MapResolver:
|
|
|
92
102
|
|
|
93
103
|
return self.layers
|
|
94
104
|
|
|
105
|
+
def _resolve_missing_key_mode(self):
|
|
106
|
+
for m in self.layers.all_maps():
|
|
107
|
+
mode = getattr(m, '__missing_key__', None)
|
|
108
|
+
if mode is not None:
|
|
109
|
+
return mode
|
|
110
|
+
return 'error'
|
|
111
|
+
|
|
95
112
|
|
|
96
113
|
# ---------------------------------------
|
|
97
114
|
# SIMPLE GLOBAL USAGE
|
mapres/tokenizer.py
CHANGED
|
@@ -28,8 +28,8 @@ class Token:
|
|
|
28
28
|
|
|
29
29
|
def __repr__(self):
|
|
30
30
|
if self.value is not None:
|
|
31
|
-
return f
|
|
32
|
-
return f
|
|
31
|
+
return f'Token({self.type}, {self.value!r}, pos={self.pos})'
|
|
32
|
+
return f'Token({self.type}, pos={self.pos})'
|
|
33
33
|
|
|
34
34
|
|
|
35
35
|
class Tokenizer:
|
|
@@ -38,10 +38,15 @@ class Tokenizer:
|
|
|
38
38
|
self.i = 0
|
|
39
39
|
self.n = len(text)
|
|
40
40
|
self.tokens = []
|
|
41
|
+
|
|
42
|
+
# toggle states
|
|
41
43
|
self.in_pipe = False
|
|
42
44
|
self.in_percent = False
|
|
43
45
|
self.in_colon = False
|
|
44
46
|
|
|
47
|
+
# mixed states
|
|
48
|
+
self.in_dollar = False
|
|
49
|
+
|
|
45
50
|
def peek(self, k=0):
|
|
46
51
|
idx = self.i + k
|
|
47
52
|
if idx >= self.n:
|
|
@@ -68,6 +73,7 @@ class Tokenizer:
|
|
|
68
73
|
while self.i < self.n:
|
|
69
74
|
ch = self.peek()
|
|
70
75
|
|
|
76
|
+
# escapes
|
|
71
77
|
if self.match(r'\:'):
|
|
72
78
|
buf.append(':'); self.advance(2); continue
|
|
73
79
|
if self.match(r'\<'):
|
|
@@ -89,43 +95,57 @@ class Tokenizer:
|
|
|
89
95
|
if self.match(r'\)'):
|
|
90
96
|
buf.append(')'); self.advance(2); continue
|
|
91
97
|
|
|
92
|
-
|
|
98
|
+
# --- paired delimiters ---
|
|
99
|
+
|
|
100
|
+
# braces {{ }}
|
|
101
|
+
if self.match('{{'):
|
|
93
102
|
flush_text()
|
|
94
103
|
self.emit(TokenType.BRACE_OPEN)
|
|
95
104
|
self.advance(2)
|
|
96
105
|
continue
|
|
97
106
|
|
|
98
|
-
if self.match(
|
|
107
|
+
if self.match('}}'):
|
|
99
108
|
flush_text()
|
|
100
109
|
self.emit(TokenType.BRACE_CLOSE)
|
|
101
110
|
self.advance(2)
|
|
102
111
|
continue
|
|
103
112
|
|
|
104
|
-
|
|
113
|
+
# angles < >
|
|
114
|
+
if self.match('<'):
|
|
105
115
|
flush_text()
|
|
106
|
-
self.emit(TokenType.
|
|
107
|
-
self.advance(
|
|
116
|
+
self.emit(TokenType.ANGLE_OPEN)
|
|
117
|
+
self.advance()
|
|
108
118
|
continue
|
|
109
119
|
|
|
110
|
-
if self.match(
|
|
120
|
+
if self.match('>'):
|
|
111
121
|
flush_text()
|
|
112
|
-
self.emit(TokenType.
|
|
122
|
+
self.emit(TokenType.ANGLE_CLOSE)
|
|
113
123
|
self.advance()
|
|
114
124
|
continue
|
|
115
125
|
|
|
116
|
-
|
|
126
|
+
# --- mixed delimiters (dollars) ---
|
|
127
|
+
|
|
128
|
+
if self.match('${'):
|
|
117
129
|
flush_text()
|
|
118
|
-
self.emit(TokenType.
|
|
119
|
-
self.
|
|
130
|
+
self.emit(TokenType.DOLLAR_OPEN)
|
|
131
|
+
self.in_dollar = True
|
|
132
|
+
self.advance(2)
|
|
120
133
|
continue
|
|
121
134
|
|
|
122
|
-
if
|
|
135
|
+
if ch == '}':
|
|
123
136
|
flush_text()
|
|
124
|
-
self.
|
|
137
|
+
if self.in_dollar:
|
|
138
|
+
self.emit(TokenType.DOLLAR_CLOSE)
|
|
139
|
+
self.in_dollar = False
|
|
140
|
+
else:
|
|
141
|
+
buf.append('}')
|
|
125
142
|
self.advance()
|
|
126
143
|
continue
|
|
127
144
|
|
|
128
|
-
|
|
145
|
+
# --- toggle delimiters ---
|
|
146
|
+
|
|
147
|
+
# pipes |...|
|
|
148
|
+
if self.match('|'):
|
|
129
149
|
flush_text()
|
|
130
150
|
if not self.in_pipe:
|
|
131
151
|
self.emit(TokenType.PIPE_OPEN)
|
|
@@ -136,7 +156,8 @@ class Tokenizer:
|
|
|
136
156
|
self.advance()
|
|
137
157
|
continue
|
|
138
158
|
|
|
139
|
-
|
|
159
|
+
# percents %...%
|
|
160
|
+
if self.match('%'):
|
|
140
161
|
flush_text()
|
|
141
162
|
if not self.in_percent:
|
|
142
163
|
self.emit(TokenType.PERCENT_OPEN)
|
|
@@ -147,7 +168,8 @@ class Tokenizer:
|
|
|
147
168
|
self.advance()
|
|
148
169
|
continue
|
|
149
170
|
|
|
150
|
-
|
|
171
|
+
# colons :...:
|
|
172
|
+
if self.match(':'):
|
|
151
173
|
flush_text()
|
|
152
174
|
if not self.in_colon:
|
|
153
175
|
self.emit(TokenType.COLON_OPEN)
|
|
@@ -158,27 +180,51 @@ class Tokenizer:
|
|
|
158
180
|
self.advance()
|
|
159
181
|
continue
|
|
160
182
|
|
|
161
|
-
|
|
183
|
+
# --- parens ---
|
|
184
|
+
|
|
185
|
+
if ch == '(':
|
|
162
186
|
flush_text()
|
|
163
187
|
self.emit(TokenType.LPAREN)
|
|
164
188
|
self.advance()
|
|
165
189
|
continue
|
|
166
190
|
|
|
167
|
-
if ch ==
|
|
191
|
+
if ch == ')':
|
|
168
192
|
flush_text()
|
|
169
193
|
self.emit(TokenType.RPAREN)
|
|
170
194
|
self.advance()
|
|
171
195
|
continue
|
|
172
196
|
|
|
173
|
-
|
|
197
|
+
# --- IDENT (your rules) ---
|
|
198
|
+
|
|
199
|
+
if ch.isalnum():
|
|
174
200
|
flush_text()
|
|
175
201
|
start = self.i
|
|
176
|
-
|
|
177
|
-
|
|
202
|
+
|
|
203
|
+
# first char: [a-zA-Z0-9]
|
|
204
|
+
self.advance()
|
|
205
|
+
|
|
206
|
+
# middle chars: [a-zA-Z0-9._]*
|
|
207
|
+
while True:
|
|
208
|
+
nxt = self.peek()
|
|
209
|
+
if not nxt:
|
|
210
|
+
break
|
|
211
|
+
if nxt.isalnum() or nxt in '._':
|
|
212
|
+
self.advance()
|
|
213
|
+
else:
|
|
214
|
+
break
|
|
215
|
+
|
|
178
216
|
ident = self.text[start:self.i]
|
|
179
|
-
|
|
217
|
+
|
|
218
|
+
# enforce: no leading/trailing '_', no '-'
|
|
219
|
+
if ident.startswith('_') or ident.endswith('_') or '-' in ident:
|
|
220
|
+
# treat as TEXT instead
|
|
221
|
+
buf.append(ident)
|
|
222
|
+
else:
|
|
223
|
+
self.emit(TokenType.IDENT, ident)
|
|
180
224
|
continue
|
|
181
225
|
|
|
226
|
+
# --- fallback: TEXT ---
|
|
227
|
+
|
|
182
228
|
buf.append(ch)
|
|
183
229
|
self.advance()
|
|
184
230
|
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
mapres/__init__.py,sha256=t23ofM-wzpPByYi1rEQonxyU1yyza4ZzwwHDZUa5QqA,1010
|
|
2
|
+
mapres/ast.py,sha256=6PcMkwtNHYD8xEkdyzDgn_mRTj1ZaWSyuHZK3BtXiuo,427
|
|
3
|
+
mapres/cache.py,sha256=hbvsoc6Qw3RhWAEfOOpKHtC7i9EomoUgWzyd4dnH71w,333
|
|
4
|
+
mapres/datamap.py,sha256=v9DAIYaS-vkBU1BKmshGYnquZa2JPwcct661zaEFocs,5256
|
|
5
|
+
mapres/evaluator.py,sha256=ElKliQ4z0vSTFHtbTxSafRBVrPpksjqFjq5spKKEFwI,6962
|
|
6
|
+
mapres/exceptions.py,sha256=D2XKnrMkZDyUXQt2PmaKbj08FObWU00yXKGHwNpjvww,222
|
|
7
|
+
mapres/layers.py,sha256=sFc1eUOBg2BoB2cBXIleq8RMYcg5GZpUoCnGh-uKN6U,1380
|
|
8
|
+
mapres/parser.py,sha256=8XLCzYosvh81bN-Jddc1AM4w0X7bsHnBzmA8sq5_72w,4018
|
|
9
|
+
mapres/resolver.py,sha256=kQexTdBnPy70Azh_U6--hwUtnIJjoKcuaJ2KlBaG3Rg,3889
|
|
10
|
+
mapres/tokenizer.py,sha256=js0S1s8MkltdQoH5V1cLjdY0fFqZoGqmtP_OhEJEM6M,6539
|
|
11
|
+
mapres/maps/__init__.py,sha256=lIRRfem1Fw4iynuwUQ7BY5qrC4fbJKxqLivao2_Dtm4,239
|
|
12
|
+
mapres/maps/color.py,sha256=noRTGFNo9xLl5YEhX0tttV6i_oDAiptXmRVnkr5EzwM,1348
|
|
13
|
+
mapres/maps/time.py,sha256=TyncycYoIGVZWps32qWlFy2a2hTayvZWVHu9uh2nid8,2050
|
|
14
|
+
mapres-3.dev16.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
15
|
+
mapres-3.dev16.dist-info/METADATA,sha256=KHiZBTclH89lWV6Kr85_SIuH3igO8i6fm6tarBaD0lE,1476
|
|
16
|
+
mapres-3.dev16.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
|
|
17
|
+
mapres-3.dev16.dist-info/top_level.txt,sha256=5LMVQqJdtyjxfzv7wJBOZ-360m0Yc79w_-Y7s3n6cTw,7
|
|
18
|
+
mapres-3.dev16.dist-info/RECORD,,
|
mapres-3.dev14.dist-info/RECORD
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
mapres/__init__.py,sha256=t23ofM-wzpPByYi1rEQonxyU1yyza4ZzwwHDZUa5QqA,1010
|
|
2
|
-
mapres/ast.py,sha256=6PcMkwtNHYD8xEkdyzDgn_mRTj1ZaWSyuHZK3BtXiuo,427
|
|
3
|
-
mapres/cache.py,sha256=hbvsoc6Qw3RhWAEfOOpKHtC7i9EomoUgWzyd4dnH71w,333
|
|
4
|
-
mapres/datamap.py,sha256=0kfxBSAaIXwT8srkcurPbXBbbpmc_HbAzL-iJ45pKNc,4439
|
|
5
|
-
mapres/evaluator.py,sha256=YZ4SUTMM1XhcxH1Skm2J4QSP6_P79TfuQn33cCekRsI,3348
|
|
6
|
-
mapres/exceptions.py,sha256=jirHKK5yCffSCWWkgZY_gTSIOaFCmXOe5NJJf60HeeQ,130
|
|
7
|
-
mapres/layers.py,sha256=sFc1eUOBg2BoB2cBXIleq8RMYcg5GZpUoCnGh-uKN6U,1380
|
|
8
|
-
mapres/parser.py,sha256=8XLCzYosvh81bN-Jddc1AM4w0X7bsHnBzmA8sq5_72w,4018
|
|
9
|
-
mapres/resolver.py,sha256=7N2M-6_6NCiBBxHCNFOjKoU0E3Ntgb8o_3gA00HsKU4,3247
|
|
10
|
-
mapres/tokenizer.py,sha256=WvLHVb3x2QA70oyrC2Gd_oNOXj9VIMVGuVS-Ew11RW0,5344
|
|
11
|
-
mapres/maps/__init__.py,sha256=lIRRfem1Fw4iynuwUQ7BY5qrC4fbJKxqLivao2_Dtm4,239
|
|
12
|
-
mapres/maps/color.py,sha256=noRTGFNo9xLl5YEhX0tttV6i_oDAiptXmRVnkr5EzwM,1348
|
|
13
|
-
mapres/maps/time.py,sha256=TyncycYoIGVZWps32qWlFy2a2hTayvZWVHu9uh2nid8,2050
|
|
14
|
-
mapres-3.dev14.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
15
|
-
mapres-3.dev14.dist-info/METADATA,sha256=ks3M9DeKQwoDUXQue1vG-pQ5notahc_u0cDV8eYIZaM,1476
|
|
16
|
-
mapres-3.dev14.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
|
|
17
|
-
mapres-3.dev14.dist-info/top_level.txt,sha256=5LMVQqJdtyjxfzv7wJBOZ-360m0Yc79w_-Y7s3n6cTw,7
|
|
18
|
-
mapres-3.dev14.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|