@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.
- package/dist/chips/esp32.js +12 -0
- package/dist/chips/types.d.ts +30 -0
- package/dist/chips/xiao-ble.js +6 -0
- package/dist/display/gfx.d.ts +12 -3
- package/dist/display/gfx.js +130 -17
- package/dist/display/profiles.js +13 -0
- package/dist/display/ui-adapter.js +6 -0
- package/dist/doctor.d.ts +3 -3
- package/dist/doctor.js +56 -29
- package/dist/dt-config/kconfig.d.ts +3 -0
- package/dist/dt-config/kconfig.js +18 -0
- package/dist/dt-config/overlay.js +5 -0
- package/dist/framework.manifest.js +37 -14
- package/dist/index.d.ts +1 -0
- package/dist/index.js +5 -0
- package/dist/licenses.d.ts +59 -0
- package/dist/licenses.js +347 -0
- package/dist/lowering/dac.d.ts +15 -0
- package/dist/lowering/dac.js +69 -0
- package/dist/lowering/fs.d.ts +16 -0
- package/dist/lowering/fs.js +121 -0
- package/dist/lowering/hwtimer.d.ts +15 -0
- package/dist/lowering/hwtimer.js +84 -0
- package/dist/lowering/index.d.ts +4 -1
- package/dist/lowering/index.js +12 -3
- package/dist/strategy.js +186 -14
- package/dist/toolchain/compat.js +10 -1
- package/dist/toolchain/env-check.d.ts +93 -0
- package/dist/toolchain/env-check.js +190 -0
- package/dist/toolchain/scaffold.js +3 -0
- package/dist/toolchain/west-discover.d.ts +11 -3
- package/dist/toolchain/west-discover.js +80 -6
- package/dist/toolchain/west-spawn.js +15 -0
- package/package.json +4 -4
- package/src/chips/esp32.ts +12 -0
- package/src/chips/types.ts +29 -0
- package/src/chips/xiao-ble.ts +6 -0
- package/src/display/gfx.ts +135 -19
- package/src/display/profiles.ts +13 -0
- package/src/display/ui-adapter.ts +5 -0
- package/src/doctor.ts +77 -56
- package/src/dt-config/kconfig.ts +19 -0
- package/src/dt-config/overlay.ts +5 -0
- package/src/framework.manifest.ts +38 -14
- package/src/index.ts +6 -0
- package/src/licenses.ts +425 -0
- package/src/lowering/dac.ts +82 -0
- package/src/lowering/fs.ts +127 -0
- package/src/lowering/hwtimer.ts +101 -0
- package/src/lowering/index.ts +9 -2
- package/src/strategy.ts +180 -14
- package/src/toolchain/compat.ts +154 -145
- package/src/toolchain/env-check.ts +285 -0
- package/src/toolchain/scaffold.ts +3 -0
- package/src/toolchain/west-discover.ts +88 -8
- 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
|
+
}
|
package/src/lowering/index.ts
CHANGED
|
@@ -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/
|
|
7
|
-
//
|
|
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 (
|
|
307
|
-
//
|
|
308
|
-
// display
|
|
309
|
-
//
|
|
310
|
-
//
|
|
311
|
-
//
|
|
312
|
-
//
|
|
313
|
-
//
|
|
314
|
-
//
|
|
315
|
-
|
|
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
|
-
//
|
|
966
|
-
//
|
|
967
|
-
//
|
|
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>([
|
|
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
|