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,2 @@
1
+ from .renderer import CRenderer,RenderedC,SourceOutputMapping
2
+ from .conformance import CTextConformanceResult,validate_c_text
@@ -0,0 +1,297 @@
1
+ """Independent non-executing grammar check for PyCForge's emitted C subset.
2
+
3
+ This module intentionally does not import the C IR model or renderer. It lexes
4
+ and parses the rendered source as a separate acceptance boundary.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import re
10
+ from dataclasses import dataclass
11
+
12
+ from pycforge.converter.contracts.identifiers import (
13
+ C11_EXTERNAL_IDENTIFIERS as _C11_EXTERNAL_IDENTIFIERS,
14
+ C_KEYWORDS as _C_KEYWORDS,
15
+ TARGET_RESERVED_NAMES as _TARGET_RESERVED_NAMES,
16
+ )
17
+
18
+
19
+ _TOKEN = re.compile(
20
+ r"""
21
+ (?P<space>\s+)
22
+ | (?P<include>\#[ \t]*include[ \t]*(?:<[^>\n]+>|"[^"\n]+"))
23
+ | (?P<string>"(?:[^"\\\n]|\\(?:x[0-9A-Fa-f]+|[0-7]{1,3}|.))*")
24
+ | (?P<float>(?:(?:[0-9]+\.[0-9]*|\.[0-9]+)(?:[eE][+-]?[0-9]+)?|[0-9]+[eE][+-]?[0-9]+))
25
+ | (?P<int>(?:0|[1-9][0-9]*)(?:ULL|LLU|UL|LU|LL|L|U)?)
26
+ | (?P<identifier>[A-Za-z_][A-Za-z0-9_]*)
27
+ | (?P<operator>->|==|!=|<=|>=|<<|>>|&&|\|\||[{}\[\]();,.=*+\-/%<>!~&|^])
28
+ """,
29
+ re.VERBOSE,
30
+ )
31
+
32
+ _BASE_TYPES = {"void","char","int","long","int8_t","uint8_t","int16_t","uint16_t","int32_t","uint32_t","int64_t","uint64_t","bool","double"}
33
+ _QUALIFIERS = {"const", "volatile"}
34
+ _STORAGE = {"static", "extern"}
35
+ _REGISTERED_HEADERS = {"stdint.h", "stdbool.h"}
36
+ _STDINT_BASES = {"int8_t","uint8_t","int16_t","uint16_t","int32_t","uint32_t","int64_t","uint64_t"}
37
+ _BINARY_PRECEDENCE = {
38
+ "||": 1, "&&": 2, "|": 3, "^": 4, "&": 5,
39
+ "==": 6, "!=": 6, "<": 7, "<=": 7, ">": 7, ">=": 7,
40
+ "<<": 8, ">>": 8, "+": 9, "-": 9, "*": 10, "/": 10, "%": 10,
41
+ }
42
+
43
+
44
+ @dataclass(frozen=True, slots=True)
45
+ class CTextConformanceResult:
46
+ accepted: bool
47
+ message: str
48
+ token_count: int
49
+
50
+
51
+ class _ParseError(ValueError):
52
+ pass
53
+
54
+
55
+ class _Parser:
56
+ def __init__(self, tokens: tuple[str, ...]) -> None:
57
+ self.tokens = tokens
58
+ self.position = 0
59
+ self.headers: set[str] = set()
60
+ self.used_bases: set[str] = set()
61
+ self.record_types: set[str] = set()
62
+
63
+ def parse(self) -> None:
64
+ while self._peek().startswith("#"):
65
+ match = re.fullmatch(r"\#[ \t]*include[ \t]*<([^>\n]+)>", self._peek())
66
+ if not match or match.group(1) not in _REGISTERED_HEADERS:
67
+ raise _ParseError("unregistered or non-system include")
68
+ if match.group(1) in self.headers:
69
+ raise _ParseError(f"duplicate include: {match.group(1)}")
70
+ self.headers.add(match.group(1))
71
+ self.position += 1
72
+ while self._peek():
73
+ if self._peek().startswith("#"):
74
+ raise _ParseError("include outside translation-unit prefix")
75
+ self._external()
76
+ if self.used_bases & _STDINT_BASES and "stdint.h" not in self.headers:
77
+ raise _ParseError("fixed-width integer type without stdint.h")
78
+ if "bool" in self.used_bases and "stdbool.h" not in self.headers:
79
+ raise _ParseError("Boolean type or literal without stdbool.h")
80
+
81
+ def _external(self) -> None:
82
+ if self._peek() == "typedef":
83
+ self._record_definition()
84
+ return
85
+ self._storage()
86
+ self._type(allow_void=True)
87
+ self._identifier(file_scope=True)
88
+ if self._accept("("):
89
+ self._parameters()
90
+ self._expect(")")
91
+ if self._accept(";"):
92
+ return
93
+ self._block()
94
+ return
95
+ self._array_suffix()
96
+ if self._accept("="):
97
+ self._initializer()
98
+ self._expect(";")
99
+
100
+ def _record_definition(self) -> None:
101
+ self._expect("typedef")
102
+ self._expect("struct")
103
+ tag = self._identifier(file_scope=True)
104
+ self._expect("{")
105
+ fields: set[str] = set()
106
+ while self._peek() != "}":
107
+ if not self._peek():
108
+ raise _ParseError("unclosed record definition")
109
+ self._type()
110
+ field = self._identifier()
111
+ if field in fields:
112
+ raise _ParseError(f"duplicate record field: {field}")
113
+ fields.add(field)
114
+ self._expect(";")
115
+ if not fields:
116
+ raise _ParseError("empty record definition")
117
+ self._expect("}")
118
+ alias = self._identifier(file_scope=True)
119
+ if alias != tag:
120
+ raise _ParseError("record tag and typedef name must match")
121
+ if alias in self.record_types:
122
+ raise _ParseError(f"duplicate record type: {alias}")
123
+ self.record_types.add(alias)
124
+ self._expect(";")
125
+
126
+ def _parameters(self) -> None:
127
+ if self._peek() == ")":
128
+ raise _ParseError("empty C parameter list must be written as void")
129
+ if self._peek() == "void" and self._peek(1) == ")":
130
+ self.position += 1
131
+ return
132
+ first = True
133
+ while self._peek() != ")":
134
+ if not first:
135
+ self._expect(",")
136
+ self._type()
137
+ self._identifier()
138
+ first = False
139
+
140
+ def _block(self) -> None:
141
+ self._expect("{")
142
+ while self._peek() != "}":
143
+ if not self._peek():
144
+ raise _ParseError("unclosed block")
145
+ self._statement()
146
+ self._expect("}")
147
+
148
+ def _statement(self) -> None:
149
+ token = self._peek()
150
+ if token == "return":
151
+ self.position += 1
152
+ if self._peek() != ";": self._expression()
153
+ self._expect(";")
154
+ return
155
+ if token in {"break", "continue"}:
156
+ self.position += 1; self._expect(";"); return
157
+ if token == "if":
158
+ self.position += 1; self._expect("("); self._expression(); self._expect(")"); self._block()
159
+ if self._accept("else"): self._block()
160
+ return
161
+ if token == "while":
162
+ self.position += 1; self._expect("("); self._expression(); self._expect(")"); self._block(); return
163
+ if token == "for":
164
+ self.position += 1; self._expect("(")
165
+ self._storage(); self._type(); self._identifier(); self._expect("="); self._expression(); self._expect(";")
166
+ self._expression(); self._expect(";")
167
+ self._expression(); self._expect("="); self._expression(); self._expect(")"); self._block(); return
168
+ if self._declaration_start():
169
+ self._storage(); self._type(); self._identifier(); self._array_suffix()
170
+ if self._accept("="): self._initializer()
171
+ self._expect(";")
172
+ return
173
+ self._expression()
174
+ if self._accept("="): self._expression()
175
+ self._expect(";")
176
+
177
+ def _expression(self, minimum: int = 1) -> None:
178
+ self._unary()
179
+ while self._peek() in _BINARY_PRECEDENCE and _BINARY_PRECEDENCE[self._peek()] >= minimum:
180
+ precedence = _BINARY_PRECEDENCE[self._peek()]
181
+ self.position += 1
182
+ self._expression(precedence + 1)
183
+
184
+ def _unary(self) -> None:
185
+ if self._peek() in {"-", "+", "!", "~", "&", "*"}:
186
+ self.position += 1; self._unary(); return
187
+ self._primary()
188
+
189
+ def _primary(self) -> None:
190
+ token = self._peek()
191
+ if token == "(":
192
+ self.position += 1; self._expression(); self._expect(")")
193
+ elif token.startswith('"') or token in {"true", "false"} or re.fullmatch(r"(?:[0-9].*|\.[0-9].*)", token):
194
+ if token in {"true", "false"}: self.used_bases.add("bool")
195
+ self.position += 1
196
+ elif re.fullmatch(r"[A-Za-z_][A-Za-z0-9_]*", token):
197
+ self.position += 1
198
+ else:
199
+ raise _ParseError(f"expected expression, found {token or 'end of input'}")
200
+ while True:
201
+ if self._accept("("):
202
+ if self._peek() != ")":
203
+ self._expression()
204
+ while self._accept(","): self._expression()
205
+ self._expect(")")
206
+ elif self._accept("["):
207
+ self._expression(); self._expect("]")
208
+ elif self._accept(".") or self._accept("->"):
209
+ self._identifier()
210
+ else:
211
+ break
212
+
213
+ def _initializer(self) -> None:
214
+ if not self._accept("{"):
215
+ self._expression()
216
+ return
217
+ if self._peek() == "}":
218
+ raise _ParseError("empty initializer list")
219
+ self._expression()
220
+ while self._accept(","):
221
+ self._expression()
222
+ self._expect("}")
223
+
224
+ def _array_suffix(self) -> None:
225
+ while self._accept("["):
226
+ token = self._peek()
227
+ if not re.fullmatch(r"(?:0|[1-9][0-9]*)", token):
228
+ raise _ParseError("array extent must be an unsuffixed decimal integer")
229
+ self.position += 1
230
+ self._expect("]")
231
+
232
+ def _type(self, *, allow_void: bool = False) -> None:
233
+ while self._peek() in _QUALIFIERS: self.position += 1
234
+ base = self._peek()
235
+ if base not in _BASE_TYPES | self.record_types or (base == "void" and not allow_void):
236
+ raise _ParseError(f"expected C type, found {base or 'end of input'}")
237
+ self.used_bases.add(base)
238
+ self.position += 1
239
+ while self._accept("*"):
240
+ while self._peek() in _QUALIFIERS:
241
+ self.position += 1
242
+
243
+ def _storage(self) -> None:
244
+ if self._peek() in _STORAGE: self.position += 1
245
+
246
+ def _identifier(self, *, file_scope: bool = False) -> str:
247
+ token = self._peek()
248
+ reserved = _C_KEYWORDS | _TARGET_RESERVED_NAMES | _QUALIFIERS | _STORAGE
249
+ if file_scope:
250
+ reserved |= _C11_EXTERNAL_IDENTIFIERS
251
+ if not re.fullmatch(r"[A-Za-z_][A-Za-z0-9_]*", token) or token.startswith("_") or token in reserved:
252
+ raise _ParseError(f"expected identifier, found {token or 'end of input'}")
253
+ self.position += 1
254
+ return token
255
+
256
+ def _declaration_start(self) -> bool:
257
+ index = self.position
258
+ if self.tokens[index:index+1] and self.tokens[index] in _STORAGE: index += 1
259
+ while index < len(self.tokens) and self.tokens[index] in _QUALIFIERS: index += 1
260
+ return (
261
+ index < len(self.tokens)
262
+ and self.tokens[index] in _BASE_TYPES | self.record_types
263
+ and self.tokens[index] != "void"
264
+ )
265
+
266
+ def _peek(self, offset: int = 0) -> str:
267
+ index = self.position + offset
268
+ return self.tokens[index] if index < len(self.tokens) else ""
269
+
270
+ def _accept(self, token: str) -> bool:
271
+ if self._peek() == token:
272
+ self.position += 1
273
+ return True
274
+ return False
275
+
276
+ def _expect(self, token: str) -> None:
277
+ if not self._accept(token):
278
+ raise _ParseError(f"expected {token}, found {self._peek() or 'end of input'}")
279
+
280
+
281
+ def validate_c_text(text: str) -> CTextConformanceResult:
282
+ if text and not text.endswith("\n"):
283
+ return CTextConformanceResult(False, "missing final newline", 0)
284
+ position = 0
285
+ tokens: list[str] = []
286
+ while position < len(text):
287
+ match = _TOKEN.match(text, position)
288
+ if not match:
289
+ return CTextConformanceResult(False, f"unexpected character at byte {len(text[:position].encode('utf-8'))}", len(tokens))
290
+ position = match.end()
291
+ if match.lastgroup != "space":
292
+ tokens.append(match.group(0))
293
+ try:
294
+ _Parser(tuple(tokens)).parse()
295
+ except _ParseError as exc:
296
+ return CTextConformanceResult(False, str(exc), len(tokens))
297
+ return CTextConformanceResult(True, "accepted", len(tokens))
@@ -0,0 +1,220 @@
1
+ from __future__ import annotations
2
+ from dataclasses import dataclass
3
+ from pycforge.converter.ir.c_ir.model import *
4
+ from pycforge.converter.ir.c_ir.validation import validate_translation_unit
5
+
6
+ _PRECEDENCE={CBinaryOp.LOGICAL_OR:3,CBinaryOp.LOGICAL_AND:4,CBinaryOp.BIT_OR:5,CBinaryOp.BIT_XOR:6,CBinaryOp.BIT_AND:7,CBinaryOp.EQUAL:8,CBinaryOp.NOT_EQUAL:8,CBinaryOp.LESS:9,CBinaryOp.LESS_EQUAL:9,CBinaryOp.GREATER:9,CBinaryOp.GREATER_EQUAL:9,CBinaryOp.SHIFT_LEFT:10,CBinaryOp.SHIFT_RIGHT:10,CBinaryOp.ADD:11,CBinaryOp.SUBTRACT:11,CBinaryOp.MULTIPLY:12,CBinaryOp.DIVIDE:12,CBinaryOp.REMAINDER:12}
7
+ @dataclass(frozen=True,slots=True)
8
+ class SourceOutputMapping:
9
+ c_node_id:str; origin_kind:str; source_document_id:str|None; source_node_ids:tuple[str,...]; rule_plan_id:str|None; start_byte:int; end_byte:int; start_line:int; start_column:int; end_line:int; end_column:int
10
+ @dataclass(frozen=True,slots=True)
11
+ class RenderedC:
12
+ text:str; mappings:tuple[SourceOutputMapping,...]
13
+
14
+ class CRenderer:
15
+ def __init__(self)->None:self._parts=[];self._maps=[];self._line=1;self._column=0;self._byte=0
16
+ def _render_external(self,d:CExternalDeclaration)->None:
17
+ self._mapped(d.node_id,d.provenance,lambda:self._render_external_inner(d))
18
+ def _render_external_inner(self,d):
19
+ if isinstance(d,CRecordDefinition):
20
+ self._render_record_definition(d)
21
+ elif isinstance(d,CFunctionPrototype):
22
+ self._render_function_signature(d); self._write(";\n")
23
+ elif isinstance(d,CFunctionDefinition):
24
+ self._render_function_signature(d); self._write("\n"); self._render_block(d.body)
25
+ elif isinstance(d,CVariableDeclaration):self._render_var(d,0)
26
+ else:raise TypeError(type(d).__name__)
27
+ def _render_record_definition(self,d:CRecordDefinition)->None:
28
+ self._write("typedef struct "+d.identifier.spelling+" {\n")
29
+ for field in d.fields:
30
+ self._mapped(
31
+ field.node_id,
32
+ field.provenance,
33
+ lambda field=field:self._write(
34
+ " "+self._declarator(field.type_ref,field.identifier.spelling)+";\n"
35
+ ),
36
+ )
37
+ self._write("} "+d.identifier.spelling+";\n")
38
+ def _render_function_signature(self,d):
39
+ prefix="" if d.storage is CStorage.NONE else d.storage.value+" "
40
+ self._write(prefix+self._type(d.return_type)+" "+d.identifier.spelling+"(")
41
+ if not d.parameters:self._write("void")
42
+ for index,p in enumerate(d.parameters):
43
+ if index:self._write(", ")
44
+ self._mapped(p.node_id,p.provenance,lambda p=p:self._write(self._declarator(p.type_ref,p.identifier.spelling)))
45
+ self._write(")")
46
+ def _render_block(self,b:CBlock)->None:
47
+ self._mapped(b.node_id,b.provenance,lambda:self._render_block_inner(b))
48
+ def _render_block_inner(self,b):
49
+ self._write("{\n")
50
+ for s in b.statements:self._render_statement(s,1)
51
+ self._write("}\n")
52
+ def _render_statement(self,s:CStatement,indent:int)->None:
53
+ def inner():
54
+ self._write(" "*indent)
55
+ if isinstance(s,CVariableDeclaration):self._render_var(s,indent,leading=False)
56
+ elif isinstance(s,CExpressionStatement):
57
+ self._render_expr(s.expression); self._write(";\n")
58
+ elif isinstance(s,CReturnStatement):
59
+ self._write("return")
60
+ if s.expression is not None:
61
+ self._write(" "); self._render_expr(s.expression)
62
+ self._write(";\n")
63
+ elif isinstance(s,CAssignmentStatement):
64
+ self._render_expr(s.target); self._write(" = "); self._render_expr(s.value); self._write(";\n")
65
+ elif isinstance(s,CIfStatement):
66
+ self._write("if ("); self._render_expr(s.condition); self._write(")\n"); self._render_block_at(s.then_block,indent)
67
+ if s.else_block is not None:
68
+ self._write(" "*indent+"else\n"); self._render_block_at(s.else_block,indent)
69
+ elif isinstance(s,CWhileStatement):
70
+ self._write("while ("); self._render_expr(s.condition); self._write(")\n"); self._render_block_at(s.body,indent)
71
+ elif isinstance(s,CForStatement):
72
+ d=s.initializer
73
+ self._write("for (")
74
+ self._mapped(d.node_id,d.provenance,lambda:self._render_for_initializer(d))
75
+ self._write("; ")
76
+ self._render_expr(s.condition); self._write("; ")
77
+ self._mapped(s.update.node_id,s.update.provenance,lambda:self._render_assignment_fragment(s.update))
78
+ self._write(")\n")
79
+ self._render_block_at(s.body,indent)
80
+ elif isinstance(s,CBreakStatement): self._write("break;\n")
81
+ elif isinstance(s,CContinueStatement): self._write("continue;\n")
82
+ else:raise TypeError(type(s).__name__)
83
+ self._mapped(s.node_id,s.provenance,inner)
84
+ def _render_block_at(self,b:CBlock,indent:int)->None:
85
+ self._mapped(b.node_id,b.provenance,lambda:self._render_block_at_inner(b,indent))
86
+ def _render_block_at_inner(self,b,indent):
87
+ self._write(" "*indent+"{\n")
88
+ for st in b.statements:self._render_statement(st,indent+1)
89
+ self._write(" "*indent+"}\n")
90
+ def _render_var(self,d,indent,leading=True):
91
+ prefix="" if d.storage is CStorage.NONE else d.storage.value+" "
92
+ self._write(prefix+self._declarator(d.type_ref,d.identifier.spelling))
93
+ if d.initializer is not None:
94
+ self._write(" = "); self._render_expr(d.initializer)
95
+ self._write(";\n")
96
+ def _render_for_initializer(self,d:CVariableDeclaration)->None:
97
+ self._write(self._declarator(d.type_ref,d.identifier.spelling)+" = ")
98
+ self._render_expr(d.initializer)
99
+ def _render_assignment_fragment(self,s:CAssignmentStatement)->None:
100
+ self._render_expr(s.target); self._write(" = "); self._render_expr(s.value)
101
+ def _type(self,t:CType)->str:
102
+ q=" ".join(x.value for x in t.qualifiers)
103
+ base=(q+" " if q else "")+t.base
104
+ return base+(" " + "*"*t.pointer_depth if t.pointer_depth else "")
105
+ def _declarator(self,t:CType,name:str)->str:
106
+ suffix="".join(f"[{extent}]" for extent in t.array_extents)
107
+ rendered_type=self._type(t)
108
+ if t.object_const:
109
+ if t.pointer_depth:
110
+ return rendered_type+" const "+name+suffix
111
+ return "const "+rendered_type+" "+name+suffix
112
+ return rendered_type+" "+name+suffix
113
+ def _precedence(self,e:CExpression)->int:
114
+ if isinstance(e,(CIntegerLiteral,CFloatLiteral,CBooleanLiteral,CStringLiteral,CIdentifierRef,CInitializerList,CRecordInitializer)): return 15
115
+ if isinstance(e,(CCallExpr,CSubscriptExpr,CMemberAccessExpr)): return 14
116
+ if isinstance(e,CUnaryExpr): return 13
117
+ if isinstance(e,CBinaryExpr): return _PRECEDENCE[e.op]
118
+ raise TypeError(type(e).__name__)
119
+ def _render_expr(self,e:CExpression,parent:int=0,right_child:bool=False)->None:
120
+ prec=self._precedence(e)
121
+ paren=prec<parent or (right_child and prec==parent and isinstance(e,CBinaryExpr))
122
+ def inner():
123
+ if paren:self._write("(")
124
+ if isinstance(e,CIntegerLiteral):self._write(str(e.value)+e.suffix)
125
+ elif isinstance(e,CFloatLiteral):self._write(repr(e.value))
126
+ elif isinstance(e,CBooleanLiteral):self._write("true" if e.value else "false")
127
+ elif isinstance(e,CStringLiteral):self._write('"'+self._escape_string(e.value)+'"')
128
+ elif isinstance(e,CIdentifierRef):self._write(self._binding_spelling(e.binding_id))
129
+ elif isinstance(e,(CInitializerList,CRecordInitializer)):
130
+ self._write("{")
131
+ for i,item in enumerate(e.elements):
132
+ if i:self._write(", ")
133
+ self._render_expr(item)
134
+ self._write("}")
135
+ elif isinstance(e,CCallExpr):
136
+ self._render_expr(e.callee,14);self._write("(")
137
+ for i,a in enumerate(e.arguments):
138
+ if i:self._write(", ")
139
+ self._render_expr(a)
140
+ self._write(")")
141
+ elif isinstance(e,CSubscriptExpr):
142
+ self._render_expr(e.container,14);self._write("[");self._render_expr(e.index);self._write("]")
143
+ elif isinstance(e,CMemberAccessExpr):
144
+ self._render_expr(e.receiver,14)
145
+ self._write("." if e.mode is CMemberAccessMode.DIRECT else "->")
146
+ self._write(self._binding_spelling(e.field_binding_id))
147
+ elif isinstance(e,CUnaryExpr):
148
+ # Parenthesize a nested unary expression so two adjacent '-'
149
+ # tokens cannot be re-lexed by C as pre-decrement.
150
+ self._write(e.op.value);self._render_expr(e.operand,prec+1)
151
+ elif isinstance(e,CBinaryExpr):
152
+ self._render_expr(e.left,prec);self._write(f" {e.op.value} ");self._render_expr(e.right,prec,True)
153
+ else:raise TypeError(type(e).__name__)
154
+ if paren:self._write(")")
155
+ self._mapped(e.node_id,e.provenance,inner)
156
+ @staticmethod
157
+ def _escape_string(value:str)->str:
158
+ out=[]
159
+ for index,ch in enumerate(value):
160
+ code=ord(ch)
161
+ if ch=='\\': out.append('\\\\')
162
+ elif ch=='"': out.append('\\"')
163
+ # C11 performs trigraph replacement before tokenization. Escaping
164
+ # every question mark prevents text such as ??/ from becoming a
165
+ # backslash during translation phase 1.
166
+ elif ch=='?': out.append('\\?')
167
+ elif ch=='\n': out.append('\\n')
168
+ elif ch=='\r': out.append('\\r')
169
+ elif ch=='\t': out.append('\\t')
170
+ elif 32 <= code < 127: out.append(ch)
171
+ else:
172
+ encoded=ch.encode('utf-8')
173
+ next_is_hex=index+1<len(value) and value[index+1] in '0123456789abcdefABCDEF'
174
+ for byte_index,b in enumerate(encoded):
175
+ # C hexadecimal escapes consume an unbounded run of hex
176
+ # digits. Use a fixed-width octal escape for the final
177
+ # byte when the following source character is hexadecimal.
178
+ out.append(f"\\{b:03o}" if next_is_hex and byte_index+1==len(encoded) else f"\\x{b:02x}")
179
+ return ''.join(out)
180
+ def _binding_spelling(self,binding_id:str)->str:
181
+ # populated from validated declarations before expression rendering
182
+ return self._bindings[binding_id]
183
+ def _mapped(self,node_id,prov,fn):
184
+ start=(self._byte,self._line,self._column);fn();end=(self._byte,self._line,self._column)
185
+ self._maps.append(SourceOutputMapping(node_id,prov.origin_kind,prov.source_document_id,prov.source_node_ids,prov.rule_plan_id,start[0],end[0],start[1],start[2],end[1],end[2]))
186
+ def _write(self,text:str)->None:
187
+ self._parts.append(text);self._byte+=len(text.encode("utf-8"))
188
+ lines=text.split("\n")
189
+ if len(lines)>1:self._line+=len(lines)-1;self._column=len(lines[-1])
190
+ else:self._column+=len(text)
191
+ def render(self,unit:CTranslationUnit)->RenderedC:
192
+ valid=validate_translation_unit(unit)
193
+ if not valid.accepted:raise ValueError("invalid C IR: "+"; ".join(valid.errors))
194
+ self._bindings={}
195
+ for d in unit.declarations:
196
+ if isinstance(d,CRecordDefinition):
197
+ for field in d.fields:
198
+ self._bindings[field.identifier.binding_id]=field.identifier.spelling
199
+ elif isinstance(d,(CFunctionPrototype,CFunctionDefinition)):
200
+ self._bindings[d.identifier.binding_id]=d.identifier.spelling
201
+ for p in d.parameters:self._bindings[p.identifier.binding_id]=p.identifier.spelling
202
+ def collect(st):
203
+ if isinstance(st,CVariableDeclaration): self._bindings[st.identifier.binding_id]=st.identifier.spelling
204
+ elif isinstance(st,CIfStatement):
205
+ for x in st.then_block.statements: collect(x)
206
+ if st.else_block:
207
+ for x in st.else_block.statements: collect(x)
208
+ elif isinstance(st,(CWhileStatement,CForStatement)):
209
+ if isinstance(st,CForStatement): self._bindings[st.initializer.identifier.binding_id]=st.initializer.identifier.spelling
210
+ for x in st.body.statements: collect(x)
211
+ if isinstance(d,CFunctionDefinition):
212
+ for st in d.body.statements: collect(st)
213
+ elif isinstance(d,CVariableDeclaration):self._bindings[d.identifier.binding_id]=d.identifier.spelling
214
+ self._parts=[];self._maps=[];self._line=1;self._column=0;self._byte=0
215
+ for inc in unit.includes:self._mapped(inc.node_id,inc.provenance,lambda inc=inc:self._write(f"#include <{inc.header}>\n" if inc.system else f'#include "{inc.header}"\n'))
216
+ if unit.includes and unit.declarations:self._write("\n")
217
+ for index,decl in enumerate(unit.declarations):
218
+ self._render_external(decl)
219
+ if index+1<len(unit.declarations):self._write("\n")
220
+ return RenderedC("".join(self._parts),tuple(self._maps))
@@ -0,0 +1,71 @@
1
+ """Phase 14B conditional temporary-region analysis and lowering."""
2
+
3
+ from .analysis import ConditionalRegionAnalyzer
4
+ from .model import (
5
+ CONDITIONAL_REGION_KEY_DOMAIN,
6
+ CONDITIONAL_REGION_LOWERING_SHAPE,
7
+ CONDITIONAL_REGION_OBLIGATIONS,
8
+ CONDITIONAL_REGION_PROVENANCE_EVIDENCE,
9
+ CONDITIONAL_REGION_TABLE_DEPENDENCIES,
10
+ CONDITIONAL_REGION_TABLE_ID,
11
+ ConditionalEvaluationMode,
12
+ ConditionalGuardPolarity,
13
+ ConditionalOperandPlacementFact,
14
+ ConditionalRegionAnalysis,
15
+ ConditionalRegionAnalysisCanceled,
16
+ ConditionalRegionAnalysisError,
17
+ ConditionalRegionFact,
18
+ ConditionalRegionKind,
19
+ ConditionalRegionValidationCanceled,
20
+ conditional_region_id,
21
+ )
22
+
23
+ # validation is imported last so its independent reconstruction cannot become
24
+ # an accidental dependency of the producer or lowerer.
25
+ from .validation import validate_conditional_region_facts
26
+
27
+ _LOWERING_EXPORTS = {
28
+ "ConditionalRegionCIRLowerer",
29
+ "ConditionalRegionLoweringServices",
30
+ "bind_conditional_region_lowerer",
31
+ }
32
+
33
+
34
+ def __getattr__(name: str):
35
+ """Load the C-IR-facing half only when a lowering symbol is requested.
36
+
37
+ Importing conditional analysis or validation must not acquire C IR through
38
+ package initialization.
39
+ """
40
+
41
+ if name not in _LOWERING_EXPORTS:
42
+ raise AttributeError(name)
43
+ from . import lowering
44
+
45
+ value = getattr(lowering, name)
46
+ globals()[name] = value
47
+ return value
48
+
49
+ __all__ = [
50
+ "CONDITIONAL_REGION_KEY_DOMAIN",
51
+ "CONDITIONAL_REGION_LOWERING_SHAPE",
52
+ "CONDITIONAL_REGION_OBLIGATIONS",
53
+ "CONDITIONAL_REGION_PROVENANCE_EVIDENCE",
54
+ "CONDITIONAL_REGION_TABLE_DEPENDENCIES",
55
+ "CONDITIONAL_REGION_TABLE_ID",
56
+ "ConditionalEvaluationMode",
57
+ "ConditionalGuardPolarity",
58
+ "ConditionalOperandPlacementFact",
59
+ "ConditionalRegionAnalysis",
60
+ "ConditionalRegionAnalysisCanceled",
61
+ "ConditionalRegionAnalysisError",
62
+ "ConditionalRegionValidationCanceled",
63
+ "ConditionalRegionCIRLowerer",
64
+ "ConditionalRegionFact",
65
+ "ConditionalRegionKind",
66
+ "ConditionalRegionLoweringServices",
67
+ "ConditionalRegionAnalyzer",
68
+ "bind_conditional_region_lowerer",
69
+ "conditional_region_id",
70
+ "validate_conditional_region_facts",
71
+ ]