@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
@@ -16,6 +16,17 @@ export function expressionStatementToIR(statement, fileName, sourceText, diagnos
16
16
  if (callStmt && callStmt.kind === "call") {
17
17
  return { ...callStmt, isAwaited: true };
18
18
  }
19
+ // Awaited network HAL ops (WiFi.connect / WiFi.untilConnected / Http.send
20
+ // / ...) resolve to hal-op statements, which would otherwise lower to the
21
+ // BLOCKING shim call even inside an async state machine. Rewrite them to
22
+ // an awaited __WIFI_WAIT__/__HTTP_WAIT__ marker call carrying the original
23
+ // op; the async state-machine generator turns it into a start + poll state
24
+ // pair, and the statement renderer falls back to the blocking form when
25
+ // the marker is rendered outside a state machine (top-level await, awaits
26
+ // nested in unsupported positions).
27
+ const netMarker = awaitedNetMarker(callStmt);
28
+ if (netMarker)
29
+ return netMarker;
19
30
  return callStmt;
20
31
  }
21
32
  // ── Register bit-field write ────────────────────────────────────────
@@ -404,3 +415,54 @@ export function expressionStatementToIR(statement, fileName, sourceText, diagnos
404
415
  }
405
416
  return undefined;
406
417
  }
418
+ /** HAL ops with a start/poll split available in the async state machine
419
+ * (see async-state-machine.ts netWaitInfo — keep the two in sync).
420
+ * timing.delay is included because `delay()` from @typecad/hal resolves to a
421
+ * hal-op, dropping the isAwaited flag the state machine keys on. */
422
+ const AWAITABLE_HAL_OPS = new Set([
423
+ "timing.delay",
424
+ "wifi.connect",
425
+ "wifi.wait_connected",
426
+ "wifi.wait_disconnected",
427
+ "wifi.scan",
428
+ "http.send",
429
+ "ble.until_connected",
430
+ // Worker offload: `await worker.submit(...)` rewrites to submit + poll
431
+ // worker.done — mirrors `await http.send(...)`. The start op is worker.submit
432
+ // (emitted by the previous state segment); the poll predicate is worker.done.
433
+ "worker.submit",
434
+ ]);
435
+ /**
436
+ * Rewrite an awaited hal-op statement (or a block whose LAST statement is
437
+ * one — chained HAL calls resolve to blocks of hal-ops) into an awaited
438
+ * `__WIFI_WAIT__` / `__HTTP_WAIT__` / `__HAL_WAIT__` marker call carrying the
439
+ * original op as a hal-expr argument. Returns undefined when the statement is
440
+ * not an awaitable HAL op.
441
+ */
442
+ function awaitedNetMarker(stmt) {
443
+ if (!stmt)
444
+ return undefined;
445
+ if (stmt.kind === "hal-op" && AWAITABLE_HAL_OPS.has(stmt.operation.operation)) {
446
+ const opName = stmt.operation.operation;
447
+ const callee = opName.startsWith("http.") ? "__HTTP_WAIT__"
448
+ : opName.startsWith("wifi.") ? "__WIFI_WAIT__"
449
+ : opName.startsWith("ble.") ? "__BLE_WAIT__"
450
+ : "__HAL_WAIT__";
451
+ return {
452
+ kind: "call",
453
+ sourceSpan: stmt.sourceSpan,
454
+ leadingComments: stmt.leadingComments,
455
+ trailingComments: stmt.trailingComments,
456
+ callee,
457
+ args: [{ kind: "hal-expr", operation: stmt.operation }],
458
+ isAwaited: true,
459
+ };
460
+ }
461
+ if (stmt.kind === "block" && stmt.body.length > 0) {
462
+ const last = awaitedNetMarker(stmt.body[stmt.body.length - 1]);
463
+ if (last) {
464
+ return { ...stmt, body: [...stmt.body.slice(0, -1), last] };
465
+ }
466
+ }
467
+ return undefined;
468
+ }
@@ -270,5 +270,26 @@ export function tryResolveHALExpression(call, sourceText, diagnostics, pointerVa
270
270
  }
271
271
  }
272
272
  }
