@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
@@ -1,7 +1,8 @@
1
1
  import ts from "typescript";
2
- import { getCurrentBoardConstants, halInstances } from "../build-ir-state.js";
2
+ import { getCurrentBoardConstants, halInstances, getContext } from "../build-ir-state.js";
3
3
  import { resolveExpressionText } from "./hal-emitter.js";
4
4
  import { renderExprAsText } from "../render-expr.js";
5
+ import { hasSafetyHook, requireSafetyHook } from "../../safety-hook.js";
5
6
  /**
6
7
  * Split a comma-joined argument list back into individual arguments, respecting
7
8
  * nesting (parens/brackets/braces) and string literals so a comma inside one of
@@ -105,6 +106,44 @@ function resolveNumericOrExpression(args, idx, instance, paramNames, callArgText
105
106
  return num;
106
107
  return resolveSemanticArg(args, idx, instance, paramNames, callArgTexts, paramDefaults);
107
108
  }
109
+ /** Normalize an optional resolved arg: an omitted optional parameter with no
110
+ * default resolves to the literal text "undefined" (see resolveExpressionText);
111
+ * map that to null so op builders can omit the field entirely. */
112
+ function dropUndefined(value) {
113
+ return value === "undefined" ? null : value;
114
+ }
115
+ /** Quote a resolved string value unless it is already a quoted literal or a
116
+ * plain identifier / member-access expression (i.e. a runtime variable). */
117
+ function quoteNonIdentifier(value) {
118
+ const t = value.trim();
119
+ if (/^".*"$/.test(t))
120
+ return t;
121
+ if (/^[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*)*$/.test(t))
122
+ return t;
123
+ return JSON.stringify(t);
124
+ }
125
+ /** Resolve a BlePerm expression to its numeric bitmask.
126
+ * Handles: plain numbers (3), "BlePerm.Read", "BlePerm.Read | BlePerm.Notify". */
127
+ const BLE_PERM_VALUES = {
128
+ Read: 1, Write: 2, Notify: 4,
129
+ };
130
+ function resolveBlePermExpr(raw) {
131
+ if (raw === null)
132
+ return 0;
133
+ const s = String(raw).trim();
134
+ if (/^\d+$/.test(s))
135
+ return Number(s);
136
+ // Extract all BlePerm.X member names and sum their values.
137
+ let sum = 0;
138
+ const re = /BlePerm\.(\w+)/g;
139
+ let m;
140
+ let matched = false;
141
+ while ((m = re.exec(s)) !== null) {
142
+ matched = true;
143
+ sum += BLE_PERM_VALUES[m[1]] ?? 0;
144
+ }
145
+ return matched ? sum : 0;
146
+ }
108
147
  /** Extract the MCU port name from the current HAL instance, if available. */
