@typecad/framework-zephyr 1.0.0-alpha.10 → 1.0.0-alpha.11

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 (56) hide show
  1. package/dist/chips/esp32.js +12 -0
  2. package/dist/chips/types.d.ts +30 -0
  3. package/dist/chips/xiao-ble.js +6 -0
  4. package/dist/display/gfx.d.ts +12 -3
  5. package/dist/display/gfx.js +130 -17
  6. package/dist/display/profiles.js +13 -0
  7. package/dist/display/ui-adapter.js +6 -0
  8. package/dist/doctor.d.ts +3 -3
  9. package/dist/doctor.js +56 -29
  10. package/dist/dt-config/kconfig.d.ts +3 -0
  11. package/dist/dt-config/kconfig.js +18 -0
  12. package/dist/dt-config/overlay.js +5 -0
  13. package/dist/framework.manifest.js +37 -14
  14. package/dist/index.d.ts +1 -0
  15. package/dist/index.js +5 -0
  16. package/dist/licenses.d.ts +59 -0
  17. package/dist/licenses.js +347 -0
  18. package/dist/lowering/dac.d.ts +15 -0
  19. package/dist/lowering/dac.js +69 -0
  20. package/dist/lowering/fs.d.ts +16 -0
  21. package/dist/lowering/fs.js +121 -0
  22. package/dist/lowering/hwtimer.d.ts +15 -0
  23. package/dist/lowering/hwtimer.js +84 -0
  24. package/dist/lowering/index.d.ts +4 -1
  25. package/dist/lowering/index.js +12 -3
  26. package/dist/strategy.js +186 -14
  27. package/dist/toolchain/compat.js +10 -1
  28. package/dist/toolchain/env-check.d.ts +93 -0
  29. package/dist/toolchain/env-check.js +190 -0
  30. package/dist/toolchain/scaffold.js +3 -0
  31. package/dist/toolchain/west-discover.d.ts +11 -3
  32. package/dist/toolchain/west-discover.js +80 -6
  33. package/dist/toolchain/west-spawn.js +15 -0
  34. package/package.json +4 -4
  35. package/src/chips/esp32.ts +12 -0
  36. package/src/chips/types.ts +29 -0
  37. package/src/chips/xiao-ble.ts +6 -0
  38. package/src/display/gfx.ts +135 -19
  39. package/src/display/profiles.ts +13 -0
  40. package/src/display/ui-adapter.ts +5 -0
  41. package/src/doctor.ts +77 -56
  42. package/src/dt-config/kconfig.ts +19 -0
  43. package/src/dt-config/overlay.ts +5 -0
  44. package/src/framework.manifest.ts +38 -14
  45. package/src/index.ts +6 -0
  46. package/src/licenses.ts +425 -0
  47. package/src/lowering/dac.ts +82 -0
  48. package/src/lowering/fs.ts +127 -0
  49. package/src/lowering/hwtimer.ts +101 -0
  50. package/src/lowering/index.ts +9 -2
  51. package/src/strategy.ts +180 -14
  52. package/src/toolchain/compat.ts +154 -145
  53. package/src/toolchain/env-check.ts +285 -0
  54. package/src/toolchain/scaffold.ts +3 -0
  55. package/src/toolchain/west-discover.ts +88 -8
  56. package/src/toolchain/west-spawn.ts +15 -0
