rocket-engine 0.0.2b1__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 (241) hide show
  1. rocket_engine/__init__.py +8 -0
  2. rocket_engine/__main__.py +12 -0
  3. rocket_engine/adapters/README.md +54 -0
  4. rocket_engine/adapters/__init__.py +7 -0
  5. rocket_engine/adapters/api.py +39 -0
  6. rocket_engine/adapters/base.py +46 -0
  7. rocket_engine/adapters/cace/__init__.py +163 -0
  8. rocket_engine/adapters/circulax/README.md +10 -0
  9. rocket_engine/adapters/circulax/__init__.py +112 -0
  10. rocket_engine/adapters/circulax/_driver.py +190 -0
  11. rocket_engine/adapters/circulax/adapter.py +538 -0
  12. rocket_engine/adapters/circulax/results.py +291 -0
  13. rocket_engine/adapters/cli/README.md +34 -0
  14. rocket_engine/adapters/cli/__init__.py +305 -0
  15. rocket_engine/adapters/connectivity/__init__.py +196 -0
  16. rocket_engine/adapters/connectivity/results.py +214 -0
  17. rocket_engine/adapters/cosim/README.md +9 -0
  18. rocket_engine/adapters/cosim/__init__.py +1390 -0
  19. rocket_engine/adapters/cosim/results.py +214 -0
  20. rocket_engine/adapters/devsim/__init__.py +171 -0
  21. rocket_engine/adapters/digital_flow/README.md +9 -0
  22. rocket_engine/adapters/digital_flow/__init__.py +1121 -0
  23. rocket_engine/adapters/digital_flow/results.py +227 -0
  24. rocket_engine/adapters/digital_flow/state.py +462 -0
  25. rocket_engine/adapters/drc/README.klayout.md +7 -0
  26. rocket_engine/adapters/drc/README.md +7 -0
  27. rocket_engine/adapters/drc/__init__.py +1 -0
  28. rocket_engine/adapters/drc/klayout.py +952 -0
  29. rocket_engine/adapters/drc/magic.py +287 -0
  30. rocket_engine/adapters/drc/results.py +184 -0
  31. rocket_engine/adapters/drc/results_kl.py +188 -0
  32. rocket_engine/adapters/elmer/__init__.py +163 -0
  33. rocket_engine/adapters/envelope.py +120 -0
  34. rocket_engine/adapters/equivalence/__init__.py +156 -0
  35. rocket_engine/adapters/errors.py +181 -0
  36. rocket_engine/adapters/extract_magic/README.md +15 -0
  37. rocket_engine/adapters/extract_magic/__init__.py +425 -0
  38. rocket_engine/adapters/extract_magic/results.py +209 -0
  39. rocket_engine/adapters/formal_sby/README.md +7 -0
  40. rocket_engine/adapters/formal_sby/__init__.py +773 -0
  41. rocket_engine/adapters/formal_sby/formal_edge.py +195 -0
  42. rocket_engine/adapters/formal_sby/results.py +185 -0
  43. rocket_engine/adapters/gdsfactory/__init__.py +194 -0
  44. rocket_engine/adapters/gdsfactory/_driver.py +459 -0
  45. rocket_engine/adapters/gdsfactory/adapter.py +609 -0
  46. rocket_engine/adapters/gdsfactory/results.py +181 -0
  47. rocket_engine/adapters/hdl_sim/README.md +7 -0
  48. rocket_engine/adapters/hdl_sim/__init__.py +369 -0
  49. rocket_engine/adapters/hdl_sim/ghdl.py +155 -0
  50. rocket_engine/adapters/hdl_sim/results.py +178 -0
  51. rocket_engine/adapters/hdl_sim/verilator.py +167 -0
  52. rocket_engine/adapters/http/README.md +15 -0
  53. rocket_engine/adapters/http/__init__.py +458 -0
  54. rocket_engine/adapters/http/events.py +63 -0
  55. rocket_engine/adapters/ipc/README.md +9 -0
  56. rocket_engine/adapters/ipc/__init__.py +111 -0
  57. rocket_engine/adapters/lvs_netgen/README.md +20 -0
  58. rocket_engine/adapters/lvs_netgen/__init__.py +889 -0
  59. rocket_engine/adapters/lvs_netgen/results.py +168 -0
  60. rocket_engine/adapters/mcp/README.md +19 -0
  61. rocket_engine/adapters/mcp/__init__.py +85 -0
  62. rocket_engine/adapters/mcp/api-schema.json +6107 -0
  63. rocket_engine/adapters/mcp/pull.py +246 -0
  64. rocket_engine/adapters/mcp/server.py +794 -0
  65. rocket_engine/adapters/meep/__init__.py +167 -0
  66. rocket_engine/adapters/meow/README.md +7 -0
  67. rocket_engine/adapters/meow/__init__.py +190 -0
  68. rocket_engine/adapters/meow/_driver.py +227 -0
  69. rocket_engine/adapters/meow/_envelope.py +108 -0
  70. rocket_engine/adapters/meow/adapter.py +491 -0
  71. rocket_engine/adapters/meow/results.py +218 -0
  72. rocket_engine/adapters/mutation/__init__.py +192 -0
  73. rocket_engine/adapters/ngspice/README.md +28 -0
  74. rocket_engine/adapters/ngspice/__init__.py +627 -0
  75. rocket_engine/adapters/ngspice/results.py +215 -0
  76. rocket_engine/adapters/postlayout/README.md +28 -0
  77. rocket_engine/adapters/postlayout/__init__.py +17 -0
  78. rocket_engine/adapters/postlayout/adapter.py +1062 -0
  79. rocket_engine/adapters/postlayout/results.py +330 -0
  80. rocket_engine/adapters/postlayout/translate.py +71 -0
  81. rocket_engine/adapters/profile.py +220 -0
  82. rocket_engine/adapters/sax/README.md +23 -0
  83. rocket_engine/adapters/sax/__init__.py +516 -0
  84. rocket_engine/adapters/sax/_driver.py +148 -0
  85. rocket_engine/adapters/sax/results.py +291 -0
  86. rocket_engine/adapters/stdio_console.py +1151 -0
  87. rocket_engine/adapters/syntax/README.md +5 -0
  88. rocket_engine/adapters/syntax/__init__.py +366 -0
  89. rocket_engine/adapters/syntax/results.py +181 -0
  90. rocket_engine/adapters/synth_yosys/README.md +7 -0
  91. rocket_engine/adapters/synth_yosys/__init__.py +655 -0
  92. rocket_engine/adapters/synth_yosys/amaranth.py +120 -0
  93. rocket_engine/adapters/synth_yosys/ghdl.py +128 -0
  94. rocket_engine/adapters/synth_yosys/results.py +173 -0
  95. rocket_engine/adapters/timing_sta/README.md +9 -0
  96. rocket_engine/adapters/timing_sta/__init__.py +726 -0
  97. rocket_engine/adapters/view_sync/__init__.py +185 -0
  98. rocket_engine/adapters/view_sync/results.py +189 -0
  99. rocket_engine/adapters/vlsir/__init__.py +142 -0
  100. rocket_engine/adapters/xschem/__init__.py +440 -0
  101. rocket_engine/adapters/xschem/deck.py +266 -0
  102. rocket_engine/clients/__init__.py +5 -0
  103. rocket_engine/clients/runtime.py +255 -0
  104. rocket_engine/composition.py +2279 -0
  105. rocket_engine/execution/README.md +30 -0
  106. rocket_engine/execution/__init__.py +150 -0
  107. rocket_engine/execution/apig/__init__.py +1 -0
  108. rocket_engine/execution/apig/pool.py +463 -0
  109. rocket_engine/execution/apig/submit.py +124 -0
  110. rocket_engine/execution/block_runs_scope.py +479 -0
  111. rocket_engine/execution/coalesce.py +414 -0
  112. rocket_engine/execution/console/__init__.py +1 -0
  113. rocket_engine/execution/console/apply_session.py +318 -0
  114. rocket_engine/execution/console/lifecycle.py +265 -0
  115. rocket_engine/execution/dispatch.py +205 -0
  116. rocket_engine/execution/errors.py +31 -0
  117. rocket_engine/execution/ports.py +797 -0
  118. rocket_engine/execution/pyexec/__init__.py +1 -0
  119. rocket_engine/execution/pyexec/apply_context.py +547 -0
  120. rocket_engine/execution/pyexec/approvals.py +173 -0
  121. rocket_engine/execution/pyexec/runner.py +404 -0
  122. rocket_engine/execution/pyexec/support.py +464 -0
  123. rocket_engine/execution/python_exec.py +897 -0
  124. rocket_engine/execution/queue.py +85 -0
  125. rocket_engine/execution/replay_safe.py +601 -0
  126. rocket_engine/execution/routing/__init__.py +1 -0
  127. rocket_engine/execution/routing/calls.py +364 -0
  128. rocket_engine/execution/routing/namespace.py +785 -0
  129. rocket_engine/execution/routing/ports.py +102 -0
  130. rocket_engine/execution/routing/tools.py +346 -0
  131. rocket_engine/execution/runner.py +311 -0
  132. rocket_engine/execution/runtime.py +1149 -0
  133. rocket_engine/execution/service.py +279 -0
  134. rocket_engine/execution/worker.py +464 -0
  135. rocket_engine/interfaces/README.md +26 -0
  136. rocket_engine/interfaces/__init__.py +58 -0
  137. rocket_engine/interfaces/apig_assets.py +407 -0
  138. rocket_engine/interfaces/celllink.py +461 -0
  139. rocket_engine/interfaces/documents.py +92 -0
  140. rocket_engine/interfaces/envelopes.py +131 -0
  141. rocket_engine/interfaces/errors.py +16 -0
  142. rocket_engine/interfaces/layout.py +410 -0
  143. rocket_engine/interfaces/layout_schema.yaml +278 -0
  144. rocket_engine/interfaces/paths.py +223 -0
  145. rocket_engine/interfaces/pdks.py +550 -0
  146. rocket_engine/interfaces/ports.py +72 -0
  147. rocket_engine/interfaces/references.py +115 -0
  148. rocket_engine/interfaces/refs.py +85 -0
  149. rocket_engine/interfaces/runparams.py +304 -0
  150. rocket_engine/interfaces/setup.py +552 -0
  151. rocket_engine/interfaces/setup_catalogue.yaml +243 -0
  152. rocket_engine/interfaces/setup_probes.py +66 -0
  153. rocket_engine/interfaces/vocabulary.py +58 -0
  154. rocket_engine/interfaces/yaml_dump.py +31 -0
  155. rocket_engine/library/README.md +22 -0
  156. rocket_engine/library/__init__.py +13 -0
  157. rocket_engine/library/apig_catalog.py +371 -0
  158. rocket_engine/library/catalog.py +185 -0
  159. rocket_engine/library/errors.py +16 -0
  160. rocket_engine/mutate/__init__.py +776 -0
  161. rocket_engine/mutate/operators.py +700 -0
  162. rocket_engine/ops/__init__.py +1 -0
  163. rocket_engine/ops/deferred.py +279 -0
  164. rocket_engine/ops/editors.py +404 -0
  165. rocket_engine/ops/evidence.py +194 -0
  166. rocket_engine/ops/gates.py +262 -0
  167. rocket_engine/ops/ports.py +76 -0
  168. rocket_engine/ops/queries.py +417 -0
  169. rocket_engine/ops/runs.py +553 -0
  170. rocket_engine/ops/submit.py +614 -0
  171. rocket_engine/project/README.md +27 -0
  172. rocket_engine/project/__init__.py +6 -0
  173. rocket_engine/project/canvas/__init__.py +55 -0
  174. rocket_engine/project/canvas/compile.py +109 -0
  175. rocket_engine/project/canvas/integrate.py +48 -0
  176. rocket_engine/project/canvas/schema.py +241 -0
  177. rocket_engine/project/canvas/templates.py +46 -0
  178. rocket_engine/project/canvas/validate.py +603 -0
  179. rocket_engine/project/edit/__init__.py +5 -0
  180. rocket_engine/project/edit/cas.py +87 -0
  181. rocket_engine/project/edit/transactions.py +282 -0
  182. rocket_engine/project/errors.py +16 -0
  183. rocket_engine/project/frontend13.py +685 -0
  184. rocket_engine/project/geometry/__init__.py +23 -0
  185. rocket_engine/project/geometry/ops.py +451 -0
  186. rocket_engine/project/geometry/snap.py +103 -0
  187. rocket_engine/project/ip/__init__.py +1 -0
  188. rocket_engine/project/ip/blocks.py +195 -0
  189. rocket_engine/project/ip/entry.schema.json +78 -0
  190. rocket_engine/project/ip/lifecycle.py +402 -0
  191. rocket_engine/project/ip/verbs.py +123 -0
  192. rocket_engine/project/layers/__init__.py +6 -0
  193. rocket_engine/project/layers/bindings.py +115 -0
  194. rocket_engine/project/layers/catalog.py +228 -0
  195. rocket_engine/project/layers/presets.py +43 -0
  196. rocket_engine/project/ledger/__init__.py +1 -0
  197. rocket_engine/project/ledger/docs.py +167 -0
  198. rocket_engine/project/ledger/entities.py +176 -0
  199. rocket_engine/project/ledger/events.py +29 -0
  200. rocket_engine/project/ledger/runs.py +257 -0
  201. rocket_engine/project/ledger/shared.py +174 -0
  202. rocket_engine/project/ledger/snapshots.py +385 -0
  203. rocket_engine/project/ledger/wires.py +552 -0
  204. rocket_engine/project/process/__init__.py +14 -0
  205. rocket_engine/project/process/model.py +193 -0
  206. rocket_engine/project/process/recipes.py +121 -0
  207. rocket_engine/project/schematic/__init__.py +1 -0
  208. rocket_engine/project/schematic/hierarchy.py +312 -0
  209. rocket_engine/project/schematic/instances.py +393 -0
  210. rocket_engine/project/schematic/sheets.py +198 -0
  211. rocket_engine/project/service.py +2941 -0
  212. rocket_engine/project/symbols/__init__.py +1 -0
  213. rocket_engine/project/symbols/cells.py +324 -0
  214. rocket_engine/project/symbols/linkage.py +356 -0
  215. rocket_engine/project/symbols/symbols.py +352 -0
  216. rocket_engine/project/templates/__init__.py +303 -0
  217. rocket_engine/project/templates/errors.py +25 -0
  218. rocket_engine/project/testbench/__init__.py +49 -0
  219. rocket_engine/project/testbench/blocks.py +619 -0
  220. rocket_engine/project/testbench/templates.py +134 -0
  221. rocket_engine/stores/README.md +12 -0
  222. rocket_engine/stores/__init__.py +6 -0
  223. rocket_engine/stores/ecs.py +1508 -0
  224. rocket_engine/stores/layers.py +1023 -0
  225. rocket_engine/stores/settings.py +1188 -0
  226. rocket_engine/stores/wireplace.py +1324 -0
  227. rocket_engine/workflows/README.md +18 -0
  228. rocket_engine/workflows/__init__.py +53 -0
  229. rocket_engine/workflows/capability_plan.py +102 -0
  230. rocket_engine/workflows/completion.py +228 -0
  231. rocket_engine/workflows/connectgraph.py +734 -0
  232. rocket_engine/workflows/connectgraph_emit.py +336 -0
  233. rocket_engine/workflows/gates.py +144 -0
  234. rocket_engine/workflows/photonic_wire.py +76 -0
  235. rocket_engine/workflows/stages.py +324 -0
  236. rocket_engine/workflows/viewsync.py +399 -0
  237. rocket_engine-0.0.2b1.dist-info/METADATA +200 -0
  238. rocket_engine-0.0.2b1.dist-info/RECORD +241 -0
  239. rocket_engine-0.0.2b1.dist-info/WHEEL +4 -0
  240. rocket_engine-0.0.2b1.dist-info/entry_points.txt +2 -0
  241. rocket_engine-0.0.2b1.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,5 @@
