infinity-data 1.0.0__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.
- infinity_data/__init__.py +41 -0
- infinity_data/emit/__init__.py +9 -0
- infinity_data/emit/converter.py +55 -0
- infinity_data/frontend.py +37 -0
- infinity_data/infra/__init__.py +4 -0
- infinity_data/infra/diagnostics.py +212 -0
- infinity_data/infra/file.py +83 -0
- infinity_data/infra/ll1_stream.py +71 -0
- infinity_data/infra/location.py +80 -0
- infinity_data/infra/path.py +46 -0
- infinity_data/parser/__init__.py +79 -0
- infinity_data/parser/diagnostics.py +92 -0
- infinity_data/parser/models.py +295 -0
- infinity_data/parser/parser.py +988 -0
- infinity_data/parser/token_stream.py +128 -0
- infinity_data/pipeline.py +252 -0
- infinity_data/sandbox/__init__.py +32 -0
- infinity_data/sandbox/config.py +61 -0
- infinity_data/sandbox/errors.py +106 -0
- infinity_data/sandbox/mediator.py +214 -0
- infinity_data/sandbox/schema.py +21 -0
- infinity_data/semantic/__init__.py +59 -0
- infinity_data/semantic/builder/__init__.py +29 -0
- infinity_data/semantic/builder/builder.py +523 -0
- infinity_data/semantic/builder/models.py +163 -0
- infinity_data/semantic/constraints.py +163 -0
- infinity_data/semantic/diagnostics.py +172 -0
- infinity_data/semantic/executor/__init__.py +10 -0
- infinity_data/semantic/executor/executor.py +259 -0
- infinity_data/semantic/registry/__init__.py +168 -0
- infinity_data/semantic/registry/_core.py +212 -0
- infinity_data/semantic/registry/dict_constraints.py +56 -0
- infinity_data/semantic/registry/general.py +362 -0
- infinity_data/semantic/registry/logic.py +134 -0
- infinity_data/semantic/registry/types.py +136 -0
- infinity_data/semantic/resolver/__init__.py +20 -0
- infinity_data/semantic/resolver/imports.py +191 -0
- infinity_data/semantic/resolver/models.py +67 -0
- infinity_data/semantic/resolver/resolver.py +345 -0
- infinity_data/tokenizer/__init__.py +16 -0
- infinity_data/tokenizer/char_stream.py +72 -0
- infinity_data/tokenizer/diagnostics.py +62 -0
- infinity_data/tokenizer/finalizer.py +271 -0
- infinity_data/tokenizer/models/__init__.py +17 -0
- infinity_data/tokenizer/models/raw_tokens.py +61 -0
- infinity_data/tokenizer/models/tokens.py +245 -0
- infinity_data/tokenizer/tokenizer.py +531 -0
- infinity_data-1.0.0.dist-info/METADATA +60 -0
- infinity_data-1.0.0.dist-info/RECORD +52 -0
- infinity_data-1.0.0.dist-info/WHEEL +5 -0
- infinity_data-1.0.0.dist-info/licenses/LICENSE +21 -0
- infinity_data-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,523 @@
|
|
|
1
|
+
"""AST 构建器(Phase 2a):RawAst + ResolvedContext → StdAst(携带约束,不执行)。
|
|
2
|
+
|
|
3
|
+
本层是纯数据变换:输入 :class:`Document`(语法产物)与 :class:`ResolvedContext`
|
|
4
|
+
(Phase 1 产物,数据模型),输出 :class:`StdDocument`。**不持有任何其他阶段对象**
|
|
5
|
+
(无 resolver / 无 executor / 无 registry / 无 schema)——子模块间仅经数据模型依赖。
|
|
6
|
+
|
|
7
|
+
值语义:字面量 / ``$`` 引用 / 模板展开 / dict 与 array 组装;
|
|
8
|
+
并把源文档约束解析为 :class:`ResolvedConstraint` 挂到节点(**不执行**)。
|
|
9
|
+
约束执行与顶层 schema 校验由 Phase 2b(:class:`~infinity_data.semantic.executor.ConstraintExecutor`)
|
|
10
|
+
在流水线中独立完成。
|
|
11
|
+
|
|
12
|
+
诊断写入调用方注入的共享 :class:`DiagnosticCollector`(流水线单一收集器),
|
|
13
|
+
本层不持有诊断列表、不产出诊断数据。
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import decimal
|
|
19
|
+
from collections.abc import Iterator
|
|
20
|
+
from typing import Any, cast
|
|
21
|
+
|
|
22
|
+
from infinity_data.infra.diagnostics import Diagnostic, DiagnosticCollector, Severity
|
|
23
|
+
from infinity_data.parser import (
|
|
24
|
+
ArrayValue,
|
|
25
|
+
ConstraintStmt,
|
|
26
|
+
DictValue,
|
|
27
|
+
Document,
|
|
28
|
+
DollarValue,
|
|
29
|
+
EnvImportStmt,
|
|
30
|
+
ErrorValue,
|
|
31
|
+
Field,
|
|
32
|
+
FileImportStmt,
|
|
33
|
+
LiteralValue,
|
|
34
|
+
TemplateCallValue,
|
|
35
|
+
TemplateDef,
|
|
36
|
+
TemplateImportStmt,
|
|
37
|
+
Value,
|
|
38
|
+
)
|
|
39
|
+
from infinity_data.semantic.builder.models import (
|
|
40
|
+
ResolvedConstraint,
|
|
41
|
+
StdArray,
|
|
42
|
+
StdDocument,
|
|
43
|
+
StdField,
|
|
44
|
+
StdLiteral,
|
|
45
|
+
StdObject,
|
|
46
|
+
StdValue,
|
|
47
|
+
)
|
|
48
|
+
from infinity_data.semantic.constraints import resolve_constraint_list, resolve_constraints
|
|
49
|
+
from infinity_data.semantic.resolver.models import ResolvedContext, Scope, TemplateKey
|
|
50
|
+
from infinity_data.tokenizer.models.raw_tokens import SourceRange
|
|
51
|
+
from infinity_data.tokenizer.models.tokens import (
|
|
52
|
+
BoolToken,
|
|
53
|
+
FloatToken,
|
|
54
|
+
IntegerToken,
|
|
55
|
+
NoexistToken,
|
|
56
|
+
NullToken,
|
|
57
|
+
StringToken,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
MAX_NESTING_DEPTH = 200
|
|
61
|
+
"""值嵌套深度上限,防止递归下降导致 RecursionError。"""
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class AstBuilder:
|
|
65
|
+
"""AST 构建器(Phase 2a):RawAst + ResolvedContext → StdAst(携带约束)。
|
|
66
|
+
|
|
67
|
+
只做「值是什么」:字面量 / ``$`` 引用 / 模板展开 / dict 与 array 组装,
|
|
68
|
+
并把源文档约束解析为 :class:`ResolvedConstraint` 挂到节点;
|
|
69
|
+
**不执行任何约束**——校验由 Phase 2b 完成,本层与执行器零耦合。
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
def __init__(self) -> None:
|
|
73
|
+
# 执行期状态(每次 build 重置)
|
|
74
|
+
self._templates: dict[TemplateKey, TemplateDef] = {}
|
|
75
|
+
self._template_scopes: dict[TemplateKey, Scope] = {}
|
|
76
|
+
self._root_scope: Scope = {}
|
|
77
|
+
self._schema_scope: Scope | None = None
|
|
78
|
+
self._namespace: dict[str, Any] = {} # $ 引用解析目标
|
|
79
|
+
self._collector: DiagnosticCollector = DiagnosticCollector() # 本次 build 的共享收集器
|
|
80
|
+
self._depth = 0
|
|
81
|
+
self._recursive_defaults: set[TemplateKey] = set()
|
|
82
|
+
|
|
83
|
+
# ═══════════════════════════════════════════════════════
|
|
84
|
+
# 公开入口
|
|
85
|
+
# ═══════════════════════════════════════════════════════
|
|
86
|
+
|
|
87
|
+
def build(self, doc: Document, context: ResolvedContext, collector: DiagnosticCollector) -> StdDocument:
|
|
88
|
+
"""构建带约束的 StdAst(纯数据变换,不执行约束)。
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
doc: 语法分析产物
|
|
92
|
+
context: Phase 1 产物(模板图 / 可见名表 / 命名空间,数据模型)
|
|
93
|
+
collector: 共享诊断收集器(流水线单一收集器)
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
:class:`StdDocument`——root 已构建、约束已挂载未执行;
|
|
97
|
+
``diagnostics`` 字段为空(诊断全部进 ``collector``,由流水线末端快照)。
|
|
98
|
+
"""
|
|
99
|
+
self._templates = {}
|
|
100
|
+
self._template_scopes = {}
|
|
101
|
+
self._root_scope = {}
|
|
102
|
+
self._schema_scope = None
|
|
103
|
+
self._namespace = {}
|
|
104
|
+
self._collector = collector
|
|
105
|
+
self._depth = 0
|
|
106
|
+
self._recursive_defaults = set()
|
|
107
|
+
|
|
108
|
+
# Phase 1 产物注入执行期状态(按引用共享,约定只读)
|
|
109
|
+
self._templates = context.templates
|
|
110
|
+
self._template_scopes = context.template_scopes
|
|
111
|
+
self._root_scope = context.root_scope
|
|
112
|
+
self._schema_scope = context.schema_scope
|
|
113
|
+
self._namespace = context.namespace
|
|
114
|
+
|
|
115
|
+
# 静态防护:默认值引用环检测(默认值禁止自引用,见 neo_desg.md 2.6)
|
|
116
|
+
self._detect_recursive_defaults()
|
|
117
|
+
|
|
118
|
+
# 构建 root(顶层结构约束挂在 root.constraints,不执行)
|
|
119
|
+
root_fields: list[StdField] = []
|
|
120
|
+
root_constraints: list[ResolvedConstraint] = []
|
|
121
|
+
for stmt in doc.statements:
|
|
122
|
+
match stmt:
|
|
123
|
+
case TemplateDef() | TemplateImportStmt() | EnvImportStmt() | FileImportStmt():
|
|
124
|
+
continue # 模板定义与导入不产生输出
|
|
125
|
+
case Field():
|
|
126
|
+
f = self._build_field(stmt, path=stmt.name, scope=self._root_scope)
|
|
127
|
+
if f is not None:
|
|
128
|
+
root_fields.append(f)
|
|
129
|
+
case ConstraintStmt(constraints=cs):
|
|
130
|
+
specs, diags = resolve_constraint_list(cs, self._root_scope)
|
|
131
|
+
root_constraints.extend(specs)
|
|
132
|
+
self._collector.extend(diags)
|
|
133
|
+
case _:
|
|
134
|
+
pass # ErrorStatement 已在语法阶段诊断
|
|
135
|
+
|
|
136
|
+
return StdDocument(
|
|
137
|
+
root=StdObject(fields=root_fields, constraints=root_constraints),
|
|
138
|
+
templates=dict(self._templates),
|
|
139
|
+
scope=dict(self._root_scope),
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
# ═══════════════════════════════════════════════════════
|
|
143
|
+
# 递归默认值防护(方案 C:默认引用图 + 静态环检测)
|
|
144
|
+
# ═══════════════════════════════════════════════════════
|
|
145
|
+
|
|
146
|
+
def _detect_recursive_defaults(self) -> None:
|
|
147
|
+
"""静态检测默认值引用环,标记禁展开模板并报告根因。
|
|
148
|
+
|
|
149
|
+
模板实例化是原地展开;若某模板的默认值(经引用链)能回到自身,展开
|
|
150
|
+
永不终止。只有**默认值**参与建图——调用方提供的值是新鲜的、有限的,
|
|
151
|
+
不会引发无限展开。检测在展开前一次性完成,避免运行时白白展开
|
|
152
|
+
``MAX_NESTING_DEPTH`` 层再报错。
|
|
153
|
+
"""
|
|
154
|
+
graph: dict[TemplateKey, set[TemplateKey]] = {}
|
|
155
|
+
sources: dict[TemplateKey, dict[TemplateKey, SourceRange | None]] = {}
|
|
156
|
+
|
|
157
|
+
for key, tpl in self._templates.items():
|
|
158
|
+
scope = self._template_scopes.get(key, self._root_scope)
|
|
159
|
+
refs: set[TemplateKey] = set()
|
|
160
|
+
ref_src: dict[TemplateKey, SourceRange | None] = {}
|
|
161
|
+
for tf in tpl.fields:
|
|
162
|
+
for dep, src in self._iter_default_refs(tf.default_value, scope):
|
|
163
|
+
if dep not in refs:
|
|
164
|
+
refs.add(dep)
|
|
165
|
+
ref_src[dep] = src
|
|
166
|
+
graph[key] = refs
|
|
167
|
+
sources[key] = ref_src
|
|
168
|
+
|
|
169
|
+
# DFS 三色标记找环:GRAY(1) 表示在当前展开栈上
|
|
170
|
+
self._recursive_defaults = set()
|
|
171
|
+
color: dict[TemplateKey, int] = {}
|
|
172
|
+
stack: list[TemplateKey] = []
|
|
173
|
+
|
|
174
|
+
def visit(key: TemplateKey) -> None:
|
|
175
|
+
color[key] = 1
|
|
176
|
+
stack.append(key)
|
|
177
|
+
for dep in graph.get(key, ()):
|
|
178
|
+
c = color.get(dep, 0)
|
|
179
|
+
if c == 0:
|
|
180
|
+
visit(dep)
|
|
181
|
+
elif c == 1:
|
|
182
|
+
start = stack.index(dep)
|
|
183
|
+
self._recursive_defaults.update(stack[start:])
|
|
184
|
+
stack.pop()
|
|
185
|
+
color[key] = 2
|
|
186
|
+
|
|
187
|
+
for key in self._templates:
|
|
188
|
+
if color.get(key, 0) == 0:
|
|
189
|
+
visit(key)
|
|
190
|
+
|
|
191
|
+
# 报告:定位到环上模板的默认值字段(根因),而非展开深处
|
|
192
|
+
for key in self._recursive_defaults:
|
|
193
|
+
src = next((s for dep, s in sources.get(key, {}).items() if dep in self._recursive_defaults), None)
|
|
194
|
+
self._collector.add(
|
|
195
|
+
Diagnostic(
|
|
196
|
+
Severity.ERROR,
|
|
197
|
+
'template.recursive_default',
|
|
198
|
+
{'template': key.name},
|
|
199
|
+
src,
|
|
200
|
+
)
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
def _iter_default_refs(
|
|
204
|
+
self,
|
|
205
|
+
v: Value | None,
|
|
206
|
+
scope: Scope,
|
|
207
|
+
) -> Iterator[tuple[TemplateKey, SourceRange | None]]:
|
|
208
|
+
"""遍历默认值 Value 树,产出其中的模板调用(真名, 调用位置)。"""
|
|
209
|
+
if v is None:
|
|
210
|
+
return
|
|
211
|
+
match v:
|
|
212
|
+
case TemplateCallValue(template_name=name, source=src):
|
|
213
|
+
k = scope.get(name)
|
|
214
|
+
if k is not None:
|
|
215
|
+
yield k, src
|
|
216
|
+
case ArrayValue(elements=els):
|
|
217
|
+
for e in els:
|
|
218
|
+
yield from self._iter_default_refs(e, scope)
|
|
219
|
+
case DictValue(fields=fs):
|
|
220
|
+
for f in fs:
|
|
221
|
+
yield from self._iter_default_refs(f.value, scope)
|
|
222
|
+
case _:
|
|
223
|
+
pass # 字面量 / $ 引用 / 错误值不含模板调用
|
|
224
|
+
|
|
225
|
+
# ═══════════════════════════════════════════════════════
|
|
226
|
+
# 字段构建
|
|
227
|
+
# ═══════════════════════════════════════════════════════
|
|
228
|
+
|
|
229
|
+
def _build_field(self, field: Field, path: str, scope: Scope) -> StdField | None:
|
|
230
|
+
"""构建字段:解析值 + 解析注解约束(挂到节点,不执行)。"""
|
|
231
|
+
value = self._resolve_value(field.value, path, scope)
|
|
232
|
+
# 值缺失:设计文档未定义「裸 key」,noexist 需显式字面量
|
|
233
|
+
if value is None:
|
|
234
|
+
if not isinstance(field.value, ErrorValue):
|
|
235
|
+
# 值解析失败已由语法层报告(parse.unrecognized_value 等),不重复报
|
|
236
|
+
self._collector.add(Diagnostic(Severity.ERROR, 'field.missing_value', {}, field.source, path))
|
|
237
|
+
return StdField(name=field.name, value=None, source=field.source)
|
|
238
|
+
specs: list[ResolvedConstraint] = []
|
|
239
|
+
if field.constraints is not None:
|
|
240
|
+
specs, diags = resolve_constraints(field.constraints, scope)
|
|
241
|
+
self._collector.extend(diags)
|
|
242
|
+
return StdField(name=field.name, value=value, source=field.source, constraints=specs)
|
|
243
|
+
|
|
244
|
+
# ═══════════════════════════════════════════════════════
|
|
245
|
+
# 值解析
|
|
246
|
+
# ═══════════════════════════════════════════════════════
|
|
247
|
+
|
|
248
|
+
def _resolve_value(self, raw: Value | None, path: str, scope: Scope) -> StdValue | None:
|
|
249
|
+
"""将 RawAst Value 转换为 StdValue。"""
|
|
250
|
+
if raw is None:
|
|
251
|
+
return None
|
|
252
|
+
|
|
253
|
+
self._depth += 1
|
|
254
|
+
try:
|
|
255
|
+
if self._depth > MAX_NESTING_DEPTH:
|
|
256
|
+
self._collector.add(
|
|
257
|
+
Diagnostic(Severity.ERROR, 'value.nesting_depth', {'max': MAX_NESTING_DEPTH}, raw.source, path)
|
|
258
|
+
)
|
|
259
|
+
return None
|
|
260
|
+
|
|
261
|
+
match raw:
|
|
262
|
+
case LiteralValue(value=tok):
|
|
263
|
+
return self._convert_literal(tok)
|
|
264
|
+
case DollarValue(name=n, type_cast=tc):
|
|
265
|
+
return self._resolve_dollar(n, tc, path)
|
|
266
|
+
case DictValue(fields=fs, constraints=cs):
|
|
267
|
+
std_fields: list[StdField] = []
|
|
268
|
+
for f in fs:
|
|
269
|
+
child = f'{path}.{f.name}' if path else f.name
|
|
270
|
+
sf = self._build_field(f, path=child, scope=scope)
|
|
271
|
+
if sf is not None:
|
|
272
|
+
std_fields.append(sf)
|
|
273
|
+
# dict 结构级约束(作用于该字面量整体):解析后挂节点,不执行
|
|
274
|
+
specs, diags = resolve_constraint_list(cs, scope)
|
|
275
|
+
self._collector.extend(diags)
|
|
276
|
+
return StdObject(fields=std_fields, constraints=specs)
|
|
277
|
+
case ArrayValue(elements=els):
|
|
278
|
+
std_elements: list[StdValue] = []
|
|
279
|
+
for i, e in enumerate(els):
|
|
280
|
+
rv = self._resolve_value(e, f'{path}[{i}]', scope)
|
|
281
|
+
if rv is None:
|
|
282
|
+
continue
|
|
283
|
+
if isinstance(rv, StdLiteral) and rv.kind == 'noexist':
|
|
284
|
+
# noexist 仅用于 dict 字段;数组元素中无意义 → 报错并按 null 处理
|
|
285
|
+
self._collector.add(
|
|
286
|
+
Diagnostic(
|
|
287
|
+
Severity.ERROR,
|
|
288
|
+
'value.noexist_in_array',
|
|
289
|
+
{},
|
|
290
|
+
e.source,
|
|
291
|
+
f'{path}[{i}]',
|
|
292
|
+
)
|
|
293
|
+
)
|
|
294
|
+
rv = StdLiteral(kind='null', value=None)
|
|
295
|
+
std_elements.append(rv)
|
|
296
|
+
return StdArray(elements=std_elements)
|
|
297
|
+
case TemplateCallValue(
|
|
298
|
+
template_name=tn,
|
|
299
|
+
positional_args=pa,
|
|
300
|
+
named_args=na,
|
|
301
|
+
):
|
|
302
|
+
return self._expand_template_call(tn, pa, na, path, raw.source, scope)
|
|
303
|
+
case ErrorValue():
|
|
304
|
+
# 值解析失败已在语法层报告(parse.value_field / parse.unrecognized_value),不重复
|
|
305
|
+
return None
|
|
306
|
+
return None
|
|
307
|
+
finally:
|
|
308
|
+
self._depth -= 1
|
|
309
|
+
|
|
310
|
+
def _convert_literal(
|
|
311
|
+
self,
|
|
312
|
+
tok: StringToken | IntegerToken | FloatToken | BoolToken | NullToken | NoexistToken,
|
|
313
|
+
) -> StdLiteral:
|
|
314
|
+
"""将字面量 Token 转为 StdLiteral。"""
|
|
315
|
+
match tok:
|
|
316
|
+
case StringToken(value=v):
|
|
317
|
+
return StdLiteral(kind='str', value=v)
|
|
318
|
+
case IntegerToken(value=v):
|
|
319
|
+
return StdLiteral(kind='int', value=v)
|
|
320
|
+
case FloatToken(value=v):
|
|
321
|
+
return StdLiteral(kind='float', value=v) # Decimal,含 NaN/±Inf
|
|
322
|
+
case BoolToken(value=v):
|
|
323
|
+
return StdLiteral(kind='bool', value=v)
|
|
324
|
+
case NullToken():
|
|
325
|
+
return StdLiteral(kind='null', value=None)
|
|
326
|
+
case NoexistToken():
|
|
327
|
+
return StdLiteral(kind='noexist', value=None)
|
|
328
|
+
raise TypeError(f'未知字面量 token 类型: {type(tok)}')
|
|
329
|
+
|
|
330
|
+
def _resolve_dollar(self, name: str, type_cast: str | None, path: str) -> StdValue:
|
|
331
|
+
"""解析 ``$name`` 引用,type_cast 为显式 as bool/int/float/str 转换。"""
|
|
332
|
+
if name not in self._namespace:
|
|
333
|
+
self._collector.add(Diagnostic(Severity.WARNING, 'dollar.undefined', {'name': name}, path=path))
|
|
334
|
+
return StdLiteral(kind='null', value=None)
|
|
335
|
+
|
|
336
|
+
raw = self._namespace[name]
|
|
337
|
+
if type_cast is None:
|
|
338
|
+
return self._auto_literal(raw)
|
|
339
|
+
|
|
340
|
+
match type_cast:
|
|
341
|
+
case 'bool':
|
|
342
|
+
if isinstance(raw, bool):
|
|
343
|
+
val: bool = raw
|
|
344
|
+
elif isinstance(raw, str):
|
|
345
|
+
val = raw.lower() in ('true', '1')
|
|
346
|
+
elif isinstance(raw, int | float):
|
|
347
|
+
val = bool(raw)
|
|
348
|
+
else:
|
|
349
|
+
val = False
|
|
350
|
+
return StdLiteral(kind='bool', value=val)
|
|
351
|
+
case 'int':
|
|
352
|
+
try:
|
|
353
|
+
return StdLiteral(kind='int', value=int(raw))
|
|
354
|
+
except (ValueError, TypeError):
|
|
355
|
+
self._collector.add(
|
|
356
|
+
Diagnostic(
|
|
357
|
+
Severity.WARNING,
|
|
358
|
+
'dollar.convert_failed',
|
|
359
|
+
{'name': name, 'raw': raw, 'type': 'int'},
|
|
360
|
+
path=path,
|
|
361
|
+
)
|
|
362
|
+
)
|
|
363
|
+
return StdLiteral(kind='int', value=0)
|
|
364
|
+
case 'float':
|
|
365
|
+
try:
|
|
366
|
+
return StdLiteral(kind='float', value=decimal.Decimal(str(raw)))
|
|
367
|
+
except (ValueError, TypeError, decimal.InvalidOperation):
|
|
368
|
+
self._collector.add(
|
|
369
|
+
Diagnostic(
|
|
370
|
+
Severity.WARNING,
|
|
371
|
+
'dollar.convert_failed',
|
|
372
|
+
{'name': name, 'raw': raw, 'type': 'float'},
|
|
373
|
+
path=path,
|
|
374
|
+
)
|
|
375
|
+
)
|
|
376
|
+
return StdLiteral(kind='float', value=decimal.Decimal(0))
|
|
377
|
+
case 'str':
|
|
378
|
+
return StdLiteral(kind='str', value=str(raw))
|
|
379
|
+
case _:
|
|
380
|
+
return self._auto_literal(raw)
|
|
381
|
+
|
|
382
|
+
def _auto_literal(self, raw: Any) -> StdValue:
|
|
383
|
+
"""根据 Python 类型自动推断 StdValue(导入数据)。"""
|
|
384
|
+
if raw is None:
|
|
385
|
+
return StdLiteral(kind='null', value=None)
|
|
386
|
+
if isinstance(raw, bool):
|
|
387
|
+
return StdLiteral(kind='bool', value=raw)
|
|
388
|
+
if isinstance(raw, int):
|
|
389
|
+
return StdLiteral(kind='int', value=raw)
|
|
390
|
+
if isinstance(raw, decimal.Decimal):
|
|
391
|
+
return StdLiteral(kind='float', value=raw)
|
|
392
|
+
if isinstance(raw, float):
|
|
393
|
+
return StdLiteral(kind='float', value=decimal.Decimal(str(raw)))
|
|
394
|
+
if isinstance(raw, str):
|
|
395
|
+
return StdLiteral(kind='str', value=raw)
|
|
396
|
+
if isinstance(raw, list):
|
|
397
|
+
items = cast(list[Any], raw)
|
|
398
|
+
return StdArray(elements=[self._auto_literal(e) for e in items])
|
|
399
|
+
if isinstance(raw, dict):
|
|
400
|
+
mapping = cast(dict[Any, Any], raw)
|
|
401
|
+
return StdObject(fields=[StdField(name=str(k), value=self._auto_literal(v)) for k, v in mapping.items()])
|
|
402
|
+
return StdLiteral(kind='str', value=str(raw))
|
|
403
|
+
|
|
404
|
+
# ═══════════════════════════════════════════════════════
|
|
405
|
+
# 模板展开
|
|
406
|
+
# ═══════════════════════════════════════════════════════
|
|
407
|
+
|
|
408
|
+
def _expand_template_call(
|
|
409
|
+
self,
|
|
410
|
+
template_name: str,
|
|
411
|
+
positional_args: list[Value],
|
|
412
|
+
named_args: dict[str, Value],
|
|
413
|
+
path: str,
|
|
414
|
+
source: SourceRange | None,
|
|
415
|
+
scope: Scope,
|
|
416
|
+
) -> StdValue:
|
|
417
|
+
"""展开模板调用为 StdObject(名字经调用点 scope 翻译,展开用模板定义点 scope)。
|
|
418
|
+
|
|
419
|
+
字段 / 模板级约束解析后挂到节点(不执行),由执行器统一校验。
|
|
420
|
+
"""
|
|
421
|
+
key = scope.get(template_name)
|
|
422
|
+
if key is None:
|
|
423
|
+
self._collector.add(
|
|
424
|
+
Diagnostic(Severity.ERROR, 'template.undefined', {'template': template_name}, source, path)
|
|
425
|
+
)
|
|
426
|
+
return StdObject()
|
|
427
|
+
# 静态环检测已标记:默认值递归引用,禁止展开(根因错误已在定义处报告)
|
|
428
|
+
if key in self._recursive_defaults:
|
|
429
|
+
return StdObject()
|
|
430
|
+
template = self._templates[key]
|
|
431
|
+
inner_scope = self._template_scopes[key]
|
|
432
|
+
|
|
433
|
+
required = [tf for tf in template.fields if tf.default_value is None]
|
|
434
|
+
|
|
435
|
+
# 模板配置 positional=false:位置参数违规报错,但值仍绑定必填字段
|
|
436
|
+
# (放宽必填绑定:只报 positional_disabled 一条,避免 missing_required 级联)
|
|
437
|
+
if not template.config.positional and positional_args:
|
|
438
|
+
self._collector.add(
|
|
439
|
+
Diagnostic(
|
|
440
|
+
Severity.ERROR,
|
|
441
|
+
'template.positional_disabled',
|
|
442
|
+
{'template': template_name},
|
|
443
|
+
source,
|
|
444
|
+
path,
|
|
445
|
+
)
|
|
446
|
+
)
|
|
447
|
+
|
|
448
|
+
# 未知命名参数 → ERROR(拒绝静默忽略)
|
|
449
|
+
declared = {tf.name for tf in template.fields}
|
|
450
|
+
for name in named_args:
|
|
451
|
+
if name not in declared:
|
|
452
|
+
self._collector.add(
|
|
453
|
+
Diagnostic(
|
|
454
|
+
Severity.ERROR,
|
|
455
|
+
'template.unknown_argument',
|
|
456
|
+
{'template': template_name, 'arg': name},
|
|
457
|
+
source,
|
|
458
|
+
path,
|
|
459
|
+
)
|
|
460
|
+
)
|
|
461
|
+
|
|
462
|
+
# 参数映射:位置参数按定义顺序绑定必填字段
|
|
463
|
+
param_values: dict[str, Value] = dict(named_args)
|
|
464
|
+
for idx, pos_val in enumerate(positional_args):
|
|
465
|
+
if idx < len(required):
|
|
466
|
+
rf = required[idx]
|
|
467
|
+
if rf.name in param_values:
|
|
468
|
+
self._collector.add(
|
|
469
|
+
Diagnostic(
|
|
470
|
+
Severity.ERROR,
|
|
471
|
+
'template.arg_conflict',
|
|
472
|
+
{'template': template_name, 'field': rf.name},
|
|
473
|
+
source,
|
|
474
|
+
path,
|
|
475
|
+
)
|
|
476
|
+
)
|
|
477
|
+
else:
|
|
478
|
+
param_values[rf.name] = pos_val
|
|
479
|
+
else:
|
|
480
|
+
self._collector.add(
|
|
481
|
+
Diagnostic(
|
|
482
|
+
Severity.WARNING,
|
|
483
|
+
'template.too_many_positional',
|
|
484
|
+
{'template': template_name, 'count': len(required), 'given': len(positional_args)},
|
|
485
|
+
source,
|
|
486
|
+
path,
|
|
487
|
+
)
|
|
488
|
+
)
|
|
489
|
+
|
|
490
|
+
# 必填字段缺失检查
|
|
491
|
+
for rf in required:
|
|
492
|
+
if rf.name not in param_values:
|
|
493
|
+
self._collector.add(
|
|
494
|
+
Diagnostic(
|
|
495
|
+
Severity.ERROR,
|
|
496
|
+
'template.missing_required',
|
|
497
|
+
{'template': template_name, 'field': rf.name},
|
|
498
|
+
source,
|
|
499
|
+
path,
|
|
500
|
+
)
|
|
501
|
+
)
|
|
502
|
+
|
|
503
|
+
# 展开所有字段(参数覆盖 > 默认值 > 缺失)
|
|
504
|
+
# 参数值按调用点 scope 解析;默认值按模板定义点 scope(inner_scope)解析
|
|
505
|
+
std_fields: list[StdField] = []
|
|
506
|
+
for tf in template.fields:
|
|
507
|
+
child = f'{path}.{tf.name}' if path else tf.name
|
|
508
|
+
|
|
509
|
+
if tf.name in param_values:
|
|
510
|
+
v = self._resolve_value(param_values[tf.name], child, scope)
|
|
511
|
+
elif tf.default_value is not None:
|
|
512
|
+
v = self._resolve_value(tf.default_value, child, inner_scope)
|
|
513
|
+
else:
|
|
514
|
+
continue # 必填且未提供 → 已在上面报错
|
|
515
|
+
|
|
516
|
+
specs, diags = resolve_constraints(tf.constraints, inner_scope)
|
|
517
|
+
self._collector.extend(diags)
|
|
518
|
+
std_fields.append(StdField(name=tf.name, value=v, source=tf.source, constraints=specs))
|
|
519
|
+
|
|
520
|
+
# 模板级约束(: 起始,约束整个 dict):解析后挂到实例节点
|
|
521
|
+
tpl_specs, tpl_diags = resolve_constraint_list(template.constraints, inner_scope)
|
|
522
|
+
self._collector.extend(tpl_diags)
|
|
523
|
+
return StdObject(fields=std_fields, template=key, constraints=tpl_specs)
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"""Phase 2a(构建)数据模型:标准 AST(StdAst)与已解析约束。
|
|
2
|
+
|
|
3
|
+
本子模块**只定义数据**,不包含任何构建逻辑(构建器见 :mod:`builder`)。
|
|
4
|
+
Phase 2b(约束执行)通过本层模型消费构建产物——子模块间仅经数据模型依赖。
|
|
5
|
+
|
|
6
|
+
- 三态可空:``noexist``(键不存在)/ ``null``(键存在值为 null)/ value
|
|
7
|
+
- 浮点统一为 :class:`decimal.Decimal`(规范要求无限精度十进制浮点)
|
|
8
|
+
- ``nan`` / ``+inf`` / ``-inf`` 以 ``Decimal("NaN")`` / ``Decimal("Infinity")`` /
|
|
9
|
+
``Decimal("-Infinity")`` 表示,kind 均为 ``"float"``
|
|
10
|
+
- 节点携带已解析约束(:class:`ResolvedConstraint`),由 Phase 2b 遍历执行
|
|
11
|
+
(只校验,不转换)
|
|
12
|
+
|
|
13
|
+
模板真名 :class:`TemplateKey` 与可见名表 :class:`Scope` 属 Phase 1 数据模型
|
|
14
|
+
(:mod:`infinity_data.semantic.resolver.models`),本层仅消费。
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import decimal
|
|
20
|
+
from dataclasses import dataclass, field
|
|
21
|
+
from typing import TYPE_CHECKING, Any, Literal
|
|
22
|
+
|
|
23
|
+
from infinity_data.infra.location import SourceRange
|
|
24
|
+
from infinity_data.parser import TemplateDef
|
|
25
|
+
|
|
26
|
+
if TYPE_CHECKING:
|
|
27
|
+
# 仅类型注解使用(from __future__ import annotations 下为惰性字符串):
|
|
28
|
+
# 运行时导入会经 resolver/__init__ → resolver.resolver → registry → builder.models
|
|
29
|
+
# 形成循环(emit.converter 引导导入期间)。注解不发散,TYPE_CHECKING 即可。
|
|
30
|
+
from infinity_data.semantic.resolver.models import Scope, TemplateKey
|
|
31
|
+
|
|
32
|
+
__all__ = [
|
|
33
|
+
'ResolvedConstraint',
|
|
34
|
+
'StdArray',
|
|
35
|
+
'StdDocument',
|
|
36
|
+
'StdField',
|
|
37
|
+
'StdLiteral',
|
|
38
|
+
'StdObject',
|
|
39
|
+
'StdValue',
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
LiteralKind = Literal['str', 'int', 'float', 'bool', 'null', 'noexist']
|
|
43
|
+
"""字面量 kind 枚举。"""
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
# ═══════════════════════════════════════════════════════════
|
|
47
|
+
# 已解析约束
|
|
48
|
+
# ═══════════════════════════════════════════════════════════
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@dataclass
|
|
52
|
+
class ResolvedConstraint:
|
|
53
|
+
"""已解析的约束(挂在 StdAst 节点上,由执行器消费)。
|
|
54
|
+
|
|
55
|
+
- ``name``:约束真名(模板名已经 scope 翻译)
|
|
56
|
+
- ``args``:参数(字面量 → Python 值;嵌套约束 → :class:`ResolvedConstraint`)
|
|
57
|
+
- ``source``:约束表达式来源(诊断寻址)
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
name: str
|
|
61
|
+
args: list[Any] = field(default_factory=list[Any])
|
|
62
|
+
source: SourceRange | None = None
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
# ═══════════════════════════════════════════════════════════
|
|
66
|
+
# 值
|
|
67
|
+
# ═══════════════════════════════════════════════════════════
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@dataclass
|
|
71
|
+
class StdLiteral:
|
|
72
|
+
"""标准字面量值。
|
|
73
|
+
|
|
74
|
+
kind 与 Python 值的对应:
|
|
75
|
+
- ``"str"`` → str
|
|
76
|
+
- ``"int"`` → int
|
|
77
|
+
- ``"float"`` → Decimal(含 NaN / ±Infinity)
|
|
78
|
+
- ``"bool"`` → bool
|
|
79
|
+
- ``"null"`` → None
|
|
80
|
+
- ``"noexist"``→ None(键不出现在结果中)
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
kind: LiteralKind
|
|
84
|
+
value: str | int | decimal.Decimal | bool | None
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
type StdValue = StdLiteral | StdArray | StdObject
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@dataclass
|
|
91
|
+
class StdField:
|
|
92
|
+
"""标准字段:名称 + 值 + 来源信息 + 注解约束。
|
|
93
|
+
|
|
94
|
+
``constraints``:字段注解约束(``key: <c> = v``),已解析未执行。
|
|
95
|
+
"""
|
|
96
|
+
|
|
97
|
+
name: str
|
|
98
|
+
value: StdValue | None
|
|
99
|
+
source: SourceRange | None = None
|
|
100
|
+
constraints: list[ResolvedConstraint] = field(default_factory=list[ResolvedConstraint])
|
|
101
|
+
|
|
102
|
+
@property
|
|
103
|
+
def is_noexist(self) -> bool:
|
|
104
|
+
"""是否为 noexist 标记(键不出现在结果中)。"""
|
|
105
|
+
return isinstance(self.value, StdLiteral) and self.value.kind == 'noexist'
|
|
106
|
+
|
|
107
|
+
@property
|
|
108
|
+
def is_null(self) -> bool:
|
|
109
|
+
"""值是否为 null。"""
|
|
110
|
+
return isinstance(self.value, StdLiteral) and self.value.kind == 'null'
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
@dataclass
|
|
114
|
+
class StdArray:
|
|
115
|
+
"""标准数组值。"""
|
|
116
|
+
|
|
117
|
+
elements: list[StdValue] = field(default_factory=list[StdValue])
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
@dataclass
|
|
121
|
+
class StdObject:
|
|
122
|
+
"""标准对象值。
|
|
123
|
+
|
|
124
|
+
- ``fields``:字段列表
|
|
125
|
+
- ``template``:可选的来源模板(:class:`TemplateKey`)。模板展开的实例,
|
|
126
|
+
或经「模板即约束」校验的手写 dict 会携带;None = 无关联模板(纯字面量)
|
|
127
|
+
- ``constraints``:结构级约束(``: <...>`` 作用于整个 dict,含模板级约束),
|
|
128
|
+
已解析未执行
|
|
129
|
+
"""
|
|
130
|
+
|
|
131
|
+
fields: list[StdField] = field(default_factory=list[StdField])
|
|
132
|
+
template: TemplateKey | None = None
|
|
133
|
+
constraints: list[ResolvedConstraint] = field(default_factory=list[ResolvedConstraint])
|
|
134
|
+
|
|
135
|
+
def get(self, name: str) -> StdField | None:
|
|
136
|
+
"""按名称查找字段(无则 None)。"""
|
|
137
|
+
for f in self.fields:
|
|
138
|
+
if f.name == name:
|
|
139
|
+
return f
|
|
140
|
+
return None
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
# ═══════════════════════════════════════════════════════════
|
|
144
|
+
# 文档
|
|
145
|
+
# ═══════════════════════════════════════════════════════════
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
@dataclass
|
|
149
|
+
class StdDocument:
|
|
150
|
+
"""标准文档(**纯数据**):顶层对象 + 模板表与可见名表。
|
|
151
|
+
|
|
152
|
+
- ``root``:编译产物(顶层对象)
|
|
153
|
+
- ``templates``:全部已加载模板(:class:`TemplateKey` → 定义,含 ``!from`` 导入的)
|
|
154
|
+
- ``scope``:**入口文件**的可见名表(可见名 → :class:`TemplateKey`),
|
|
155
|
+
与 ``templates`` 配合可完整解析:可见名 → TemplateKey → TemplateDef
|
|
156
|
+
|
|
157
|
+
不携带诊断:所有诊断经共享 :class:`DiagnosticCollector` 收集,
|
|
158
|
+
由流水线在 :class:`CompilationResult` 上承载——诊断不属于文档数据。
|
|
159
|
+
"""
|
|
160
|
+
|
|
161
|
+
root: StdObject = field(default_factory=StdObject)
|
|
162
|
+
templates: dict[TemplateKey, TemplateDef] = field(default_factory=lambda: {})
|
|
163
|
+
scope: Scope = field(default_factory=lambda: {})
|