@typecad/cuttlefish 0.1.0-alpha.2 → 1.0.0-alpha.10

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 (222) hide show
  1. package/README.md +10 -10
  2. package/dist/api/board-types.d.ts +1 -1
  3. package/dist/api/config.d.ts +32 -0
  4. package/dist/api/index.d.ts +1 -1
  5. package/dist/api/schema/types.d.ts +15 -0
  6. package/dist/api/shared/async-runtime-static.d.ts +12 -1
  7. package/dist/api/shared/async-runtime-static.js +69 -24
  8. package/dist/api/shared/async-symbol-detector.d.ts +11 -0
  9. package/dist/api/shared/async-symbol-detector.js +140 -0
  10. package/dist/api/shared/async-types.d.ts +24 -0
  11. package/dist/api/shared/coop-scheduler.d.ts +60 -0
  12. package/dist/api/shared/coop-scheduler.js +149 -0
  13. package/dist/api/shared/display-adapter.d.ts +4 -3
  14. package/dist/api/shared/display-adapter.js +15 -118
  15. package/dist/api/shared/display-adapters/sdl.js +38 -31
  16. package/dist/api/shared/display-profile.d.ts +47 -25
  17. package/dist/api/shared/display-profile.js +31 -164
  18. package/dist/api/shared/framework-manifest-registry.d.ts +9 -0
  19. package/dist/api/shared/framework-manifest-registry.js +27 -0
  20. package/dist/api/shared/framework-manifest.d.ts +595 -0
  21. package/dist/api/shared/framework-manifest.js +193 -0
  22. package/dist/api/shared/glcdfont.d.ts +12 -0
  23. package/dist/api/shared/glcdfont.js +124 -0
  24. package/dist/api/shared/graphics-strategy.d.ts +37 -0
  25. package/dist/api/shared/hal-op-ir.d.ts +764 -1
  26. package/dist/api/shared/hal-op-ir.js +129 -1
  27. package/dist/api/shared/index.d.ts +20 -2
  28. package/dist/api/shared/index.js +25 -1
  29. package/dist/api/shared/ir-core.d.ts +4 -0
  30. package/dist/api/shared/ir-declarations.d.ts +6 -0
  31. package/dist/api/shared/native-display-op-resolver.d.ts +10 -0
  32. package/dist/api/shared/native-display-op-resolver.js +64 -0
  33. package/dist/api/shared/platform-strategy.d.ts +119 -2
  34. package/dist/api/shared/polyfill-helper-registry.js +18 -1
  35. package/dist/api/shared/promise-runtime.d.ts +1 -1
  36. package/dist/api/shared/promise-runtime.js +95 -13
  37. package/dist/api/shared/toolchain-types.d.ts +17 -0
  38. package/dist/api/shared/types.d.ts +8 -0
  39. package/dist/api/shared/validate-framework-manifest.d.ts +28 -0
  40. package/dist/api/shared/validate-framework-manifest.js +494 -0
  41. package/dist/api/shared/worker-runtime-polyfill.d.ts +22 -0
  42. package/dist/api/shared/worker-runtime-polyfill.js +34 -0
  43. package/dist/api/shared/worker-runtime.d.ts +69 -0
  44. package/dist/api/shared/worker-runtime.js +163 -0
  45. package/dist/cli-utils.d.ts +1 -0
  46. package/dist/cli-utils.js +3 -1
  47. package/dist/cli.js +249 -32
  48. package/dist/config-loader.d.ts +7 -2
  49. package/dist/config-loader.js +23 -3
  50. package/dist/config-schema.d.ts +112 -70
  51. package/dist/config-schema.js +14 -0
  52. package/dist/contract/board-generator.d.ts +17 -0
  53. package/dist/contract/board-generator.js +57 -0
  54. package/dist/contract/contract-parser.d.ts +217 -0
  55. package/dist/contract/contract-parser.js +224 -0
  56. package/dist/contract/index.d.ts +22 -0
  57. package/dist/contract/index.js +68 -0
  58. package/dist/create/board-codegen.js +4 -4
  59. package/dist/create/board-generators.js +4 -5
  60. package/dist/create/board-spec.d.ts +72 -75
  61. package/dist/create/board-spec.js +0 -1
  62. package/dist/create/framework-catalog.d.ts +64 -0
  63. package/dist/create/framework-catalog.js +136 -0
  64. package/dist/create/index.d.ts +5 -1
  65. package/dist/create/index.js +3 -1
  66. package/dist/create/init-scaffold.d.ts +12 -3
  67. package/dist/create/init-scaffold.js +49 -14
  68. package/dist/create/init-templates.d.ts +4 -0
  69. package/dist/create/init-templates.js +263 -17
  70. package/dist/create/init-wizard.js +41 -27
  71. package/dist/create/install-deps.d.ts +32 -0
  72. package/dist/create/install-deps.js +46 -0
  73. package/dist/debug/preprocessor.js +178 -39
  74. package/dist/debug/types.d.ts +23 -0
  75. package/dist/diagnostics/mermaid-builder.d.ts +1 -1
  76. package/dist/diagnostics/mermaid-builder.js +34 -24
  77. package/dist/emit/compliance/arxml-writer.d.ts +11 -0
  78. package/dist/emit/compliance/arxml-writer.js +34 -0
  79. package/dist/emit/compliance/compliance-context.d.ts +57 -0
  80. package/dist/emit/compliance/compliance-context.js +113 -0
  81. package/dist/emit/compliance/deviation-ledger.d.ts +27 -0
  82. package/dist/emit/compliance/deviation-ledger.js +47 -0
  83. package/dist/emit/compliance/deviation-writer.d.ts +30 -0
  84. package/dist/emit/compliance/deviation-writer.js +37 -0
  85. package/dist/emit/compliance/index.d.ts +7 -0
  86. package/dist/emit/compliance/index.js +6 -0
  87. package/dist/emit/compliance/rule-engine.d.ts +13 -0
  88. package/dist/emit/compliance/rule-engine.js +101 -0
  89. package/dist/emit/compliance/rules.d.ts +16 -0
  90. package/dist/emit/compliance/rules.js +191 -0
  91. package/dist/emit/compliance/types.d.ts +59 -0
  92. package/dist/emit/compliance/types.js +8 -0
  93. package/dist/emit/cpp-emitter.js +4 -3
  94. package/dist/emit/emitters/class-emitter.js +6 -1
  95. package/dist/emit/emitters/emitter-context.d.ts +28 -1
  96. package/dist/emit/emitters/function-emitter-impl.js +153 -53
  97. package/dist/emit/emitters/line-appender.js +19 -0
  98. package/dist/emit/emitters/line-marker.d.ts +38 -0
  99. package/dist/emit/emitters/line-marker.js +39 -0
  100. package/dist/emit/emitters/output-finalizer.d.ts +6 -0
  101. package/dist/emit/emitters/output-finalizer.js +101 -12
  102. package/dist/emit/emitters/setup.d.ts +17 -0
  103. package/dist/emit/emitters/setup.js +372 -30
  104. package/dist/emit/emitters/top-level-prep.js +8 -0
  105. package/dist/emit/emitters/type-decl-emitter.js +28 -3
  106. package/dist/emit/emitters/ui-emitter.js +33 -9
  107. package/dist/emit/expression-renderer.d.ts +1 -1
  108. package/dist/emit/expression-renderer.js +91 -6
  109. package/dist/emit/route-hal-op.js +18 -5
  110. package/dist/emit/snprintf-helpers.js +15 -4
  111. package/dist/emit/statement-renderer.d.ts +10 -0
  112. package/dist/emit/statement-renderer.js +34 -5
  113. package/dist/emit/utils/async-state-machine.js +221 -125
  114. package/dist/emit/utils/hal-op-cpp-type.d.ts +6 -0
  115. package/dist/emit/utils/hal-op-cpp-type.js +40 -0
  116. package/dist/framework-package.js +2 -0
  117. package/dist/framework-registry.d.ts +17 -0
  118. package/dist/install/framework-catalog.d.ts +53 -0
  119. package/dist/install/framework-catalog.js +107 -0
  120. package/dist/install/handle-install.d.ts +35 -0
  121. package/dist/install/handle-install.js +177 -0
  122. package/dist/install/index.d.ts +4 -0
  123. package/dist/install/index.js +3 -0
  124. package/dist/ir/adc-range-validation.js +40 -26
  125. package/dist/ir/build-ir-state.d.ts +1 -0
  126. package/dist/ir/build-ir-state.js +14 -0
  127. package/dist/ir/build-ir.js +9 -5
  128. package/dist/ir/call-graph.js +16 -0
  129. package/dist/ir/expression-to-ir.js +207 -6
  130. package/dist/ir/feature-registry.js +7 -25
  131. package/dist/ir/function-builder.js +22 -0
  132. package/dist/ir/hal/hal-emitter.d.ts +5 -2
  133. package/dist/ir/hal/hal-emitter.js +50 -18
  134. package/dist/ir/hal/hal-parser.d.ts +6 -0
  135. package/dist/ir/hal/hal-parser.js +81 -0
  136. package/dist/ir/hal/hal-plugins.js +733 -1
  137. package/dist/ir/identifier-collector.js +35 -0
  138. package/dist/ir/interrupt-analysis.d.ts +5 -1
  139. package/dist/ir/interrupt-analysis.js +16 -19
  140. package/dist/ir/memory-budget-validation.js +1 -0
  141. package/dist/ir/network-validation.d.ts +4 -0
  142. package/dist/ir/network-validation.js +184 -0
  143. package/dist/ir/ownership-analysis.js +39 -1
  144. package/dist/ir/peripheral-ownership.js +5 -0
  145. package/dist/ir/peripheral-validation.d.ts +1 -1
  146. package/dist/ir/peripheral-validation.js +6 -3
  147. package/dist/ir/pin-alias-conflict.d.ts +1 -1
  148. package/dist/ir/pin-alias-conflict.js +2 -1
  149. package/dist/ir/pin-capability-validation.js +71 -32
  150. package/dist/ir/pin-mode-validation.d.ts +2 -2
  151. package/dist/ir/pin-mode-validation.js +54 -18
  152. package/dist/ir/pin-safety.d.ts +1 -1
  153. package/dist/ir/pin-safety.js +2 -1
  154. package/dist/ir/program-analysis.d.ts +81 -0
  155. package/dist/ir/program-analysis.js +429 -0
  156. package/dist/ir/pulldown-validation.d.ts +1 -1
  157. package/dist/ir/pulldown-validation.js +2 -1
  158. package/dist/ir/pwm-timer-sharing.d.ts +1 -1
  159. package/dist/ir/pwm-timer-sharing.js +2 -1
  160. package/dist/ir/render-expr.js +11 -0
  161. package/dist/ir/resource-analysis.js +2 -0
  162. package/dist/ir/timer0-pwm-timing-conflict.d.ts +1 -1
  163. package/dist/ir/timer0-pwm-timing-conflict.js +2 -1
  164. package/dist/ir/timing-validation.d.ts +6 -1
  165. package/dist/ir/timing-validation.js +61 -13
  166. package/dist/ir/transformers/call-statement.js +105 -0
  167. package/dist/ir/transformers/expressions.js +62 -0
  168. package/dist/ir/transformers/hal-call-resolver.js +21 -0
  169. package/dist/ir/transformers/hal-emit-helpers.js +1 -1
  170. package/dist/ir/transformers/namespace-methods.js +17 -12
  171. package/dist/ir/transformers/ui-reactive.js +2 -2
  172. package/dist/ir/transformers/variables.js +137 -19
  173. package/dist/ir/try-catch-validation.js +2 -0
  174. package/dist/ir/type-resolution.js +20 -2
  175. package/dist/ir/ui-element-auto-wire.js +7 -5
  176. package/dist/ir/unit-suspicion-validation.js +9 -7
  177. package/dist/ir/utils/map-statements.d.ts +4 -0
  178. package/dist/ir/utils/map-statements.js +79 -0
  179. package/dist/ir/validation-orchestrator.js +15 -8
  180. package/dist/ir/worker-analysis.d.ts +10 -0
  181. package/dist/ir/worker-analysis.js +261 -0
  182. package/dist/libdef/c-to-decl.d.ts +27 -0
  183. package/dist/libdef/c-to-decl.js +397 -0
  184. package/dist/libdef/component-decls.d.ts +2 -0
  185. package/dist/libdef/component-decls.js +6 -0
  186. package/dist/libdef/component-discovery.d.ts +43 -0
  187. package/dist/libdef/component-discovery.js +83 -0
  188. package/dist/libdef/cpp-to-decl.d.ts +9 -0
  189. package/dist/libdef/cpp-to-decl.js +72 -0
  190. package/dist/libdef/registry.js +5 -2
  191. package/dist/lint-cache.d.ts +59 -0
  192. package/dist/lint-cache.js +257 -0
  193. package/dist/orchestrator/graph-builder.js +14 -9
  194. package/dist/platform/coop-scheduler-runtime.d.ts +19 -0
  195. package/dist/platform/coop-scheduler-runtime.js +41 -0
  196. package/dist/platform/generic-strategy.d.ts +15 -3
  197. package/dist/platform/generic-strategy.js +49 -4
  198. package/dist/safety/safety-bridge.d.ts +11 -0
  199. package/dist/safety/safety-bridge.js +48 -0
  200. package/dist/safety/sidecar-bridge.d.ts +5 -0
  201. package/dist/safety/sidecar-bridge.js +22 -0
  202. package/dist/safety-hook.d.ts +79 -0
  203. package/dist/safety-hook.js +35 -0
  204. package/dist/testing.d.ts +8 -2
  205. package/dist/testing.js +9 -2
  206. package/dist/transpile.d.ts +3 -0
  207. package/dist/transpile.js +191 -49
  208. package/dist/types.d.ts +22 -3
  209. package/dist/ui-hook.d.ts +17 -3
  210. package/dist/utils/cli.js +122 -2
  211. package/dist/utils/fs.d.ts +13 -0
  212. package/dist/utils/fs.js +50 -0
  213. package/dist/utils/ui.js +3 -1
  214. package/package.json +23 -3
  215. package/dist/api/shared/display-adapters/eink-mono.d.ts +0 -2
  216. package/dist/api/shared/display-adapters/eink-mono.js +0 -53
  217. package/dist/api/shared/display-adapters/ssd1309.d.ts +0 -2
  218. package/dist/api/shared/display-adapters/ssd1309.js +0 -136
  219. package/dist/api/shared/display-adapters/st7796.d.ts +0 -2
  220. package/dist/api/shared/display-adapters/st7796.js +0 -132
  221. package/dist/ir/heap-array-validation.d.ts +0 -24
  222. package/dist/ir/heap-array-validation.js +0 -29