1
+ # Slang syntax adapter
2
+
3
+ SyntaxAdapter serves run_syntax with Slang. It runs lint-only mode over project sources and normalizes file, line, and column diagnostics. Errors fail the check; warnings are findings and do not fail it by themselves. A failed build with no parsed diagnostic is reported as an error rather than clean output.
4
+
5
+ The adapter is registered in Engine as the slang solver. Tool resolution uses PATH or an explicit job tool_dir, with a version-checked remedy when the binary is unavailable.
@@ -0,0 +1,366 @@
1
+ """Slang syntax solver adapter: run_syntax behind the ToolAdapter port.
2
+
3
+ Protocol, verified against slang 12.0.0: `slang --lint-only -Wextra`
4
+ over the sources, then parse `file:line:col: (error|warning):`
5
+ diagnostics plus the `Build ...: N errors, M warnings` summary line.
6
+ Exit code is trustworthy here (0 clean, 1 errors) but the verdict
7
+ still comes from parsed output: any parsed error is FAIL, warnings
8
+ never fail, and a failed build with no parsed diagnostics reports a
9
+ synthesized error rather than going clean.
10
+
11
+ Tool resolution: an explicit job `tool_dir` wins, else PATH. Where
12
+ the tools live on disk is B10 integration scope.
13
+ """
14
+
15
+ import json
16
+ import re
17
+ import shutil
18
+ import subprocess
19
+ from pathlib import Path
20
+
21
+ from ...execution.errors import ExecutionError
22
+ from ...interfaces import ToolAdapter
23
+ from ..errors import AdapterError
24
+
25
+ OPERATION = "run_syntax"
26
+ _TIMEOUT_S = 120
27
+ _DEFAULT_FLAGS = ["-Wextra"]
28
+ _DIAG_NAME = "diag.json"
29
+
30
+ _DIAG_RE = re.compile(r"^(.+?):(\d+):(\d+):\s*(error|warning):\s*(.*)$", re.M)
31
+ _RULE_RE = re.compile(r"\[(-W[\w-]+)\]\s*$")
32
+ _SUMMARY_RE = re.compile(r"^Build (succeeded|failed): (\d+) errors?, (\d+) warnings?\s*$", re.M)
33
+ _LOCATION_RE = re.compile(r"^(.*?):(\d+):(\d+)$")
34
+
35
+ #: Pinned slang --diag-json entry shape (verified slang 12.0.0):
36
+ #: [{severity, message, code, location: "f:l:c", ranges:
37
+ #: [{start: {file, line, column}, ...}], symbolPath}]. Counts
38
+ #: derive from array entries, never from the Build summary line.
39
+ _DIAG_SEVERITY = {"error": "error", "warning": "warning"}
40
+
41
+
42
+ def _diag_entry_location(entry):
43
+ """(file, line, col) for one pinned --diag-json entry.
44
+
45
+ Ranges carry the structured position; the flat `location`
46
+ string is the fallback. Returns (None, None, None) when
47
+ neither parses.
48
+
49
+ Args:
50
+ entry: One slang JSON diagnostic mapping.
51
+
52
+ Returns:
53
+ (file, line, col) triple with ints for line/col.
54
+ """
55
+ ranges = entry.get("ranges") or []
56
+ if ranges:
57
+ start = ranges[0].get("start") or {}
58
+ try:
59
+ return (
60
+ start.get("file"),
61
+ int(start.get("line")),
62
+ int(start.get("column")),
63
+ )
64
+ except (TypeError, ValueError):
65
+ pass
66
+ match = _LOCATION_RE.match(entry.get("location") or "")
67
+ if match is not None:
68
+ return match.group(1), int(match.group(2)), int(match.group(3))
69
+ return None, None, None
70
+
71
+
72
+ def _diag_entry_violation(entry):
73
+ """Map one pinned --diag-json entry to a v0 violation.
74
+
75
+ Notes surface as warnings (never fail); the slang `code`
76
+ (e.g. UndeclaredIdentifier) is the rule since JSON messages
77
+ carry no [-W...] suffix.
78
+
79
+ Args:
80
+ entry: One slang JSON diagnostic mapping.
81
+
82
+ Returns:
83
+ Violation with rule, severity, message, and located
84
+ file/line/col when the entry carries them.
85
+ """
86
+ severity = _DIAG_SEVERITY.get(entry.get("severity"), "warning")
87
+ violation = {
88
+ "rule": entry.get("code") or "syntax",
89
+ "severity": severity,
90
+ "message": entry.get("message") or "",
91
+ }
92
+ file, line, col = _diag_entry_location(entry)
93
+ if file is not None:
94
+ violation["file"] = file
95
+ if line is not None:
96
+ violation["line"] = line
97
+ if col is not None:
98
+ violation["col"] = col
99
+ return violation
100
+
101
+
102
+ class SyntaxAdapter(ToolAdapter):
103
+ """run_syntax via Slang: SystemVerilog lint with diagnostics.
104
+
105
+ Serves the run_syntax operation with deck.tool `slang` (the only
106
+ tool). Lints the sources and parses file:line:col diagnostics
107
+ plus the Build summary; errors are FAIL, warnings never fail.
108
+
109
+ Job keys:
110
+ sources (required): HDL source paths under lint.
111
+ slang_flags: Extra slang flags (default [-Wextra]).
112
+ timeout_s: Lint budget in seconds (default 120).
113
+ tool_dir: Directory holding slang; PATH otherwise.
114
+ deck: Optional mapping; deck.tool must be slang here.
115
+ workdir: Run directory (default ".").
116
+
117
+ Args:
118
+ runner: Optional isolated runner; direct subprocess when None.
119
+
120
+ Caution: A failed build with no parsed diagnostics reports a
121
+ synthesized error rather than going clean.
122
+ """
123
+
124
+ def __init__(self, runner=None):
125
+ self._runner = runner
126
+
127
+ def capabilities(self, target):
128
+ """Report the served operation and tool name.
129
+
130
+ Args:
131
+ target: Target the capabilities are reported for
132
+ (accepted, unused — one source set per job).
133
+
134
+ Returns:
135
+ Capability mapping with operations and tool.
136
+ """
137
+ return {"operations": [OPERATION], "tool": "slang"}
138
+
139
+ def preflight(self, job):
140
+ """Check operation, timeout, flags, sources, and slang.
141
+
142
+ Cheap deterministic checks first, tool resolution last.
143
+
144
+ Args:
145
+ job: Solver job with operation run_syntax plus
146
+ sources, flags, and timeout inputs.
147
+
148
+ Returns:
149
+ Checked mapping with the resolved slang path,
150
+ sources, flags, and timeout_s.
151
+
152
+ Raises:
153
+ AdapterError: unsupported_operation for another
154
+ operation, invalid_command for a bad timeout or
155
+ slang_flags, invalid_reference for missing sources,
156
+ missing_dependency for an unresolvable slang.
157
+ """
158
+ # Cheap deterministic checks first: operation, timeout, flags,
159
+ # sources. Tool resolution last, since it depends on the machine.
160
+ if job.get("operation") != OPERATION:
161
+ raise AdapterError(
162
+ "unsupported_operation",
163
+ f"Syntax handles {OPERATION}, got {job.get('operation')}.",
164
+ )
165
+ timeout = job.get("timeout_s", _TIMEOUT_S)
166
+ if (
167
+ not isinstance(timeout, (int, float))
168
+ or isinstance(timeout, bool)
169
+ or not timeout > 0
170
+ ):
171
+ raise AdapterError("invalid_command", f"Bad timeout_s: {timeout!r}.")
172
+ flags = job.get("slang_flags", list(_DEFAULT_FLAGS))
173
+ if not isinstance(flags, list) or not all(
174
+ isinstance(f, str) for f in flags
175
+ ):
176
+ raise AdapterError("invalid_command", f"Bad slang_flags: {flags!r}.")
177
+ sources = job.get("sources") or []
178
+ missing = [s for s in sources if not Path(s).is_file()]
179
+ if not sources or missing:
180
+ raise AdapterError(
181
+ "invalid_reference", f"Sources not found: {missing or sources}."
182
+ )
183
+ return {
184
+ "slang": self._resolve(job, "slang"),
185
+ "sources": list(sources),
186
+ "flags": list(flags),
187
+ "timeout_s": timeout,
188
+ }
189
+
190
+ def execute(self, job):
191
+ """Lint the sources with slang --lint-only.
192
+
193
+ Runs with `--diag-json diag.json` so diagnostics land in
194
+ a pinned JSON file first (MIGRATE-STDIO-FILE; schema
195
+ pinned at slang 12.0.0, see _diag_entry_violation).
196
+
197
+ Args:
198
+ job: Solver job with operation run_syntax plus
199
+ sources, flags, and timeout inputs.
200
+
201
+ Returns:
202
+ Native output with exit_code, stdout/stderr, the
203
+ linted sources, and the parsed diag entries
204
+ under `diag_json` (None when slang wrote none).
205
+
206
+ Raises:
207
+ AdapterError: invalid_reference for a missing workdir,
208
+ runner_unavailable on a slang timeout, plus the
209
+ preflight codes (execute re-checks first).
210
+ """
211
+ checked = self.preflight(job)
212
+ workdir = job.get("workdir", ".")
213
+ if not Path(workdir).is_dir():
214
+ raise AdapterError(
215
+ "invalid_reference", f"Workdir not found: {workdir}."
216
+ )
217
+ diag_path = Path(workdir) / _DIAG_NAME
218
+ proc = self._run(
219
+ [checked["slang"], "--lint-only", *checked["flags"],
220
+ "--diag-json", str(diag_path), *checked["sources"]],
221
+ workdir,
222
+ checked["timeout_s"],
223
+ )
224
+ try:
225
+ diag_json = json.loads(diag_path.read_text(encoding="utf-8"))
226
+ except (OSError, ValueError):
227
+ diag_json = None
228
+ return {
229
+ "exit_code": proc.returncode,
230
+ "stdout": proc.stdout,
231
+ "stderr": proc.stderr,
232
+ "sources": checked["sources"],
233
+ "diag_json": diag_json,
234
+ }
235
+
236
+ def parse(self, native_output):
237
+ """Parse slang diagnostics from JSON first, stdio second.
238
+
239
+ MIGRATE-STDIO-FILE: `--diag-json` entries are the parse
240
+ channel (counts derive from the array, never from the
241
+ Build summary line); the stdio regexes survive only as a
242
+ loud fallback for natives recorded before slang wrote
243
+ the file. A failed build with no parsed error
244
+ diagnostics synthesizes one rather than going clean;
245
+ warnings never fail.
246
+
247
+ Args:
248
+ native_output: Native output with slang stdout/stderr
249
+ plus the optional parsed `diag_json` entries.
250
+
251
+ Returns:
252
+ {total, violations[], source} with rule, severity,
253
+ message, and file/line/col locations; source is
254
+ `diag_json` or `stdio_fallback`.
255
+
256
+ Raises:
257
+ AdapterError: runner_unavailable when the output has
258
+ no diag entries and no Build summary line.
259
+ """
260
+ diag_json = native_output.get("diag_json")
261
+ if isinstance(diag_json, list):
262
+ # Counts derive from the array; [-W...] rule labels are
263
+ # recovered from stdio when it carries the same
264
+ # diagnostic (keeps the pinned rule grammar stable),
265
+ # else the slang code stands as the rule.
266
+ stdio_text = native_output.get("stdout", "") + "\n" + (
267
+ native_output.get("stderr", ""))
268
+ flag_by_loc = {}
269
+ for match in _DIAG_RE.finditer(stdio_text):
270
+ file, line, col, severity, message = match.groups()
271
+ flagged = _RULE_RE.search(message)
272
+ if flagged:
273
+ flag_by_loc[(file, int(line), int(col),
274
+ severity)] = flagged.group(1)
275
+ violations = []
276
+ for entry in diag_json:
277
+ if not isinstance(entry, dict):
278
+ continue
279
+ violation = _diag_entry_violation(entry)
280
+ key = (violation.get("file"), violation.get("line"),
281
+ violation.get("col"), violation.get("severity"))
282
+ if key in flag_by_loc:
283
+ violation["rule"] = flag_by_loc[key]
284
+ violations.append(violation)
285
+ errors = sum(1 for v in violations if v["severity"] == "error")
286
+ source = "diag_json"
287
+ fallback_summary = ""
288
+ else:
289
+ text = native_output.get("stdout", "") + "\n" + native_output.get(
290
+ "stderr", "")
291
+ violations = []
292
+ for match in _DIAG_RE.finditer(text):
293
+ file, line, col, severity, message = match.groups()
294
+ flagged = _RULE_RE.search(message)
295
+ violations.append(
296
+ {
297
+ "rule": flagged.group(1) if flagged else "syntax",
298
+ "severity": severity,
299
+ "message": message,
300
+ "file": file,
301
+ "line": int(line),
302
+ "col": int(col),
303
+ }
304
+ )
305
+ summary = _SUMMARY_RE.search(text)
306
+ if summary is None:
307
+ raise AdapterError(
308
+ "runner_unavailable",
309
+ "slang output has no Build summary line.",
310
+ )
311
+ errors = int(summary.group(2))
312
+ source = "stdio_fallback"
313
+ fallback_summary = summary.group(0)
314
+ if errors > 0 and not any(v["severity"] == "error" for v in violations):
315
+ # Defensive: a failed build must never parse as clean.
316
+ violations.append(
317
+ {
318
+ "rule": "syntax",
319
+ "severity": "error",
320
+ "message": fallback_summary or (
321
+ f"slang reported {errors} errors, none parsed."
322
+ ),
323
+ }
324
+ )
325
+ return {
326
+ "total": len(violations),
327
+ "violations": violations,
328
+ "source": source,
329
+ }
330
+
331
+ @staticmethod
332
+ def _resolve(job, binary):
333
+ tool_dir = job.get("tool_dir")
334
+ if tool_dir:
335
+ candidate = Path(tool_dir) / binary
336
+ if candidate.is_file():
337
+ return str(candidate)
338
+ raise AdapterError(
339
+ "missing_dependency", f"{binary} not found in {tool_dir}."
340
+ )
341
+ found = shutil.which(binary)
342
+ if found is None:
343
+ raise AdapterError(
344
+ "missing_dependency",
345
+ f"{binary} not on PATH (pass tool_dir or integrate via B10).",
346
+ )
347
+ return found
348
+
349
+ def _run(self, argv, workdir, timeout_s):
350
+ if self._runner is not None:
351
+ try:
352
+ return self._runner.run(argv, workdir, timeout_s)
353
+ except ExecutionError as exc:
354
+ raise AdapterError(exc.code, str(exc)) from exc
355
+ try:
356
+ return subprocess.run(
357
+ argv,
358
+ capture_output=True,
359
+ text=True,
360
+ timeout=timeout_s,
361
+ cwd=workdir,
362
+ )
363
+ except subprocess.TimeoutExpired as exc:
364
+ raise AdapterError(
365
+ "runner_unavailable", f"slang timed out after {timeout_s}s."
366
+ ) from exc
@@ -0,0 +1,181 @@
1
+ """Syntax v1 normalizer: v0 parse output to a ResultBundle.
2
+
3
+ Maps the ``run_syntax`` parse shape (``violations[]`` with rule,
4
+ severity, message, and file/line/col locations) onto a typed
5
+ diagnostics table, bound views, and findings. Pure function of
6
+ the parsed output: no I/O, no tool calls, deterministic ids
7
+ derived from the run id. Errors become error findings; warnings
8
+ never fail the verdict on their own.
9
+ """
10
+
11
+ TASK = "run_syntax"
12
+
13
+ _DIAG_CAP = 240
14
+
15
+ _SEVERITY = {"error": "error", "warning": "warning"}
16
+
17
+
18
+ def _cap(text):
19
+ """Cap one diagnostic string at the bundle prose limit.
20
+
21
+ Args:
22
+ text: Raw diagnostic text, possibly unbounded tool prose.
23
+
24
+ Returns:
25
+ The text unchanged when short, else truncated with an
26
+ ellipsis marker.
27
+ """
28
+ text = text if isinstance(text, str) else str(text)
29
+ if len(text) <= _DIAG_CAP:
30
+ return text
31
+ return text[: _DIAG_CAP - 3] + "..."
32
+
33
+
34
+ def _location(violation):
35
+ """Render one file:line:col location string for a diagnostic.
36
+
37
+ Args:
38
+ violation: v0 violation with optional file, line, col.
39
+
40
+ Returns:
41
+ Location text, or an empty string when unlocated.
42
+ """
43
+ parts = [violation.get("file") or ""]
44
+ if violation.get("line") is not None:
45
+ parts.append(str(violation["line"]))
46
+ if violation.get("col") is not None:
47
+ parts.append(str(violation["col"]))
48
+ if not parts[0]:
49
+ return ""
50
+ return ":".join(parts)
51
+
52
+
53
+ def _provenance(project_id, run_id, tools, provenance):
54
+ """Build ResultProvenance, defaulting to a placeholder closure.
55
+
56
+ Args:
57
+ project_id: Project the run belongs to.
58
+ run_id: Run the bundle describes.
59
+ tools: Tool ingredient mappings (id, version, digest?).
60
+ provenance: Full provenance mapping when the caller has
61
+ one; a deterministic placeholder otherwise.
62
+
63
+ Returns:
64
+ Provenance mapping valid against the v1 schema.
65
+ """
66
+ if provenance is not None:
67
+ return provenance
68
+ ref = {"scope": "project", "id": project_id, "revision": "r1"}
69
+ return {
70
+ "snapshot": dict(ref),
71
+ "input_lock": dict(ref),
72
+ "closure_hash": "0" * 64,
73
+ "tools": [
74
+ {
75
+ "id": item["id"],
76
+ "version": item.get("version", ""),
77
+ "digest": item.get("digest", "0" * 64),
78
+ }
79
+ for item in tools
80
+ ],
81
+ "parents": [],
82
+ }
83
+
84
+
85
+ def normalize(parsed, *, run_id, project_id="project", target=None,
86
+ tools=(), provenance=None):
87
+ """Normalize one syntax parse output to a v1 ResultBundle.
88
+
89
+ Diagnostic rows become a table dataset (rule, severity,
90
+ message, location) plus a bound table view; every diagnostic
91
+ also becomes a finding with a capped message.
92
+
93
+ Args:
94
+ parsed: v0 parse mapping with ``violations[]``.
95
+ run_id: Run the bundle describes (anchors all ids).
96
+ project_id: Project the run belongs to.
97
+ target: v1 target mapping; project kind by default.
98
+ tools: Tool ingredient mappings for provenance.
99
+ provenance: Full provenance mapping when the caller has
100
+ one; a deterministic placeholder otherwise.
101
+
102
+ Returns:
103
+ Schema-valid ResultBundle mapping.
104
+ """
105
+ violations = parsed.get("violations") or []
106
+ table_id = f"{run_id}:dataset:diagnostics"
107
+ datasets = [
108
+ {
109
+ "id": table_id,
110
+ "kind": "table",
111
+ "row_count": len(violations),
112
+ "columns": [
113
+ {
114
+ "id": f"{table_id}:rule",
115
+ "name": "rule",
116
+ "dtype": "utf8",
117
+ "unit": "1",
118
+ "data": [v.get("rule", "") for v in violations],
119
+ },
120
+ {
121
+ "id": f"{table_id}:severity",
122
+ "name": "severity",
123
+ "dtype": "utf8",
124
+ "unit": "1",
125
+ "data": [v.get("severity", "") for v in violations],
126
+ },
127
+ {
128
+ "id": f"{table_id}:message",
129
+ "name": "message",
130
+ "dtype": "utf8",
131
+ "unit": "1",
132
+ "data": [_cap(v.get("message", "")) for v in violations],
133
+ },
134
+ {
135
+ "id": f"{table_id}:location",
136
+ "name": "location",
137
+ "dtype": "utf8",
138
+ "unit": "1",
139
+ "data": [_location(v) for v in violations],
140
+ },
141
+ ],
142
+ "conditions": [],
143
+ }
144
+ ]
145
+ views = [
146
+ {
147
+ "id": f"{run_id}:view:diagnostics",
148
+ "kind": "table",
149
+ "dataset_id": table_id,
150
+ "name": "diagnostics",
151
+ }
152
+ ]
153
+ findings = [
154
+ {
155
+ "id": f"{run_id}:finding:{index}",
156
+ "severity": _SEVERITY.get(v.get("severity"), "info"),
157
+ "code": v.get("rule", "syntax"),
158
+ "message": _cap(v.get("message", "")),
159
+ }
160
+ for index, v in enumerate(violations)
161
+ ]
162
+ return {
163
+ "schema": "rocketeda/results/v1",
164
+ "ref": {"scope": "project", "id": run_id, "revision": "r1"},
165
+ "run_id": run_id,
166
+ "task": TASK,
167
+ "target": dict(target)
168
+ if target is not None
169
+ else {"project_id": project_id, "kind": "project"},
170
+ "state": "complete",
171
+ "freshness": "current",
172
+ "convergence": "not_applicable",
173
+ "provenance": _provenance(project_id, run_id, tools, provenance),
174
+ "datasets": datasets,
175
+ "views": views,
176
+ "measurements": [],
177
+ "findings": findings,
178
+ "artifacts": [],
179
+ "logs": [],
180
+ "gaps": [],
181
+ }
@@ -0,0 +1,7 @@
1
+ # Yosys synthesis adapter
2
+
3
+ YosysSynthAdapter serves run_synth. It reads the project RTL, checks and synthesizes the requested top, optionally maps cells using a supplied Liberty file, and returns normalized findings, cell counts, area when available, and the generated netlist artifact.
4
+
5
+ The adapter is registered in Engine as the yosys solver. Tool resolution uses PATH or an explicit job tool_dir and checks the Yosys version banner. A BwrapRunner can confine the process with tool roots read-only and a writable job directory.
6
+
7
+ Qualification covers counter fixtures, SERV, and picorv32, including mapped and generic runs. Warnings such as latches and undriven wires are surfaced as findings; broken RTL and missing tops fail loudly. See the [SYNTH-YOSYS report](../../../docs/work/SYNTH-YOSYS/report.md).