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,388 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Sequence
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from onec_runtime.bsl.parser_artifact_identity import (
|
|
9
|
+
ParserArtifactManifest,
|
|
10
|
+
verify_parser_artifact_manifest,
|
|
11
|
+
)
|
|
12
|
+
from onec_runtime.bsl.lexer import Token, tokenize
|
|
13
|
+
from onec_runtime.bsl.source_maps import SourceSpan
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass(frozen=True, slots=True)
|
|
17
|
+
class ParseNode:
|
|
18
|
+
production: str
|
|
19
|
+
alternative: int
|
|
20
|
+
children: tuple["ParseNode | Token", ...]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class BslParseError(ValueError):
|
|
24
|
+
def __init__(
|
|
25
|
+
self,
|
|
26
|
+
message: str,
|
|
27
|
+
*,
|
|
28
|
+
span: SourceSpan = SourceSpan(0, 0),
|
|
29
|
+
code: str = "parse_error",
|
|
30
|
+
) -> None:
|
|
31
|
+
super().__init__(message)
|
|
32
|
+
self.span = span
|
|
33
|
+
self.code = code
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass(frozen=True, slots=True)
|
|
37
|
+
class GeneratedParserMetadata:
|
|
38
|
+
"""Provenance retained by the installed generated parser artifact."""
|
|
39
|
+
|
|
40
|
+
grammar_sha256: str
|
|
41
|
+
parsergen_package_sha256: str | None
|
|
42
|
+
manifest: ParserArtifactManifest | None = None
|
|
43
|
+
|
|
44
|
+
def __post_init__(self) -> None:
|
|
45
|
+
if (
|
|
46
|
+
self.manifest is not None
|
|
47
|
+
and self.grammar_sha256 != self.manifest.identity_sha256
|
|
48
|
+
):
|
|
49
|
+
raise ValueError("generated parser metadata identity is inconsistent")
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def parser_identity_sha256(self) -> str:
|
|
53
|
+
"""Return the complete artifact identity retained for cache admission."""
|
|
54
|
+
return self.grammar_sha256
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@dataclass(frozen=True, slots=True)
|
|
58
|
+
class DevelopmentParserDetails:
|
|
59
|
+
"""Parsergen-only details available from ``from_files()`` validation."""
|
|
60
|
+
|
|
61
|
+
resolved: Any
|
|
62
|
+
analysis: Any
|
|
63
|
+
parser_ir: Any
|
|
64
|
+
validation_warnings: tuple[dict[str, str], ...]
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class PythonParserTarget:
|
|
68
|
+
"""Runs the committed semantic parser, or a development validation build."""
|
|
69
|
+
|
|
70
|
+
ENTRYPOINTS = {
|
|
71
|
+
"module": "Модуль",
|
|
72
|
+
"notebook": "БлокНоутбука",
|
|
73
|
+
"notebook_cell": "ЯчейкаНоутбука",
|
|
74
|
+
"expression": "ОтдельноеВыражение",
|
|
75
|
+
"statement": "ОтдельнаяИнструкция",
|
|
76
|
+
"preprocessor": "ДирективаПрепроцессора",
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
def __init__(
|
|
80
|
+
self,
|
|
81
|
+
generated_parser_type: type[Any],
|
|
82
|
+
generated_error: type[Exception],
|
|
83
|
+
metadata: GeneratedParserMetadata,
|
|
84
|
+
*,
|
|
85
|
+
generated_source: str | None = None,
|
|
86
|
+
development: DevelopmentParserDetails | None = None,
|
|
87
|
+
) -> None:
|
|
88
|
+
self._generated_parser_type = generated_parser_type
|
|
89
|
+
self.generated_parser = generated_parser_type()
|
|
90
|
+
self.generated_error = generated_error
|
|
91
|
+
self.metadata = metadata
|
|
92
|
+
self.generated_source = generated_source
|
|
93
|
+
self.development = development
|
|
94
|
+
|
|
95
|
+
@property
|
|
96
|
+
def grammar_sha256(self) -> str:
|
|
97
|
+
return self.metadata.grammar_sha256
|
|
98
|
+
|
|
99
|
+
@classmethod
|
|
100
|
+
def from_generated(cls) -> "PythonParserTarget":
|
|
101
|
+
"""Load packaged code and allocate one mutable parser cursor."""
|
|
102
|
+
from onec_runtime.bsl import generated_semantic_parser
|
|
103
|
+
|
|
104
|
+
manifest = verify_parser_artifact_manifest(
|
|
105
|
+
generated_semantic_parser.PARSER_ARTIFACT_MANIFEST_JSON
|
|
106
|
+
)
|
|
107
|
+
metadata = GeneratedParserMetadata(
|
|
108
|
+
manifest.identity_sha256,
|
|
109
|
+
manifest.parsergen_package_sha256,
|
|
110
|
+
manifest,
|
|
111
|
+
)
|
|
112
|
+
return cls(
|
|
113
|
+
generated_semantic_parser.GeneratedParser,
|
|
114
|
+
generated_semantic_parser.GeneratedParseError,
|
|
115
|
+
metadata,
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
@classmethod
|
|
119
|
+
def from_files(
|
|
120
|
+
cls,
|
|
121
|
+
grammar_path: Path,
|
|
122
|
+
parsergen_src: Path,
|
|
123
|
+
*,
|
|
124
|
+
lookahead: int = 1,
|
|
125
|
+
) -> "PythonParserTarget":
|
|
126
|
+
"""Build through the optional build-time development adapter."""
|
|
127
|
+
from importlib import import_module
|
|
128
|
+
|
|
129
|
+
adapter = import_module("onec_runtime_build.parser_target_development")
|
|
130
|
+
return adapter.build_combined_python_parser_target(
|
|
131
|
+
cls,
|
|
132
|
+
grammar_path,
|
|
133
|
+
parsergen_src,
|
|
134
|
+
lookahead=lookahead,
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
def parse(self, source: str, entrypoint: str) -> None:
|
|
138
|
+
self.parse_ast(source, entrypoint)
|
|
139
|
+
|
|
140
|
+
def new_instance(self) -> "PythonParserTarget":
|
|
141
|
+
"""Reuse generated code while keeping parser cursor state controller-local."""
|
|
142
|
+
return PythonParserTarget(
|
|
143
|
+
self._generated_parser_type,
|
|
144
|
+
self.generated_error,
|
|
145
|
+
self.metadata,
|
|
146
|
+
generated_source=self.generated_source,
|
|
147
|
+
development=self.development,
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
def parse_ast(self, source: str, entrypoint: str) -> Any:
|
|
151
|
+
return self.parse_tokens_ast(tokenize(source), entrypoint)
|
|
152
|
+
|
|
153
|
+
def parse_tokens(self, tokens: Sequence[Token], entrypoint: str) -> None:
|
|
154
|
+
self.parse_tokens_ast(tokens, entrypoint)
|
|
155
|
+
|
|
156
|
+
def parse_tokens_ast(
|
|
157
|
+
self,
|
|
158
|
+
tokens: Sequence[Token],
|
|
159
|
+
entrypoint: str,
|
|
160
|
+
) -> Any:
|
|
161
|
+
entrypoint_name = self._entrypoint_name(entrypoint)
|
|
162
|
+
try:
|
|
163
|
+
return self.generated_parser.parse(tokens, entrypoint_name)
|
|
164
|
+
except self.generated_error as error:
|
|
165
|
+
actual = error.actual
|
|
166
|
+
start = (
|
|
167
|
+
tokens[error.position].start
|
|
168
|
+
if error.position < len(tokens)
|
|
169
|
+
else tokens[-1].end if tokens else 0
|
|
170
|
+
)
|
|
171
|
+
raise BslParseError(
|
|
172
|
+
f"Unexpected {actual!r} at {start}; expected {error.expected!r}",
|
|
173
|
+
span=SourceSpan(start, start),
|
|
174
|
+
code="unexpected_token",
|
|
175
|
+
) from error
|
|
176
|
+
|
|
177
|
+
def _entrypoint_name(self, entrypoint: str) -> str:
|
|
178
|
+
entrypoint_name = next(
|
|
179
|
+
(
|
|
180
|
+
name
|
|
181
|
+
for name, production in self.ENTRYPOINTS.items()
|
|
182
|
+
if production == entrypoint
|
|
183
|
+
),
|
|
184
|
+
None,
|
|
185
|
+
)
|
|
186
|
+
if entrypoint_name is None:
|
|
187
|
+
raise ValueError(f"Unknown parser entrypoint production {entrypoint!r}")
|
|
188
|
+
return entrypoint_name
|
|
189
|
+
|
|
190
|
+
def parse_interpreted(self, source: str, entrypoint: str) -> ParseNode:
|
|
191
|
+
return self.parse_tokens_interpreted(tokenize(source), entrypoint)
|
|
192
|
+
|
|
193
|
+
def parse_tokens_interpreted(
|
|
194
|
+
self,
|
|
195
|
+
tokens: Sequence[Token],
|
|
196
|
+
entrypoint: str,
|
|
197
|
+
) -> ParseNode:
|
|
198
|
+
self._require_development()
|
|
199
|
+
self.tokens = tokens
|
|
200
|
+
self.position = 0
|
|
201
|
+
node = self._production(entrypoint)
|
|
202
|
+
if self.position != len(self.tokens):
|
|
203
|
+
token = self.tokens[self.position]
|
|
204
|
+
raise BslParseError(
|
|
205
|
+
f"Unexpected {token.text!r} at {token.start}",
|
|
206
|
+
span=SourceSpan(token.start, token.start),
|
|
207
|
+
code="unexpected_token",
|
|
208
|
+
)
|
|
209
|
+
return node
|
|
210
|
+
|
|
211
|
+
def recognize_tokens_interpreted(
|
|
212
|
+
self,
|
|
213
|
+
tokens: Sequence[Token],
|
|
214
|
+
entrypoint: str,
|
|
215
|
+
) -> None:
|
|
216
|
+
self._require_development()
|
|
217
|
+
self.tokens = tokens
|
|
218
|
+
self.position = 0
|
|
219
|
+
self._recognize_production(entrypoint)
|
|
220
|
+
if self.position != len(self.tokens):
|
|
221
|
+
token = self.tokens[self.position]
|
|
222
|
+
raise BslParseError(
|
|
223
|
+
f"Unexpected {token.text!r} at {token.start}",
|
|
224
|
+
span=SourceSpan(token.start, token.start),
|
|
225
|
+
code="unexpected_token",
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
def _lookahead_matches(self, word: tuple[str, ...]) -> bool:
|
|
229
|
+
actual = tuple(token.type for token in self.tokens[self.position : self.position + len(word)])
|
|
230
|
+
if len(actual) < len(word):
|
|
231
|
+
actual += ("$",) * (len(word) - len(actual))
|
|
232
|
+
return actual == word
|
|
233
|
+
|
|
234
|
+
def _production(self, name: str) -> ParseNode:
|
|
235
|
+
development = self._require_development()
|
|
236
|
+
alternatives = development.resolved.productions[name]
|
|
237
|
+
matches = [
|
|
238
|
+
(alternative_number, alternative)
|
|
239
|
+
for alternative_number, alternative in enumerate(alternatives, start=1)
|
|
240
|
+
if any(
|
|
241
|
+
self._lookahead_matches(word)
|
|
242
|
+
for word in development.analysis.select[(name, alternative_number)]
|
|
243
|
+
)
|
|
244
|
+
]
|
|
245
|
+
if len(matches) != 1:
|
|
246
|
+
lookahead = [
|
|
247
|
+
token.type
|
|
248
|
+
for token in self.tokens[
|
|
249
|
+
self.position : self.position + development.analysis.k
|
|
250
|
+
]
|
|
251
|
+
]
|
|
252
|
+
offset = self._current_offset()
|
|
253
|
+
raise BslParseError(
|
|
254
|
+
f"No unique alternative for {name} at {lookahead}",
|
|
255
|
+
span=SourceSpan(offset, offset),
|
|
256
|
+
code="ambiguous_alternative",
|
|
257
|
+
)
|
|
258
|
+
alternative_number, alternative = matches[0]
|
|
259
|
+
children: list[ParseNode | Token] = []
|
|
260
|
+
for symbol in alternative.symbols:
|
|
261
|
+
if hasattr(symbol, "token_types"):
|
|
262
|
+
if self.position >= len(self.tokens):
|
|
263
|
+
offset = self._current_offset()
|
|
264
|
+
raise BslParseError(
|
|
265
|
+
f"Unexpected end while parsing {name}",
|
|
266
|
+
span=SourceSpan(offset, offset),
|
|
267
|
+
code="unexpected_end",
|
|
268
|
+
)
|
|
269
|
+
token = self.tokens[self.position]
|
|
270
|
+
if token.type not in symbol.token_types:
|
|
271
|
+
raise BslParseError(
|
|
272
|
+
f"Expected {sorted(symbol.token_types)}, got {token.type} at {token.start}",
|
|
273
|
+
span=SourceSpan(token.start, token.start),
|
|
274
|
+
code="unexpected_token",
|
|
275
|
+
)
|
|
276
|
+
self.position += 1
|
|
277
|
+
children.append(token)
|
|
278
|
+
else:
|
|
279
|
+
children.append(self._production(symbol.name))
|
|
280
|
+
return ParseNode(name, alternative_number, tuple(children))
|
|
281
|
+
|
|
282
|
+
def _recognize_production(self, name: str) -> None:
|
|
283
|
+
development = self._require_development()
|
|
284
|
+
alternatives = development.resolved.productions[name]
|
|
285
|
+
matches = [
|
|
286
|
+
alternative
|
|
287
|
+
for alternative_number, alternative in enumerate(alternatives, start=1)
|
|
288
|
+
if any(
|
|
289
|
+
self._lookahead_matches(word)
|
|
290
|
+
for word in development.analysis.select[(name, alternative_number)]
|
|
291
|
+
)
|
|
292
|
+
]
|
|
293
|
+
if len(matches) != 1:
|
|
294
|
+
lookahead = [
|
|
295
|
+
token.type
|
|
296
|
+
for token in self.tokens[
|
|
297
|
+
self.position : self.position + development.analysis.k
|
|
298
|
+
]
|
|
299
|
+
]
|
|
300
|
+
offset = self._current_offset()
|
|
301
|
+
raise BslParseError(
|
|
302
|
+
f"No unique alternative for {name} at {lookahead}",
|
|
303
|
+
span=SourceSpan(offset, offset),
|
|
304
|
+
code="ambiguous_alternative",
|
|
305
|
+
)
|
|
306
|
+
for symbol in matches[0].symbols:
|
|
307
|
+
if hasattr(symbol, "token_types"):
|
|
308
|
+
if self.position >= len(self.tokens):
|
|
309
|
+
offset = self._current_offset()
|
|
310
|
+
raise BslParseError(
|
|
311
|
+
f"Unexpected end while parsing {name}",
|
|
312
|
+
span=SourceSpan(offset, offset),
|
|
313
|
+
code="unexpected_end",
|
|
314
|
+
)
|
|
315
|
+
token = self.tokens[self.position]
|
|
316
|
+
if token.type not in symbol.token_types:
|
|
317
|
+
raise BslParseError(
|
|
318
|
+
f"Expected {sorted(symbol.token_types)}, got "
|
|
319
|
+
f"{token.type} at {token.start}",
|
|
320
|
+
span=SourceSpan(token.start, token.start),
|
|
321
|
+
code="unexpected_token",
|
|
322
|
+
)
|
|
323
|
+
self.position += 1
|
|
324
|
+
else:
|
|
325
|
+
self._recognize_production(symbol.name)
|
|
326
|
+
|
|
327
|
+
@property
|
|
328
|
+
def lookahead(self) -> int:
|
|
329
|
+
return self._require_development().analysis.k
|
|
330
|
+
|
|
331
|
+
@property
|
|
332
|
+
def parser_ir(self) -> Any:
|
|
333
|
+
return self._require_development().parser_ir
|
|
334
|
+
|
|
335
|
+
@property
|
|
336
|
+
def validation_warnings(self) -> tuple[dict[str, str], ...]:
|
|
337
|
+
return self._require_development().validation_warnings
|
|
338
|
+
|
|
339
|
+
def _current_offset(self) -> int:
|
|
340
|
+
if self.position < len(self.tokens):
|
|
341
|
+
return self.tokens[self.position].start
|
|
342
|
+
return self.tokens[-1].end if self.tokens else 0
|
|
343
|
+
|
|
344
|
+
def _require_development(self) -> DevelopmentParserDetails:
|
|
345
|
+
if self.development is None:
|
|
346
|
+
raise RuntimeError(
|
|
347
|
+
"Interpreted parser details are available only from from_files()"
|
|
348
|
+
)
|
|
349
|
+
return self.development
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
def parse_raw_module(
|
|
353
|
+
source: str,
|
|
354
|
+
parser_target: PythonParserTarget,
|
|
355
|
+
) -> tuple[Any, tuple[Token, ...]]:
|
|
356
|
+
"""Parse a raw BSL module while retaining directive-covered source code.
|
|
357
|
+
|
|
358
|
+
Each leading preprocessor line is validated by the grammar's directive
|
|
359
|
+
entrypoint. Only those line tokens are omitted from the module token view;
|
|
360
|
+
all returned tokens and AST spans keep their offsets in ``source``.
|
|
361
|
+
"""
|
|
362
|
+
tokens = tuple(tokenize(source))
|
|
363
|
+
module_tokens: list[Token] = []
|
|
364
|
+
position = 0
|
|
365
|
+
while position < len(tokens):
|
|
366
|
+
token = tokens[position]
|
|
367
|
+
line_start = source.rfind("\n", 0, token.start) + 1
|
|
368
|
+
indentation = source[line_start : token.start]
|
|
369
|
+
if token.type != "#" or not all(
|
|
370
|
+
character.isspace() for character in indentation
|
|
371
|
+
):
|
|
372
|
+
module_tokens.append(token)
|
|
373
|
+
position += 1
|
|
374
|
+
continue
|
|
375
|
+
|
|
376
|
+
line_end = source.find("\n", token.end)
|
|
377
|
+
if line_end < 0:
|
|
378
|
+
line_end = len(source)
|
|
379
|
+
directive_start = position
|
|
380
|
+
while position < len(tokens) and tokens[position].start < line_end:
|
|
381
|
+
position += 1
|
|
382
|
+
parser_target.parse_tokens_ast(
|
|
383
|
+
tokens[directive_start:position],
|
|
384
|
+
"ДирективаПрепроцессора",
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
root = parser_target.parse_tokens_ast(tuple(module_tokens), "Модуль")
|
|
388
|
+
return root, tokens
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def _blank(line: str) -> str:
|
|
5
|
+
if line.endswith("\r\n"):
|
|
6
|
+
return "\r\n"
|
|
7
|
+
if line.endswith("\n") or line.endswith("\r"):
|
|
8
|
+
return line[-1]
|
|
9
|
+
return ""
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def preprocess_server_source(source: str) -> str:
|
|
13
|
+
return "".join(
|
|
14
|
+
_blank(line) if line.lstrip().startswith(("#", "&")) else line
|
|
15
|
+
for line in source.splitlines(keepends=True)
|
|
16
|
+
)
|