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,980 @@
1
+ from __future__ import annotations
2
+
3
+ import base64
4
+ from collections.abc import Iterable
5
+ from uuid import UUID
6
+ from xml.etree import ElementTree
7
+ from xml.sax.saxutils import escape
8
+
9
+ from onec_runtime.errors import ProtocolError
10
+ from onec_runtime.rdbg.models import (
11
+ CollectionCell,
12
+ CollectionRow,
13
+ DebugTarget,
14
+ EvaluationResult,
15
+ FrameVariable,
16
+ LocalVariablesResult,
17
+ ModuleLocation,
18
+ ModifyResult,
19
+ StackFrame,
20
+ StopEvent,
21
+ TargetId,
22
+ )
23
+
24
+
25
+ RDBG_NS = "http://v8.1c.ru/8.3/debugger/debugRDBGRequestResponse"
26
+ BASE_NS = "http://v8.1c.ru/8.3/debugger/debugBaseData"
27
+ BP_NS = "http://v8.1c.ru/8.3/debugger/debugBreakpoints"
28
+ CALC_NS = "http://v8.1c.ru/8.3/debugger/debugCalculations"
29
+ AUTO_ATTACH_NS = "http://v8.1c.ru/8.3/debugger/debugAutoAttach"
30
+ XSI_NS = "http://www.w3.org/2001/XMLSchema-instance"
31
+
32
+ ElementTree.register_namespace("rdbg", RDBG_NS)
33
+ ElementTree.register_namespace("bd", BASE_NS)
34
+ ElementTree.register_namespace("bp", BP_NS)
35
+ ElementTree.register_namespace("calc", CALC_NS)
36
+ ElementTree.register_namespace("aa", AUTO_ATTACH_NS)
37
+ ElementTree.register_namespace("xsi", XSI_NS)
38
+
39
+
40
+ def _tag(namespace: str, name: str) -> str:
41
+ return f"{{{namespace}}}{name}"
42
+
43
+
44
+ def _local_name(element: ElementTree.Element) -> str:
45
+ return element.tag.rsplit("}", 1)[-1]
46
+
47
+
48
+ def _children(element: ElementTree.Element, name: str) -> list[ElementTree.Element]:
49
+ return [child for child in element if _local_name(child) == name]
50
+
51
+
52
+ def _descendants(element: ElementTree.Element, name: str) -> list[ElementTree.Element]:
53
+ return [child for child in element.iter() if _local_name(child) == name]
54
+
55
+
56
+ def _required_text(element: ElementTree.Element, name: str) -> str:
57
+ matches = _descendants(element, name)
58
+ if not matches or matches[0].text is None or not matches[0].text.strip():
59
+ raise ProtocolError(f"RDBG XML is missing required {name}")
60
+ return matches[0].text.strip()
61
+
62
+
63
+ def _optional_text(element: ElementTree.Element, name: str) -> str | None:
64
+ matches = _descendants(element, name)
65
+ if not matches or matches[0].text is None:
66
+ return None
67
+ value = matches[0].text.strip()
68
+ return value or None
69
+
70
+
71
+ def _target_id(element: ElementTree.Element) -> TargetId:
72
+ seance_id = _optional_text(element, "seanceId")
73
+ seance_no = _optional_text(element, "seanceNo")
74
+ infobase_instance_id = _optional_text(element, "infoBaseInstanceID")
75
+ return TargetId(
76
+ UUID(_required_text(element, "id")),
77
+ _required_text(element, "infoBaseAlias"),
78
+ UUID(seance_id) if seance_id is not None else None,
79
+ int(seance_no) if seance_no is not None else None,
80
+ UUID(infobase_instance_id) if infobase_instance_id is not None else None,
81
+ _optional_text(element, "configVersion") or "",
82
+ )
83
+
84
+
85
+ def _optional_bool(element: ElementTree.Element, name: str) -> bool | None:
86
+ matches = _descendants(element, name)
87
+ if not matches or matches[0].text is None:
88
+ return None
89
+ value = matches[0].text.strip().casefold()
90
+ if value == "true":
91
+ return True
92
+ if value == "false":
93
+ return False
94
+ raise ProtocolError(f"RDBG XML has invalid {name}: {matches[0].text!r}")
95
+
96
+
97
+ def _parse(payload: bytes) -> ElementTree.Element:
98
+ try:
99
+ return ElementTree.fromstring(payload)
100
+ except ElementTree.ParseError as error:
101
+ raise ProtocolError(f"Malformed RDBG XML: {error}") from error
102
+
103
+
104
+ def parse_ping_document(payload: bytes) -> ElementTree.Element:
105
+ """Parse a ping response once for the specialized event extractors below."""
106
+ return _parse(payload)
107
+
108
+
109
+ def validate_command_acknowledgement(payload: bytes, *, command: str) -> None:
110
+ """Accept only the two success forms emitted by RDBG command endpoints."""
111
+ if not payload.strip():
112
+ return
113
+ try:
114
+ root = _parse(payload)
115
+ except ProtocolError as error:
116
+ raise ProtocolError(f"Invalid {command} acknowledgement") from error
117
+ results = _children(root, "result")
118
+ if (
119
+ _local_name(root) != "response"
120
+ or len(results) != 1
121
+ or (results[0].text or "").strip().casefold() != "success"
122
+ or len(results[0]) != 0
123
+ ):
124
+ raise ProtocolError(f"Invalid {command} acknowledgement")
125
+
126
+
127
+ def _serialize(root: ElementTree.Element) -> bytes:
128
+ return ElementTree.tostring(root, encoding="utf-8", xml_declaration=True)
129
+
130
+
131
+ def _request(alias: str, ui_id: UUID) -> ElementTree.Element:
132
+ root = ElementTree.Element(_tag(RDBG_NS, "request"))
133
+ ElementTree.SubElement(root, _tag(RDBG_NS, "infoBaseAlias")).text = alias
134
+ ElementTree.SubElement(root, _tag(RDBG_NS, "idOfDebuggerUI")).text = str(ui_id)
135
+ return root
136
+
137
+
138
+ def _target_id_element(parent: ElementTree.Element, name: str, target: TargetId) -> None:
139
+ target_element = ElementTree.SubElement(
140
+ parent,
141
+ _tag(RDBG_NS, name),
142
+ {_tag(XSI_NS, "type"): "bd:DebugTargetIdLight"},
143
+ )
144
+ ElementTree.SubElement(target_element, _tag(BASE_NS, "id")).text = str(target.id)
145
+
146
+
147
+ def build_attach_request(alias: str, ui_id: UUID) -> bytes:
148
+ root = _request(alias, ui_id)
149
+ options = ElementTree.SubElement(root, _tag(RDBG_NS, "options"))
150
+ ElementTree.SubElement(options, _tag(RDBG_NS, "foregroundAbility")).text = "true"
151
+ return _serialize(root)
152
+
153
+
154
+ def build_detach_request(alias: str, ui_id: UUID) -> bytes:
155
+ return _serialize(_request(alias, ui_id))
156
+
157
+
158
+ def build_terminate_request(
159
+ alias: str, ui_id: UUID, targets: tuple[TargetId, ...], states_payload: bytes,
160
+ *, target_type: str = "Server",
161
+ ) -> bytes:
162
+ """Terminate requires full native identities, unlike attach/step Light IDs."""
163
+ if target_type not in {"Server", "ManagedClient"}:
164
+ raise ValueError("Unsupported termination target type")
165
+ document = _parse(states_payload)
166
+ root = _request(alias, ui_id)
167
+ for target in targets:
168
+ matches = [
169
+ node for node in _descendants(document, "targetID")
170
+ if _target_id(node) == target
171
+ and _required_text(node, "targetType") == target_type
172
+ ]
173
+ if len(matches) != 1:
174
+ raise ProtocolError(f"{target_type} termination requires one exact target identity")
175
+ identity = ElementTree.fromstring(ElementTree.tostring(matches[0]))
176
+ identity.tag = _tag(RDBG_NS, "targetID")
177
+ root.append(identity)
178
+ if not targets:
179
+ raise ValueError(f"{target_type} termination requires at least one target")
180
+ return _serialize(root)
181
+
182
+
183
+ def build_init_settings_request(
184
+ alias: str, ui_id: UUID, *, break_on_next: bool = False
185
+ ) -> bytes:
186
+ root = _request(alias, ui_id)
187
+ data = ElementTree.SubElement(root, _tag(RDBG_NS, "data"))
188
+ ElementTree.SubElement(data, _tag(RDBG_NS, "breakOnNextLine")).text = str(
189
+ break_on_next
190
+ ).lower()
191
+ return _serialize(root)
192
+
193
+
194
+ def build_auto_attach_request(
195
+ alias: str,
196
+ ui_id: UUID,
197
+ *,
198
+ target_types: Iterable[str] = ("ServerEmulation", "ManagedClient"),
199
+ ) -> bytes:
200
+ root = _request(alias, ui_id)
201
+ settings = ElementTree.SubElement(
202
+ root,
203
+ _tag(RDBG_NS, "autoAttachSettings"),
204
+ {_tag(XSI_NS, "type"): "aa:DebugAutoAttachSettings"},
205
+ )
206
+ for target_type in target_types:
207
+ ElementTree.SubElement(
208
+ settings, _tag(AUTO_ATTACH_NS, "targetType")
209
+ ).text = target_type
210
+ ElementTree.SubElement(settings, _tag(AUTO_ATTACH_NS, "areaName")).text = ""
211
+ return _serialize(root)
212
+
213
+
214
+ def build_get_targets_request(alias: str, ui_id: UUID) -> bytes:
215
+ return _serialize(_request(alias, ui_id))
216
+
217
+
218
+ def build_clear_break_request(alias: str, ui_id: UUID) -> bytes:
219
+ return _serialize(_request(alias, ui_id))
220
+
221
+
222
+ def build_attach_target_request(
223
+ alias: str, ui_id: UUID, target: TargetId, *, attach: bool
224
+ ) -> bytes:
225
+ root = _request(alias, ui_id)
226
+ ElementTree.SubElement(root, _tag(RDBG_NS, "attach")).text = str(attach).lower()
227
+ _target_id_element(root, "id", target)
228
+ return _serialize(root)
229
+
230
+
231
+ def build_call_stack_request(alias: str, ui_id: UUID, target: TargetId) -> bytes:
232
+ root = _request(alias, ui_id)
233
+ outer = ElementTree.SubElement(root, _tag(RDBG_NS, "id"))
234
+ ElementTree.SubElement(outer, _tag(BASE_NS, "id")).text = str(target.id)
235
+ return _serialize(root)
236
+
237
+
238
+ def build_breakpoint_request(
239
+ alias: str, ui_id: UUID, location: ModuleLocation
240
+ ) -> bytes:
241
+ return build_breakpoints_request(alias, ui_id, (location,))
242
+
243
+
244
+ def build_breakpoints_request(
245
+ alias: str,
246
+ ui_id: UUID,
247
+ locations: Iterable[ModuleLocation],
248
+ ) -> bytes:
249
+ location_items = tuple(locations)
250
+ if not location_items:
251
+ raise ValueError("At least one breakpoint location is required")
252
+ fields: Iterable[tuple[str, str]] = (
253
+ ("isActive", "true"),
254
+ ("breakOnCondition", "false"),
255
+ ("breakOnParentMethod", "false"),
256
+ ("breakOnHitCount", "false"),
257
+ ("hitCountVariant", "0"),
258
+ ("hitCount", "1"),
259
+ ("showOutputMessage", "false"),
260
+ ("putStackTrace", "false"),
261
+ ("putHitCount", "false"),
262
+ ("continueExecution", "false"),
263
+ ("temp", "false"),
264
+ ("user", "true"),
265
+ )
266
+ common_fields = "".join(
267
+ f"<{name}>{escape(value)}</{name}>" for name, value in fields
268
+ )
269
+
270
+ def module_key(location: ModuleLocation) -> tuple[object, ...]:
271
+ return (
272
+ location.module_type,
273
+ location.url,
274
+ location.extension_name,
275
+ location.object_id,
276
+ location.property_id,
277
+ location.ext_id,
278
+ )
279
+
280
+ grouped: dict[tuple[object, ...], list[ModuleLocation]] = {}
281
+ for location in location_items:
282
+ grouped.setdefault(module_key(location), []).append(location)
283
+
284
+ def module_breakpoint(module_locations: list[ModuleLocation]) -> str:
285
+ location = module_locations[0]
286
+ bp_items = "".join(
287
+ f"<bpInfo><line>{item.line}</line>{common_fields}</bpInfo>"
288
+ for item in module_locations
289
+ )
290
+ return (
291
+ f'<moduleBPInfo xmlns="{BP_NS}"><id>'
292
+ f'<type xmlns="{BASE_NS}">{escape(location.module_type)}</type>'
293
+ f'<URL xmlns="{BASE_NS}">{escape(location.url)}</URL>'
294
+ f'<extensionName xmlns="{BASE_NS}">{escape(location.extension_name)}</extensionName>'
295
+ f'<objectID xmlns="{BASE_NS}">{location.object_id}</objectID>'
296
+ f'<propertyID xmlns="{BASE_NS}">{location.property_id}</propertyID>'
297
+ f'<extId xmlns="{BASE_NS}">{location.ext_id}</extId>'
298
+ f"</id>{bp_items}</moduleBPInfo>"
299
+ )
300
+
301
+ breakpoint_items = "".join(
302
+ module_breakpoint(module_locations)
303
+ for module_locations in grouped.values()
304
+ )
305
+ xml = (
306
+ '<?xml version="1.0" encoding="utf-8"?>'
307
+ "<request>"
308
+ f'<infoBaseAlias xmlns="{RDBG_NS}">{escape(alias)}</infoBaseAlias>'
309
+ f'<idOfDebuggerUI xmlns="{RDBG_NS}">{ui_id}</idOfDebuggerUI>'
310
+ f'<bpWorkspace xmlns="{RDBG_NS}">'
311
+ f"{breakpoint_items}"
312
+ "</bpWorkspace></request>"
313
+ )
314
+ return xml.encode("utf-8")
315
+
316
+
317
+ def _calculation_storage(
318
+ parent: ElementTree.Element,
319
+ element_name: str,
320
+ expression: str,
321
+ result_id: UUID,
322
+ *,
323
+ max_text_size: int = 307_200,
324
+ interface: str = "context",
325
+ start_index: int | None = None,
326
+ page_size: int | None = None,
327
+ stack_level: int = 0,
328
+ ) -> None:
329
+ storage = ElementTree.SubElement(
330
+ parent,
331
+ _tag(RDBG_NS, element_name),
332
+ {_tag(XSI_NS, "type"): "calc:CalculationSourceDataStorage"},
333
+ )
334
+ ElementTree.SubElement(storage, _tag(CALC_NS, "stackLevel")).text = str(
335
+ stack_level
336
+ )
337
+ source = ElementTree.SubElement(storage, _tag(CALC_NS, "srcCalcInfo"))
338
+ ElementTree.SubElement(source, _tag(CALC_NS, "expressionResultID")).text = str(result_id)
339
+ item = ElementTree.SubElement(source, _tag(CALC_NS, "calcItem"))
340
+ ElementTree.SubElement(item, _tag(CALC_NS, "itemType")).text = "expression"
341
+ ElementTree.SubElement(item, _tag(CALC_NS, "expression")).text = expression
342
+ ElementTree.SubElement(item, _tag(CALC_NS, "property")).text = ""
343
+ ElementTree.SubElement(source, _tag(CALC_NS, "interfaces")).text = interface
344
+ if start_index is not None:
345
+ ElementTree.SubElement(source, _tag(CALC_NS, "startIndex")).text = str(
346
+ start_index
347
+ )
348
+ if page_size is not None:
349
+ ElementTree.SubElement(source, _tag(CALC_NS, "pageSize")).text = str(page_size)
350
+ options = ElementTree.SubElement(storage, _tag(CALC_NS, "presOptions"))
351
+ ElementTree.SubElement(options, _tag(CALC_NS, "maxTextSize")).text = str(
352
+ max_text_size
353
+ )
354
+ ElementTree.SubElement(options, _tag(CALC_NS, "stopOnFirstEOL")).text = "false"
355
+
356
+
357
+ def build_eval_request(
358
+ alias: str,
359
+ ui_id: UUID,
360
+ target: TargetId,
361
+ expression: str,
362
+ result_id: UUID,
363
+ *,
364
+ max_text_size: int = 307_200,
365
+ stack_level: int = 0,
366
+ ) -> bytes:
367
+ if max_text_size <= 0:
368
+ raise ValueError("max_text_size must be positive")
369
+ if stack_level < 0:
370
+ raise ValueError("stack_level must be non-negative")
371
+ root = _request(alias, ui_id)
372
+ # This is a server-side batching delay, not the HTTP timeout. Large values
373
+ # add directly to every variable read even when calculation is immediate.
374
+ ElementTree.SubElement(root, _tag(RDBG_NS, "calcWaitingTime")).text = "25"
375
+ _target_id_element(root, "targetID", target)
376
+ _calculation_storage(
377
+ root,
378
+ "expr",
379
+ expression,
380
+ result_id,
381
+ max_text_size=max_text_size,
382
+ stack_level=stack_level,
383
+ )
384
+ return _serialize(root)
385
+
386
+
387
+ def build_collection_eval_request(
388
+ alias: str,
389
+ ui_id: UUID,
390
+ target: TargetId,
391
+ expression: str,
392
+ result_id: UUID,
393
+ *,
394
+ start_index: int,
395
+ page_size: int = 2400,
396
+ max_text_size: int = 4096,
397
+ stack_level: int = 0,
398
+ ) -> bytes:
399
+ if start_index < 0:
400
+ raise ValueError("start_index must be non-negative")
401
+ if page_size <= 0:
402
+ raise ValueError("page_size must be positive")
403
+ if max_text_size <= 0:
404
+ raise ValueError("max_text_size must be positive")
405
+ if stack_level < 0:
406
+ raise ValueError("stack_level must be non-negative")
407
+ root = _request(alias, ui_id)
408
+ ElementTree.SubElement(root, _tag(RDBG_NS, "calcWaitingTime")).text = "25"
409
+ _target_id_element(root, "targetID", target)
410
+ _calculation_storage(
411
+ root,
412
+ "expr",
413
+ expression,
414
+ result_id,
415
+ max_text_size=max_text_size,
416
+ interface="collection",
417
+ start_index=start_index,
418
+ page_size=page_size,
419
+ stack_level=stack_level,
420
+ )
421
+ return _serialize(root)
422
+
423
+
424
+ def build_local_variables_request(
425
+ alias: str,
426
+ ui_id: UUID,
427
+ target: TargetId,
428
+ stack_level: int,
429
+ result_id: UUID,
430
+ *,
431
+ max_text_size: int = 307_200,
432
+ ) -> bytes:
433
+ if type(max_text_size) is not int or max_text_size <= 0:
434
+ raise ValueError("max_text_size must be positive")
435
+ root = _request(alias, ui_id)
436
+ ElementTree.SubElement(root, _tag(RDBG_NS, "calcWaitingTime")).text = "25"
437
+ _target_id_element(root, "targetID", target)
438
+ storage = ElementTree.SubElement(
439
+ root,
440
+ _tag(RDBG_NS, "expr"),
441
+ {_tag(XSI_NS, "type"): "calc:CalculationSourceDataStorage"},
442
+ )
443
+ ElementTree.SubElement(storage, _tag(CALC_NS, "stackLevel")).text = str(
444
+ stack_level
445
+ )
446
+ source = ElementTree.SubElement(storage, _tag(CALC_NS, "srcCalcInfo"))
447
+ ElementTree.SubElement(source, _tag(CALC_NS, "expressionResultID")).text = str(
448
+ result_id
449
+ )
450
+ ElementTree.SubElement(source, _tag(CALC_NS, "interfaces")).text = "context"
451
+ options = ElementTree.SubElement(storage, _tag(CALC_NS, "presOptions"))
452
+ ElementTree.SubElement(options, _tag(CALC_NS, "maxTextSize")).text = str(max_text_size)
453
+ ElementTree.SubElement(options, _tag(CALC_NS, "stopOnFirstEOL")).text = "false"
454
+ return _serialize(root)
455
+
456
+
457
+ def build_modify_request(
458
+ alias: str,
459
+ ui_id: UUID,
460
+ target: TargetId,
461
+ variable: str,
462
+ value_expression: str,
463
+ result_id: UUID,
464
+ ) -> bytes:
465
+ root = _request(alias, ui_id)
466
+ _target_id_element(root, "targetID", target)
467
+ _calculation_storage(root, "modifyDataPath", variable, result_id)
468
+ value = ElementTree.SubElement(
469
+ root,
470
+ _tag(RDBG_NS, "newValueInfo"),
471
+ {_tag(XSI_NS, "type"): "calc:NewValueInfo"},
472
+ )
473
+ ElementTree.SubElement(value, _tag(CALC_NS, "variant")).text = "expr"
474
+ ElementTree.SubElement(value, _tag(CALC_NS, "valueExpression")).text = value_expression
475
+ ElementTree.SubElement(root, _tag(RDBG_NS, "timeout")).text = "60000"
476
+ return _serialize(root)
477
+
478
+
479
+ def build_step_request(alias: str, ui_id: UUID, target: TargetId) -> bytes:
480
+ root = _request(alias, ui_id)
481
+ _target_id_element(root, "targetID", target)
482
+ ElementTree.SubElement(root, _tag(RDBG_NS, "action")).text = "Continue"
483
+ ElementTree.SubElement(root, _tag(RDBG_NS, "simple")).text = "false"
484
+ return _serialize(root)
485
+
486
+
487
+ def _module_location(frame: ElementTree.Element) -> ModuleLocation:
488
+ module_matches = _descendants(frame, "moduleID")
489
+ if not module_matches:
490
+ raise ProtocolError("RDBG XML is missing required moduleID")
491
+ module = module_matches[0]
492
+ try:
493
+ type_nodes = _descendants(module, "type")
494
+ module_type = (
495
+ (type_nodes[0].text or "").strip() if type_nodes else "ConfigModule"
496
+ )
497
+ if not module_type:
498
+ module_type = "ConfigModule"
499
+ url_nodes = _descendants(module, "URL")
500
+ extension_nodes = _descendants(module, "extensionName")
501
+ ext_id_nodes = _descendants(module, "extId")
502
+ return ModuleLocation(
503
+ module_type=module_type,
504
+ url=(url_nodes[0].text or "").strip() if url_nodes else "",
505
+ object_id=UUID(_required_text(module, "objectID")),
506
+ property_id=UUID(_required_text(module, "propertyID")),
507
+ line=int(_required_text(frame, "lineNo")),
508
+ extension_name=(extension_nodes[0].text or "").strip()
509
+ if extension_nodes
510
+ else "",
511
+ ext_id=int((ext_id_nodes[0].text or "0").strip())
512
+ if ext_id_nodes
513
+ else 0,
514
+ )
515
+ except ValueError as error:
516
+ raise ProtocolError(f"Invalid module location: {error}") from error
517
+
518
+
519
+ def parse_targets(payload: bytes) -> list[DebugTarget]:
520
+ root = _parse(payload)
521
+ targets: list[DebugTarget] = []
522
+ for item in _descendants(root, "item"):
523
+ target_nodes = _children(item, "targetID")
524
+ if not target_nodes:
525
+ raise ProtocolError("RDBG XML is missing required targetID")
526
+ target_node = target_nodes[0]
527
+ try:
528
+ target_id = _target_id(target_node)
529
+ state_number_text = _required_text(item, "stateNum")
530
+ targets.append(
531
+ DebugTarget(
532
+ target_id=target_id,
533
+ target_type=_required_text(target_node, "targetType"),
534
+ state=_required_text(item, "state"),
535
+ state_number=int(state_number_text),
536
+ )
537
+ )
538
+ except ValueError as error:
539
+ raise ProtocolError(f"Invalid target state: {error}") from error
540
+ return targets
541
+
542
+
543
+ def parse_call_stack(payload: bytes, target: TargetId) -> list[StackFrame]:
544
+ root = _parse(payload)
545
+ frames: list[StackFrame] = []
546
+ protocol_frames = _descendants(root, "callStack")
547
+ for level, frame in enumerate(reversed(protocol_frames)):
548
+ if not (
549
+ _descendants(frame, "objectID")
550
+ and _descendants(frame, "propertyID")
551
+ ):
552
+ continue
553
+ frames.append(StackFrame(target, level, _module_location(frame)))
554
+ return frames
555
+
556
+
557
+ def parse_ping_events(payload: bytes) -> list[StopEvent]:
558
+ return parse_ping_events_from_document(parse_ping_document(payload))
559
+
560
+
561
+ def parse_ping_events_from_document(
562
+ root: ElementTree.Element,
563
+ ) -> list[StopEvent]:
564
+ events: list[StopEvent] = []
565
+ for item in _children(root, "result"):
566
+ commands = _children(item, "cmdID")
567
+ if not commands or not commands[0].text:
568
+ continue
569
+ command = commands[0].text.strip()
570
+ if command != "callStackFormed":
571
+ continue
572
+ target_nodes = _children(item, "targetID")
573
+ stack_nodes = _children(item, "callStack")
574
+ if not target_nodes or not stack_nodes:
575
+ raise ProtocolError("Stop event is missing targetID or callStack")
576
+ target_node = target_nodes[0]
577
+ target = _target_id(target_node)
578
+ runtime_errors = _descendants(item, "exceptionStr")
579
+ runtime_error = (
580
+ _decode_presentation(runtime_errors[0].text or "")
581
+ if runtime_errors
582
+ else ""
583
+ )
584
+ addressable_frames: list[StackFrame] = []
585
+ for level, frame in enumerate(reversed(stack_nodes)):
586
+ if not (
587
+ _descendants(frame, "objectID")
588
+ and _descendants(frame, "propertyID")
589
+ ):
590
+ continue
591
+ addressable_frames.append(StackFrame(target, level, _module_location(frame)))
592
+ if not addressable_frames:
593
+ raise ProtocolError("Stop event has no addressable stack frame")
594
+ stack = tuple(frame.location for frame in addressable_frames)
595
+ events.append(
596
+ StopEvent(
597
+ target,
598
+ stack[0],
599
+ command,
600
+ stop_by_breakpoint=_optional_bool(item, "stopByBP"),
601
+ suspended_by_other=_optional_bool(item, "suspendedByOther"),
602
+ stack=stack,
603
+ runtime_error=runtime_error,
604
+ stack_frames=tuple(addressable_frames),
605
+ )
606
+ )
607
+ return events
608
+
609
+
610
+ def parse_ping_target_events(payload: bytes) -> list[DebugTarget]:
611
+ return parse_ping_target_events_from_document(parse_ping_document(payload))
612
+
613
+
614
+ def parse_ping_target_events_from_document(
615
+ root: ElementTree.Element,
616
+ ) -> list[DebugTarget]:
617
+ targets: list[DebugTarget] = []
618
+ for item in _children(root, "result"):
619
+ commands = _children(item, "cmdID")
620
+ if not commands or (commands[0].text or "").strip() != "targetStarted":
621
+ continue
622
+ target_nodes = _children(item, "targetID")
623
+ if not target_nodes:
624
+ raise ProtocolError("Target-started event is missing targetID")
625
+ target_node = target_nodes[0]
626
+ targets.append(
627
+ DebugTarget(
628
+ _target_id(target_node),
629
+ _required_text(target_node, "targetType"),
630
+ "Started",
631
+ )
632
+ )
633
+ return targets
634
+
635
+
636
+ def _decode_presentation(value: str) -> str:
637
+ try:
638
+ compact = "".join(value.split())
639
+ return base64.b64decode(compact, validate=True).decode("utf-8")
640
+ except (ValueError, UnicodeDecodeError):
641
+ return value
642
+
643
+
644
+ def _preferred(element: ElementTree.Element, name: str) -> list[ElementTree.Element]:
645
+ direct = _children(element, name)
646
+ return direct if direct else _descendants(element, name)
647
+
648
+
649
+ def _required_preferred_text(element: ElementTree.Element, name: str) -> str:
650
+ matches = _children(element, name)
651
+ if matches and matches[0].text is not None and matches[0].text.strip():
652
+ return matches[0].text.strip()
653
+ return _required_text(element, name)
654
+
655
+
656
+ def _parse_evaluation_node(result: ElementTree.Element) -> EvaluationResult:
657
+ base_nodes = _children(result, "evalExprResBaseData")
658
+ container = base_nodes[0] if base_nodes else result
659
+ value_nodes = _preferred(container, "resultValueInfo")
660
+ if not value_nodes:
661
+ raise ProtocolError("RDBG XML is missing required resultValueInfo")
662
+ value = value_nodes[0]
663
+ error_nodes = _preferred(container, "errorOccurred")
664
+ error_occurred = bool(error_nodes and (error_nodes[0].text or "").lower() == "true")
665
+ error_text_nodes = _preferred(container, "exceptionStr")
666
+ error_text = _decode_presentation(error_text_nodes[0].text or "") if error_text_nodes else ""
667
+ type_nodes = _preferred(value, "typeName")
668
+ if error_occurred and not type_nodes:
669
+ type_name = "Ошибка"
670
+ else:
671
+ type_name = _required_text(value, "typeName")
672
+ presentation_nodes = _preferred(value, "pres")
673
+ type_code_nodes = _preferred(value, "typeCode")
674
+ value_string_nodes = _preferred(value, "valueString")
675
+ value_decimal_nodes = _preferred(value, "valueDecimal")
676
+ collection_size_nodes = _preferred(value, "collectionSize")
677
+ if error_occurred and not presentation_nodes:
678
+ presentation = error_text
679
+ else:
680
+ presentation = (
681
+ _decode_presentation(presentation_nodes[0].text or "")
682
+ if presentation_nodes
683
+ else (
684
+ (value_decimal_nodes[0].text or "").strip()
685
+ if value_decimal_nodes
686
+ else type_name
687
+ )
688
+ )
689
+ collection_rows: list[CollectionRow] = []
690
+ calculation_nodes = _children(container, "calculationResult")
691
+ row_nodes = (
692
+ _children(calculation_nodes[0], "valueOfCollectionInfo")
693
+ if calculation_nodes
694
+ else _descendants(container, "valueOfCollectionInfo")
695
+ )
696
+ for row_index, row_node in enumerate(row_nodes):
697
+ cells: list[CollectionCell] = []
698
+ cell_values: list[tuple[str, ElementTree.Element]] = []
699
+ for property_node in _children(row_node, "valueOfContextPropInfo"):
700
+ property_info_nodes = _children(property_node, "propInfo")
701
+ name_nodes = (
702
+ _children(property_info_nodes[0], "propName")
703
+ if property_info_nodes
704
+ else _descendants(property_node, "propName")
705
+ )
706
+ cell_value_nodes = _children(property_node, "valueInfo")
707
+ if not cell_value_nodes:
708
+ continue
709
+ cell_values.append(
710
+ (
711
+ (
712
+ (name_nodes[0].text or "").strip()
713
+ if name_nodes
714
+ else "Значение"
715
+ ),
716
+ cell_value_nodes[0],
717
+ )
718
+ )
719
+ if not cell_values:
720
+ cell_values.extend(
721
+ ("Значение", cell_value)
722
+ for cell_value in _children(row_node, "valueInfo")
723
+ )
724
+ for cell_name, cell_value in cell_values:
725
+ cell_type = _required_text(cell_value, "typeName")
726
+ cell_presentations = _children(cell_value, "pres")
727
+ cell_strings = _children(cell_value, "valueString")
728
+ cell_decimals = _children(cell_value, "valueDecimal")
729
+ cell_dates = _children(cell_value, "valueDateTime")
730
+ cell_booleans = _children(cell_value, "valueBoolean")
731
+ cell_presentation = (
732
+ _decode_presentation(cell_presentations[0].text or "")
733
+ if cell_presentations
734
+ else cell_type
735
+ )
736
+ cells.append(
737
+ CollectionCell(
738
+ name=cell_name,
739
+ type_name=cell_type,
740
+ presentation=cell_presentation,
741
+ value_string=(
742
+ _decode_presentation(cell_strings[0].text or "")
743
+ if cell_strings
744
+ else ""
745
+ ),
746
+ value_decimal=(
747
+ (cell_decimals[0].text or "").strip()
748
+ if cell_decimals
749
+ else ""
750
+ ),
751
+ value_date_time=(
752
+ (cell_dates[0].text or "").strip() if cell_dates else ""
753
+ ),
754
+ value_boolean=(
755
+ (cell_booleans[0].text or "").strip().casefold() == "true"
756
+ if cell_booleans
757
+ else None
758
+ ),
759
+ )
760
+ )
761
+ collection_rows.append(CollectionRow(row_index, tuple(cells)))
762
+ return EvaluationResult(
763
+ result_id=UUID(_required_preferred_text(container, "expressionResultID")),
764
+ type_name=type_name,
765
+ presentation=presentation,
766
+ error_occurred=error_occurred,
767
+ error_text=error_text,
768
+ type_code=(
769
+ int((type_code_nodes[0].text or "").strip())
770
+ if type_code_nodes and (type_code_nodes[0].text or "").strip()
771
+ else None
772
+ ),
773
+ value_string=(
774
+ _decode_presentation(value_string_nodes[0].text or "")
775
+ if value_string_nodes
776
+ else ""
777
+ ),
778
+ collection_size=(
779
+ int((collection_size_nodes[0].text or "").strip())
780
+ if collection_size_nodes
781
+ and (collection_size_nodes[0].text or "").strip()
782
+ else None
783
+ ),
784
+ collection_rows=tuple(collection_rows),
785
+ value_decimal=(
786
+ (value_decimal_nodes[0].text or "").strip()
787
+ if value_decimal_nodes
788
+ else ""
789
+ ),
790
+ )
791
+
792
+
793
+ def parse_eval_response(payload: bytes) -> EvaluationResult | None:
794
+ root = _parse(payload)
795
+ result_nodes = _children(root, "result")
796
+ if not result_nodes:
797
+ if not list(root) and not (root.text or "").strip():
798
+ return None
799
+ raise ProtocolError("RDBG XML is missing required result")
800
+ return _parse_evaluation_node(result_nodes[0])
801
+
802
+
803
+ def parse_eval_result(payload: bytes) -> EvaluationResult:
804
+ result = parse_eval_response(payload)
805
+ if result is None:
806
+ raise ProtocolError("RDBG XML is missing required result")
807
+ return result
808
+
809
+
810
+ def parse_modify_result(payload: bytes) -> ModifyResult:
811
+ root = _parse(payload)
812
+ state_nodes = _children(root, "newValueState")
813
+ if not state_nodes:
814
+ raise ProtocolError("RDBG XML is missing required newValueState")
815
+ state = state_nodes[0]
816
+ state_text = _required_text(state, "evalResultState")
817
+ error_nodes = _descendants(state, "errorOccurred")
818
+ error_text_nodes = _descendants(state, "exceptionStr")
819
+ error_text = (
820
+ _decode_presentation(error_text_nodes[0].text or "")
821
+ if error_text_nodes
822
+ else ""
823
+ )
824
+ error_occurred = (
825
+ state_text.casefold() != "correctly"
826
+ or bool(
827
+ error_nodes
828
+ and (error_nodes[0].text or "").strip().casefold() == "true"
829
+ )
830
+ or bool(error_text)
831
+ )
832
+ value_nodes = _descendants(state, "resultValueInfo")
833
+ if error_occurred:
834
+ type_name = "Ошибка"
835
+ presentation = error_text or state_text
836
+ elif not value_nodes:
837
+ raise ProtocolError("RDBG XML is missing required resultValueInfo")
838
+ else:
839
+ value = value_nodes[0]
840
+ type_name = _required_text(value, "typeName")
841
+ type_code_nodes = _descendants(value, "typeCode")
842
+ value_string_nodes = _descendants(value, "valueString")
843
+ presentation_nodes = _descendants(value, "pres")
844
+ presentation = (
845
+ _decode_presentation(presentation_nodes[0].text or "")
846
+ if presentation_nodes
847
+ else type_name
848
+ )
849
+ return ModifyResult(
850
+ result_id=UUID(_required_text(state, "expressionResultID")),
851
+ type_name=type_name,
852
+ presentation=presentation,
853
+ error_occurred=error_occurred,
854
+ error_text=error_text,
855
+ type_code=(
856
+ int((type_code_nodes[0].text or "").strip())
857
+ if not error_occurred
858
+ and type_code_nodes
859
+ and (type_code_nodes[0].text or "").strip()
860
+ else None
861
+ ),
862
+ value_string=(
863
+ _decode_presentation(value_string_nodes[0].text or "")
864
+ if not error_occurred and value_string_nodes
865
+ else ""
866
+ ),
867
+ )
868
+
869
+
870
+ def _frame_variable(
871
+ node: ElementTree.Element,
872
+ *,
873
+ name_field: str,
874
+ value_field: str,
875
+ ) -> FrameVariable | None:
876
+ name_nodes = _descendants(node, name_field)
877
+ value_nodes = _descendants(node, value_field)
878
+ if not name_nodes or not (name_nodes[0].text or "").strip() or not value_nodes:
879
+ return None
880
+ value = value_nodes[0]
881
+ type_name = _required_text(value, "typeName")
882
+ presentation_nodes = _descendants(value, "pres")
883
+ presentation = (
884
+ _decode_presentation(presentation_nodes[0].text or "")
885
+ if presentation_nodes
886
+ else type_name
887
+ )
888
+ size_nodes = _descendants(value, "collectionSize")
889
+ return FrameVariable(
890
+ name=(name_nodes[0].text or "").strip(),
891
+ type_name=type_name,
892
+ presentation=presentation,
893
+ collection_size=(
894
+ int((size_nodes[0].text or "").strip())
895
+ if size_nodes and (size_nodes[0].text or "").strip()
896
+ else None
897
+ ),
898
+ )
899
+
900
+
901
+ def _parse_local_variables_node(result: ElementTree.Element) -> LocalVariablesResult:
902
+ variables: list[FrameVariable] = []
903
+ for node in _descendants(result, "valueOfContextPropInfo"):
904
+ variable = _frame_variable(
905
+ node, name_field="propName", value_field="valueInfo"
906
+ )
907
+ if variable is not None:
908
+ variables.append(variable)
909
+ for node in _descendants(result, "localVariables"):
910
+ variable = _frame_variable(
911
+ node,
912
+ name_field="localVariableName",
913
+ value_field="resultValueInfo",
914
+ )
915
+ if variable is not None:
916
+ variables.append(variable)
917
+ error_nodes = _descendants(result, "errorOccurred")
918
+ error_occurred = bool(
919
+ error_nodes and (error_nodes[0].text or "").strip().lower() == "true"
920
+ )
921
+ error_text_nodes = _descendants(result, "exceptionStr")
922
+ error_text = (
923
+ _decode_presentation(error_text_nodes[0].text or "")
924
+ if error_text_nodes
925
+ else ""
926
+ )
927
+ return LocalVariablesResult(
928
+ result_id=UUID(_required_text(result, "expressionResultID")),
929
+ variables=tuple(variables),
930
+ error_occurred=error_occurred,
931
+ error_text=error_text,
932
+ )
933
+
934
+
935
+ def parse_local_variables_result(payload: bytes) -> LocalVariablesResult:
936
+ root = _parse(payload)
937
+ result_nodes = _children(root, "result")
938
+ if not result_nodes:
939
+ raise ProtocolError("RDBG XML is missing required result")
940
+ return _parse_local_variables_node(result_nodes[0])
941
+
942
+
943
+ def parse_ping_local_variables(payload: bytes) -> list[LocalVariablesResult]:
944
+ return parse_ping_local_variables_from_document(parse_ping_document(payload))
945
+
946
+
947
+ def parse_ping_local_variables_from_document(
948
+ root: ElementTree.Element,
949
+ ) -> list[LocalVariablesResult]:
950
+ results: list[LocalVariablesResult] = []
951
+ for item in _children(root, "result"):
952
+ commands = _children(item, "cmdID")
953
+ if not commands or (commands[0].text or "").strip() != "exprEvaluated":
954
+ continue
955
+ if _descendants(item, "valueOfCollectionInfo"):
956
+ continue
957
+ results.append(_parse_local_variables_node(item))
958
+ return results
959
+
960
+
961
+ def parse_ping_evaluations(payload: bytes) -> list[EvaluationResult]:
962
+ return parse_ping_evaluations_from_document(parse_ping_document(payload))
963
+
964
+
965
+ def parse_ping_evaluations_from_document(
966
+ root: ElementTree.Element,
967
+ ) -> list[EvaluationResult]:
968
+ evaluations: list[EvaluationResult] = []
969
+ for item in _children(root, "result"):
970
+ commands = _children(item, "cmdID")
971
+ if not commands or (commands[0].text or "").strip() != "exprEvaluated":
972
+ continue
973
+ collection_nodes = _descendants(item, "valueOfCollectionInfo")
974
+ if not collection_nodes and (
975
+ _descendants(item, "valueOfContextPropInfo")
976
+ or _descendants(item, "localVariables")
977
+ ):
978
+ continue
979
+ evaluations.append(_parse_evaluation_node(item))
980
+ return evaluations