pycforge 0.15.2__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 (221) hide show
  1. pycforge/__init__.py +8 -0
  2. pycforge/__main__.py +2 -0
  3. pycforge/_version.py +1 -0
  4. pycforge/converter/__init__.py +0 -0
  5. pycforge/converter/analysis/__init__.py +5 -0
  6. pycforge/converter/analysis/functions.py +723 -0
  7. pycforge/converter/analysis/model.py +158 -0
  8. pycforge/converter/analysis/planning.py +464 -0
  9. pycforge/converter/analysis/stage.py +1976 -0
  10. pycforge/converter/analysis/symbols.py +237 -0
  11. pycforge/converter/analysis/validation.py +2166 -0
  12. pycforge/converter/c_output/__init__.py +2 -0
  13. pycforge/converter/c_output/conformance.py +297 -0
  14. pycforge/converter/c_output/renderer.py +220 -0
  15. pycforge/converter/conditional_regions/__init__.py +71 -0
  16. pycforge/converter/conditional_regions/analysis.py +481 -0
  17. pycforge/converter/conditional_regions/lowering.py +579 -0
  18. pycforge/converter/conditional_regions/model.py +243 -0
  19. pycforge/converter/conditional_regions/validation.py +690 -0
  20. pycforge/converter/containers/__init__.py +19 -0
  21. pycforge/converter/containers/analysis.py +542 -0
  22. pycforge/converter/containers/lowering.py +290 -0
  23. pycforge/converter/containers/model.py +146 -0
  24. pycforge/converter/contracts/__init__.py +0 -0
  25. pycforge/converter/contracts/configuration.py +178 -0
  26. pycforge/converter/contracts/identifiers.py +68 -0
  27. pycforge/converter/contracts/versions.py +71 -0
  28. pycforge/converter/core/__init__.py +0 -0
  29. pycforge/converter/core/artifact_io.py +87 -0
  30. pycforge/converter/core/cancellation.py +7 -0
  31. pycforge/converter/core/canonicalization.py +254 -0
  32. pycforge/converter/core/context.py +13 -0
  33. pycforge/converter/core/diagnostics.py +95 -0
  34. pycforge/converter/core/enums.py +23 -0
  35. pycforge/converter/core/fingerprint.py +22 -0
  36. pycforge/converter/core/pipeline.py +8 -0
  37. pycforge/converter/core/progress.py +25 -0
  38. pycforge/converter/core/request.py +50 -0
  39. pycforge/converter/core/resource_policy.py +31 -0
  40. pycforge/converter/core/result.py +23 -0
  41. pycforge/converter/core/serialization.py +36 -0
  42. pycforge/converter/core/stage.py +10 -0
  43. pycforge/converter/core/stage_artifact.py +80 -0
  44. pycforge/converter/core/stage_outcome.py +15 -0
  45. pycforge/converter/decision_trace/__init__.py +0 -0
  46. pycforge/converter/decision_trace/recorder.py +52 -0
  47. pycforge/converter/facade.py +459 -0
  48. pycforge/converter/frontend/__init__.py +4 -0
  49. pycforge/converter/frontend/normalizer.py +59 -0
  50. pycforge/converter/frontend/parser.py +20 -0
  51. pycforge/converter/frontend/source_document.py +93 -0
  52. pycforge/converter/frontend/stages.py +518 -0
  53. pycforge/converter/frontend/validation.py +76 -0
  54. pycforge/converter/io/__init__.py +0 -0
  55. pycforge/converter/io/atomic_writer.py +33 -0
  56. pycforge/converter/ir/__init__.py +1 -0
  57. pycforge/converter/ir/c_ir/__init__.py +4 -0
  58. pycforge/converter/ir/c_ir/builder.py +26 -0
  59. pycforge/converter/ir/c_ir/model.py +318 -0
  60. pycforge/converter/ir/c_ir/serialization.py +88 -0
  61. pycforge/converter/ir/c_ir/validation.py +834 -0
  62. pycforge/converter/ir/python_ir/__init__.py +17 -0
  63. pycforge/converter/ir/python_ir/nodes.py +86 -0
  64. pycforge/converter/keyword_calls/__init__.py +68 -0
  65. pycforge/converter/keyword_calls/analysis.py +458 -0
  66. pycforge/converter/keyword_calls/lowering.py +271 -0
  67. pycforge/converter/keyword_calls/model.py +304 -0
  68. pycforge/converter/keyword_calls/validation.py +802 -0
  69. pycforge/converter/keyword_only_calls/__init__.py +68 -0
  70. pycforge/converter/keyword_only_calls/analysis.py +640 -0
  71. pycforge/converter/keyword_only_calls/lowering.py +376 -0
  72. pycforge/converter/keyword_only_calls/model.py +328 -0
  73. pycforge/converter/keyword_only_calls/validation.py +1357 -0
  74. pycforge/converter/lowering.py +1000 -0
  75. pycforge/converter/modules/__init__.py +13 -0
  76. pycforge/converter/modules/analysis.py +1250 -0
  77. pycforge/converter/modules/lowering.py +200 -0
  78. pycforge/converter/modules/model.py +73 -0
  79. pycforge/converter/modules/stage.py +192 -0
  80. pycforge/converter/numeric_semantics/__init__.py +23 -0
  81. pycforge/converter/numeric_semantics/analysis.py +420 -0
  82. pycforge/converter/numeric_semantics/lowering.py +153 -0
  83. pycforge/converter/numeric_semantics/model.py +123 -0
  84. pycforge/converter/records/__init__.py +34 -0
  85. pycforge/converter/records/analysis.py +1394 -0
  86. pycforge/converter/records/lowering.py +393 -0
  87. pycforge/converter/records/model.py +319 -0
  88. pycforge/converter/rules/__init__.py +3 -0
  89. pycforge/converter/support_templates/__init__.py +49 -0
  90. pycforge/converter/support_templates/assembly.py +106 -0
  91. pycforge/converter/support_templates/factories.py +348 -0
  92. pycforge/converter/support_templates/model.py +159 -0
  93. pycforge/converter/support_templates/registry.py +388 -0
  94. pycforge/converter/telemetry/__init__.py +0 -0
  95. pycforge/converter/telemetry/sink.py +39 -0
  96. pycforge/ide/__init__.py +19 -0
  97. pycforge/ide/__main__.py +5 -0
  98. pycforge/ide/_worker_event_codec.py +276 -0
  99. pycforge/ide/_worker_protocol_json.py +197 -0
  100. pycforge/ide/_worker_protocol_types.py +90 -0
  101. pycforge/ide/_worker_request_codec.py +200 -0
  102. pycforge/ide/_worker_request_data.py +260 -0
  103. pycforge/ide/_worker_result_data.py +343 -0
  104. pycforge/ide/action_contract.py +469 -0
  105. pycforge/ide/action_validation.py +229 -0
  106. pycforge/ide/command_palette.py +223 -0
  107. pycforge/ide/controller.py +904 -0
  108. pycforge/ide/controller_conversion.py +546 -0
  109. pycforge/ide/controller_io.py +205 -0
  110. pycforge/ide/editor.py +951 -0
  111. pycforge/ide/editor_commands_qt.py +274 -0
  112. pycforge/ide/editor_lexical.py +43 -0
  113. pycforge/ide/editor_sidebars.py +236 -0
  114. pycforge/ide/editor_syntax.py +243 -0
  115. pycforge/ide/find_replace.py +535 -0
  116. pycforge/ide/icons.py +91 -0
  117. pycforge/ide/io_service.py +196 -0
  118. pycforge/ide/model.py +164 -0
  119. pycforge/ide/panels.py +903 -0
  120. pycforge/ide/positions.py +220 -0
  121. pycforge/ide/process_worker.py +130 -0
  122. pycforge/ide/qt.py +230 -0
  123. pycforge/ide/qt_actions.py +418 -0
  124. pycforge/ide/qt_close.py +169 -0
  125. pycforge/ide/qt_command_palette.py +297 -0
  126. pycforge/ide/qt_contract.py +220 -0
  127. pycforge/ide/qt_documents.py +590 -0
  128. pycforge/ide/qt_editor_buffers.py +113 -0
  129. pycforge/ide/qt_editor_surfaces.py +112 -0
  130. pycforge/ide/qt_menus.py +324 -0
  131. pycforge/ide/qt_projection.py +500 -0
  132. pycforge/ide/qt_shell.py +348 -0
  133. pycforge/ide/qt_shell_interactions.py +320 -0
  134. pycforge/ide/qt_state.py +393 -0
  135. pycforge/ide/qt_workspace_features.py +398 -0
  136. pycforge/ide/qt_workspace_navigation.py +208 -0
  137. pycforge/ide/qt_workspace_observers.py +173 -0
  138. pycforge/ide/qt_workspace_panels.py +545 -0
  139. pycforge/ide/qt_workspace_widgets.py +228 -0
  140. pycforge/ide/resources/icons/about.svg +1 -0
  141. pycforge/ide/resources/icons/add-document.svg +1 -0
  142. pycforge/ide/resources/icons/brand-mark.svg +1 -0
  143. pycforge/ide/resources/icons/bundle-search.svg +1 -0
  144. pycforge/ide/resources/icons/cancel.svg +1 -0
  145. pycforge/ide/resources/icons/check.svg +1 -0
  146. pycforge/ide/resources/icons/chevron-right.svg +1 -0
  147. pycforge/ide/resources/icons/close.svg +1 -0
  148. pycforge/ide/resources/icons/collapse-all.svg +1 -0
  149. pycforge/ide/resources/icons/command-palette.svg +1 -0
  150. pycforge/ide/resources/icons/convert.svg +1 -0
  151. pycforge/ide/resources/icons/copy.svg +1 -0
  152. pycforge/ide/resources/icons/cut.svg +1 -0
  153. pycforge/ide/resources/icons/decision-trace.svg +1 -0
  154. pycforge/ide/resources/icons/details.svg +1 -0
  155. pycforge/ide/resources/icons/diagnostics.svg +1 -0
  156. pycforge/ide/resources/icons/duplicate-line.svg +1 -0
  157. pycforge/ide/resources/icons/exit.svg +1 -0
  158. pycforge/ide/resources/icons/expand-all.svg +1 -0
  159. pycforge/ide/resources/icons/export.svg +1 -0
  160. pycforge/ide/resources/icons/find.svg +1 -0
  161. pycforge/ide/resources/icons/go-to-line.svg +1 -0
  162. pycforge/ide/resources/icons/go-to-output.svg +1 -0
  163. pycforge/ide/resources/icons/go-to-source.svg +1 -0
  164. pycforge/ide/resources/icons/history.svg +1 -0
  165. pycforge/ide/resources/icons/indent.svg +1 -0
  166. pycforge/ide/resources/icons/link-c.svg +1 -0
  167. pycforge/ide/resources/icons/mappings.svg +1 -0
  168. pycforge/ide/resources/icons/module.svg +1 -0
  169. pycforge/ide/resources/icons/move-down.svg +1 -0
  170. pycforge/ide/resources/icons/move-line-down.svg +1 -0
  171. pycforge/ide/resources/icons/move-line-up.svg +1 -0
  172. pycforge/ide/resources/icons/move-up.svg +1 -0
  173. pycforge/ide/resources/icons/next.svg +1 -0
  174. pycforge/ide/resources/icons/open.svg +1 -0
  175. pycforge/ide/resources/icons/outdent.svg +1 -0
  176. pycforge/ide/resources/icons/outline.svg +1 -0
  177. pycforge/ide/resources/icons/paste.svg +1 -0
  178. pycforge/ide/resources/icons/previous.svg +1 -0
  179. pycforge/ide/resources/icons/primary-module.svg +1 -0
  180. pycforge/ide/resources/icons/redo.svg +1 -0
  181. pycforge/ide/resources/icons/remove-document.svg +1 -0
  182. pycforge/ide/resources/icons/replace.svg +1 -0
  183. pycforge/ide/resources/icons/save-as.svg +1 -0
  184. pycforge/ide/resources/icons/save.svg +1 -0
  185. pycforge/ide/resources/icons/select-all.svg +1 -0
  186. pycforge/ide/resources/icons/settings.svg +1 -0
  187. pycforge/ide/resources/icons/show-c.svg +1 -0
  188. pycforge/ide/resources/icons/split-view.svg +1 -0
  189. pycforge/ide/resources/icons/summary.svg +1 -0
  190. pycforge/ide/resources/icons/telemetry.svg +1 -0
  191. pycforge/ide/resources/icons/toggle-comment.svg +1 -0
  192. pycforge/ide/resources/icons/toggle-fold.svg +1 -0
  193. pycforge/ide/resources/icons/undo.svg +1 -0
  194. pycforge/ide/resources/icons/whitespace.svg +1 -0
  195. pycforge/ide/revisions.py +400 -0
  196. pycforge/ide/search_service.py +280 -0
  197. pycforge/ide/session_history.py +150 -0
  198. pycforge/ide/source_editing.py +417 -0
  199. pycforge/ide/source_structure.py +558 -0
  200. pycforge/ide/source_structure_async.py +191 -0
  201. pycforge/ide/supervisor.py +503 -0
  202. pycforge/ide/theme.py +119 -0
  203. pycforge/ide/theme_stylesheet.py +598 -0
  204. pycforge/ide/theme_workspace_stylesheet.py +155 -0
  205. pycforge/ide/visual_tokens.py +142 -0
  206. pycforge/ide/worker_protocol.py +80 -0
  207. pycforge/ide/workspace_search.py +462 -0
  208. pycforge/ide/workspace_session.py +256 -0
  209. pycforge/laboratory/__init__.py +6 -0
  210. pycforge/laboratory/audits.py +2244 -0
  211. pycforge/laboratory/checkpoint_e.py +2685 -0
  212. pycforge/laboratory/cli.py +119 -0
  213. pycforge/laboratory/exit_codes.py +11 -0
  214. pycforge/laboratory/keyword_audit.py +661 -0
  215. pycforge/laboratory/keyword_only_audit.py +481 -0
  216. pycforge-0.15.2.dist-info/METADATA +209 -0
  217. pycforge-0.15.2.dist-info/RECORD +221 -0
  218. pycforge-0.15.2.dist-info/WHEEL +5 -0
  219. pycforge-0.15.2.dist-info/entry_points.txt +5 -0
  220. pycforge-0.15.2.dist-info/licenses/LICENSE +674 -0
  221. pycforge-0.15.2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,579 @@
