@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
@@ -43,7 +43,7 @@ function getTimer0PwmPins(boardConstants) {
43
43
  }
44
44
  return timer0Pins;
45
45
  }
46
- export function validateTimer0PWMTimingConflict(usage, boardConstants) {
46
+ export function validateTimer0PWMTimingConflict(usage, boardConstants, filePath) {
47
47
  if (!usage.timer0 || usage.pwmPinsUsed.size === 0) {
48
48
  return [];
49
49
  }
@@ -65,6 +65,7 @@ export function validateTimer0PWMTimingConflict(usage, boardConstants) {
65
65
  return [{
66
66
  severity: 'info',
67
67
  code: 'timer0-pwm-timing-conflict',
68
+ filePath,
68
69
  source: 'timer0-pwm-timing-conflict',
69
70
  message: `${pinReferences.join(', ')} use Timer0 PWM${boardLabel}, and your program also relies on Timer0-backed timing APIs such as delay(), millis(), or micros(). This coupling is common on AVR boards, so prefer non-Timer0 PWM pins when you want PWM behavior isolated from core timing.`,
70
71
  }];
@@ -1,7 +1,12 @@
1
1
  import type { ProgramIR } from '../api/index.js';
2
2
  import type { Diagnostic } from '../types.js';
3
+ import type { PlatformStrategy } from '../api/shared/index.js';
3
4
  /**
4
5
  * Detect blocking delay() calls inside loop()'s body. Emits a warning for
5
6
  * each, explaining the cooperative-async alternative.
7
+ *
8
+ * On RTOS targets (ESP-IDF), timing.delay lowers to vTaskDelay which yields
9
+ * the CPU — it does NOT freeze the async queue or UI. Only delayMicroseconds
10
+ * (busy-wait) triggers the warning.
6
11
  */
7
- export declare function validateBlockingDelayInLoop(program: ProgramIR): Diagnostic[];
12
+ export declare function validateBlockingDelayInLoop(program: ProgramIR, strategy?: PlatformStrategy): Diagnostic[];
@@ -12,20 +12,43 @@
12
12
  // g++ sees only an ordinary function. This validator flags blocking delays in
13
13
  // loop()'s direct body so the user can replace them with the cooperative
14
14
  // Async.sleep() / millis()-comparison pattern.
15
+ //
16
+ // IMPORTANT: on RTOS targets (ESP-IDF / FreeRTOS), delay() lowers to
17
+ // vTaskDelay which YIELDS the CPU — it does not freeze other tasks, UI
18
+ // rendering, or the async queue. Only delayMicroseconds (esp_rom_delay_us)
19
+ // is a true busy-wait. So for RTOS targets, only delay_microseconds triggers
20
+ // the warning; delay is safe.
15
21
  // ---------------------------------------------------------------------------
16
- /** Check a single statement for a blocking delay call or hal-op. */
17
- function isBlockingDelay(stmt) {
22
+ import { hasLoadedFramework, getLoadedFramework } from '../framework-registry.js';
23
+ /** Check a single statement for a blocking delay call or hal-op.
24
+ * On RTOS targets, timing.delay (vTaskDelay) is NOT blocking — only
25
+ * timing.delay_microseconds (esp_rom_delay_us) is. */
26
+ function isBlockingDelay(stmt, isRtos) {
18
27
  const s = stmt;
19
28
  // Direct call: delay(...) or delayMicroseconds(...)
20
29
  if (stmt.kind === 'call' && typeof s.callee === 'string') {
21
- if (s.callee === 'delay' || s.callee === 'delayMicroseconds')
22
- return true;
30
+ if (isRtos) {
31
+ // On RTOS targets, only delayMicroseconds is a busy-wait.
32
+ if (s.callee === 'delayMicroseconds')
33
+ return true;
34
+ }
35
+ else {
36
+ if (s.callee === 'delay' || s.callee === 'delayMicroseconds')
37
+ return true;
38
+ }
23
39
  }
24
40
  // Hal-op: after HAL resolution, delay() becomes timing.delay /
25
41
  // timing.delay_microseconds.
26
42
  if (stmt.kind === 'hal-op' && s.operation?.operation) {
27
- if (s.operation.operation === 'timing.delay' || s.operation.operation === 'timing.delay_microseconds') {
28
- return true;
43
+ if (isRtos) {
44
+ // On RTOS targets, only timing.delay_microseconds is blocking.
45
+ if (s.operation.operation === 'timing.delay_microseconds')
46
+ return true;
47
+ }
48
+ else {
49
+ if (s.operation.operation === 'timing.delay' || s.operation.operation === 'timing.delay_microseconds') {
50
+ return true;
51
+ }
29
52
  }
30
53
  }
31
54
  return false;
@@ -33,24 +56,40 @@ function isBlockingDelay(stmt) {
33
56
  /**
34
57
  * Detect blocking delay() calls inside loop()'s body. Emits a warning for
35
58
  * each, explaining the cooperative-async alternative.
59
+ *
60
+ * On RTOS targets (ESP-IDF), timing.delay lowers to vTaskDelay which yields
61
+ * the CPU — it does NOT freeze the async queue or UI. Only delayMicroseconds
62
+ * (busy-wait) triggers the warning.
36
63
  */
37
- export function validateBlockingDelayInLoop(program) {
64
+ export function validateBlockingDelayInLoop(program, strategy) {
38
65
  const diagnostics = [];
66
+ // Detect RTOS targets where delay() yields rather than blocks.
67
+ // framework-esp32 uses FreeRTOS; vTaskDelay is a yielding delay.
68
+ const isRtos = strategy?.isRtosTarget?.() === true
69
+ || (hasLoadedFramework() && getLoadedFramework().strategy.isRtosTarget?.() === true);
39
70
  const loopFn = program.functions.find(fn => fn.originalName === 'loop');
40
71
  if (!loopFn || !loopFn.statements)
41
72
  return diagnostics;
42
73
  const visit = (stmts) => {
43
74
  for (const stmt of stmts) {
44
- if (isBlockingDelay(stmt)) {
75
+ if (isBlockingDelay(stmt, isRtos)) {
45
76
  const s = stmt;
77
+ const message = isRtos
78
+ ? `Blocking delayMicroseconds() inside loop() is a busy-wait that wastes CPU cycles. ` +
79
+ `On RTOS targets (ESP-IDF), prefer timing.delay() (vTaskDelay) which yields the CPU to other tasks.`
80
+ : `Blocking delay() inside loop() freezes the async microtask queue and UI rendering ` +
81
+ `for the delay duration. This causes display tearing and makes the sketch unresponsive.`;
82
+ const hint = isRtos
83
+ ? `Replace delayMicroseconds with delay() if the timing permits, or accept the brief busy-wait if sub-millisecond precision is required.`
84
+ : `Use the cooperative pattern instead: track elapsed time with millis() comparisons, ` +
85
+ `or use Async.sleep(ms) / Async.yield() to let other tasks run between checks.`;
46
86
  diagnostics.push({
47
87
  severity: 'warning',
48
- message: `Blocking delay() inside loop() freezes the async microtask queue and UI rendering ` +
49
- `for the delay duration. This causes display tearing and makes the sketch unresponsive.`,
50
- hint: `Use the cooperative pattern instead: track elapsed time with millis() comparisons, ` +
51
- `or use Async.sleep(ms) / Async.yield() to let other tasks run between checks.`,
88
+ message,
89
+ hint,
52
90
  line: stmt.sourceSpan?.startLine,
53
91
  column: stmt.sourceSpan?.startColumn,
92
+ filePath: stmt.sourceSpan?.filePath,
54
93
  code: 'blocking-delay-in-loop',
55
94
  source: 'timing-validation',
56
95
  });
@@ -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,50 @@ 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
+ ]);
431
+ /**
432
+ * Rewrite an awaited hal-op statement (or a block whose LAST statement is
433
+ * one — chained HAL calls resolve to blocks of hal-ops) into an awaited
434
+ * `__WIFI_WAIT__` / `__HTTP_WAIT__` / `__HAL_WAIT__` marker call carrying the
435
+ * original op as a hal-expr argument. Returns undefined when the statement is
436
+ * not an awaitable HAL op.
437
+ */
438
+ function awaitedNetMarker(stmt) {
439
+ if (!stmt)
440
+ return undefined;
441
+ if (stmt.kind === "hal-op" && AWAITABLE_HAL_OPS.has(stmt.operation.operation)) {
442
+ const opName = stmt.operation.operation;
443
+ const callee = opName.startsWith("http.") ? "__HTTP_WAIT__"
444
+ : opName.startsWith("wifi.") ? "__WIFI_WAIT__"
445
+ : opName.startsWith("ble.") ? "__BLE_WAIT__"
446
+ : "__HAL_WAIT__";
447
+ return {
448
+ kind: "call",
449
+ sourceSpan: stmt.sourceSpan,
450
+ leadingComments: stmt.leadingComments,
451
+ trailingComments: stmt.trailingComments,
452
+ callee,
453
+ args: [{ kind: "hal-expr", operation: stmt.operation }],
454
+ isAwaited: true,
455
+ };
456
+ }
457
+ if (stmt.kind === "block" && stmt.body.length > 0) {
458
+ const last = awaitedNetMarker(stmt.body[stmt.body.length - 1]);
459
+ if (last) {
460
+ return { ...stmt, body: [...stmt.body.slice(0, -1), last] };
461
+ }
462
+ }
463
+ return undefined;
464
+ }
@@ -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
  }
@@ -7,13 +7,17 @@ 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";
13
14
  function replaceHalReadBufferPlaceholder(op, varName) {
14
15
  if (op.operation === "i2c.read_buffer" && op.buffer === "__HAL_READ_BUF__") {
15
16
  return { ...op, buffer: varName };
16
17
  }
18
+ if (op.operation === "spi.read_buffer" && op.buffer === "__HAL_READ_BUF__") {
19
+ return { ...op, buffer: varName };
20
+ }
17
21
  return op;
18
22
  }
19
23
  export function assignmentOperatorToString(kind) {
@@ -405,9 +409,26 @@ export function variableStatementToIR(statement, fileName, sourceText, diagnosti
405
409
  }
406
410
  }
407
411
  }
412
+ // new HttpRequest(method, url) — positional ctor fields. The URL is
413
+ // stored as C++ expression text (quoted literal or identifier).
414
+ if (className === "HttpRequest" && ctorArgs && ctorArgs.length >= 2) {
415
+ const methodArg = ctorArgs[0];
416
+ if (ts.isStringLiteral(methodArg))
417
+ fieldValues.set("_method", methodArg.text.toUpperCase());
418
+ const urlText = httpUrlArgText(ctorArgs[1]);
419
+ if (urlText)
420
+ fieldValues.set("_url", urlText);
421
+ }
408
422
  if (ctorArgs) {
423
+ // Resolve a Pin-identifier ctor arg to its _pin number, for any HAL
424
+ // class whose first constructor field is _pin (Pin itself, plus
425
+ // pin-bearing wrappers like RmtChannel). Mirrors the Pin branch
426
+ // above but keyed off the class's registered ctor field map so new
427
+ // pin-keyed classes work without a per-class branch here.
428
+ const ctorFieldMap = getHALCtorFieldMap(className);
429
+ const firstFieldIsPin = ctorFieldMap && Array.from(ctorFieldMap.keys())[0] === "_pin";
409
430
  for (const arg of ctorArgs) {
410
- if (ts.isIdentifier(arg) && className === "Pin") {
431
+ if (ts.isIdentifier(arg) && (className === "Pin" || firstFieldIsPin)) {
411
432
  const existing = halInstances.get(arg.text);
412
433
  if (existing && existing.fieldValues.has("_pin")) {
413
434
  fieldValues.set("_pin", existing.fieldValues.get("_pin"));
@@ -430,6 +451,45 @@ export function variableStatementToIR(statement, fileName, sourceText, diagnosti
430
451
  const isSingletonReceiver = ts.isIdentifier(receiver) && isHALSingleton(receiver.text);
431
452
  if (result && (!isOwnershipMethod || isSingletonReceiver)) {
432
453
  const isHalOpReturn = result.returnValue === "__hal_op_return__";
454
+ // A __TYPED_ARRAY__ return comes ONLY from a HAL method body
455
+ // (`return new Uint8Array(count)` in e.g. I2CDevice.readBytes /
456
+ // SPIDevice.readRegister). The method's side-effect HAL ops (the
457
+ // i2c.read_buffer / spi.read_buffer fill loops) write INTO this buffer
458
+ // via the __HAL_READ_BUF__ placeholder (rewritten to `varName`). So the
459
+ // buffer var_decl MUST precede the fill ops in emitted order — at top
460
+ // level the var_decl is hoisted to file scope which masks this, but a
461
+ // function-local `const data = ...readBytes()` would otherwise emit the
462
+ // fill loop referencing `data` before its declaration. Detect the typed
463
+ // array up front so we can emit its declaration first.
464
+ const isTypedArrayReturn = typeof result.returnValue === "string" && result.returnValue.startsWith("__TYPED_ARRAY__:");
465
+ if (isTypedArrayReturn && typeof result.returnValue === "string") {
466
+ const parts = result.returnValue.split(":");
467
+ const elementType = parts[1];
468
+ const size = parts[2];
469
+ // Force non-const storage (the buffer is written by the fill op) and
470
+ // synthesize a zero-init array initializer so the var_decl renderer
471
+ // emits `T data[] = { 0, 0, ... }` — a bare `const T data[N];` that
472
+ // is later written would fail to compile (assignment to const).
473
+ // Mirrors plain `new Uint8Array(N)` (expression-to-ir.ts).
474
+ const count = parseInt(size, 10);
475
+ const initElements = !isNaN(count) && count > 0 && count <= 256
476
+ ? Array(count).fill(0).map(() => ({ kind: "number", value: 0 }))
477
+ : [];
478
+ lowered.push({
479
+ kind: "var_decl",
480
+ sourceSpan: makeSourceSpan(declaration, fileName, sourceText),
481
+ leadingComments: commentsAssigned ? [] : statementComments.leadingComments,
482
+ trailingComments: [],
483
+ name: varName,
484
+ storage: "let",
485
+ cppType: `${elementType}[${size}]`,
486
+ initializer: initElements.length > 0
487
+ ? { kind: "array", elements: initElements, elementType }
488
+ : undefined,
489
+ });
490
+ activeCArrayVars.add(varName);
491
+ commentsAssigned = true;
492
+ }
433
493
  if (result.halOps && result.halOps.length > 0) {
434
494
  const sideEffectOps = isHalOpReturn ? result.halOps.slice(0, -1) : result.halOps;
435
495
  const halStmts = sideEffectOps.map(op => ({
@@ -474,6 +534,19 @@ export function variableStatementToIR(statement, fileName, sourceText, diagnosti
474
534
  }
475
535
  }
476
536
  }
537
+ // For Http factory calls (const req = Http.get(url)), record the
538
+ // HTTP verb + URL so req.send() can resolve this._method/this._url.
539
+ if (result.returnClassName === "HttpRequest") {
540
+ const verb = httpFactoryVerb(init.expression.name.text);
541
+ if (verb)
542
+ fieldValues.set("_method", verb);
543
+ const urlArg = init.arguments?.[0];
544
+ if (urlArg) {
545
+ const urlText = httpUrlArgText(urlArg);
546
+ if (urlText)
547
+ fieldValues.set("_url", urlText);
548
+ }
549
+ }
477
550
  halInstances.set(varName, {
478
551
  className: result.returnClassName,
479
552
  fieldValues,
@@ -527,24 +600,18 @@ export function variableStatementToIR(statement, fileName, sourceText, diagnosti
527
600
  if (/\b\d+\.\d+\b/.test(result.returnValue)) {
528
601
  registerFloatVariable(varName);
529
602
  }
603
+ // __TYPED_ARRAY__ returns are handled above (declared BEFORE the
604
+ // fill ops). Everything else is a scalar/value return captured
605
+ // after the side-effect ops.
530
606
  const isTypedArray = result.returnValue.startsWith("__TYPED_ARRAY__:");
531
- if (isTypedArray) {
532
- const parts = result.returnValue.split(":");
533
- const elementType = parts[1];
534
- const size = parts[2];
535
- lowered.push({
536
- kind: "var_decl",
537
- sourceSpan: makeSourceSpan(declaration, fileName, sourceText),
538
- leadingComments: commentsAssigned ? [] : statementComments.leadingComments,
539
- trailingComments: [],
540
- name: varName,
541
- storage,
542
- cppType: `${elementType}[${size}]`,
543
- initializer: undefined,
544
- });
545
- activeCArrayVars.add(varName);
546
- }
547
- else {
607
+ // A factory returning a compile-time HAL instance (e.g.
608
+ // `const req = Http.get(url)` → returnValue `new HttpRequest(...)`)
609
+ // is fully tracked via halInstances — every later method call on
610
+ // the variable resolves at compile time. Emitting the raw
611
+ // `new HttpRequest(...)` would reference a class that doesn't
612
+ // exist in the C++ output.
613
+ const isHalInstanceReturn = !!result.returnClassName && /^new\s/.test(result.returnValue.trim());
614
+ if (!isTypedArray && !isHalInstanceReturn) {
548
615
  lowered.push({
549
616
  kind: "var_decl",
550
617
  sourceSpan: makeSourceSpan(declaration, fileName, sourceText),
@@ -39,6 +39,7 @@ export function validateTryCatch(program, boardConstants, strategy) {
39
39
  hint: `function readSensor(): number | null {\n const data = sensor.read();\n if (!data) return null; // error path\n return data.value; // success path\n}`,
40
40
  line: stmt.sourceSpan?.startLine,
41
41
  column: stmt.sourceSpan?.startColumn,
42
+ filePath: stmt.sourceSpan?.filePath,
42
43
  source: 'try-catch-validation',
43
44
  });
44
45
  // Still recurse into nested blocks to find other try/catch
@@ -57,6 +58,7 @@ export function validateTryCatch(program, boardConstants, strategy) {
57
58
  hint: `return null; // or return an error code`,
58
59
  line: stmt.sourceSpan?.startLine,
59
60
  column: stmt.sourceSpan?.startColumn,
61
+ filePath: stmt.sourceSpan?.filePath,
60
62
  source: 'try-catch-validation',
61
63
  });
62
64
  return;
@@ -33,9 +33,9 @@ const PIN_INTERFACE_TYPE_NAMES = new Set([
33
33
  "PinMode", "InterruptMode",
34
34
  ]);
35
35
  const BUS_INTERFACE_TYPE_NAMES = new Set([
36
- "II2CBus", "ISPIBus", "ISerialPort", "IUART",
36
+ "II2CBus", "ISPIBus", "ISerialPort",
37
37
  "I2CConfig", "SPIConfig", "UARTConfig",
38
- "I2CAddress", "UARTStatus", "SPITransferOptions",
38
+ "I2CAddress", "SPITransferOptions",
39
39
  ]);
40
40
  const STRATEGY_TYPE_NAMES = new Set([
41
41
  "NativeStrategy", "ArduinoStrategy", "BoardStrategy",
@@ -130,7 +130,7 @@ function checkSPIFrequency(value) {
130
130
  * baud rate used as SPI freq, etc.). Only literal numbers are checked —
131
131
  * expressions/variables are opaque to this validator.
132
132
  */
133
- function checkConfigValue(kind, rawValue, diagnostics) {
133
+ function checkConfigValue(kind, rawValue, filePath, diagnostics) {
134
134
  // HAL ops carry resolved numeric values as `number` when the source arg was
135
135
  // a literal, or as `string` expression text otherwise. Only check numbers.
136
136
  const value = typeof rawValue === 'number' ? rawValue
@@ -146,6 +146,7 @@ function checkConfigValue(kind, rawValue, diagnostics) {
146
146
  severity: 'warning',
147
147
  message,
148
148
  code: 'unit-suspicion',
149
+ filePath,
149
150
  source: 'unit-suspicion-validation',
150
151
  });
151
152
  }
@@ -160,15 +161,16 @@ function scanStatement(stmt, diagnostics) {
160
161
  if (!stmt || typeof stmt !== 'object')
161
162
  return;
162
163
  const s = stmt;
164
+ const filePath = s.sourceSpan?.filePath;
163
165
  // HAL-op statements carry structured operations with resolved values.
164
166
  if (stmt.kind === 'hal-op' && s.operation) {
165
167
  const op = s.operation;
166
168
  switch (op.operation) {
167
169
  case 'i2c.set_clock':
168
- checkConfigValue('i2c', op.hz, diagnostics);
170
+ checkConfigValue('i2c', op.hz, filePath, diagnostics);
169
171
  break;
170
172
  case 'uart.begin':
171
- checkConfigValue('baud', op.baud, diagnostics);
173
+ checkConfigValue('baud', op.baud, filePath, diagnostics);
172
174
  break;
173
175
  // SPI frequency flows through SPISettings construction text, which the
174
176
  // HAL op carries as a string — the numeric extraction in
@@ -178,7 +180,7 @@ function scanStatement(stmt, diagnostics) {
178
180
  // SPISettings({freq}, ...) — try to extract the leading frequency.
179
181
  const m = op.settings.match(/^\s*(\d+)/);
180
182
  if (m)
181
- checkConfigValue('spi', parseInt(m[1], 10), diagnostics);
183
+ checkConfigValue('spi', parseInt(m[1], 10), filePath, diagnostics);
182
184
  }
183
185
  break;
184
186
  }
@@ -188,18 +190,18 @@ function scanStatement(stmt, diagnostics) {
188
190
  if (stmt.kind === 'call' && typeof s.callee === 'string' && Array.isArray(s.args)) {
189
191
  const method = s.callee.split('.').pop() ?? s.callee;
190
192
  if (method === 'setClock' && s.args.length >= 2) {
191
- checkConfigValue('i2c', extractNumericValue(s.args[1]), diagnostics);
193
+ checkConfigValue('i2c', extractNumericValue(s.args[1]), filePath, diagnostics);
192
194
  }
193
195
  else if (method === 'setBaudRate' || method === 'begin') {
194
196
  // Serial.begin(baud) / setBaudRate(baud) — last numeric arg is the baud.
195
197
  for (const arg of s.args) {
196
198
  const v = extractNumericValue(arg);
197
199
  if (v !== undefined)
198
- checkConfigValue('baud', v, diagnostics);
200
+ checkConfigValue('baud', v, filePath, diagnostics);
199
201
  }
200
202
  }
201
203
  else if (method === 'setFrequency' && s.args.length >= 2) {
202
- checkConfigValue('spi', extractNumericValue(s.args[1]), diagnostics);
204
+ checkConfigValue('spi', extractNumericValue(s.args[1]), filePath, diagnostics);
203
205
  }
204
206
  }
205
207
  // Recurse into nested statements
@@ -18,18 +18,19 @@ import { validateBlockingDelayInLoop } from "./timing-validation.js";
18
18
  import { validateUnitSuspicion } from "./unit-suspicion-validation.js";
19
19
  import { validateOwnership } from "./ownership-analysis.js";
20
20
  import { validatePinCapabilities } from "./pin-capability-validation.js";
21
+ import { validateNetworkUsage } from "./network-validation.js";
21
22
  export function runProgramValidations(program, strategy) {
22
23
  const resolvedStrategy = strategy ?? (hasLoadedFramework() ? getLoadedFramework().strategy : resolveStrategy('generic'));
23
24
  const diagnostics = [];
24
25
  const peripheralUsage = program.peripheralUsage ?? createEmptyPeripheralUsage();
25
26
  diagnostics.push(...validatePinCapabilities(program));
26
- diagnostics.push(...validatePeripherals(peripheralUsage, program.boardConstants));
27
- diagnostics.push(...validateUnsafePins(peripheralUsage, program.boardConstants));
27
+ diagnostics.push(...validatePeripherals(peripheralUsage, program.boardConstants, program.fileName));
28
+ diagnostics.push(...validateUnsafePins(peripheralUsage, program.boardConstants, program.fileName));
28
29
  diagnostics.push(...analyzeResources(program, resolvedStrategy));
29
- diagnostics.push(...validatePinAliasConflicts(peripheralUsage, program.boardConstants));
30
- diagnostics.push(...validatePWMTimerSharing(peripheralUsage, program.boardConstants));
31
- diagnostics.push(...validateTimer0PWMTimingConflict(peripheralUsage, program.boardConstants));
32
- diagnostics.push(...validatePulldownSupport(peripheralUsage, program.boardConstants));
30
+ diagnostics.push(...validatePinAliasConflicts(peripheralUsage, program.boardConstants, program.fileName));
31
+ diagnostics.push(...validatePWMTimerSharing(peripheralUsage, program.boardConstants, program.fileName));
32
+ diagnostics.push(...validateTimer0PWMTimingConflict(peripheralUsage, program.boardConstants, program.fileName));
33
+ diagnostics.push(...validatePulldownSupport(peripheralUsage, program.boardConstants, program.fileName));
33
34
  diagnostics.push(...analyzeInterruptSafety(program, peripheralUsage));
34
35
  inferVolatileForIsrSharedVars(program, diagnostics);
35
36
  detectReentrancyRisk(program, diagnostics);
@@ -40,6 +41,7 @@ export function runProgramValidations(program, strategy) {
40
41
  diagnostics.push(...validateOwnership(program));
41
42
  diagnostics.push(...validateTryCatch(program, program.boardConstants, resolvedStrategy));
42
43
  diagnostics.push(...validateMemoryBudget(program, program.boardConstants));
43
- diagnostics.push(...validateBlockingDelayInLoop(program));
44
+ diagnostics.push(...validateBlockingDelayInLoop(program, resolvedStrategy));
45
+ diagnostics.push(...validateNetworkUsage(program));
44
46
  return diagnostics;
45
47
  }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * C-header to TypeScript declaration generator.
3
+ *
4
+ * Complements cpp-to-decl.ts (which is class-focused). ESP-IDF components
5
+ * are mostly C: free functions, opaque handles, typedef'd enums and structs.
6
+ *
7
+ * EMISSION POLICY — names match the C header 1-to-1.
8
+ *
9
+ * esp_err_t esp_wifi_init(const wifi_config_t *config);
10
+ *
11
+ * becomes
12
+ *
13
+ * export declare function esp_wifi_init(config: number): esp_err_t;
14
+ *
15
+ * not `esp_wifi.init(...)`. ESP-IDF examples call `esp_wifi_init`, never
16
+ * `esp_wifi.init`; the dotted form has no C++ representation (there is no
17
+ * `esp_wifi` object or namespace in the real header) and would not link.
18
+ * Mirroring the C names verbatim means the transpiler lowers TS calls
19
+ * directly to valid C with zero translation.
20
+ *
21
+ * Spec: docs/superpowers/specs/2026-07-19-demo-wifi-design.md
22
+ */
23
+ /**
24
+ * Top-level entry: read a header file, write `<header>.d.ts` alongside it.
25
+ * Returns the output path, or null if no declarations could be extracted.
26
+ */
27
+ export declare function generateCDecl(filePath: string, outputPath?: string): string | null;