@typecad/cuttlefish 1.0.0-alpha.6 → 1.0.0-alpha.8

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 (187) hide show
  1. package/dist/api/config.d.ts +32 -0
  2. package/dist/api/index.d.ts +1 -1
  3. package/dist/api/schema/types.d.ts +15 -0
  4. package/dist/api/shared/async-runtime-static.d.ts +12 -1
  5. package/dist/api/shared/async-runtime-static.js +69 -24
  6. package/dist/api/shared/async-symbol-detector.d.ts +11 -0
  7. package/dist/api/shared/async-symbol-detector.js +140 -0
  8. package/dist/api/shared/async-types.d.ts +24 -0
  9. package/dist/api/shared/coop-scheduler.d.ts +60 -0
  10. package/dist/api/shared/coop-scheduler.js +149 -0
  11. package/dist/api/shared/display-adapter.d.ts +4 -3
  12. package/dist/api/shared/display-adapter.js +15 -118
  13. package/dist/api/shared/display-adapters/sdl.js +38 -31
  14. package/dist/api/shared/display-profile.d.ts +47 -25
  15. package/dist/api/shared/display-profile.js +31 -164
  16. package/dist/api/shared/framework-manifest-registry.d.ts +9 -0
  17. package/dist/api/shared/framework-manifest-registry.js +27 -0
  18. package/dist/api/shared/framework-manifest.d.ts +581 -0
  19. package/dist/api/shared/framework-manifest.js +182 -0
  20. package/dist/api/shared/glcdfont.d.ts +12 -0
  21. package/dist/api/shared/glcdfont.js +124 -0
  22. package/dist/api/shared/graphics-strategy.d.ts +37 -0
  23. package/dist/api/shared/hal-op-ir.d.ts +755 -1
  24. package/dist/api/shared/hal-op-ir.js +129 -1
  25. package/dist/api/shared/index.d.ts +20 -2
  26. package/dist/api/shared/index.js +25 -1
  27. package/dist/api/shared/ir-core.d.ts +4 -0
  28. package/dist/api/shared/ir-declarations.d.ts +6 -0
  29. package/dist/api/shared/native-display-op-resolver.d.ts +10 -0
  30. package/dist/api/shared/native-display-op-resolver.js +64 -0
  31. package/dist/api/shared/platform-strategy.d.ts +113 -2
  32. package/dist/api/shared/polyfill-helper-registry.js +18 -1
  33. package/dist/api/shared/promise-runtime.d.ts +1 -1
  34. package/dist/api/shared/promise-runtime.js +95 -13
  35. package/dist/api/shared/toolchain-types.d.ts +17 -0
  36. package/dist/api/shared/validate-framework-manifest.d.ts +28 -0
  37. package/dist/api/shared/validate-framework-manifest.js +494 -0
  38. package/dist/api/shared/worker-runtime-polyfill.d.ts +22 -0
  39. package/dist/api/shared/worker-runtime-polyfill.js +34 -0
  40. package/dist/api/shared/worker-runtime.d.ts +69 -0
  41. package/dist/api/shared/worker-runtime.js +163 -0
  42. package/dist/cli.js +186 -82
  43. package/dist/config-loader.d.ts +7 -2
  44. package/dist/config-loader.js +23 -3
  45. package/dist/config-schema.d.ts +112 -70
  46. package/dist/config-schema.js +14 -0
  47. package/dist/contract/board-generator.d.ts +17 -0
  48. package/dist/contract/board-generator.js +57 -0
  49. package/dist/contract/contract-parser.d.ts +217 -0
  50. package/dist/contract/contract-parser.js +224 -0
  51. package/dist/contract/index.d.ts +22 -0
  52. package/dist/contract/index.js +68 -0
  53. package/dist/create/board-codegen.js +4 -4
  54. package/dist/create/board-generators.js +4 -5
  55. package/dist/create/board-spec.d.ts +72 -75
  56. package/dist/create/board-spec.js +0 -1
  57. package/dist/create/init-scaffold.d.ts +9 -2
  58. package/dist/create/init-scaffold.js +0 -14
  59. package/dist/create/init-templates.d.ts +2 -0
  60. package/dist/create/init-templates.js +74 -8
  61. package/dist/create/init-wizard.js +31 -7
  62. package/dist/debug/preprocessor.js +178 -39
  63. package/dist/debug/types.d.ts +23 -0
  64. package/dist/diagnostics/mermaid-builder.d.ts +1 -1
  65. package/dist/diagnostics/mermaid-builder.js +34 -24
  66. package/dist/emit/compliance/arxml-writer.d.ts +11 -0
  67. package/dist/emit/compliance/arxml-writer.js +34 -0
  68. package/dist/emit/compliance/compliance-context.d.ts +57 -0
  69. package/dist/emit/compliance/compliance-context.js +113 -0
  70. package/dist/emit/compliance/deviation-ledger.d.ts +27 -0
  71. package/dist/emit/compliance/deviation-ledger.js +47 -0
  72. package/dist/emit/compliance/deviation-writer.d.ts +30 -0
  73. package/dist/emit/compliance/deviation-writer.js +37 -0
  74. package/dist/emit/compliance/index.d.ts +7 -0
  75. package/dist/emit/compliance/index.js +6 -0
  76. package/dist/emit/compliance/rule-engine.d.ts +13 -0
  77. package/dist/emit/compliance/rule-engine.js +101 -0
  78. package/dist/emit/compliance/rules.d.ts +16 -0
  79. package/dist/emit/compliance/rules.js +191 -0
  80. package/dist/emit/compliance/types.d.ts +59 -0
  81. package/dist/emit/compliance/types.js +8 -0
  82. package/dist/emit/cpp-emitter.js +4 -3
  83. package/dist/emit/emitters/class-emitter.js +6 -1
  84. package/dist/emit/emitters/emitter-context.d.ts +28 -1
  85. package/dist/emit/emitters/function-emitter-impl.js +153 -53
  86. package/dist/emit/emitters/line-appender.js +19 -0
  87. package/dist/emit/emitters/line-marker.d.ts +38 -0
  88. package/dist/emit/emitters/line-marker.js +39 -0
  89. package/dist/emit/emitters/output-finalizer.d.ts +6 -0
  90. package/dist/emit/emitters/output-finalizer.js +101 -12
  91. package/dist/emit/emitters/setup.d.ts +17 -0
  92. package/dist/emit/emitters/setup.js +257 -36
  93. package/dist/emit/emitters/top-level-prep.js +8 -0
  94. package/dist/emit/emitters/type-decl-emitter.js +28 -3
  95. package/dist/emit/emitters/ui-emitter.js +33 -9
  96. package/dist/emit/expression-renderer.d.ts +1 -1
  97. package/dist/emit/expression-renderer.js +91 -6
  98. package/dist/emit/route-hal-op.js +18 -5
  99. package/dist/emit/snprintf-helpers.js +15 -4
  100. package/dist/emit/statement-renderer.d.ts +10 -0
  101. package/dist/emit/statement-renderer.js +34 -5
  102. package/dist/emit/utils/async-state-machine.js +221 -125
  103. package/dist/emit/utils/hal-op-cpp-type.d.ts +6 -0
  104. package/dist/emit/utils/hal-op-cpp-type.js +40 -0
  105. package/dist/framework-package.js +2 -0
  106. package/dist/framework-registry.d.ts +17 -0
  107. package/dist/ir/adc-range-validation.js +14 -1
  108. package/dist/ir/build-ir-state.d.ts +1 -0
  109. package/dist/ir/build-ir-state.js +14 -0
  110. package/dist/ir/build-ir.js +9 -5
  111. package/dist/ir/call-graph.js +16 -0
  112. package/dist/ir/expression-to-ir.js +164 -0
  113. package/dist/ir/feature-registry.js +7 -25
  114. package/dist/ir/function-builder.js +22 -0
  115. package/dist/ir/hal/hal-emitter.d.ts +5 -2
  116. package/dist/ir/hal/hal-emitter.js +50 -18
  117. package/dist/ir/hal/hal-parser.d.ts +6 -0
  118. package/dist/ir/hal/hal-parser.js +81 -0
  119. package/dist/ir/hal/hal-plugins.js +723 -1
  120. package/dist/ir/identifier-collector.js +35 -0
  121. package/dist/ir/interrupt-analysis.d.ts +5 -1
  122. package/dist/ir/interrupt-analysis.js +8 -16
  123. package/dist/ir/network-validation.d.ts +4 -0
  124. package/dist/ir/network-validation.js +184 -0
  125. package/dist/ir/ownership-analysis.js +20 -1
  126. package/dist/ir/pin-capability-validation.js +37 -0
  127. package/dist/ir/pin-mode-validation.d.ts +2 -2
  128. package/dist/ir/pin-mode-validation.js +49 -18
  129. package/dist/ir/program-analysis.d.ts +65 -0
  130. package/dist/ir/program-analysis.js +318 -2
  131. package/dist/ir/render-expr.js +11 -0
  132. package/dist/ir/timing-validation.d.ts +6 -1
  133. package/dist/ir/timing-validation.js +60 -13
  134. package/dist/ir/transformers/call-statement.js +105 -0
  135. package/dist/ir/transformers/expressions.js +62 -0
  136. package/dist/ir/transformers/hal-call-resolver.js +21 -0
  137. package/dist/ir/transformers/hal-emit-helpers.js +1 -1
  138. package/dist/ir/transformers/namespace-methods.js +17 -12
  139. package/dist/ir/transformers/ui-reactive.js +2 -2
  140. package/dist/ir/transformers/variables.js +92 -3
  141. package/dist/ir/type-resolution.js +18 -0
  142. package/dist/ir/ui-element-auto-wire.js +7 -5
  143. package/dist/ir/utils/map-statements.d.ts +4 -0
  144. package/dist/ir/utils/map-statements.js +79 -0
  145. package/dist/ir/validation-orchestrator.js +9 -2
  146. package/dist/ir/worker-analysis.d.ts +10 -0
  147. package/dist/ir/worker-analysis.js +261 -0
  148. package/dist/libdef/c-to-decl.d.ts +27 -0
  149. package/dist/libdef/c-to-decl.js +397 -0
  150. package/dist/libdef/component-decls.d.ts +2 -0
  151. package/dist/libdef/component-decls.js +6 -0
  152. package/dist/libdef/component-discovery.d.ts +43 -0
  153. package/dist/libdef/component-discovery.js +83 -0
  154. package/dist/libdef/cpp-to-decl.d.ts +9 -0
  155. package/dist/libdef/cpp-to-decl.js +72 -0
  156. package/dist/libdef/registry.js +5 -2
  157. package/dist/lint-cache.d.ts +59 -0
  158. package/dist/lint-cache.js +257 -0
  159. package/dist/orchestrator/graph-builder.js +14 -9
  160. package/dist/platform/coop-scheduler-runtime.d.ts +19 -0
  161. package/dist/platform/coop-scheduler-runtime.js +41 -0
  162. package/dist/platform/generic-strategy.d.ts +15 -3
  163. package/dist/platform/generic-strategy.js +49 -4
  164. package/dist/safety/safety-bridge.d.ts +11 -0
  165. package/dist/safety/safety-bridge.js +48 -0
  166. package/dist/safety/sidecar-bridge.d.ts +5 -0
  167. package/dist/safety/sidecar-bridge.js +22 -0
  168. package/dist/safety-hook.d.ts +79 -0
  169. package/dist/safety-hook.js +35 -0
  170. package/dist/testing.d.ts +7 -1
  171. package/dist/testing.js +8 -1
  172. package/dist/transpile.d.ts +3 -0
  173. package/dist/transpile.js +175 -41
  174. package/dist/types.d.ts +13 -2
  175. package/dist/ui-hook.d.ts +17 -3
  176. package/dist/utils/cli.js +78 -4
  177. package/dist/utils/fs.d.ts +13 -0
  178. package/dist/utils/fs.js +50 -0
  179. package/package.json +24 -5
  180. package/dist/api/shared/display-adapters/eink-mono.d.ts +0 -2
  181. package/dist/api/shared/display-adapters/eink-mono.js +0 -53
  182. package/dist/api/shared/display-adapters/ssd1309.d.ts +0 -2
  183. package/dist/api/shared/display-adapters/ssd1309.js +0 -136
  184. package/dist/api/shared/display-adapters/st7796.d.ts +0 -2
  185. package/dist/api/shared/display-adapters/st7796.js +0 -132
  186. package/dist/licenses.d.ts +0 -185
  187. package/dist/licenses.js +0 -963