273
+ // Namespace method fallbacks (Pulse, Shift, Random) in expression context.
274
+ // Value-returning namespace methods (e.g. Random.upTo/between/int) lower to
275
+ // HAL ops; surface the last op as a hal-expr so the strategy lowers it to the
276
+ // platform PRNG (Arduino random(), ESP-IDF esp_random()). Without this, an
277
+ // inline `if (Random.upTo(5) > 2)` would fall through to a 0 placeholder.
278
+ if (ts.isIdentifier(receiver)) {
279
+ const ns = receiver.text;
280
+ const nsResult = resolveNamespaceMethodCall(ns, method, argIRs);
281
+ if (nsResult) {
282
+ if (nsResult.halOps.length > 0) {
283
+ const lastOp = nsResult.halOps[nsResult.halOps.length - 1];
284
+ return {
285
+ ir: { kind: "hal-expr", operation: lastOp },
286
+ sideEffects: nsResult.emitLines,
287
+ };
288
+ }
289
+ if (nsResult.returnValue && nsResult.returnValue !== "__hal_op_return__") {
290
+ return { ir: { kind: "raw", value: nsResult.returnValue }, sideEffects: nsResult.emitLines };
291
+ }
292
+ }
293
+ }
273
294
  return null;
274
295
  }
@@ -27,7 +27,7 @@ export function collectChainedHALEmits(expr, sourceText, diagnostics, pointerVar
27
27
  }
28
28
  }
29
29
  // Continue recursion to collect deeper chain levels
30
- if (ts.isCallExpression(innerReceiver) && ts.isPropertyAccessExpression(innerReceiver)) {
30
+ if (ts.isCallExpression(innerReceiver) && ts.isPropertyAccessExpression(innerReceiver.expression)) {
31
31
  collectChainedHALEmits(innerReceiver, sourceText, diagnostics, pointerVars, emitLines, halOps);
32
32
  }
33
33
  }
@@ -81,29 +81,34 @@ export function resolveNamespaceMethodCall(ns, method, argIRs) {
81
81
  }
82
82
  }
83
83
  if (ns === "Random") {
84
+ // Random.* lowers to the random.* HAL op family, which frameworks lower to
85
+ // their platform PRNG (Arduino random()/randomSeed(), ESP-IDF esp_random()).
86
+ // Previously these emitted bare `random()`/`randomSeed()` calls, which only
87
+ // resolve to symbols on Arduino-core frameworks — on ESP-IDF they were
88
+ // undefined and failed at C++ link time.
84
89
  if (method === "seed") {
85
- return { emitLines: [`randomSeed(${argText(0)});`], halOps: [] };
90
+ return { emitLines: [], halOps: [{ operation: "random.seed", seed: argText(0) }] };
86
91
  }
87
92
  if (method === "number") {
88
93
  const min = argText(0);
89
94
  const max = argText(1);
90
- return {
91
- emitLines: [],
92
- halOps: [],
93
- returnValue: max ? `random(${min}, ${max})` : `random(${min})`,
94
- };
95
+ if (max) {
96
+ return { emitLines: [], halOps: [{ operation: "random.range", min, max }], returnValue: "__hal_op_return__" };
97
+ }
98
+ // Single-arg form: Random.number(max) [0, max-1]
99
+ return { emitLines: [], halOps: [{ operation: "random.range", min: "0", max: min }], returnValue: "__hal_op_return__" };
95
100
  }
96
- // Current class API: Random.upTo(max) → random(max)
101
+ // Random.upTo(max) → [0, max-1]
97
102
  if (method === "upTo") {
98
- return { emitLines: [], halOps: [], returnValue: `random(${argText(0)})` };
103
+ return { emitLines: [], halOps: [{ operation: "random.range", min: "0", max: argText(0) }], returnValue: "__hal_op_return__" };
99
104
  }
100
- // Random.between(min, max) → random(min, max)
105
+ // Random.between(min, max) → [min, max-1]
101
106
  if (method === "between") {
102
- return { emitLines: [], halOps: [], returnValue: `random(${argText(0)}, ${argText(1)})` };
107
+ return { emitLines: [], halOps: [{ operation: "random.range", min: argText(0), max: argText(1) }], returnValue: "__hal_op_return__" };
103
108
  }
104
- // Random.int() → random(2147483647)
109
+ // Random.int() → non-negative 31-bit integer
105
110
  if (method === "int") {
106
- return { emitLines: [], halOps: [], returnValue: `random(2147483647)` };
111
+ return { emitLines: [], halOps: [{ operation: "random.int" }], returnValue: "__hal_op_return__" };
107
112
  }
108
113
  }
109
114
  return null;
