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.
Files changed (87) hide show
  1. onec_interactive_runtime_core-0.1.19.dist-info/METADATA +12 -0
  2. onec_interactive_runtime_core-0.1.19.dist-info/RECORD +87 -0
  3. onec_interactive_runtime_core-0.1.19.dist-info/WHEEL +4 -0
  4. onec_interactive_runtime_core-0.1.19.dist-info/licenses/COPYRIGHT +9 -0
  5. onec_interactive_runtime_core-0.1.19.dist-info/licenses/LICENSE +232 -0
  6. onec_interactive_runtime_core-0.1.19.dist-info/licenses/THIRD_PARTY_NOTICES.txt +32 -0
  7. onec_runtime/AGENTS.md +7 -0
  8. onec_runtime/__init__.py +3 -0
  9. onec_runtime/artifacts.py +59 -0
  10. onec_runtime/bootstrap.py +310 -0
  11. onec_runtime/breakpoint_workspace.py +249 -0
  12. onec_runtime/bsl/__init__.py +139 -0
  13. onec_runtime/bsl/diagnostics.py +1046 -0
  14. onec_runtime/bsl/full_ast_worker_projection.py +522 -0
  15. onec_runtime/bsl/generated_semantic_parser.py +6322 -0
  16. onec_runtime/bsl/lexer.py +164 -0
  17. onec_runtime/bsl/module_catalog.py +420 -0
  18. onec_runtime/bsl/module_delta.py +1079 -0
  19. onec_runtime/bsl/module_universe.py +1877 -0
  20. onec_runtime/bsl/notebook_cells.py +232 -0
  21. onec_runtime/bsl/notebook_method_globals.py +74 -0
  22. onec_runtime/bsl/notebook_methods.py +175 -0
  23. onec_runtime/bsl/parser_artifact_identity.py +119 -0
  24. onec_runtime/bsl/parser_target.py +388 -0
  25. onec_runtime/bsl/preprocessor.py +16 -0
  26. onec_runtime/bsl/semantic_lowering.py +1539 -0
  27. onec_runtime/bsl/source_maps.py +2411 -0
  28. onec_runtime/bsl/worker_dependency_resolver.py +289 -0
  29. onec_runtime/bsl/worker_preprocessor.py +247 -0
  30. onec_runtime/bsl/worker_projection_model.py +137 -0
  31. onec_runtime/bsl/worker_reload_source_map.py +1697 -0
  32. onec_runtime/capture.py +113 -0
  33. onec_runtime/capture_source.py +453 -0
  34. onec_runtime/compact_table.py +376 -0
  35. onec_runtime/compact_table_backend.py +500 -0
  36. onec_runtime/config.py +198 -0
  37. onec_runtime/configurator_agent.py +520 -0
  38. onec_runtime/controller_worker.py +400 -0
  39. onec_runtime/credentials.py +18 -0
  40. onec_runtime/epf_container.py +196 -0
  41. onec_runtime/errors.py +205 -0
  42. onec_runtime/experiment.py +130 -0
  43. onec_runtime/extension_bundle.py +944 -0
  44. onec_runtime/extension_lifecycle.py +402 -0
  45. onec_runtime/extension_state.py +222 -0
  46. onec_runtime/fault_injection.py +30 -0
  47. onec_runtime/kernel.py +179 -0
  48. onec_runtime/lease.py +123 -0
  49. onec_runtime/observation.py +287 -0
  50. onec_runtime/performance_profile.py +245 -0
  51. onec_runtime/privacy.py +263 -0
  52. onec_runtime/processes.py +265 -0
  53. onec_runtime/prototype_runtime.py +2644 -0
  54. onec_runtime/rdbg/__init__.py +1 -0
  55. onec_runtime/rdbg/models.py +130 -0
  56. onec_runtime/rdbg/reconnect.py +59 -0
  57. onec_runtime/rdbg/session.py +913 -0
  58. onec_runtime/rdbg/transport.py +138 -0
  59. onec_runtime/rdbg/xml_codec.py +980 -0
  60. onec_runtime/recovery.py +106 -0
  61. onec_runtime/recovery_journal.py +79 -0
  62. onec_runtime/resources/extension/OnecInteractiveRuntime.cfe +0 -0
  63. onec_runtime/resources/extension/extension-manifest.json +108 -0
  64. onec_runtime/runtime_api.py +5272 -0
  65. onec_runtime/runtime_contracts.py +196 -0
  66. onec_runtime/server_worker.py +2034 -0
  67. onec_runtime/session.py +2123 -0
  68. onec_runtime/startup_diagnostics.py +45 -0
  69. onec_runtime/stop_routing.py +65 -0
  70. onec_runtime/supervised_processes.py +232 -0
  71. onec_runtime/supervised_runtime.py +512 -0
  72. onec_runtime/supervisor.py +363 -0
  73. onec_runtime/supervisor_model.py +148 -0
  74. onec_runtime/supervisor_protocol.py +77 -0
  75. onec_runtime/table_materialization.py +471 -0
  76. onec_runtime/table_transfer_backend.py +203 -0
  77. onec_runtime/table_value.py +311 -0
  78. onec_runtime/toolchain.py +730 -0
  79. onec_runtime/value_materialization.py +432 -0
  80. onec_runtime/value_transfer_backend.py +199 -0
  81. onec_runtime/worker_breakpoints.py +1431 -0
  82. onec_runtime/worker_epf.py +313 -0
  83. onec_runtime/worker_epf_template.py +53 -0
  84. onec_runtime/worker_stage_protocol.py +516 -0
  85. onec_runtime/worker_universe.py +3772 -0
  86. onec_runtime_build/__init__.py +1 -0
  87. 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
+ ]