1
+ """Structured C IR lowering for proved conditional temporary regions."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import hashlib
6
+ from dataclasses import dataclass
7
+ from typing import Any, Callable
8
+
9
+ from pycforge.converter.ir.c_ir import (
10
+ CAssignmentStatement,
11
+ CBinaryExpr,
12
+ CBinaryOp,
13
+ CBlock,
14
+ CBooleanLiteral,
15
+ CFloatLiteral,
16
+ CIdentifierRef,
17
+ CIfStatement,
18
+ CIntegerLiteral,
19
+ CProvenance,
20
+ CType,
21
+ CUnaryExpr,
22
+ CUnaryOp,
23
+ )
24
+
25
+ from .model import (
26
+ CONDITIONAL_REGION_LOWERING_SHAPE,
27
+ ConditionalGuardPolarity,
28
+ ConditionalRegionKind,
29
+ )
30
+
31
+
32
+ def _sid(prefix: str, *parts: str) -> str:
33
+ digest = hashlib.sha256("\x1f".join(parts).encode("utf-8")).hexdigest()[:20]
34
+ return prefix + digest
35
+
36
+
37
+ _COMPARISON_OPERATORS = {
38
+ "Eq": CBinaryOp.EQUAL,
39
+ "NotEq": CBinaryOp.NOT_EQUAL,
40
+ "Lt": CBinaryOp.LESS,
41
+ "LtE": CBinaryOp.LESS_EQUAL,
42
+ "Gt": CBinaryOp.GREATER,
43
+ "GtE": CBinaryOp.GREATER_EQUAL,
44
+ }
45
+
46
+
47
+ @dataclass(slots=True)
48
+ class ConditionalRegionLoweringServices:
49
+ nodes: dict[str, dict[str, Any]]
50
+ categories: dict[str, str]
51
+ regions: dict[str, dict[str, Any]]
52
+ expression: Callable[[dict[str, Any]], tuple[tuple[Any, ...], Any]]
53
+ temporary: Callable[..., tuple[Any, CIdentifierRef]]
54
+ category_type: Callable[[str], CType]
55
+ provenance: Callable[..., CProvenance]
56
+ synthetic_provenance: Callable[..., CProvenance]
57
+ reject: Callable[..., Any]
58
+ check_cancellation: Callable[[], None]
59
+
60
+
61
+ class ConditionalRegionCIRLowerer:
62
+ """Lower legacy-direct or independently proved guarded expression forms."""
63
+
64
+ def __init__(self, services: ConditionalRegionLoweringServices) -> None:
65
+ self.services = services
66
+
67
+ def boolean(self, node: dict[str, Any]) -> tuple[tuple[Any, ...], Any]:
68
+ fact = self.services.regions.get(node["node_id"])
69
+ if fact is None:
70
+ return self._legacy_boolean(node)
71
+ self._require_fact(node, fact, ConditionalRegionKind.BOOLEAN_SHORT_CIRCUIT)
72
+ return self._guarded_boolean(node, fact)
73
+
74
+ def comparison(self, node: dict[str, Any]) -> tuple[tuple[Any, ...], Any]:
75
+ fact = self.services.regions.get(node["node_id"])
76
+ if fact is None:
77
+ return self._legacy_comparison(node)
78
+ self._require_fact(node, fact, ConditionalRegionKind.CHAINED_COMPARISON)
79
+ return self._guarded_comparison(node, fact)
80
+
81
+ def _legacy_boolean(self, node: dict[str, Any]) -> tuple[tuple[Any, ...], Any]:
82
+ s = self.services
83
+ values = node["fields"].get("values", [])
84
+ operator_kind = s.nodes[node["fields"]["op"]]["kind"]
85
+ operator = (
86
+ CBinaryOp.LOGICAL_AND
87
+ if operator_kind == "And"
88
+ else CBinaryOp.LOGICAL_OR
89
+ if operator_kind == "Or"
90
+ else None
91
+ )
92
+ if (
93
+ operator is None
94
+ or len(values) < 2
95
+ or any(s.categories.get(item) != "boolean-like" for item in values)
96
+ ):
97
+ s.reject(
98
+ "PYC2826",
99
+ "Only Boolean-represented and/or expressions are supported",
100
+ node,
101
+ )
102
+ lowered = [s.expression(s.nodes[item]) for item in values]
103
+ if any(prelude for prelude, _ in lowered):
104
+ s.reject(
105
+ "PYC2950",
106
+ "Calls inside short-circuit Boolean expressions require a conditional-temporary proof",
107
+ node,
108
+ )
109
+ result = lowered[0][1]
110
+ provenance = s.provenance(node)
111
+ for child_id, (_, child) in zip(values[1:], lowered[1:]):
112
+ result = CBinaryExpr(
113
+ _sid("c-boolop-", node["node_id"], child_id),
114
+ operator,
115
+ result,
116
+ child,
117
+ provenance,
118
+ )
119
+ return (), result
120
+
121
+ def _guarded_boolean(
122
+ self,
123
+ node: dict[str, Any],
124
+ fact: dict[str, Any],
125
+ ) -> tuple[tuple[Any, ...], Any]:
126
+ s = self.services
127
+ values = tuple(fact["operand_node_ids"])
128
+ operator_kind = fact["operator_kinds"][0]
129
+ polarity = (
130
+ ConditionalGuardPolarity.WHEN_RESULT_TRUE.value
131
+ if operator_kind == "And"
132
+ else ConditionalGuardPolarity.WHEN_RESULT_FALSE.value
133
+ )
134
+ s.check_cancellation()
135
+ first_prelude, first_expression = s.expression(s.nodes[values[0]])
136
+ result_declaration, result_template = s.temporary(
137
+ "bool_region_result",
138
+ node,
139
+ 0,
140
+ CType("bool"),
141
+ first_expression,
142
+ (values[0], node["node_id"]),
143
+ )
144
+ prelude: list[Any] = [*first_prelude, result_declaration]
145
+ result_binding = result_template.binding_id
146
+ placements = tuple(fact["placements"])
147
+ for ordinal, operand_id in enumerate(values[1:], 1):
148
+ s.check_cancellation()
149
+ placement = placements[ordinal]
150
+ if (
151
+ placement.get("evaluation_mode") != "guarded"
152
+ or placement.get("guard_polarity") != polarity
153
+ or placement.get("guard_after_operand_ordinal") != ordinal - 1
154
+ ):
155
+ s.reject("PYC2950", "Boolean conditional placement fact is inconsistent", node)
156
+ operand_prelude, operand_expression = s.expression(s.nodes[operand_id])
157
+ target = self._reference(
158
+ result_binding,
159
+ "c-bool-region-target-",
160
+ node,
161
+ ordinal,
162
+ (operand_id, node["node_id"]),
163
+ )
164
+ assignment = CAssignmentStatement(
165
+ _sid("c-bool-region-assign-", node["node_id"], str(ordinal)),
166
+ target,
167
+ operand_expression,
168
+ s.synthetic_provenance((operand_id, node["node_id"]), node["node_id"]),
169
+ )
170
+ condition_reference = self._reference(
171
+ result_binding,
172
+ "c-bool-region-guard-ref-",
173
+ node,
174
+ ordinal,
175
+ (node["node_id"], operand_id),
176
+ )
177
+ condition = (
178
+ condition_reference
179
+ if polarity == ConditionalGuardPolarity.WHEN_RESULT_TRUE.value
180
+ else CUnaryExpr(
181
+ _sid("c-bool-region-not-", node["node_id"], str(ordinal)),
182
+ CUnaryOp.LOGICAL_NOT,
183
+ condition_reference,
184
+ s.synthetic_provenance(
185
+ (node["node_id"], operand_id), node["node_id"]
186
+ ),
187
+ )
188
+ )
189
+ block = CBlock(
190
+ _sid("c-bool-region-block-", node["node_id"], str(ordinal)),
191
+ tuple(operand_prelude) + (assignment,),
192
+ s.synthetic_provenance((operand_id, node["node_id"]), node["node_id"]),
193
+ )
194
+ prelude.append(
195
+ CIfStatement(
196
+ _sid("c-bool-region-if-", node["node_id"], str(ordinal)),
197
+ condition,
198
+ block,
199
+ None,
200
+ s.synthetic_provenance(
201
+ (node["node_id"], operand_id), node["node_id"]
202
+ ),
203
+ )
204
+ )
205
+ s.check_cancellation()
206
+ return tuple(prelude), self._reference(
207
+ result_binding,
208
+ "c-bool-region-result-ref-",
209
+ node,
210
+ len(values),
211
+ (node["node_id"],),
212
+ )
213
+
214
+ def _legacy_comparison(self, node: dict[str, Any]) -> tuple[tuple[Any, ...], Any]:
215
+ s = self.services
216
+ fields = node["fields"]
217
+ operand_ids = [fields["left"]] + list(fields.get("comparators", []))
218
+ operator_ids = list(fields.get("ops", []))
219
+ if not operator_ids or len(operand_ids) != len(operator_ids) + 1:
220
+ s.reject("PYC2829", "Malformed comparison", node)
221
+ prelude: list[Any] = []
222
+ operands: list[Any] = []
223
+ chained = len(operator_ids) > 1
224
+ for ordinal, operand_id in enumerate(operand_ids):
225
+ expression_prelude, expression = s.expression(s.nodes[operand_id])
226
+ if chained and ordinal >= 2 and (
227
+ expression_prelude
228
+ or s.nodes[operand_id]["kind"] not in {"Name", "Constant"}
229
+ ):
230
+ s.reject(
231
+ "PYC2951",
232
+ "A conditionally evaluated chained-comparison operand must have a conditional-temporary proof",
233
+ s.nodes[operand_id],
234
+ )
235
+ prelude.extend(expression_prelude)
236
+ category = s.categories.get(operand_id, "unknown")
237
+ if category not in {"integer-like", "floating-like", "boolean-like"}:
238
+ s.reject(
239
+ "PYC2860",
240
+ f"Comparison operand has unsupported representation: {category}",
241
+ s.nodes[operand_id],
242
+ )
243
+ if chained:
244
+ declaration, reference = s.temporary(
245
+ "cmp",
246
+ node,
247
+ ordinal,
248
+ s.category_type(category),
249
+ expression,
250
+ (operand_id, node["node_id"]),
251
+ )
252
+ prelude.append(declaration)
253
+ operands.append(reference)
254
+ else:
255
+ operands.append(expression)
256
+ pieces: list[Any] = []
257
+ for ordinal, operator_id in enumerate(operator_ids):
258
+ operator_kind = s.nodes[operator_id]["kind"]
259
+ if operator_kind not in _COMPARISON_OPERATORS:
260
+ s.reject(
261
+ "PYC2828", f"Unsupported comparison operator: {operator_kind}", node
262
+ )
263
+ left, right = operands[ordinal], operands[ordinal + 1]
264
+ if chained:
265
+ left = CIdentifierRef(
266
+ _sid("c-cmp-ref-l-", node["node_id"], str(ordinal)),
267
+ left.binding_id,
268
+ s.synthetic_provenance(
269
+ (operand_ids[ordinal], node["node_id"]), node["node_id"]
270
+ ),
271
+ )
272
+ right = CIdentifierRef(
273
+ _sid("c-cmp-ref-r-", node["node_id"], str(ordinal)),
274
+ right.binding_id,
275
+ s.synthetic_provenance(
276
+ (operand_ids[ordinal + 1], node["node_id"]), node["node_id"]
277
+ ),
278
+ )
279
+ pieces.append(
280
+ CBinaryExpr(
281
+ _sid("c-cmp-", node["node_id"], str(ordinal)),
282
+ _COMPARISON_OPERATORS[operator_kind],
283
+ left,
284
+ right,
285
+ s.provenance(node),
286
+ )
287
+ )
288
+ result = pieces[0]
289
+ for ordinal, piece in enumerate(pieces[1:], 1):
290
+ result = CBinaryExpr(
291
+ _sid("c-chain-", node["node_id"], str(ordinal)),
292
+ CBinaryOp.LOGICAL_AND,
293
+ result,
294
+ piece,
295
+ s.provenance(node),
296
+ )
297
+ return tuple(prelude), result
298
+
299
+ def _guarded_comparison(
300
+ self,
301
+ node: dict[str, Any],
302
+ fact: dict[str, Any],
303
+ ) -> tuple[tuple[Any, ...], Any]:
304
+ s = self.services
305
+ operand_ids = tuple(fact["operand_node_ids"])
306
+ operator_ids = tuple(fact["operator_node_ids"])
307
+ operator_kinds = tuple(fact["operator_kinds"])
308
+ prelude: list[Any] = []
309
+ operand_bindings: list[str] = []
310
+
311
+ for ordinal, operand_id in enumerate(operand_ids[:2]):
312
+ s.check_cancellation()
313
+ expression_prelude, expression = s.expression(s.nodes[operand_id])
314
+ prelude.extend(expression_prelude)
315
+ category = s.categories.get(operand_id, "unknown")
316
+ declaration, reference = s.temporary(
317
+ "cmp",
318
+ node,
319
+ ordinal,
320
+ s.category_type(category),
321
+ expression,
322
+ (operand_id, node["node_id"]),
323
+ )
324
+ prelude.append(declaration)
325
+ operand_bindings.append(reference.binding_id)
326
+
327
+ for ordinal, operand_id in enumerate(operand_ids[2:], 2):
328
+ s.check_cancellation()
329
+ category = s.categories.get(operand_id, "unknown")
330
+ declaration, reference = s.temporary(
331
+ "cmp",
332
+ node,
333
+ ordinal,
334
+ s.category_type(category),
335
+ self._zero(node, operand_id, ordinal, category),
336
+ (operand_id, node["node_id"]),
337
+ )
338
+ prelude.append(declaration)
339
+ operand_bindings.append(reference.binding_id)
340
+
341
+ first_comparison = self._comparison_expression(
342
+ node,
343
+ 0,
344
+ operator_kinds[0],
345
+ operand_ids,
346
+ operand_bindings,
347
+ )
348
+ result_declaration, result_template = s.temporary(
349
+ "comparison_region_result",
350
+ node,
351
+ 0,
352
+ CType("bool"),
353
+ first_comparison,
354
+ (operand_ids[0], operand_ids[1], node["node_id"]),
355
+ )
356
+ prelude.append(result_declaration)
357
+ result_binding = result_template.binding_id
358
+ placements = tuple(fact["placements"])
359
+
360
+ for ordinal, operand_id in enumerate(operand_ids[2:], 2):
361
+ s.check_cancellation()
362
+ placement = placements[ordinal]
363
+ if (
364
+ placement.get("evaluation_mode") != "guarded"
365
+ or placement.get("guard_polarity")
366
+ != ConditionalGuardPolarity.WHEN_RESULT_TRUE.value
367
+ or placement.get("guard_after_operand_ordinal") != ordinal - 1
368
+ ):
369
+ s.reject(
370
+ "PYC2951", "Chained-comparison placement fact is inconsistent", node
371
+ )
372
+ expression_prelude, expression = s.expression(s.nodes[operand_id])
373
+ operand_assignment = CAssignmentStatement(
374
+ _sid("c-chain-region-value-assign-", node["node_id"], str(ordinal)),
375
+ self._reference(
376
+ operand_bindings[ordinal],
377
+ "c-chain-region-value-target-",
378
+ node,
379
+ ordinal,
380
+ (operand_id, node["node_id"]),
381
+ ),
382
+ expression,
383
+ s.synthetic_provenance((operand_id, node["node_id"]), node["node_id"]),
384
+ )
385
+ comparison = self._comparison_expression(
386
+ node,
387
+ ordinal - 1,
388
+ operator_kinds[ordinal - 1],
389
+ operand_ids,
390
+ operand_bindings,
391
+ )
392
+ result_assignment = CAssignmentStatement(
393
+ _sid("c-chain-region-result-assign-", node["node_id"], str(ordinal)),
394
+ self._reference(
395
+ result_binding,
396
+ "c-chain-region-result-target-",
397
+ node,
398
+ ordinal,
399
+ (node["node_id"], operand_id),
400
+ ),
401
+ comparison,
402
+ s.synthetic_provenance((operand_id, node["node_id"]), node["node_id"]),
403
+ )
404
+ block = CBlock(
405
+ _sid("c-chain-region-block-", node["node_id"], str(ordinal)),
406
+ tuple(expression_prelude) + (operand_assignment, result_assignment),
407
+ s.synthetic_provenance((operand_id, node["node_id"]), node["node_id"]),
408
+ )
409
+ prelude.append(
410
+ CIfStatement(
411
+ _sid("c-chain-region-if-", node["node_id"], str(ordinal)),
412
+ self._reference(
413
+ result_binding,
414
+ "c-chain-region-guard-ref-",
415
+ node,
416
+ ordinal,
417
+ (node["node_id"], operand_id),
418
+ ),
419
+ block,
420
+ None,
421
+ s.synthetic_provenance(
422
+ (node["node_id"], operand_id), node["node_id"]
423
+ ),
424
+ )
425
+ )
426
+ s.check_cancellation()
427
+ return tuple(prelude), self._reference(
428
+ result_binding,
429
+ "c-chain-region-final-ref-",
430
+ node,
431
+ len(operand_ids),
432
+ (node["node_id"],),
433
+ )
434
+
435
+ def _comparison_expression(
436
+ self,
437
+ node: dict[str, Any],
438
+ comparison_ordinal: int,
439
+ operator_kind: str,
440
+ operand_ids: tuple[str, ...],
441
+ operand_bindings: list[str],
442
+ ) -> CBinaryExpr:
443
+ s = self.services
444
+ if operator_kind not in _COMPARISON_OPERATORS:
445
+ s.reject("PYC2828", f"Unsupported comparison operator: {operator_kind}", node)
446
+ left_ordinal = comparison_ordinal
447
+ right_ordinal = comparison_ordinal + 1
448
+ left = self._reference(
449
+ operand_bindings[left_ordinal],
450
+ "c-chain-region-left-ref-",
451
+ node,
452
+ comparison_ordinal,
453
+ (operand_ids[left_ordinal], node["node_id"]),
454
+ )
455
+ right = self._reference(
456
+ operand_bindings[right_ordinal],
457
+ "c-chain-region-right-ref-",
458
+ node,
459
+ comparison_ordinal,
460
+ (operand_ids[right_ordinal], node["node_id"]),
461
+ )
462
+ return CBinaryExpr(
463
+ _sid("c-chain-region-compare-", node["node_id"], str(comparison_ordinal)),
464
+ _COMPARISON_OPERATORS[operator_kind],
465
+ left,
466
+ right,
467
+ s.provenance(node),
468
+ )
469
+
470
+ def _zero(
471
+ self,
472
+ owner: dict[str, Any],
473
+ operand_id: str,
474
+ ordinal: int,
475
+ category: str,
476
+ ) -> Any:
477
+ provenance = self.services.synthetic_provenance(
478
+ (operand_id, owner["node_id"]), owner["node_id"]
479
+ )
480
+ if category == "integer-like":
481
+ return CIntegerLiteral(
482
+ _sid("c-chain-region-zero-int-", owner["node_id"], str(ordinal)),
483
+ 0,
484
+ "LL",
485
+ provenance,
486
+ )
487
+ if category == "floating-like":
488
+ return CFloatLiteral(
489
+ _sid("c-chain-region-zero-float-", owner["node_id"], str(ordinal)),
490
+ 0.0,
491
+ provenance,
492
+ )
493
+ if category == "boolean-like":
494
+ return CBooleanLiteral(
495
+ _sid("c-chain-region-zero-bool-", owner["node_id"], str(ordinal)),
496
+ False,
497
+ provenance,
498
+ )
499
+ self.services.reject(
500
+ "PYC2860",
501
+ f"Comparison operand has unsupported representation: {category}",
502
+ self.services.nodes[operand_id],
503
+ )
504
+
505
+ def _reference(
506
+ self,
507
+ binding_id: str,
508
+ prefix: str,
509
+ owner: dict[str, Any],
510
+ ordinal: int,
511
+ origin_ids: tuple[str, ...],
512
+ ) -> CIdentifierRef:
513
+ return CIdentifierRef(
514
+ _sid(prefix, owner["node_id"], str(ordinal), *origin_ids),
515
+ binding_id,
516
+ self.services.synthetic_provenance(origin_ids, owner["node_id"]),
517
+ )
518
+
519
+ def _require_fact(
520
+ self,
521
+ node: dict[str, Any],
522
+ fact: dict[str, Any],
523
+ expected_kind: ConditionalRegionKind,
524
+ ) -> None:
525
+ fields = node.get("fields", {})
526
+ expected_operands = (
527
+ tuple(fields.get("values", ()))
528
+ if expected_kind is ConditionalRegionKind.BOOLEAN_SHORT_CIRCUIT
529
+ else (fields.get("left"), *tuple(fields.get("comparators", ())))
530
+ )
531
+ expected_operators = (
532
+ (fields.get("op"),)
533
+ if expected_kind is ConditionalRegionKind.BOOLEAN_SHORT_CIRCUIT
534
+ else tuple(fields.get("ops", ()))
535
+ )
536
+ code = (
537
+ "PYC2950"
538
+ if expected_kind is ConditionalRegionKind.BOOLEAN_SHORT_CIRCUIT
539
+ else "PYC2951"
540
+ )
541
+ if (
542
+ fact.get("region_node_id") != node.get("node_id")
543
+ or fact.get("region_kind") != expected_kind.value
544
+ or tuple(fact.get("operand_node_ids", ())) != expected_operands
545
+ or tuple(fact.get("operator_node_ids", ())) != expected_operators
546
+ or fact.get("lowering_shape") != CONDITIONAL_REGION_LOWERING_SHAPE
547
+ or fact.get("result_category") != "boolean-like"
548
+ or fact.get("result_c_type") != "bool"
549
+ or fact.get("operands_evaluated_once") is not True
550
+ ):
551
+ self.services.reject(code, "Conditional-region fact is incomplete or inconsistent", node)
552
+
553
+
554
+ def bind_conditional_region_lowerer(owner: Any) -> ConditionalRegionCIRLowerer:
555
+ """Bind the feature lowerer while keeping the cumulative lowerer small."""
556
+
557
+ return ConditionalRegionCIRLowerer(
558
+ ConditionalRegionLoweringServices(
559
+ nodes=owner.nodes,
560
+ categories=owner.categories,
561
+ regions=owner._optional_values(
562
+ "conditional-region-facts", "region_node_id"
563
+ ),
564
+ expression=owner._expression,
565
+ temporary=owner._temporary,
566
+ category_type=owner._category_type,
567
+ provenance=owner._prov,
568
+ synthetic_provenance=owner._synthetic_prov,
569
+ reject=owner._reject,
570
+ check_cancellation=owner._check_cancel,
571
+ )
572
+ )
573
+
574
+
575
+ __all__ = [
576
+ "ConditionalRegionCIRLowerer",
577
+ "ConditionalRegionLoweringServices",
578
+ "bind_conditional_region_lowerer",
579
+ ]