@@ -30,9 +30,9 @@ function propEnum(property) {
30
30
  export function emitBindingEntry(spec) {
31
31
  // Text bindings wire to textFn (void fill-style); color bindings wire to fn.
32
32
  if (spec.property === "text") {
33
- return ` { .node=${spec.nodeIndex}, .prop=${propEnum(spec.property)}, .fn=nullptr, .textFn=${spec.fnName} },`;
33
+ return ` { .node=${spec.nodeIndex}, .prop=${propEnum(spec.property)}, .fn=nullptr, .textFn=${spec.fnName}, .lastValue=0, .initialized=0 },`;
34
34
  }
35
- return ` { .node=${spec.nodeIndex}, .prop=${propEnum(spec.property)}, .fn=${spec.fnName}, .textFn=nullptr },`;
35
+ return ` { .node=${spec.nodeIndex}, .prop=${propEnum(spec.property)}, .fn=${spec.fnName}, .textFn=nullptr, .lastValue=0, .initialized=0 },`;
36
36
  }
37
37
  /** Emit a full binding table from a list of specs. */
38
38
  export function emitBindingTable(specs) {
@@ -7,9 +7,11 @@ import { getCurrentIrTypeScope, setScopeLocalType } from "../symbol-types.js";
7
7
  import { renderExprAsText } from "../render-expr.js";
8
8
  import { expressionToIR } from "../expression-to-ir.js";
9
9
  import { buildInlineForLoop } from "./array-methods.js";
10
- import { isKnownHALClass, getCtorIncludes, registerFloatVariable, resolveHALReceiver, isHALSingleton, } from "../hal-resolver.js";
10
+ import { isKnownHALClass, getCtorIncludes, getHALCtorFieldMap, registerFloatVariable, resolveHALReceiver, isHALSingleton, } from "../hal-resolver.js";
11
+ import { httpFactoryVerb, httpUrlArgText } from "../hal/hal-parser.js";
11
12
  import { resolveHALCallForVarInit } from "./hal-call-resolver.js";
12
13
  import { recordSignal } from "./ui-call-resolver.js";
14
+ import { hasSafetyHook, requireSafetyHook } from "../../safety-hook.js";
13
15
  function replaceHalReadBufferPlaceholder(op, varName) {
14
16
  if (op.operation === "i2c.read_buffer" && op.buffer === "__HAL_READ_BUF__") {
15
17
  return { ...op, buffer: varName };
@@ -374,6 +376,56 @@ export function variableStatementToIR(statement, fileName, sourceText, diagnosti
374
376
  localVariableTypes.set(varName, cppType);
375
377
  continue;
376
378
  }
379
+ // ── safe.<method>(...) — @typecad/safety call as initializer.
380
+ // Handles expression-position safe.* calls like `const r = safe.read(pin)`.
381
+ // The statement-position form (safe.pinMode(...)) is handled in the
382
+ // call-statement transformer. Both produce hal-op IR nodes that
383
+ // routeHALOp() later dispatches to the safety hook.
384
+ if (hasSafetyHook() &&
385
+ ts.isCallExpression(declaration.initializer) &&
386
+ ts.isPropertyAccessExpression(declaration.initializer.expression) &&
387
+ ts.isIdentifier(declaration.initializer.expression.expression) &&
388
+ declaration.initializer.expression.expression.text === "safe") {
389
+ const method = declaration.initializer.expression.name.text;
390
+ const argValues = declaration.initializer.arguments.map((a) => {
391
+ if (ts.isNumericLiteral(a))
392
+ return Number(a.text);
393
+ if (ts.isStringLiteral(a))
394
+ return a.text;
395
+ if (a.kind === ts.SyntaxKind.TrueKeyword)
396
+ return true;
397
+ if (a.kind === ts.SyntaxKind.FalseKeyword)
398
+ return false;
399
+ if (ts.isIdentifier(a)) {
400
+ const name = a.text;
401
+ if (name === "INPUT")
402
+ return 0;
403
+ if (name === "OUTPUT")
404
+ return 1;
405
+ if (name === "INPUT_PULLUP")
406
+ return 2;
407
+ return name;
408
+ }
409
+ return undefined;
410
+ });
411
+ const op = requireSafetyHook().resolveSemanticCall?.(`safe.${method}`, argValues);
412
+ if (op) {
413
+ // Emit a var_decl initialized by a hal-expr carrying the safety op.
414
+ // routeHALOp() resolves it to the C++ expression at emit time.
415
+ const initExpr = { kind: "hal-expr", operation: op };
416
+ lowered.push({
417
+ kind: "var_decl",
418
+ name: varName,
419
+ storage,
420
+ cppType: "auto",
421
+ initializer: initExpr,
422
+ sourceSpan: makeSourceSpan(declaration, fileName, sourceText),
423
+ });
424
+ // auto-deduced to SafeReadResult on the C++ side.
425
+ localVariableTypes.set(varName, "auto");
426
+ continue;
427
+ }
428
+ }
377
429
  if (ts.isNewExpression(declaration.initializer) && ts.isIdentifier(declaration.initializer.expression)) {
378
430
  const className = declaration.initializer.expression.text;
379
431
  const ctorArgs = declaration.initializer.arguments;
@@ -408,9 +460,26 @@ export function variableStatementToIR(statement, fileName, sourceText, diagnosti
408
460
  }
409
461
  }
410
462
  }
463
+ // new HttpRequest(method, url) — positional ctor fields. The URL is
464
+ // stored as C++ expression text (quoted literal or identifier).
465
+ if (className === "HttpRequest" && ctorArgs && ctorArgs.length >= 2) {
466
+ const methodArg = ctorArgs[0];
467
+ if (ts.isStringLiteral(methodArg))
468
+ fieldValues.set("_method", methodArg.text.toUpperCase());
469
+ const urlText = httpUrlArgText(ctorArgs[1]);
470
+ if (urlText)
471
+ fieldValues.set("_url", urlText);
472
+ }
411
473
  if (ctorArgs) {
474
+ // Resolve a Pin-identifier ctor arg to its _pin number, for any HAL
475
+ // class whose first constructor field is _pin (Pin itself, plus
476
+ // pin-bearing wrappers like RmtChannel). Mirrors the Pin branch
477
+ // above but keyed off the class's registered ctor field map so new
478
+ // pin-keyed classes work without a per-class branch here.
479
+ const ctorFieldMap = getHALCtorFieldMap(className);
480
+ const firstFieldIsPin = ctorFieldMap && Array.from(ctorFieldMap.keys())[0] === "_pin";
412
481
  for (const arg of ctorArgs) {
413
- if (ts.isIdentifier(arg) && className === "Pin") {
482
+ if (ts.isIdentifier(arg) && (className === "Pin" || firstFieldIsPin)) {
414
483
  const existing = halInstances.get(arg.text);
415
484
  if (existing && existing.fieldValues.has("_pin")) {
416
485
  fieldValues.set("_pin", existing.fieldValues.get("_pin"));
@@ -516,6 +585,19 @@ export function variableStatementToIR(statement, fileName, sourceText, diagnosti
516
585
  }
517
586
  }
518
587
  }
588
+ // For Http factory calls (const req = Http.get(url)), record the
589
+ // HTTP verb + URL so req.send() can resolve this._method/this._url.
590
+ if (result.returnClassName === "HttpRequest") {
591
+ const verb = httpFactoryVerb(init.expression.name.text);
592
+ if (verb)
593
+ fieldValues.set("_method", verb);
594
+ const urlArg = init.arguments?.[0];
595
+ if (urlArg) {
596
+ const urlText = httpUrlArgText(urlArg);
597
+ if (urlText)
598
+ fieldValues.set("_url", urlText);
599
+ }
600
+ }
519
601
  halInstances.set(varName, {
520
602
  className: result.returnClassName,
521
603
  fieldValues,
@@ -573,7 +655,14 @@ export function variableStatementToIR(statement, fileName, sourceText, diagnosti
573
655
  // fill ops). Everything else is a scalar/value return captured
574
656
  // after the side-effect ops.
575
657
  const isTypedArray = result.returnValue.startsWith("__TYPED_ARRAY__:");
576
- if (!isTypedArray) {
658
+ // A factory returning a compile-time HAL instance (e.g.
659
+ // `const req = Http.get(url)` → returnValue `new HttpRequest(...)`)
660
+ // is fully tracked via halInstances — every later method call on
661
+ // the variable resolves at compile time. Emitting the raw
662
+ // `new HttpRequest(...)` would reference a class that doesn't
663
+ // exist in the C++ output.
664
+ const isHalInstanceReturn = !!result.returnClassName && /^new\s/.test(result.returnValue.trim());
665
+ if (!isTypedArray && !isHalInstanceReturn) {
577
666
  lowered.push({
578
667
  kind: "var_decl",
579
668
  sourceSpan: makeSourceSpan(declaration, fileName, sourceText),
@@ -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",
@@ -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,6 +19,7 @@ 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 = [];
@@ -30,9 +32,13 @@ export function runProgramValidations(program, strategy) {
30
32
  diagnostics.push(...validatePWMTimerSharing(peripheralUsage, program.boardConstants, program.fileName));
31
33
  diagnostics.push(...validateTimer0PWMTimingConflict(peripheralUsage, program.boardConstants, program.fileName));
32
34
  diagnostics.push(...validatePulldownSupport(peripheralUsage, program.boardConstants, program.fileName));
33
- diagnostics.push(...analyzeInterruptSafety(program, peripheralUsage));
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[];