@@ -0,0 +1,101 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Hardware-timer lowering — Zephyr counter API
3
+ //
4
+ // HAL `hwtimer.*` (instance → frequency/overflow/start/stop) maps onto Zephyr's
5
+ // counter driver (<zephyr/drivers/counter.h>). A chip declares the counter
6
+ // devices it exposes (e.g. nRF RTC1 — RTC0 is kernel-owned) via
7
+ // `hwtimer.controllers[instance].nodeLabel`.
8
+ //
9
+ // Frequency model: a Zephyr counter has a fixed clock; the HAL
10
+ // set_frequency(hz) is realized as a top value of counter_freq/hz with an alarm
11
+ // callback (the on_overflow handler). start() arms both the top value and the
12
+ // callback so the call order (setFrequency → onOverflow → start, or any
13
+ // permutation) is handled uniformly.
14
+ //
15
+ // Targets without a free counter omit `hwtimer`; usage lowers to a comment and
16
+ // profileDiagnostics flags it. The JS setInterval/setTimeout polyfill
17
+ // (k_timer) is a separate surface and is unaffected.
18
+ // ---------------------------------------------------------------------------
19
+
20
+ import type { HALOpIR } from '@typecad/cuttlefish/api/shared';
21
+ import type { ZephyrChipDescriptor } from '../chips/types.js';
22
+
23
+ /** Per-instance emitted symbol stems. */
24
+ function devVar(instance: number): string {
25
+ return `__tc_hw_dev_${instance}`;
26
+ }
27
+ function hzVar(instance: number): string {
28
+ return `__tc_hw_hz_${instance}`;
29
+ }
30
+ function cbVar(instance: number): string {
31
+ return `__tc_hw_cb_${instance}`;
32
+ }
33
+
34
+ /**
35
+ * Emit the per-instance counter device handles + frequency/callback state.
36
+ * Called from shimLines when the program uses hardware timers.
37
+ */
38
+ export function hwtimerInitLines(chip: ZephyrChipDescriptor): string[] {
39
+ const controllers = chip.hwtimer?.controllers ?? [];
40
+ if (controllers.length === 0) return [];
41
+ const lines: string[] = ['// CUTTLEFISH_HWTIMER_BEGIN'];
42
+ controllers.forEach((c, i) => {
43
+ lines.push(
44
+ `static const struct device* ${devVar(i)} = DEVICE_DT_GET(DT_NODELABEL(${c.nodeLabel}));`,
45
+ `static uint32_t ${hzVar(i)} = 1;`,
46
+ `static counter_top_callback_t ${cbVar(i)} = NULL;`,
47
+ );
48
+ });
49
+ lines.push('// CUTTLEFISH_HWTIMER_END');
50
+ return lines;
51
+ }
52
+
53
+ /**
54
+ * Resolve a HAL hwtimer.* op to Zephyr C++.
55
+ * Returns `{ code }` for statement ops.
56
+ */
57
+ export function lowerHwtimer(
58
+ op: HALOpIR,
59
+ chip: ZephyrChipDescriptor,
60
+ ): { code?: string; expression?: string } {
61
+ const o = op as any;
62
+ const controllers = chip.hwtimer?.controllers ?? [];
63
+
64
+ // No counter on this target. Comment + (in profileDiagnostics) a clear error.
65
+ if (controllers.length === 0) {
66
+ return { code: `/* hwtimer instance ${o.instance}: no counter device on ${chip.id} */` };
67
+ }
68
+
69
+ const instance = typeof o.instance === 'number' ? o.instance : parseInt(String(o.instance), 10);
70
+ if (isNaN(instance) || instance < 0 || instance >= controllers.length) {
71
+ return { code: `/* hwtimer instance ${o.instance}: out of range on ${chip.id} */` };
72
+ }
73
+
74
+ switch (op.operation) {
75
+ case 'hwtimer.set_frequency':
76
+ return { code: `${hzVar(instance)} = ${o.hz};` };
77
+ case 'hwtimer.on_overflow':
78
+ return { code: `${cbVar(instance)} = (${o.handler});` };
79
+ case 'hwtimer.start':
80
+ // Arm the top value (counter_freq / desired_hz) + the overflow callback,
81
+ // then start the counter. Doing both here handles any call order — the
82
+ // HAL typical sequence (setFrequency → onOverflow → start) and permutations.
83
+ return {
84
+ code: [
85
+ `{`,
86
+ ` uint32_t __f = counter_get_frequency(${devVar(instance)});`,
87
+ ` uint32_t __top = __f ? (__f / ${hzVar(instance)}) : 0U;`,
88
+ ` if (__top > 0U) { (void)counter_set_top_value(${devVar(instance)}, __top, ${cbVar(instance)}, NULL); }`,
89
+ ` (void)counter_start(${devVar(instance)});`,
90
+ `}`,
91
+ ].join(' '),
92
+ };
93
+ case 'hwtimer.stop':
94
+ return { code: `(void)counter_stop(${devVar(instance)});` };
95
+ default:
96
+ throw new Error(
97
+ `framework-zephyr does not yet support HAL op \`${op.operation}\`. ` +
98
+ `Open an issue or use rawCpp() to emit it manually.`,
99
+ );
100
+ }
101
+ }
@@ -3,8 +3,8 @@
3
3
  //
