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,289 @@
|
|
|
1
|
+
"""Resolve minimal Worker name projections against a session catalog snapshot."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Iterable
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
|
|
8
|
+
from onec_runtime.bsl.module_catalog import (
|
|
9
|
+
CommonModuleCatalogSnapshot,
|
|
10
|
+
CommonModuleDescriptor,
|
|
11
|
+
)
|
|
12
|
+
from onec_runtime.bsl.source_maps import SourceSpan
|
|
13
|
+
from onec_runtime.bsl.worker_projection_model import (
|
|
14
|
+
BareName,
|
|
15
|
+
BareNameKind,
|
|
16
|
+
ParsedMethodModel,
|
|
17
|
+
ParsedModuleModel,
|
|
18
|
+
)
|
|
19
|
+
from onec_runtime.errors import ModuleUniverseAdmissionError
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
MODULE_SCOPE_DEPENDENCY = "module_scope_dependency"
|
|
23
|
+
AMBIGUOUS_BINDING = "ambiguous_module_dependency"
|
|
24
|
+
|
|
25
|
+
_PLATFORM_GLOBALS = frozenset(
|
|
26
|
+
{
|
|
27
|
+
"статуссообщения",
|
|
28
|
+
"символы",
|
|
29
|
+
"кодировкатекста",
|
|
30
|
+
"справочники",
|
|
31
|
+
"документы",
|
|
32
|
+
"журналыдокументов",
|
|
33
|
+
"регистрысведений",
|
|
34
|
+
"регистрынакопления",
|
|
35
|
+
"регистрыбухгалтерии",
|
|
36
|
+
"регистрырасчета",
|
|
37
|
+
"планывидовхарактеристик",
|
|
38
|
+
"планысчетов",
|
|
39
|
+
"планывидоврасчета",
|
|
40
|
+
"планыобмена",
|
|
41
|
+
"бизнеспроцессы",
|
|
42
|
+
"задачи",
|
|
43
|
+
"критерииотбора",
|
|
44
|
+
"последовательности",
|
|
45
|
+
"константы",
|
|
46
|
+
"перечисления",
|
|
47
|
+
"внешниеобработки",
|
|
48
|
+
"внешниеотчеты",
|
|
49
|
+
"обработки",
|
|
50
|
+
"отчеты",
|
|
51
|
+
"метаданные",
|
|
52
|
+
"параметрысеанса",
|
|
53
|
+
"частидаты",
|
|
54
|
+
"обходрезультатазапроса",
|
|
55
|
+
"видсравнениякомпоновкиданных",
|
|
56
|
+
"типгруппыэлементовотборакомпоновкиданных",
|
|
57
|
+
"цветастиля",
|
|
58
|
+
}
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@dataclass(frozen=True, slots=True)
|
|
63
|
+
class ResolvedMethodPlan:
|
|
64
|
+
source: ParsedMethodModel
|
|
65
|
+
local_names: tuple[str, ...]
|
|
66
|
+
implicit_local_names: tuple[str, ...]
|
|
67
|
+
dependencies: tuple[str, ...]
|
|
68
|
+
forbidden_global_writes: tuple[str, ...]
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@dataclass(frozen=True, slots=True)
|
|
72
|
+
class ResolvedModulePlan:
|
|
73
|
+
source: ParsedModuleModel
|
|
74
|
+
catalog_generation: int
|
|
75
|
+
catalog_identity: tuple[str, str, int, str]
|
|
76
|
+
implicit_local_names: tuple[str, ...]
|
|
77
|
+
forbidden_global_writes: tuple[str, ...]
|
|
78
|
+
methods: tuple[ResolvedMethodPlan, ...]
|
|
79
|
+
dependencies: tuple[str, ...]
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@dataclass(frozen=True, slots=True)
|
|
83
|
+
class _ResolvedNames:
|
|
84
|
+
implicit_locals: tuple[str, ...]
|
|
85
|
+
forbidden_writes: tuple[str, ...]
|
|
86
|
+
dependencies: tuple[str, ...]
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def worker_model_candidate_names(
|
|
90
|
+
models: Iterable[ParsedModuleModel],
|
|
91
|
+
) -> tuple[str, ...]:
|
|
92
|
+
"""Return the canonical catalog lookup set for parsed module models."""
|
|
93
|
+
candidates: set[str] = set()
|
|
94
|
+
for model in models:
|
|
95
|
+
if not isinstance(model, ParsedModuleModel):
|
|
96
|
+
raise TypeError("models must contain ParsedModuleModel values")
|
|
97
|
+
candidates.update(item.normalized_name for item in model.module_bare_names)
|
|
98
|
+
candidates.update(
|
|
99
|
+
item.normalized_name
|
|
100
|
+
for method in model.methods
|
|
101
|
+
for item in method.bare_names
|
|
102
|
+
)
|
|
103
|
+
return tuple(sorted(candidates))
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def resolve_worker_dependencies(
|
|
107
|
+
model: ParsedModuleModel,
|
|
108
|
+
catalog: CommonModuleCatalogSnapshot,
|
|
109
|
+
*,
|
|
110
|
+
previous: ResolvedModulePlan | None = None,
|
|
111
|
+
) -> ResolvedModulePlan:
|
|
112
|
+
"""Resolve one immutable syntax model without revisiting source or tokens."""
|
|
113
|
+
|
|
114
|
+
if not isinstance(model, ParsedModuleModel):
|
|
115
|
+
raise TypeError("model must be ParsedModuleModel")
|
|
116
|
+
if not isinstance(catalog, CommonModuleCatalogSnapshot):
|
|
117
|
+
raise TypeError("catalog must be CommonModuleCatalogSnapshot")
|
|
118
|
+
if previous is not None:
|
|
119
|
+
if not isinstance(previous, ResolvedModulePlan):
|
|
120
|
+
raise TypeError("previous must be ResolvedModulePlan")
|
|
121
|
+
if (
|
|
122
|
+
previous.source.source_sha256 != model.source_sha256
|
|
123
|
+
or previous.source.parser_identity != model.parser_identity
|
|
124
|
+
):
|
|
125
|
+
raise ValueError("previous plan must describe the same source version")
|
|
126
|
+
current_identity = _catalog_identity(catalog)
|
|
127
|
+
if previous.catalog_identity[:2] != current_identity[:2]:
|
|
128
|
+
raise ValueError("catalog profile identity cannot change")
|
|
129
|
+
if previous.catalog_generation > catalog.revision:
|
|
130
|
+
raise ValueError("catalog generation cannot move backwards")
|
|
131
|
+
if (
|
|
132
|
+
previous.catalog_generation == catalog.revision
|
|
133
|
+
and previous.catalog_identity != current_identity
|
|
134
|
+
):
|
|
135
|
+
raise ValueError("catalog identity changed within one generation")
|
|
136
|
+
|
|
137
|
+
method_names: set[str] = set()
|
|
138
|
+
for method in model.methods:
|
|
139
|
+
if method.normalized_name in method_names:
|
|
140
|
+
error = ModuleUniverseAdmissionError(
|
|
141
|
+
"worker module contains an ambiguous method declaration"
|
|
142
|
+
)
|
|
143
|
+
error.code = AMBIGUOUS_BINDING
|
|
144
|
+
error.span = method.declaration_span
|
|
145
|
+
raise error
|
|
146
|
+
method_names.add(method.normalized_name)
|
|
147
|
+
|
|
148
|
+
catalog_by_name = {
|
|
149
|
+
descriptor.canonical_name.casefold(): descriptor
|
|
150
|
+
for descriptor in catalog.modules
|
|
151
|
+
}
|
|
152
|
+
catalog_order = tuple(catalog_by_name)
|
|
153
|
+
internal_methods = frozenset(method_names)
|
|
154
|
+
module_hard_locals = frozenset(model.module_variables)
|
|
155
|
+
previous_module_implicit = (
|
|
156
|
+
frozenset(previous.implicit_local_names) if previous is not None else frozenset()
|
|
157
|
+
)
|
|
158
|
+
module_names = _resolve_names(
|
|
159
|
+
model.module_bare_names,
|
|
160
|
+
hard_locals=module_hard_locals,
|
|
161
|
+
frozen_implicit=previous_module_implicit,
|
|
162
|
+
internal_methods=internal_methods,
|
|
163
|
+
catalog_by_name=catalog_by_name,
|
|
164
|
+
catalog_order=catalog_order,
|
|
165
|
+
)
|
|
166
|
+
if module_names.dependencies:
|
|
167
|
+
error = ModuleUniverseAdmissionError(
|
|
168
|
+
"common module dependency is unsupported in module scope"
|
|
169
|
+
)
|
|
170
|
+
error.code = MODULE_SCOPE_DEPENDENCY
|
|
171
|
+
error.span = SourceSpan(0, 0)
|
|
172
|
+
raise error
|
|
173
|
+
|
|
174
|
+
previous_methods = (
|
|
175
|
+
{method.source.normalized_name: method for method in previous.methods}
|
|
176
|
+
if previous is not None
|
|
177
|
+
else {}
|
|
178
|
+
)
|
|
179
|
+
methods: list[ResolvedMethodPlan] = []
|
|
180
|
+
all_dependencies: set[str] = set()
|
|
181
|
+
for method in model.methods:
|
|
182
|
+
prior = previous_methods.get(method.normalized_name)
|
|
183
|
+
hard_locals = (
|
|
184
|
+
module_hard_locals
|
|
185
|
+
| frozenset(module_names.implicit_locals)
|
|
186
|
+
| frozenset(method.declared_names)
|
|
187
|
+
)
|
|
188
|
+
resolved = _resolve_names(
|
|
189
|
+
method.bare_names,
|
|
190
|
+
hard_locals=hard_locals,
|
|
191
|
+
frozen_implicit=(
|
|
192
|
+
frozenset(prior.implicit_local_names)
|
|
193
|
+
if prior is not None
|
|
194
|
+
else frozenset()
|
|
195
|
+
),
|
|
196
|
+
internal_methods=internal_methods,
|
|
197
|
+
catalog_by_name=catalog_by_name,
|
|
198
|
+
catalog_order=catalog_order,
|
|
199
|
+
)
|
|
200
|
+
all_dependencies.update(name.casefold() for name in resolved.dependencies)
|
|
201
|
+
methods.append(
|
|
202
|
+
ResolvedMethodPlan(
|
|
203
|
+
source=method,
|
|
204
|
+
local_names=tuple(
|
|
205
|
+
sorted(hard_locals | frozenset(resolved.implicit_locals))
|
|
206
|
+
),
|
|
207
|
+
implicit_local_names=resolved.implicit_locals,
|
|
208
|
+
dependencies=resolved.dependencies,
|
|
209
|
+
forbidden_global_writes=resolved.forbidden_writes,
|
|
210
|
+
)
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
return ResolvedModulePlan(
|
|
214
|
+
source=model,
|
|
215
|
+
catalog_generation=catalog.revision,
|
|
216
|
+
catalog_identity=_catalog_identity(catalog),
|
|
217
|
+
implicit_local_names=module_names.implicit_locals,
|
|
218
|
+
forbidden_global_writes=module_names.forbidden_writes,
|
|
219
|
+
methods=tuple(methods),
|
|
220
|
+
dependencies=tuple(
|
|
221
|
+
catalog_by_name[name].canonical_name
|
|
222
|
+
for name in catalog_order
|
|
223
|
+
if name in all_dependencies
|
|
224
|
+
),
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def _catalog_identity(
|
|
229
|
+
catalog: CommonModuleCatalogSnapshot,
|
|
230
|
+
) -> tuple[str, str, int, str]:
|
|
231
|
+
return (
|
|
232
|
+
catalog.profile,
|
|
233
|
+
catalog.preprocessor_profile,
|
|
234
|
+
catalog.revision,
|
|
235
|
+
catalog.sha256,
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def _resolve_names(
|
|
240
|
+
bare_names: tuple[BareName, ...],
|
|
241
|
+
*,
|
|
242
|
+
hard_locals: frozenset[str],
|
|
243
|
+
frozen_implicit: frozenset[str],
|
|
244
|
+
internal_methods: frozenset[str],
|
|
245
|
+
catalog_by_name: dict[str, CommonModuleDescriptor],
|
|
246
|
+
catalog_order: tuple[str, ...],
|
|
247
|
+
) -> _ResolvedNames:
|
|
248
|
+
by_name = {item.normalized_name: item for item in bare_names}
|
|
249
|
+
implicit: set[str] = set(frozen_implicit)
|
|
250
|
+
forbidden: set[str] = set()
|
|
251
|
+
dependencies: set[str] = set()
|
|
252
|
+
shadowed = hard_locals | frozen_implicit | internal_methods | _PLATFORM_GLOBALS
|
|
253
|
+
|
|
254
|
+
for normalized, item in by_name.items():
|
|
255
|
+
if normalized in shadowed:
|
|
256
|
+
continue
|
|
257
|
+
descriptor = catalog_by_name.get(normalized)
|
|
258
|
+
if item.kinds & BareNameKind.BARE_WRITE:
|
|
259
|
+
if descriptor is None:
|
|
260
|
+
implicit.add(normalized)
|
|
261
|
+
else:
|
|
262
|
+
forbidden.add(normalized)
|
|
263
|
+
continue
|
|
264
|
+
if item.kinds & BareNameKind.READ and descriptor is not None:
|
|
265
|
+
dependencies.add(normalized)
|
|
266
|
+
|
|
267
|
+
return _ResolvedNames(
|
|
268
|
+
implicit_locals=tuple(sorted(implicit)),
|
|
269
|
+
forbidden_writes=tuple(
|
|
270
|
+
catalog_by_name[name].canonical_name
|
|
271
|
+
for name in catalog_order
|
|
272
|
+
if name in forbidden
|
|
273
|
+
),
|
|
274
|
+
dependencies=tuple(
|
|
275
|
+
catalog_by_name[name].canonical_name
|
|
276
|
+
for name in catalog_order
|
|
277
|
+
if name in dependencies
|
|
278
|
+
),
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
__all__ = [
|
|
283
|
+
"AMBIGUOUS_BINDING",
|
|
284
|
+
"MODULE_SCOPE_DEPENDENCY",
|
|
285
|
+
"ResolvedMethodPlan",
|
|
286
|
+
"ResolvedModulePlan",
|
|
287
|
+
"resolve_worker_dependencies",
|
|
288
|
+
"worker_model_candidate_names",
|
|
289
|
+
]
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
"""Select the server-effective token view without rewriting Worker source."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
from onec_runtime.bsl.lexer import Token
|
|
8
|
+
from onec_runtime.bsl.parser_target import BslParseError
|
|
9
|
+
from onec_runtime.bsl.source_maps import SourceSpan
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
_TRUE_SYMBOLS = frozenset({"сервер", "server"})
|
|
13
|
+
_FALSE_SYMBOLS = frozenset(
|
|
14
|
+
{
|
|
15
|
+
"клиент",
|
|
16
|
+
"client",
|
|
17
|
+
"вебклиент",
|
|
18
|
+
"webclient",
|
|
19
|
+
"тонкийклиент",
|
|
20
|
+
"thinclient",
|
|
21
|
+
"толстыйклиентобычноеприложение",
|
|
22
|
+
"thickclientordinaryapplication",
|
|
23
|
+
"толстыйклиентуправляемоеприложение",
|
|
24
|
+
"thickclientmanagedapplication",
|
|
25
|
+
"внешнеесоединение",
|
|
26
|
+
"externalconnection",
|
|
27
|
+
"мобильныйклиент",
|
|
28
|
+
"mobileclient",
|
|
29
|
+
"мобильныйавтономныйсервер",
|
|
30
|
+
"mobilestandaloneserver",
|
|
31
|
+
"мобильноеприложениеклиент",
|
|
32
|
+
"mobileapplicationclient",
|
|
33
|
+
"мобильноеприложениесервер",
|
|
34
|
+
"mobileapplicationserver",
|
|
35
|
+
"сервермобильногоприложения",
|
|
36
|
+
"mobileapplicationserver",
|
|
37
|
+
}
|
|
38
|
+
)
|
|
39
|
+
_SERVICE_DIRECTIVES = frozenset(
|
|
40
|
+
{"ОБЛАСТЬ", "КОНЕЦОБЛАСТИ", "ИСПОЛЬЗОВАТЬ", "NATIVE", "STACK"}
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class WorkerPreprocessorError(BslParseError):
|
|
45
|
+
pass
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclass(slots=True)
|
|
49
|
+
class _ConditionalFrame:
|
|
50
|
+
opening: Token
|
|
51
|
+
parent_active: bool
|
|
52
|
+
any_taken: bool
|
|
53
|
+
current_active: bool
|
|
54
|
+
else_seen: bool = False
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class _ExpressionParser:
|
|
58
|
+
def __init__(self, tokens: tuple[Token, ...]) -> None:
|
|
59
|
+
self._tokens = tokens
|
|
60
|
+
self._index = 0
|
|
61
|
+
|
|
62
|
+
def parse(self) -> bool:
|
|
63
|
+
if not self._tokens:
|
|
64
|
+
self._fail("empty preprocessor expression")
|
|
65
|
+
value = self._parse_or()
|
|
66
|
+
if self._index != len(self._tokens):
|
|
67
|
+
self._fail("unexpected token in preprocessor expression")
|
|
68
|
+
return value
|
|
69
|
+
|
|
70
|
+
def _parse_or(self) -> bool:
|
|
71
|
+
value = self._parse_and()
|
|
72
|
+
while self._accept("ИЛИ"):
|
|
73
|
+
operand = self._parse_and()
|
|
74
|
+
value = value or operand
|
|
75
|
+
return value
|
|
76
|
+
|
|
77
|
+
def _parse_and(self) -> bool:
|
|
78
|
+
value = self._parse_unary()
|
|
79
|
+
while self._accept("И"):
|
|
80
|
+
operand = self._parse_unary()
|
|
81
|
+
value = value and operand
|
|
82
|
+
return value
|
|
83
|
+
|
|
84
|
+
def _parse_unary(self) -> bool:
|
|
85
|
+
if self._accept("НЕ"):
|
|
86
|
+
return not self._parse_unary()
|
|
87
|
+
if self._accept("("):
|
|
88
|
+
value = self._parse_or()
|
|
89
|
+
if not self._accept(")"):
|
|
90
|
+
self._fail("missing closing parenthesis")
|
|
91
|
+
return value
|
|
92
|
+
if self._index >= len(self._tokens) or self._tokens[self._index].type != "ID":
|
|
93
|
+
self._fail("expected a preprocessor symbol")
|
|
94
|
+
token = self._tokens[self._index]
|
|
95
|
+
self._index += 1
|
|
96
|
+
normalized = token.text.casefold()
|
|
97
|
+
if normalized in _TRUE_SYMBOLS:
|
|
98
|
+
return True
|
|
99
|
+
if normalized in _FALSE_SYMBOLS:
|
|
100
|
+
return False
|
|
101
|
+
raise WorkerPreprocessorError(
|
|
102
|
+
f"Unknown server preprocessor symbol {token.text!r} at {token.start}",
|
|
103
|
+
span=SourceSpan(token.start, token.end),
|
|
104
|
+
code="unknown_symbol",
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
def _accept(self, token_type: str) -> bool:
|
|
108
|
+
if (
|
|
109
|
+
self._index < len(self._tokens)
|
|
110
|
+
and self._tokens[self._index].type == token_type
|
|
111
|
+
):
|
|
112
|
+
self._index += 1
|
|
113
|
+
return True
|
|
114
|
+
return False
|
|
115
|
+
|
|
116
|
+
def _fail(self, message: str) -> None:
|
|
117
|
+
token = self._tokens[min(self._index, len(self._tokens) - 1)]
|
|
118
|
+
raise WorkerPreprocessorError(
|
|
119
|
+
f"{message} at {token.start}",
|
|
120
|
+
span=SourceSpan(token.start, token.end),
|
|
121
|
+
code="invalid_expression",
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _directive_end(source: str, token: Token) -> int:
|
|
126
|
+
newline = source.find("\n", token.end)
|
|
127
|
+
return len(source) if newline < 0 else newline + 1
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _error(token: Token, message: str, code: str) -> WorkerPreprocessorError:
|
|
131
|
+
return WorkerPreprocessorError(
|
|
132
|
+
f"{message} at {token.start}",
|
|
133
|
+
span=SourceSpan(token.start, token.end),
|
|
134
|
+
code=code,
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _validate_service_directive(directive: tuple[Token, ...]) -> None:
|
|
139
|
+
kind = directive[1].type
|
|
140
|
+
valid = (
|
|
141
|
+
kind == "ОБЛАСТЬ"
|
|
142
|
+
and len(directive) == 3
|
|
143
|
+
and directive[2].type == "ID"
|
|
144
|
+
) or (
|
|
145
|
+
kind == "ИСПОЛЬЗОВАТЬ"
|
|
146
|
+
and len(directive) == 3
|
|
147
|
+
and directive[2].type in {"ID", "STRING"}
|
|
148
|
+
) or (
|
|
149
|
+
kind in {"КОНЕЦОБЛАСТИ", "NATIVE", "STACK"}
|
|
150
|
+
and len(directive) == 2
|
|
151
|
+
)
|
|
152
|
+
if not valid:
|
|
153
|
+
raise _error(
|
|
154
|
+
directive[-1],
|
|
155
|
+
f"invalid #{directive[1].text} directive",
|
|
156
|
+
"invalid_directive",
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def select_server_effective_tokens(
|
|
161
|
+
source: str,
|
|
162
|
+
tokens: tuple[Token, ...],
|
|
163
|
+
) -> tuple[Token, ...]:
|
|
164
|
+
"""Return original tokens belonging to the server-effective source branch."""
|
|
165
|
+
selected: list[Token] = []
|
|
166
|
+
stack: list[_ConditionalFrame] = []
|
|
167
|
+
active = True
|
|
168
|
+
index = 0
|
|
169
|
+
while index < len(tokens):
|
|
170
|
+
token = tokens[index]
|
|
171
|
+
if token.type != "#":
|
|
172
|
+
if active:
|
|
173
|
+
selected.append(token)
|
|
174
|
+
index += 1
|
|
175
|
+
continue
|
|
176
|
+
|
|
177
|
+
line_start = max(
|
|
178
|
+
source.rfind("\n", 0, token.start),
|
|
179
|
+
source.rfind("\r", 0, token.start),
|
|
180
|
+
) + 1
|
|
181
|
+
if source[line_start : token.start].strip():
|
|
182
|
+
raise _error(token, "preprocessor directive must start its line", "invalid_directive")
|
|
183
|
+
end = _directive_end(source, token)
|
|
184
|
+
next_index = index + 1
|
|
185
|
+
while next_index < len(tokens) and tokens[next_index].start < end:
|
|
186
|
+
next_index += 1
|
|
187
|
+
directive = tokens[index:next_index]
|
|
188
|
+
index = next_index
|
|
189
|
+
if len(directive) < 2:
|
|
190
|
+
raise _error(token, "empty preprocessor directive", "invalid_directive")
|
|
191
|
+
kind = directive[1].type
|
|
192
|
+
|
|
193
|
+
if kind in _SERVICE_DIRECTIVES:
|
|
194
|
+
_validate_service_directive(directive)
|
|
195
|
+
continue
|
|
196
|
+
if kind == "ЕСЛИ":
|
|
197
|
+
if len(directive) < 4 or directive[-1].type != "ТОГДА":
|
|
198
|
+
raise _error(token, "invalid #If directive", "invalid_directive")
|
|
199
|
+
condition = _ExpressionParser(directive[2:-1]).parse()
|
|
200
|
+
frame = _ConditionalFrame(token, active, condition, active and condition)
|
|
201
|
+
stack.append(frame)
|
|
202
|
+
active = frame.current_active
|
|
203
|
+
continue
|
|
204
|
+
if kind == "ИНАЧЕЕСЛИ":
|
|
205
|
+
if not stack:
|
|
206
|
+
raise _error(token, "#ElsIf without #If", "unexpected_directive")
|
|
207
|
+
frame = stack[-1]
|
|
208
|
+
if frame.else_seen:
|
|
209
|
+
raise _error(token, "#ElsIf after #Else", "unexpected_directive")
|
|
210
|
+
if len(directive) < 4 or directive[-1].type != "ТОГДА":
|
|
211
|
+
raise _error(token, "invalid #ElsIf directive", "invalid_directive")
|
|
212
|
+
condition = _ExpressionParser(directive[2:-1]).parse()
|
|
213
|
+
frame.current_active = frame.parent_active and not frame.any_taken and condition
|
|
214
|
+
frame.any_taken = frame.any_taken or condition
|
|
215
|
+
active = frame.current_active
|
|
216
|
+
continue
|
|
217
|
+
if kind == "ИНАЧЕ":
|
|
218
|
+
if not stack:
|
|
219
|
+
raise _error(token, "#Else without #If", "unexpected_directive")
|
|
220
|
+
frame = stack[-1]
|
|
221
|
+
if frame.else_seen or len(directive) != 2:
|
|
222
|
+
raise _error(token, "invalid or repeated #Else", "unexpected_directive")
|
|
223
|
+
frame.else_seen = True
|
|
224
|
+
frame.current_active = frame.parent_active and not frame.any_taken
|
|
225
|
+
frame.any_taken = True
|
|
226
|
+
active = frame.current_active
|
|
227
|
+
continue
|
|
228
|
+
if kind == "КОНЕЦЕСЛИ":
|
|
229
|
+
if not stack:
|
|
230
|
+
raise _error(token, "#EndIf without #If", "unexpected_directive")
|
|
231
|
+
if len(directive) != 2:
|
|
232
|
+
raise _error(token, "invalid #EndIf directive", "invalid_directive")
|
|
233
|
+
frame = stack.pop()
|
|
234
|
+
active = frame.parent_active
|
|
235
|
+
continue
|
|
236
|
+
raise _error(directive[1], "unsupported preprocessor directive", "invalid_directive")
|
|
237
|
+
|
|
238
|
+
if stack:
|
|
239
|
+
raise _error(
|
|
240
|
+
stack[-1].opening,
|
|
241
|
+
"unterminated #If directive",
|
|
242
|
+
"unbalanced_directive",
|
|
243
|
+
)
|
|
244
|
+
return tuple(selected)
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
__all__ = ["WorkerPreprocessorError", "select_server_effective_tokens"]
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"""Catalog-independent public model produced by the Worker projection parser."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from enum import IntFlag
|
|
7
|
+
import re
|
|
8
|
+
|
|
9
|
+
from onec_runtime.bsl.source_maps import SourceSpan
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class BareNameKind(IntFlag):
|
|
13
|
+
READ = 1
|
|
14
|
+
BARE_WRITE = 2
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _require_name(value: str, field: str) -> None:
|
|
18
|
+
if type(value) is not str or not value:
|
|
19
|
+
raise ValueError(f"{field} must be a non-empty string")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _require_normalized(value: str, field: str) -> None:
|
|
23
|
+
_require_name(value, field)
|
|
24
|
+
if value != value.casefold():
|
|
25
|
+
raise ValueError(f"{field} must be casefold-normalized")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _require_sorted_names(values: tuple[str, ...], field: str) -> None:
|
|
29
|
+
if type(values) is not tuple:
|
|
30
|
+
raise TypeError(f"{field} must be a tuple")
|
|
31
|
+
for value in values:
|
|
32
|
+
_require_normalized(value, field)
|
|
33
|
+
if values != tuple(sorted(set(values))):
|
|
34
|
+
raise ValueError(f"{field} must contain unique sorted names")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass(frozen=True, slots=True)
|
|
38
|
+
class BareName:
|
|
39
|
+
name: str
|
|
40
|
+
normalized_name: str
|
|
41
|
+
kinds: BareNameKind
|
|
42
|
+
|
|
43
|
+
def __post_init__(self) -> None:
|
|
44
|
+
_require_name(self.name, "name")
|
|
45
|
+
_require_normalized(self.normalized_name, "normalized_name")
|
|
46
|
+
if self.normalized_name != self.name.casefold():
|
|
47
|
+
raise ValueError("normalized_name must match name")
|
|
48
|
+
if not isinstance(self.kinds, BareNameKind) or not self.kinds:
|
|
49
|
+
raise ValueError("kinds must contain at least one BareNameKind")
|
|
50
|
+
known_kinds = BareNameKind.READ | BareNameKind.BARE_WRITE
|
|
51
|
+
if int(self.kinds) & ~int(known_kinds):
|
|
52
|
+
raise ValueError("kinds contains unknown BareNameKind bits")
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@dataclass(frozen=True, slots=True)
|
|
56
|
+
class ParsedMethodModel:
|
|
57
|
+
name: str
|
|
58
|
+
normalized_name: str
|
|
59
|
+
exported: bool
|
|
60
|
+
declaration_span: SourceSpan
|
|
61
|
+
alias_declaration_offset: int
|
|
62
|
+
alias_initializer_offset: int
|
|
63
|
+
declared_names: tuple[str, ...]
|
|
64
|
+
bare_names: tuple[BareName, ...]
|
|
65
|
+
|
|
66
|
+
def __post_init__(self) -> None:
|
|
67
|
+
_require_name(self.name, "name")
|
|
68
|
+
_require_normalized(self.normalized_name, "normalized_name")
|
|
69
|
+
if self.normalized_name != self.name.casefold():
|
|
70
|
+
raise ValueError("normalized_name must match name")
|
|
71
|
+
if type(self.exported) is not bool:
|
|
72
|
+
raise TypeError("exported must be bool")
|
|
73
|
+
if not isinstance(self.declaration_span, SourceSpan):
|
|
74
|
+
raise TypeError("declaration_span must be SourceSpan")
|
|
75
|
+
for field, offset in (
|
|
76
|
+
("alias_declaration_offset", self.alias_declaration_offset),
|
|
77
|
+
("alias_initializer_offset", self.alias_initializer_offset),
|
|
78
|
+
):
|
|
79
|
+
if type(offset) is not int or offset < 0:
|
|
80
|
+
raise ValueError(f"{field} must be a non-negative integer")
|
|
81
|
+
if self.alias_initializer_offset < self.alias_declaration_offset:
|
|
82
|
+
raise ValueError("initializer cannot precede declarations")
|
|
83
|
+
_require_sorted_names(self.declared_names, "declared_names")
|
|
84
|
+
if type(self.bare_names) is not tuple or not all(
|
|
85
|
+
isinstance(item, BareName) for item in self.bare_names
|
|
86
|
+
):
|
|
87
|
+
raise TypeError("bare_names must be a tuple of BareName")
|
|
88
|
+
normalized = tuple(item.normalized_name for item in self.bare_names)
|
|
89
|
+
if len(normalized) != len(set(normalized)):
|
|
90
|
+
raise ValueError("bare_names must be unique by normalized_name")
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@dataclass(frozen=True, slots=True)
|
|
94
|
+
class ParsedModuleModel:
|
|
95
|
+
source_sha256: str
|
|
96
|
+
module_variables: tuple[str, ...]
|
|
97
|
+
module_bare_names: tuple[BareName, ...]
|
|
98
|
+
methods: tuple[ParsedMethodModel, ...]
|
|
99
|
+
parser_identity: tuple[str, str] = field(compare=False, repr=False)
|
|
100
|
+
|
|
101
|
+
def __post_init__(self) -> None:
|
|
102
|
+
if (
|
|
103
|
+
type(self.source_sha256) is not str
|
|
104
|
+
or len(self.source_sha256) != 64
|
|
105
|
+
or any(character not in "0123456789abcdef" for character in self.source_sha256)
|
|
106
|
+
):
|
|
107
|
+
raise ValueError("source_sha256 must be a lowercase hexadecimal SHA-256")
|
|
108
|
+
_require_sorted_names(self.module_variables, "module_variables")
|
|
109
|
+
if type(self.module_bare_names) is not tuple or not all(
|
|
110
|
+
isinstance(item, BareName) for item in self.module_bare_names
|
|
111
|
+
):
|
|
112
|
+
raise TypeError("module_bare_names must be a tuple of BareName")
|
|
113
|
+
normalized = tuple(item.normalized_name for item in self.module_bare_names)
|
|
114
|
+
if len(normalized) != len(set(normalized)):
|
|
115
|
+
raise ValueError("module_bare_names must be unique by normalized_name")
|
|
116
|
+
if type(self.methods) is not tuple or not all(
|
|
117
|
+
isinstance(item, ParsedMethodModel) for item in self.methods
|
|
118
|
+
):
|
|
119
|
+
raise TypeError("methods must be a tuple of ParsedMethodModel")
|
|
120
|
+
if (
|
|
121
|
+
type(self.parser_identity) is not tuple
|
|
122
|
+
or len(self.parser_identity) != 2
|
|
123
|
+
or any(
|
|
124
|
+
type(value) is not str
|
|
125
|
+
or re.fullmatch(r"[0-9a-f]{64}", value) is None
|
|
126
|
+
for value in self.parser_identity
|
|
127
|
+
)
|
|
128
|
+
):
|
|
129
|
+
raise ValueError("parser_identity must contain two SHA-256 values")
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
__all__ = [
|
|
133
|
+
"BareName",
|
|
134
|
+
"BareNameKind",
|
|
135
|
+
"ParsedMethodModel",
|
|
136
|
+
"ParsedModuleModel",
|
|
137
|
+
]
|