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.
Files changed (52) hide show
  1. infinity_data/__init__.py +41 -0
  2. infinity_data/emit/__init__.py +9 -0
  3. infinity_data/emit/converter.py +55 -0
  4. infinity_data/frontend.py +37 -0
  5. infinity_data/infra/__init__.py +4 -0
  6. infinity_data/infra/diagnostics.py +212 -0
  7. infinity_data/infra/file.py +83 -0
  8. infinity_data/infra/ll1_stream.py +71 -0
  9. infinity_data/infra/location.py +80 -0
  10. infinity_data/infra/path.py +46 -0
  11. infinity_data/parser/__init__.py +79 -0
  12. infinity_data/parser/diagnostics.py +92 -0
  13. infinity_data/parser/models.py +295 -0
  14. infinity_data/parser/parser.py +988 -0
  15. infinity_data/parser/token_stream.py +128 -0
  16. infinity_data/pipeline.py +252 -0
  17. infinity_data/sandbox/__init__.py +32 -0
  18. infinity_data/sandbox/config.py +61 -0
  19. infinity_data/sandbox/errors.py +106 -0
  20. infinity_data/sandbox/mediator.py +214 -0
  21. infinity_data/sandbox/schema.py +21 -0
  22. infinity_data/semantic/__init__.py +59 -0
  23. infinity_data/semantic/builder/__init__.py +29 -0
  24. infinity_data/semantic/builder/builder.py +523 -0
  25. infinity_data/semantic/builder/models.py +163 -0
  26. infinity_data/semantic/constraints.py +163 -0
  27. infinity_data/semantic/diagnostics.py +172 -0
  28. infinity_data/semantic/executor/__init__.py +10 -0
  29. infinity_data/semantic/executor/executor.py +259 -0
  30. infinity_data/semantic/registry/__init__.py +168 -0
  31. infinity_data/semantic/registry/_core.py +212 -0
  32. infinity_data/semantic/registry/dict_constraints.py +56 -0
  33. infinity_data/semantic/registry/general.py +362 -0
  34. infinity_data/semantic/registry/logic.py +134 -0
  35. infinity_data/semantic/registry/types.py +136 -0
  36. infinity_data/semantic/resolver/__init__.py +20 -0
  37. infinity_data/semantic/resolver/imports.py +191 -0
  38. infinity_data/semantic/resolver/models.py +67 -0
  39. infinity_data/semantic/resolver/resolver.py +345 -0
  40. infinity_data/tokenizer/__init__.py +16 -0
  41. infinity_data/tokenizer/char_stream.py +72 -0
  42. infinity_data/tokenizer/diagnostics.py +62 -0
  43. infinity_data/tokenizer/finalizer.py +271 -0
  44. infinity_data/tokenizer/models/__init__.py +17 -0
  45. infinity_data/tokenizer/models/raw_tokens.py +61 -0
  46. infinity_data/tokenizer/models/tokens.py +245 -0
  47. infinity_data/tokenizer/tokenizer.py +531 -0
  48. infinity_data-1.0.0.dist-info/METADATA +60 -0
  49. infinity_data-1.0.0.dist-info/RECORD +52 -0
  50. infinity_data-1.0.0.dist-info/WHEEL +5 -0
  51. infinity_data-1.0.0.dist-info/licenses/LICENSE +21 -0
  52. 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: {})