mapres 3.dev22__tar.gz → 3.dev24__tar.gz
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-3.dev22/src/mapres.egg-info → mapres-3.dev24}/PKG-INFO +1 -1
- {mapres-3.dev22 → mapres-3.dev24}/pyproject.toml +1 -1
- {mapres-3.dev22 → mapres-3.dev24}/src/mapres/datamap.py +5 -1
- {mapres-3.dev22 → mapres-3.dev24}/src/mapres/evaluator.py +43 -0
- {mapres-3.dev22 → mapres-3.dev24}/src/mapres/tokenizer.py +17 -0
- {mapres-3.dev22 → mapres-3.dev24/src/mapres.egg-info}/PKG-INFO +1 -1
- {mapres-3.dev22 → mapres-3.dev24}/LICENSE +0 -0
- {mapres-3.dev22 → mapres-3.dev24}/README.md +0 -0
- {mapres-3.dev22 → mapres-3.dev24}/setup.cfg +0 -0
- {mapres-3.dev22 → mapres-3.dev24}/src/mapres/__init__.py +0 -0
- {mapres-3.dev22 → mapres-3.dev24}/src/mapres/ast.py +0 -0
- {mapres-3.dev22 → mapres-3.dev24}/src/mapres/cache.py +0 -0
- {mapres-3.dev22 → mapres-3.dev24}/src/mapres/exceptions.py +0 -0
- {mapres-3.dev22 → mapres-3.dev24}/src/mapres/layers.py +0 -0
- {mapres-3.dev22 → mapres-3.dev24}/src/mapres/maps/__init__.py +0 -0
- {mapres-3.dev22 → mapres-3.dev24}/src/mapres/maps/color.py +0 -0
- {mapres-3.dev22 → mapres-3.dev24}/src/mapres/maps/time.py +0 -0
- {mapres-3.dev22 → mapres-3.dev24}/src/mapres/parser.py +0 -0
- {mapres-3.dev22 → mapres-3.dev24}/src/mapres/resolver.py +0 -0
- {mapres-3.dev22 → mapres-3.dev24}/src/mapres.egg-info/SOURCES.txt +0 -0
- {mapres-3.dev22 → mapres-3.dev24}/src/mapres.egg-info/dependency_links.txt +0 -0
- {mapres-3.dev22 → mapres-3.dev24}/src/mapres.egg-info/requires.txt +0 -0
- {mapres-3.dev22 → mapres-3.dev24}/src/mapres.egg-info/top_level.txt +0 -0
|
@@ -36,6 +36,8 @@ class DataMap:
|
|
|
36
36
|
__syntax__: str = None
|
|
37
37
|
__mode__: str | None = None # 'dynamic', 'config', 'callable', or None
|
|
38
38
|
__missing_key__: str = 'error' # 'error', 'silent', 'placeholder'
|
|
39
|
+
__recursive__: bool = False
|
|
40
|
+
__max_depth__: int = 10
|
|
39
41
|
|
|
40
42
|
def as_map(self):
|
|
41
43
|
'''
|
|
@@ -92,11 +94,13 @@ class DataMap:
|
|
|
92
94
|
return out
|
|
93
95
|
|
|
94
96
|
|
|
95
|
-
def datamap(_cls=None, *, syntax=None, mode=None, missing_key=None):
|
|
97
|
+
def datamap(_cls=None, *, syntax=None, mode=None, missing_key=None, recursive=None, max_depth=None):
|
|
96
98
|
def wrap(cls):
|
|
97
99
|
cls.__syntax__ = syntax
|
|
98
100
|
cls.__mode__ = mode
|
|
99
101
|
cls.__missing_key__ = missing_key or 'error'
|
|
102
|
+
cls.__recursive__ = recursive or False
|
|
103
|
+
cls.__max_depth__ = max_depth or 10
|
|
100
104
|
|
|
101
105
|
namespace = dict(cls.__dict__)
|
|
102
106
|
namespace['__dict__'] = {}
|
|
@@ -33,6 +33,13 @@ class Evaluator:
|
|
|
33
33
|
return self._eval_call(node)
|
|
34
34
|
|
|
35
35
|
raise EvaluationError(f'Unknown AST node: {node}')
|
|
36
|
+
|
|
37
|
+
# re-resolve
|
|
38
|
+
def _re_resolve(self, text: str) -> str:
|
|
39
|
+
tokens = Tokenizer(text).tokenize()
|
|
40
|
+
ast = Parser(tokens).parse()
|
|
41
|
+
return self.evaluate(ast)
|
|
42
|
+
|
|
36
43
|
|
|
37
44
|
# template
|
|
38
45
|
def _eval_template(self, node: TemplateNode):
|
|
@@ -40,6 +47,42 @@ class Evaluator:
|
|
|
40
47
|
for child in node.children:
|
|
41
48
|
parts.append(self._eval(child))
|
|
42
49
|
out = ''.join(parts)
|
|
50
|
+
|
|
51
|
+
# ---------------------------------------
|
|
52
|
+
# PER-MAP RECURSION (syntax-scoped)
|
|
53
|
+
# ---------------------------------------
|
|
54
|
+
# Find all maps in the layerstack that have recursive=True
|
|
55
|
+
recursive_maps = [
|
|
56
|
+
m for m in self.layerstack.all_maps()
|
|
57
|
+
if getattr(m, '__recursive__', False) is True
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
# If no maps are recursive, return the first-pass output
|
|
61
|
+
if not recursive_maps:
|
|
62
|
+
return out
|
|
63
|
+
|
|
64
|
+
# Determine max recursion depth across all recursive maps
|
|
65
|
+
max_depth = max(getattr(m, '__max_depth__', 10) for m in recursive_maps)
|
|
66
|
+
|
|
67
|
+
seen = set()
|
|
68
|
+
depth = 0
|
|
69
|
+
|
|
70
|
+
while depth < max_depth:
|
|
71
|
+
# Re-tokenize and re-evaluate the ENTIRE output string
|
|
72
|
+
new_out = self._re_resolve(out)
|
|
73
|
+
|
|
74
|
+
# If stable, stop recursion
|
|
75
|
+
if new_out == out:
|
|
76
|
+
break
|
|
77
|
+
|
|
78
|
+
# Cycle detection
|
|
79
|
+
if new_out in seen:
|
|
80
|
+
raise EvaluationError('Cycle detected in recursive map resolution')
|
|
81
|
+
|
|
82
|
+
seen.add(new_out)
|
|
83
|
+
out = new_out
|
|
84
|
+
depth += 1
|
|
85
|
+
|
|
43
86
|
return out
|
|
44
87
|
|
|
45
88
|
# identifier
|
|
@@ -121,6 +121,23 @@ class Tokenizer:
|
|
|
121
121
|
self.advance()
|
|
122
122
|
continue
|
|
123
123
|
|
|
124
|
+
# --- $(IDENT) ---
|
|
125
|
+
if self.match('$('):
|
|
126
|
+
flush_text()
|
|
127
|
+
start = self.i
|
|
128
|
+
self.advance(2)
|
|
129
|
+
ident = self._read_ident()
|
|
130
|
+
if ident is not None and self.peek() == ')':
|
|
131
|
+
self.emit(TokenType.DOLLAR_OPEN)
|
|
132
|
+
self.emit(TokenType.IDENT, ident)
|
|
133
|
+
self.advance()
|
|
134
|
+
self.emit(TokenType.DOLLAR_CLOSE)
|
|
135
|
+
continue
|
|
136
|
+
self.i = start
|
|
137
|
+
buf.append(self.peek())
|
|
138
|
+
self.advance()
|
|
139
|
+
continue
|
|
140
|
+
|
|
124
141
|
# --- <IDENT> ---
|
|
125
142
|
if ch == '<':
|
|
126
143
|
flush_text()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|