@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
@@ -26,7 +26,7 @@ function polyfillDefinitionGuard(definition) {
26
26
  return null;
27
27
  }
28
28
  export function emitPreamble(ctx) {
29
- const { strategy, effectiveEmitMode, program, shimLines, emittedPolyfills, asyncTaskClasses, includes, programAnalysis } = ctx;
29
+ const { strategy, effectiveEmitMode, program, shimLines, emittedPolyfills, includes, programAnalysis } = ctx;
30
30
  for (const include of dedupe(includes)) {
31
31
  appendSourceLineLocal(ctx, `#include ${include}`);
32
32
  }
@@ -73,16 +73,8 @@ export function emitPreamble(ctx) {
73
73
  }
74
74
  appendSourceLineLocal(ctx, "");
75
75
  }
76
- if (asyncTaskClasses.length > 0) {
77
- for (const { classDef, instanceDecl } of asyncTaskClasses) {
78
- for (const line of classDef.split("\n")) {
79
- appendSourceLineLocal(ctx, line);
80
- }
81
- appendSourceLineLocal(ctx, "");
82
- appendSourceLineLocal(ctx, instanceDecl);
83
- appendSourceLineLocal(ctx, "");
84
- }
85
- }
76
+ // Async task classes are emitted later (emitAsyncTaskClasses) AFTER top-level
77
+ // globals so references like WIFI_SSID inside the state machine compile.
86
78
  if (strategy.needsVectorOverload() && hasConsoleCalls(program, strategy) && (programAnalysis.usesVectorTypes || programAnalysis.hasArrayInObjectLiteral)) {
87
79
  appendSourceLineLocal(ctx, "template <typename T>");
88
80
  appendSourceLineLocal(ctx, "std::ostream& operator<<(std::ostream& os, const std::vector<T>& values)");
@@ -136,6 +128,24 @@ export function emitPreamble(ctx) {
136
128
  appendSourceLineLocal(ctx, "");
137
129
  }
138
130
  }
131
+ /**
132
+ * Emit cooperative async state-machine classes. Must run AFTER top-level
133
+ * globals (emitTypeDeclarations) so identifiers like WIFI_SSID referenced
134
+ * from task bodies are already declared.
135
+ */
136
+ export function emitAsyncTaskClasses(ctx) {
137
+ const { asyncTaskClasses } = ctx;
138
+ if (asyncTaskClasses.length === 0)
139
+ return;
140
+ for (const { classDef, instanceDecl } of asyncTaskClasses) {
141
+ for (const line of classDef.split("\n")) {
142
+ appendSourceLineLocal(ctx, line);
143
+ }
144
+ appendSourceLineLocal(ctx, "");
145
+ appendSourceLineLocal(ctx, instanceDecl);
146
+ appendSourceLineLocal(ctx, "");
147
+ }
148
+ }
139
149
  export function finalizeOutput(ctx) {
140
150
  const { program, strategy, options, effectiveEmitMode, isNpmPackage, baseName, includes, shimLines, emittedPolyfills } = ctx;
141
151
  const outDir = options.outDir;
@@ -31,6 +31,80 @@ function filterShimBlock(lines, startMarker, endMarker) {
31
31
  }
32
32
  return filtered;
33
33
  }