@@ -0,0 +1,261 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Worker Isolation Analysis (Phase 1/2)
3
+ //
4
+ // Models the safety property of the generalized worker-offload feature: worker
5
+ // functions run on a separate thread of control (a FreeRTOS task / Zephyr
6
+ // workqueue thread), so any shared mutable state between a worker and the main
7
+ // loop() is a real data race that the cooperative model otherwise eliminates.
8
+ //
9
+ // This pass mirrors interrupt-analysis.ts but splits the verdict by the surface
10
+ // a worker shares with main:
11
+ //
12
+ // (a) Shared GLOBALS — the worker writes a global the main loop reads (or
13
+ // both write it). The worker can run on a different core, so:
14
+ // - worker-written + main-read → promote to volatile + info diagnostic
15
+ // (mirrors the ISR volatile pass; the barrier contract makes the
16
+ // write visible, volatile stops the compiler caching the read).
17
+ // - worker-written + main-written (mutated by both) → warning:
18
+ // a true data race the barrier alone cannot fix.
19
+ //
20
+ // (b) Shared BUSES (I2C/SPI/UART/Wire/Serial) — a worker touching a bus is a
21
+ // HARD ERROR in v1. Rationale: the existing take()/release() bus-ownership
22
+ // mechanism is statically enforced (peripheral-ownership.ts) but runtime-
23
+ // UNIMPLEMENTED (take() is a no-op). A bus is a protocol-level conflict
24
+ // (two masters on the wire), so leaning on a lock that isn't there would
25
+ // be unsound. Workers are compute/pure-only in v1. When the take/release
26
+ // runtime backend lands (the named follow-on), this tier flips from
27
+ // error → allowed-by-take/release.
28
+ //
29
+ // Worker function identification: a worker function is one referenced by a
30
+ // worker.submit HAL op (expr.operation.operation === 'worker.submit'). The op
31
+ // carries fnRef (the C++ symbol), which we correlate to a program function by
32
+ // name. This mirrors how interrupt-analysis identifies ISRs via
33
+ // isInterruptHandler, but resolves through the HAL op instead of a callback
34
+ // flag (worker functions are plain functions, not callbacks).
35
+ // ---------------------------------------------------------------------------
36
+ import { collectAssignedNames, collectReadNames } from './interrupt-analysis.js';
37
+ import { walkProgramIR } from './utils/walk-ir.js';
38
+ // Bus-object prefixes a worker is forbidden from touching in v1. Mirrors the
39
+ // getBusName regex in peripheral-ownership.ts (I2C/SPI/UART/Wire/Serial).
40
+ const BUS_PREFIXES = ['I2C', 'SPI', 'UART', 'Wire', 'Serial'];
41
+ /** True if an identifier/callee string names a bus object (forbidden in workers). */
42
+ function isBusName(name) {
43
+ // Wire / Serial exactly, or I2C<n> / SPI<n> / UART<n> / Serial<n>.
44
+ if (name === 'Wire' || name === 'Serial')
45
+ return true;
46
+ for (const p of ['I2C', 'SPI', 'UART']) {
47
+ if (new RegExp(`^${p}\\d`).test(name))
48
+ return true;
49
+ }
50
+ if (/^Serial\d/.test(name))
51
+ return true;
52
+ return false;
53
+ }
54
+ /**
55
+ * Walk every expression in the program, invoking the visitor for worker.submit
56
+ * HAL ops. HAL ops appear as expressions whose `.operation.operation` is the
57
+ * op-kind string (mirrors the scan in program-analysis.ts:348).
58
+ */
59
+ function scanForWorkerSubmitOps(program, visit) {
60
+ walkProgramIR(program, (stmt) => {
61
+ walkExpressionsInStatementLocal(stmt, (expr) => {
62
+ if (expr && typeof expr === 'object'
63
+ && expr.operation && typeof expr.operation === 'object'
64
+ && expr.operation.operation === 'worker.submit') {
65
+ const fnRef = expr.operation.fnRef;
66
+ if (typeof fnRef === 'string')
67
+ visit(fnRef, expr.sourceSpan ?? expr.operation.sourceSpan);
68
+ }
69
+ });
70
+ });
71
+ }
72
+ /** Collect the set of bus object names referenced (read/called) in a body. */
73
+ function collectBusAccesses(stmts, hits) {
74
+ for (const stmt of stmts) {
75
+ collectBusAccessesInStatement(stmt, hits);
76
+ const nested = getNestedStatements(stmt);
77
+ if (nested)
78
+ collectBusAccesses(nested, hits);
79
+ }
80
+ }
81
+ function collectBusAccessesInStatement(stmt, hits) {
82
+ const s = stmt;
83
+ // Method calls like Wire.begin() / I2C0.writeByte(...): callee is "Wire.begin".
84
+ if (s.callee && typeof s.callee === 'string') {
85
+ const base = s.callee.split('.')[0];
86
+ if (isBusName(base))
87
+ hits.push({ name: base, span: s.sourceSpan });
88
+ }
89
+ // Identifier references (Wire, Serial, I2C0 used bare).
90
+ const visit = (expr) => {
91
+ if (expr && typeof expr === 'object' && expr.kind === 'identifier' && typeof expr.value === 'string') {
92
+ if (isBusName(expr.value))
93
+ hits.push({ name: expr.value, span: expr.sourceSpan });
94
+ }
95
+ };
96
+ walkExpressionsInStatementLocal(stmt, visit);
97
+ }
98
+ function walkExpressionsInStatementLocal(stmt, visitor) {
99
+ const s = stmt;
100
+ if (s.initializer && typeof s.initializer === 'object')
101
+ walkExpressionsInExpressionLocal(s.initializer, visitor);
102
+ if (s.value && typeof s.value === 'object' && 'kind' in s.value)
103
+ walkExpressionsInExpressionLocal(s.value, visitor);
104
+ if (s.condition && typeof s.condition === 'object')
105
+ walkExpressionsInExpressionLocal(s.condition, visitor);
106
+ if (s.expression && typeof s.expression === 'object' && 'kind' in s.expression)
107
+ walkExpressionsInExpressionLocal(s.expression, visitor);
108
+ if (Array.isArray(s.args))
109
+ for (const a of s.args)
110
+ walkExpressionsInExpressionLocal(a, visitor);
111
+ if (s.callee && typeof s.callee === 'object')
112
+ walkExpressionsInExpressionLocal(s.callee, visitor);
113
+ }
114
+ function walkExpressionsInExpressionLocal(expr, visitor) {
115
+ if (!expr || typeof expr !== 'object')
116
+ return;
117
+ visitor(expr);
118
+ for (const k of ['left', 'right', 'object', 'property', 'value', 'condition', 'whenTrue', 'whenFalse', 'expression', 'callee', 'target']) {
119
+ const v = expr[k];
120
+ if (v && typeof v === 'object' && 'kind' in v)
121
+ walkExpressionsInExpressionLocal(v, visitor);
122
+ }
123
+ if (Array.isArray(expr.args))
124
+ for (const a of expr.args)
125
+ walkExpressionsInExpressionLocal(a, visitor);
126
+ if (Array.isArray(expr.elements))
127
+ for (const a of expr.elements)
128
+ walkExpressionsInExpressionLocal(a, visitor);
129
+ }
130
+ function getNestedStatements(stmt) {
131
+ // Reuse the walk-ir helper if it exposes nesting; otherwise return undefined.
132
+ const s = stmt;
133
+ if (Array.isArray(s.body))
134
+ return s.body;
135
+ if (Array.isArray(s.statements))
136
+ return s.statements;
137
+ if (s.thenBlock && Array.isArray(s.thenBlock.statements))
138
+ return s.thenBlock.statements;
139
+ if (s.elseBlock && Array.isArray(s.elseBlock.statements))
140
+ return s.elseBlock.statements;
141
+ return undefined;
142
+ }
143
+ /** Find a function in program.functions by name (or originalName). */
144
+ function findFunctionByName(program, name) {
145
+ return program.functions.find((fn) => fn.name === name || fn.originalName === name);
146
+ }
147
+ /**
148
+ * Analyze worker isolation: (a) promote volatile for worker-shared globals,
149
+ * (b) flag shared-mutable globals, (c) hard-error on bus access in workers.
150
+ *
151
+ * Mutates the IR for (a) (sets isVolatile=true on qualifying var_decls) and
152
+ * pushes diagnostics for all three tiers. Returns the diagnostics it added.
153
+ */
154
+ export function analyzeWorkerIsolation(program, diagnostics) {
155
+ const added = [];
156
+ const push = (d) => { diagnostics.push(d); added.push(d); };
157
+ // 1. Identify worker-submitted functions via worker.submit HAL ops.
158
+ const workerFnNames = new Set();
159
+ scanForWorkerSubmitOps(program, (fnRef) => {
160
+ // fnRef is the C++ symbol; the IR function name is usually the same root.
161
+ workerFnNames.add(fnRef);
162
+ });
163
+ if (workerFnNames.size === 0)
164
+ return added;
165
+ // 2. Collect the bodies of worker functions present in this program.
166
+ const workerBodies = [];
167
+ for (const name of workerFnNames) {
168
+ const fn = findFunctionByName(program, name);
169
+ if (fn && Array.isArray(fn.statements))
170
+ workerBodies.push(fn.statements);
171
+ }
172
+ if (workerBodies.length === 0)
173
+ return added;
174
+ // ── Tier (b): bus access in workers → HARD ERROR ────────────────────────
175
+ for (let i = 0; i < workerFnNames.size; i++) {
176
+ const name = [...workerFnNames][i];
177
+ const fn = findFunctionByName(program, name);
178
+ if (!fn || !Array.isArray(fn.statements))
179
+ continue;
180
+ const busHits = [];
181
+ collectBusAccesses(fn.statements, busHits);
182
+ for (const hit of busHits) {
183
+ push({
184
+ severity: 'error',
185
+ message: `Worker function '${name}' accesses bus '${hit.name}'. Bus access is forbidden in worker functions because the take()/release() bus-ownership lock is not implemented at runtime — two threads would race on the wire despite validation passing.`,
186
+ hint: `Keep workers compute/pure-only. Bus-sharing workers require the take()/release() runtime backend (a planned follow-on).`,
187
+ filePath: (hit.span ?? fn.sourceSpan)?.filePath,
188
+ line: (hit.span ?? fn.sourceSpan)?.startLine,
189
+ column: (hit.span ?? fn.sourceSpan)?.startColumn,
190
+ code: 'worker-bus-access-forbidden',
191
+ source: 'worker-analysis',
192
+ });
193
+ }
194
+ }
195
+ // ── Tier (a): shared globals → volatile + warning ───────────────────────
196
+ const globalVars = new Map();
197
+ for (const stmt of program.topLevelStatements) {
198
+ if (stmt.kind === 'var_decl' && typeof stmt.name === 'string') {
199
+ globalVars.set(stmt.name, stmt);
200
+ }
201
+ }
202
+ if (globalVars.size === 0)
203
+ return added;
204
+ // Names assigned inside worker bodies.
205
+ const workerAssigned = new Set();
206
+ for (const body of workerBodies)
207
+ collectAssignedNames(body, workerAssigned);
208
+ if (workerAssigned.size === 0)
209
+ return added;
210
+ // Names assigned AND read in main (non-worker) code.
211
+ const mainAssigned = new Set();
212
+ const mainRead = new Set();
213
+ for (const stmt of program.topLevelStatements) {
214
+ collectReadNames([stmt], mainRead);
215
+ collectAssignedNames([stmt], mainAssigned);
216
+ }
217
+ for (const fn of program.functions) {
218
+ // Skip the worker functions themselves when collecting main usage.
219
+ if (workerFnNames.has(fn.name) || workerFnNames.has(fn.originalName))
220
+ continue;
221
+ if (Array.isArray(fn.statements)) {
222
+ collectReadNames(fn.statements, mainRead);
223
+ collectAssignedNames(fn.statements, mainAssigned);
224
+ }
225
+ }
226
+ for (const [name, stmt] of globalVars) {
227
+ if (!workerAssigned.has(name))
228
+ continue;
229
+ const workerShared = stmt;
230
+ if (mainRead.has(name) && !mainAssigned.has(name)) {
231
+ // worker-written + main-read → promote to volatile (the barrier makes the
232
+ // write visible; volatile stops the compiler caching the read).
233
+ if (!workerShared.isVolatile) {
234
+ workerShared.isVolatile = true;
235
+ push({
236
+ severity: 'info',
237
+ message: `'${name}' is written in a worker function and read in main code — emitted as \`volatile\` so the compiler does not cache it in a register across the worker/main boundary.`,
238
+ filePath: workerShared.sourceSpan?.filePath,
239
+ line: workerShared.sourceSpan?.startLine,
240
+ column: workerShared.sourceSpan?.startColumn,
241
+ code: 'volatile-worker-shared',
242
+ source: 'worker-analysis',
243
+ });
244
+ }
245
+ }
246
+ else if (mainAssigned.has(name)) {
247
+ // worker-written + main-written → true data race; barrier alone can't fix.
248
+ push({
249
+ severity: 'warning',
250
+ message: `'${name}' is written by both a worker function and main-thread code — a data race the worker completion barrier alone cannot fix. Restructure so only one side writes it (e.g. the worker writes; main only reads).`,
251
+ hint: `Move all writes to '${name}' into the worker (or into main); do not mutate it from both.`,
252
+ filePath: workerShared.sourceSpan?.filePath,
253
+ line: workerShared.sourceSpan?.startLine,
254
+ column: workerShared.sourceSpan?.startColumn,
255
+ code: 'worker-shared-mutable',
256
+ source: 'worker-analysis',
257
+ });
258
+ }
259
+ }
260
+ return added;
261
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * C-header to TypeScript declaration generator.
3
+ *
4
+ * Complements cpp-to-decl.ts (which is class-focused). ESP-IDF components
5
+ * are mostly C: free functions, opaque handles, typedef'd enums and structs.
6
+ *
7
+ * EMISSION POLICY — names match the C header 1-to-1.
8
+ *
9
+ * esp_err_t esp_wifi_init(const wifi_config_t *config);
10
+ *
11
+ * becomes
12
+ *
13
+ * export declare function esp_wifi_init(config: number): esp_err_t;
14
+ *
15
+ * not `esp_wifi.init(...)`. ESP-IDF examples call `esp_wifi_init`, never
16
+ * `esp_wifi.init`; the dotted form has no C++ representation (there is no
17
+ * `esp_wifi` object or namespace in the real header) and would not link.
18
+ * Mirroring the C names verbatim means the transpiler lowers TS calls
19
+ * directly to valid C with zero translation.
20
+ *
21
+ * Spec: docs/superpowers/specs/2026-07-19-demo-wifi-design.md
22
+ */
23
+ /**
24
+ * Top-level entry: read a header file, write `<header>.d.ts` alongside it.
25
+ * Returns the output path, or null if no declarations could be extracted.
26
+ */
27
+ export declare function generateCDecl(filePath: string, outputPath?: string): string | null;
@@ -0,0 +1,397 @@
1
+ /**
2
+ * C-header to TypeScript declaration generator.
3
+ *
4
+ * Complements cpp-to-decl.ts (which is class-focused). ESP-IDF components
5
+ * are mostly C: free functions, opaque handles, typedef'd enums and structs.
6
+ *
7
+ * EMISSION POLICY — names match the C header 1-to-1.
8
+ *
9
+ * esp_err_t esp_wifi_init(const wifi_config_t *config);
10
+ *
11
+ * becomes
12
+ *
13
+ * export declare function esp_wifi_init(config: number): esp_err_t;
14
+ *
15
+ * not `esp_wifi.init(...)`. ESP-IDF examples call `esp_wifi_init`, never
16
+ * `esp_wifi.init`; the dotted form has no C++ representation (there is no
17
+ * `esp_wifi` object or namespace in the real header) and would not link.
18
+ * Mirroring the C names verbatim means the transpiler lowers TS calls
19
+ * directly to valid C with zero translation.
20
+ *
21
+ * Spec: docs/superpowers/specs/2026-07-19-demo-wifi-design.md
22
+ */
23
+ import fs from 'node:fs';
24
+ import path from 'node:path';
25
+ import { stripPreprocessorBlocks } from './header-parser.js';
26
+ const BUILTIN_TS_TYPES = new Set(['number', 'string', 'boolean', 'void', 'any']);
27
+ /**
28
+ * Map a C type string to a TS type string. Pointer types become `number`
29
+ * (handles/opaque addresses) since the transpiler treats C pointers as
30
+ * numbers anyway. Unknown named types are returned as-is so they resolve
31
+ * to a typedef alias emitted by this same header (or fall back to `number`
32
+ * via the alias-emission pass in generateCDecl).
33
+ */
34
+ function mapCTypeToTs(cType) {
35
+ let t = cType.trim();
36
+ // Strip leading `const`/`volatile` qualifiers — they don't affect the TS shape.
37
+ t = t.replace(/^(?:const|volatile)\s+/, '');
38
+ const typeMap = {
39
+ int: 'number',
40
+ 'unsigned int': 'number',
41
+ 'unsigned': 'number',
42
+ 'short': 'number',
43
+ 'unsigned short': 'number',
44
+ 'long': 'number',
45
+ 'unsigned long': 'number',
46
+ 'long long': 'number',
47
+ 'unsigned long long': 'number',
48
+ uint8_t: 'number',
49
+ uint16_t: 'number',
50
+ uint32_t: 'number',
51
+ uint64_t: 'number',
52
+ int8_t: 'number',
53
+ int16_t: 'number',
54
+ int32_t: 'number',
55
+ int64_t: 'number',
56
+ size_t: 'number',
57
+ ssize_t: 'number',
58
+ intptr_t: 'number',
59
+ uintptr_t: 'number',
60
+ float: 'number',
61
+ double: 'number',
62
+ bool: 'boolean',
63
+ _Bool: 'boolean',
64
+ void: 'void',
65
+ char: 'number',
66
+ };
67
+ if (typeMap[t])
68
+ return typeMap[t];
69
+ // Strip a trailing pointer; pointers are number-typed handles/addresses.
70
+ if (t.endsWith('*'))
71
+ return 'number';
72
+ return t;
73
+ }
74
+ /** Parse function signatures like `esp_err_t foo(int x, const char *y);`. */
75
+ function parseFunctions(stripped) {
76
+ const fns = [];
77
+ const fnRegex = /^([\w\s\*]+?)\s+(\w+)\s*\(([^;]*)\)\s*;/gm;
78
+ let m;
79
+ while ((m = fnRegex.exec(stripped)) !== null) {
80
+ const returnType = m[1].trim();
81
+ const name = m[2];
82
+ const paramsRaw = m[3].trim();
83
+ // Skip non-function declarations these regexes might catch.
84
+ if (returnType === 'typedef' || returnType === 'struct' || returnType === 'enum')
85
+ continue;
86
+ if (returnType === 'static' || returnType === 'extern' || returnType === 'inline')
87
+ continue;
88
+ if (name === 'if' || name === 'for' || name === 'while' || name === 'return' || name === 'switch')
89
+ continue;
90
+ fns.push({ returnType, name, params: parseParams(paramsRaw) });
91
+ }
92
+ return fns;
93
+ }
94
+ function parseParams(raw) {
95
+ if (!raw || raw === 'void')
96
+ return [];
97
+ return raw.split(',').map((part, idx) => {
98
+ let trimmed = part.trim();
99
+ trimmed = trimmed.split('=')[0].trim();
100
+ if (!trimmed)
101
+ return { type: '', name: `_arg${idx}` };
102
+ // C array declarator on a parameter: `<type> <name>[<size>]` or `<type> <name>[]`.
103
+ // Normalize: the array decays to a pointer in C anyway, so we record the
104
+ // base type and the original name. e.g. `uint8_t mac[6]` → type="uint8_t",
105
+ // name="mac"; `uint8_t mac[]` → same.
106
+ const arrParam = trimmed.match(/^(.*?)\b(\w+)\s*\[[^\]]*\]\s*$/);
107
+ if (arrParam) {
108
+ const type = arrParam[1].trim();
109
+ // If the type is empty (e.g. bare `mac[6]` with no preceding type),
110
+ // fall through to the default parser rather than emitting a bogus type.
111
+ if (type)
112
+ return { type, name: arrParam[2] };
113
+ }
114
+ // A parameter is `<type> <name>` where name is the trailing identifier
115
+ // and type is everything before. For function-pointer params (rare in
116
+ // user-facing IDF APIs) we fall back to treating the whole thing as a
117
+ // type with a synthesized name.
118
+ if (trimmed.includes('(*)')) {
119
+ return { type: trimmed, name: `_arg${idx}` };
120
+ }
121
+ const m = trimmed.match(/^(.*?)(\b\w+)$/);
122
+ if (!m)
123
+ return { type: trimmed, name: `_arg${idx}` };
124
+ let type = m[1].trim();
125
+ const name = m[2];
126
+ // If the regex left the * glued to the name, pull it back to the type.
127
+ if (!type && trimmed.includes('*')) {
128
+ type = '*';
129
+ }
130
+ return { type: type || trimmed, name };
131
+ });
132
+ }
133
+ /** Parse typedef'd enums: `typedef enum { A, B=2, C } foo_t;`. */
134
+ function parseEnumTypedefs(stripped) {
135
+ const tds = [];
136
+ const re = /typedef\s+enum\s*(?:\w+\s*)?\{([^}]*)\}\s*(\w+)\s*;/g;
137
+ let m;
138
+ while ((m = re.exec(stripped)) !== null) {
139
+ const body = m[1];
140
+ const name = m[2];
141
+ const members = body.split(',').map((s) => s.trim()).filter(Boolean);
142
+ let next = 0;
143
+ const values = members.map((mem) => {
144
+ const eq = mem.indexOf('=');
145
+ if (eq >= 0) {
146
+ const val = parseInt(mem.slice(eq + 1).trim(), 10);
147
+ if (!Number.isNaN(val))
148
+ next = val + 1;
149
+ return { name: mem.slice(0, eq).trim(), value: Number.isNaN(val) ? undefined : val };
150
+ }
151
+ const v = next++;
152
+ return { name: mem, value: v };
153
+ });
154
+ tds.push({ kind: 'enum', name, values });
155
+ }
156
+ return tds;
157
+ }
158
+ /** Parse typedef'd structs: `typedef struct { int x; } foo_t;` or
159
+ * `typedef struct foo { int x; } foo_t;`. Handles array fields
160
+ * (`int arr[6]`) by recording the array-ness on the field. */
161
+ function parseStructTypedefs(stripped) {
162
+ const tds = [];
163
+ const re = /typedef\s+struct\s*(?:\w+\s*)?\{([^}]*)\}\s*(\w+)\s*;/g;
164
+ let m;
165
+ while ((m = re.exec(stripped)) !== null) {
166
+ const body = m[1];
167
+ const name = m[2];
168
+ const fields = body
169
+ .split(';')
170
+ .map((s) => s.trim())
171
+ .filter(Boolean)
172
+ .map((field, idx) => {
173
+ // Array field: `<type> <name>[<size>]` → name + isArray.
174
+ const arr = field.match(/^(.*?)\b(\w+)\s*\[[^\]]*\]\s*$/);
175
+ if (arr) {
176
+ return { type: arr[1].trim() || field, name: arr[2], isArray: true };
177
+ }
178
+ const pm = field.match(/^(.*?)(\b\w+)$/);
179
+ if (!pm)
180
+ return { type: field, name: `_f${idx}`, isArray: false };
181
+ return { type: pm[1].trim() || field, name: pm[2], isArray: false };
182
+ });
183
+ tds.push({ kind: 'struct', name, fields });
184
+ }
185
+ return tds;
186
+ }
187
+ /** Parse opaque handle typedefs: `typedef struct foo *foo_handle_t;`. */
188
+ function parseOpaqueTypedefs(stripped) {
189
+ const tds = [];
190
+ const re = /typedef\s+struct\s+\w+\s*\*\s*(\w+)\s*;/g;
191
+ let m;
192
+ while ((m = re.exec(stripped)) !== null) {
193
+ tds.push({ kind: 'opaque', name: m[1] });
194
+ }
195
+ return tds;
196
+ }
197
+ /** Parse function-pointer typedefs: `typedef void (*handler_t)(void *arg);`.
198
+ * Emits as `any` — TS has no faithful representation of a C function pointer,
199
+ * and IDF user code that registers one needs `rawCpp()` anyway. */
200
+ function parseFuncPtrTypedefs(stripped) {
201
+ const tds = [];
202
+ const re = /typedef\s+[\w\s\*]+?\(\s*\*\s*(\w+)\s*\)\s*\([^;]*\)\s*;/g;
203
+ let m;
204
+ while ((m = re.exec(stripped)) !== null) {
205
+ tds.push({ kind: 'funcptr', name: m[1] });
206
+ }
207
+ return tds;
208
+ }
209
+ /** Parse plain alias typedefs: `typedef int esp_err_t;`, `typedef uint32_t foo_t;`.
210
+ * These are extremely common in ESP-IDF (esp_err_t, TickType_t, etc.). */
211
+ function parseAliasTypedefs(stripped) {
212
+ const tds = [];
213
+ // `typedef <type> <name>;` where <type> is a single token (possibly with
214
+ // qualifiers) and <name> is the alias. Struct/enum/funcptr typedefs are
215
+ // handled by their own parsers, so exclude those keywords here.
216
+ const re = /typedef\s+(?!struct\b)(?!enum\b)(?!union\b)([\w\s\*]+?)\s+(\w+)\s*;/g;
217
+ let m;
218
+ while ((m = re.exec(stripped)) !== null) {
219
+ const aliasedType = m[1].trim();
220
+ const name = m[2];
221
+ // Skip if this is actually the tail of a struct/enum/funcptr typedef that
222
+ // a sibling parser already consumed (defensive — the negative lookahead
223
+ // should already exclude those, but the regex can still match fragments
224
+ // inside a `typedef struct { ... } foo_t;` body in edge cases).
225
+ if (aliasedType === '' || aliasedType.includes('{'))
226
+ continue;
227
+ // Skip array typedefs: `typedef uint8_t mac[6];` is NOT a plain alias.
228
+ // It would parse as aliasedType="uint8_t" name="mac" with the regex, but
229
+ // the trailing `[6]` between name and `;` makes it an array typedef — the
230
+ // regex's `\s*;` lookahead fails to match `[6];`, so this branch only
231
+ // fires for true plain aliases. Defensive: bail if the captured aliasedType
232
+ // somehow contains a `[` (would indicate a parse fragment).
233
+ if (aliasedType.includes('['))
234
+ continue;
235
+ tds.push({ kind: 'alias', name, aliasedType });
236
+ }
237
+ return tds;
238
+ }
239
+ /** Parse `#define FOO 42` and `#define BAR "str"` (integers and strings only). */
240
+ function parseSimpleDefines(content) {
241
+ const out = [];
242
+ const re = /^\s*#define\s+(\w+)\s+(.+?)\s*$/gm;
243
+ let m;
244
+ while ((m = re.exec(content)) !== null) {
245
+ const name = m[1];
246
+ const value = m[2].trim();
247
+ if (/^-?\d+$/.test(value) || /^"[^"]*"$/.test(value)) {
248
+ out.push({ name, value });
249
+ }
250
+ }
251
+ return out;
252
+ }
253
+ // Strip C-style comments so they don't interfere with parsing.
254
+ // Block comments and line comments are both removed; preprocessor
255
+ // directives are preserved (stripPreprocessorBlocks handles those next).
256
+ function stripComments(content) {
257
+ return content.replace(/\/\*[\s\S]*?\*\//g, '').replace(/\/\/[^\n]*/g, '');
258
+ }
259
+ function parseHeader(content) {
260
+ const noComments = stripComments(content);
261
+ const stripped = stripPreprocessorBlocks(noComments);
262
+ return {
263
+ functions: parseFunctions(stripped),
264
+ typedefs: [
265
+ ...parseEnumTypedefs(stripped),
266
+ ...parseStructTypedefs(stripped),
267
+ ...parseOpaqueTypedefs(stripped),
268
+ ...parseFuncPtrTypedefs(stripped),
269
+ ...parseAliasTypedefs(stripped),
270
+ ],
271
+ defines: parseSimpleDefines(noComments),
272
+ };
273
+ }
274
+ function emitEnumTypedef(td) {
275
+ const literalUnion = td.values.every((v) => typeof v.value === 'number')
276
+ ? td.values.map((v) => v.value).join(' | ')
277
+ : 'number';
278
+ const lines = [`export type ${td.name} = ${literalUnion};`];
279
+ // Export named constants so user code can reference WIFI_MODE_STA directly,
280
+ // exactly as ESP-IDF examples do.
281
+ for (const v of td.values) {
282
+ lines.push(`export const ${v.name}: ${td.name} = ${v.value ?? 0};`);
283
+ }
284
+ return lines;
285
+ }
286
+ function emitStructTypedef(td) {
287
+ const fields = td.fields.map((f) => {
288
+ const tsType = mapCTypeToTs(f.type);
289
+ return ` ${f.name}: ${f.isArray ? `${tsType}[]` : tsType};`;
290
+ });
291
+ return [`export interface ${td.name} {`, ...fields, `}`];
292
+ }
293
+ function emitTypedef(td) {
294
+ switch (td.kind) {
295
+ case 'enum':
296
+ return emitEnumTypedef(td);
297
+ case 'struct':
298
+ return emitStructTypedef(td);
299
+ case 'opaque':
300
+ return [`export type ${td.name} = number;`];
301
+ case 'alias':
302
+ return [`export type ${td.name} = ${mapCTypeToTs(td.aliasedType)};`];
303
+ case 'funcptr':
304
+ // Function-pointer typedefs have no faithful TS representation. Emit
305
+ // `any` with a comment so users know to use `rawCpp()` for callbacks.
306
+ return [
307
+ `// ${td.name} is a C function-pointer typedef; TS has no faithful representation.`,
308
+ `export type ${td.name} = any;`,
309
+ ];
310
+ }
311
+ }
312
+ /** Emit a free function declaration, name matching the C header 1-to-1. */
313
+ function emitFunction(fn) {
314
+ const params = fn.params.map((p, idx) => {
315
+ const name = p.name || `_arg${idx}`;
316
+ const tsType = p.type === '' ? 'any' : mapCTypeToTs(p.type);
317
+ return `${name}: ${tsType}`;
318
+ });
319
+ const returnType = mapCTypeToTs(fn.returnType);
320
+ return `export declare function ${fn.name}(${params.join(', ')}): ${returnType};`;
321
+ }
322
+ /**
323
+ * Top-level entry: read a header file, write `<header>.d.ts` alongside it.
324
+ * Returns the output path, or null if no declarations could be extracted.
325
+ */
326
+ export function generateCDecl(filePath, outputPath) {
327
+ if (!fs.existsSync(filePath))
328
+ return null;
329
+ const content = fs.readFileSync(filePath, 'utf8');
330
+ const header = parseHeader(content);
331
+ // Collect every type name the header defines so the alias-emission pass
332
+ // below doesn't emit spurious `export type X = number;` for types that
333
+ // are already declared.
334
+ const knownTypes = new Set([
335
+ ...header.typedefs.map((t) => t.name),
336
+ ...BUILTIN_TS_TYPES,
337
+ ]);
338
+ // Find referenced types that aren't declared anywhere in this header and
339
+ // aren't builtins — emit them as `export type X = number;` so the .d.ts
340
+ // compiles standalone. (Common case: a header references `esp_err_t` from
341
+ // another header without re-typedef'ing it.)
342
+ const referencedTypes = new Set();
343
+ for (const f of header.functions) {
344
+ referencedTypes.add(mapCTypeToTs(f.returnType));
345
+ for (const p of f.params)
346
+ referencedTypes.add(mapCTypeToTs(p.type));
347
+ }
348
+ for (const td of header.typedefs) {
349
+ if (td.kind === 'struct') {
350
+ for (const f of td.fields)
351
+ referencedTypes.add(mapCTypeToTs(f.type));
352
+ }
353
+ else if (td.kind === 'alias') {
354
+ referencedTypes.add(mapCTypeToTs(td.aliasedType));
355
+ }
356
+ }
357
+ const aliasesToEmit = [...referencedTypes].filter((t) => !knownTypes.has(t) && !t.includes('|') && !BUILTIN_TS_TYPES.has(t));
358
+ // Nothing to emit → tell the caller.
359
+ if (header.functions.length === 0 &&
360
+ header.typedefs.length === 0 &&
361
+ header.defines.length === 0 &&
362
+ aliasesToEmit.length === 0) {
363
+ return null;
364
+ }
365
+ const lines = [
366
+ '// Auto-generated by cuttlefish gen-decls. Do not edit.',
367
+ '// Source: ' + path.basename(filePath),
368
+ '// C names are preserved verbatim; calls lower 1-to-1 to the C header.',
369
+ '',
370
+ ];
371
+ // Standalone type aliases for cross-header references (e.g. esp_err_t).
372
+ for (const a of aliasesToEmit) {
373
+ lines.push(`export type ${a} = number;`);
374
+ }
375
+ if (aliasesToEmit.length > 0)
376
+ lines.push('');
377
+ // Typedefs (enums, structs, opaque handles, aliases, function pointers).
378
+ for (const td of header.typedefs) {
379
+ lines.push(...emitTypedef(td), '');
380
+ }
381
+ // #define constants (integer/string literals only).
382
+ for (const d of header.defines) {
383
+ lines.push(`export const ${d.name}: ${/^"/.test(d.value) ? 'string' : 'number'} = ${d.value};`);
384
+ }
385
+ if (header.defines.length > 0)
386
+ lines.push('');
387
+ // Free functions, named 1-to-1 with the C header.
388
+ for (const fn of header.functions) {
389
+ lines.push(emitFunction(fn));
390
+ }
391
+ const outPath = outputPath ?? filePath.replace(/\.h$/i, '.d.ts');
392
+ const outContent = lines.join('\n') + '\n';
393
+ if (!fs.existsSync(outPath) || fs.readFileSync(outPath, 'utf8') !== outContent) {
394
+ fs.writeFileSync(outPath, outContent, 'utf8');
395
+ }
396
+ return outPath;
397
+ }