@typecad/cuttlefish 1.0.0-alpha.3 → 1.0.0-alpha.7

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 (123) hide show
  1. package/README.md +4 -4
  2. package/dist/api/shared/display-adapter.d.ts +2 -1
  3. package/dist/api/shared/display-adapter.js +8 -1
  4. package/dist/api/shared/display-adapters/sdl.js +9 -2
  5. package/dist/api/shared/display-profile.d.ts +20 -3
  6. package/dist/api/shared/display-profile.js +21 -6
  7. package/dist/api/shared/framework-manifest-registry.d.ts +9 -0
  8. package/dist/api/shared/framework-manifest-registry.js +25 -0
  9. package/dist/api/shared/framework-manifest.d.ts +462 -0
  10. package/dist/api/shared/framework-manifest.js +149 -0
  11. package/dist/api/shared/glcdfont.d.ts +12 -0
  12. package/dist/api/shared/glcdfont.js +124 -0
  13. package/dist/api/shared/graphics-strategy.d.ts +28 -0
  14. package/dist/api/shared/hal-op-ir.d.ts +427 -1
  15. package/dist/api/shared/hal-op-ir.js +95 -1
  16. package/dist/api/shared/index.d.ts +11 -1
  17. package/dist/api/shared/index.js +14 -0
  18. package/dist/api/shared/native-display-op-resolver.d.ts +10 -0
  19. package/dist/api/shared/native-display-op-resolver.js +64 -0
  20. package/dist/api/shared/platform-strategy.d.ts +9 -0
  21. package/dist/api/shared/promise-runtime.js +78 -0
  22. package/dist/api/shared/types.d.ts +8 -0
  23. package/dist/api/shared/validate-framework-manifest.d.ts +28 -0
  24. package/dist/api/shared/validate-framework-manifest.js +417 -0
  25. package/dist/cli-utils.d.ts +1 -0
  26. package/dist/cli-utils.js +3 -1
  27. package/dist/cli.js +175 -4
  28. package/dist/config-loader.js +20 -1
  29. package/dist/config-schema.d.ts +36 -36
  30. package/dist/create/board-spec.d.ts +4 -4
  31. package/dist/create/index.d.ts +1 -1
  32. package/dist/create/index.js +1 -1
  33. package/dist/create/init-scaffold.d.ts +3 -0
  34. package/dist/create/init-scaffold.js +74 -2
  35. package/dist/create/init-templates.d.ts +4 -0
  36. package/dist/create/init-templates.js +217 -17
  37. package/dist/create/init-wizard.js +20 -1
  38. package/dist/emit/cpp-emitter.js +4 -3
  39. package/dist/emit/emitters/emitter-context.d.ts +5 -0
  40. package/dist/emit/emitters/function-emitter-impl.js +69 -59
  41. package/dist/emit/emitters/output-finalizer.d.ts +6 -0
  42. package/dist/emit/emitters/output-finalizer.js +21 -11
  43. package/dist/emit/emitters/setup.js +155 -0
  44. package/dist/emit/emitters/top-level-prep.js +2 -0
  45. package/dist/emit/emitters/ui-emitter.js +23 -8
  46. package/dist/emit/expression-renderer.js +10 -1
  47. package/dist/emit/snprintf-helpers.js +8 -0
  48. package/dist/emit/statement-renderer.js +13 -0
  49. package/dist/emit/utils/async-state-machine.js +185 -116
  50. package/dist/emit/utils/hal-op-cpp-type.d.ts +6 -0
  51. package/dist/emit/utils/hal-op-cpp-type.js +40 -0
  52. package/dist/ir/adc-range-validation.js +26 -25
  53. package/dist/ir/build-ir.js +5 -1
  54. package/dist/ir/expression-to-ir.js +57 -6
  55. package/dist/ir/feature-registry.js +7 -25
  56. package/dist/ir/hal/hal-emitter.d.ts +5 -2
  57. package/dist/ir/hal/hal-emitter.js +40 -12
  58. package/dist/ir/hal/hal-parser.d.ts +6 -0
  59. package/dist/ir/hal/hal-parser.js +74 -0
  60. package/dist/ir/hal/hal-plugins.js +571 -0
  61. package/dist/ir/identifier-collector.js +18 -0
  62. package/dist/ir/interrupt-analysis.js +8 -3
  63. package/dist/ir/memory-budget-validation.js +1 -0
  64. package/dist/ir/network-validation.d.ts +4 -0
  65. package/dist/ir/network-validation.js +184 -0
  66. package/dist/ir/ownership-analysis.js +33 -1
  67. package/dist/ir/peripheral-ownership.js +5 -0
  68. package/dist/ir/peripheral-validation.d.ts +1 -1
  69. package/dist/ir/peripheral-validation.js +6 -3
  70. package/dist/ir/pin-alias-conflict.d.ts +1 -1
  71. package/dist/ir/pin-alias-conflict.js +2 -1
  72. package/dist/ir/pin-capability-validation.js +34 -32
  73. package/dist/ir/pin-mode-validation.js +5 -0
  74. package/dist/ir/pin-safety.d.ts +1 -1
  75. package/dist/ir/pin-safety.js +2 -1
  76. package/dist/ir/program-analysis.d.ts +39 -0
  77. package/dist/ir/program-analysis.js +192 -0
  78. package/dist/ir/pulldown-validation.d.ts +1 -1
  79. package/dist/ir/pulldown-validation.js +2 -1
  80. package/dist/ir/pwm-timer-sharing.d.ts +1 -1
  81. package/dist/ir/pwm-timer-sharing.js +2 -1
  82. package/dist/ir/resource-analysis.js +2 -0
  83. package/dist/ir/timer0-pwm-timing-conflict.d.ts +1 -1
  84. package/dist/ir/timer0-pwm-timing-conflict.js +2 -1
  85. package/dist/ir/timing-validation.d.ts +6 -1
  86. package/dist/ir/timing-validation.js +51 -12
  87. package/dist/ir/transformers/expressions.js +58 -0
  88. package/dist/ir/transformers/hal-emit-helpers.js +1 -1
  89. package/dist/ir/transformers/variables.js +86 -19
  90. package/dist/ir/try-catch-validation.js +2 -0
  91. package/dist/ir/type-resolution.js +2 -2
  92. package/dist/ir/unit-suspicion-validation.js +9 -7
  93. package/dist/ir/validation-orchestrator.js +9 -7
  94. package/dist/libdef/c-to-decl.d.ts +27 -0
  95. package/dist/libdef/c-to-decl.js +397 -0
  96. package/dist/libdef/component-decls.d.ts +2 -0
  97. package/dist/libdef/component-decls.js +6 -0
  98. package/dist/libdef/component-discovery.d.ts +43 -0
  99. package/dist/libdef/component-discovery.js +83 -0
  100. package/dist/libdef/cpp-to-decl.d.ts +9 -0
  101. package/dist/libdef/cpp-to-decl.js +72 -0
  102. package/dist/libdef/idf-discovery.d.ts +7 -0
  103. package/dist/libdef/idf-discovery.js +59 -0
  104. package/dist/libdef/registry.js +5 -2
  105. package/dist/licenses.d.ts +185 -0
  106. package/dist/licenses.js +963 -0
  107. package/dist/lint-cache.d.ts +59 -0
  108. package/dist/lint-cache.js +257 -0
  109. package/dist/orchestrator/graph-builder.js +6 -2
  110. package/dist/stores/display-profile-store.d.ts +1 -0
  111. package/dist/stores/display-profile-store.js +1 -0
  112. package/dist/testing.d.ts +4 -2
  113. package/dist/testing.js +4 -2
  114. package/dist/transpile.d.ts +3 -0
  115. package/dist/transpile.js +78 -32
  116. package/dist/types.d.ts +5 -1
  117. package/dist/ui-hook.d.ts +17 -1
  118. package/dist/utils/cli.js +71 -1
  119. package/dist/utils/fs.d.ts +13 -0
  120. package/dist/utils/fs.js +50 -0
  121. package/package.json +9 -4
  122. package/dist/ir/heap-array-validation.d.ts +0 -24
  123. package/dist/ir/heap-array-validation.js +0 -29
