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,214 @@
1
+ """沙盒中介:库的系统访问唯一通道。
2
+
3
+ 库不直接接触文件系统/环境变量——文件来源(:class:`File`)与变量值
4
+ 一律由沙盒产出;授权匹配与授权谓词也在此实现(config 纯数据零行为)。
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import os
10
+ import re
11
+ from functools import lru_cache
12
+ from pathlib import Path, PurePosixPath
13
+
14
+ from infinity_data.infra.file import DiskFile, File
15
+ from infinity_data.infra.location import SourceRange
16
+ from infinity_data.infra.path import native_to_posix, posix_to_native
17
+ from infinity_data.sandbox.config import SandboxConfig
18
+ from infinity_data.sandbox.errors import AccessDeniedError, EnvNotAuthorizedError, EnvNotSetError
19
+
20
+ __all__ = ['Sandbox']
21
+
22
+
23
+ @lru_cache(maxsize=128)
24
+ def _translate_segment(segment: str) -> re.Pattern[str] | None:
25
+ """单个路径段 glob → 编译正则(``**`` 段返回 None = 匹配任意多段)。
26
+
27
+ 语义(与常见 glob 一致):
28
+ - ``*`` 匹配段内任意字符(不跨 ``/``)
29
+ - ``?`` 匹配段内单个字符
30
+ - ``[...]`` 字符类(``!`` / ``^`` 取反)
31
+ """
32
+ if segment == '**':
33
+ return None
34
+ i, n = 0, len(segment)
35
+ out: list[str] = []
36
+ while i < n:
37
+ c = segment[i]
38
+ if c == '*':
39
+ out.append('[^/]*')
40
+ elif c == '?':
41
+ out.append('[^/]')
42
+ elif c == '[':
43
+ j = i + 1
44
+ if j < n and segment[j] in ('!', '^'):
45
+ j += 1
46
+ if j < n and segment[j] == ']':
47
+ j += 1
48
+ while j < n and segment[j] != ']':
49
+ j += 1
50
+ if j >= n: # 未闭合的 '[' → 当作字面量
51
+ out.append(r'\[')
52
+ else:
53
+ cls = segment[i : j + 1]
54
+ if cls.startswith('[!'):
55
+ cls = '[^' + cls[2:]
56
+ out.append(cls)
57
+ i = j
58
+ else:
59
+ out.append(re.escape(c))
60
+ i += 1
61
+ return re.compile(''.join(out))
62
+
63
+
64
+ def _match_pattern(pattern: str, segments: tuple[str, ...]) -> bool:
65
+ """单条 glob 模式与目标路径段序列匹配。
66
+
67
+ 段级 DP:``**`` 匹配零或多段(含零段,故 ``**/*.json`` 能命中根级文件),
68
+ 其余段按正则逐段匹配。与 ``Path.match`` 不同,语义不依赖 Python 版本。
69
+ """
70
+ pattern_segments = tuple(PurePosixPath(pattern).parts)
71
+ if not pattern_segments:
72
+ return not segments
73
+ compiled = [_translate_segment(s) for s in pattern_segments]
74
+ m, n = len(compiled), len(segments)
75
+ dp = [[False] * (n + 1) for _ in range(m + 1)]
76
+ dp[m][n] = True
77
+ for i in range(m - 1, -1, -1):
78
+ seg_re = compiled[i]
79
+ if seg_re is None: # ** 段:匹配零或多段
80
+ for j in range(n, -1, -1):
81
+ dp[i][j] = dp[i + 1][j] or (j < n and dp[i][j + 1])
82
+ else:
83
+ for j in range(n, -1, -1):
84
+ if j < n and seg_re.fullmatch(segments[j]):
85
+ dp[i][j] = dp[i + 1][j + 1]
86
+ return dp[0][0]
87
+
88
+
89
+ def match_globs(target: Path, patterns: list[str] | None, base: Path) -> bool:
90
+ """目标路径是否命中任一 glob 白名单模式。
91
+
92
+ - ``patterns is None`` → 全部允许(full_access)
93
+ - 绝对模式(``/`` 起始)直接对绝对路径匹配
94
+ - 相对模式以 ``base`` 为基准;``*`` 不跨目录分隔符,``**`` 跨任意段(含零段)
95
+ """
96
+ if patterns is None:
97
+ return True
98
+ target_abs = target.resolve()
99
+ base_abs = base.resolve()
100
+ target_segments = tuple(PurePosixPath(native_to_posix(target_abs)).parts)
101
+ try:
102
+ rel = target_abs.relative_to(base_abs)
103
+ rel_segments: tuple[str, ...] | None = tuple(PurePosixPath(rel.as_posix()).parts)
104
+ except ValueError:
105
+ rel_segments = None # 目标在 base 之外:仅能由绝对模式命中
106
+ for pattern in patterns:
107
+ p = pattern.strip()
108
+ if not p:
109
+ continue
110
+ if p.startswith('./'):
111
+ p = p[2:]
112
+ if p.startswith('/'):
113
+ if _match_pattern(p, target_segments):
114
+ return True
115
+ continue
116
+ if rel_segments is not None and _match_pattern(p, rel_segments):
117
+ return True
118
+ return False
119
+
120
+
121
+ class Sandbox:
122
+ """系统访问中介:从 :class:`SandboxConfig` 构造。
123
+
124
+ - :meth:`open_file` / :meth:`open_template`:路径解析 + glob 授权 → File
125
+ - :meth:`getenv`:env 授权查询
126
+ - strict 违规 → 抛 :class:`SandboxError`;非 strict → None(调用方警告)
127
+ """
128
+
129
+ def __init__(self, config: SandboxConfig, base_dir: Path) -> None:
130
+ self._config = config
131
+ self._base_dir = base_dir
132
+
133
+ @property
134
+ def config(self) -> SandboxConfig:
135
+ return self._config
136
+
137
+ @property
138
+ def base_dir(self) -> Path:
139
+ """编译入口目录(glob 授权基准)。"""
140
+ return self._base_dir
141
+
142
+ # ── 授权谓词(公开,无副作用)────────────────────
143
+
144
+ def authorize_file(self, target: Path, base: Path | None = None) -> bool:
145
+ """!file 导入目标是否授权。"""
146
+ return match_globs(target, self._config.allow_files, base or self._base_dir)
147
+
148
+ def authorize_template(self, target: Path, base: Path | None = None) -> bool:
149
+ """!from 导入目标是否授权。"""
150
+ return match_globs(target, self._config.allow_templates, base or self._base_dir)
151
+
152
+ # ── 环境变量 ──────────────────────────────────────
153
+
154
+ def getenv(self, name: str, *, source: SourceRange | None = None) -> str:
155
+ """env 授权查询。
156
+
157
+ 解析顺序:
158
+ 1. 注入值(``SandboxConfig.env``)命中 → 返回(注入优先)
159
+ 2. ``allow_env`` 白名单 / 全量授权(None)→ 从真实 OS 环境实时读取
160
+ 3. 其余一律抛 :class:`SandboxError`(无 strict 例外)
161
+
162
+ 已授权但进程未设置同样失败——绝不退化为空字符串注入
163
+ (``DB_PASSWORD`` 变 ``''`` 会悄悄吞掉关键配置)。
164
+ """
165
+ if name in self._config.env:
166
+ return self._config.env[name]
167
+ if self._config.allow_env is None or name in self._config.allow_env:
168
+ value = os.environ.get(name)
169
+ if value is None:
170
+ raise EnvNotSetError(name, source)
171
+ return value
172
+ raise EnvNotAuthorizedError(name, source)
173
+
174
+ # ── 文件来源 ──────────────────────────────────────
175
+
176
+ def open_file(
177
+ self,
178
+ from_path: str,
179
+ *,
180
+ base_dir: Path | None = None,
181
+ source: SourceRange | None = None,
182
+ ) -> File | None:
183
+ """!file 目标:路径解析 + allow_files 授权 → File。"""
184
+ return self._open(from_path, self._config.allow_files, '文件导入', base_dir, source)
185
+
186
+ def open_template(
187
+ self,
188
+ from_path: str,
189
+ *,
190
+ base_dir: Path | None = None,
191
+ source: SourceRange | None = None,
192
+ ) -> File | None:
193
+ """!from 目标:路径解析 + allow_templates 授权 → File。"""
194
+ return self._open(from_path, self._config.allow_templates, '模板导入', base_dir, source)
195
+
196
+ def _open(
197
+ self,
198
+ from_path: str,
199
+ patterns: list[str] | None,
200
+ label: str,
201
+ base_dir: Path | None,
202
+ source: SourceRange | None,
203
+ ) -> File | None:
204
+ """公共路径解析 + 授权(相对路径以 base_dir 解析,glob 以入口目录匹配)。"""
205
+ base = base_dir if base_dir is not None else self._base_dir
206
+ path = posix_to_native(from_path) # 语言内 POSIX → 当前平台原生(/c/... → C:\...)
207
+ if not path.is_absolute():
208
+ path = base / path
209
+
210
+ if not match_globs(path, patterns, self._base_dir):
211
+ if self._config.strict:
212
+ raise AccessDeniedError(label, from_path, source)
213
+ return None
214
+ return DiskFile.from_fullpath(path)
@@ -0,0 +1,21 @@
1
+ """顶层 Schema 约束。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from typing import Literal
7
+
8
+ __all__ = ['Schema']
9
+
10
+
11
+ @dataclass
12
+ class Schema:
13
+ """顶层模板约束:强制编译产物符合指定模板的结构。"""
14
+
15
+ template: str # 模板名
16
+ from_file: str | None = None # 模板所在文件;None = 与配置同文件(或已导入)
17
+ mode: Literal['strict', 'lenient', 'strip'] = 'strict'
18
+
19
+ # strict: 额外字段 → 报错(SchemaError)。必填字段缺失 → 报错
20
+ # lenient: 额外字段 → 警告。必填字段缺失 → 报错
21
+ # strip: 额外字段 → 静默丢弃。必填字段缺失 → 报错
@@ -0,0 +1,59 @@
1
+ """语义层:数据模型与高级 API(产物面)。
2
+
3
+ 三阶段子模块(按职责命名):
4
+
5
+ - :mod:`infinity_data.semantic.resolver`——Phase 1 导入求解器
6
+ - :mod:`infinity_data.semantic.builder`——Phase 2a AST 构建器
7
+ - :mod:`infinity_data.semantic.executor`——Phase 2b 约束执行器
8
+
9
+ 子模块间仅经数据模型依赖(resolver.models → builder.models → executor),
10
+ 无对象引用;诊断经共享 :class:`DiagnosticCollector` 由流水线统一收集。
11
+
12
+ 本包导出:
13
+ - 数据模型(StdAst / 已解析约束 / Phase 1 上下文)
14
+ - 高级 API(:class:`TemplateGraphResolver` / :class:`AstBuilder` /
15
+ :class:`ConstraintExecutor` / :class:`ImportResolver`——流水线组装对象)
16
+
17
+ 导入 :mod:`infinity_data.semantic.diagnostics` 以注册本层全部诊断定义
18
+ (副作用,导入即注册进全局注册表)。
19
+ """
20
+
21
+ from infinity_data.semantic import (
22
+ diagnostics,
23
+ )
24
+ from infinity_data.semantic.builder import (
25
+ AstBuilder,
26
+ ResolvedConstraint,
27
+ StdArray,
28
+ StdDocument,
29
+ StdField,
30
+ StdLiteral,
31
+ StdObject,
32
+ StdValue,
33
+ )
34
+ from infinity_data.semantic.executor import ConstraintExecutor
35
+ from infinity_data.semantic.resolver import (
36
+ ImportResolver,
37
+ ResolvedContext,
38
+ Scope,
39
+ TemplateGraphResolver,
40
+ TemplateKey,
41
+ )
42
+
43
+ __all__ = [
44
+ 'AstBuilder',
45
+ 'ConstraintExecutor',
46
+ 'ImportResolver',
47
+ 'ResolvedConstraint',
48
+ 'ResolvedContext',
49
+ 'Scope',
50
+ 'StdArray',
51
+ 'StdDocument',
52
+ 'StdField',
53
+ 'StdLiteral',
54
+ 'StdObject',
55
+ 'StdValue',
56
+ 'TemplateGraphResolver',
57
+ 'TemplateKey',
58
+ 'diagnostics',
59
+ ]
@@ -0,0 +1,29 @@
1
+ """Phase 2a(AST 构建器):RawAst + ResolvedContext → StdDocument。
2
+
3
+ - :class:`AstBuilder`:纯数据变换构建器(builder.py)
4
+ - 数据模型::class:`StdDocument` / :class:`StdObject` / :class:`ResolvedConstraint` 等(models.py)
5
+
6
+ 子模块导出自身模型与类,供流水线(pipeline / 测试)直接引用。
7
+ """
8
+
9
+ from infinity_data.semantic.builder.builder import AstBuilder
10
+ from infinity_data.semantic.builder.models import (
11
+ ResolvedConstraint,
12
+ StdArray,
13
+ StdDocument,
14
+ StdField,
15
+ StdLiteral,
16
+ StdObject,
17
+ StdValue,
18
+ )
19
+
20
+ __all__ = [
21
+ 'AstBuilder',
22
+ 'ResolvedConstraint',
23
+ 'StdArray',
24
+ 'StdDocument',
25
+ 'StdField',
26
+ 'StdLiteral',
27
+ 'StdObject',
28
+ 'StdValue',
29
+ ]