34
+ /**
35
+ * Detect whether a program uses the watchdog timer. The HAL resolver lowers
36
+ * `WDT.enable/reset/disable` calls to structured `wdt.*` hal-ops, which the
37
+ * setup emitter then renders as bare `wdt_enable/wdt_reset/wdt_disable` calls
38
+ * (or constant-folded `wdt_enable(WDTO_*)` macros). All of those require
39
+ * `<avr/wdt.h>`, so the include is gated on this check instead of being forced
40
+ * into every AVR program.
41
+ *
42
+ * `programAnalysis.usesWDT` is NOT used here: it scans `WDT.`-prefixed
43
+ * callees and raw-code hal-ops, but the structured `wdt.*` ops carry a typed
44
+ * operation name with no raw code, so the analysis misses them.
45
+ *
46
+ * The recursion mirrors `collectStatementIdentifiers` (identifier-collector.ts)
47
+ * — the canonical IR walker — so every compound statement shape is covered.
48
+ */
49
+ function programUsesWdt(program) {
50
+ const visitStatement = (stmt) => {
51
+ if (stmt.kind === "hal-op") {
52
+ const opName = stmt.operation?.operation;
53
+ if (typeof opName === "string" && opName.startsWith("wdt."))
54
+ return true;
55
+ }
56
+ switch (stmt.kind) {
57
+ case "block":
58
+ case "labeled":
59
+ return visit(stmt.body);
60
+ case "if":
61
+ return visit(stmt.thenBranch) || visit(stmt.elseBranch ?? []);
62
+ case "for":
63
+ return visit(stmt.body)
64
+ || (stmt.initializer ? visitStatement(stmt.initializer) : false);
65
+ case "while":
66
+ case "do_while":
67
+ case "for_of":
68
+ case "for_in":
69
+ return visit(stmt.body);
70
+ case "switch":
71
+ return stmt.cases.some((c) => visit(c.body ?? []));
72
+ case "try":
73
+ return visit(stmt.tryBlock) || visit(stmt.catchBlock ?? []) || visit(stmt.finallyBlock ?? []);
74
+ default:
75
+ return false;
76
+ }
77
+ };
78
+ const visit = (statements) => {
79
+ if (!statements)
80
+ return false;
81
+ for (const stmt of statements) {
82
+ if (visitStatement(stmt))
83
+ return true;
84
+ }
85
+ return false;
86
+ };
87
+ if (visit(program.topLevelStatements))
88
+ return true;
89
+ for (const fn of program.functions) {
90
+ if (visit(fn.statements))
91
+ return true;
92
+ }
93
+ for (const cls of program.classes) {
94
+ for (const m of cls.methods)
95
+ if (visit(m.statements))
96
+ return true;
97
+ for (const g of cls.getters)
98
+ if (visit(g.statements))
99
+ return true;
100
+ for (const s of cls.setters)
101
+ if (visit(s.statements))
102
+ return true;
103
+ if (cls.constructor && visit(cls.constructor.statements))
104
+ return true;
105
+ }
106
+ return false;
107
+ }
34
108
  /**
35
109
  * Extract a `#ifndef MACRO ... #endif` include-guard block from a shim line
36
110
  * list. Used to emit just the macro definition (e.g. CUTTLEFISH_UNDEFINED) in
@@ -118,6 +192,14 @@ export function buildEmitterContext(program, options) {
118
192
  })();
119
193
  const programAnalysis = analyzeProgram(program, strategy);
120
194
  if (options.platformContext) {
195
+ // The UI runtime's per-frame tick calls millis() (injected by the emitter,
196
+ // not present in user source), so a mounted UI needs the native millis ISR
197
+ // even when usesNativeTiming is false. OR entryHasUI() into the flag the
198
+ // AVR strategy reads when gating native_millis, so framework-avr doesn't
199
+ // drop the Timer0 ISR from a UI program.
200
+ if (!programAnalysis.usesNativeTiming && entryHasUI()) {
201
+ programAnalysis.usesNativeTiming = true;
202
+ }
121
203
  options.platformContext.analysis = programAnalysis;
122
204
  if (!options.platformContext.architecture && program.boardConstants) {
123
205
  options.platformContext.architecture = program.boardConstants.get("architecture");
@@ -223,6 +305,68 @@ export function buildEmitterContext(program, options) {
223
305
  if (!programAnalysis.usesStrPtr) {
224
306
  shimLines = filterShimBlock(shimLines, '#ifndef CUTTLEFISH_STR_BUF_SIZE', 'inline size_t (strlen)(const __tc_str_ptr& s) { return ::strlen(s.buf); }');
225
307
  }
308
+ // framework-avr native peripheral driver shims. Each block carries a
309
+ // CUTTLEFISH_*_BEGIN/END marker pair; strip the ones the program doesn't
310
+ // use. framework-arduino's shimLines contain none of these markers, so
311
+ // these filters are no-ops there. The strategies also self-gate on the
312
+ // same flags; this is the defensive backstop (mirrors how usesWDT/etc.
313
+ // backstop the strategy-side gating above).
314
+ if (!programAnalysis.usesUart) {
315
+ shimLines = filterShimBlock(shimLines, '// CUTTLEFISH_UART_BEGIN', '// CUTTLEFISH_UART_END');
316
+ shimLines = filterShimBlock(shimLines, '// CUTTLEFISH_UART_EXT_BEGIN', '// CUTTLEFISH_UART_EXT_END');
317
+ }
318
+ if (!programAnalysis.usesSPI) {
319
+ shimLines = filterShimBlock(shimLines, '// CUTTLEFISH_SPI_BEGIN', '// CUTTLEFISH_SPI_END');
320
+ }
321
+ if (!programAnalysis.usesI2C) {
322
+ shimLines = filterShimBlock(shimLines, '// CUTTLEFISH_TWI_BEGIN', '// CUTTLEFISH_TWI_END');
323
+ }
324
+ if (!programAnalysis.usesEEPROM) {
325
+ shimLines = filterShimBlock(shimLines, '// CUTTLEFISH_EEPROM_BEGIN', '// CUTTLEFISH_EEPROM_END');
326
+ }
327
+ if (!programAnalysis.usesTone) {
328
+ shimLines = filterShimBlock(shimLines, '// CUTTLEFISH_TONE_BEGIN', '// CUTTLEFISH_TONE_END');
329
+ }
330
+ // framework-esp32 native peripheral driver shims. Same defensive-backstop
331
+ // pattern as the UART/SPI/TWI/EEPROM/tone blocks above. framework-arduino's
332
+ // and framework-avr's shimLines contain none of these markers, so these
333
+ // filters are no-ops there.
334
+ if (!programAnalysis.usesGPIO) {
335
+ shimLines = filterShimBlock(shimLines, '// CUTTLEFISH_GPIO_BEGIN', '// CUTTLEFISH_GPIO_END');
336
+ }
337
+ if (!programAnalysis.usesPWM) {
338
+ shimLines = filterShimBlock(shimLines, '// CUTTLEFISH_PWM_BEGIN', '// CUTTLEFISH_PWM_END');
339
+ }
340
+ if (!programAnalysis.usesADC) {
341
+ shimLines = filterShimBlock(shimLines, '// CUTTLEFISH_ADC_BEGIN', '// CUTTLEFISH_ADC_END');
342
+ }
343
+ if (!programAnalysis.usesDAC) {
344
+ shimLines = filterShimBlock(shimLines, '// CUTTLEFISH_DAC_BEGIN', '// CUTTLEFISH_DAC_END');
345
+ }
346
+ if (!programAnalysis.usesPower) {
347
+ shimLines = filterShimBlock(shimLines, '// CUTTLEFISH_POWER_BEGIN', '// CUTTLEFISH_POWER_END');
348
+ }
349
+ if (!programAnalysis.usesWdt) {
350
+ shimLines = filterShimBlock(shimLines, '// CUTTLEFISH_WDT_BEGIN', '// CUTTLEFISH_WDT_END');
351
+ }
352
+ if (!programAnalysis.usesInterrupts) {
353
+ shimLines = filterShimBlock(shimLines, '// CUTTLEFISH_INTR_BEGIN', '// CUTTLEFISH_INTR_END');
354
+ }
355
+ if (!programAnalysis.usesPulse) {
356
+ shimLines = filterShimBlock(shimLines, '// CUTTLEFISH_PULSE_BEGIN', '// CUTTLEFISH_PULSE_END');
357
+ }
358
+ if (!programAnalysis.usesShift) {
359
+ shimLines = filterShimBlock(shimLines, '// CUTTLEFISH_SHIFT_BEGIN', '// CUTTLEFISH_SHIFT_END');
360
+ }
361
+ if (!programAnalysis.usesWifi) {
362
+ shimLines = filterShimBlock(shimLines, '// CUTTLEFISH_WIFI_BEGIN', '// CUTTLEFISH_WIFI_END');
363
+ }
364
+ if (!programAnalysis.usesHttp) {
365
+ shimLines = filterShimBlock(shimLines, '// CUTTLEFISH_HTTP_BEGIN', '// CUTTLEFISH_HTTP_END');
366
+ }
367
+ if (!programAnalysis.usesPreferences) {
368
+ shimLines = filterShimBlock(shimLines, '// CUTTLEFISH_PREFERENCES_BEGIN', '// CUTTLEFISH_PREFERENCES_END');
369
+ }
226
370
  profileDiagnostics = [...strategy.profileDiagnostics(program, options.platformContext)];
227
371
  }
228
372
  for (const imported of program.imports) {
@@ -745,6 +889,17 @@ export function buildEmitterContext(program, options) {
745
889
  if (stringEnumNames.size > 0) {
746
890
  includes.push(strategy.cstringHeader());
747
891
  }
892
+ // `<avr/wdt.h>` is only needed when the program actually uses the watchdog.
893
+ // The HAL resolver lowers WDT.enable/reset/disable to bare wdt_*() calls
894
+ // (or constant-folded wdt_enable(WDTO_*) macros), all of which require the
895
+ // header. Detect the structured `wdt.*` hal-ops directly here rather than
896
+ // relying on programAnalysis.usesWDT (which misses these ops — they carry
897
+ // a typed operation name, not raw code the analysis scans) or on a forced
898
+ // include in the AVR profile (which leaked the header into every AVR
899
+ // program, even ones that never touch the watchdog like `led.toggle()`).
900
+ if (programUsesWdt(program)) {
901
+ includes.push("<avr/wdt.h>");
902
+ }
748
903
  // Placeholder defaults for fields that are computed later by other phases
749
904
  const noopFixPointer = (c) => c;
750
905
  // Demo #18 Finding B: collect the names of non-exported free functions that
@@ -597,6 +597,8 @@ export function runTopLevelPreprocessing(ctx) {
597
597
  statements: callbackIR.statements ?? callbackIR.body ?? [],
598
598
  debounceMs: callbackIR.debounceMs,
599
599
  isInterruptHandler: callbackIR.isInterruptHandler,
600
+ ...(callbackIR.returnType ? { returnType: callbackIR.returnType } : {}),
601
+ ...(callbackIR.typedParams ? { typedParams: callbackIR.typedParams } : {}),
600
602
  });
601
603
  replacePlaceholderInAllStatements(filteredTopLevelExecutables, rc.placeholderName, callbackName);
602
604
  for (const fn of mappedFunctions) {
@@ -34,7 +34,7 @@ import { getRadioGroups } from "../../ir/ui-element-auto-wire.js";
34
34
  // minPressure gate is skipped for them. Resistive controllers (XPT2046,
35
35
  // STMPE610, Adafruit_TouchScreen) keep the gate.
36
36
  // ---------------------------------------------------------------------------
37
- const CAPACITIVE_TOUCH_LIBS = new Set(["FT6336U", "sdl"]);
37
+ const CAPACITIVE_TOUCH_LIBS = new Set(["FT6336U", "GT911", "CST816S", "sdl"]);
38
38
  /** Generate the ui_poll_touch() body, or null for the no-touch path. */