@@ -4,6 +4,7 @@
4
4
  * Extracted from cpp-emitter.ts
5
5
  */
6
6
  import { escapeCppStringLiteral } from "../../utils/strings.js";
7
+ import { routeHALOp } from "../route-hal-op.js";
7
8
  /**
8
9
  * Converts a string to PascalCase.
9
10
  * Used for generating class names from function names.
@@ -53,6 +54,20 @@ export function generateAsyncTaskClass(fnName, fnStatements, strategy, knownFunc
53
54
  segments.push({ preStatements: currentPre, awaitedCallee: stmt.callee, awaitedArgs: stmt.args });
54
55
  currentPre = [];
55
56
  }
57
+ else if (
58
+ // Chained HAL calls (e.g. `await Http.get(url).send()`) resolve to a
59
+ // block of hal-ops whose LAST statement is the awaited wait marker.
60
+ // Flatten it so the split sees the marker; the leading ops (begin,
61
+ // setters) run as pre-statements of the same segment.
62
+ stmt.kind === "block" &&
63
+ stmt.body.length > 0 &&
64
+ stmt.body[stmt.body.length - 1].kind === "call" &&
65
+ stmt.body[stmt.body.length - 1].isAwaited) {
66
+ const last = stmt.body[stmt.body.length - 1];
67
+ currentPre.push(...stmt.body.slice(0, -1));
68
+ segments.push({ preStatements: currentPre, awaitedCallee: last.callee, awaitedArgs: last.args });
69
+ currentPre = [];
70
+ }
56
71
  else {
57
72
  currentPre.push(stmt);
58
73
  }
@@ -101,16 +116,74 @@ export function generateAsyncTaskClass(fnName, fnStatements, strategy, knownFunc
101
116
  tapMembers.set(member, i);
102
117
  }
103
118
  }
119
+ // Collect net-wait (WiFi/HTTP/HAL) markers: awaited hal-ops rewritten to
120
+ // __WIFI_WAIT__/__HTTP_WAIT__/__HAL_WAIT__ calls carrying the op as a
121
+ // hal-expr arg. The strategy lowers the op to start code + poll condition.
122
+ const netInfoMap = new Map();
123
+ for (let i = 0; i < segments.length; i++) {
124
+ const seg = segments[i];
125
+ if ((seg.awaitedCallee === "__WIFI_WAIT__" || seg.awaitedCallee === "__HTTP_WAIT__" || seg.awaitedCallee === "__BLE_WAIT__" || seg.awaitedCallee === "__HAL_WAIT__") &&
126
+ seg.awaitedArgs[0]?.kind === "hal-expr") {
127
+ const op = seg.awaitedArgs[0].operation;
128
+ netInfoMap.set(i, netWaitInfo(op, strategy));
129
+ }
130
+ }
131
+ // ── Render each state ──────────────────────────────────────────────────
132
+ // Decomposition: a state's ENTRY condition comes from the await that ended
133
+ // the PREVIOUS segment (edge / tap / net poll / timed deadline); once the
134
+ // condition fires, the segment body runs and the await ending THIS segment
135
+ // is armed (pin snapshot / tap snapshot / net start op / deadline).
136
+ // Lines that arm the await ending segment i and advance the state.
137
+ const armNextLines = (i, pad) => {
138
+ const seg = segments[i];
139
+ const lines = [];
140
+ if (seg.awaitedCallee === undefined) {
141
+ lines.push(`${pad}_state = ${isCyclic ? "STATE_0" : "STATE_DONE"};`);
142
+ return lines;
143
+ }
144
+ const edge = edgeInfoMap.get(i);
145
+ const tap = tapInfoMap.get(i);
146
+ const net = netInfoMap.get(i);
147
+ if (edge) {
148
+ lines.push(`${pad}_edgePrev_p${edge.pin} = digitalRead(${edge.pin});`);
149
+ if (edge.timeout !== null) {
150
+ lines.push(`${pad}_waitUntil = ${strategy.currentTimeMillis()} + ${edge.timeout};`);
151
+ }
152
+ }
153
+ else if (tap) {
154
+ lines.push(`${pad}_tapPrev_${i} = __ui_tap_seq;`);
155
+ }
156
+ else if (net) {
157
+ for (const startLine of net.startLines)
158
+ lines.push(`${pad}${startLine}`);
159
+ if (net.timeoutExpr !== null) {
160
+ lines.push(`${pad}_waitUntil = ${strategy.currentTimeMillis()} + ${net.timeoutExpr};`);
161
+ }
162
+ }
163
+ else {
164
+ // Plain timed wait (await delay(ms)) — arm its deadline.
165
+ const ms = seg.awaitedArgs[0] ? renderExpression(seg.awaitedArgs[0], strategy) : "0";
166
+ lines.push(`${pad}_waitUntil = ${strategy.currentTimeMillis()} + ${ms};`);
167
+ }
168
+ lines.push(`${pad}_state = STATE_${i + 1};`);
169
+ return lines;
170
+ };
104
171
  const caseLines = [];
105
172
  for (let i = 0; i < segments.length; i++) {
106
173
  const seg = segments[i];
107
174
  const stateName = `STATE_${i}`;
108
- const isTerminal = seg.awaitedCallee === undefined;
109
175
  const body = [];
110
- const edgeSetup = edgeInfoMap.get(i);
111
176
  const edgePoll = i > 0 ? edgeInfoMap.get(i - 1) : undefined;
112
- const tapSetup = tapInfoMap.get(i);
113
177
  const tapPoll = i > 0 ? tapInfoMap.get(i - 1) : undefined;
178
+ const netPoll = i > 0 ? netInfoMap.get(i - 1) : undefined;
179
+ // Segment body + arming of the next await, at the given indent.
180
+ const runSegment = (pad) => {
181
+ const lines = [];
182
+ for (const stmt of seg.preStatements)
183
+ lines.push(`${pad}${renderStmt(stmt)}`);
184
+ lines.push(...armNextLines(i, pad));
185
+ return lines;
186
+ };
114
187
  if (edgePoll) {
115
188
  // Poll state: check pin transition via digitalRead
116
189
  const prevVar = `_edgePrev_p${edgePoll.pin}`;
@@ -124,53 +197,12 @@ export function generateAsyncTaskClass(fnName, fnStatements, strategy, knownFunc
124
197
  body.push(` int _cur = digitalRead(${edgePoll.pin});`);
125
198
  body.push(` if (${fullCond}) {`);
126
199
  body.push(` ${prevVar} = _cur;`);
127
- for (const stmt of seg.preStatements)
128
- body.push(` ${renderStmt(stmt)}`);
129
- if (edgeSetup) {
130
- // Next await is also edge detection — set up its pin tracking
131
- body.push(` _edgePrev_p${edgeSetup.pin} = digitalRead(${edgeSetup.pin});`);
132
- if (edgeSetup.timeout !== null) {
133
- body.push(` _waitUntil = ${strategy.currentTimeMillis()} + ${edgeSetup.timeout};`);
134
- }
135
- body.push(` _state = STATE_${i + 1};`);
136
- }
137
- else if (!isTerminal) {
138
- const ms = seg.awaitedArgs[0] ? renderExpression(seg.awaitedArgs[0], strategy) : "0";
139
- body.push(` _waitUntil = ${strategy.currentTimeMillis()} + ${ms};`);
140
- body.push(` _state = STATE_${i + 1};`);
141
- }
142
- else {
143
- body.push(` _state = ${isCyclic ? "STATE_0" : "STATE_DONE"};`);
144
- }
200
+ body.push(...runSegment(` `));
145
201
  body.push(` } else {`);
146
202
  body.push(` ${prevVar} = _cur;`);
147
203
  body.push(` }`);
148
204
  body.push(` }`);
149
205
  }
150
- else if (edgeSetup) {
151
- // Setup state: capture initial pin state before polling
152
- const prevVar = `_edgePrev_p${edgeSetup.pin}`;
153
- if (i === 0) {
154
- for (const stmt of seg.preStatements)
155
- body.push(` ${renderStmt(stmt)}`);
156
- body.push(` ${prevVar} = digitalRead(${edgeSetup.pin});`);
157
- if (edgeSetup.timeout !== null) {
158
- body.push(` _waitUntil = ${strategy.currentTimeMillis()} + ${edgeSetup.timeout};`);
159
- }
160
- body.push(` _state = STATE_${i + 1};`);
161
- }
162
- else {
163
- body.push(` if (${strategy.currentTimeMillis()} >= _waitUntil) {`);
164
- for (const stmt of seg.preStatements)
165
- body.push(` ${renderStmt(stmt)}`);
166
- body.push(` ${prevVar} = digitalRead(${edgeSetup.pin});`);
167
- if (edgeSetup.timeout !== null) {
168
- body.push(` _waitUntil = ${strategy.currentTimeMillis()} + ${edgeSetup.timeout};`);
169
- }
170
- body.push(` _state = STATE_${i + 1};`);
171
- body.push(` }`);
172
- }
173
- }
174
206
  else if (tapPoll) {
175
207
  // Tap-poll state: a previous segment ended with `await ui.onTap()`.
176
208
  // Wait until __ui_tap_seq bumps from the captured snapshot. A per-node
@@ -181,81 +213,31 @@ export function generateAsyncTaskClass(fnName, fnStatements, strategy, knownFunc
181
213
  ? `__ui_tap_seq != ${prevVar}`
182
214
  : `(__ui_tap_seq != ${prevVar}) && (__ui_tap_node == ${nodeFilter})`;
183
215
  body.push(` if (${cond}) {`);
184
- for (const stmt of seg.preStatements)
185
- body.push(` ${renderStmt(stmt)}`);
186
- // What does the NEXT await (ending this segment) look like? Re-derive
187
- // from the maps via a fresh lookup so TS doesn't narrow edgeSetup away.
188
- const nextEdge = edgeInfoMap.get(i) ?? null;
189
- if (tapSetup) {
190
- // Next await is also a tap — capture its snapshot now.
191
- body.push(` _tapPrev_${i} = __ui_tap_seq;`);
192
- body.push(` _state = STATE_${i + 1};`);
193
- }
194
- else if (nextEdge) {
195
- body.push(` _edgePrev_p${nextEdge.pin} = digitalRead(${nextEdge.pin});`);
196
- if (nextEdge.timeout !== null) {
197
- body.push(` _waitUntil = ${strategy.currentTimeMillis()} + ${nextEdge.timeout};`);
198
- }
199
- body.push(` _state = STATE_${i + 1};`);
200
- }
201
- else if (!isTerminal) {
202
- // Next await is a normal timed wait (delay) — arm its deadline.
203
- const ms = seg.awaitedArgs[0] ? renderExpression(seg.awaitedArgs[0], strategy) : "0";
204
- body.push(` _waitUntil = ${strategy.currentTimeMillis()} + ${ms};`);
205
- body.push(` _state = STATE_${i + 1};`);
206
- }
207
- else {
208
- body.push(` _state = ${isCyclic ? "STATE_0" : "STATE_DONE"};`);
209
- }
216
+ body.push(...runSegment(` `));
210
217
  body.push(` }`);
211
218
  }
212
- else if (tapSetup && !edgePoll) {
213
- // Tap-setup state: this segment ends with `await ui.onTap()`. Capture the
214
- // current tap counter so the poll state can detect the NEXT bump.
215
- // (edgePoll takes precedence when the previous await was an edge.)
216
- if (i === 0) {
217
- for (const stmt of seg.preStatements)
218
- body.push(` ${renderStmt(stmt)}`);
219
- body.push(` _tapPrev_${i} = __ui_tap_seq;`);
220
- body.push(` _state = STATE_${i + 1};`);
221
- }
222
- else {
223
- body.push(` if (${strategy.currentTimeMillis()} >= _waitUntil) {`);
224
- for (const stmt of seg.preStatements)
225
- body.push(` ${renderStmt(stmt)}`);
226
- body.push(` _tapPrev_${i} = __ui_tap_seq;`);
227
- body.push(` _state = STATE_${i + 1};`);
228
- body.push(` }`);
229
- }
219
+ else if (netPoll) {
220
+ // Net-poll state: a previous segment ended with an awaited WiFi/HTTP
221
+ // op. Wait until its poll condition fires (or its deadline expires).
222
+ const deadline = `${strategy.currentTimeMillis()} >= _waitUntil`;
223
+ const cond = netPoll.pollCond === null
224
+ ? deadline
225
+ : netPoll.timeoutExpr !== null
226
+ ? `(${netPoll.pollCond}) || ${deadline}`
227
+ : netPoll.pollCond;
228
+ body.push(` if (${cond}) {`);
229
+ body.push(...runSegment(` `));
230
+ body.push(` }`);
231
+ }
232
+ else if (i === 0) {
233
+ // First state runs immediately.
234
+ body.push(...runSegment(` `));
230
235
  }
231
236
  else {
232
- // Normal timed-wait state
233
- if (i === 0) {
234
- for (const stmt of seg.preStatements)
235
- body.push(` ${renderStmt(stmt)}`);
236
- if (!isTerminal) {
237
- const ms = seg.awaitedArgs[0] ? renderExpression(seg.awaitedArgs[0], strategy) : "0";
238
- body.push(` _waitUntil = ${strategy.currentTimeMillis()} + ${ms};`);
239
- body.push(` _state = STATE_${i + 1};`);
240
- }
241
- else {
242
- body.push(` _state = ${isCyclic ? "STATE_0" : "STATE_DONE"};`);
243
- }
244
- }
245
- else {
246
- body.push(` if (${strategy.currentTimeMillis()} >= _waitUntil) {`);
247
- for (const stmt of seg.preStatements)
248
- body.push(` ${renderStmt(stmt)}`);
249
- if (!isTerminal) {
250
- const ms = seg.awaitedArgs[0] ? renderExpression(seg.awaitedArgs[0], strategy) : "0";
251
- body.push(` _waitUntil = ${strategy.currentTimeMillis()} + ${ms};`);
252
- body.push(` _state = STATE_${i + 1};`);
253
- }
254
- else {
255
- body.push(` _state = ${isCyclic ? "STATE_0" : "STATE_DONE"};`);
256
- }
257
- body.push(` }`);
258
- }
237
+ // Previous await was a plain timed wait — poll its deadline.
238
+ body.push(` if (${strategy.currentTimeMillis()} >= _waitUntil) {`);
239
+ body.push(...runSegment(` `));
240
+ body.push(` }`);
259
241
  }
260
242
  caseLines.push(` case ${stateName}:`, ` {`, ...body, ` }`, ` break;`);
261
243
  }
@@ -331,6 +313,93 @@ function renderExpression(expr, strategy) {
331
313
  return "/* complex expr */";
