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,168 @@
1
+ """约束注册表:内置约束 + 用户自定义(模板即约束)。
2
+
3
+ - 表驱动:每个约束登记 ``(name, fn, min_args, max_args, description)``
4
+ - 内置约束按类别拆分子模块:types / general / dict_constraints / logic,
5
+ 注册表在此组装(单注册表 = 唯一执行入口)
6
+ - ``executor`` 回调支持嵌套约束:``each`` / ``not`` / ``any`` / ``one`` /
7
+ ``all`` / ``when`` / ``field`` / 模板约束
8
+ - M5 将在 :class:`ConstraintEntry` 上追加 json_schema 生成回调,
9
+ 实现「模板即约束 → JSON Schema」的单注册表复用
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ from infinity_data.semantic.builder.models import ResolvedConstraint, StdValue
15
+ from infinity_data.semantic.registry._core import (
16
+ ConstraintEntry,
17
+ ConstraintFn,
18
+ ConstraintResult,
19
+ Executor,
20
+ describe,
21
+ fail_result,
22
+ ok_result,
23
+ )
24
+ from infinity_data.semantic.registry.dict_constraints import _check_field, _check_has
25
+ from infinity_data.semantic.registry.general import (
26
+ _check_each,
27
+ _check_email,
28
+ _check_eq,
29
+ _check_hostname,
30
+ _check_in,
31
+ _check_ip,
32
+ _check_ip4,
33
+ _check_ip6,
34
+ _check_negative,
35
+ _check_nonnegative,
36
+ _check_positive,
37
+ _check_range,
38
+ _check_regex,
39
+ _check_size,
40
+ _check_unique,
41
+ _check_url,
42
+ _check_uuid,
43
+ )
44
+ from infinity_data.semantic.registry.logic import (
45
+ _check_all,
46
+ _check_any,
47
+ _check_not,
48
+ _check_one,
49
+ _check_when,
50
+ )
51
+ from infinity_data.semantic.registry.types import (
52
+ _check_bool,
53
+ _check_dict,
54
+ _check_float,
55
+ _check_int,
56
+ _check_list,
57
+ _check_nullable,
58
+ _check_object,
59
+ _check_str,
60
+ )
61
+ from infinity_data.tokenizer.models.raw_tokens import SourceRange
62
+
63
+ __all__ = [
64
+ 'ConstraintRegistry',
65
+ 'ConstraintResult',
66
+ 'ConstraintEntry',
67
+ 'ConstraintFn',
68
+ 'Executor',
69
+ 'ok_result',
70
+ 'fail_result',
71
+ 'describe',
72
+ ]
73
+
74
+
75
+ class ConstraintRegistry:
76
+ """约束注册表:内置约束 + 用户自定义(模板即约束)。"""
77
+
78
+ def __init__(self) -> None:
79
+ self._entries: dict[str, ConstraintEntry] = {}
80
+ self._register_builtins()
81
+
82
+ def register(
83
+ self,
84
+ name: str,
85
+ fn: ConstraintFn,
86
+ *,
87
+ min_args: int = 0,
88
+ max_args: int | None = None,
89
+ description: str = '',
90
+ ) -> None:
91
+ self._entries[name] = ConstraintEntry(
92
+ name=name,
93
+ fn=fn,
94
+ min_args=min_args,
95
+ max_args=max_args,
96
+ description=description,
97
+ )
98
+
99
+ def lookup(self, name: str) -> ConstraintEntry | None:
100
+ return self._entries.get(name)
101
+
102
+ @property
103
+ def names(self) -> list[str]:
104
+ return list(self._entries)
105
+
106
+ def apply(
107
+ self,
108
+ constraint: ResolvedConstraint,
109
+ value: StdValue | None,
110
+ source: SourceRange | None,
111
+ path: str,
112
+ executor: Executor,
113
+ ) -> ConstraintResult:
114
+ """按名称执行约束(含参数个数校验)。"""
115
+ entry = self._entries.get(constraint.name)
116
+ if entry is None:
117
+ return fail_result('constraint.unknown', {'name': constraint.name}, source, path)
118
+ n = len(constraint.args)
119
+ if n < entry.min_args or (entry.max_args is not None and n > entry.max_args):
120
+ expected = str(entry.min_args) if entry.max_args is None else f'{entry.min_args}~{entry.max_args}'
121
+ return fail_result(
122
+ 'constraint.arg_count', {'name': constraint.name, 'expected': expected, 'given': n}, source, path
123
+ )
124
+ return entry.fn(value, source, path, constraint.args, executor)
125
+
126
+ def _register_builtins(self) -> None:
127
+ """注册 neo_desg.md §1.2 全部内置约束。"""
128
+ # ── 类型约束 ──────────────────────────────
129
+ self.register('object', _check_object, description='通用超类型,总是通过')
130
+ self.register('?', _check_nullable, description='纯可空类型:noexist 或 null')
131
+ self.register('int', _check_int, description='有符号整数')
132
+ self.register('float', _check_float, description='十进制浮点(只接受 float,不含 int 提升)')
133
+ self.register('str', _check_str, description='utf-8 字符串')
134
+ self.register('bool', _check_bool, description='布尔值')
135
+ self.register('list', _check_list, description='数组')
136
+ self.register('dict', _check_dict, description='字典')
137
+
138
+ # ── 一般约束 ──────────────────────────────
139
+ self.register('range', _check_range, min_args=1, max_args=2, description='数值范围 range(ge[, le]),可省略一端')
140
+ self.register('size', _check_size, min_args=1, max_args=2, description='集合大小或字符串长度 size(ge[, le])')
141
+ self.register('each', _check_each, min_args=1, max_args=1, description='每个元素均满足 each(constraint)')
142
+ self.register('in', _check_in, min_args=1, description='值在给定选项中 in(choice, ...)')
143
+ self.register('ip', _check_ip, description='IPv4 或 IPv6 地址')
144
+ self.register('ip4', _check_ip4, description='IPv4 地址')
145
+ self.register('ip6', _check_ip6, description='IPv6 地址')
146
+ self.register('regex', _check_regex, min_args=1, max_args=1, description='正则全匹配 regex("re")')
147
+ self.register('email', _check_email, description='邮箱格式')
148
+ self.register('url', _check_url, description='URL 格式')
149
+ self.register('uuid', _check_uuid, description='UUID 格式')
150
+ self.register('hostname', _check_hostname, description='主机名格式')
151
+ self.register('positive', _check_positive, description='正数 (> 0)')
152
+ self.register('negative', _check_negative, description='负数 (< 0)')
153
+ self.register('nonnegative', _check_nonnegative, description='非负数 (>= 0)')
154
+ self.register('eq', _check_eq, min_args=1, max_args=1, description='等于指定值 eq(value)')
155
+ self.register('unique', _check_unique, description='集合元素不重复')
156
+
157
+ # ── 字典约束 ──────────────────────────────
158
+ self.register('has', _check_has, min_args=1, max_args=1, description='包含指定键 has(key)')
159
+ self.register('field', _check_field, min_args=2, max_args=2, description='字段约束 field(name, constraint)')
160
+
161
+ # ── 逻辑约束 ──────────────────────────────
162
+ self.register('not', _check_not, min_args=1, max_args=1, description='内部约束不满足则满足')
163
+ self.register('any', _check_any, min_args=1, description='任意子约束满足')
164
+ self.register('one', _check_one, min_args=1, description='恰好一个子约束满足')
165
+ self.register('all', _check_all, min_args=1, description='全部子约束满足')
166
+ self.register(
167
+ 'when', _check_when, min_args=2, max_args=2, description='条件满足时要求另一约束满足 when(cond, req)'
168
+ )
@@ -0,0 +1,212 @@
1
+ """约束核心类型与共享辅助(注册表包的内部基础层)。
2
+
3
+ - 执行结果 / 嵌套回调协议 / 登记项
4
+ - 人类可读描述与数值、嵌套参数辅助
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from collections.abc import Callable, Mapping
10
+ from dataclasses import dataclass, field
11
+ from decimal import Decimal, InvalidOperation
12
+ from typing import Any, Protocol, cast
13
+
14
+ from infinity_data.infra.diagnostics import Diagnostic, Severity
15
+ from infinity_data.semantic.builder.models import (
16
+ ResolvedConstraint,
17
+ StdArray,
18
+ StdField,
19
+ StdLiteral,
20
+ StdObject,
21
+ StdValue,
22
+ )
23
+ from infinity_data.tokenizer.models.raw_tokens import SourceRange
24
+
25
+ # ═══════════════════════════════════════════════════════════
26
+ # 基础类型
27
+ # ═══════════════════════════════════════════════════════════
28
+
29
+
30
+ @dataclass
31
+ class ConstraintResult:
32
+ """约束执行结果(只校验,不转换值)。"""
33
+
34
+ ok: bool
35
+ diagnostics: list[Diagnostic] = field(default_factory=list[Diagnostic])
36
+
37
+
38
+ class Executor(Protocol):
39
+ """嵌套约束执行回调(由 ConstraintExecutor 提供)。"""
40
+
41
+ def __call__(
42
+ self,
43
+ constraint: ResolvedConstraint,
44
+ value: StdValue | None,
45
+ source: SourceRange | None,
46
+ path: str,
47
+ ) -> ConstraintResult: ...
48
+
49
+
50
+ ConstraintFn = Callable[
51
+ [StdValue | None, SourceRange | None, str, list[Any], Executor],
52
+ ConstraintResult,
53
+ ]
54
+
55
+
56
+ @dataclass
57
+ class ConstraintEntry:
58
+ """约束登记项。"""
59
+
60
+ name: str
61
+ fn: ConstraintFn
62
+ min_args: int = 0
63
+ max_args: int | None = None
64
+ description: str = ''
65
+
66
+
67
+ def ok_result() -> ConstraintResult:
68
+ """构造通过结果。"""
69
+ return ConstraintResult(ok=True)
70
+
71
+
72
+ def fail_result(
73
+ code: str,
74
+ params: Mapping[str, Any],
75
+ source: SourceRange | None,
76
+ path: str,
77
+ ) -> ConstraintResult:
78
+ """构造结构化失败结果(code + params,message 由注册表渲染)。"""
79
+ return ConstraintResult(
80
+ ok=False,
81
+ diagnostics=[
82
+ Diagnostic(severity=Severity.ERROR, code=code, params=dict(params), source=source, path=path),
83
+ ],
84
+ )
85
+
86
+
87
+ # ═══════════════════════════════════════════════════════════
88
+ # 共享辅助
89
+ # ═══════════════════════════════════════════════════════════
90
+
91
+
92
+ def describe(val: StdValue | None) -> str:
93
+ """人类可读的值描述。"""
94
+ if val is None:
95
+ return '无值'
96
+ if isinstance(val, StdLiteral):
97
+ return f'{val.kind}({val.value!r})'
98
+ if isinstance(val, StdArray):
99
+ return f'list[{len(val.elements)}]'
100
+ return f'dict[{len(val.fields)}]'
101
+
102
+
103
+ def as_number(val: StdValue | None) -> Decimal | None:
104
+ """字面量 → 数值(int/float kind 才有效)。"""
105
+ if isinstance(val, StdLiteral) and val.kind in ('int', 'float'):
106
+ v = val.value
107
+ if isinstance(v, int):
108
+ return Decimal(v)
109
+ if isinstance(v, Decimal):
110
+ return v
111
+ return None
112
+
113
+
114
+ def as_str(val: StdValue | None) -> str | None:
115
+ """字面量 → 字符串(str kind 才有效)。"""
116
+ if isinstance(val, StdLiteral) and val.kind == 'str':
117
+ v = val.value
118
+ return v if isinstance(v, str) else None
119
+ return None
120
+
121
+
122
+ def as_decimal_arg(a: Any) -> Decimal | None:
123
+ """约束参数 → Decimal(int / Decimal 才有效;NaN 视为无效参数)。"""
124
+ if isinstance(a, bool):
125
+ return None
126
+ if isinstance(a, int):
127
+ return Decimal(a)
128
+ if isinstance(a, Decimal):
129
+ # Decimal 比较遇 NaN 会抛 InvalidOperation(默认 trap),此处拒绝 NaN 参数
130
+ return None if a.is_nan() else a
131
+ return None
132
+
133
+
134
+ def safe_equal(a: Any, b: Any) -> bool:
135
+ """安全相等比较:Decimal NaN 参与比较会抛 InvalidOperation,视为不相等。"""
136
+ try:
137
+ return bool(a == b)
138
+ except InvalidOperation:
139
+ return False
140
+
141
+
142
+ def std_equal(a: StdValue | None, b: StdValue | None) -> bool:
143
+ """StdValue 结构相等(直接工作在 Std 节点上,不经 Python 降维)。
144
+
145
+ 语义:
146
+ - 字面量:kind 相同且值相等;int/float 数值交叉相等(eq(42) 对 float 42 成立)
147
+ - bool 与 int 不相等(避免 Python ``True == 1`` 陷阱)
148
+ - NaN 与任何值不相等(IEEE 754)
149
+ - 数组:等长且逐元素相等
150
+ - 对象:等字段集且逐字段值相等(与字段顺序无关)
151
+ """
152
+ if a is None or b is None:
153
+ return a is b
154
+ if isinstance(a, StdLiteral) and isinstance(b, StdLiteral):
155
+ if a.kind == b.kind:
156
+ return safe_equal(a.value, b.value)
157
+ if {a.kind, b.kind} == {'int', 'float'}:
158
+ na = as_number(a)
159
+ nb = as_number(b)
160
+ if na is not None and nb is not None:
161
+ return safe_equal(na, nb)
162
+ return False
163
+ if isinstance(a, StdArray) and isinstance(b, StdArray):
164
+ if len(a.elements) != len(b.elements):
165
+ return False
166
+ return all(std_equal(x, y) for x, y in zip(a.elements, b.elements))
167
+ if isinstance(a, StdObject) and isinstance(b, StdObject):
168
+ if len(a.fields) != len(b.fields):
169
+ return False
170
+ for f in a.fields:
171
+ other = b.get(f.name)
172
+ if other is None or not std_equal(f.value, other.value):
173
+ return False
174
+ return True
175
+ return False
176
+
177
+
178
+ def as_std_value(v: Any) -> StdValue:
179
+ """约束参数(Python 值)→ StdValue(与值同构后比较)。"""
180
+ if v is None:
181
+ return StdLiteral(kind='null', value=None)
182
+ if isinstance(v, bool):
183
+ return StdLiteral(kind='bool', value=v)
184
+ if isinstance(v, int):
185
+ return StdLiteral(kind='int', value=v)
186
+ if isinstance(v, Decimal):
187
+ return StdLiteral(kind='float', value=v)
188
+ if isinstance(v, str):
189
+ return StdLiteral(kind='str', value=v)
190
+ if isinstance(v, (list, tuple)):
191
+ items = cast(list[Any], v)
192
+ return StdArray(elements=[as_std_value(e) for e in items])
193
+ if isinstance(v, dict):
194
+ mapping = cast(dict[Any, Any], v)
195
+ return StdObject(fields=[StdField(name=str(k), value=as_std_value(e)) for k, e in mapping.items()])
196
+ return StdLiteral(kind='str', value=str(v))
197
+
198
+
199
+ def as_spec(arg: Any) -> ResolvedConstraint | None:
200
+ """嵌套参数 → 约束规格(ResolvedConstraint 或裸名字符串)。"""
201
+ if isinstance(arg, ResolvedConstraint):
202
+ return arg
203
+ if isinstance(arg, str):
204
+ return ResolvedConstraint(name=arg) # pyright: ignore[reportCallIssue]
205
+ return None
206
+
207
+
208
+ def spec_name(arg: Any) -> str:
209
+ """取参数的约束名(has/field 的第一个参数是裸名字)。"""
210
+ if isinstance(arg, ResolvedConstraint):
211
+ return arg.name
212
+ return str(arg)
@@ -0,0 +1,56 @@
1
+ """内置字典约束:has / field。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from infinity_data.semantic.builder.models import StdObject
8
+ from infinity_data.semantic.registry._core import (
9
+ ConstraintResult,
10
+ Executor,
11
+ fail_result,
12
+ ok_result,
13
+ )
14
+ from infinity_data.semantic.registry._core import (
15
+ as_spec as _as_spec,
16
+ )
17
+ from infinity_data.semantic.registry._core import (
18
+ spec_name as _spec_name,
19
+ )
20
+ from infinity_data.tokenizer.models.raw_tokens import SourceRange
21
+
22
+ __all__ = ['_check_has', '_check_field']
23
+
24
+
25
+ def _check_has(
26
+ val: Any,
27
+ source: SourceRange | None,
28
+ path: str,
29
+ args: list[Any],
30
+ executor: Executor,
31
+ ) -> ConstraintResult:
32
+ if not isinstance(val, StdObject):
33
+ return fail_result('constraint.has_only', {}, source, path)
34
+ key = _spec_name(args[0])
35
+ if val.get(key) is not None:
36
+ return ok_result()
37
+ return fail_result('constraint.has_missing', {'key': key}, source, path)
38
+
39
+
40
+ def _check_field(
41
+ val: Any,
42
+ source: SourceRange | None,
43
+ path: str,
44
+ args: list[Any],
45
+ executor: Executor,
46
+ ) -> ConstraintResult:
47
+ if not isinstance(val, StdObject):
48
+ return fail_result('constraint.field_only', {}, source, path)
49
+ name = _spec_name(args[0])
50
+ spec = _as_spec(args[1])
51
+ if spec is None:
52
+ return fail_result('constraint.field_need', {}, source, path)
53
+ f = val.get(name)
54
+ if f is None:
55
+ return fail_result('constraint.field_missing', {'field': name}, source, path)
56
+ return executor(spec, f.value, source, f'{path}.{name}')