4
4
  // Routes a HALOpIR to the per-category lowering module by category prefix
5
5
  // (e.g. 'gpio.write' → lowerGpio). Returns undefined for categories the
6
- // framework does not lower (display/fs/mdns/ota/rmt/dac/
7
- // hwtimer/capacitive/temp/espnow/crypto/i2s/twai/usb/eth/pcnt/mcpwm — see
6
+ // framework does not lower (display/mdns/ota/rmt/
7
+ // capacitive/temp/espnow/crypto/i2s/twai/usb/eth/pcnt/mcpwm — see
8
8
  // the manifest), so the transpiler falls back and the manifest validator
9
9
  // cross-checks the unsupported categories. (board.* is registered below but is
10
10
  // a dead-letter — its values are constant-folded at IR-build time.)
@@ -38,11 +38,15 @@ import { lowerMqtt } from './mqtt.js';
38
38
  import { lowerPreferences } from './preferences.js';
39
39
  import { lowerBoard } from './board.js';
40
40
  import { lowerRandom } from './random.js';
41
+ import { lowerDac } from './dac.js';
42
+ import { lowerFs } from './fs.js';
43
+ import { lowerHwtimer } from './hwtimer.js';
41
44
 
42
45
  export {
43
46
  lowerGpio, lowerTiming, lowerAdc, lowerPwm, lowerI2c, lowerSpi, lowerUart,
44
47
  lowerInterrupt, lowerWdt, lowerPower, lowerTone, lowerPulseOrShift, lowerBle,
45
48
  lowerWifi, lowerHttp, lowerMqtt, lowerPreferences, lowerBoard, lowerRandom,
49
+ lowerDac, lowerFs, lowerHwtimer,
46
50
  };
47
51
 