109
148
  export function portFromInstance(instance) {
110
149
  const port = instance.fieldValues.get('_port');
@@ -265,6 +304,17 @@ export function tryResolveCompoundSemanticReturn(expr, instance, paramNames, cal
265
304
  * Returns the HALOpIR if the function name is recognized, or null.
266
305
  */
267
306
  export function tryResolveSemanticCall(fnName, args, instance, paramNames, callArgTexts, paramDefaults, callArgs) {
307
+ // ── Safety (@typecad/safety — safe.read) ────────────────────────────────
308
+ // The safety package owns lowering for `safe.*` callees (v2: only safe.read;
309
+ // safe.pinMode was removed — mode config is the HAL's job via Pin.asInput()
310
+ // etc.). Check the hook before the switch so safety calls never fall
311
+ // through to GPIO/etc. hasSafetyHook() guards the call site (no-op when
312
+ // @typecad/safety absent).
313
+ if (hasSafetyHook()) {
314
+ const op = requireSafetyHook().resolveSemanticCall?.(fnName, callArgs ?? []);
315
+ if (op)
316
+ return op;
317
+ }
268
318
  // Extract MCU port name from instance (set by Pin.fromPort())
269
319
  const port = portFromInstance(instance);
270
320
  switch (fnName) {
@@ -312,6 +362,101 @@ export function tryResolveSemanticCall(fnName, args, instance, paramNames, callA
312
362
  return null;
313
363
  return { operation: "pwm.write", port, pin, duty };
314
364
  }
365
+ // ── RMT ──
366
+ // rmtTxInit/rmtRxInit are POSITIONAL semantic primitives (the ergonomic
367
+ // opts-object form lives in hal/rmt.ts's RmtChannel class, which
368
+ // destructures opts and forwards positionally). The resolver dispatches on
369
+ // the literal callee name, so rmt.ts must call these by their real names
370
+ // (no `as _rmtTxInit` aliasing — that breaks the switch).
371
+ // rmtTxInit(pin, resolutionHz, bit0Hi, bit0Lo, bit1Hi, bit1Lo, msbFirst, queueDepth)
372
+ case "rmtTxInit": {
373
+ const pin = resolveNumericArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
374
+ const resolutionHz = resolveNumericOrExpression(args, 1, instance, paramNames, callArgTexts, paramDefaults);
375
+ const bit0Hi = resolveNumericOrExpression(args, 2, instance, paramNames, callArgTexts, paramDefaults);
376
+ const bit0Lo = resolveNumericOrExpression(args, 3, instance, paramNames, callArgTexts, paramDefaults);
377
+ const bit1Hi = resolveNumericOrExpression(args, 4, instance, paramNames, callArgTexts, paramDefaults);
378
+ const bit1Lo = resolveNumericOrExpression(args, 5, instance, paramNames, callArgTexts, paramDefaults);
379
+ const msbFirst = resolveSemanticArg(args, 6, instance, paramNames, callArgTexts, paramDefaults);
380
+ const queueDepth = resolveNumericOrExpression(args, 7, instance, paramNames, callArgTexts, paramDefaults);
381
+ if (pin === null || resolutionHz === null)
382
+ return null;
383
+ const op = { operation: "rmt.tx_init", port, pin, resolutionHz, bit0Hi, bit0Lo, bit1Hi, bit1Lo };
384
+ if (queueDepth !== null)
385
+ op.queueDepth = queueDepth;
386
+ if (msbFirst !== null)
387
+ op.msbFirst = msbFirst;
388
+ return op;
389
+ }
390
+ case "rmtTxWriteBytes": {
391
+ const pin = resolveNumericArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
392
+ // bytes is an array literal — resolveSemanticArg renders it positionally as "1, 2, 3".
393
+ const bytes = resolveSemanticArg(args, 1, instance, paramNames, callArgTexts, paramDefaults);
394
+ if (pin === null || bytes === null)
395
+ return null;
396
+ return { operation: "rmt.tx_write_bytes", port, pin, bytes };
397
+ }
398
+ case "rmtTxWriteSymbols": {
399
+ const pin = resolveNumericArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
400
+ const symbols = resolveSemanticArg(args, 1, instance, paramNames, callArgTexts, paramDefaults);
401
+ if (pin === null || symbols === null)
402
+ return null;
403
+ return { operation: "rmt.tx_write_symbols", port, pin, symbols };
404
+ }
405
+ case "rmtTxWaitDone": {
406
+ const pin = resolveNumericArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
407
+ const timeoutMs = resolveNumericOrExpression(args, 1, instance, paramNames, callArgTexts, paramDefaults);
408
+ if (pin === null)
409
+ return null;
410
+ const op = { operation: "rmt.tx_wait_done", port, pin };
411
+ if (timeoutMs !== null)
412
+ op.timeoutMs = timeoutMs;
413
+ return op;
414
+ }
415
+ case "rmtTxDeinit": {
416
+ const pin = resolveNumericArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
417
+ if (pin === null)
418
+ return null;
419
+ return { operation: "rmt.tx_deinit", port, pin };
420
+ }
421
+ case "rmtRxInit": {
422
+ const pin = resolveNumericArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
423
+ const resolutionHz = resolveNumericOrExpression(args, 1, instance, paramNames, callArgTexts, paramDefaults);
424
+ if (pin === null || resolutionHz === null)
425
+ return null;
426
+ return { operation: "rmt.rx_init", port, pin, resolutionHz };
427
+ }
428
+ case "rmtRxOnReceived": {
429
+ const pin = resolveNumericArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
430
+ const handler = resolveSemanticArg(args, 1, instance, paramNames, callArgTexts, paramDefaults);
431
+ if (pin === null || handler === null)
432
+ return null;
433
+ return { operation: "rmt.rx_on_received", port, pin, handler };
434
+ }
435
+ case "rmtRxStart": {
436
+ const pin = resolveNumericArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
437
+ if (pin === null)
438
+ return null;
439
+ return { operation: "rmt.rx_start", port, pin };
440
+ }
441
+ case "rmtRxStop": {
442
+ const pin = resolveNumericArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
443
+ if (pin === null)
444
+ return null;
445
+ return { operation: "rmt.rx_stop", port, pin };
446
+ }
447
+ case "rmtRxRead": {
448
+ const pin = resolveNumericArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
449
+ const maxCount = resolveNumericOrExpression(args, 1, instance, paramNames, callArgTexts, paramDefaults);
450
+ if (pin === null || maxCount === null)
451
+ return null;
452
+ return { operation: "rmt.rx_read", port, pin, maxCount };
453
+ }
454
+ case "rmtRxDeinit": {
455
+ const pin = resolveNumericArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
456
+ if (pin === null)
457
+ return null;
458
+ return { operation: "rmt.rx_deinit", port, pin };
459
+ }
315
460
  // ── ADC ──
316
461
  case "adcRead": {
317
462
  const pin = resolveNumericArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
@@ -383,6 +528,583 @@ export function tryResolveSemanticCall(fnName, args, instance, paramNames, callA
383
528
  return null;
384
529
  return { operation: "power.set_cpu_frequency", mhz };
385
530
  }
531
+ case "powerDeepSleepPin": {
532
+ const pin = resolveNumericArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
533
+ const level = resolveNumericArg(args, 1, instance, paramNames, callArgTexts, paramDefaults);
534
+ if (pin === null || level === null)
535
+ return null;
536
+ return { operation: "power.deep_sleep_pin", pin, level };
537
+ }
538
+ // ── WiFi ──
539
+ // Optional string args (password, dns) resolve to the text "undefined"
540
+ // when omitted at the call site (no default in the HAL signature). Treat
541
+ // that as absent so the backend emits its own default ("").
542
+ case "wifiConnect": {
543
+ const ssid = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
544
+ const password = dropUndefined(resolveSemanticArg(args, 1, instance, paramNames, callArgTexts, paramDefaults));
545
+ const timeoutMs = resolveSemanticArg(args, 2, instance, paramNames, callArgTexts, paramDefaults) ?? 15000;
546
+ if (ssid === null)
547
+ return null;
548
+ return {
549
+ operation: "wifi.connect",
550
+ ssid,
551
+ ...(password !== null ? { password } : {}),
552
+ timeoutMs,
553
+ blocking: true,
554
+ };
555
+ }
556
+ case "wifiConnectStart": {
557
+ const ssid = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
558
+ const password = dropUndefined(resolveSemanticArg(args, 1, instance, paramNames, callArgTexts, paramDefaults));
559
+ if (ssid === null)
560
+ return null;
561
+ return { operation: "wifi.connect_start", ssid, ...(password !== null ? { password } : {}) };
562
+ }
563
+ case "wifiDisconnect":
564
+ return { operation: "wifi.disconnect" };
565
+ case "wifiStatus":
566
+ return { operation: "wifi.status" };
567
+ case "wifiIsConnected":
568
+ return { operation: "wifi.is_connected" };
569
+ case "wifiLocalIp":
570
+ return { operation: "wifi.local_ip" };
571
+ case "wifiRssi":
572
+ return { operation: "wifi.rssi" };
573
+ case "wifiMac":
574
+ return { operation: "wifi.mac" };
575
+ case "wifiSetHostname": {
576
+ const name = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
577
+ if (name === null)
578
+ return null;
579
+ return { operation: "wifi.set_hostname", name };
580
+ }
581
+ case "wifiSetStaticIp": {
582
+ const ip = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
583
+ const gateway = resolveSemanticArg(args, 1, instance, paramNames, callArgTexts, paramDefaults);
584
+ const subnet = resolveSemanticArg(args, 2, instance, paramNames, callArgTexts, paramDefaults);
585
+ const dns = dropUndefined(resolveSemanticArg(args, 3, instance, paramNames, callArgTexts, paramDefaults));
586
+ if (ip === null || gateway === null || subnet === null)
587
+ return null;
588
+ return { operation: "wifi.set_static_ip", ip, gateway, subnet, ...(dns !== null ? { dns } : {}) };
589
+ }
590
+ case "wifiSetAutoReconnect": {
591
+ const enabled = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
592
+ if (enabled === null)
593
+ return null;
594
+ return { operation: "wifi.set_auto_reconnect", enabled: enabled === "true" };
595
+ }
596
+ case "wifiSetPowerSave": {
597
+ const mode = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
598
+ if (mode === null)
599
+ return null;
600
+ return { operation: "wifi.set_power_save", mode };
601
+ }
602
+ case "wifiSetTxPower": {
603
+ const dbm = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
604
+ if (dbm === null)
605
+ return null;
606
+ return { operation: "wifi.set_tx_power", dbm };
607
+ }
608
+ case "wifiOnEvent": {
609
+ const event = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
610
+ const handler = resolveSemanticArg(args, 1, instance, paramNames, callArgTexts, paramDefaults);
611
+ if (event === null || handler === null)
612
+ return null;
613
+ return { operation: "wifi.on_event", event: String(event).replace(/^["']|["']$/g, ""), handler };
614
+ }
615
+ case "wifiApStart": {
616
+ const ssid = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
617
+ const password = dropUndefined(resolveSemanticArg(args, 1, instance, paramNames, callArgTexts, paramDefaults));
618
+ const channel = dropUndefined(resolveSemanticArg(args, 2, instance, paramNames, callArgTexts, paramDefaults));
619
+ const hidden = dropUndefined(resolveSemanticArg(args, 3, instance, paramNames, callArgTexts, paramDefaults));
620
+ const maxClients = dropUndefined(resolveSemanticArg(args, 4, instance, paramNames, callArgTexts, paramDefaults));
621
+ if (ssid === null)
622
+ return null;
623
+ return {
624
+ operation: "wifi.ap_start",
625
+ ssid,
626
+ ...(password !== null ? { password } : {}),
627
+ ...(channel !== null ? { channel } : {}),
628
+ ...(hidden !== null ? { hidden } : {}),
629
+ ...(maxClients !== null ? { maxClients } : {}),
630
+ };
631
+ }
632
+ case "wifiApStop":
633
+ return { operation: "wifi.ap_stop" };
634
+ case "wifiApClientCount":
635
+ return { operation: "wifi.ap_client_count" };
636
+ case "wifiApIp":
637
+ return { operation: "wifi.ap_ip" };
638
+ case "wifiApSetChannel": {
639
+ const channel = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
640
+ if (channel === null)
641
+ return null;
642
+ return { operation: "wifi.ap_set_channel", channel };
643
+ }
644
+ case "wifiApSetHidden": {
645
+ const hidden = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
646
+ if (hidden === null)
647
+ return null;
648
+ return { operation: "wifi.ap_set_hidden", hidden };
649
+ }
650
+ case "wifiApSetMaxClients": {
651
+ const maxClients = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
652
+ if (maxClients === null)
653
+ return null;
654
+ return { operation: "wifi.ap_set_max_clients", maxClients };
655
+ }
656
+ case "wifiScan":
657
+ return { operation: "wifi.scan" };
658
+ case "wifiScanStart":
659
+ return { operation: "wifi.scan_start" };
660
+ case "wifiScanCount":
661
+ return { operation: "wifi.scan_count" };
662
+ case "wifiScanSsid": {
663
+ const index = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
664
+ if (index === null)
665
+ return null;
666
+ return { operation: "wifi.scan_ssid", index };
667
+ }
668
+ case "wifiScanRssi": {
669
+ const index = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
670
+ if (index === null)
671
+ return null;
672
+ return { operation: "wifi.scan_rssi", index };
673
+ }
674
+ case "wifiScanEncryption": {
675
+ const index = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
676
+ if (index === null)
677
+ return null;
678
+ return { operation: "wifi.scan_encryption", index };
679
+ }
680
+ case "wifiScanChannel": {
681
+ const index = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
682
+ if (index === null)
683
+ return null;
684
+ return { operation: "wifi.scan_channel", index };
685
+ }
686
+ case "wifiSaveCredentials": {
687
+ const ssid = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
688
+ const password = resolveSemanticArg(args, 1, instance, paramNames, callArgTexts, paramDefaults);
689
+ if (ssid === null || password === null)
690
+ return null;
691
+ return { operation: "wifi.save_credentials", ssid, password };
692
+ }
693
+ case "wifiConnectSaved": {
694
+ const timeoutMs = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults) ?? 15000;
695
+ return { operation: "wifi.connect_saved", timeoutMs };
696
+ }
697
+ case "wifiClearCredentials":
698
+ return { operation: "wifi.clear_credentials" };
699
+ case "wifiWaitConnected": {
700
+ const timeoutMs = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults) ?? 15000;
701
+ return { operation: "wifi.wait_connected", timeoutMs };
702
+ }
703
+ case "wifiWaitDisconnected":
704
+ return { operation: "wifi.wait_disconnected" };
705
+ // ── HTTP ──
706
+ case "httpBegin": {
707
+ const method = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
708
+ const url = resolveSemanticArg(args, 1, instance, paramNames, callArgTexts, paramDefaults);
709
+ if (method === null || url === null)
710
+ return null;
711
+ // this._url resolved through `new HttpRequest(...)` ctor fields stores
712
+ // string literals without quotes; re-quote anything that isn't already
713
+ // a quoted literal or a plain identifier/member expression so the
714
+ // lowering emits valid C++.
715
+ return { operation: "http.begin", method, url: quoteNonIdentifier(url) };
716
+ }
717
+ case "httpReset":
718
+ return { operation: "http.reset" };
719
+ case "httpSetHeader": {
720
+ const name = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
721
+ const value = resolveSemanticArg(args, 1, instance, paramNames, callArgTexts, paramDefaults);
722
+ if (name === null || value === null)
723
+ return null;
724
+ return { operation: "http.set_header", name, value };
725
+ }
726
+ case "httpSetTimeout": {
727
+ const ms = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
728
+ if (ms === null)
729
+ return null;
730
+ return { operation: "http.set_timeout", ms };
731
+ }
732
+ case "httpSetMaxBody": {
733
+ const bytes = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
734
+ if (bytes === null)
735
+ return null;
736
+ return { operation: "http.set_max_body", bytes };
737
+ }
738
+ case "httpSetBody": {
739
+ const data = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
740
+ const json = resolveSemanticArg(args, 1, instance, paramNames, callArgTexts, paramDefaults);
741
+ if (data === null)
742
+ return null;
743
+ return { operation: "http.set_body", data, json: json === "true" };
744
+ }
745
+ case "httpSetInsecure":
746
+ return { operation: "http.set_insecure" };
747
+ case "httpSetCaCert": {
748
+ const pem = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
749
+ if (pem === null)
750
+ return null;
751
+ return { operation: "http.set_ca_cert", pem };
752
+ }
753
+ case "httpSend":
754
+ return { operation: "http.send", blocking: true };
755
+ case "httpSendStart":
756
+ return { operation: "http.send_start" };
757
+ case "httpStatus":
758
+ return { operation: "http.status" };
759
+ case "httpOk":
760
+ return { operation: "http.ok" };
761
+ case "httpBody":
762
+ return { operation: "http.body" };
763
+ case "httpContentLength":
764
+ return { operation: "http.content_length" };
765
+ case "httpResponseHeader": {
766
+ const name = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
767
+ if (name === null)
768
+ return null;
769
+ return { operation: "http.response_header", name };
770
+ }
771
+ // ── BLE (NimBLE GATT peripheral) ──
772
+ case "bleServerBegin": {
773
+ const name = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
774
+ if (name === null)
775
+ return null;
776
+ return { operation: "ble.server_begin", name: quoteNonIdentifier(name) };
777
+ }
778
+ case "bleAdvertiseStart":
779
+ return { operation: "ble.advertise_start" };
780
+ case "bleAdvertiseStop":
781
+ return { operation: "ble.advertise_stop" };
782
+ case "bleAddService": {
783
+ const uuid = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
784
+ if (uuid === null)
785
+ return null;
786
+ return { operation: "ble.add_service", uuid: quoteNonIdentifier(uuid) };
787
+ }
788
+ case "bleAddChar": {
789
+ // The characteristic index is assigned HERE from a per-file counter, not
790
+ // resolved from `this._charCount`. resolveHALReceiver re-resolves the
791
+ // receiver (Ble.server(name)) freshly for each chain level when emitting,
792
+ // so a value stamped onto the instance field in the resolver would be
793
+ // dropped — leaving every characteristic on slot 0, clobbering the
794
+ // previous on_read/on_write handler. Consuming the counter at the single
795
+ // emit site (this plugin, called once per characteristic in declaration
796
+ // order) makes indices sequential (0,1,2,…) and stable. The counter is
797
+ // per-file (reset in resetHALResolver). onRead/onWrite/onConnect don't
798
+ // need a compile-time index — the C++ shim keys them off
799
+ // __tc_ble.current_char, which __tc_ble_add_char sets at runtime from
800
+ // this index.
801
+ const uuid = resolveSemanticArg(args, 1, instance, paramNames, callArgTexts, paramDefaults);
802
+ const type = resolveSemanticArg(args, 2, instance, paramNames, callArgTexts, paramDefaults);
803
+ const perms = resolveNumericOrExpression(args, 3, instance, paramNames, callArgTexts, paramDefaults);
804
+ const svcIndex = resolveNumericOrExpression(args, 4, instance, paramNames, callArgTexts, paramDefaults);
805
+ if (uuid === null || type === null)
806
+ return null;
807
+ // Resolve BleValueType.X → the enum's string value (e.g. Int16 → 'int16').
808
+ const resolvedType = type.startsWith("BleValueType.")
809
+ ? JSON.stringify(type.replace(/^BleValueType\./, "").toLowerCase())
810
+ : quoteNonIdentifier(type);
811
+ // Resolve BlePerm.X | BlePerm.Y → numeric bitmask.
812
+ // The | expression renders as "BlePerm.Read | BlePerm.Notify" via the
813
+ // binary-expression handler in resolveExpressionText, which keeps the
814
+ // raw text. Evaluate it by extracting member names and mapping to values.
815
+ const resolvedPerms = resolveBlePermExpr(perms);
816
+ return {
817
+ operation: "ble.add_char",
818
+ index: getContext().bleCharCounter++,
819
+ uuid: quoteNonIdentifier(uuid),
820
+ type: resolvedType,
821
+ perms: resolvedPerms,
822
+ svcIndex: svcIndex ?? 0,
823
+ };
824
+ }
825
+ case "bleOnRead": {
826
+ const index = resolveNumericOrExpression(args, 0, instance, paramNames, callArgTexts, paramDefaults);
827
+ const handler = resolveSemanticArg(args, 1, instance, paramNames, callArgTexts, paramDefaults);
828
+ if (handler === null)
829
+ return null;
830
+ return { operation: "ble.on_read", index: index ?? 0, handler };
831
+ }
832
+ case "bleOnWrite": {
833
+ const index = resolveNumericOrExpression(args, 0, instance, paramNames, callArgTexts, paramDefaults);
834
+ const handler = resolveSemanticArg(args, 1, instance, paramNames, callArgTexts, paramDefaults);
835
+ if (handler === null)
836
+ return null;
837
+ return { operation: "ble.on_write", index: index ?? 0, handler };
838
+ }
839
+ case "bleOnConnect": {
840
+ const handler = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
841
+ if (handler === null)
842
+ return null;
843
+ return { operation: "ble.on_connect", handler };
844
+ }
845
+ case "bleOnDisconnect": {
846
+ const handler = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
847
+ if (handler === null)
848
+ return null;
849
+ return { operation: "ble.on_disconnect", handler };
850
+ }
851
+ case "bleNotify": {
852
+ const index = resolveNumericOrExpression(args, 0, instance, paramNames, callArgTexts, paramDefaults);
853
+ const value = resolveNumericOrExpression(args, 1, instance, paramNames, callArgTexts, paramDefaults);
854
+ if (value === null)
855
+ return null;
856
+ return { operation: "ble.notify", index: index ?? 0, value };
857
+ }
858
+ case "bleIsConnected":
859
+ return { operation: "ble.is_connected" };
860
+ case "bleClientCount":
861
+ return { operation: "ble.client_count" };
862
+ case "bleStatus":
863
+ return { operation: "ble.status" };
864
+ case "bleSetName": {
865
+ const name = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
866
+ if (name === null)
867
+ return null;
868
+ return { operation: "ble.set_name", name: quoteNonIdentifier(name) };
869
+ }
870
+ case "bleUntilConnected": {
871
+ const timeoutMs = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults) ?? 0;
872
+ return { operation: "ble.until_connected", timeoutMs, blocking: true };
873
+ }
874
+ case "bleUntilConnectedStart":
875
+ return { operation: "ble.until_connected_start" };
876
+ case "bleSetTxPower": {
877
+ const dbm = resolveNumericOrExpression(args, 0, instance, paramNames, callArgTexts, paramDefaults);
878
+ if (dbm === null)
879
+ return null;
880
+ return { operation: "ble.set_tx_power", dbm };
881
+ }
882
+ // ── Preferences (NVS key/value store) ──
883
+ case "preferencesBegin": {
884
+ const ns = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
885
+ const ro = resolveSemanticArg(args, 1, instance, paramNames, callArgTexts, paramDefaults);
886
+ if (ns === null)
887
+ return null;
888
+ return { operation: "preferences.begin", namespace: quoteNonIdentifier(ns), readOnly: ro === "true" };
889
+ }
890
+ case "preferencesEnd":
891
+ return { operation: "preferences.end" };
892
+ case "preferencesClear":
893
+ return { operation: "preferences.clear" };
894
+ case "preferencesRemove": {
895
+ const key = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
896
+ if (key === null)
897
+ return null;
898
+ return { operation: "preferences.remove", key: quoteNonIdentifier(key) };
899
+ }
900
+ case "preferencesPutInt": {
901
+ const key = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
902
+ const value = resolveNumericOrExpression(args, 1, instance, paramNames, callArgTexts, paramDefaults);
903
+ if (key === null || value === null)
904
+ return null;
905
+ return { operation: "preferences.put_int", key: quoteNonIdentifier(key), value };
906
+ }
907
+ case "preferencesGetInt": {
908
+ const key = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
909
+ const def = resolveNumericOrExpression(args, 1, instance, paramNames, callArgTexts, paramDefaults) ?? 0;
910
+ if (key === null)
911
+ return null;
912
+ return { operation: "preferences.get_int", key: quoteNonIdentifier(key), defaultValue: def };
913
+ }
914
+ case "preferencesPutUInt": {
915
+ const key = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
916
+ const value = resolveNumericOrExpression(args, 1, instance, paramNames, callArgTexts, paramDefaults);
917
+ if (key === null || value === null)
918
+ return null;
919
+ return { operation: "preferences.put_uint", key: quoteNonIdentifier(key), value };
920
+ }
921
+ case "preferencesGetUInt": {
922
+ const key = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
923
+ const def = resolveNumericOrExpression(args, 1, instance, paramNames, callArgTexts, paramDefaults) ?? 0;
924
+ if (key === null)
925
+ return null;
926
+ return { operation: "preferences.get_uint", key: quoteNonIdentifier(key), defaultValue: def };
927
+ }
928
+ case "preferencesPutBool": {
929
+ const key = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
930
+ const value = resolveSemanticArg(args, 1, instance, paramNames, callArgTexts, paramDefaults);
931
+ if (key === null || value === null)
932
+ return null;
933
+ return { operation: "preferences.put_bool", key: quoteNonIdentifier(key), value: value === "true" };
934
+ }
935
+ case "preferencesGetBool": {
936
+ const key = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
937
+ const def = resolveSemanticArg(args, 1, instance, paramNames, callArgTexts, paramDefaults);
938
+ if (key === null)
939
+ return null;
940
+ return { operation: "preferences.get_bool", key: quoteNonIdentifier(key), defaultValue: def === "true" };
941
+ }
942
+ case "preferencesPutFloat": {
943
+ const key = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
944
+ const value = resolveNumericOrExpression(args, 1, instance, paramNames, callArgTexts, paramDefaults);
945
+ if (key === null || value === null)
946
+ return null;
947
+ return { operation: "preferences.put_float", key: quoteNonIdentifier(key), value };
948
+ }
949
+ case "preferencesGetFloat": {
950
+ const key = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
951
+ const def = resolveNumericOrExpression(args, 1, instance, paramNames, callArgTexts, paramDefaults) ?? 0;
952
+ if (key === null)
953
+ return null;
954
+ return { operation: "preferences.get_float", key: quoteNonIdentifier(key), defaultValue: def };
955
+ }
956
+ case "preferencesPutString": {
957
+ const key = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
958
+ const value = resolveSemanticArg(args, 1, instance, paramNames, callArgTexts, paramDefaults);
959
+ if (key === null || value === null)
960
+ return null;
961
+ return { operation: "preferences.put_string", key: quoteNonIdentifier(key), value: quoteNonIdentifier(value) };
962
+ }
963
+ case "preferencesGetString": {
964
+ const key = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
965
+ const def = resolveSemanticArg(args, 1, instance, paramNames, callArgTexts, paramDefaults) ?? '""';
966
+ if (key === null)
967
+ return null;
968
+ return { operation: "preferences.get_string", key: quoteNonIdentifier(key), defaultValue: quoteNonIdentifier(def) };
969
+ }
970
+ // ── FS (filesystem) ──
971
+ case "fsBegin":
972
+ return { operation: "fs.begin" };
973
+ case "fsReadText": {
974
+ const path = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
975
+ if (path === null)
976
+ return null;
977
+ return { operation: "fs.read_text", path };
978
+ }
979
+ case "fsWriteText": {
980
+ const path = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
981
+ const content = resolveSemanticArg(args, 1, instance, paramNames, callArgTexts, paramDefaults);
982
+ if (path === null || content === null)
983
+ return null;
984
+ return { operation: "fs.write_text", path, content };
985
+ }
986
+ case "fsExists": {
987
+ const path = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
988
+ if (path === null)
989
+ return null;
990
+ return { operation: "fs.exists", path };
991
+ }
992
+ case "fsRemove": {
993
+ const path = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
994
+ if (path === null)
995
+ return null;
996
+ return { operation: "fs.remove", path };
997
+ }
998
+ // ── mDNS ──
999
+ case "mdnsStart": {
1000
+ const hostname = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
1001
+ if (hostname === null)
1002
+ return null;
1003
+ return { operation: "mdns.start", hostname };
1004
+ }
1005
+ case "mdnsSetHostname": {
1006
+ const name = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
1007
+ if (name === null)
1008
+ return null;
1009
+ return { operation: "mdns.set_hostname", name };
1010
+ }
1011
+ case "mdnsAddService": {
1012
+ const instanceName = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
1013
+ const proto = resolveSemanticArg(args, 1, instance, paramNames, callArgTexts, paramDefaults);
1014
+ const port = resolveNumericOrExpression(args, 2, instance, paramNames, callArgTexts, paramDefaults);
1015
+ if (instanceName === null || proto === null || port === null)
1016
+ return null;
1017
+ return { operation: "mdns.add_service", instance: instanceName, proto, port };
1018
+ }
1019
+ case "mdnsAnnounce":
1020
+ return { operation: "mdns.announce" };
1021
+ case "mdnsStop":
1022
+ return { operation: "mdns.stop" };
1023
+ // ── MQTT ──
1024
+ case "mqttConnect": {
1025
+ const brokerUri = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
1026
+ const clientId = resolveSemanticArg(args, 1, instance, paramNames, callArgTexts, paramDefaults);
1027
+ if (brokerUri === null || clientId === null)
1028
+ return null;
1029
+ return { operation: "mqtt.connect", brokerUri, clientId };
1030
+ }
1031
+ case "mqttOnMessage": {
1032
+ const handler = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
1033
+ if (handler === null)
1034
+ return null;
1035
+ return { operation: "mqtt.on_message", handler };
1036
+ }
1037
+ case "mqttSubscribe": {
1038
+ const topic = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
1039
+ if (topic === null)
1040
+ return null;
1041
+ return { operation: "mqtt.subscribe", topic };
1042
+ }
1043
+ case "mqttPublish": {
1044
+ const topic = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
1045
+ const data = resolveSemanticArg(args, 1, instance, paramNames, callArgTexts, paramDefaults);
1046
+ if (topic === null || data === null)
1047
+ return null;
1048
+ return { operation: "mqtt.publish", topic, data };
1049
+ }
1050
+ case "mqttConnected":
1051
+ return { operation: "mqtt.connected" };
1052
+ case "mqttDisconnect":
1053
+ return { operation: "mqtt.disconnect" };
1054
+ // ── OTA ──
1055
+ case "otaFromUrl": {
1056
+ const url = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
1057
+ if (url === null)
1058
+ return null;
1059
+ return { operation: "ota.from_url", url };
1060
+ }
1061
+ case "otaBegin":
1062
+ return { operation: "ota.begin" };
1063
+ case "otaWrite": {
1064
+ const chunk = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
1065
+ if (chunk === null)
1066
+ return null;
1067
+ return { operation: "ota.write", chunk };
1068
+ }
1069
+ case "otaApply":
1070
+ return { operation: "ota.apply" };
1071
+ // ── Temperature (die temp) ──
1072
+ case "tempRead":
1073
+ return { operation: "temp.read" };
1074
+ // ── Hardware timer (GPTimer) ──
1075
+ case "hwtimerSetFrequency": {
1076
+ const inst = resolveNumericOrExpression(args, 0, instance, paramNames, callArgTexts, paramDefaults);
1077
+ const hz = resolveNumericOrExpression(args, 1, instance, paramNames, callArgTexts, paramDefaults);
1078
+ if (inst === null || hz === null)
1079
+ return null;
1080
+ return { operation: "hwtimer.set_frequency", instance: inst, hz };
1081
+ }
1082
+ case "hwtimerOnOverflow": {
1083
+ const inst = resolveNumericOrExpression(args, 0, instance, paramNames, callArgTexts, paramDefaults);
1084
+ const handler = resolveSemanticArg(args, 1, instance, paramNames, callArgTexts, paramDefaults);
1085
+ if (inst === null || handler === null)
1086
+ return null;
1087
+ return { operation: "hwtimer.on_overflow", instance: inst, handler };
1088
+ }
1089
+ case "hwtimerStart": {
1090
+ const inst = resolveNumericOrExpression(args, 0, instance, paramNames, callArgTexts, paramDefaults);
1091
+ if (inst === null)
1092
+ return null;
1093
+ return { operation: "hwtimer.start", instance: inst };
1094
+ }
1095
+ case "hwtimerStop": {
1096
+ const inst = resolveNumericOrExpression(args, 0, instance, paramNames, callArgTexts, paramDefaults);
1097
+ if (inst === null)
1098
+ return null;
1099
+ return { operation: "hwtimer.stop", instance: inst };
1100
+ }
1101
+ // ── Capacitive touch pins ──
1102
+ case "capacitiveRead": {
1103
+ const pin = resolveNumericArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
1104
+ if (pin === null)
1105
+ return null;
1106
+ return { operation: "capacitive.read", port, pin };
1107
+ }
386
1108
  // ── Interrupts ──
387
1109
  case "interruptAttach": {
388
1110
  const pin = resolveNumericArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
@@ -580,6 +1302,16 @@ export function tryResolveSemanticCall(fnName, args, instance, paramNames, callA
580
1302
  return null;
581
1303
  return { operation: "spi.set_bit_order", bus, order };
582
1304
  }
1305
+ case "spiReadBuffer": {
1306
+ const bus = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
1307
+ const count = resolveNumericOrExpression(args, 1, instance, paramNames, callArgTexts, paramDefaults);
1308
+ if (bus === null || count === null)
1309
+ return null;
1310
+ // The buffer arg (args[2]) is only a placeholder for type resolution;
1311
+ // the real target is the caller's variable, rewritten from
1312
+ // __HAL_READ_BUF__ by replaceHalReadBufferPlaceholder.
1313
+ return { operation: "spi.read_buffer", bus, count, buffer: "__HAL_READ_BUF__" };
1314
+ }
583
1315
  // ── UART ──
584
1316
  case "uartBegin": {
585
1317
  const port = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);