332
314
  }
333
315
  }
316
+ /** Numeric-or-rendered HAL field → C++ text. */
317
+ function ms(v) {
318
+ return String(v);
319
+ }
320
+ /** True when a timeout field is the literal 0 (wait forever, no deadline). */
321
+ function isZeroTimeout(v) {
322
+ return v === undefined || v === null || String(v).trim() === "0";
323
+ }
324
+ /**
325
+ * Map an awaitable HAL op to its start statements + poll condition, lowering
326
+ * the start op and poll predicate through the platform strategy. Keep the set
327
+ * of handled ops in sync with AWAITABLE_HAL_OPS in ir/transformers/expressions.ts.
328
+ *
329
+ * Falls back to running the blocking form as the "start" with an immediate
330
+ * completion when the strategy can't lower the split ops.
331
+ */
332
+ function netWaitInfo(op, strategy) {
333
+ const o = op;
334
+ const route = (routedOp) => routeHALOp(routedOp, strategy);
335
+ const expr = (operation) => route({ operation })?.expression ?? null;
336
+ switch (op.operation) {
337
+ case "timing.delay":
338
+ return { startLines: [], pollCond: null, timeoutExpr: ms(o.ms) };
339
+ case "wifi.connect": {
340
+ const start = route({ operation: "wifi.connect_start", ssid: o.ssid, password: o.password });
341
+ const poll = expr("wifi.is_connected");
342
+ if (start?.code && poll) {
343
+ return {
344
+ startLines: [start.code],
345
+ pollCond: poll,
346
+ timeoutExpr: isZeroTimeout(o.timeoutMs) ? null : ms(o.timeoutMs),
347
+ };
348
+ }
349
+ break;
350
+ }
351
+ case "wifi.wait_connected": {
352
+ const poll = expr("wifi.is_connected");
353
+ if (poll) {
354
+ return {
355
+ startLines: [],
356
+ pollCond: poll,
357
+ timeoutExpr: isZeroTimeout(o.timeoutMs) ? null : ms(o.timeoutMs),
358
+ };
359
+ }
360
+ break;
361
+ }
362
+ case "wifi.wait_disconnected": {
363
+ const poll = expr("wifi.is_connected");
364
+ if (poll) {
365
+ return { startLines: [], pollCond: `!${poll}`, timeoutExpr: null };
366
+ }
367
+ break;
368
+ }
369
+ case "wifi.scan": {
370
+ const start = route({ operation: "wifi.scan_start" });
371
+ const poll = expr("wifi.scan_done");
372
+ if (start?.code && poll) {
373
+ return { startLines: [start.code], pollCond: poll, timeoutExpr: null };
374
+ }
375
+ break;
376
+ }
377
+ case "http.send": {
378
+ const start = route({ operation: "http.send_start" });
379
+ const poll = expr("http.done");
380
+ if (start?.code && poll) {
381
+ return { startLines: [start.code], pollCond: poll, timeoutExpr: null };
382
+ }
383
+ break;
384
+ }
385
+ case "ble.until_connected": {
386
+ const start = route({ operation: "ble.until_connected_start" });
387
+ const poll = expr("ble.is_connected");
388
+ if (start?.code && poll) {
389
+ return {
390
+ startLines: [start.code],
391
+ pollCond: poll,
392
+ timeoutExpr: isZeroTimeout(o.timeoutMs) ? null : ms(o.timeoutMs),
393
+ };
394
+ }
395
+ break;
396
+ }
397
+ }
398
+ // Fallback: run the blocking form immediately and complete on the next tick.
399
+ const blocking = routeHALOp(op, strategy);
400
+ const line = blocking?.code ?? (blocking?.expression ? `${blocking.expression};` : `/* unhandled awaited hal-op: ${op.operation} */`);
401
+ return { startLines: [line], pollCond: null, timeoutExpr: "0" };
402
+ }
334
403
  function parseEdgeMarker(argText) {
335
404
  const match = argText.match(/^__EDGE_(RISING|FALLING)__([a-zA-Z0-9_]+)__T(.*)$/);
336
405
  if (!match)
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Best-effort C++ return type for a HAL expression op.
3
+ * Used by snprintf format inference and expression type inference so
4
+ * string-returning ops (e.g. wifi.scan_ssid → const char*) use %s, not %d.
5
+ */
6
+ export declare function cppTypeForHalOp(operation: string): string | undefined;
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Best-effort C++ return type for a HAL expression op.
3
+ * Used by snprintf format inference and expression type inference so
4
+ * string-returning ops (e.g. wifi.scan_ssid → const char*) use %s, not %d.
5
+ */
6
+ export function cppTypeForHalOp(operation) {
7
+ switch (operation) {
8
+ case "wifi.local_ip":
9
+ case "wifi.mac":
10
+ case "wifi.ap_ip":
11
+ case "wifi.scan_ssid":
12
+ case "http.body":
13
+ case "http.response_header":
14
+ return "const char*";
15
+ case "wifi.is_connected":
16
+ case "wifi.connect":
17
+ case "wifi.wait_connected":
18
+ case "wifi.scan_done":
19
+ case "wifi.ap_start":
20
+ case "wifi.connect_saved":
21
+ case "http.ok":
22
+ case "http.done":
23
+ return "bool";
24
+ case "wifi.status":
25
+ case "wifi.rssi":
26
+ case "wifi.scan":
27
+ case "wifi.scan_count":
28
+ case "wifi.scan_rssi":
29
+ case "wifi.scan_encryption":
30
+ case "wifi.scan_channel":
31
+ case "wifi.ap_client_count":
32
+ case "http.status":
33
+ case "http.content_length":
34
+ return "int";
35
+ case "http.send":
36
+ return "bool";
37
+ default:
38
+ return undefined;
39
+ }
40
+ }
@@ -59,7 +59,7 @@ function extractNumericValue(expr) {
59
59
  /**
60
60
  * Check binary comparison expressions for ADC range issues.
61
61
  */
62
- function checkComparisonForADCRange(left, operator, right, adcConfig, diagnostics) {
62
+ function checkComparisonForADCRange(left, operator, right, adcConfig, filePath, diagnostics) {
63
63
  // Check if one side is an analog read and the other is a literal
64
64
  let analogReadSide = null;
65
65
  let literalValue = null;
@@ -83,6 +83,7 @@ function checkComparisonForADCRange(left, operator, right, adcConfig, diagnostic
83
83
  severity: 'info',
84
84
  message: `Comparison ${comparisonDesc} may never be true. ADC resolution is ${adcConfig.resolution}-bit (max ${adcConfig.maxValue}) on this board.`,
85
85
  code: 'adc-range-warning',
86
+ filePath,
86
87
  source: 'adc-range-validation',
87
88
  });
88
89
  }
@@ -90,7 +91,7 @@ function checkComparisonForADCRange(left, operator, right, adcConfig, diagnostic
90
91
  /**
91
92
  * Scan an expression for ADC range issues.
92
93
  */
93
- function scanExpressionForADCRange(expr, adcConfig, diagnostics) {
94
+ function scanExpressionForADCRange(expr, adcConfig, filePath, diagnostics) {
94
95
  if (!expr || typeof expr !== 'object')
95
96
  return;
96
97
  // Check binary comparisons
@@ -98,59 +99,59 @@ function scanExpressionForADCRange(expr, adcConfig, diagnostics) {
98
99
  const bin = expr;
99
100
  const comparisonOps = ['>', '>=', '<', '<=', '===', '==', '!==', '!='];
100
101
  if (comparisonOps.includes(bin.operator)) {
101
- checkComparisonForADCRange(bin.left, bin.operator, bin.right, adcConfig, diagnostics);
102
+ checkComparisonForADCRange(bin.left, bin.operator, bin.right, adcConfig, filePath, diagnostics);
102
103
  }
103
104
  // Recursively scan both sides
104
- scanExpressionForADCRange(bin.left, adcConfig, diagnostics);
105
- scanExpressionForADCRange(bin.right, adcConfig, diagnostics);
105
+ scanExpressionForADCRange(bin.left, adcConfig, filePath, diagnostics);
106
+ scanExpressionForADCRange(bin.right, adcConfig, filePath, diagnostics);
106
107
  }
107
108
  // Check ternary conditions
108
109
  if (expr.kind === 'ternary') {
109
110
  const ternary = expr;
110
- scanExpressionForADCRange(ternary.condition, adcConfig, diagnostics);
111
- scanExpressionForADCRange(ternary.whenTrue, adcConfig, diagnostics);
112
- scanExpressionForADCRange(ternary.whenFalse, adcConfig, diagnostics);
111
+ scanExpressionForADCRange(ternary.condition, adcConfig, filePath, diagnostics);
112
+ scanExpressionForADCRange(ternary.whenTrue, adcConfig, filePath, diagnostics);
113
+ scanExpressionForADCRange(ternary.whenFalse, adcConfig, filePath, diagnostics);
113
114
  }
114
115
  // Check property access
115
116
  if (expr.kind === 'property-access') {
116
117
  const pa = expr;
117
- scanExpressionForADCRange(pa.object, adcConfig, diagnostics);
118
+ scanExpressionForADCRange(pa.object, adcConfig, filePath, diagnostics);
118
119
  }
119
120
  }
120
121
  /**
121
122
  * Scan a statement for ADC range issues.
122
123
  */
123
- function scanStatementForADCRange(stmt, adcConfig, diagnostics) {
124
+ function scanStatementForADCRange(stmt, adcConfig, filePath, diagnostics) {
124
125
  if (!stmt || typeof stmt !== 'object')
125
126
  return;
126
127
  switch (stmt.kind) {
127
128
  case 'var_decl': {
128
129
  const varDecl = stmt;
129
130
  if (varDecl.initializer) {
130
- scanExpressionForADCRange(varDecl.initializer, adcConfig, diagnostics);
131
+ scanExpressionForADCRange(varDecl.initializer, adcConfig, filePath, diagnostics);
131
132
  }
132
133
  break;
133
134
  }
134
135
  case 'assign': {
135
136
  const assign = stmt;
136
137
  if (assign.value) {
137
- scanExpressionForADCRange(assign.value, adcConfig, diagnostics);
138
+ scanExpressionForADCRange(assign.value, adcConfig, filePath, diagnostics);
138
139
  }
139
140
  break;
140
141
  }
141
142
  case 'if': {
142
143
  const ifStmt = stmt;
143
144
  if (ifStmt.condition) {
144
- scanExpressionForADCRange(ifStmt.condition, adcConfig, diagnostics);
145
+ scanExpressionForADCRange(ifStmt.condition, adcConfig, filePath, diagnostics);
145
146
  }
146
147
  if (ifStmt.thenBranch) {
147
148
  for (const s of ifStmt.thenBranch) {
148
- scanStatementForADCRange(s, adcConfig, diagnostics);
149
+ scanStatementForADCRange(s, adcConfig, filePath, diagnostics);
149
150
  }
150
151
  }
151
152
  if (ifStmt.elseBranch) {
152
153
  for (const s of ifStmt.elseBranch) {
153
- scanStatementForADCRange(s, adcConfig, diagnostics);
154
+ scanStatementForADCRange(s, adcConfig, filePath, diagnostics);
154
155
  }
155
156
  }
156
157
  break;
@@ -158,11 +159,11 @@ function scanStatementForADCRange(stmt, adcConfig, diagnostics) {
158
159
  case 'while': {
159
160
  const whileStmt = stmt;
160
161
  if (whileStmt.condition) {
161
- scanExpressionForADCRange(whileStmt.condition, adcConfig, diagnostics);
162
+ scanExpressionForADCRange(whileStmt.condition, adcConfig, filePath, diagnostics);
162
163
  }
163
164
  if (whileStmt.body) {
164
165
  for (const s of whileStmt.body) {
165
- scanStatementForADCRange(s, adcConfig, diagnostics);
166
+ scanStatementForADCRange(s, adcConfig, filePath, diagnostics);
166
167
  }
167
168
  }
168
169
  break;
@@ -170,11 +171,11 @@ function scanStatementForADCRange(stmt, adcConfig, diagnostics) {
170
171
  case 'for': {
171
172
  const forStmt = stmt;
172
173
  if (forStmt.condition) {
173
- scanExpressionForADCRange(forStmt.condition, adcConfig, diagnostics);
174
+ scanExpressionForADCRange(forStmt.condition, adcConfig, filePath, diagnostics);
174
175
  }
175
176
  if (forStmt.body) {
176
177
  for (const s of forStmt.body) {
177
- scanStatementForADCRange(s, adcConfig, diagnostics);
178
+ scanStatementForADCRange(s, adcConfig, filePath, diagnostics);
178
179
  }
179
180
  }
180
181
  break;
@@ -182,7 +183,7 @@ function scanStatementForADCRange(stmt, adcConfig, diagnostics) {
182
183
  case 'return': {
183
184
  const retStmt = stmt;
184
185
  if (retStmt.value) {
185
- scanExpressionForADCRange(retStmt.value, adcConfig, diagnostics);
186
+ scanExpressionForADCRange(retStmt.value, adcConfig, filePath, diagnostics);
186
187
  }
187
188
  break;
188
189
  }
@@ -190,7 +191,7 @@ function scanStatementForADCRange(stmt, adcConfig, diagnostics) {
190
191
  const call = stmt;
191
192
  if (call.args) {
192
193
  for (const arg of call.args) {
193
- scanExpressionForADCRange(arg, adcConfig, diagnostics);
194
+ scanExpressionForADCRange(arg, adcConfig, filePath, diagnostics);
194
195
  }
195
196
  }
196
197
  break;
@@ -214,7 +215,7 @@ export function validateADCRange(program, boardConstants) {
214
215
  // Scan top-level statements
215
216
  if (program.topLevelStatements) {
216
217
  for (const stmt of program.topLevelStatements) {
217
- scanStatementForADCRange(stmt, adcConfig, diagnostics);
218
+ scanStatementForADCRange(stmt, adcConfig, program.fileName, diagnostics);
218
219
  }
219
220
  }
220
221
  // Scan function bodies
@@ -222,7 +223,7 @@ export function validateADCRange(program, boardConstants) {
222
223
  for (const fn of program.functions) {
223
224
  if (fn.statements) {
224
225
  for (const stmt of fn.statements) {
225
- scanStatementForADCRange(stmt, adcConfig, diagnostics);
226
+ scanStatementForADCRange(stmt, adcConfig, program.fileName, diagnostics);
226
227
  }
227
228
  }
228
229
  }
@@ -234,14 +235,14 @@ export function validateADCRange(program, boardConstants) {
234
235
  for (const method of cls.methods) {
235
236
  if (method.statements) {
236
237
  for (const stmt of method.statements) {
237
- scanStatementForADCRange(stmt, adcConfig, diagnostics);
238
+ scanStatementForADCRange(stmt, adcConfig, program.fileName, diagnostics);
238
239
  }
239
240
  }
240
241
  }
241
242
  }
242
243
  if (cls.constructor?.statements) {
243
244
  for (const stmt of cls.constructor.statements) {
244
- scanStatementForADCRange(stmt, adcConfig, diagnostics);
245
+ scanStatementForADCRange(stmt, adcConfig, program.fileName, diagnostics);
245
246
  }
246
247
  }
247
248
  }
@@ -144,7 +144,11 @@ export function buildProgramIR(fileName, sourceText, boardPackage, prebuiltClass
144
144
  const parentStrategy = getContext().activeStrategy;
145
145
  return contextStorage.run(new CompilationContext(), () => {
146
146
  getContext().activeStrategy = parentStrategy;
147
- loadHALModules(true); // Parse HAL source files (force reload to pick up changes)
147
+ // Ensure HAL modules are loaded. transpile.ts warms the HAL registry once
148
+ // per transpile run (before the per-file IR build), so this is normally a
149
+ // cheap no-op. The non-forced call is a safety net for direct/test callers
150
+ // of buildProgramIR that didn't warm the registry first.
151
+ loadHALModules();
148
152
  const normalizedSourceText = normalizeEntrypointSyntax(sourceText);
149
153
  const source = parseSource(fileName, normalizedSourceText);
150
154
  const diagnostics = [];