48
52
  /**
@@ -58,11 +62,13 @@ export function lowerHalOp(
58
62
  if (op.operation.startsWith('timing.')) return lowerTiming(op);
59
63
  if (op.operation.startsWith('adc.')) return lowerAdc(op, chip);
60
64
  if (op.operation.startsWith('pwm.')) return lowerPwm(op, chip);
65
+ if (op.operation.startsWith('dac.')) return lowerDac(op, chip);
61
66
  if (op.operation.startsWith('i2c.')) return lowerI2c(op, chip);
62
67
  if (op.operation.startsWith('spi.')) return lowerSpi(op, chip);
63
68
  if (op.operation.startsWith('uart.')) return lowerUart(op);
64
69
  if (op.operation.startsWith('interrupt.')) return lowerInterrupt(op, chip);
65
70
  if (op.operation.startsWith('wdt.')) return lowerWdt(op);
71
+ if (op.operation.startsWith('hwtimer.')) return lowerHwtimer(op, chip);
66
72
  if (op.operation.startsWith('power.')) return lowerPower(op);
67
73
  if (op.operation.startsWith('tone.')) return lowerTone(op, chip);
68
74
  // pulse.* and shift.* share a bit-bang lowering module.
@@ -74,6 +80,7 @@ export function lowerHalOp(
74
80
  if (op.operation.startsWith('http.')) return lowerHttp(op);
75
81
  if (op.operation.startsWith('mqtt.')) return lowerMqtt(op);
76
82
  if (op.operation.startsWith('preferences.')) return lowerPreferences(op);
83
+ if (op.operation.startsWith('fs.')) return lowerFs(op);
77
84
  // board.resolve is constant-folded at IR-build time; lowerBoard is the
78
85
  // dead-letter reached only on an unresolvable path.
79
86
  if (op.operation.startsWith('board.')) return lowerBoard(op);
package/src/strategy.ts CHANGED
@@ -36,7 +36,9 @@ import type {
36
36
  } from '@typecad/cuttlefish/api/shared';
37
37
  import { DEFAULT_STDLIB_SUPPORT } from '@typecad/cuttlefish/api/shared';
38
38
  import { buildWorkerRuntimePolyfill } from '@typecad/cuttlefish/api/shared';
39
+ import { applyStringMethodRewrites } from '@typecad/cuttlefish/api/shared';
39
40
  import { programUsesSafety } from '@typecad/cuttlefish/api';
41
+ import { entryHasUI } from '@typecad/cuttlefish/ui-hook';
40
42
  import { chipForTarget, setActiveChip, getActiveChip } from './chips/index.js';
41
43
  import { resolveChipFromBoard } from './chips/resolve.js';
42
44
  import { emitGpioDevDispatcher } from './chips/controllers.js';
@@ -44,6 +46,9 @@ import { lowerHalOp } from './lowering/index.js';
44
46
  import { buildZephyrWorkerBacking } from './lowering/worker-backing.js';
45
47
  import { adcInitLines } from './lowering/adc.js';
46
48
  import { pwmInitLines } from './lowering/pwm.js';
49
+ import { dacInitLines } from './lowering/dac.js';
50
+ import { fsInitLines } from './lowering/fs.js';
51
+ import { hwtimerInitLines } from './lowering/hwtimer.js';
47
52
  import { i2cInitLines } from './lowering/i2c.js';
48
53
  import { spiInitLines } from './lowering/spi.js';
49
54
  import { uartInitLines } from './lowering/uart.js';
@@ -137,6 +142,13 @@ export class ZephyrStrategy implements PlatformStrategy {
137
142
  if (isPrintf && !inc.includes('<zephyr/drivers/uart.h>')) inc.push('<zephyr/drivers/uart.h>');
138
143
  if (uses('usesADC')) inc.push('<zephyr/drivers/adc.h>');
139
144
  if (uses('usesPWM')) inc.push('<zephyr/drivers/pwm.h>');
145
+ if (uses('usesDAC')) inc.push('<zephyr/drivers/dac.h>');
146
+ // Filesystem: littlefs on the storage partition. <cstring> backs the
147
+ // shim's strlen; the storage/flash_map + fs/littlefs headers carry the
148
+ // FIXED_PARTITION_ID macro + FS_LITTLEFS_DECLARE_DEFAULT_CONFIG the shim uses.
149
+ if (uses('usesFS')) inc.push('<zephyr/fs/fs.h>', '<zephyr/fs/littlefs.h>', '<zephyr/storage/flash_map.h>', '<cstring>');
150
+ // Hardware timers via the counter driver.
151
+ if (uses('usesHwtimer')) inc.push('<zephyr/drivers/counter.h>');
140
152
  if (uses('usesWDT')) inc.push('<zephyr/drivers/watchdog.h>');
141
153
  if (uses('usesPower')) inc.push('<zephyr/pm/pm.h>', '<zephyr/pm/state.h>', '<zephyr/pm/policy.h>');
142
154
  // BLE: the bt_* GATT API + the flat-string headers the shim uses. <string>
@@ -300,19 +312,23 @@ export class ZephyrStrategy implements PlatformStrategy {
300
312
  }
301
313
  if (uses('usesADC') && chip.adc) lines.push(...adcInitLines(chip));
302
314
  if (uses('usesPWM') && chip.pwm) lines.push(...pwmInitLines(chip));
315
+ if (uses('usesDAC') && chip.dac) lines.push(...dacInitLines(chip));
316
+ if (uses('usesHwtimer') && chip.hwtimer) lines.push(...hwtimerInitLines(chip));
303
317
  if (uses('usesInterrupts')) lines.push(...interruptInitLines(chip));
304
318
  if (uses('usesWDT') && chip.wdt) lines.push(...wdtInitLines(chip));
305
319
  if (uses('usesBle')) lines.push(...bleInitLines());
306
- // Display runtime (minimal rect/text renderer): gated on the analyzer's
307
- // usesDisplay flag (set by display.* hal-ops). This is the DIRECT-call
308
- // display path (user code calling screen.display.fillRect etc.). It is
309
- // SUPPRESSED when the UI display adapter is active — the adapter seam
310
- // (providesDisplayAdapter/resolveDisplayAdapter) emits its own display_*
311
- // runtime that drives the UI rendering pipeline via CuttlefishGFX, and the
312
- // two define the same symbols (display_init, __tc_display_line) so emitting
313
- // both causes redefinition errors. The adapter is active for the
314
- // strategy-owned drivers (ili9341-zephyr, st7796-zephyr).
315
- if (uses('usesDisplay') && !this.providesDisplayAdapter()) {
320
+ // Display runtime (rect/text renderer): the DIRECT-call display path (user
321
+ // code calling screen.display.fillRect etc., no @typecad/ui). Emitted only
322
+ // when the program uses display.* but is NOT a UI program the UI display
323
+ // adapter (emitted by cuttlefish's emitUIRuntime, solely under entryHasUI())
324
+ // defines the same display_init symbol, so emitting both would collide.
325
+ // `providesDisplayAdapter()` is a static capability (always true here) and
326
+ // does NOT track whether the adapter is actually emitted for THIS build, so
327
+ // the per-program UI signal (entryHasUI) is the correct gate. Without this,
328
+ // a direct display.* program has no definition for display_init/
329
+ // display_fill_rect/draw_rect/draw_text/flush (the gfx runtime was
330
+ // previously dead code).
331
+ if (uses('usesDisplay') && !entryHasUI()) {
316
332
  const rt = buildDisplayRuntime(this._displayState.profile);
317
333
  lines.push(...rt.stateLines);
318
334
  lines.push(rt.fontTable);
@@ -322,6 +338,7 @@ export class ZephyrStrategy implements PlatformStrategy {
322
338
  if (uses('usesHttp')) lines.push(...httpInitLines());
323
339
  if (uses('usesMqtt')) lines.push(...mqttInitLines());
324
340
  if (uses('usesPreferences')) lines.push(...preferencesInitLines());
341
+ if (uses('usesFS')) lines.push(...fsInitLines());
325
342
  if (uses('usesRandom')) lines.push(...randomInitLines());
326
343
 
327
344
  lines.push('#endif // CUTTLEFISH_SHIM_DEFINED');
@@ -429,6 +446,8 @@ export class ZephyrStrategy implements PlatformStrategy {
429
446
  const outputPins = new Set<number>();
430
447
  const adcReadPins = new Set<number>();
431
448
  const interruptPins = new Set<number>();
449
+ const dacPins = new Set<number>();
450
+ const hwtimerInstances = new Set<number>();
432
451
  let usesWifiOps = false;
433
452
  let usesHttpOps = false;
434
453
  let usesMqttOps = false;
@@ -449,6 +468,15 @@ export class ZephyrStrategy implements PlatformStrategy {
449
468
  if (op.operation === 'interrupt.attach' && typeof op.pin === 'number') {
450
469
  interruptPins.add(op.pin);
451
470
  }
471
+ if (op.operation === 'dac.write' && typeof op.pin === 'number') {
472
+ dacPins.add(op.pin);
473
+ }
474
+ if (typeof op.operation === 'string' && op.operation.startsWith('hwtimer.')) {
475
+ const inst = typeof op.instance === 'number'
476
+ ? op.instance
477
+ : parseInt(String(op.instance), 10);
478
+ if (!isNaN(inst)) hwtimerInstances.add(inst);
479
+ }
452
480
  if (typeof op.operation === 'string' && op.operation.startsWith('wifi.')) {
453
481
  usesWifiOps = true;
454
482
  }
@@ -510,6 +538,56 @@ export class ZephyrStrategy implements PlatformStrategy {
510
538
  }
511
539
  }
512
540
 
541
+ // ── DAC pin validity ────────────────────────────────────────────────────
542
+ // dac.write resolves a HAL pin to a channel via the chip descriptor's
543
+ // dac.channels map. A pin not in that map lowers to a comment (silent
544
+ // no-op), and a chip without a `dac` entry (nRF52840, ESP32-S3) has no DAC
545
+ // at all. Flag either case so the user gets a clear message instead of a
546
+ // pin that silently does nothing.
547
+ if (dacPins.size > 0) {
548
+ const dacChannels = new Set((chip.dac?.channels ?? []).map((c) => c.pin));
549
+ for (const pin of dacPins) {
550
+ if (!dacChannels.has(pin)) {
551
+ diags.push({
552
+ severity: 'error',
553
+ code: 'zephyr-dac-pin-unavailable',
554
+ message: `GPIO ${pin} is not a DAC channel on ${chip.id} and cannot be driven with dac.write.`,
555
+ hint: dacChannels.size > 0
556
+ ? `Use a DAC-capable pin. On ${chip.id}: ${[...dacChannels].sort((x, y) => x - y).join(', ')}.`
557
+ : `${chip.id} has no DAC. Use an esp32_devkitc target (ESP32 DAC on GPIO25/26).`,
558
+ source: program.fileName,
559
+ });
560
+ }
561
+ }
562
+ }
563
+
564
+ // ── Hardware-timer instance validity ────────────────────────────────────
565
+ // hwtimer.* resolves the instance index to a counter device via the chip
566
+ // descriptor's hwtimer.controllers. A chip without that entry (or an
567
+ // out-of-range instance) lowers to a comment — flag it so the user knows
568
+ // the timer will never fire.
569
+ if (hwtimerInstances.size > 0) {
570
+ const controllerCount = chip.hwtimer?.controllers.length ?? 0;
571
+ for (const inst of hwtimerInstances) {
572
+ if (controllerCount === 0) {
573
+ diags.push({
574
+ severity: 'error',
575
+ code: 'zephyr-hwtimer-unavailable',
576
+ message: `Hardware timer instance ${inst} is used but ${chip.id} exposes no free counter device.`,
577
+ hint: `${chip.id} declares no hwtimer.controllers. Use a target with a free counter (e.g. nRF RTC1).`,
578
+ source: program.fileName,
579
+ });
580
+ } else if (inst < 0 || inst >= controllerCount) {
581
+ diags.push({
582
+ severity: 'error',
583
+ code: 'zephyr-hwtimer-instance-out-of-range',
584
+ message: `Hardware timer instance ${inst} is out of range on ${chip.id} (0..${controllerCount - 1}).`,
585
+ source: program.fileName,
586
+ });
587
+ }
588
+ }
589
+ }
590
+
513
591
  // ── WiFi target validity ────────────────────────────────────────────────
514
592
  // WiFi ops require a chip with a WiFi radio. The ESP32-S3 descriptor sets
515
593
  // wifi.supported; the XIAO nRF52840 omits it (no radio). Flag wifi usage on
@@ -656,6 +734,18 @@ export class ZephyrStrategy implements PlatformStrategy {
656
734
  v = v.replace(/\bundefined\b/g, 'CUTTLEFISH_UNDEFINED');
657
735
  v = v.replace(/\bnull\b/g, 'CUTTLEFISH_UNDEFINED');
658
736
  }
737
+ // String-method lowering is shared across targets (see string-method-
738
+ // registry). Zephyr's strings are const char*, so the __tc_* helpers this
739
+ // rewrites to (defined by the string_methods polyfill) take const char*.
740
+ // includes/startsWith lower to inline strstr/strncmp (matching framework-
741
+ // arduino); everything else → a __tc_* helper call.
742
+ v = applyStringMethodRewrites(v, {
743
+ wrapReceiverFor: new Set(['indexOf']),
744
+ special: {
745
+ includes: (recv, args) => `(strstr(${recv}, ${args[0]}) != NULL)`,
746
+ startsWith: (recv, args) => `(strncmp(${recv}, ${args[0]}, strlen(${args[0]})) == 0)`,
747
+ },
748
+ });
659
749
  return v;
660
750
  }
661
751
 
@@ -962,16 +1052,23 @@ export class ZephyrStrategy implements PlatformStrategy {
962
1052
  }
963
1053
 
964
1054
  // ── Polyfills ───────────────────────────────────────────────────────────
965
- // No native polyfills for the MVP the shared runtime's string/array
966
- // polyfills are pulled in when a program uses them. cuttlefish_halt is the
967
- // one symbol the runtime header may reference; supply it as a halt loop.
1055
+ // Zephyr is a no-STL target (hasVector/hasString = false), so array/string
1056
+ // literals lower to __tc_StaticArray / const char* and string methods lower
1057
+ // to __tc_* helpers both need STL-free definitions emitted here (there is
1058
+ // no shared-runtime fallback; the pipeline sources 100% of polyfills from
1059
+ // generateNativePolyfills). Mirrors framework-arduino's AVR polyfills.
968
1060
 
969
1061
  nativePolyfills(): Set<string> {
970
1062
  // cuttlefish_halt: always (the runtime header may reference it).
1063
+ // string_methods / static_array: STL-free array + string helpers a no-STL
1064
+ // target needs (mutated/struct array literals + any string method).
971
1065
  // timer_methods: k_timer/k_work pool for setInterval/setTimeout (gated on
972
1066
  // timerCallCount at emit time in generateNativePolyfills).
973
1067
  // async_runtime: heap-free static Promise/microtask runtime (no STL needed).
974
- return new Set<string>(['cuttlefish_halt', 'wiring_compat', 'timer_methods', 'async_runtime']);
1068
+ return new Set<string>([
1069
+ 'cuttlefish_halt', 'wiring_compat', 'string_methods', 'static_array',
1070
+ 'timer_methods', 'async_runtime',
1071
+ ]);
975
1072
  }
976
1073
 
977
1074
  generateNativePolyfills(program?: ProgramIR, ctx?: PlatformContext): RuntimePolyfillIR[] {
@@ -1021,6 +1118,75 @@ export class ZephyrStrategy implements PlatformStrategy {
1021
1118
  ],
1022
1119
  dependencies: [],
1023
1120
  },
1121
+ {
1122
+ // STL-free string-method polyfills. String methods (.toUpperCase(),
1123
+ // .includes(), .substring(), …) lower at IR level to __tc_* helpers for
1124
+ // every target; this supplies their definitions. Minimal-libc friendly:
1125
+ // only <cstring> primitives (no <cctype> — case conversion is inline
1126
+ // ASCII so the polyfill is self-contained). Mirrors framework-arduino.
1127
+ kind: 'polyfill',
1128
+ id: 'string_methods',
1129
+ domain: 'embedded' as const,
1130
+ requiredIncludes: ['<cstring>'],
1131
+ forwardDeclarations: [],
1132
+ helperStructs: [],
1133
+ helperFunctions: [`
1134
+ // TypeCAD string method polyfills (Zephyr, minimal-libc).
1135
+ #ifndef CUTTLEFISH_STR_BUF_SIZE
1136
+ #define CUTTLEFISH_STR_BUF_SIZE 64
1137
+ #endif
1138
+ bool __tc_endsWith(const char* s, const char* suffix) { int sl = strlen(s), tl = strlen(suffix); return sl >= tl && strcmp(s + sl - tl, suffix) == 0; }
1139
+ const char* __tc_toUpperCase(const char* s) { static char buf[2][CUTTLEFISH_STR_BUF_SIZE]; static uint8_t slot = 0; slot ^= 1; char* b = buf[slot]; strncpy(b, s, CUTTLEFISH_STR_BUF_SIZE - 1); b[CUTTLEFISH_STR_BUF_SIZE - 1] = '\\0'; for (char* p = b; *p; p++) { if (*p >= 'a' && *p <= 'z') { *p = static_cast<char>(*p - 32); } } return b; }
1140
+ const char* __tc_toLowerCase(const char* s) { static char buf[2][CUTTLEFISH_STR_BUF_SIZE]; static uint8_t slot = 0; slot ^= 1; char* b = buf[slot]; strncpy(b, s, CUTTLEFISH_STR_BUF_SIZE - 1); b[CUTTLEFISH_STR_BUF_SIZE - 1] = '\\0'; for (char* p = b; *p; p++) { if (*p >= 'A' && *p <= 'Z') { *p = static_cast<char>(*p + 32); } } return b; }
1141
+ const char* __tc_trim(const char* s) { static char buf[2][CUTTLEFISH_STR_BUF_SIZE]; static uint8_t slot = 0; slot ^= 1; char* b = buf[slot]; while (*s == ' ' || *s == '\\t' || *s == '\\n' || *s == '\\r') s++; int len = strlen(s); while (len > 0 && (s[len-1] == ' ' || s[len-1] == '\\t' || s[len-1] == '\\n' || s[len-1] == '\\r')) len--; int cplen = len < CUTTLEFISH_STR_BUF_SIZE - 1 ? len : CUTTLEFISH_STR_BUF_SIZE - 1; strncpy(b, s, cplen); b[cplen] = '\\0'; return b; }
1142
+ const char* __tc_substring2(const char* s, int start, int end) { static char buf[2][CUTTLEFISH_STR_BUF_SIZE]; static uint8_t slot = 0; slot ^= 1; char* b = buf[slot]; int slen = strlen(s); if (start < 0) start = 0; if (end > slen) end = slen; if (end < start) end = start; int len = end - start; if (len >= CUTTLEFISH_STR_BUF_SIZE) len = CUTTLEFISH_STR_BUF_SIZE - 1; strncpy(b, s + start, len); b[len] = '\\0'; return b; }
1143
+ const char* __tc_substring1(const char* s, int start) { return __tc_substring2(s, start, strlen(s)); }
1144
+ const char* __tc_slice2(const char* s, int start, int end) { return __tc_substring2(s, start, end); }
1145
+ const char* __tc_slice1(const char* s, int start) { return __tc_substring2(s, start, strlen(s)); }
1146
+ const char* __tc_replace(const char* s, const char* old, const char* repl) { static char buf[2][CUTTLEFISH_STR_BUF_SIZE]; static uint8_t slot = 0; slot ^= 1; char* b = buf[slot]; const char* pos = strstr(s, old); if (!pos) { strncpy(b, s, CUTTLEFISH_STR_BUF_SIZE - 1); b[CUTTLEFISH_STR_BUF_SIZE - 1] = '\\0'; return b; } int beforeLen = static_cast<int>(pos - s); int oldLen = static_cast<int>(strlen(old)); int replLen = static_cast<int>(strlen(repl)); if (beforeLen + replLen + static_cast<int>(strlen(pos + oldLen)) >= CUTTLEFISH_STR_BUF_SIZE) { strncpy(b, s, CUTTLEFISH_STR_BUF_SIZE - 1); b[CUTTLEFISH_STR_BUF_SIZE - 1] = '\\0'; return b; } memcpy(b, s, beforeLen); memcpy(b + beforeLen, repl, replLen); strcpy(b + beforeLen + replLen, pos + oldLen); return b; }
1147
+ const char* __tc_charAt(const char* s, int idx) { static char buf[2][2]; static uint8_t slot = 0; slot ^= 1; buf[slot][0] = s[idx]; buf[slot][1] = '\\0'; return buf[slot]; }
1148
+ int __tc_charCodeAt(const char* s, int idx) { return static_cast<int>(static_cast<unsigned char>(s[idx])); }
1149
+ int __tc_indexOf(const char* s, const char* needle) { const char* p = strstr(s, needle); return p ? static_cast<int>(p - s) : -1; }
1150
+ `],
1151
+ shimMacros: [],
1152
+ dependencies: [],
1153
+ },
1154
+ {
1155
+ // STL-free fixed-size array wrapper. Mutated/struct-element array
1156
+ // literals and array methods (.push/.pop/.map/.filter) lower to
1157
+ // __tc_StaticArray<T,N>; this supplies the template. Idempotent guard
1158
+ // so a redefinition is a no-op. Mirrors framework-arduino.
1159
+ kind: 'polyfill',
1160
+ id: 'static_array',
1161
+ domain: 'embedded' as const,
1162
+ requiredIncludes: [],
1163
+ forwardDeclarations: [],
1164
+ helperStructs: [],
1165
+ helperFunctions: [`
1166
+ #ifndef __TC_STATIC_ARRAY_DEFINED
1167
+ #define __TC_STATIC_ARRAY_DEFINED
1168
+ template<typename T, int N>
1169
+ struct __tc_StaticArray {
1170
+ T data[N];
1171
+ int _size;
1172
+ __tc_StaticArray() : _size(0) {}
1173
+ int length() const { return _size; }
1174
+ int size() const { return _size; }
1175
+ void push(T val) { if (_size < N) data[_size++] = val; }
1176
+ T pop() { return (_size > 0) ? data[--_size] : T(); }
1177
+ int indexOf(T val) const { for (int i = 0; i < _size; i++) if (data[i] == val) return i; return -1; }
1178
+ T& operator[](int i) { return data[i]; }
1179
+ const T& operator[](int i) const { return data[i]; }
1180
+ T* begin() { return &data[0]; }
1181
+ T* end() { return &data[_size]; }
1182
+ const T* begin() const { return &data[0]; }
1183
+ const T* end() const { return &data[_size]; }
1184
+ };
1185
+ #endif
1186
+ `],
1187
+ shimMacros: [],
1188
+ dependencies: [],
1189
+ },
1024
1190
  ];
1025
1191
 
1026
1192
  // Worker-offload runtime (Phase 1). Emitted only when the program uses