39
39
  export function generateTouchPollBody(input) {
40
40
  const { library, minPressure, calibration, profile } = input;
@@ -141,9 +141,14 @@ export function emitUIRuntime(ctx) {
141
141
  // display-adapter registry. Must precede the runtime header so the
142
142
  // adapter functions (__tc_display, display_init, etc.) are available.
143
143
  const profile = getDisplayProfile();
144
- // 0. SPI-bus display drivers (ILI9341, ST7796, …) need the SPI library.
145
- // Host render targets (sdl, native-preview) have no SPI bus, so skip it.
146
- const spiDriver = profile?.driver !== "sdl" && profile?.driver !== "native-preview";
144
+ // 0. SPI-bus display drivers (ILI9341, ST7796, …) need the Arduino SPI
145
+ // library but ONLY on the Adafruit path. Native adapters (AVR, ESP32)
146
+ // emit their own bus setup and would break on <SPI.h>. Host render targets
147
+ // (sdl, native-preview) have no SPI bus at all.
148
+ const nativeAdapter = ctx.strategy?.providesDisplayAdapter?.() ?? false;
149
+ const spiDriver = profile?.driver !== "sdl"
150
+ && profile?.driver !== "native-preview"
151
+ && !nativeAdapter;
147
152
  if (spiDriver && !ctx.includes.includes("<SPI.h>")) {
148
153
  ctx.includes.push("<SPI.h>");
149
154
  }
@@ -151,7 +156,7 @@ export function emitUIRuntime(ctx) {
151
156
  // buildEmitterContext (setup.ts), NOT here — emitPreamble runs before this
152
157
  // function, so a push here would be too late to land in the output.
153
158
  if (profile) {
154
- const adapter = generateDisplayAdapter(profile);
159
+ const adapter = generateDisplayAdapter(profile, ctx.strategy);
155
160
  // Prepend includes to the very front — Arduino's auto-prototyper scans
156
161
  // the whole .ino and generates prototypes that reference GFXcanvas16
157
162
  // etc. before any #include, so the GFX header must come first.
@@ -164,6 +169,14 @@ export function emitUIRuntime(ctx) {
164
169
  ctx.sourceLines.push(is888
165
170
  ? "#define UI_COLOR_DEPTH 888\n#define UI_COLOR_T uint32_t\n#define UI_DIM_MASK 0x7F7F7Fu"
166
171
  : "#define UI_COLOR_DEPTH 565\n#define UI_COLOR_T uint16_t\n#define UI_DIM_MASK 0x7BEFu");
172
+ // Native display adapters (AVR, ESP32) instantiate CuttlefishGFX by value
173
+ // in their declaration block. The class MUST be defined before that —
174
+ // emit the cuttlefish-gfx slice here, between the color preamble and the
175
+ // adapter declaration. Arduino path (Adafruit) returns "" from the slice.
176
+ const nativeDisplayActive = ctx.strategy?.providesDisplayAdapter?.() ?? false;
177
+ if (nativeDisplayActive) {
178
+ ctx.sourceLines.push(ui.emitCuttlefishGfx(true));
179
+ }
167
180
  ctx.sourceLines.push(adapter.declaration);
168
181
  ctx.sourceLines.push(adapter.functions);
169
182
  }
@@ -173,7 +186,7 @@ export function emitUIRuntime(ctx) {
173
186
  if (profile.touch) {
174
187
  const t = profile.touch;
175
188
  if (t.library) {
176
- touchAdapter = generateTouchAdapter(t);
189
+ touchAdapter = generateTouchAdapter(t, ctx.strategy);
177
190
  // Prepend includes to the front (Arduino auto-prototyper safety).
178
191
  ctx.sourceLines.unshift(touchAdapter.includes[0]);
179
192
  for (let i = 1; i < touchAdapter.includes.length; i++) {
@@ -252,13 +265,15 @@ export function emitUIRuntime(ctx) {
252
265
  // header so its #ifndef guards adopt them. Source of truth:
253
266
  // resolveScrollConfig(profile.scroll). Defaults derive from the declared
254
267
  // touch hardware, so the demo (XPT2046) gets resistive+full with no config.
255
- const scroll = resolveScrollConfig(profile, { buildTarget: profile._buildTarget });
268
+ const scroll = resolveScrollConfig(profile, { buildTarget: profile._buildTarget, psram: profile._psram });
256
269
  ctx.sourceLines.push(`#define UI_SCROLL_MAX_OVERSCROLL ${scroll.maxOverscroll}`, `#define UI_SCROLL_STIFFNESS_X10 ${Math.round(scroll.stiffness * 10)}`, `#define UI_SCROLL_EDGE_SNAP_PX ${scroll.edgeSnapPx}`, `#define UI_SCROLL_DRAG_SCALE_X10 ${Math.round(scroll.dragScale * 10)}`, `#define UI_SCROLL_INPUT_TIER_CAPACITIVE ${scroll.inputTier === "capacitive" ? 1 : 0}`, `#define UI_SCROLL_INPUT_TIER_RESISTIVE ${scroll.inputTier === "resistive" ? 1 : 0}`, `#define UI_SCROLL_INPUT_TIER_NONE ${scroll.inputTier === "none" ? 1 : 0}`, `#define UI_SCROLL_RENDER_TIER_FULL ${scroll.renderTier === "full" ? 1 : 0}`, `#define UI_SCROLL_RENDER_TIER_CONSTRAINED ${scroll.renderTier === "constrained" ? 1 : 0}`, `#define UI_SCROLL_CANVAS_BUDGET_BYTES ${scroll.scrollCanvasBudgetBytes}`, ...(scroll.debug ? [`#define UI_SCROLL_DEBUG 1`] : []));
257
270
  // Forward-declare __ui_kb_set_onchange before the runtime header: the
258
271
  // header's keyboard-open function calls it, but the definition is emitted
259
272
  // later (section 8b). On native (single TU) this must precede the header.
260
273
  ctx.sourceLines.push(`void __ui_kb_set_onchange();`);
261
- ctx.sourceLines.push(emitRuntimeHeader());
274
+ ctx.sourceLines.push(emitRuntimeHeader({
275
+ nativeDisplayActive: ctx.strategy?.providesDisplayAdapter?.() ?? false,
276
+ }));
262
277
  // 1.5. Touch poll function (uses the adapter pattern). Body is generated by
263
278
  // generateTouchPollBody — extracted for unit-testability of the capacitive
264
279
  // vs resistive branch (FT6336U/SDL skip the minPressure gate).
@@ -10,6 +10,7 @@ import { accessorGetterName } from "./utils/cpp-helpers.js";
10
10
  import { INTEGRAL_CPP_TYPE_RE } from "./utils/cpp-helpers.js";
11
11
  import { renderPeripheralProperty } from "../mapping/peripheral-names.js";
12
12
  import { parseCppType, renderCppType, bareType, parsedIsPointer, parsedIsVector, needsCStrForStringLike } from "../api/shared/cpp-type-ir.js";
13
+ import { cppTypeForHalOp } from "./utils/hal-op-cpp-type.js";
13
14
  /**
14
15
  * Renders ExpressionIR nodes to C++ code strings.
15
16
  */
@@ -403,6 +404,8 @@ export class ExpressionRenderer {
403
404
  }
404
405
  case "unary":
405
406
  return expr.operator === "!" ? "bool" : this.inferExpressionCppType(expr.operand, knownVariableTypes);
407
+ case "hal-expr":
408
+ return cppTypeForHalOp(expr.operation.operation);
406
409
  default:
407
410
  return undefined;
408
411
  }
@@ -714,10 +717,16 @@ export class ExpressionRenderer {
714
717
  }
715
718
  case "hal-expr": {
716
719
  const rendered = this.render(expr, exprTransformer);
717
- // HAL expressions that resolve to string-like outputs use %s
718
720
  if (rendered.startsWith('"') || this.stringVarNames?.has(rendered)) {
719
721
  return { format: "%s", arg: rendered, estimatedLength: 32 };
720
722
  }
723
+ const cppType = cppTypeForHalOp(expr.operation.operation);
724
+ if (cppType && this.isStringLikeCppType(cppType)) {
725
+ return { format: "%s", arg: rendered, estimatedLength: 32 };
726
+ }
727
+ if (cppType === "bool") {
728
+ return { format: "%s", arg: `(${rendered} ? "true" : "false")`, estimatedLength: 5 };
729
+ }
721
730
  return { format: "%d", arg: rendered, estimatedLength: 12 };
722
731
  }
723
732
  case "method-call":
@@ -8,6 +8,7 @@
8
8
  // Types are imported from @typecad/cuttlefish/api/shared.
9
9
  // ---------------------------------------------------------------------------
10
10
  import { escapeCppStringLiteral, escapeSnprintfFormatFragment } from "../utils/strings.js";
11
+ import { cppTypeForHalOp } from "./utils/hal-op-cpp-type.js";
11
12
  import { parsedElementString, parsedIsStringLike } from "../api/shared/cpp-type-ir.js";
12
13
  // ---------------------------------------------------------------------------
13
14
  // Scope state management
@@ -369,6 +370,13 @@ export function inferSnprintfArg(expr, strategy, scopeState, renderExpression, p
369
370
  if (rendered.startsWith('"') || stringVarNames?.has(rendered)) {
370
371
  return { format: "%s", arg: rendered, estimatedLength: 32, preludeLines: [] };
371
372
  }
373
+ const cppType = cppTypeForHalOp(expr.operation.operation);
374
+ if (cppType && strategy.isStringLikeType(cppType)) {
375
+ return { format: "%s", arg: rendered, estimatedLength: 32, preludeLines: [] };
376
+ }
377
+ if (cppType === "bool") {
378
+ return { format: "%s", arg: `(${rendered} ? "true" : "false")`, estimatedLength: 5, preludeLines: [] };
379
+ }
372
380
  return { format: "%d", arg: rendered, estimatedLength: 12, preludeLines: [] };
373
381
  }
374
382
  default:
@@ -485,6 +485,19 @@ export class StatementRenderer {
485
485
  const rawStmt = statement.callee.slice('__RAW_STMT__'.length);
486
486
  return forHeader ? rawStmt : `${rawStmt.endsWith(';') ? rawStmt : rawStmt + ';'}`;
487
487
  }
488
+ // Awaited network markers (__WIFI_WAIT__/__HTTP_WAIT__) reaching the
489
+ // plain renderer means the await sits outside an async state machine
490
+ // (top-level await, or a position the state-machine splitter doesn't
491
+ // support). Fall back to the blocking form of the op they carry.
492
+ if ((statement.callee === "__WIFI_WAIT__" || statement.callee === "__HTTP_WAIT__" || statement.callee === "__BLE_WAIT__" || statement.callee === "__HAL_WAIT__")
493
+ && statement.args[0]?.kind === "hal-expr") {
494
+ const resolved = routeHALOp(statement.args[0].operation, this.strategy);
495
+ const code = resolved?.code ?? (resolved?.expression ? `${resolved.expression};` : undefined);
496
+ if (code) {
497
+ return forHeader ? code.replace(/;$/, "") : code;
498
+ }
499
+ return `/* unhandled awaited hal-op: ${statement.args[0].operation.operation} */`;
500
+ }
488
501
  // Handle emit() — compile-time C++ injection
489
502
  if (statement.callee === "__EMIT__") {
490
503
  const rawText = statement.args.map(arg => this.renderEmitArg(arg)).join("");