onec-interactive-runtime-core 0.1.19__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.
- onec_interactive_runtime_core-0.1.19.dist-info/METADATA +12 -0
- onec_interactive_runtime_core-0.1.19.dist-info/RECORD +87 -0
- onec_interactive_runtime_core-0.1.19.dist-info/WHEEL +4 -0
- onec_interactive_runtime_core-0.1.19.dist-info/licenses/COPYRIGHT +9 -0
- onec_interactive_runtime_core-0.1.19.dist-info/licenses/LICENSE +232 -0
- onec_interactive_runtime_core-0.1.19.dist-info/licenses/THIRD_PARTY_NOTICES.txt +32 -0
- onec_runtime/AGENTS.md +7 -0
- onec_runtime/__init__.py +3 -0
- onec_runtime/artifacts.py +59 -0
- onec_runtime/bootstrap.py +310 -0
- onec_runtime/breakpoint_workspace.py +249 -0
- onec_runtime/bsl/__init__.py +139 -0
- onec_runtime/bsl/diagnostics.py +1046 -0
- onec_runtime/bsl/full_ast_worker_projection.py +522 -0
- onec_runtime/bsl/generated_semantic_parser.py +6322 -0
- onec_runtime/bsl/lexer.py +164 -0
- onec_runtime/bsl/module_catalog.py +420 -0
- onec_runtime/bsl/module_delta.py +1079 -0
- onec_runtime/bsl/module_universe.py +1877 -0
- onec_runtime/bsl/notebook_cells.py +232 -0
- onec_runtime/bsl/notebook_method_globals.py +74 -0
- onec_runtime/bsl/notebook_methods.py +175 -0
- onec_runtime/bsl/parser_artifact_identity.py +119 -0
- onec_runtime/bsl/parser_target.py +388 -0
- onec_runtime/bsl/preprocessor.py +16 -0
- onec_runtime/bsl/semantic_lowering.py +1539 -0
- onec_runtime/bsl/source_maps.py +2411 -0
- onec_runtime/bsl/worker_dependency_resolver.py +289 -0
- onec_runtime/bsl/worker_preprocessor.py +247 -0
- onec_runtime/bsl/worker_projection_model.py +137 -0
- onec_runtime/bsl/worker_reload_source_map.py +1697 -0
- onec_runtime/capture.py +113 -0
- onec_runtime/capture_source.py +453 -0
- onec_runtime/compact_table.py +376 -0
- onec_runtime/compact_table_backend.py +500 -0
- onec_runtime/config.py +198 -0
- onec_runtime/configurator_agent.py +520 -0
- onec_runtime/controller_worker.py +400 -0
- onec_runtime/credentials.py +18 -0
- onec_runtime/epf_container.py +196 -0
- onec_runtime/errors.py +205 -0
- onec_runtime/experiment.py +130 -0
- onec_runtime/extension_bundle.py +944 -0
- onec_runtime/extension_lifecycle.py +402 -0
- onec_runtime/extension_state.py +222 -0
- onec_runtime/fault_injection.py +30 -0
- onec_runtime/kernel.py +179 -0
- onec_runtime/lease.py +123 -0
- onec_runtime/observation.py +287 -0
- onec_runtime/performance_profile.py +245 -0
- onec_runtime/privacy.py +263 -0
- onec_runtime/processes.py +265 -0
- onec_runtime/prototype_runtime.py +2644 -0
- onec_runtime/rdbg/__init__.py +1 -0
- onec_runtime/rdbg/models.py +130 -0
- onec_runtime/rdbg/reconnect.py +59 -0
- onec_runtime/rdbg/session.py +913 -0
- onec_runtime/rdbg/transport.py +138 -0
- onec_runtime/rdbg/xml_codec.py +980 -0
- onec_runtime/recovery.py +106 -0
- onec_runtime/recovery_journal.py +79 -0
- onec_runtime/resources/extension/OnecInteractiveRuntime.cfe +0 -0
- onec_runtime/resources/extension/extension-manifest.json +108 -0
- onec_runtime/runtime_api.py +5272 -0
- onec_runtime/runtime_contracts.py +196 -0
- onec_runtime/server_worker.py +2034 -0
- onec_runtime/session.py +2123 -0
- onec_runtime/startup_diagnostics.py +45 -0
- onec_runtime/stop_routing.py +65 -0
- onec_runtime/supervised_processes.py +232 -0
- onec_runtime/supervised_runtime.py +512 -0
- onec_runtime/supervisor.py +363 -0
- onec_runtime/supervisor_model.py +148 -0
- onec_runtime/supervisor_protocol.py +77 -0
- onec_runtime/table_materialization.py +471 -0
- onec_runtime/table_transfer_backend.py +203 -0
- onec_runtime/table_value.py +311 -0
- onec_runtime/toolchain.py +730 -0
- onec_runtime/value_materialization.py +432 -0
- onec_runtime/value_transfer_backend.py +199 -0
- onec_runtime/worker_breakpoints.py +1431 -0
- onec_runtime/worker_epf.py +313 -0
- onec_runtime/worker_epf_template.py +53 -0
- onec_runtime/worker_stage_protocol.py +516 -0
- onec_runtime/worker_universe.py +3772 -0
- onec_runtime_build/__init__.py +1 -0
- onec_runtime_build/parser_target_development.py +116 -0
|
@@ -0,0 +1,1877 @@
|
|
|
1
|
+
"""Immutable admission records for server-side common-module lowering."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from bisect import bisect_left
|
|
6
|
+
from collections.abc import Callable, Iterable
|
|
7
|
+
from dataclasses import dataclass, fields, is_dataclass, replace
|
|
8
|
+
from hashlib import sha256
|
|
9
|
+
from hmac import compare_digest, new as hmac_new
|
|
10
|
+
import json
|
|
11
|
+
import re
|
|
12
|
+
from secrets import token_bytes
|
|
13
|
+
from threading import RLock
|
|
14
|
+
from typing import Any, Literal
|
|
15
|
+
from weakref import ReferenceType, WeakKeyDictionary, ref
|
|
16
|
+
|
|
17
|
+
from onec_runtime.bsl.lexer import BslLexError, tokenize
|
|
18
|
+
from onec_runtime.bsl.parser_target import (
|
|
19
|
+
BslParseError,
|
|
20
|
+
PythonParserTarget,
|
|
21
|
+
parse_raw_module,
|
|
22
|
+
)
|
|
23
|
+
from onec_runtime.bsl.module_catalog import (
|
|
24
|
+
CommonModuleCatalogSnapshot,
|
|
25
|
+
CommonModuleDescriptor,
|
|
26
|
+
CommonModuleScope,
|
|
27
|
+
)
|
|
28
|
+
from onec_runtime.bsl.source_maps import (
|
|
29
|
+
MappedSource,
|
|
30
|
+
SourceArtifactKind,
|
|
31
|
+
SourceSpan,
|
|
32
|
+
SourceTransformBuilder,
|
|
33
|
+
SourceUnitKind,
|
|
34
|
+
SourceUnitRef,
|
|
35
|
+
_observe_text_work,
|
|
36
|
+
_trusted_mapped_semantic_summary,
|
|
37
|
+
_validated_mapped_semantic_summary,
|
|
38
|
+
)
|
|
39
|
+
from onec_runtime.bsl.worker_dependency_resolver import (
|
|
40
|
+
ResolvedMethodPlan,
|
|
41
|
+
ResolvedModulePlan,
|
|
42
|
+
)
|
|
43
|
+
from onec_runtime.bsl.worker_projection_model import BareNameKind, ParsedModuleModel
|
|
44
|
+
from onec_runtime.bsl.worker_reload_source_map import (
|
|
45
|
+
ReloadInsertion,
|
|
46
|
+
ReloadMethodBoundary,
|
|
47
|
+
materialize_worker_reload_source,
|
|
48
|
+
)
|
|
49
|
+
from onec_runtime.errors import ModuleUniverseAdmissionError
|
|
50
|
+
from onec_runtime.performance_profile import PhaseRecorder
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
_BSL_IDENTIFIER_RE = re.compile(r"[A-Za-zА-Яа-яЁё_][0-9A-Za-zА-Яа-яЁё_]*\Z")
|
|
54
|
+
|
|
55
|
+
MODULE_SCOPE_DEPENDENCY = "module_scope_dependency"
|
|
56
|
+
DYNAMIC_EXECUTE = "dynamic_execute"
|
|
57
|
+
AMBIGUOUS_BINDING = "ambiguous_module_dependency"
|
|
58
|
+
|
|
59
|
+
DEPENDENCY_FIELD_REGION = "worker_dependency_field"
|
|
60
|
+
DEPENDENCY_ALIAS_DECLARATION_REGION = "worker_dependency_alias_declaration"
|
|
61
|
+
DEPENDENCY_ALIAS_INITIALIZER_REGION = "worker_dependency_alias_initializer"
|
|
62
|
+
IMPLICIT_LOCAL_DECLARATION_REGION = "worker_implicit_local_declaration"
|
|
63
|
+
|
|
64
|
+
_PLATFORM_GLOBALS = frozenset(
|
|
65
|
+
{
|
|
66
|
+
"статуссообщения",
|
|
67
|
+
"символы",
|
|
68
|
+
"кодировкатекста",
|
|
69
|
+
"справочники",
|
|
70
|
+
"документы",
|
|
71
|
+
"журналыдокументов",
|
|
72
|
+
"регистрысведений",
|
|
73
|
+
"регистрынакопления",
|
|
74
|
+
"регистрыбухгалтерии",
|
|
75
|
+
"регистрырасчета",
|
|
76
|
+
"планывидовхарактеристик",
|
|
77
|
+
"планысчетов",
|
|
78
|
+
"планывидоврасчета",
|
|
79
|
+
"планыобмена",
|
|
80
|
+
"бизнеспроцессы",
|
|
81
|
+
"задачи",
|
|
82
|
+
"критерииотбора",
|
|
83
|
+
"последовательности",
|
|
84
|
+
"константы",
|
|
85
|
+
"перечисления",
|
|
86
|
+
"внешниеобработки",
|
|
87
|
+
"внешниеотчеты",
|
|
88
|
+
"обработки",
|
|
89
|
+
"отчеты",
|
|
90
|
+
"метаданные",
|
|
91
|
+
"параметрысеанса",
|
|
92
|
+
"частидаты",
|
|
93
|
+
"обходрезультатазапроса",
|
|
94
|
+
"видсравнениякомпоновкиданных",
|
|
95
|
+
"типгруппыэлементовотборакомпоновкиданных",
|
|
96
|
+
"цветастиля",
|
|
97
|
+
}
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@dataclass(frozen=True, slots=True, repr=False)
|
|
102
|
+
class WorkerModuleUnit:
|
|
103
|
+
logical_name: str
|
|
104
|
+
kind: Literal["module", "test-module"]
|
|
105
|
+
revision: int
|
|
106
|
+
mapped_source: MappedSource
|
|
107
|
+
|
|
108
|
+
def __post_init__(self) -> None:
|
|
109
|
+
if not _is_bsl_identifier(self.logical_name):
|
|
110
|
+
raise ModuleUniverseAdmissionError("worker module source identity is invalid")
|
|
111
|
+
if self.kind not in ("module", "test-module"):
|
|
112
|
+
raise ModuleUniverseAdmissionError("worker module source identity is invalid")
|
|
113
|
+
if type(self.revision) is not int or self.revision < 0:
|
|
114
|
+
raise ModuleUniverseAdmissionError("worker module source identity is invalid")
|
|
115
|
+
if not isinstance(self.mapped_source, MappedSource):
|
|
116
|
+
raise ModuleUniverseAdmissionError("worker module source identity is invalid")
|
|
117
|
+
unit = _single_visible_unit(self.mapped_source)
|
|
118
|
+
expected_kind = (
|
|
119
|
+
SourceUnitKind.MODULE if self.kind == "module" else SourceUnitKind.TEST_MODULE
|
|
120
|
+
)
|
|
121
|
+
if (
|
|
122
|
+
unit is None
|
|
123
|
+
or unit.kind is not expected_kind
|
|
124
|
+
or unit.unit_id != self.logical_name
|
|
125
|
+
or unit.revision != self.revision
|
|
126
|
+
or unit.source_sha256 != self.mapped_source.artifact.source_sha256
|
|
127
|
+
):
|
|
128
|
+
raise ModuleUniverseAdmissionError("worker module source identity is invalid")
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
@dataclass(frozen=True, slots=True)
|
|
132
|
+
class DependencyUse:
|
|
133
|
+
method_name: str
|
|
134
|
+
category: Literal["call", "access", "value"]
|
|
135
|
+
span: SourceSpan
|
|
136
|
+
method_declaration: SourceSpan
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
@dataclass(frozen=True, slots=True)
|
|
140
|
+
class ModuleDependencyBinding:
|
|
141
|
+
target_module: str
|
|
142
|
+
export_variable: str
|
|
143
|
+
uses: tuple[DependencyUse, ...]
|
|
144
|
+
sha256: str
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
@dataclass(frozen=True, slots=True)
|
|
148
|
+
class MethodInsertionPoints:
|
|
149
|
+
method_name: str
|
|
150
|
+
method_declaration: SourceSpan
|
|
151
|
+
declarations_end: int
|
|
152
|
+
first_statement_start: int
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
@dataclass(frozen=True, slots=True)
|
|
156
|
+
class WorkerModuleAnalysisContext:
|
|
157
|
+
module_variable_names: frozenset[str]
|
|
158
|
+
method_names: frozenset[str]
|
|
159
|
+
source_identifier_names: frozenset[str]
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
@dataclass(frozen=True, slots=True)
|
|
163
|
+
class WorkerMethodAnalysis:
|
|
164
|
+
method_name: str
|
|
165
|
+
exported: bool
|
|
166
|
+
signature_sha256: str
|
|
167
|
+
method_declaration: SourceSpan
|
|
168
|
+
executable_body: SourceSpan
|
|
169
|
+
declarations_end: int
|
|
170
|
+
first_statement_start: int
|
|
171
|
+
dependency_uses: tuple[tuple[str, DependencyUse], ...]
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
@dataclass(frozen=True, slots=True)
|
|
175
|
+
class _IsolatedWorkerMethodAnalysis:
|
|
176
|
+
method: WorkerMethodAnalysis
|
|
177
|
+
insertion_point: MethodInsertionPoints
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
@dataclass(frozen=True, slots=True)
|
|
181
|
+
class _PreparedIsolatedWorkerMethodAnalysis:
|
|
182
|
+
analyze: Callable[[], _IsolatedWorkerMethodAnalysis | None]
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
@dataclass(frozen=True, slots=True, repr=False)
|
|
186
|
+
class AnalyzedWorkerModule:
|
|
187
|
+
unit: WorkerModuleUnit
|
|
188
|
+
dependencies: tuple[ModuleDependencyBinding, ...]
|
|
189
|
+
exported_methods: tuple[str, ...]
|
|
190
|
+
method_insertion_points: tuple[MethodInsertionPoints, ...]
|
|
191
|
+
context: WorkerModuleAnalysisContext
|
|
192
|
+
methods: tuple[WorkerMethodAnalysis, ...]
|
|
193
|
+
catalog_identity: tuple[str, str, int, str]
|
|
194
|
+
parser_identity: tuple[str, str]
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
@dataclass(frozen=True, slots=True, repr=False, weakref_slot=True, eq=False)
|
|
198
|
+
class LoweredWorkerModule:
|
|
199
|
+
analysis: AnalyzedWorkerModule
|
|
200
|
+
mapped_source: MappedSource
|
|
201
|
+
dependency_bindings_sha256: str
|
|
202
|
+
transform_version: str = "module-universe-v1"
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
class WorkerSemanticAdmission:
|
|
206
|
+
"""Opaque process-local proof for one exact lowered semantic result."""
|
|
207
|
+
|
|
208
|
+
__slots__ = ("__lowered", "__identity", "__proof")
|
|
209
|
+
|
|
210
|
+
def __init__(
|
|
211
|
+
self,
|
|
212
|
+
lowered: ReferenceType[LoweredWorkerModule],
|
|
213
|
+
identity: _WorkerSemanticIdentity,
|
|
214
|
+
proof: bytes,
|
|
215
|
+
*,
|
|
216
|
+
_authority: object,
|
|
217
|
+
) -> None:
|
|
218
|
+
if _authority is not _SEMANTIC_ADMISSION_AUTHORITY:
|
|
219
|
+
raise TypeError("Worker semantic admissions are process-private")
|
|
220
|
+
object.__setattr__(self, "_WorkerSemanticAdmission__lowered", lowered)
|
|
221
|
+
object.__setattr__(self, "_WorkerSemanticAdmission__identity", identity)
|
|
222
|
+
object.__setattr__(self, "_WorkerSemanticAdmission__proof", proof)
|
|
223
|
+
|
|
224
|
+
def __setattr__(self, name: str, value: object) -> None:
|
|
225
|
+
raise AttributeError("Worker semantic admissions are immutable")
|
|
226
|
+
|
|
227
|
+
def __repr__(self) -> str:
|
|
228
|
+
return "<redacted worker semantic admission>"
|
|
229
|
+
|
|
230
|
+
def _contents(
|
|
231
|
+
self,
|
|
232
|
+
) -> tuple[
|
|
233
|
+
ReferenceType[LoweredWorkerModule],
|
|
234
|
+
_WorkerSemanticIdentity,
|
|
235
|
+
bytes,
|
|
236
|
+
]:
|
|
237
|
+
return self.__lowered, self.__identity, self.__proof
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
@dataclass(frozen=True, slots=True)
|
|
241
|
+
class _WorkerSemanticIdentity:
|
|
242
|
+
logical_name: str
|
|
243
|
+
raw_source_object_id: int
|
|
244
|
+
raw_text_object_id: int
|
|
245
|
+
raw_source_sha256: str
|
|
246
|
+
raw_text_sha256: str
|
|
247
|
+
lowered_source_object_id: int
|
|
248
|
+
lowered_text_object_id: int
|
|
249
|
+
lowered_source_sha256: str
|
|
250
|
+
lowered_text_sha256: str
|
|
251
|
+
source_map_object_id: int
|
|
252
|
+
lowered_source_map_sha256: str
|
|
253
|
+
lineage_object_id: int
|
|
254
|
+
lineage_sha256: str
|
|
255
|
+
dependency_bindings_sha256: str
|
|
256
|
+
canonical_exported_methods: tuple[str, ...]
|
|
257
|
+
catalog_identity: tuple[str, str, int, str]
|
|
258
|
+
parser_identity: tuple[str, str]
|
|
259
|
+
transform_version: str
|
|
260
|
+
packer_identity: str | None
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
_SEMANTIC_ADMISSION_AUTHORITY = object()
|
|
264
|
+
_SEMANTIC_ADMISSION_PROOF_KEY = token_bytes(32)
|
|
265
|
+
_SEMANTIC_ADMISSIONS: WeakKeyDictionary[
|
|
266
|
+
LoweredWorkerModule,
|
|
267
|
+
dict[str | None, WorkerSemanticAdmission],
|
|
268
|
+
] = WeakKeyDictionary()
|
|
269
|
+
_SEMANTIC_ADMISSIONS_LOCK = RLock()
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def dependency_export_name(canonical_name: str) -> str:
|
|
273
|
+
if not _is_bsl_identifier(canonical_name):
|
|
274
|
+
raise ValueError("dependency canonical name is invalid")
|
|
275
|
+
digest = sha256(canonical_name.casefold().encode("utf-8")).hexdigest()[:12]
|
|
276
|
+
return f"__OnecDependency_{digest}"
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def analyze_worker_module(
|
|
280
|
+
unit: WorkerModuleUnit,
|
|
281
|
+
catalog: CommonModuleCatalogSnapshot,
|
|
282
|
+
parser_target: PythonParserTarget,
|
|
283
|
+
*,
|
|
284
|
+
profiler: PhaseRecorder | None = None,
|
|
285
|
+
) -> AnalyzedWorkerModule:
|
|
286
|
+
if not isinstance(unit, WorkerModuleUnit):
|
|
287
|
+
raise _analysis_error(
|
|
288
|
+
"worker module analysis input is invalid",
|
|
289
|
+
SourceSpan(0, 0),
|
|
290
|
+
AMBIGUOUS_BINDING,
|
|
291
|
+
)
|
|
292
|
+
if not isinstance(catalog, CommonModuleCatalogSnapshot):
|
|
293
|
+
raise _analysis_error(
|
|
294
|
+
"worker module analysis input is invalid",
|
|
295
|
+
SourceSpan(0, 0),
|
|
296
|
+
AMBIGUOUS_BINDING,
|
|
297
|
+
)
|
|
298
|
+
if not isinstance(parser_target, PythonParserTarget):
|
|
299
|
+
raise _analysis_error(
|
|
300
|
+
"worker module analysis input is invalid",
|
|
301
|
+
SourceSpan(0, 0),
|
|
302
|
+
AMBIGUOUS_BINDING,
|
|
303
|
+
)
|
|
304
|
+
parse = lambda: parse_raw_module(unit.mapped_source.text, parser_target)
|
|
305
|
+
root, tokens = (
|
|
306
|
+
parse()
|
|
307
|
+
if profiler is None
|
|
308
|
+
else profiler.measure_parser("full_module_parses", parse)
|
|
309
|
+
)
|
|
310
|
+
analyze = lambda: _analyze_parsed_worker_module(
|
|
311
|
+
unit,
|
|
312
|
+
catalog,
|
|
313
|
+
root,
|
|
314
|
+
tokens,
|
|
315
|
+
parser_identity=(
|
|
316
|
+
parser_target.metadata.parser_identity_sha256,
|
|
317
|
+
parser_target.metadata.parsergen_package_sha256 or "",
|
|
318
|
+
),
|
|
319
|
+
)
|
|
320
|
+
return (
|
|
321
|
+
analyze()
|
|
322
|
+
if profiler is None
|
|
323
|
+
else profiler.measure("dependency_analysis", analyze)
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
def analyze_resolved_worker_module(
|
|
328
|
+
unit: WorkerModuleUnit,
|
|
329
|
+
plan: ResolvedModulePlan,
|
|
330
|
+
) -> AnalyzedWorkerModule:
|
|
331
|
+
"""Convert a resolved minimal projection to the legacy admission shape."""
|
|
332
|
+
|
|
333
|
+
if not isinstance(unit, WorkerModuleUnit) or not isinstance(
|
|
334
|
+
plan, ResolvedModulePlan
|
|
335
|
+
):
|
|
336
|
+
raise _analysis_error(
|
|
337
|
+
"worker module analysis input is invalid",
|
|
338
|
+
SourceSpan(0, 0),
|
|
339
|
+
AMBIGUOUS_BINDING,
|
|
340
|
+
)
|
|
341
|
+
if not _resolved_plan_structure_is_valid(plan):
|
|
342
|
+
raise _analysis_error(
|
|
343
|
+
"worker module analysis input is invalid",
|
|
344
|
+
SourceSpan(0, 0),
|
|
345
|
+
AMBIGUOUS_BINDING,
|
|
346
|
+
)
|
|
347
|
+
if unit.mapped_source.artifact.source_sha256 != plan.source.source_sha256:
|
|
348
|
+
raise _analysis_error(
|
|
349
|
+
"worker module projection does not match its source",
|
|
350
|
+
SourceSpan(0, 0),
|
|
351
|
+
AMBIGUOUS_BINDING,
|
|
352
|
+
)
|
|
353
|
+
if (
|
|
354
|
+
type(plan.catalog_identity) is not tuple
|
|
355
|
+
or len(plan.catalog_identity) != 4
|
|
356
|
+
or any(
|
|
357
|
+
type(value) is not str or not value
|
|
358
|
+
for value in plan.catalog_identity[:2]
|
|
359
|
+
)
|
|
360
|
+
or type(plan.catalog_generation) is not int
|
|
361
|
+
or plan.catalog_generation < 0
|
|
362
|
+
or type(plan.catalog_identity[2]) is not int
|
|
363
|
+
or plan.catalog_identity[2] != plan.catalog_generation
|
|
364
|
+
or not re.fullmatch(r"[0-9a-f]{64}", plan.catalog_identity[3])
|
|
365
|
+
):
|
|
366
|
+
raise _analysis_error(
|
|
367
|
+
"worker module analysis input is invalid",
|
|
368
|
+
SourceSpan(0, 0),
|
|
369
|
+
AMBIGUOUS_BINDING,
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
source = unit.mapped_source.text
|
|
373
|
+
identifier_names = set(plan.source.module_variables)
|
|
374
|
+
method_names = frozenset(method.normalized_name for method in plan.source.methods)
|
|
375
|
+
identifier_names.update(method_names)
|
|
376
|
+
for method in plan.source.methods:
|
|
377
|
+
identifier_names.update(method.declared_names)
|
|
378
|
+
identifier_names.update(item.normalized_name for item in method.bare_names)
|
|
379
|
+
identifier_names.update(
|
|
380
|
+
item.normalized_name for item in plan.source.module_bare_names
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
uses_by_dependency: dict[str, list[DependencyUse]] = {
|
|
384
|
+
dependency.casefold(): [] for dependency in plan.dependencies
|
|
385
|
+
}
|
|
386
|
+
insertion_points: list[MethodInsertionPoints] = []
|
|
387
|
+
method_analyses: list[WorkerMethodAnalysis] = []
|
|
388
|
+
exported_methods: list[str] = []
|
|
389
|
+
for method in plan.methods:
|
|
390
|
+
declaration = method.source.declaration_span
|
|
391
|
+
if method.source.exported:
|
|
392
|
+
exported_methods.append(method.source.name)
|
|
393
|
+
insertion_points.append(
|
|
394
|
+
MethodInsertionPoints(
|
|
395
|
+
method.source.name,
|
|
396
|
+
declaration,
|
|
397
|
+
method.source.alias_declaration_offset,
|
|
398
|
+
method.source.alias_initializer_offset,
|
|
399
|
+
)
|
|
400
|
+
)
|
|
401
|
+
dependency_uses: list[tuple[str, DependencyUse]] = []
|
|
402
|
+
for dependency in method.dependencies:
|
|
403
|
+
normalized = dependency.casefold()
|
|
404
|
+
use = DependencyUse(
|
|
405
|
+
method.source.name,
|
|
406
|
+
"value",
|
|
407
|
+
declaration,
|
|
408
|
+
declaration,
|
|
409
|
+
)
|
|
410
|
+
dependency_uses.append((normalized, use))
|
|
411
|
+
try:
|
|
412
|
+
uses_by_dependency[normalized].append(use)
|
|
413
|
+
except KeyError:
|
|
414
|
+
raise _analysis_error(
|
|
415
|
+
"resolved worker dependency plan is inconsistent",
|
|
416
|
+
declaration,
|
|
417
|
+
AMBIGUOUS_BINDING,
|
|
418
|
+
) from None
|
|
419
|
+
method_analyses.append(
|
|
420
|
+
WorkerMethodAnalysis(
|
|
421
|
+
method.source.name,
|
|
422
|
+
method.source.exported,
|
|
423
|
+
sha256(
|
|
424
|
+
source[
|
|
425
|
+
declaration.start : method.source.alias_initializer_offset
|
|
426
|
+
].encode("utf-8")
|
|
427
|
+
).hexdigest(),
|
|
428
|
+
declaration,
|
|
429
|
+
SourceSpan(method.source.alias_initializer_offset, declaration.end),
|
|
430
|
+
method.source.alias_declaration_offset,
|
|
431
|
+
method.source.alias_initializer_offset,
|
|
432
|
+
tuple(dependency_uses),
|
|
433
|
+
)
|
|
434
|
+
)
|
|
435
|
+
|
|
436
|
+
bindings: list[ModuleDependencyBinding] = []
|
|
437
|
+
generated_names: set[str] = set()
|
|
438
|
+
for dependency in plan.dependencies:
|
|
439
|
+
normalized = dependency.casefold()
|
|
440
|
+
uses = tuple(uses_by_dependency[normalized])
|
|
441
|
+
if not uses:
|
|
442
|
+
raise _analysis_error(
|
|
443
|
+
"resolved worker dependency plan is inconsistent",
|
|
444
|
+
SourceSpan(0, 0),
|
|
445
|
+
AMBIGUOUS_BINDING,
|
|
446
|
+
)
|
|
447
|
+
binding = _dependency_binding(
|
|
448
|
+
CommonModuleDescriptor(dependency, CommonModuleScope.SERVER),
|
|
449
|
+
uses,
|
|
450
|
+
)
|
|
451
|
+
generated = binding.export_variable.casefold()
|
|
452
|
+
if generated in generated_names or generated in identifier_names:
|
|
453
|
+
raise _analysis_error(
|
|
454
|
+
"generated dependency name collides with a source identifier",
|
|
455
|
+
uses[0].span,
|
|
456
|
+
AMBIGUOUS_BINDING,
|
|
457
|
+
)
|
|
458
|
+
generated_names.add(generated)
|
|
459
|
+
bindings.append(binding)
|
|
460
|
+
|
|
461
|
+
parser_identity = plan.source.parser_identity
|
|
462
|
+
if parser_identity is None:
|
|
463
|
+
raise _analysis_error(
|
|
464
|
+
"worker module parser provenance is unavailable",
|
|
465
|
+
SourceSpan(0, 0),
|
|
466
|
+
AMBIGUOUS_BINDING,
|
|
467
|
+
)
|
|
468
|
+
return AnalyzedWorkerModule(
|
|
469
|
+
unit,
|
|
470
|
+
tuple(bindings),
|
|
471
|
+
tuple(exported_methods),
|
|
472
|
+
tuple(insertion_points),
|
|
473
|
+
WorkerModuleAnalysisContext(
|
|
474
|
+
frozenset(plan.source.module_variables),
|
|
475
|
+
method_names,
|
|
476
|
+
frozenset(identifier_names),
|
|
477
|
+
),
|
|
478
|
+
tuple(method_analyses),
|
|
479
|
+
plan.catalog_identity,
|
|
480
|
+
parser_identity,
|
|
481
|
+
)
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
def _resolved_plan_structure_is_valid(plan: ResolvedModulePlan) -> bool:
|
|
485
|
+
if (
|
|
486
|
+
not isinstance(plan.source, ParsedModuleModel)
|
|
487
|
+
or type(plan.methods) is not tuple
|
|
488
|
+
or not all(isinstance(method, ResolvedMethodPlan) for method in plan.methods)
|
|
489
|
+
or tuple(method.source for method in plan.methods) != plan.source.methods
|
|
490
|
+
or not _normalized_name_tuple(plan.implicit_local_names)
|
|
491
|
+
or not _casefold_unique_names(plan.forbidden_global_writes)
|
|
492
|
+
or not _casefold_unique_names(plan.dependencies)
|
|
493
|
+
or not _implicit_names_match_writes(
|
|
494
|
+
plan.implicit_local_names,
|
|
495
|
+
plan.source.module_bare_names,
|
|
496
|
+
)
|
|
497
|
+
):
|
|
498
|
+
return False
|
|
499
|
+
|
|
500
|
+
dependencies = {name.casefold() for name in plan.dependencies}
|
|
501
|
+
module_implicit = set(plan.implicit_local_names)
|
|
502
|
+
method_names = {
|
|
503
|
+
method.normalized_name for method in plan.source.methods
|
|
504
|
+
}
|
|
505
|
+
module_hard_locals = (
|
|
506
|
+
set(plan.source.module_variables) | method_names | set(_PLATFORM_GLOBALS)
|
|
507
|
+
)
|
|
508
|
+
module_forbidden = {
|
|
509
|
+
name.casefold() for name in plan.forbidden_global_writes
|
|
510
|
+
}
|
|
511
|
+
if module_implicit & (
|
|
512
|
+
module_forbidden | dependencies | module_hard_locals
|
|
513
|
+
):
|
|
514
|
+
return False
|
|
515
|
+
resolved_dependencies: set[str] = set()
|
|
516
|
+
for method in plan.methods:
|
|
517
|
+
expected_locals = tuple(
|
|
518
|
+
sorted(
|
|
519
|
+
{
|
|
520
|
+
*plan.source.module_variables,
|
|
521
|
+
*plan.implicit_local_names,
|
|
522
|
+
*method.source.declared_names,
|
|
523
|
+
*method.implicit_local_names,
|
|
524
|
+
}
|
|
525
|
+
)
|
|
526
|
+
)
|
|
527
|
+
if (
|
|
528
|
+
not _normalized_name_tuple(method.local_names)
|
|
529
|
+
or not _normalized_name_tuple(method.implicit_local_names)
|
|
530
|
+
or method.local_names != expected_locals
|
|
531
|
+
or not _implicit_names_match_writes(
|
|
532
|
+
method.implicit_local_names,
|
|
533
|
+
method.source.bare_names,
|
|
534
|
+
)
|
|
535
|
+
or not _casefold_unique_names(method.forbidden_global_writes)
|
|
536
|
+
or not _casefold_unique_names(method.dependencies)
|
|
537
|
+
):
|
|
538
|
+
return False
|
|
539
|
+
method_dependencies = {
|
|
540
|
+
dependency.casefold() for dependency in method.dependencies
|
|
541
|
+
}
|
|
542
|
+
method_forbidden = {
|
|
543
|
+
name.casefold() for name in method.forbidden_global_writes
|
|
544
|
+
}
|
|
545
|
+
method_hard_locals = (
|
|
546
|
+
set(plan.source.module_variables)
|
|
547
|
+
| module_implicit
|
|
548
|
+
| set(method.source.declared_names)
|
|
549
|
+
| method_names
|
|
550
|
+
| set(_PLATFORM_GLOBALS)
|
|
551
|
+
)
|
|
552
|
+
if (
|
|
553
|
+
not method_dependencies.issubset(dependencies)
|
|
554
|
+
or method_dependencies & method_forbidden
|
|
555
|
+
or set(method.implicit_local_names)
|
|
556
|
+
& (method_dependencies | method_forbidden | method_hard_locals)
|
|
557
|
+
):
|
|
558
|
+
return False
|
|
559
|
+
resolved_dependencies.update(method_dependencies)
|
|
560
|
+
return resolved_dependencies == dependencies
|
|
561
|
+
|
|
562
|
+
|
|
563
|
+
def _normalized_name_tuple(names: object) -> bool:
|
|
564
|
+
return (
|
|
565
|
+
type(names) is tuple
|
|
566
|
+
and all(
|
|
567
|
+
type(name) is str and name and name == name.casefold()
|
|
568
|
+
for name in names
|
|
569
|
+
)
|
|
570
|
+
and names == tuple(sorted(set(names)))
|
|
571
|
+
)
|
|
572
|
+
|
|
573
|
+
|
|
574
|
+
def _casefold_unique_names(names: object) -> bool:
|
|
575
|
+
return (
|
|
576
|
+
type(names) is tuple
|
|
577
|
+
and all(type(name) is str and name for name in names)
|
|
578
|
+
and len(names) == len({name.casefold() for name in names})
|
|
579
|
+
)
|
|
580
|
+
|
|
581
|
+
|
|
582
|
+
def _implicit_names_match_writes(
|
|
583
|
+
names: tuple[str, ...],
|
|
584
|
+
bare_names: tuple[Any, ...],
|
|
585
|
+
) -> bool:
|
|
586
|
+
candidates = {item.normalized_name: item for item in bare_names}
|
|
587
|
+
return all(
|
|
588
|
+
(candidate := candidates.get(name)) is not None
|
|
589
|
+
and candidate.kinds & BareNameKind.BARE_WRITE
|
|
590
|
+
and _is_bsl_identifier(candidate.name)
|
|
591
|
+
for name in names
|
|
592
|
+
)
|
|
593
|
+
|
|
594
|
+
|
|
595
|
+
def _analyze_parsed_worker_module(
|
|
596
|
+
unit: WorkerModuleUnit,
|
|
597
|
+
catalog: CommonModuleCatalogSnapshot,
|
|
598
|
+
root: Any,
|
|
599
|
+
tokens: tuple[Any, ...],
|
|
600
|
+
*,
|
|
601
|
+
parser_identity: tuple[str, str] = ("", ""),
|
|
602
|
+
) -> AnalyzedWorkerModule:
|
|
603
|
+
module_variables = tuple(
|
|
604
|
+
variable
|
|
605
|
+
for declaration in root.Declarations
|
|
606
|
+
for variable in declaration.Variables
|
|
607
|
+
)
|
|
608
|
+
module_names = {variable.Name.casefold() for variable in module_variables}
|
|
609
|
+
methods = tuple(_module_methods(root))
|
|
610
|
+
method_names: set[str] = set()
|
|
611
|
+
for method in methods:
|
|
612
|
+
normalized = method.Declaration.Name.casefold()
|
|
613
|
+
if normalized in method_names:
|
|
614
|
+
raise _analysis_error(
|
|
615
|
+
"worker module contains an ambiguous method declaration",
|
|
616
|
+
_source_span(method.span),
|
|
617
|
+
AMBIGUOUS_BINDING,
|
|
618
|
+
)
|
|
619
|
+
method_names.add(normalized)
|
|
620
|
+
catalog_by_name = {item.canonical_name.casefold(): item for item in catalog.modules}
|
|
621
|
+
for variable in module_variables:
|
|
622
|
+
if variable.Export is not None and variable.Name.casefold() in catalog_by_name:
|
|
623
|
+
raise _analysis_error(
|
|
624
|
+
"exported module variable conflicts with a common module",
|
|
625
|
+
_source_span(variable.span),
|
|
626
|
+
AMBIGUOUS_BINDING,
|
|
627
|
+
)
|
|
628
|
+
identifier_spans: dict[str, SourceSpan] = {}
|
|
629
|
+
for_token_starts: list[int] = []
|
|
630
|
+
for token in tokens:
|
|
631
|
+
if token.type == "ID":
|
|
632
|
+
identifier_spans.setdefault(
|
|
633
|
+
token.text.casefold(), SourceSpan(token.start, token.end)
|
|
634
|
+
)
|
|
635
|
+
elif token.type == "ДЛЯ":
|
|
636
|
+
for_token_starts.append(token.start)
|
|
637
|
+
loop_token_starts = tuple(for_token_starts)
|
|
638
|
+
context = WorkerModuleAnalysisContext(
|
|
639
|
+
frozenset(module_names),
|
|
640
|
+
frozenset(method_names),
|
|
641
|
+
frozenset(identifier_spans),
|
|
642
|
+
)
|
|
643
|
+
uses_by_module: dict[str, list[DependencyUse]] = {}
|
|
644
|
+
insertion_points: list[MethodInsertionPoints] = []
|
|
645
|
+
exported_methods: list[str] = []
|
|
646
|
+
method_analyses: list[WorkerMethodAnalysis] = []
|
|
647
|
+
|
|
648
|
+
elements = root.Elements
|
|
649
|
+
while elements.Item is not None:
|
|
650
|
+
item = elements.Item
|
|
651
|
+
if type(item).__name__ != "Method":
|
|
652
|
+
for node in _walk_ast(item):
|
|
653
|
+
kind = type(node).__name__
|
|
654
|
+
if kind == "ExecuteStatement":
|
|
655
|
+
raise _analysis_error(
|
|
656
|
+
"dynamic execution is unsupported in worker modules",
|
|
657
|
+
_source_span(node.span),
|
|
658
|
+
DYNAMIC_EXECUTE,
|
|
659
|
+
)
|
|
660
|
+
if kind != "AccessChain":
|
|
661
|
+
continue
|
|
662
|
+
normalized = node.Root.casefold()
|
|
663
|
+
if (
|
|
664
|
+
normalized in module_names
|
|
665
|
+
or normalized in _PLATFORM_GLOBALS
|
|
666
|
+
):
|
|
667
|
+
continue
|
|
668
|
+
if normalized in method_names and node.Arguments is not None:
|
|
669
|
+
continue
|
|
670
|
+
if normalized in catalog_by_name:
|
|
671
|
+
raise _analysis_error(
|
|
672
|
+
"common module dependency is unsupported in module scope",
|
|
673
|
+
_source_span(node.span),
|
|
674
|
+
MODULE_SCOPE_DEPENDENCY,
|
|
675
|
+
)
|
|
676
|
+
if node.Postfix:
|
|
677
|
+
raise _analysis_error(
|
|
678
|
+
"qualified module dependency cannot be resolved",
|
|
679
|
+
_source_span(node.span),
|
|
680
|
+
AMBIGUOUS_BINDING,
|
|
681
|
+
)
|
|
682
|
+
elements = elements.Rest
|
|
683
|
+
|
|
684
|
+
for method in methods:
|
|
685
|
+
declaration = method.Declaration
|
|
686
|
+
if declaration.Export is not None:
|
|
687
|
+
exported_methods.append(declaration.Name)
|
|
688
|
+
method_span = _source_span(method.span)
|
|
689
|
+
first_statement_start = declaration.Body.Code.span.start
|
|
690
|
+
declarations_end = (
|
|
691
|
+
_declaration_end(declaration.Body.LocalDeclarations[-1], tokens)
|
|
692
|
+
if declaration.Body.LocalDeclarations
|
|
693
|
+
else declaration.Body.Code.span.start
|
|
694
|
+
)
|
|
695
|
+
insertion_points.append(
|
|
696
|
+
MethodInsertionPoints(
|
|
697
|
+
declaration.Name,
|
|
698
|
+
method_span,
|
|
699
|
+
declarations_end,
|
|
700
|
+
first_statement_start,
|
|
701
|
+
)
|
|
702
|
+
)
|
|
703
|
+
executable_body = _executable_body(
|
|
704
|
+
declaration.Body.Code,
|
|
705
|
+
loop_token_starts,
|
|
706
|
+
)
|
|
707
|
+
shadowed = set(module_names)
|
|
708
|
+
if declaration.Parameters is not None:
|
|
709
|
+
shadowed |= {item.Name.casefold() for item in declaration.Parameters.Items}
|
|
710
|
+
shadowed |= {
|
|
711
|
+
name.casefold()
|
|
712
|
+
for local in declaration.Body.LocalDeclarations
|
|
713
|
+
for name in local.Names
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
accesses: list[Any] = []
|
|
717
|
+
for node in _walk_ast(declaration.Body.Code):
|
|
718
|
+
kind = type(node).__name__
|
|
719
|
+
if kind == "ExecuteStatement":
|
|
720
|
+
raise _analysis_error(
|
|
721
|
+
"dynamic execution is unsupported in worker modules",
|
|
722
|
+
_source_span(node.span),
|
|
723
|
+
DYNAMIC_EXECUTE,
|
|
724
|
+
)
|
|
725
|
+
if kind in {"ForEachStatement", "ForRangeStatement"}:
|
|
726
|
+
shadowed.add(node.Variable.casefold())
|
|
727
|
+
if kind == "SimpleStatement" and node.Value is not None:
|
|
728
|
+
target = node.Target
|
|
729
|
+
if (
|
|
730
|
+
type(target).__name__ == "AccessChain"
|
|
731
|
+
and target.Arguments is None
|
|
732
|
+
and not target.Postfix
|
|
733
|
+
):
|
|
734
|
+
shadowed.add(target.Root.casefold())
|
|
735
|
+
if kind == "AccessChain":
|
|
736
|
+
accesses.append(node)
|
|
737
|
+
|
|
738
|
+
dependency_uses: list[tuple[str, DependencyUse]] = []
|
|
739
|
+
for node in accesses:
|
|
740
|
+
normalized = node.Root.casefold()
|
|
741
|
+
if normalized in shadowed or normalized in _PLATFORM_GLOBALS:
|
|
742
|
+
continue
|
|
743
|
+
if normalized in method_names and node.Arguments is not None:
|
|
744
|
+
continue
|
|
745
|
+
descriptor = catalog_by_name.get(normalized)
|
|
746
|
+
if descriptor is None:
|
|
747
|
+
if node.Arguments is not None and not node.Postfix:
|
|
748
|
+
continue
|
|
749
|
+
if node.Postfix:
|
|
750
|
+
raise _analysis_error(
|
|
751
|
+
"qualified module dependency cannot be resolved",
|
|
752
|
+
_source_span(node.span),
|
|
753
|
+
AMBIGUOUS_BINDING,
|
|
754
|
+
)
|
|
755
|
+
continue
|
|
756
|
+
|
|
757
|
+
category = _dependency_category(node)
|
|
758
|
+
use = DependencyUse(
|
|
759
|
+
declaration.Name,
|
|
760
|
+
category,
|
|
761
|
+
_source_span(node.span),
|
|
762
|
+
method_span,
|
|
763
|
+
)
|
|
764
|
+
dependency_uses.append((normalized, use))
|
|
765
|
+
uses_by_module.setdefault(normalized, []).append(use)
|
|
766
|
+
|
|
767
|
+
method_analyses.append(
|
|
768
|
+
WorkerMethodAnalysis(
|
|
769
|
+
declaration.Name,
|
|
770
|
+
declaration.Export is not None,
|
|
771
|
+
sha256(
|
|
772
|
+
unit.mapped_source.text[
|
|
773
|
+
method_span.start : executable_body.start
|
|
774
|
+
].encode("utf-8")
|
|
775
|
+
).hexdigest(),
|
|
776
|
+
method_span,
|
|
777
|
+
executable_body,
|
|
778
|
+
declarations_end,
|
|
779
|
+
first_statement_start,
|
|
780
|
+
tuple(dependency_uses),
|
|
781
|
+
)
|
|
782
|
+
)
|
|
783
|
+
|
|
784
|
+
generated_names: set[str] = set()
|
|
785
|
+
for normalized in sorted(uses_by_module):
|
|
786
|
+
generated_name = dependency_export_name(
|
|
787
|
+
catalog_by_name[normalized].canonical_name
|
|
788
|
+
).casefold()
|
|
789
|
+
if generated_name in generated_names:
|
|
790
|
+
raise _analysis_error(
|
|
791
|
+
"generated dependency names collide",
|
|
792
|
+
uses_by_module[normalized][0].span,
|
|
793
|
+
AMBIGUOUS_BINDING,
|
|
794
|
+
)
|
|
795
|
+
generated_names.add(generated_name)
|
|
796
|
+
if generated_name in context.source_identifier_names:
|
|
797
|
+
raise _analysis_error(
|
|
798
|
+
"generated dependency name collides with a source identifier",
|
|
799
|
+
identifier_spans[generated_name],
|
|
800
|
+
AMBIGUOUS_BINDING,
|
|
801
|
+
)
|
|
802
|
+
|
|
803
|
+
bindings = tuple(
|
|
804
|
+
_dependency_binding(catalog_by_name[name], tuple(uses_by_module[name]))
|
|
805
|
+
for name in sorted(uses_by_module)
|
|
806
|
+
)
|
|
807
|
+
return AnalyzedWorkerModule(
|
|
808
|
+
unit,
|
|
809
|
+
bindings,
|
|
810
|
+
tuple(exported_methods),
|
|
811
|
+
tuple(insertion_points),
|
|
812
|
+
context,
|
|
813
|
+
tuple(method_analyses),
|
|
814
|
+
(
|
|
815
|
+
catalog.profile,
|
|
816
|
+
catalog.preprocessor_profile,
|
|
817
|
+
catalog.revision,
|
|
818
|
+
catalog.sha256,
|
|
819
|
+
),
|
|
820
|
+
parser_identity,
|
|
821
|
+
)
|
|
822
|
+
|
|
823
|
+
|
|
824
|
+
def _prepare_isolated_worker_method(
|
|
825
|
+
unit: WorkerModuleUnit,
|
|
826
|
+
catalog: CommonModuleCatalogSnapshot,
|
|
827
|
+
parser_target: PythonParserTarget,
|
|
828
|
+
method_span: SourceSpan,
|
|
829
|
+
context: WorkerModuleAnalysisContext,
|
|
830
|
+
*,
|
|
831
|
+
profiler: PhaseRecorder | None = None,
|
|
832
|
+
) -> _PreparedIsolatedWorkerMethodAnalysis:
|
|
833
|
+
"""Parse one complete method and defer its context-dependent analysis."""
|
|
834
|
+
|
|
835
|
+
method_source = unit.mapped_source.text[method_span.start : method_span.end]
|
|
836
|
+
|
|
837
|
+
def parse() -> tuple[Any, tuple[Any, ...]] | None:
|
|
838
|
+
try:
|
|
839
|
+
return parse_raw_module(method_source, parser_target)
|
|
840
|
+
except BslLexError as error:
|
|
841
|
+
if error.code in {
|
|
842
|
+
"unterminated_string",
|
|
843
|
+
"unterminated_date_literal",
|
|
844
|
+
}:
|
|
845
|
+
try:
|
|
846
|
+
tokenize(unit.mapped_source.text)
|
|
847
|
+
except BslLexError as canonical_error:
|
|
848
|
+
raise canonical_error from error
|
|
849
|
+
return None
|
|
850
|
+
absolute_start = method_span.start + error.span.start
|
|
851
|
+
raise BslLexError(
|
|
852
|
+
_remap_isolated_error_message(
|
|
853
|
+
str(error),
|
|
854
|
+
error.span.start,
|
|
855
|
+
absolute_start,
|
|
856
|
+
),
|
|
857
|
+
span=SourceSpan(
|
|
858
|
+
absolute_start,
|
|
859
|
+
method_span.start + error.span.end,
|
|
860
|
+
),
|
|
861
|
+
code=error.code,
|
|
862
|
+
) from error
|
|
863
|
+
except BslParseError as error:
|
|
864
|
+
absolute_start = method_span.start + error.span.start
|
|
865
|
+
raise BslParseError(
|
|
866
|
+
_remap_isolated_error_message(
|
|
867
|
+
str(error),
|
|
868
|
+
error.span.start,
|
|
869
|
+
absolute_start,
|
|
870
|
+
),
|
|
871
|
+
span=SourceSpan(
|
|
872
|
+
absolute_start,
|
|
873
|
+
method_span.start + error.span.end,
|
|
874
|
+
),
|
|
875
|
+
code=error.code,
|
|
876
|
+
) from error
|
|
877
|
+
|
|
878
|
+
parsed = (
|
|
879
|
+
parse()
|
|
880
|
+
if profiler is None
|
|
881
|
+
else profiler.measure_parser("delta_method_parses", parse)
|
|
882
|
+
)
|
|
883
|
+
if parsed is None:
|
|
884
|
+
return _PreparedIsolatedWorkerMethodAnalysis(lambda: None)
|
|
885
|
+
root, tokens = parsed
|
|
886
|
+
|
|
887
|
+
def analyze() -> _IsolatedWorkerMethodAnalysis | None:
|
|
888
|
+
elements: list[Any] = []
|
|
889
|
+
cursor = root.Elements
|
|
890
|
+
while cursor.Item is not None:
|
|
891
|
+
elements.append(cursor.Item)
|
|
892
|
+
cursor = cursor.Rest
|
|
893
|
+
if (
|
|
894
|
+
root.Declarations
|
|
895
|
+
or len(elements) != 1
|
|
896
|
+
or type(elements[0]).__name__ != "Method"
|
|
897
|
+
):
|
|
898
|
+
return None
|
|
899
|
+
|
|
900
|
+
method = elements[0]
|
|
901
|
+
declaration = method.Declaration
|
|
902
|
+
local_method_span = _source_span(method.span)
|
|
903
|
+
absolute_method_span = _offset_source_span(local_method_span, method_span.start)
|
|
904
|
+
loop_token_starts = tuple(
|
|
905
|
+
token.start for token in tokens if token.type == "ДЛЯ"
|
|
906
|
+
)
|
|
907
|
+
local_executable_body = _executable_body(
|
|
908
|
+
declaration.Body.Code,
|
|
909
|
+
loop_token_starts,
|
|
910
|
+
)
|
|
911
|
+
executable_body = _offset_source_span(
|
|
912
|
+
local_executable_body,
|
|
913
|
+
method_span.start,
|
|
914
|
+
)
|
|
915
|
+
declarations_end = (
|
|
916
|
+
_declaration_end(declaration.Body.LocalDeclarations[-1], tokens)
|
|
917
|
+
if declaration.Body.LocalDeclarations
|
|
918
|
+
else declaration.Body.Code.span.start
|
|
919
|
+
)
|
|
920
|
+
first_statement_start = declaration.Body.Code.span.start
|
|
921
|
+
shadowed = set(context.module_variable_names)
|
|
922
|
+
if declaration.Parameters is not None:
|
|
923
|
+
shadowed |= {
|
|
924
|
+
item.Name.casefold() for item in declaration.Parameters.Items
|
|
925
|
+
}
|
|
926
|
+
shadowed |= {
|
|
927
|
+
name.casefold()
|
|
928
|
+
for local in declaration.Body.LocalDeclarations
|
|
929
|
+
for name in local.Names
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
accesses: list[Any] = []
|
|
933
|
+
for node in _walk_ast(declaration.Body.Code):
|
|
934
|
+
kind = type(node).__name__
|
|
935
|
+
if kind == "ExecuteStatement":
|
|
936
|
+
raise _analysis_error(
|
|
937
|
+
"dynamic execution is unsupported in worker modules",
|
|
938
|
+
_offset_source_span(_source_span(node.span), method_span.start),
|
|
939
|
+
DYNAMIC_EXECUTE,
|
|
940
|
+
)
|
|
941
|
+
if kind in {"ForEachStatement", "ForRangeStatement"}:
|
|
942
|
+
shadowed.add(node.Variable.casefold())
|
|
943
|
+
if kind == "SimpleStatement" and node.Value is not None:
|
|
944
|
+
target = node.Target
|
|
945
|
+
if (
|
|
946
|
+
type(target).__name__ == "AccessChain"
|
|
947
|
+
and target.Arguments is None
|
|
948
|
+
and not target.Postfix
|
|
949
|
+
):
|
|
950
|
+
shadowed.add(target.Root.casefold())
|
|
951
|
+
if kind == "AccessChain":
|
|
952
|
+
accesses.append(node)
|
|
953
|
+
|
|
954
|
+
catalog_by_name = {
|
|
955
|
+
item.canonical_name.casefold(): item for item in catalog.modules
|
|
956
|
+
}
|
|
957
|
+
dependency_uses: list[tuple[str, DependencyUse]] = []
|
|
958
|
+
for node in accesses:
|
|
959
|
+
normalized = node.Root.casefold()
|
|
960
|
+
if normalized in shadowed or normalized in _PLATFORM_GLOBALS:
|
|
961
|
+
continue
|
|
962
|
+
if normalized in context.method_names and node.Arguments is not None:
|
|
963
|
+
continue
|
|
964
|
+
descriptor = catalog_by_name.get(normalized)
|
|
965
|
+
if descriptor is None:
|
|
966
|
+
if node.Arguments is not None and not node.Postfix:
|
|
967
|
+
continue
|
|
968
|
+
if node.Postfix:
|
|
969
|
+
raise _analysis_error(
|
|
970
|
+
"qualified module dependency cannot be resolved",
|
|
971
|
+
_offset_source_span(
|
|
972
|
+
_source_span(node.span), method_span.start
|
|
973
|
+
),
|
|
974
|
+
AMBIGUOUS_BINDING,
|
|
975
|
+
)
|
|
976
|
+
continue
|
|
977
|
+
try:
|
|
978
|
+
category = _dependency_category(node)
|
|
979
|
+
except ModuleUniverseAdmissionError as error:
|
|
980
|
+
raise _analysis_error(
|
|
981
|
+
str(error),
|
|
982
|
+
_offset_source_span(error.span, method_span.start),
|
|
983
|
+
error.code,
|
|
984
|
+
) from error
|
|
985
|
+
use = DependencyUse(
|
|
986
|
+
declaration.Name,
|
|
987
|
+
category,
|
|
988
|
+
_offset_source_span(_source_span(node.span), method_span.start),
|
|
989
|
+
absolute_method_span,
|
|
990
|
+
)
|
|
991
|
+
dependency_uses.append((normalized, use))
|
|
992
|
+
|
|
993
|
+
analysis = WorkerMethodAnalysis(
|
|
994
|
+
declaration.Name,
|
|
995
|
+
declaration.Export is not None,
|
|
996
|
+
sha256(
|
|
997
|
+
method_source[
|
|
998
|
+
local_method_span.start : local_executable_body.start
|
|
999
|
+
].encode("utf-8")
|
|
1000
|
+
).hexdigest(),
|
|
1001
|
+
absolute_method_span,
|
|
1002
|
+
executable_body,
|
|
1003
|
+
method_span.start + declarations_end,
|
|
1004
|
+
method_span.start + first_statement_start,
|
|
1005
|
+
tuple(dependency_uses),
|
|
1006
|
+
)
|
|
1007
|
+
return _IsolatedWorkerMethodAnalysis(
|
|
1008
|
+
analysis,
|
|
1009
|
+
MethodInsertionPoints(
|
|
1010
|
+
declaration.Name,
|
|
1011
|
+
absolute_method_span,
|
|
1012
|
+
method_span.start + declarations_end,
|
|
1013
|
+
method_span.start + first_statement_start,
|
|
1014
|
+
),
|
|
1015
|
+
)
|
|
1016
|
+
|
|
1017
|
+
return _PreparedIsolatedWorkerMethodAnalysis(analyze)
|
|
1018
|
+
|
|
1019
|
+
|
|
1020
|
+
@dataclass(frozen=True, slots=True)
|
|
1021
|
+
class _AliasTransformPlan:
|
|
1022
|
+
source: MappedSource
|
|
1023
|
+
line_ending: str
|
|
1024
|
+
fields: tuple[tuple[str, SourceSpan, str], ...]
|
|
1025
|
+
declarations: dict[int, tuple[tuple[str, DependencyUse, str], ...]]
|
|
1026
|
+
initializers: dict[int, tuple[tuple[str, DependencyUse, str], ...]]
|
|
1027
|
+
reuse_boundaries: tuple[int, ...]
|
|
1028
|
+
|
|
1029
|
+
|
|
1030
|
+
def lower_worker_module(
|
|
1031
|
+
analysis: AnalyzedWorkerModule,
|
|
1032
|
+
*,
|
|
1033
|
+
profiler: PhaseRecorder | None = None,
|
|
1034
|
+
) -> LoweredWorkerModule:
|
|
1035
|
+
if not isinstance(analysis, AnalyzedWorkerModule):
|
|
1036
|
+
raise ValueError("worker module lowering input is invalid")
|
|
1037
|
+
|
|
1038
|
+
plan_operation = lambda: _plan_alias_transform(analysis)
|
|
1039
|
+
plan = (
|
|
1040
|
+
plan_operation()
|
|
1041
|
+
if profiler is None
|
|
1042
|
+
else profiler.measure("alias_transform", plan_operation)
|
|
1043
|
+
)
|
|
1044
|
+
compose = lambda: _compose_worker_module_source(plan)
|
|
1045
|
+
mapped_source = (
|
|
1046
|
+
compose()
|
|
1047
|
+
if profiler is None
|
|
1048
|
+
else profiler.measure("source_map_composition", compose)
|
|
1049
|
+
)
|
|
1050
|
+
dependency_bindings_sha256 = sha256(
|
|
1051
|
+
json.dumps(
|
|
1052
|
+
[binding.sha256 for binding in analysis.dependencies],
|
|
1053
|
+
ensure_ascii=False,
|
|
1054
|
+
separators=(",", ":"),
|
|
1055
|
+
).encode("utf-8")
|
|
1056
|
+
).hexdigest()
|
|
1057
|
+
lowered = LoweredWorkerModule(
|
|
1058
|
+
analysis,
|
|
1059
|
+
mapped_source,
|
|
1060
|
+
dependency_bindings_sha256,
|
|
1061
|
+
)
|
|
1062
|
+
admit = lambda: _mint_worker_semantic_admission(lowered)
|
|
1063
|
+
if profiler is None:
|
|
1064
|
+
admit()
|
|
1065
|
+
else:
|
|
1066
|
+
profiler.measure("admission", admit)
|
|
1067
|
+
return lowered
|
|
1068
|
+
|
|
1069
|
+
|
|
1070
|
+
def lower_resolved_worker_module(
|
|
1071
|
+
unit: WorkerModuleUnit,
|
|
1072
|
+
plan: ResolvedModulePlan,
|
|
1073
|
+
*,
|
|
1074
|
+
profiler: PhaseRecorder | None = None,
|
|
1075
|
+
) -> LoweredWorkerModule:
|
|
1076
|
+
"""Lower a resolved projection directly, without parsing or map composition."""
|
|
1077
|
+
|
|
1078
|
+
analyze = lambda: analyze_resolved_worker_module(unit, plan)
|
|
1079
|
+
analysis = (
|
|
1080
|
+
analyze()
|
|
1081
|
+
if profiler is None
|
|
1082
|
+
else profiler.measure("resolved_analysis_adapter", analyze)
|
|
1083
|
+
)
|
|
1084
|
+
plan_insertions = lambda: _resolved_reload_insertions(analysis, plan)
|
|
1085
|
+
insertions = (
|
|
1086
|
+
plan_insertions()
|
|
1087
|
+
if profiler is None
|
|
1088
|
+
else profiler.measure("alias_transform", plan_insertions)
|
|
1089
|
+
)
|
|
1090
|
+
materialize = lambda: materialize_worker_reload_source(
|
|
1091
|
+
unit.mapped_source,
|
|
1092
|
+
insertions,
|
|
1093
|
+
method_boundaries=tuple(
|
|
1094
|
+
ReloadMethodBoundary(
|
|
1095
|
+
method.source.name,
|
|
1096
|
+
method.source.declaration_span.start,
|
|
1097
|
+
method.source.alias_initializer_offset,
|
|
1098
|
+
method.source.declaration_span.end,
|
|
1099
|
+
)
|
|
1100
|
+
for method in plan.methods
|
|
1101
|
+
),
|
|
1102
|
+
)
|
|
1103
|
+
mapped_source = (
|
|
1104
|
+
materialize()
|
|
1105
|
+
if profiler is None
|
|
1106
|
+
else profiler.measure("source_map_composition", materialize)
|
|
1107
|
+
)
|
|
1108
|
+
lowered = LoweredWorkerModule(
|
|
1109
|
+
analysis,
|
|
1110
|
+
mapped_source,
|
|
1111
|
+
_canonical_dependency_bindings_sha256(analysis.dependencies),
|
|
1112
|
+
"module-universe-v2",
|
|
1113
|
+
)
|
|
1114
|
+
admit = lambda: _mint_worker_semantic_admission(lowered)
|
|
1115
|
+
if profiler is None:
|
|
1116
|
+
admit()
|
|
1117
|
+
else:
|
|
1118
|
+
profiler.measure("admission", admit)
|
|
1119
|
+
return lowered
|
|
1120
|
+
|
|
1121
|
+
|
|
1122
|
+
def _resolved_reload_insertions(
|
|
1123
|
+
analysis: AnalyzedWorkerModule,
|
|
1124
|
+
plan: ResolvedModulePlan,
|
|
1125
|
+
) -> tuple[ReloadInsertion, ...]:
|
|
1126
|
+
source = analysis.unit.mapped_source.text
|
|
1127
|
+
bindings = {
|
|
1128
|
+
binding.target_module.casefold(): binding
|
|
1129
|
+
for binding in analysis.dependencies
|
|
1130
|
+
}
|
|
1131
|
+
insertions: list[ReloadInsertion] = []
|
|
1132
|
+
for binding in analysis.dependencies:
|
|
1133
|
+
insertions.append(
|
|
1134
|
+
ReloadInsertion.synthetic(
|
|
1135
|
+
source_offset=0,
|
|
1136
|
+
text=f"Перем {binding.export_variable} Экспорт;\n",
|
|
1137
|
+
method_name=None,
|
|
1138
|
+
synthetic_region=DEPENDENCY_FIELD_REGION,
|
|
1139
|
+
anchor=SourceSpan(0, 0),
|
|
1140
|
+
)
|
|
1141
|
+
)
|
|
1142
|
+
module_implicit_names = _implicit_local_spellings(
|
|
1143
|
+
plan.implicit_local_names,
|
|
1144
|
+
plan.source.module_bare_names,
|
|
1145
|
+
)
|
|
1146
|
+
if module_implicit_names:
|
|
1147
|
+
module_span = SourceSpan(0, len(source))
|
|
1148
|
+
insertions.append(
|
|
1149
|
+
ReloadInsertion.derived(
|
|
1150
|
+
source_offset=0,
|
|
1151
|
+
text="".join(f"Перем {name};\n" for name in module_implicit_names),
|
|
1152
|
+
method_name=None,
|
|
1153
|
+
synthetic_region=IMPLICIT_LOCAL_DECLARATION_REGION,
|
|
1154
|
+
origin=module_span,
|
|
1155
|
+
anchor=module_span,
|
|
1156
|
+
)
|
|
1157
|
+
)
|
|
1158
|
+
|
|
1159
|
+
dependent_offsets: list[int] = []
|
|
1160
|
+
for method in plan.methods:
|
|
1161
|
+
if method.dependencies or method.implicit_local_names:
|
|
1162
|
+
dependent_offsets.append(method.source.alias_declaration_offset)
|
|
1163
|
+
if method.dependencies:
|
|
1164
|
+
dependent_offsets.append(method.source.alias_initializer_offset)
|
|
1165
|
+
line_contexts = (
|
|
1166
|
+
_reload_line_contexts(source, tuple(dependent_offsets))
|
|
1167
|
+
if dependent_offsets
|
|
1168
|
+
else {}
|
|
1169
|
+
)
|
|
1170
|
+
for method in plan.methods:
|
|
1171
|
+
if not method.dependencies and not method.implicit_local_names:
|
|
1172
|
+
continue
|
|
1173
|
+
method_bindings = tuple(
|
|
1174
|
+
bindings[dependency.casefold()]
|
|
1175
|
+
for dependency in method.dependencies
|
|
1176
|
+
)
|
|
1177
|
+
declaration_offset = method.source.alias_declaration_offset
|
|
1178
|
+
implicit_names = _implicit_local_spellings(
|
|
1179
|
+
method.implicit_local_names,
|
|
1180
|
+
method.source.bare_names,
|
|
1181
|
+
)
|
|
1182
|
+
if implicit_names:
|
|
1183
|
+
insertions.append(
|
|
1184
|
+
ReloadInsertion.derived(
|
|
1185
|
+
source_offset=declaration_offset,
|
|
1186
|
+
text=_render_reload_block(
|
|
1187
|
+
tuple(f"Перем {name};" for name in implicit_names),
|
|
1188
|
+
line_contexts[declaration_offset],
|
|
1189
|
+
),
|
|
1190
|
+
method_name=method.source.name,
|
|
1191
|
+
synthetic_region=IMPLICIT_LOCAL_DECLARATION_REGION,
|
|
1192
|
+
origin=method.source.declaration_span,
|
|
1193
|
+
anchor=method.source.declaration_span,
|
|
1194
|
+
)
|
|
1195
|
+
)
|
|
1196
|
+
if method_bindings:
|
|
1197
|
+
initializer_offset = method.source.alias_initializer_offset
|
|
1198
|
+
insertions.append(
|
|
1199
|
+
ReloadInsertion.derived(
|
|
1200
|
+
source_offset=declaration_offset,
|
|
1201
|
+
text=_render_reload_block(
|
|
1202
|
+
tuple(
|
|
1203
|
+
f"Перем {binding.target_module};"
|
|
1204
|
+
for binding in method_bindings
|
|
1205
|
+
),
|
|
1206
|
+
line_contexts[declaration_offset],
|
|
1207
|
+
),
|
|
1208
|
+
method_name=method.source.name,
|
|
1209
|
+
synthetic_region=DEPENDENCY_ALIAS_DECLARATION_REGION,
|
|
1210
|
+
origin=method.source.declaration_span,
|
|
1211
|
+
anchor=method.source.declaration_span,
|
|
1212
|
+
)
|
|
1213
|
+
)
|
|
1214
|
+
insertions.append(
|
|
1215
|
+
ReloadInsertion.derived(
|
|
1216
|
+
source_offset=initializer_offset,
|
|
1217
|
+
text=_render_reload_block(
|
|
1218
|
+
tuple(
|
|
1219
|
+
f"{binding.target_module} = {binding.export_variable};"
|
|
1220
|
+
for binding in method_bindings
|
|
1221
|
+
),
|
|
1222
|
+
line_contexts[initializer_offset],
|
|
1223
|
+
),
|
|
1224
|
+
method_name=method.source.name,
|
|
1225
|
+
synthetic_region=DEPENDENCY_ALIAS_INITIALIZER_REGION,
|
|
1226
|
+
origin=method.source.declaration_span,
|
|
1227
|
+
anchor=method.source.declaration_span,
|
|
1228
|
+
)
|
|
1229
|
+
)
|
|
1230
|
+
return tuple(insertions)
|
|
1231
|
+
|
|
1232
|
+
|
|
1233
|
+
def _implicit_local_spellings(
|
|
1234
|
+
names: tuple[str, ...],
|
|
1235
|
+
bare_names: tuple[Any, ...],
|
|
1236
|
+
) -> tuple[str, ...]:
|
|
1237
|
+
by_name = {item.normalized_name: item.name for item in bare_names}
|
|
1238
|
+
return tuple(by_name[name] for name in names)
|
|
1239
|
+
|
|
1240
|
+
|
|
1241
|
+
def _render_reload_block(
|
|
1242
|
+
lines: tuple[str, ...],
|
|
1243
|
+
context: tuple[bool, str, bool],
|
|
1244
|
+
) -> str:
|
|
1245
|
+
prefix_has_code, indent, next_is_newline = context
|
|
1246
|
+
leading = f"\n{indent}" if prefix_has_code else ""
|
|
1247
|
+
trailing = "" if next_is_newline else f"\n{indent}"
|
|
1248
|
+
return leading + f"\n{indent}".join(lines) + trailing
|
|
1249
|
+
|
|
1250
|
+
|
|
1251
|
+
def _reload_line_contexts(
|
|
1252
|
+
source: str,
|
|
1253
|
+
offsets: tuple[int, ...],
|
|
1254
|
+
) -> dict[int, tuple[bool, str, bool]]:
|
|
1255
|
+
_observe_text_work("reload_plan_source_scan", len(source))
|
|
1256
|
+
result: dict[int, tuple[bool, str, bool]] = {}
|
|
1257
|
+
line_start = 0
|
|
1258
|
+
previous = 0
|
|
1259
|
+
for offset in sorted(set(offsets)):
|
|
1260
|
+
newline = max(
|
|
1261
|
+
source.rfind("\n", previous, offset),
|
|
1262
|
+
source.rfind("\r", previous, offset),
|
|
1263
|
+
)
|
|
1264
|
+
if newline >= 0:
|
|
1265
|
+
line_start = newline + 1
|
|
1266
|
+
prefix = source[line_start:offset]
|
|
1267
|
+
indent_end = 0
|
|
1268
|
+
while indent_end < len(prefix) and prefix[indent_end] in " \t":
|
|
1269
|
+
indent_end += 1
|
|
1270
|
+
result[offset] = (
|
|
1271
|
+
bool(prefix.strip()),
|
|
1272
|
+
prefix[:indent_end],
|
|
1273
|
+
offset < len(source) and source[offset] in "\r\n",
|
|
1274
|
+
)
|
|
1275
|
+
previous = offset
|
|
1276
|
+
return result
|
|
1277
|
+
|
|
1278
|
+
|
|
1279
|
+
def _mint_worker_semantic_admission(
|
|
1280
|
+
lowered: LoweredWorkerModule,
|
|
1281
|
+
*,
|
|
1282
|
+
packer_identity: str | None = None,
|
|
1283
|
+
semantic_identity: _WorkerSemanticIdentity | None = None,
|
|
1284
|
+
) -> WorkerSemanticAdmission:
|
|
1285
|
+
"""Mint the one semantic capability shared by full and future delta lowering."""
|
|
1286
|
+
if not isinstance(lowered, LoweredWorkerModule):
|
|
1287
|
+
raise ValueError("worker semantic admission input is invalid")
|
|
1288
|
+
if packer_identity is not None and not _safe_packer_identity(packer_identity):
|
|
1289
|
+
raise ValueError("worker semantic admission input is invalid")
|
|
1290
|
+
identity = (
|
|
1291
|
+
_worker_semantic_admission_identity(
|
|
1292
|
+
lowered,
|
|
1293
|
+
packer_identity=packer_identity,
|
|
1294
|
+
)
|
|
1295
|
+
if semantic_identity is None
|
|
1296
|
+
else replace(semantic_identity, packer_identity=packer_identity)
|
|
1297
|
+
)
|
|
1298
|
+
proof = _worker_semantic_admission_proof(identity)
|
|
1299
|
+
admission = WorkerSemanticAdmission(
|
|
1300
|
+
ref(lowered),
|
|
1301
|
+
identity,
|
|
1302
|
+
proof,
|
|
1303
|
+
_authority=_SEMANTIC_ADMISSION_AUTHORITY,
|
|
1304
|
+
)
|
|
1305
|
+
with _SEMANTIC_ADMISSIONS_LOCK:
|
|
1306
|
+
admissions = _SEMANTIC_ADMISSIONS.setdefault(lowered, {})
|
|
1307
|
+
admissions[packer_identity] = admission
|
|
1308
|
+
return admission
|
|
1309
|
+
|
|
1310
|
+
|
|
1311
|
+
def worker_semantic_admission(
|
|
1312
|
+
lowered: LoweredWorkerModule,
|
|
1313
|
+
*,
|
|
1314
|
+
packer_identity: str,
|
|
1315
|
+
) -> WorkerSemanticAdmission:
|
|
1316
|
+
"""Return the process-private semantic proof minted during lowering."""
|
|
1317
|
+
if not isinstance(lowered, LoweredWorkerModule):
|
|
1318
|
+
raise ValueError("worker semantic admission is invalid")
|
|
1319
|
+
if not _safe_packer_identity(packer_identity):
|
|
1320
|
+
raise ValueError("worker semantic admission is invalid")
|
|
1321
|
+
with _SEMANTIC_ADMISSIONS_LOCK:
|
|
1322
|
+
admissions = _SEMANTIC_ADMISSIONS.get(lowered)
|
|
1323
|
+
basis = None if admissions is None else admissions.get(None)
|
|
1324
|
+
_validate_worker_semantic_admission(
|
|
1325
|
+
basis,
|
|
1326
|
+
source=lowered.mapped_source,
|
|
1327
|
+
exported_methods=lowered.analysis.exported_methods,
|
|
1328
|
+
expected_packer_identity=None,
|
|
1329
|
+
)
|
|
1330
|
+
with _SEMANTIC_ADMISSIONS_LOCK:
|
|
1331
|
+
admissions = _SEMANTIC_ADMISSIONS.get(lowered)
|
|
1332
|
+
admission = None if admissions is None else admissions.get(packer_identity)
|
|
1333
|
+
if admission is None:
|
|
1334
|
+
assert basis is not None
|
|
1335
|
+
_, basis_identity, _ = basis._contents()
|
|
1336
|
+
admission = _mint_worker_semantic_admission(
|
|
1337
|
+
lowered,
|
|
1338
|
+
packer_identity=packer_identity,
|
|
1339
|
+
semantic_identity=basis_identity,
|
|
1340
|
+
)
|
|
1341
|
+
assert admission is not None
|
|
1342
|
+
return admission
|
|
1343
|
+
|
|
1344
|
+
|
|
1345
|
+
def _validate_worker_semantic_admission(
|
|
1346
|
+
admission: WorkerSemanticAdmission | None,
|
|
1347
|
+
*,
|
|
1348
|
+
source: MappedSource,
|
|
1349
|
+
exported_methods: tuple[str, ...],
|
|
1350
|
+
expected_packer_identity: str | None,
|
|
1351
|
+
) -> LoweredWorkerModule:
|
|
1352
|
+
"""Validate an admission without exposing its semantic manifest."""
|
|
1353
|
+
try:
|
|
1354
|
+
if (
|
|
1355
|
+
not isinstance(admission, WorkerSemanticAdmission)
|
|
1356
|
+
or not isinstance(source, MappedSource)
|
|
1357
|
+
or (
|
|
1358
|
+
expected_packer_identity is not None
|
|
1359
|
+
and not _safe_packer_identity(expected_packer_identity)
|
|
1360
|
+
)
|
|
1361
|
+
or type(exported_methods) is not tuple
|
|
1362
|
+
or any(type(item) is not str for item in exported_methods)
|
|
1363
|
+
):
|
|
1364
|
+
raise ValueError
|
|
1365
|
+
lowered_reference, identity, proof = admission._contents()
|
|
1366
|
+
lowered = lowered_reference()
|
|
1367
|
+
if (
|
|
1368
|
+
lowered is None
|
|
1369
|
+
or lowered.mapped_source is not source
|
|
1370
|
+
or _canonical_exported_methods(exported_methods)
|
|
1371
|
+
!= _canonical_exported_methods(lowered.analysis.exported_methods)
|
|
1372
|
+
or identity.packer_identity != expected_packer_identity
|
|
1373
|
+
or not _worker_semantic_identity_matches(identity, lowered)
|
|
1374
|
+
or not compare_digest(
|
|
1375
|
+
proof,
|
|
1376
|
+
_worker_semantic_admission_proof(identity),
|
|
1377
|
+
)
|
|
1378
|
+
):
|
|
1379
|
+
raise ValueError
|
|
1380
|
+
with _SEMANTIC_ADMISSIONS_LOCK:
|
|
1381
|
+
admissions = _SEMANTIC_ADMISSIONS.get(lowered)
|
|
1382
|
+
if (
|
|
1383
|
+
admissions is None
|
|
1384
|
+
or admissions.get(expected_packer_identity) is not admission
|
|
1385
|
+
):
|
|
1386
|
+
raise ValueError
|
|
1387
|
+
return lowered
|
|
1388
|
+
except Exception:
|
|
1389
|
+
raise ValueError("worker semantic admission is invalid") from None
|
|
1390
|
+
|
|
1391
|
+
|
|
1392
|
+
def _worker_semantic_admission_proof(identity: _WorkerSemanticIdentity) -> bytes:
|
|
1393
|
+
mac = hmac_new(_SEMANTIC_ADMISSION_PROOF_KEY, digestmod="sha256")
|
|
1394
|
+
|
|
1395
|
+
def update(value: object) -> int:
|
|
1396
|
+
if value is None:
|
|
1397
|
+
mac.update(b"n")
|
|
1398
|
+
return 1
|
|
1399
|
+
if type(value) is int:
|
|
1400
|
+
mac.update(b"i")
|
|
1401
|
+
if value < 0:
|
|
1402
|
+
raise ValueError("worker semantic admission identity is invalid")
|
|
1403
|
+
payload = value.to_bytes(max(1, (value.bit_length() + 7) // 8), "big")
|
|
1404
|
+
length = len(payload).to_bytes(8, "big", signed=False)
|
|
1405
|
+
mac.update(length)
|
|
1406
|
+
mac.update(payload)
|
|
1407
|
+
return 1 + len(length) + len(payload)
|
|
1408
|
+
if type(value) is str:
|
|
1409
|
+
mac.update(b"s")
|
|
1410
|
+
payload = value.encode("utf-8")
|
|
1411
|
+
length = len(payload).to_bytes(8, "big", signed=False)
|
|
1412
|
+
mac.update(length)
|
|
1413
|
+
mac.update(payload)
|
|
1414
|
+
return 1 + len(length) + len(payload)
|
|
1415
|
+
if type(value) is tuple:
|
|
1416
|
+
mac.update(b"t")
|
|
1417
|
+
length = len(value).to_bytes(8, "big", signed=False)
|
|
1418
|
+
mac.update(length)
|
|
1419
|
+
return 1 + len(length) + sum(update(item) for item in value)
|
|
1420
|
+
raise ValueError("worker semantic admission identity is invalid")
|
|
1421
|
+
|
|
1422
|
+
width = 0
|
|
1423
|
+
for identity_field in fields(identity):
|
|
1424
|
+
width += update(identity_field.name)
|
|
1425
|
+
width += update(getattr(identity, identity_field.name))
|
|
1426
|
+
_observe_text_work("semantic_admission_proof_bytes", width)
|
|
1427
|
+
return mac.digest()
|
|
1428
|
+
|
|
1429
|
+
|
|
1430
|
+
def _worker_semantic_admission_identity(
|
|
1431
|
+
lowered: LoweredWorkerModule,
|
|
1432
|
+
*,
|
|
1433
|
+
packer_identity: str | None,
|
|
1434
|
+
) -> _WorkerSemanticIdentity:
|
|
1435
|
+
analysis = lowered.analysis
|
|
1436
|
+
raw = analysis.unit.mapped_source
|
|
1437
|
+
mapped = lowered.mapped_source
|
|
1438
|
+
summary = _trusted_mapped_semantic_summary(mapped)
|
|
1439
|
+
return _WorkerSemanticIdentity(
|
|
1440
|
+
analysis.unit.logical_name,
|
|
1441
|
+
id(raw),
|
|
1442
|
+
id(raw.text),
|
|
1443
|
+
raw.artifact.source_sha256,
|
|
1444
|
+
raw.artifact.source_sha256,
|
|
1445
|
+
id(mapped),
|
|
1446
|
+
id(mapped.text),
|
|
1447
|
+
mapped.artifact.source_sha256,
|
|
1448
|
+
mapped.artifact.source_sha256,
|
|
1449
|
+
id(mapped.source_map),
|
|
1450
|
+
summary.source_map_sha256,
|
|
1451
|
+
id(mapped.lineage),
|
|
1452
|
+
summary.lineage_sha256,
|
|
1453
|
+
_canonical_dependency_bindings_sha256(analysis.dependencies),
|
|
1454
|
+
_canonical_exported_methods(analysis.exported_methods),
|
|
1455
|
+
analysis.catalog_identity,
|
|
1456
|
+
analysis.parser_identity,
|
|
1457
|
+
lowered.transform_version,
|
|
1458
|
+
packer_identity,
|
|
1459
|
+
)
|
|
1460
|
+
|
|
1461
|
+
|
|
1462
|
+
def _worker_semantic_identity_matches(
|
|
1463
|
+
identity: _WorkerSemanticIdentity,
|
|
1464
|
+
lowered: LoweredWorkerModule,
|
|
1465
|
+
) -> bool:
|
|
1466
|
+
analysis = lowered.analysis
|
|
1467
|
+
raw = analysis.unit.mapped_source
|
|
1468
|
+
mapped = lowered.mapped_source
|
|
1469
|
+
summary = _validated_mapped_semantic_summary(mapped)
|
|
1470
|
+
dependency_bindings_sha256 = _canonical_dependency_bindings_sha256(
|
|
1471
|
+
analysis.dependencies
|
|
1472
|
+
)
|
|
1473
|
+
return (
|
|
1474
|
+
identity.logical_name == analysis.unit.logical_name
|
|
1475
|
+
and identity.raw_source_object_id == id(raw)
|
|
1476
|
+
and identity.raw_text_object_id == id(raw.text)
|
|
1477
|
+
and identity.raw_source_sha256 == raw.artifact.source_sha256
|
|
1478
|
+
and identity.raw_text_sha256 == raw.artifact.source_sha256
|
|
1479
|
+
and identity.lowered_source_object_id == id(mapped)
|
|
1480
|
+
and identity.lowered_text_object_id == id(mapped.text)
|
|
1481
|
+
and identity.lowered_source_sha256 == mapped.artifact.source_sha256
|
|
1482
|
+
and identity.lowered_text_sha256 == mapped.artifact.source_sha256
|
|
1483
|
+
and identity.source_map_object_id == id(mapped.source_map)
|
|
1484
|
+
and identity.lowered_source_map_sha256 == summary.source_map_sha256
|
|
1485
|
+
and identity.lineage_object_id == id(mapped.lineage)
|
|
1486
|
+
and identity.lineage_sha256 == summary.lineage_sha256
|
|
1487
|
+
and identity.dependency_bindings_sha256
|
|
1488
|
+
== dependency_bindings_sha256
|
|
1489
|
+
and lowered.dependency_bindings_sha256 == dependency_bindings_sha256
|
|
1490
|
+
and identity.canonical_exported_methods
|
|
1491
|
+
== _canonical_exported_methods(analysis.exported_methods)
|
|
1492
|
+
and identity.catalog_identity == analysis.catalog_identity
|
|
1493
|
+
and identity.parser_identity == analysis.parser_identity
|
|
1494
|
+
and identity.transform_version == lowered.transform_version
|
|
1495
|
+
)
|
|
1496
|
+
|
|
1497
|
+
|
|
1498
|
+
def _canonical_exported_methods(methods: tuple[str, ...]) -> tuple[str, ...]:
|
|
1499
|
+
return tuple(sorted(item.casefold() for item in methods))
|
|
1500
|
+
|
|
1501
|
+
|
|
1502
|
+
def _safe_packer_identity(value: object) -> bool:
|
|
1503
|
+
return (
|
|
1504
|
+
isinstance(value, str)
|
|
1505
|
+
and bool(value)
|
|
1506
|
+
and all(
|
|
1507
|
+
character not in value
|
|
1508
|
+
for character in ("\x00", "\r", "\n", "/", "\\", "|")
|
|
1509
|
+
)
|
|
1510
|
+
)
|
|
1511
|
+
|
|
1512
|
+
|
|
1513
|
+
def _canonical_dependency_bindings_sha256(
|
|
1514
|
+
bindings: tuple[ModuleDependencyBinding, ...],
|
|
1515
|
+
) -> str:
|
|
1516
|
+
canonical = tuple(_canonical_dependency_binding_sha256(item) for item in bindings)
|
|
1517
|
+
return sha256(
|
|
1518
|
+
json.dumps(
|
|
1519
|
+
canonical,
|
|
1520
|
+
ensure_ascii=False,
|
|
1521
|
+
separators=(",", ":"),
|
|
1522
|
+
).encode("utf-8")
|
|
1523
|
+
).hexdigest()
|
|
1524
|
+
|
|
1525
|
+
|
|
1526
|
+
def _canonical_dependency_binding_sha256(
|
|
1527
|
+
binding: ModuleDependencyBinding,
|
|
1528
|
+
) -> str:
|
|
1529
|
+
payload = json.dumps(
|
|
1530
|
+
{
|
|
1531
|
+
"target_module": binding.target_module,
|
|
1532
|
+
"export_variable": binding.export_variable,
|
|
1533
|
+
"uses": [
|
|
1534
|
+
{
|
|
1535
|
+
"method_name": use.method_name,
|
|
1536
|
+
"category": use.category,
|
|
1537
|
+
"span": [use.span.start, use.span.end],
|
|
1538
|
+
"method_declaration": [
|
|
1539
|
+
use.method_declaration.start,
|
|
1540
|
+
use.method_declaration.end,
|
|
1541
|
+
],
|
|
1542
|
+
}
|
|
1543
|
+
for use in binding.uses
|
|
1544
|
+
],
|
|
1545
|
+
},
|
|
1546
|
+
ensure_ascii=False,
|
|
1547
|
+
sort_keys=True,
|
|
1548
|
+
separators=(",", ":"),
|
|
1549
|
+
)
|
|
1550
|
+
return sha256(payload.encode("utf-8")).hexdigest()
|
|
1551
|
+
|
|
1552
|
+
|
|
1553
|
+
def _plan_alias_transform(analysis: AnalyzedWorkerModule) -> _AliasTransformPlan:
|
|
1554
|
+
source = analysis.unit.mapped_source
|
|
1555
|
+
line_ending = _preferred_line_ending(source.text)
|
|
1556
|
+
insertion_points = {
|
|
1557
|
+
item.method_name.casefold(): item
|
|
1558
|
+
for item in analysis.method_insertion_points
|
|
1559
|
+
}
|
|
1560
|
+
conditional_intervals = _conditional_directive_intervals(source.text)
|
|
1561
|
+
fields: list[tuple[str, SourceSpan, str]] = []
|
|
1562
|
+
declarations: dict[int, list[tuple[str, DependencyUse, str]]] = {}
|
|
1563
|
+
initializers: dict[int, list[tuple[str, DependencyUse, str]]] = {}
|
|
1564
|
+
|
|
1565
|
+
for binding in analysis.dependencies:
|
|
1566
|
+
first_use = min(binding.uses, key=lambda item: item.span.start)
|
|
1567
|
+
fields.append(
|
|
1568
|
+
(
|
|
1569
|
+
f"Перем {binding.export_variable} Экспорт;{line_ending}",
|
|
1570
|
+
first_use.span,
|
|
1571
|
+
DEPENDENCY_FIELD_REGION,
|
|
1572
|
+
)
|
|
1573
|
+
)
|
|
1574
|
+
method_names = {
|
|
1575
|
+
use.method_name.casefold()
|
|
1576
|
+
for use in binding.uses
|
|
1577
|
+
}
|
|
1578
|
+
for method_name in sorted(method_names):
|
|
1579
|
+
point = insertion_points[method_name]
|
|
1580
|
+
method_uses = tuple(
|
|
1581
|
+
use
|
|
1582
|
+
for use in binding.uses
|
|
1583
|
+
if use.method_name.casefold() == method_name
|
|
1584
|
+
)
|
|
1585
|
+
method_first_use = min(method_uses, key=lambda item: item.span.start)
|
|
1586
|
+
declaration_offset = _unconditional_method_offset(
|
|
1587
|
+
point.declarations_end,
|
|
1588
|
+
point.method_declaration,
|
|
1589
|
+
conditional_intervals,
|
|
1590
|
+
)
|
|
1591
|
+
initializer_offset = _unconditional_method_offset(
|
|
1592
|
+
point.first_statement_start,
|
|
1593
|
+
point.method_declaration,
|
|
1594
|
+
conditional_intervals,
|
|
1595
|
+
)
|
|
1596
|
+
declaration_text = f"Перем {binding.target_module};{line_ending}"
|
|
1597
|
+
declarations.setdefault(declaration_offset, []).append(
|
|
1598
|
+
(
|
|
1599
|
+
declaration_text,
|
|
1600
|
+
method_first_use,
|
|
1601
|
+
DEPENDENCY_ALIAS_DECLARATION_REGION,
|
|
1602
|
+
)
|
|
1603
|
+
)
|
|
1604
|
+
initializers.setdefault(initializer_offset, []).append(
|
|
1605
|
+
(
|
|
1606
|
+
f"{binding.target_module} = {binding.export_variable};{line_ending}",
|
|
1607
|
+
method_first_use,
|
|
1608
|
+
DEPENDENCY_ALIAS_INITIALIZER_REGION,
|
|
1609
|
+
)
|
|
1610
|
+
)
|
|
1611
|
+
|
|
1612
|
+
return _AliasTransformPlan(
|
|
1613
|
+
source,
|
|
1614
|
+
line_ending,
|
|
1615
|
+
tuple(fields),
|
|
1616
|
+
{offset: tuple(items) for offset, items in declarations.items()},
|
|
1617
|
+
{offset: tuple(items) for offset, items in initializers.items()},
|
|
1618
|
+
tuple(
|
|
1619
|
+
sorted(
|
|
1620
|
+
{
|
|
1621
|
+
boundary
|
|
1622
|
+
for method in analysis.methods
|
|
1623
|
+
for boundary in (
|
|
1624
|
+
method.method_declaration.start,
|
|
1625
|
+
method.method_declaration.end,
|
|
1626
|
+
)
|
|
1627
|
+
}
|
|
1628
|
+
)
|
|
1629
|
+
),
|
|
1630
|
+
)
|
|
1631
|
+
|
|
1632
|
+
|
|
1633
|
+
def _compose_worker_module_source(plan: _AliasTransformPlan) -> MappedSource:
|
|
1634
|
+
source = plan.source
|
|
1635
|
+
line_ending = plan.line_ending
|
|
1636
|
+
fields = plan.fields
|
|
1637
|
+
declarations = plan.declarations
|
|
1638
|
+
initializers = plan.initializers
|
|
1639
|
+
|
|
1640
|
+
builder = SourceTransformBuilder(source)
|
|
1641
|
+
offsets = sorted(
|
|
1642
|
+
{
|
|
1643
|
+
0,
|
|
1644
|
+
len(source.text),
|
|
1645
|
+
*declarations,
|
|
1646
|
+
*initializers,
|
|
1647
|
+
*plan.reuse_boundaries,
|
|
1648
|
+
}
|
|
1649
|
+
)
|
|
1650
|
+
cursor = 0
|
|
1651
|
+
for offset in offsets:
|
|
1652
|
+
if cursor < offset:
|
|
1653
|
+
builder.copy(SourceSpan(cursor, offset))
|
|
1654
|
+
if offset == 0:
|
|
1655
|
+
for text, anchor, region in fields:
|
|
1656
|
+
builder.synthetic(text, anchor, region)
|
|
1657
|
+
declaration_items = declarations.get(offset, ())
|
|
1658
|
+
for index, (text, use, region) in enumerate(declaration_items):
|
|
1659
|
+
if (
|
|
1660
|
+
index == 0
|
|
1661
|
+
and offset > 0
|
|
1662
|
+
and source.text[offset - 1] not in "\r\n"
|
|
1663
|
+
):
|
|
1664
|
+
text = f"{line_ending}{text}"
|
|
1665
|
+
builder.derived(
|
|
1666
|
+
text,
|
|
1667
|
+
use.span,
|
|
1668
|
+
region,
|
|
1669
|
+
anchor=use.method_declaration,
|
|
1670
|
+
)
|
|
1671
|
+
for text, use, region in initializers.get(offset, ()):
|
|
1672
|
+
builder.derived(
|
|
1673
|
+
text,
|
|
1674
|
+
use.span,
|
|
1675
|
+
region,
|
|
1676
|
+
anchor=use.method_declaration,
|
|
1677
|
+
)
|
|
1678
|
+
cursor = offset
|
|
1679
|
+
if cursor < len(source.text):
|
|
1680
|
+
builder.copy(SourceSpan(cursor, len(source.text)))
|
|
1681
|
+
|
|
1682
|
+
transformed = builder.build(SourceArtifactKind.WORKER_PROJECTION)
|
|
1683
|
+
from onec_runtime.worker_epf import prepare_worker_module_source
|
|
1684
|
+
|
|
1685
|
+
return prepare_worker_module_source(transformed)
|
|
1686
|
+
|
|
1687
|
+
|
|
1688
|
+
def _module_methods(root: Any) -> Iterable[Any]:
|
|
1689
|
+
elements = root.Elements
|
|
1690
|
+
while elements.Item is not None:
|
|
1691
|
+
if type(elements.Item).__name__ == "Method":
|
|
1692
|
+
yield elements.Item
|
|
1693
|
+
elements = elements.Rest
|
|
1694
|
+
|
|
1695
|
+
|
|
1696
|
+
def _preferred_line_ending(source: str) -> str:
|
|
1697
|
+
without_crlf = source.replace("\r\n", "")
|
|
1698
|
+
if "\r\n" in source and "\n" not in without_crlf and "\r" not in without_crlf:
|
|
1699
|
+
return "\r\n"
|
|
1700
|
+
if "\r" in source and "\n" not in source:
|
|
1701
|
+
return "\r"
|
|
1702
|
+
return "\n"
|
|
1703
|
+
|
|
1704
|
+
|
|
1705
|
+
def _conditional_directive_intervals(source: str) -> tuple[SourceSpan, ...]:
|
|
1706
|
+
stack: list[int] = []
|
|
1707
|
+
intervals: list[SourceSpan] = []
|
|
1708
|
+
line_start = 0
|
|
1709
|
+
for line in source.splitlines(keepends=True):
|
|
1710
|
+
match = re.match(
|
|
1711
|
+
r"[ \t]*#[ \t]*([A-Za-zА-Яа-яЁё]+)",
|
|
1712
|
+
line,
|
|
1713
|
+
)
|
|
1714
|
+
if match is not None:
|
|
1715
|
+
keyword = match.group(1).casefold()
|
|
1716
|
+
if keyword in {"если", "if"}:
|
|
1717
|
+
stack.append(line_start)
|
|
1718
|
+
elif keyword in {"конецесли", "endif"} and stack:
|
|
1719
|
+
intervals.append(SourceSpan(stack.pop(), line_start + len(line)))
|
|
1720
|
+
line_start += len(line)
|
|
1721
|
+
intervals.extend(SourceSpan(start, len(source)) for start in stack)
|
|
1722
|
+
return tuple(sorted(intervals))
|
|
1723
|
+
|
|
1724
|
+
|
|
1725
|
+
def _unconditional_method_offset(
|
|
1726
|
+
offset: int,
|
|
1727
|
+
method: SourceSpan,
|
|
1728
|
+
conditional_intervals: tuple[SourceSpan, ...],
|
|
1729
|
+
) -> int:
|
|
1730
|
+
containing = tuple(
|
|
1731
|
+
interval
|
|
1732
|
+
for interval in conditional_intervals
|
|
1733
|
+
if method.start < interval.start <= offset < interval.end <= method.end
|
|
1734
|
+
)
|
|
1735
|
+
return min((interval.start for interval in containing), default=offset)
|
|
1736
|
+
|
|
1737
|
+
|
|
1738
|
+
def _walk_ast(root: Any) -> Iterable[Any]:
|
|
1739
|
+
stack = [root]
|
|
1740
|
+
while stack:
|
|
1741
|
+
node = stack.pop()
|
|
1742
|
+
if not is_dataclass(node):
|
|
1743
|
+
continue
|
|
1744
|
+
yield node
|
|
1745
|
+
children: list[Any] = []
|
|
1746
|
+
for field in fields(node):
|
|
1747
|
+
if field.name == "span":
|
|
1748
|
+
continue
|
|
1749
|
+
value = getattr(node, field.name)
|
|
1750
|
+
if is_dataclass(value):
|
|
1751
|
+
children.append(value)
|
|
1752
|
+
elif isinstance(value, tuple):
|
|
1753
|
+
children.extend(item for item in value if is_dataclass(item))
|
|
1754
|
+
stack.extend(reversed(children))
|
|
1755
|
+
|
|
1756
|
+
|
|
1757
|
+
def _source_span(span: Any) -> SourceSpan:
|
|
1758
|
+
return SourceSpan(span.start, span.end)
|
|
1759
|
+
|
|
1760
|
+
|
|
1761
|
+
def _offset_source_span(span: SourceSpan, offset: int) -> SourceSpan:
|
|
1762
|
+
return SourceSpan(span.start + offset, span.end + offset)
|
|
1763
|
+
|
|
1764
|
+
|
|
1765
|
+
def _remap_isolated_error_message(
|
|
1766
|
+
message: str,
|
|
1767
|
+
local_start: int,
|
|
1768
|
+
absolute_start: int,
|
|
1769
|
+
) -> str:
|
|
1770
|
+
return re.sub(
|
|
1771
|
+
rf" at {local_start}(?=;|$)",
|
|
1772
|
+
f" at {absolute_start}",
|
|
1773
|
+
message,
|
|
1774
|
+
count=1,
|
|
1775
|
+
)
|
|
1776
|
+
|
|
1777
|
+
|
|
1778
|
+
def _declaration_end(declaration: Any, tokens: tuple[Any, ...]) -> int:
|
|
1779
|
+
delimiter = next(
|
|
1780
|
+
(
|
|
1781
|
+
token
|
|
1782
|
+
for token in tokens
|
|
1783
|
+
if token.start == declaration.span.end and token.type == ";"
|
|
1784
|
+
),
|
|
1785
|
+
None,
|
|
1786
|
+
)
|
|
1787
|
+
return delimiter.end if delimiter is not None else declaration.span.end
|
|
1788
|
+
|
|
1789
|
+
|
|
1790
|
+
def _executable_body(code: Any, for_token_starts: tuple[int, ...]) -> SourceSpan:
|
|
1791
|
+
if code.First is None:
|
|
1792
|
+
return SourceSpan(code.span.end, code.span.end)
|
|
1793
|
+
start = code.First.span.start
|
|
1794
|
+
if type(code.First).__name__ in {"ForEachStatement", "ForRangeStatement"}:
|
|
1795
|
+
# The generated loop-node spans omit their leading `Для` token.
|
|
1796
|
+
start = for_token_starts[bisect_left(for_token_starts, code.span.start)]
|
|
1797
|
+
return SourceSpan(start, code.span.end)
|
|
1798
|
+
|
|
1799
|
+
|
|
1800
|
+
def _dependency_category(node: Any) -> Literal["call", "access", "value"]:
|
|
1801
|
+
if node.Arguments is not None:
|
|
1802
|
+
raise _analysis_error(
|
|
1803
|
+
"common module object cannot be called directly",
|
|
1804
|
+
_source_span(node.span),
|
|
1805
|
+
AMBIGUOUS_BINDING,
|
|
1806
|
+
)
|
|
1807
|
+
if not node.Postfix:
|
|
1808
|
+
return "value"
|
|
1809
|
+
first = node.Postfix[0]
|
|
1810
|
+
if type(first).__name__ != "MemberAccess":
|
|
1811
|
+
raise _analysis_error(
|
|
1812
|
+
"common module dependency access is ambiguous",
|
|
1813
|
+
_source_span(node.span),
|
|
1814
|
+
AMBIGUOUS_BINDING,
|
|
1815
|
+
)
|
|
1816
|
+
return "call" if first.Arguments is not None else "access"
|
|
1817
|
+
|
|
1818
|
+
|
|
1819
|
+
def _analysis_error(
|
|
1820
|
+
message: str,
|
|
1821
|
+
span: SourceSpan,
|
|
1822
|
+
code: str,
|
|
1823
|
+
) -> ModuleUniverseAdmissionError:
|
|
1824
|
+
error = ModuleUniverseAdmissionError(message)
|
|
1825
|
+
error.span = span
|
|
1826
|
+
error.code = code
|
|
1827
|
+
return error
|
|
1828
|
+
|
|
1829
|
+
|
|
1830
|
+
def _dependency_binding(
|
|
1831
|
+
descriptor: CommonModuleDescriptor,
|
|
1832
|
+
uses: tuple[DependencyUse, ...],
|
|
1833
|
+
) -> ModuleDependencyBinding:
|
|
1834
|
+
export_variable = dependency_export_name(descriptor.canonical_name)
|
|
1835
|
+
payload = json.dumps(
|
|
1836
|
+
{
|
|
1837
|
+
"target_module": descriptor.canonical_name,
|
|
1838
|
+
"export_variable": export_variable,
|
|
1839
|
+
"uses": [
|
|
1840
|
+
{
|
|
1841
|
+
"method_name": use.method_name,
|
|
1842
|
+
"category": use.category,
|
|
1843
|
+
"span": [use.span.start, use.span.end],
|
|
1844
|
+
"method_declaration": [
|
|
1845
|
+
use.method_declaration.start,
|
|
1846
|
+
use.method_declaration.end,
|
|
1847
|
+
],
|
|
1848
|
+
}
|
|
1849
|
+
for use in uses
|
|
1850
|
+
],
|
|
1851
|
+
},
|
|
1852
|
+
ensure_ascii=False,
|
|
1853
|
+
sort_keys=True,
|
|
1854
|
+
separators=(",", ":"),
|
|
1855
|
+
)
|
|
1856
|
+
return ModuleDependencyBinding(
|
|
1857
|
+
descriptor.canonical_name,
|
|
1858
|
+
export_variable,
|
|
1859
|
+
uses,
|
|
1860
|
+
sha256(payload.encode("utf-8")).hexdigest(),
|
|
1861
|
+
)
|
|
1862
|
+
|
|
1863
|
+
|
|
1864
|
+
def _is_bsl_identifier(value: object) -> bool:
|
|
1865
|
+
return isinstance(value, str) and _BSL_IDENTIFIER_RE.fullmatch(value) is not None
|
|
1866
|
+
|
|
1867
|
+
|
|
1868
|
+
def _single_visible_unit(mapped_source: MappedSource) -> SourceUnitRef | None:
|
|
1869
|
+
units = {
|
|
1870
|
+
reference
|
|
1871
|
+
for segment in mapped_source.source_map.segments
|
|
1872
|
+
for reference in (segment.origin_ref, segment.anchor_ref)
|
|
1873
|
+
if isinstance(reference, SourceUnitRef)
|
|
1874
|
+
}
|
|
1875
|
+
if len(units) != 1:
|
|
1876
|
+
return None
|
|
1877
|
+
return next(iter(units))
|