@@ -33,9 +33,9 @@ const PIN_INTERFACE_TYPE_NAMES = new Set([
33
33
  "PinMode", "InterruptMode",
34
34
  ]);
35
35
  const BUS_INTERFACE_TYPE_NAMES = new Set([
36
- "II2CBus", "ISPIBus", "ISerialPort", "IUART",
36
+ "II2CBus", "ISPIBus", "ISerialPort",
37
37
  "I2CConfig", "SPIConfig", "UARTConfig",
38
- "I2CAddress", "UARTStatus", "SPITransferOptions",
38
+ "I2CAddress", "SPITransferOptions",
39
39
  ]);
40
40
  const STRATEGY_TYPE_NAMES = new Set([
41
41
  "NativeStrategy", "ArduinoStrategy", "BoardStrategy",
@@ -48,6 +48,14 @@ const BOARD_CONSTANT_TYPE_NAMES = new Set([
48
48
  const OWNERSHIP_WRAPPER_TYPE_NAMES = new Set([
49
49
  "Owned", "Shared", "Mutable",
50
50
  ]);
51
+ /** Safety wrapper types: like ownership wrappers (phantom TS types that carry
52
+ * a type parameter), but instead of stripping the wrapper, the C++ keeps it
53
+ * as a template instantiation: SafeVariable<number> → SafeVariable<int32_t>.
54
+ * The C++ template definition is provided by the safety polyfill. */
55
+ const SAFE_WRAPPER_TYPE_NAMES = new Set([
56
+ "SafeVariable",
57
+ "SafeInt",
58
+ ]);
51
59
  export function resolveAliasedTypeNode(node, typeAliases, visited = new Set()) {
52
60
  if (!node || !typeAliases) {
53
61
  return node;
@@ -147,6 +155,16 @@ export function typeNodeToCppType(node, typeAliases, typeParametersInScope) {
147
155
  const innerTypeNode = node.typeArguments?.[0];
148
156
  return typeNodeToCppType(innerTypeNode, typeAliases, typeParametersInScope);
149
157
  }
158
+ // SafeVariable<T> is a safety wrapper: unlike ownership wrappers (which
159
+ // strip the wrapper name entirely), SafeVariable keeps the wrapper name
160
+ // in the C++ type because the polyfill provides a template definition.
161
+ // SafeVariable<number> → SafeVariable<int> (the template is emitted by
162
+ // the safety polyfill's helperStructs).
163
+ if (SAFE_WRAPPER_TYPE_NAMES.has(wrapperName)) {
164
+ const innerTypeNode = node.typeArguments?.[0];
165
+ const innerCppType = typeNodeToCppType(innerTypeNode, typeAliases, typeParametersInScope);
166
+ return `${wrapperName}<${innerCppType}>`;
167
+ }
150
168
  }
151
169
  const resolvedNode = resolveAliasedTypeNode(node, typeAliases) ?? node;
152
170
  // If the resolved node is an object literal type (e.g. type X = { a: number }),
@@ -81,7 +81,7 @@ function autoWireNode(treeName, node, nodeIndex) {
81
81
  nodeIndex,
82
82
  property: "text",
83
83
  fnName: `__ui_bindtext_${nodeIndex}`,
84
- cppBody: `snprintf(buf, size, "%s", ${sig});`,
84
+ cppBody: `snprintf(buf, static_cast<size_t>(size), "%s", ${sig});`,
85
85
  });
86
86
  // Write: keyboard commit → signal.set(text).
87
87
  recordInputBinding({
@@ -156,13 +156,15 @@ function autoWireNode(treeName, node, nodeIndex) {
156
156
  fnName: `__ui_${node.id}_autoclick`,
157
157
  callbackBody: `__ui_nodes[${nodeIndex}].value = (__ui_nodes[${nodeIndex}].value + 1) % ${count};`,
158
158
  });
159
- // Auto-bind text to show the current option via snprintf if/else chain
159
+ // Auto-bind text to show the current option via snprintf if/else chain.
160
+ // Cast size to size_t to satisfy -Wformat (snprintf's n param is size_t;
161
+ // the textFn signature uses uint8_t).
160
162
  const branches = options.map((opt, i) => {
161
163
  if (i === 0)
162
- return `if (__ui_nodes[${nodeIndex}].value == 0) { snprintf(buf, size, "%s", "${opt}"); }`;
163
- return `else if (__ui_nodes[${nodeIndex}].value == ${i}) { snprintf(buf, size, "%s", "${opt}"); }`;
164
+ return `if (__ui_nodes[${nodeIndex}].value == 0) { snprintf(buf, static_cast<size_t>(size), "%s", "${opt}"); }`;
165
+ return `else if (__ui_nodes[${nodeIndex}].value == ${i}) { snprintf(buf, static_cast<size_t>(size), "%s", "${opt}"); }`;
164
166
  }).join(" ");
165
- const elseBranch = `else { snprintf(buf, size, "%s", "${options[0] || ""}"); }`;
167
+ const elseBranch = `else { snprintf(buf, static_cast<size_t>(size), "%s", "${options[0] || ""}"); }`;
166
168
  recordBinding({
167
169
  nodeIndex,
168
170
  property: "text",
@@ -130,7 +130,7 @@ function checkSPIFrequency(value) {
130
130
  * baud rate used as SPI freq, etc.). Only literal numbers are checked —
131
131
  * expressions/variables are opaque to this validator.
132
132
  */
133
- function checkConfigValue(kind, rawValue, diagnostics) {
133
+ function checkConfigValue(kind, rawValue, filePath, diagnostics) {
134
134
  // HAL ops carry resolved numeric values as `number` when the source arg was
135
135
  // a literal, or as `string` expression text otherwise. Only check numbers.
136
136
  const value = typeof rawValue === 'number' ? rawValue
@@ -146,6 +146,7 @@ function checkConfigValue(kind, rawValue, diagnostics) {
146
146
  severity: 'warning',
147
147
  message,
148
148
  code: 'unit-suspicion',
149
+ filePath,
149
150
  source: 'unit-suspicion-validation',
150
151
  });
151
152
  }
@@ -160,15 +161,16 @@ function scanStatement(stmt, diagnostics) {
160
161
  if (!stmt || typeof stmt !== 'object')
161
162
  return;
162
163
  const s = stmt;
164
+ const filePath = s.sourceSpan?.filePath;
163
165
  // HAL-op statements carry structured operations with resolved values.
164
166
  if (stmt.kind === 'hal-op' && s.operation) {
165
167
  const op = s.operation;
166
168
  switch (op.operation) {
167
169
  case 'i2c.set_clock':
168
- checkConfigValue('i2c', op.hz, diagnostics);
170
+ checkConfigValue('i2c', op.hz, filePath, diagnostics);
169
171
  break;
170
172
  case 'uart.begin':
171
- checkConfigValue('baud', op.baud, diagnostics);
173
+ checkConfigValue('baud', op.baud, filePath, diagnostics);
172
174
  break;
173
175
  // SPI frequency flows through SPISettings construction text, which the
174
176
  // HAL op carries as a string — the numeric extraction in
@@ -178,7 +180,7 @@ function scanStatement(stmt, diagnostics) {
178
180
  // SPISettings({freq}, ...) — try to extract the leading frequency.
179
181
  const m = op.settings.match(/^\s*(\d+)/);
180
182
  if (m)
181
- checkConfigValue('spi', parseInt(m[1], 10), diagnostics);
183
+ checkConfigValue('spi', parseInt(m[1], 10), filePath, diagnostics);
182
184
  }
183
185
  break;
184
186
  }
@@ -188,18 +190,18 @@ function scanStatement(stmt, diagnostics) {
188
190
  if (stmt.kind === 'call' && typeof s.callee === 'string' && Array.isArray(s.args)) {
189
191
  const method = s.callee.split('.').pop() ?? s.callee;
190
192
  if (method === 'setClock' && s.args.length >= 2) {
191
- checkConfigValue('i2c', extractNumericValue(s.args[1]), diagnostics);
193
+ checkConfigValue('i2c', extractNumericValue(s.args[1]), filePath, diagnostics);
192
194
  }
193
195
  else if (method === 'setBaudRate' || method === 'begin') {
194
196
  // Serial.begin(baud) / setBaudRate(baud) — last numeric arg is the baud.
195
197
  for (const arg of s.args) {
196
198
  const v = extractNumericValue(arg);
197
199
  if (v !== undefined)
198
- checkConfigValue('baud', v, diagnostics);
200
+ checkConfigValue('baud', v, filePath, diagnostics);
199
201
  }
200
202
  }
201
203
  else if (method === 'setFrequency' && s.args.length >= 2) {
202
- checkConfigValue('spi', extractNumericValue(s.args[1]), diagnostics);
204
+ checkConfigValue('spi', extractNumericValue(s.args[1]), filePath, diagnostics);
203
205
  }
204
206
  }
205
207
  // Recurse into nested statements
@@ -0,0 +1,4 @@
1
+ import type { ProgramIR, StatementIR } from "../../api/shared/index.js";
2
+ /** Map each statement in the program to zero or more statements. The result
3
+ * is a new ProgramIR; the input is not modified. */
4
+ export declare function mapProgramStatements(program: ProgramIR, fn: (stmt: StatementIR) => StatementIR[]): ProgramIR;
@@ -0,0 +1,79 @@
1
+ // ---------------------------------------------------------------------------
2
+ // mapProgramStatements — produce a NEW ProgramIR with statements mapped by fn.
3
+ //
4
+ // The existing walkers in walk-ir.ts are read-only; filterProgramIR
5
+ // (ir/filter.ts) produces a new IR but only filters, never injects. This
6
+ // helper extends the "produce-new-IR" idiom to "map each statement to N
7
+ // statements," which is what the safety pinMode-intercept pass needs to
8
+ // inject companion ops.
9
+ //
10
+ // Non-mutating: every statement container is rebuilt; the input program is
11
+ // untouched. The shape mirrors walkProgramIR's enumeration of containers
12
+ // (walk-ir.ts:107-134): topLevelStatements, functions, classes
13
+ // (constructor/methods/getters/setters), namespaces (recursively).
14
+ //
15
+ // KNOWN LIMITATION (Part A scope): does NOT recurse into nested statement
16
+ // bodies (if/for/while/switch/cases). Statements at the top level of a
17
+ // function body, class method, namespace function, or the file's top level
18
+ // ARE mapped. pinMode calls inside `if (...) { pinMode(...); }` are not. The
19
+ // safety package's design (docs/superpowers/specs/2026-07-27-safety-package-
20
+ // part-a-design.md) explicitly bounds Part A to this. A future iteration can
21
+ // thread mapping into nested bodies by recursing into thenBranch/elseBranch/
22
+ // body/initializer/increment/cases — see walkNestedStatements (walk-ir.ts:3).
23
+ // ---------------------------------------------------------------------------
24
+ /** Map each statement in the program to zero or more statements. The result
25
+ * is a new ProgramIR; the input is not modified. */
26
+ export function mapProgramStatements(program, fn) {
27
+ const mapList = (stmts) => {
28
+ const out = [];
29
+ for (const s of stmts) {
30
+ const mapped = fn(s);
31
+ for (const m of mapped)
32
+ out.push(m);
33
+ }
34
+ return out;
35
+ };
36
+ const newFunctions = program.functions.map((f) => ({
37
+ ...f,
38
+ statements: mapList(f.statements),
39
+ }));
40
+ const newClasses = program.classes.map((cls) => ({
41
+ ...cls,
42
+ constructor: cls.constructor
43
+ ? { ...cls.constructor, statements: mapList(cls.constructor.statements) }
44
+ : cls.constructor,
45
+ methods: cls.methods.map((m) => ({ ...m, statements: mapList(m.statements) })),
46
+ getters: cls.getters.map((g) => ({ ...g, statements: mapList(g.statements) })),
47
+ setters: cls.setters.map((s) => ({ ...s, statements: mapList(s.statements) })),
48
+ }));
49
+ const newNamespaces = program.namespaces.map((ns) => mapNamespace(ns, fn));
50
+ return {
51
+ ...program,
52
+ topLevelStatements: mapList(program.topLevelStatements),
53
+ functions: newFunctions,
54
+ classes: newClasses,
55
+ namespaces: newNamespaces,
56
+ };
57
+ }
58
+ function mapNamespace(ns, fn) {
59
+ const mapList = (stmts) => {
60
+ const out = [];
61
+ for (const s of stmts)
62
+ out.push(...fn(s));
63
+ return out;
64
+ };
65
+ return {
66
+ ...ns,
67
+ functions: ns.functions.map((f) => ({ ...f, statements: mapList(f.statements) })),
68
+ classes: ns.classes.map((cls) => ({
69
+ ...cls,
70
+ constructor: cls.constructor
71
+ ? { ...cls.constructor, statements: mapList(cls.constructor.statements) }
72
+ : cls.constructor,
73
+ methods: cls.methods.map((m) => ({ ...m, statements: mapList(m.statements) })),
74
+ getters: cls.getters.map((g) => ({ ...g, statements: mapList(g.statements) })),
75
+ setters: cls.setters.map((s) => ({ ...s, statements: mapList(s.statements) })),
76
+ })),
77
+ children: ns.children ? ns.children.map((c) => mapNamespace(c, fn)) : ns.children,
78
+ };
79
+ }
@@ -1,6 +1,7 @@
1
1
  import { resolveStrategy } from "../platform/registry.js";
2
2
  import { hasLoadedFramework, getLoadedFramework } from "../framework-registry.js";
3
3
  import { analyzeInterruptSafety, inferVolatileForIsrSharedVars, detectReentrancyRisk } from "./interrupt-analysis.js";
4
+ import { analyzeWorkerIsolation } from "./worker-analysis.js";
4
5
  import { validateADCRange } from "./adc-range-validation.js";
5
6
  import { createEmptyPeripheralUsage } from "./peripheral-usage.js";
6
7
  import { validatePeripherals } from "./peripheral-validation.js";
@@ -18,21 +19,26 @@ import { validateBlockingDelayInLoop } from "./timing-validation.js";
18
19
  import { validateUnitSuspicion } from "./unit-suspicion-validation.js";
19
20
  import { validateOwnership } from "./ownership-analysis.js";
20
21
  import { validatePinCapabilities } from "./pin-capability-validation.js";
22
+ import { validateNetworkUsage } from "./network-validation.js";
21
23
  export function runProgramValidations(program, strategy) {
22
24
  const resolvedStrategy = strategy ?? (hasLoadedFramework() ? getLoadedFramework().strategy : resolveStrategy('generic'));
23
25
  const diagnostics = [];
24
26
  const peripheralUsage = program.peripheralUsage ?? createEmptyPeripheralUsage();
25
27
  diagnostics.push(...validatePinCapabilities(program));
26
- diagnostics.push(...validatePeripherals(peripheralUsage, program.boardConstants));
27
- diagnostics.push(...validateUnsafePins(peripheralUsage, program.boardConstants));
28
+ diagnostics.push(...validatePeripherals(peripheralUsage, program.boardConstants, program.fileName));
29
+ diagnostics.push(...validateUnsafePins(peripheralUsage, program.boardConstants, program.fileName));
28
30
  diagnostics.push(...analyzeResources(program, resolvedStrategy));
29
- diagnostics.push(...validatePinAliasConflicts(peripheralUsage, program.boardConstants));
30
- diagnostics.push(...validatePWMTimerSharing(peripheralUsage, program.boardConstants));
31
- diagnostics.push(...validateTimer0PWMTimingConflict(peripheralUsage, program.boardConstants));
32
- diagnostics.push(...validatePulldownSupport(peripheralUsage, program.boardConstants));
33
- diagnostics.push(...analyzeInterruptSafety(program, peripheralUsage));
31
+ diagnostics.push(...validatePinAliasConflicts(peripheralUsage, program.boardConstants, program.fileName));
32
+ diagnostics.push(...validatePWMTimerSharing(peripheralUsage, program.boardConstants, program.fileName));
33
+ diagnostics.push(...validateTimer0PWMTimingConflict(peripheralUsage, program.boardConstants, program.fileName));
34
+ diagnostics.push(...validatePulldownSupport(peripheralUsage, program.boardConstants, program.fileName));
35
+ diagnostics.push(...analyzeInterruptSafety(program, peripheralUsage, resolvedStrategy.isrUnsafeOperations?.()));
34
36
  inferVolatileForIsrSharedVars(program, diagnostics);
35
37
  detectReentrancyRisk(program, diagnostics);
38
+ // Worker isolation: bus access in workers is a hard error; worker-shared
39
+ // globals get volatile promotion (info) or a data-race warning. Only fires
40
+ // when worker.submit ops are present, so non-worker programs are unaffected.
41
+ analyzeWorkerIsolation(program, diagnostics);
36
42
  diagnostics.push(...validateADCRange(program, program.boardConstants));
37
43
  diagnostics.push(...validateUnitSuspicion(program));
38
44
  diagnostics.push(...validatePinModeConfig(program));
@@ -40,6 +46,7 @@ export function runProgramValidations(program, strategy) {
40
46
  diagnostics.push(...validateOwnership(program));
41
47
  diagnostics.push(...validateTryCatch(program, program.boardConstants, resolvedStrategy));
42
48
  diagnostics.push(...validateMemoryBudget(program, program.boardConstants));
43
- diagnostics.push(...validateBlockingDelayInLoop(program));
49
+ diagnostics.push(...validateBlockingDelayInLoop(program, resolvedStrategy));
50
+ diagnostics.push(...validateNetworkUsage(program));
44
51
  return diagnostics;
45
52
  }
@@ -0,0 +1,10 @@
1
+ import type { ProgramIR } from '../api/index.js';
2
+ import type { Diagnostic } from '../types.js';
3
+ /**
4
+ * Analyze worker isolation: (a) promote volatile for worker-shared globals,
5
+ * (b) flag shared-mutable globals, (c) hard-error on bus access in workers.
6
+ *
7
+ * Mutates the IR for (a) (sets isVolatile=true on qualifying var_decls) and
8
+ * pushes diagnostics for all three tiers. Returns the diagnostics it added.
9
+ */
10
+ export declare function analyzeWorkerIsolation(program: ProgramIR, diagnostics: Diagnostic[]): Diagnostic[];
@@ -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;