@typecad/framework-zephyr 1.0.0-alpha.15 → 1.0.0-alpha.18
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/LICENSE +202 -21
- package/README.md +49 -87
- package/dist/as-built.d.ts +2 -2
- package/dist/as-built.js +2 -2
- package/dist/boardgen.d.ts +1 -9
- package/dist/boardgen.js +228 -45
- package/dist/chips/types.d.ts +1 -1
- package/dist/debug-codegen.js +1 -1
- package/dist/display/touch-adapter.js +1 -1
- package/dist/display/ui-adapter.js +549 -549
- package/dist/doctor.js +4 -4
- package/dist/dt-config/custom-board.js +2 -2
- package/dist/dt-config/kconfig.js +18 -12
- package/dist/dt-config/overlay.d.ts +2 -2
- package/dist/dt-config/overlay.js +2 -2
- package/dist/framework.manifest.d.ts +3 -3
- package/dist/framework.manifest.js +10 -7
- package/dist/index.js +5 -5
- package/dist/licenses.d.ts +2 -2
- package/dist/licenses.js +8 -8
- package/dist/lowering/fs.js +1 -1
- package/dist/lowering/gpio.js +0 -32
- package/dist/lowering/http.js +264 -32
- package/dist/lowering/i2c.js +0 -8
- package/dist/lowering/interrupts.js +6 -0
- package/dist/lowering/mqtt.js +110 -8
- package/dist/lowering/usb.js +11 -11
- package/dist/lowering/wdt.js +2 -29
- package/dist/sdk/board-catalog-sync.d.ts +1 -3
- package/dist/sdk/board-catalog-sync.js +4 -10
- package/dist/strategy.d.ts +22 -44
- package/dist/strategy.js +152 -155
- package/dist/tmp-probe.d.ts +2 -0
- package/dist/tmp-probe.js +9 -0
- package/dist/toolchain/debug-config.d.ts +50 -90
- package/dist/toolchain/debug-config.js +241 -502
- package/dist/toolchain/env-check.d.ts +1 -3
- package/dist/toolchain/env-check.js +2 -7
- package/dist/toolchain/index.d.ts +65 -2
- package/dist/toolchain/index.js +432 -55
- package/dist/toolchain/runners.d.ts +16 -0
- package/dist/toolchain/runners.js +75 -0
- package/dist/toolchain/scaffold.d.ts +1 -1
- package/dist/toolchain/scaffold.js +12 -12
- package/dist/toolchain/west-discover.d.ts +6 -0
- package/dist/toolchain/west-discover.js +36 -13
- package/dist/toolchain/west-spawn.js +8 -2
- package/installer/README.md +328 -328
- package/installer/install.sh +2 -2
- package/installer/templates/project/.typecad/activate-zephyr.ps1 +1 -1
- package/installer/templates/project/.typecad/activate-zephyr.sh +1 -1
- package/installer/templates/project/.vscode/settings.json +1 -1
- package/installer/templates/project/README.md +2 -2
- package/package.json +5 -5
- package/src/as-built.ts +206 -206
- package/src/boardgen.ts +214 -48
- package/src/chips/types.ts +567 -567
- package/src/display/touch-adapter.ts +204 -204
- package/src/display/ui-adapter.ts +781 -781
- package/src/doctor.ts +4 -4
- package/src/dt-config/custom-board.ts +2 -2
- package/src/dt-config/kconfig.ts +18 -12
- package/src/dt-config/overlay.ts +1058 -1058
- package/src/framework.manifest.ts +538 -535
- package/src/index.ts +5 -5
- package/src/licenses.ts +425 -425
- package/src/lowering/fs.ts +135 -135
- package/src/lowering/gpio.ts +0 -33
- package/src/lowering/http.ts +264 -32
- package/src/lowering/i2c.ts +0 -9
- package/src/lowering/interrupts.ts +6 -0
- package/src/lowering/mqtt.ts +109 -8
- package/src/lowering/usb.ts +221 -221
- package/src/lowering/wdt.ts +2 -25
- package/src/sdk/board-catalog-sync.ts +4 -25
- package/src/strategy.ts +2265 -2309
- package/src/toolchain/debug-config.ts +264 -514
- package/src/toolchain/env-check.ts +279 -285
- package/src/toolchain/index.ts +1703 -1286
- package/src/toolchain/runners.ts +80 -0
- package/src/toolchain/scaffold.ts +296 -296
- package/src/toolchain/west-discover.ts +35 -13
- package/src/toolchain/west-spawn.ts +174 -168
- package/dist/async/timer-polyfill.d.ts +0 -10
- package/dist/async/timer-polyfill.js +0 -95
- package/dist/chips/board-overrides.d.ts +0 -7
- package/dist/chips/board-overrides.js +0 -11
- package/dist/chips/esp32.d.ts +0 -2
- package/dist/chips/esp32.js +0 -71
- package/dist/chips/esp32s3.d.ts +0 -2
- package/dist/chips/esp32s3.js +0 -103
- package/dist/chips/soc/.d.ts +0 -2
- package/dist/chips/soc/.js +0 -129
- package/dist/chips/soc/esp32.d.ts +0 -2
- package/dist/chips/soc/esp32.js +0 -120
- package/dist/chips/soc/esp32c3.d.ts +0 -2
- package/dist/chips/soc/esp32c3.js +0 -90
- package/dist/chips/soc/esp32c6.d.ts +0 -2
- package/dist/chips/soc/esp32c6.js +0 -109
- package/dist/chips/soc/esp32s3.d.ts +0 -2
- package/dist/chips/soc/esp32s3.js +0 -189
- package/dist/chips/soc/index.d.ts +0 -2
- package/dist/chips/soc/index.js +0 -23
- package/dist/chips/soc/nrf52840.d.ts +0 -2
- package/dist/chips/soc/nrf52840.js +0 -130
- package/dist/chips/soc/rp2040.d.ts +0 -2
- package/dist/chips/soc/rp2040.js +0 -141
- package/dist/chips/soc/rp2350a.d.ts +0 -2
- package/dist/chips/soc/rp2350a.js +0 -145
- package/dist/chips/soc/samd21g18a.d.ts +0 -2
- package/dist/chips/soc/samd21g18a.js +0 -143
- package/dist/chips/soc/stm32f411xe.d.ts +0 -2
- package/dist/chips/soc/stm32f411xe.js +0 -251
- package/dist/chips/xiao-ble.d.ts +0 -2
- package/dist/chips/xiao-ble.js +0 -100
- package/dist/lowering/power.d.ts +0 -9
- package/dist/lowering/power.js +0 -60
- package/dist/lowering/pulse.d.ts +0 -7
- package/dist/lowering/pulse.js +0 -51
- package/dist/lowering/tone.d.ts +0 -10
- package/dist/lowering/tone.js +0 -63
- package/dist/lowering/worker-backing.d.ts +0 -14
- package/dist/lowering/worker-backing.js +0 -79
- package/dist/lowering/worker.d.ts +0 -6
- package/dist/lowering/worker.js +0 -14
- package/dist/sdk/board-data.generated.d.ts +0 -2
- package/dist/sdk/board-data.generated.js +0 -4
- package/dist/sdk/catalog-walker.d.ts +0 -90
- package/dist/sdk/catalog-walker.js +0 -682
- package/dist/sdk/dts-reader.d.ts +0 -83
- package/dist/sdk/dts-reader.js +0 -596
- package/src/debug-codegen.ts +0 -207
package/src/strategy.ts
CHANGED
|
@@ -1,2309 +1,2265 @@
|
|
|
1
|
-
// ---------------------------------------------------------------------------
|
|
2
|
-
// ZephyrStrategy — Zephyr RTOS target
|
|
3
|
-
//
|
|
4
|
-
// Outputs C++ built with `west` (the Zephyr build tool). Zephyr is a standard
|
|
5
|
-
// C main()-based RTOS, so the program's entrypoint IS main(): top-level
|
|
6
|
-
// statements lower straight into it. Worker programs keep the scaffolded
|
|
7
|
-
// top-level `while (true)`; event-driven programs (UI / async / native
|
|
8
|
-
// timers) end with a scheduler loop in main() that ticks the UI runtime and
|
|
9
|
-
// pumps microtasks each frame, yielding to the kernel with k_msleep(1).
|
|
10
|
-
//
|
|
11
|
-
// GPIO is lowered through devicetree specs (gpio_pin_*_dt) so an active-low
|
|
12
|
-
// LED's polarity is honored by the DT flags, not by the generated code. See
|
|
13
|
-
// src/lowering/gpio.ts.
|
|
14
|
-
//
|
|
15
|
-
// EMIT BOUNDARY: This file is a canonical entry point of the framework strategy
|
|
16
|
-
// surface (B) — its main()/shim bytes land in user firmware. The emitted bytes
|
|
17
|
-
// are covered by the TypeCAD Runtime Exception (see RUNTIME_EXCEPTION.md at the
|
|
18
|
-
// repository root) and are not subject to the license of this tool source.
|
|
19
|
-
// ---------------------------------------------------------------------------
|
|
20
|
-
|
|
21
|
-
import type {
|
|
22
|
-
PlatformStrategy,
|
|
23
|
-
ExpressionIR,
|
|
24
|
-
ProgramIR,
|
|
25
|
-
StatementIR,
|
|
26
|
-
Diagnostic,
|
|
27
|
-
PlatformContext,
|
|
28
|
-
BoardConstants,
|
|
29
|
-
RuntimePolyfillIR,
|
|
30
|
-
StdLibSupport,
|
|
31
|
-
AsyncRuntimeConfig,
|
|
32
|
-
GraphicsCapacity,
|
|
33
|
-
HALOpIR,
|
|
34
|
-
DisplayHALOp,
|
|
35
|
-
ResolvedDisplay,
|
|
36
|
-
DisplayAdapterCode,
|
|
37
|
-
DisplayProfile,
|
|
38
|
-
TouchProfile,
|
|
39
|
-
TouchAdapterCodegen,
|
|
40
|
-
} from '@typecad/cuttlefish/api/shared';
|
|
41
|
-
import { DEFAULT_STDLIB_SUPPORT } from '@typecad/cuttlefish/api/shared';
|
|
42
|
-
import { applyStringMethodRewrites } from '@typecad/cuttlefish/api/shared';
|
|
43
|
-
import { programUsesSafety } from '@typecad/cuttlefish/api';
|
|
44
|
-
import { entryHasUI } from '@typecad/cuttlefish/ui-hook';
|
|
45
|
-
import { generateBoard, generateBoardModuleFromContract } from './boardgen.js';
|
|
46
|
-
import {
|
|
47
|
-
syncBoardCatalog,
|
|
48
|
-
ensureFreshBoardCatalog,
|
|
49
|
-
type BoardCatalogSyncReport,
|
|
50
|
-
type BoardCatalogEnsureResult,
|
|
51
|
-
} from './sdk/board-catalog-sync.js';
|
|
52
|
-
import { setActiveChip, getActiveChip, NO_BOARD_CHIP } from './chips/index.js';
|
|
53
|
-
import { resolveChipFromBoard } from './chips/resolve.js';
|
|
54
|
-
import { emitGpioDevDispatcher } from './chips/controllers.js';
|
|
55
|
-
import type { ZephyrChipDescriptor } from './chips/types.js';
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
};
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
const
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
};
|
|
234
|
-
visit(
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
if (
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
}
|
|
370
|
-
import {
|
|
371
|
-
import {
|
|
372
|
-
import {
|
|
373
|
-
import {
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
*
|
|
442
|
-
*
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
//
|
|
457
|
-
//
|
|
458
|
-
//
|
|
459
|
-
//
|
|
460
|
-
//
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
// <zephyr/
|
|
468
|
-
//
|
|
469
|
-
//
|
|
470
|
-
//
|
|
471
|
-
//
|
|
472
|
-
|
|
473
|
-
//
|
|
474
|
-
//
|
|
475
|
-
//
|
|
476
|
-
//
|
|
477
|
-
|
|
478
|
-
const
|
|
479
|
-
const
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
//
|
|
488
|
-
//
|
|
489
|
-
//
|
|
490
|
-
//
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
if (uses('
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
if (uses('
|
|
510
|
-
if (uses('
|
|
511
|
-
|
|
512
|
-
//
|
|
513
|
-
//
|
|
514
|
-
//
|
|
515
|
-
|
|
516
|
-
//
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
//
|
|
520
|
-
|
|
521
|
-
//
|
|
522
|
-
//
|
|
523
|
-
//
|
|
524
|
-
|
|
525
|
-
//
|
|
526
|
-
//
|
|
527
|
-
//
|
|
528
|
-
if (uses('
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
//
|
|
535
|
-
//
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
'<zephyr/net/
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
//
|
|
545
|
-
//
|
|
546
|
-
//
|
|
547
|
-
//
|
|
548
|
-
if (uses('
|
|
549
|
-
'<zephyr/net/
|
|
550
|
-
'<zephyr/net/
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
//
|
|
554
|
-
//
|
|
555
|
-
//
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
//
|
|
562
|
-
//
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
for (const
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
//
|
|
699
|
-
//
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
'
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
//
|
|
715
|
-
//
|
|
716
|
-
//
|
|
717
|
-
//
|
|
718
|
-
if (uses('
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
'inline
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
'
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
'
|
|
751
|
-
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
'
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
//
|
|
772
|
-
//
|
|
773
|
-
//
|
|
774
|
-
//
|
|
775
|
-
//
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
//
|
|
783
|
-
{
|
|
784
|
-
guardBody.push(
|
|
785
|
-
'
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
'
|
|
822
|
-
'}',
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
//
|
|
828
|
-
//
|
|
829
|
-
//
|
|
830
|
-
//
|
|
831
|
-
//
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
//
|
|
915
|
-
//
|
|
916
|
-
//
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
if (uses('
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
//
|
|
933
|
-
//
|
|
934
|
-
//
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
//
|
|
951
|
-
//
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
//
|
|
993
|
-
//
|
|
994
|
-
//
|
|
995
|
-
//
|
|
996
|
-
//
|
|
997
|
-
//
|
|
998
|
-
//
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
//
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
//
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
//
|
|
1069
|
-
//
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
&& typeof op.
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
if (
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
//
|
|
1195
|
-
//
|
|
1196
|
-
//
|
|
1197
|
-
//
|
|
1198
|
-
//
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
: ''
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
//
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
//
|
|
1246
|
-
//
|
|
1247
|
-
//
|
|
1248
|
-
//
|
|
1249
|
-
//
|
|
1250
|
-
//
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
:
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
// ──
|
|
1310
|
-
//
|
|
1311
|
-
//
|
|
1312
|
-
//
|
|
1313
|
-
//
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
//
|
|
1338
|
-
//
|
|
1339
|
-
//
|
|
1340
|
-
//
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
//
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
//
|
|
1380
|
-
//
|
|
1381
|
-
//
|
|
1382
|
-
//
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
//
|
|
1407
|
-
//
|
|
1408
|
-
//
|
|
1409
|
-
//
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
//
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
return
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
return
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
return
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
return
|
|
1609
|
-
}
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
return
|
|
1617
|
-
}
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
//
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
return
|
|
1911
|
-
}
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
//
|
|
1944
|
-
//
|
|
1945
|
-
//
|
|
1946
|
-
//
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
'
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
int
|
|
2061
|
-
int
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
}
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
return this.
|
|
2178
|
-
}
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
//
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
//
|
|
2210
|
-
//
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
//
|
|
2232
|
-
//
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
return
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
debugMode(target?: string): 'gdb' | 'printf' {
|
|
2268
|
-
// `target` is the Zephyr board id (optionally with a /qualifier suffix,
|
|
2269
|
-
// e.g. 'esp32s3_devkitc/esp32s3/procpu'). Match on the bare board id.
|
|
2270
|
-
const boardId = (target ?? '').split('/')[0];
|
|
2271
|
-
if (boardId === 'esp32s3_devkitc' || boardId.startsWith('esp32s3')) {
|
|
2272
|
-
return 'gdb';
|
|
2273
|
-
}
|
|
2274
|
-
// The STM32 Black Pill ships a verified ST-Link probe method in its board
|
|
2275
|
-
// package (openocd runner over SWD, with the reset_config quirk for the
|
|
2276
|
-
// unwired SRST line), so F5 attaches natively out of the box.
|
|
2277
|
-
if (boardId.startsWith('blackpill_')) {
|
|
2278
|
-
return 'gdb';
|
|
2279
|
-
}
|
|
2280
|
-
// The plain ESP32 (esp32_devkitc) intentionally stays on 'printf': unlike
|
|
2281
|
-
// the S3 it has NO built-in USB-JTAG, so gdb needs an external ESP-PROG
|
|
2282
|
-
// probe + a different OpenOCD cfg/toolchain dir (deferred). Falls through.
|
|
2283
|
-
return 'printf';
|
|
2284
|
-
}
|
|
2285
|
-
|
|
2286
|
-
generateDebugInitCode(): string[] {
|
|
2287
|
-
return generateZephyrInitCode();
|
|
2288
|
-
}
|
|
2289
|
-
|
|
2290
|
-
generateDebugBreakpointCode(params: {
|
|
2291
|
-
fileName: string; lineNum: number; originalLine: string;
|
|
2292
|
-
variables: Array<{ name: string; isFunction?: boolean; cppType?: 'bool'|'int'|'long'|'float'|'string'|'unknown' }>;
|
|
2293
|
-
normalizedCondition?: string;
|
|
2294
|
-
breakpointId?: number;
|
|
2295
|
-
}): string[] {
|
|
2296
|
-
return generateZephyrBreakpointCode(
|
|
2297
|
-
params.fileName, params.lineNum, params.originalLine,
|
|
2298
|
-
params.variables, params.normalizedCondition, params.breakpointId,
|
|
2299
|
-
);
|
|
2300
|
-
}
|
|
2301
|
-
|
|
2302
|
-
generateDebugLogpointCode(params: {
|
|
2303
|
-
fileName: string; lineNum: number;
|
|
2304
|
-
parts: Array<{ type: 'text' | 'variable'; value: string }>;
|
|
2305
|
-
variables: Array<{ name: string; isFunction?: boolean }>;
|
|
2306
|
-
}): string[] {
|
|
2307
|
-
return generateZephyrLogpointCode(params.fileName, params.lineNum, params.parts, params.variables);
|
|
2308
|
-
}
|
|
2309
|
-
}
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// ZephyrStrategy — Zephyr RTOS target
|
|
3
|
+
//
|
|
4
|
+
// Outputs C++ built with `west` (the Zephyr build tool). Zephyr is a standard
|
|
5
|
+
// C main()-based RTOS, so the program's entrypoint IS main(): top-level
|
|
6
|
+
// statements lower straight into it. Worker programs keep the scaffolded
|
|
7
|
+
// top-level `while (true)`; event-driven programs (UI / async / native
|
|
8
|
+
// timers) end with a scheduler loop in main() that ticks the UI runtime and
|
|
9
|
+
// pumps microtasks each frame, yielding to the kernel with k_msleep(1).
|
|
10
|
+
//
|
|
11
|
+
// GPIO is lowered through devicetree specs (gpio_pin_*_dt) so an active-low
|
|
12
|
+
// LED's polarity is honored by the DT flags, not by the generated code. See
|
|
13
|
+
// src/lowering/gpio.ts.
|
|
14
|
+
//
|
|
15
|
+
// EMIT BOUNDARY: This file is a canonical entry point of the framework strategy
|
|
16
|
+
// surface (B) — its main()/shim bytes land in user firmware. The emitted bytes
|
|
17
|
+
// are covered by the TypeCAD Runtime Exception (see RUNTIME_EXCEPTION.md at the
|
|
18
|
+
// repository root) and are not subject to the license of this tool source.
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
|
|
21
|
+
import type {
|
|
22
|
+
PlatformStrategy,
|
|
23
|
+
ExpressionIR,
|
|
24
|
+
ProgramIR,
|
|
25
|
+
StatementIR,
|
|
26
|
+
Diagnostic,
|
|
27
|
+
PlatformContext,
|
|
28
|
+
BoardConstants,
|
|
29
|
+
RuntimePolyfillIR,
|
|
30
|
+
StdLibSupport,
|
|
31
|
+
AsyncRuntimeConfig,
|
|
32
|
+
GraphicsCapacity,
|
|
33
|
+
HALOpIR,
|
|
34
|
+
DisplayHALOp,
|
|
35
|
+
ResolvedDisplay,
|
|
36
|
+
DisplayAdapterCode,
|
|
37
|
+
DisplayProfile,
|
|
38
|
+
TouchProfile,
|
|
39
|
+
TouchAdapterCodegen,
|
|
40
|
+
} from '@typecad/cuttlefish/api/shared';
|
|
41
|
+
import { DEFAULT_STDLIB_SUPPORT } from '@typecad/cuttlefish/api/shared';
|
|
42
|
+
import { applyStringMethodRewrites } from '@typecad/cuttlefish/api/shared';
|
|
43
|
+
import { programUsesSafety } from '@typecad/cuttlefish/api';
|
|
44
|
+
import { entryHasUI } from '@typecad/cuttlefish/ui-hook';
|
|
45
|
+
import { generateBoard, generateBoardModuleFromContract } from './boardgen.js';
|
|
46
|
+
import {
|
|
47
|
+
syncBoardCatalog,
|
|
48
|
+
ensureFreshBoardCatalog,
|
|
49
|
+
type BoardCatalogSyncReport,
|
|
50
|
+
type BoardCatalogEnsureResult,
|
|
51
|
+
} from './sdk/board-catalog-sync.js';
|
|
52
|
+
import { setActiveChip, getActiveChip, NO_BOARD_CHIP } from './chips/index.js';
|
|
53
|
+
import { resolveChipFromBoard } from './chips/resolve.js';
|
|
54
|
+
import { emitGpioDevDispatcher } from './chips/controllers.js';
|
|
55
|
+
import type { ZephyrChipDescriptor } from './chips/types.js';
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Visit the program's transpile-resolved HAL ops with the same `visit` the IR
|
|
59
|
+
* walk uses. A HAL call inlined into another HAL call's template literal
|
|
60
|
+
* (`USB0.writeLine(\`n: ${gps.available()}\`)`) is lowered to C++ text while
|
|
61
|
+
* the IR is built — it never becomes a hal-op/hal-expr IR node, so the walk
|
|
62
|
+
* alone cannot see it. Such ops ride on ProgramIR.resolvedHalOps; wrapped as
|
|
63
|
+
* `{ operation: op }` they match the hal-op node shape every collector below
|
|
64
|
+
* tests for. Without this fold, shims keyed on these scans (UART RX rings,
|
|
65
|
+
* sensor device handles, per-controller bus state, ADC channels/overrides,
|
|
66
|
+
* dt specs) go undeclared while the baked text still references them — the
|
|
67
|
+
* `'__tc_dt_led0' was not declared` failure class.
|
|
68
|
+
*/
|
|
69
|
+
function visitResolvedHalOps(
|
|
70
|
+
program: ProgramIR | undefined,
|
|
71
|
+
visit: (node: unknown) => void,
|
|
72
|
+
): void {
|
|
73
|
+
for (const op of program?.resolvedHalOps ?? []) visit({ operation: op });
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Deep-walk the program IR and collect the HAL pin numbers the program
|
|
78
|
+
* actually touches for a peripheral family ('adc' | 'pwm') — the same walk
|
|
79
|
+
* profileDiagnostics does. Emit paths gate per-channel state on these sets
|
|
80
|
+
* so nothing unused reaches the single generated TU (-Wunused-function
|
|
81
|
+
* hygiene: every emitted function/variable is referenced). `undefined`
|
|
82
|
+
* (no program — probe paths) means "no information": callers emit every
|
|
83
|
+
* descriptor channel, preserving probe behavior.
|
|
84
|
+
*/
|
|
85
|
+
function collectUsedPins(
|
|
86
|
+
program: ProgramIR | undefined,
|
|
87
|
+
kind: 'adc' | 'pwm',
|
|
88
|
+
chip?: ZephyrChipDescriptor,
|
|
89
|
+
): Set<number> | undefined {
|
|
90
|
+
if (!program) return undefined;
|
|
91
|
+
const pins = new Set<number>();
|
|
92
|
+
const visit = (node: unknown): void => {
|
|
93
|
+
if (!node || typeof node !== 'object') return;
|
|
94
|
+
const n = node as Record<string, unknown>;
|
|
95
|
+
const op = n.operation;
|
|
96
|
+
if (op && typeof op === 'object') {
|
|
97
|
+
const o = op as Record<string, unknown>;
|
|
98
|
+
const name = o.operation;
|
|
99
|
+
const pin = o.pin;
|
|
100
|
+
if (typeof name === 'string' && typeof pin === 'number') {
|
|
101
|
+
if (kind === 'adc' && (name === 'adc.read' || name === 'adc.read_voltage' || name === 'adc.read_raw' || name === 'adc.read_mv')) pins.add(pin);
|
|
102
|
+
if (kind === 'pwm' && name.startsWith('pwm.')) pins.add(pin);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
for (const v of Object.values(n)) {
|
|
106
|
+
if (Array.isArray(v)) { for (const item of v) visit(item); }
|
|
107
|
+
else if (v && typeof v === 'object') visit(v);
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
visit(program);
|
|
111
|
+
visitResolvedHalOps(program, visit);
|
|
112
|
+
return pins;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* The inline-override escape hatch: ADC ops carrying construction-time
|
|
117
|
+
* device overrides (hal/adc-pin.ts opts) name DT device labels the manifest
|
|
118
|
+
* may not know — the shim's init block must declare their device handles
|
|
119
|
+
* (the reads reference them) and fire even when the chip has no adc facts
|
|
120
|
+
* at all.
|
|
121
|
+
*/
|
|
122
|
+
function collectAdcOverrideDevices(program: ProgramIR | undefined): Set<string> {
|
|
123
|
+
const devices = new Set<string>();
|
|
124
|
+
if (!program) return devices;
|
|
125
|
+
const visit = (node: unknown): void => {
|
|
126
|
+
if (!node || typeof node !== 'object') return;
|
|
127
|
+
const n = node as Record<string, unknown>;
|
|
128
|
+
const op = n.operation;
|
|
129
|
+
if (op && typeof op === 'object') {
|
|
130
|
+
const o = op as Record<string, unknown>;
|
|
131
|
+
if (typeof o.operation === 'string' && o.operation.startsWith('adc.')
|
|
132
|
+
&& typeof o.deviceOverride === 'string' && o.deviceOverride !== '') {
|
|
133
|
+
devices.add(o.deviceOverride);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
for (const v of Object.values(n)) {
|
|
137
|
+
if (Array.isArray(v)) { for (const item of v) visit(item); }
|
|
138
|
+
else if (v && typeof v === 'object') visit(v);
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
visit(program);
|
|
142
|
+
visitResolvedHalOps(program, visit);
|
|
143
|
+
return devices;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* PWM construction-time overrides (the escape hatch): controller+channel
|
|
148
|
+
* vouched for by the user on pins the manifest may not map. The shim emits
|
|
149
|
+
* their alias vars; the overlay regen's marker merge creates the DT nodes.
|
|
150
|
+
*/
|
|
151
|
+
function collectPwmOverrideSpecs(program: ProgramIR | undefined): { pin: number; controller: string; channel: number }[] {
|
|
152
|
+
const specs: { pin: number; controller: string; channel: number }[] = [];
|
|
153
|
+
if (!program) return specs;
|
|
154
|
+
const seen = new Set<number>();
|
|
155
|
+
const visit = (node: unknown): void => {
|
|
156
|
+
if (!node || typeof node !== 'object') return;
|
|
157
|
+
const n = node as Record<string, unknown>;
|
|
158
|
+
const op = n.operation;
|
|
159
|
+
if (op && typeof op === 'object') {
|
|
160
|
+
const o = op as Record<string, unknown>;
|
|
161
|
+
const ctrl = typeof o.controllerOverride === 'string' && o.controllerOverride !== '' ? o.controllerOverride : undefined;
|
|
162
|
+
const ch = typeof o.channelOverride === 'number' && o.channelOverride >= 0 ? o.channelOverride : undefined;
|
|
163
|
+
if (typeof o.operation === 'string' && o.operation.startsWith('pwm.')
|
|
164
|
+
&& typeof o.pin === 'number' && (ctrl || ch !== undefined) && !seen.has(o.pin)) {
|
|
165
|
+
seen.add(o.pin);
|
|
166
|
+
specs.push({ pin: o.pin, controller: ctrl ?? 'pwm0', channel: ch ?? 0 });
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
for (const v of Object.values(n)) {
|
|
170
|
+
if (Array.isArray(v)) { for (const item of v) visit(item); }
|
|
171
|
+
else if (v && typeof v === 'object') visit(v);
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
visit(program);
|
|
175
|
+
visitResolvedHalOps(program, visit);
|
|
176
|
+
return specs;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Which I2C/SPI/UART controller indexes the program actually drives (parsed
|
|
181
|
+
* from the ops' bus/port id, same as the lowerings' parseControllerIndex).
|
|
182
|
+
* `undefined` (no program — probe paths) means "no information": callers
|
|
183
|
+
* emit every declared controller, preserving probe behavior.
|
|
184
|
+
*
|
|
185
|
+
* The per-controller state blocks are emitted only for these indexes — an
|
|
186
|
+
* unused declared controller's `__tc_<bus>N_dev` would trip -Werror's
|
|
187
|
+
* -Wunused-variable (Zephyr builds with warnings-as-errors), the same
|
|
188
|
+
* hygiene collectUsedPins enforces for per-channel ADC/PWM state.
|
|
189
|
+
*/
|
|
190
|
+
function collectUsedBusIndices(program: ProgramIR | undefined): { i2c: Set<number>; spi: Set<number>; uart: Set<number> } | undefined {
|
|
191
|
+
if (!program) return undefined;
|
|
192
|
+
const indices = { i2c: new Set<number>(), spi: new Set<number>(), uart: new Set<number>() };
|
|
193
|
+
const visit = (node: unknown): void => {
|
|
194
|
+
if (!node || typeof node !== 'object') return;
|
|
195
|
+
const n = node as Record<string, unknown>;
|
|
196
|
+
const op = n.operation;
|
|
197
|
+
if (op && typeof op === 'object') {
|
|
198
|
+
const o = op as Record<string, unknown>;
|
|
199
|
+
const name = o.operation;
|
|
200
|
+
if (typeof name === 'string') {
|
|
201
|
+
const m = name.match(/^(i2c|spi|uart)\./);
|
|
202
|
+
if (m) {
|
|
203
|
+
const bus = m[1] as 'i2c' | 'spi' | 'uart';
|
|
204
|
+
const id = typeof o.bus === 'string' ? o.bus : typeof o.port === 'string' ? o.port : undefined;
|
|
205
|
+
// parseControllerIndex() (what the lowering uses to pick the state
|
|
206
|
+
// prefix) maps a digitless alias like 'Wire'/'SPI'/'Serial' to
|
|
207
|
+
// controller 0 — the collector must agree, or the state block for
|
|
208
|
+
// controller 0 is skipped and every __tc_<bus>0_* reference dangles.
|
|
209
|
+
const idx = id?.match(/(\d+)$/);
|
|
210
|
+
indices[bus].add(idx ? parseInt(idx[1], 10) : 0);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
for (const v of Object.values(n)) {
|
|
215
|
+
if (Array.isArray(v)) { for (const item of v) visit(item); }
|
|
216
|
+
else if (v && typeof v === 'object') visit(v);
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
visit(program);
|
|
220
|
+
visitResolvedHalOps(program, visit);
|
|
221
|
+
return indices;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Collect the distinct DT-bound sensors the program's ops reference, as
|
|
226
|
+
* `${part}|${bus}|${address}` keys (same shape as peripheral-usage's
|
|
227
|
+
* sensorPartsUsed). Feeds the per-sensor device-handle state block — only
|
|
228
|
+
* constructed sensors emit state, and only sensors whose ops were emitted
|
|
229
|
+
* get a DT node.
|
|
230
|
+
*/
|
|
231
|
+
export function collectSensors(program: ProgramIR | undefined): Map<string, { part: string; bus: string; port: number | string; busKind: string; spiHz: number | string; spiMode: number | string; alertPin: number | string }> | undefined {
|
|
232
|
+
if (!program) return undefined;
|
|
233
|
+
const sensors = new Map<string, { part: string; bus: string; port: number | string; busKind: string; spiHz: number | string; spiMode: number | string; alertPin: number | string }>();
|
|
234
|
+
const visit = (node: unknown): void => {
|
|
235
|
+
if (!node || typeof node !== 'object') return;
|
|
236
|
+
const n = node as Record<string, unknown>;
|
|
237
|
+
const op = n.operation;
|
|
238
|
+
if (op && typeof op === 'object') {
|
|
239
|
+
const o = op as Record<string, unknown>;
|
|
240
|
+
if (o.operation === 'sensor.fetch' || o.operation === 'sensor.get') {
|
|
241
|
+
const part = String(o.part ?? '');
|
|
242
|
+
const bus = String(o.bus ?? '');
|
|
243
|
+
const port = (o.port as number | string) ?? 0;
|
|
244
|
+
const busKind = String(o.busKind ?? 'i2c');
|
|
245
|
+
const spiHz = (o.spiHz as number | string) ?? 0;
|
|
246
|
+
const spiMode = (o.spiMode as number | string) ?? 0;
|
|
247
|
+
const alertPin = (o.alertPin as number | string) ?? -1;
|
|
248
|
+
const key = `${part}|${bus}|${port}|${busKind}`;
|
|
249
|
+
if (!sensors.has(key)) sensors.set(key, { part, bus, port, busKind, spiHz, spiMode, alertPin });
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
for (const v of Object.values(n)) {
|
|
253
|
+
if (Array.isArray(v)) { for (const item of v) visit(item); }
|
|
254
|
+
else if (v && typeof v === 'object') visit(v);
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
visit(program);
|
|
258
|
+
visitResolvedHalOps(program, visit);
|
|
259
|
+
return sensors;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Collect the distinct thin SPI targets the program's ops reference
|
|
264
|
+
* (spi.transceive / spi.dev_write), keyed `${bus}|${cs}`. Mirrors
|
|
265
|
+
* collectSensors: only constructed targets whose ops were emitted get a
|
|
266
|
+
* spi_dt_spec state block and a DT child node.
|
|
267
|
+
*/
|
|
268
|
+
export function collectSpiTargets(program: ProgramIR | undefined): Map<string, { bus: string; cs: number; hz: number; mode: number }> | undefined {
|
|
269
|
+
if (!program) return undefined;
|
|
270
|
+
const targets = new Map<string, { bus: string; cs: number; hz: number; mode: number }>();
|
|
271
|
+
const visit = (node: unknown): void => {
|
|
272
|
+
if (!node || typeof node !== 'object') return;
|
|
273
|
+
const n = node as Record<string, unknown>;
|
|
274
|
+
const op = n.operation;
|
|
275
|
+
if (op && typeof op === 'object') {
|
|
276
|
+
const o = op as Record<string, unknown>;
|
|
277
|
+
if (o.operation === 'spi.transceive' || o.operation === 'spi.dev_write' || o.operation === 'spi.reg_read') {
|
|
278
|
+
const bus = String(o.bus ?? '');
|
|
279
|
+
const cs = (o.cs as number) ?? 0;
|
|
280
|
+
const key = `${bus}|${cs}`;
|
|
281
|
+
if (!targets.has(key)) {
|
|
282
|
+
targets.set(key, { bus, cs, hz: (o.hz as number) ?? 0, mode: (o.mode as number) ?? 0 });
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
for (const v of Object.values(n)) {
|
|
287
|
+
if (Array.isArray(v)) { for (const item of v) visit(item); }
|
|
288
|
+
else if (v && typeof v === 'object') visit(v);
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
visit(program);
|
|
292
|
+
visitResolvedHalOps(program, visit);
|
|
293
|
+
return targets;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Collect the distinct UART RX rings the program reads (uart.rx_* ops),
|
|
298
|
+
* keyed by controller index. Mirrors collectSpiTargets: only ports whose RX
|
|
299
|
+
* ops were emitted get a ring + ISR (an unused ISR is -Wunused-function
|
|
300
|
+
* under Zephyr's -Werror).
|
|
301
|
+
*/
|
|
302
|
+
export function collectUartRings(program: ProgramIR | undefined): Map<number, { index: number; ring: number }> | undefined {
|
|
303
|
+
if (!program) return undefined;
|
|
304
|
+
const rings = new Map<number, { index: number; ring: number }>();
|
|
305
|
+
const visit = (node: unknown): void => {
|
|
306
|
+
if (!node || typeof node !== 'object') return;
|
|
307
|
+
const n = node as Record<string, unknown>;
|
|
308
|
+
const op = n.operation;
|
|
309
|
+
if (op && typeof op === 'object') {
|
|
310
|
+
const o = op as Record<string, unknown>;
|
|
311
|
+
if (o.operation === 'uart.rx_arm' || o.operation === 'uart.rx_available'
|
|
312
|
+
|| o.operation === 'uart.rx_peek' || o.operation === 'uart.rx_read') {
|
|
313
|
+
const port = String(o.port ?? 'UART0');
|
|
314
|
+
const m = port.match(/(UART|uart)(\d+)/) ?? port.match(/(\d+)/);
|
|
315
|
+
const index = m ? parseInt(m[m.length - 1], 10) : 0;
|
|
316
|
+
if (!rings.has(index)) {
|
|
317
|
+
rings.set(index, { index, ring: (o.ring as number) ?? 64 });
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
for (const v of Object.values(n)) {
|
|
322
|
+
if (Array.isArray(v)) { for (const item of v) visit(item); }
|
|
323
|
+
else if (v && typeof v === 'object') visit(v);
|
|
324
|
+
}
|
|
325
|
+
};
|
|
326
|
+
visit(program);
|
|
327
|
+
visitResolvedHalOps(program, visit);
|
|
328
|
+
return rings;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Collect the distinct threads the program STARTS (thread.start ops), keyed
|
|
333
|
+
* by instance. Only started threads emit state — see shimLines.
|
|
334
|
+
*/
|
|
335
|
+
export function collectThreads(program: ProgramIR | undefined): Map<number, { instance: number; stackBytes: number; priority: number }> | undefined {
|
|
336
|
+
if (!program) return undefined;
|
|
337
|
+
const threads = new Map<number, { instance: number; stackBytes: number; priority: number }>();
|
|
338
|
+
const visit = (node: unknown): void => {
|
|
339
|
+
if (!node || typeof node !== 'object') return;
|
|
340
|
+
const n = node as Record<string, unknown>;
|
|
341
|
+
const op = n.operation;
|
|
342
|
+
if (op && typeof op === 'object') {
|
|
343
|
+
const o = op as Record<string, unknown>;
|
|
344
|
+
if (o.operation === 'thread.start') {
|
|
345
|
+
const instance = (o.instance as number) ?? 0;
|
|
346
|
+
if (!threads.has(instance)) {
|
|
347
|
+
threads.set(instance, { instance, stackBytes: (o.stackBytes as number) ?? 2048, priority: (o.priority as number) ?? 5 });
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
for (const v of Object.values(n)) {
|
|
352
|
+
if (Array.isArray(v)) { for (const item of v) visit(item); }
|
|
353
|
+
else if (v && typeof v === 'object') visit(v);
|
|
354
|
+
}
|
|
355
|
+
};
|
|
356
|
+
visit(program);
|
|
357
|
+
visitResolvedHalOps(program, visit);
|
|
358
|
+
return threads;
|
|
359
|
+
}
|
|
360
|
+
import { lowerHalOp } from './lowering/index.js';
|
|
361
|
+
import { adcInitLines, adcChannelForPin } from './lowering/adc.js';
|
|
362
|
+
import { pwmInitLines } from './lowering/pwm.js';
|
|
363
|
+
import { dacInitLines } from './lowering/dac.js';
|
|
364
|
+
import { fsInitLines } from './lowering/fs.js';
|
|
365
|
+
import { hwtimerInitLines } from './lowering/hwtimer.js';
|
|
366
|
+
import { i2cInitLines } from './lowering/i2c.js';
|
|
367
|
+
import { sensorStateLines } from './lowering/sensor.js';
|
|
368
|
+
import { spiTargetStateLines } from './lowering/spi.js';
|
|
369
|
+
import { threadStateLines } from './lowering/thread.js';
|
|
370
|
+
import { uartRingStateLines } from './lowering/uart.js';
|
|
371
|
+
import { spiInitLines } from './lowering/spi.js';
|
|
372
|
+
import { uartInitLines } from './lowering/uart.js';
|
|
373
|
+
import { usbInitLines, usbdDeviceLines } from './lowering/usb.js';
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* STM32F4 boot-time DBGMCU setup: set DBG_SLEEP|DBG_STOP|DBG_STANDBY
|
|
377
|
+
* (DBGMCU_CR @ 0xE0042004, bits 0–2) so SWD stays attachable while the app
|
|
378
|
+
* sleeps. RCC_APB1ENR (0x40023840) bit 18 clocks the DBGMCU first — F4 gates
|
|
379
|
+
* register access behind it. Raw-register form (not the STM32 LL headers) so
|
|
380
|
+
* the shim stays include-light; AUTOSAR-clean via reinterpret_cast.
|
|
381
|
+
*/
|
|
382
|
+
function stm32f4DbgmcuLines(): string[] {
|
|
383
|
+
return [
|
|
384
|
+
'// CUTTLEFISH_STM32_DBGMCU_BEGIN',
|
|
385
|
+
'#include <zephyr/init.h>',
|
|
386
|
+
'static int __tc_stm32_dbgmcu_keep_swd_alive(void) {',
|
|
387
|
+
' volatile uint32_t* const rcc_apb1enr = reinterpret_cast<volatile uint32_t*>(0x40023840);',
|
|
388
|
+
' *rcc_apb1enr = *rcc_apb1enr | (1UL << 18);',
|
|
389
|
+
' volatile uint32_t* const dbgmcu_cr = reinterpret_cast<volatile uint32_t*>(0xE0042004);',
|
|
390
|
+
' *dbgmcu_cr = *dbgmcu_cr | 0x7u;',
|
|
391
|
+
' return 0;',
|
|
392
|
+
'}',
|
|
393
|
+
'SYS_INIT(__tc_stm32_dbgmcu_keep_swd_alive, PRE_KERNEL_1, 0);',
|
|
394
|
+
'// CUTTLEFISH_STM32_DBGMCU_END',
|
|
395
|
+
];
|
|
396
|
+
}
|
|
397
|
+
import { interruptInitLines, collectInterruptPins } from './lowering/interrupts.js';
|
|
398
|
+
import { wdtInitLines } from './lowering/wdt.js';
|
|
399
|
+
import { bleInitLines } from './lowering/ble.js';
|
|
400
|
+
import { wifiInitLines } from './lowering/wifi.js';
|
|
401
|
+
import { httpInitLines } from './lowering/http.js';
|
|
402
|
+
import { mqttInitLines } from './lowering/mqtt.js';
|
|
403
|
+
import { preferencesInitLines } from './lowering/preferences.js';
|
|
404
|
+
import { randomInitLines } from './lowering/random.js';
|
|
405
|
+
import { generateStaticAsyncRuntime } from '@typecad/cuttlefish/api/shared';
|
|
406
|
+
import { resolveZephyrDisplayOp, newDisplayState, type DisplayState } from './display/index.js';
|
|
407
|
+
import { buildDisplayRuntime } from './display/gfx.js';
|
|
408
|
+
import { ZEPHYR_DISPLAY_PROFILES, BUILT_IN_PROFILES } from './display/profiles.js';
|
|
409
|
+
import { zephyrDisplayAdapterGenerator } from './display/ui-adapter.js';
|
|
410
|
+
import { zephyrTouchAdapter } from './display/touch-adapter.js';
|
|
411
|
+
|
|
412
|
+
/** debugMode() memo — the boardgen facts lookup is not free and the method is
|
|
413
|
+
* called several times per build (transpile gate + artifact writers). */
|
|
414
|
+
const debugModeMemo = new Map<string, 'gdb' | 'none'>();
|
|
415
|
+
|
|
416
|
+
export class ZephyrStrategy implements PlatformStrategy {
|
|
417
|
+
readonly id = 'zephyr';
|
|
418
|
+
|
|
419
|
+
// ── Profile resolution ──────────────────────────────────────────────────
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Resolve + cache the active chip from the platform context. Called lazily
|
|
423
|
+
* by the methods that need the descriptor (shimLines, resolveHALOperation
|
|
424
|
+
* via lowerHalOp).
|
|
425
|
+
*
|
|
426
|
+
* The chip descriptor derives from the board/MCU manifest's zephyr
|
|
427
|
+
* fields (via boardConstants). There is no registry fallback — a board
|
|
428
|
+
* that never generated a manifest stays NO_BOARD_CHIP.
|
|
429
|
+
*/
|
|
430
|
+
/**
|
|
431
|
+
* Eagerly resolve + cache the chip for `program`. The emitter calls this
|
|
432
|
+
* before rendering any bodies — hal-op lowering reads the module-global
|
|
433
|
+
* chip cache, and the lazy resolve (inside shimLines) runs later than the
|
|
434
|
+
* first lowered statement.
|
|
435
|
+
*/
|
|
436
|
+
prepareChip(program: ProgramIR | undefined, ctx?: PlatformContext): void {
|
|
437
|
+
this.resolveChip(ctx, program);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Contract-board generation: a custom PCB spec (typecad contract) names
|
|
442
|
+
* its wired pads and bus families; the SoC's bus controller labels come
|
|
443
|
+
* from the installed Zephyr tree's soc dtsi. Same builder as every board.
|
|
444
|
+
*/
|
|
445
|
+
generateContractBoardModule(opts: {
|
|
446
|
+
soc: string;
|
|
447
|
+
zephyrBase: string;
|
|
448
|
+
pinNames: readonly string[];
|
|
449
|
+
padAliases?: readonly { exportName: string; padName: string }[];
|
|
450
|
+
peripherals: { i2c: boolean; spi: boolean; uart: boolean };
|
|
451
|
+
}): { boardTs: string; boardJson: string } {
|
|
452
|
+
return generateBoardModuleFromContract(opts);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
private resolveChip(ctx?: PlatformContext, program?: ProgramIR) {
|
|
456
|
+
// The chip view reconstructs from the project's generated board
|
|
457
|
+
// manifest (boardgen's zephyr.* constants) — the one path, for every
|
|
458
|
+
// board. No curated registry, no target-based fallback: a program with
|
|
459
|
+
// no board data resolves NO_BOARD_CHIP and the lowering reports
|
|
460
|
+
// unsupported per subsystem.
|
|
461
|
+
const fromBoard = resolveChipFromBoard(program?.boardConstants);
|
|
462
|
+
setActiveChip(fromBoard ?? NO_BOARD_CHIP);
|
|
463
|
+
return fromBoard ?? NO_BOARD_CHIP;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
forcedIncludes(_program?: ProgramIR, ctx?: PlatformContext): string[] {
|
|
467
|
+
// <zephyr/kernel.h> for k_msleep / k_uptime_get_32 / k_busy_wait / printk.
|
|
468
|
+
// <zephyr/drivers/gpio.h> for the gpio_pin_*_dt / gpio_dt_spec API.
|
|
469
|
+
// <cstdint> because DIRECT_CPP_TYPE_MAP passes int32_t/uint8_t through
|
|
470
|
+
// verbatim and Zephyr's minimal C++ lib provides it.
|
|
471
|
+
//
|
|
472
|
+
// Driver headers are usage-gated on ctx.analysis.usesX (same flags
|
|
473
|
+
// shimLines uses to emit the per-peripheral bus state), so an unused
|
|
474
|
+
// peripheral doesn't pull in its header. When analysis is absent (e.g. a
|
|
475
|
+
// capability query before a real build), the uses() helper defaults to
|
|
476
|
+
// true so nothing is stripped — mirrors framework-esp32's forcedIncludes.
|
|
477
|
+
const a = (ctx as any)?.analysis;
|
|
478
|
+
const uses = (f: string): boolean => (a ? !!a[f] : true);
|
|
479
|
+
const chip = this.resolveChip(ctx, _program);
|
|
480
|
+
// <zephyr/drivers/gpio.h> and <cstdint> stay unconditional: gpio.h is
|
|
481
|
+
// cross-cutting (gpio/power/interrupt/spi/pulse lowerings + the DT-spec
|
|
482
|
+
// machinery all reference its API, and no single usesX flag owns it), and
|
|
483
|
+
// the fixed-width types come via <zephyr/kernel.h> regardless — DIRECT_CPP_TYPE_MAP
|
|
484
|
+
// passes int32_t/uint8_t through verbatim.
|
|
485
|
+
const inc: string[] = ['<zephyr/kernel.h>', '<zephyr/drivers/gpio.h>', '<cstdint>'];
|
|
486
|
+
// <cstdio> backs the printf family only: __tc_print/__tc_println (emitted
|
|
487
|
+
// solely when the test-runner preprocessor injected them — tracked via
|
|
488
|
+
// usedPolyfillHelpers), raw printf/snprintf in user code (usesCstdio), and
|
|
489
|
+
// the fs/preferences/uart shims (their lowerings snprintf into buffers).
|
|
490
|
+
// A program touching none of those needs no <cstdio>.
|
|
491
|
+
const helpers = (a as { usedPolyfillHelpers?: Set<string> } | undefined)?.usedPolyfillHelpers;
|
|
492
|
+
const needsCstdio = uses('usesCstdio') || uses('usesFS') || uses('usesPreferences')
|
|
493
|
+
|| uses('usesUart') || uses('usesUsb')
|
|
494
|
+
|| !!helpers?.has('__tc_print') || !!helpers?.has('__tc_println');
|
|
495
|
+
if (needsCstdio) inc.push('<cstdio>');
|
|
496
|
+
if (uses('usesI2C')) inc.push('<zephyr/drivers/i2c.h>');
|
|
497
|
+
if (uses('usesSensor')) inc.push('<zephyr/drivers/sensor.h>');
|
|
498
|
+
if (uses('usesSPI')) inc.push('<zephyr/drivers/spi.h>');
|
|
499
|
+
if (uses('usesUart')) inc.push('<zephyr/drivers/uart.h>');
|
|
500
|
+
// USB CDC serial: the class instance is a UART device (uart.h); the
|
|
501
|
+
// device context macros + usbd_* API live in the next-stack header.
|
|
502
|
+
if (uses('usesUsb')) {
|
|
503
|
+
if (!inc.includes('<zephyr/drivers/uart.h>')) inc.push('<zephyr/drivers/uart.h>');
|
|
504
|
+
inc.push('<zephyr/usb/usbd.h>');
|
|
505
|
+
// touch-to-reset shim callback reboots via NVIC_SystemReset()
|
|
506
|
+
// (RAM-retaining Cortex-M reset — no CONFIG_REBOOT needed).
|
|
507
|
+
if (chip?.usb?.touchReset) inc.push('<cmsis_core.h>');
|
|
508
|
+
}
|
|
509
|
+
if (uses('usesADC')) inc.push('<zephyr/drivers/adc.h>');
|
|
510
|
+
if (uses('usesPWM')) inc.push('<zephyr/drivers/pwm.h>');
|
|
511
|
+
if (uses('usesDAC')) inc.push('<zephyr/drivers/dac.h>');
|
|
512
|
+
// Filesystem: littlefs on the storage partition. <cstring> backs the
|
|
513
|
+
// shim's strlen; the storage/flash_map + fs/littlefs headers carry the
|
|
514
|
+
// FIXED_PARTITION_ID macro + FS_LITTLEFS_DECLARE_DEFAULT_CONFIG the shim uses.
|
|
515
|
+
if (uses('usesFS')) inc.push('<zephyr/fs/fs.h>', '<zephyr/fs/littlefs.h>', '<zephyr/storage/flash_map.h>', '<cstring>');
|
|
516
|
+
// Hardware timers via the counter driver.
|
|
517
|
+
if (uses('usesHwtimer')) inc.push('<zephyr/drivers/counter.h>');
|
|
518
|
+
if (uses('usesWDT') || uses('usesWdt')) inc.push('<zephyr/drivers/watchdog.h>');
|
|
519
|
+
// BLE: the bt_* GATT API + the flat-string headers the shim uses. <string>
|
|
520
|
+
// is needed because a Utf8 (BleValueType.Utf8) read handler lowers to a
|
|
521
|
+
// std::string-returning function (the string literal return type), and the
|
|
522
|
+
// program-analysis usesStdString detector doesn't see types generated by
|
|
523
|
+
// the BLE lowering layer — so without forcing <string> here, any BLE server
|
|
524
|
+
// with a Utf8 characteristic fails to compile ('std::string does not name a
|
|
525
|
+
// type'). <cstdlib>/<cstring> (not <stdlib.h>/<string.h>) back the shim's
|
|
526
|
+
// strtol/strcmp/strncpy — the same AUTOSAR-compliant spelling the HTTP,
|
|
527
|
+
// MQTT, and Preferences paths below already use.
|
|
528
|
+
if (uses('usesBle')) inc.push('<cstdlib>', '<cstring>', '<string>', '<zephyr/bluetooth/bluetooth.h>', '<zephyr/bluetooth/conn.h>', '<zephyr/bluetooth/gatt.h>', '<zephyr/bluetooth/uuid.h>');
|
|
529
|
+
// Display: the analyzer's usesDisplay flag (set by display.* hal-ops) drives
|
|
530
|
+
// this include. When ctx.analysis is absent (capability query), uses()
|
|
531
|
+
// defaults to true so a real build never strips it.
|
|
532
|
+
if (uses('usesDisplay')) inc.push('<zephyr/drivers/display.h>');
|
|
533
|
+
// Random: <zephyr/random/random.h> for sys_rand_get (the entropy tap that
|
|
534
|
+
// seeds the __tc_rand_* xorshift32 PRNG). The shim block re-includes it, but
|
|
535
|
+
// force it here too so a split-TU emit still has the symbol available.
|
|
536
|
+
if (uses('usesRandom')) inc.push('<zephyr/random/random.h>');
|
|
537
|
+
if (uses('usesWifi')) inc.push(
|
|
538
|
+
'<zephyr/net/net_mgmt.h>', '<zephyr/net/wifi_mgmt.h>',
|
|
539
|
+
'<zephyr/net/net_if.h>', '<zephyr/net/net_ip.h>',
|
|
540
|
+
'<zephyr/net/conn_mgr_connectivity.h>', '<zephyr/net/conn_mgr_monitor.h>',
|
|
541
|
+
);
|
|
542
|
+
// HTTP/S client: Zephyr's http_client_req runs over a pre-connected socket,
|
|
543
|
+
// so the shim pulls in the BSD socket + POSIX DNS surfaces alongside the
|
|
544
|
+
// http client/parser headers. TLS sec tags need tls_credentials; <cstring>
|
|
545
|
+
// /<cstdlib>/<new> back the shim's memcpy/strlen/new-nothrow usage (the
|
|
546
|
+
// core shim only includes <cstdio>/<cstdint> — std::nothrow is not
|
|
547
|
+
// guaranteed to arrive transitively).
|
|
548
|
+
if (uses('usesHttp')) inc.push(
|
|
549
|
+
'<zephyr/net/socket.h>', '<zephyr/net/http/client.h>',
|
|
550
|
+
'<zephyr/net/http/parser.h>', '<zephyr/net/tls_credentials.h>',
|
|
551
|
+
'<zephyr/posix/sys/socket.h>', '<cstring>', '<cstdlib>', '<new>',
|
|
552
|
+
);
|
|
553
|
+
// MQTT client: <zephyr/net/mqtt.h> for mqtt_connect/publish/subscribe, plus
|
|
554
|
+
// <zephyr/net/socket.h> for the zsock_* poll/getaddrinfo API the shim's poll
|
|
555
|
+
// thread uses, and <zephyr/net/tls_credentials.h> for the mqtts:// path
|
|
556
|
+
// (mqtt_sec_config). <cstring> backs the shim's memcpy/strlen.
|
|
557
|
+
if (uses('usesMqtt')) inc.push(
|
|
558
|
+
'<zephyr/net/mqtt.h>', '<zephyr/net/socket.h>',
|
|
559
|
+
'<zephyr/net/tls_credentials.h>', '<cstring>',
|
|
560
|
+
);
|
|
561
|
+
// Preferences: Zephyr settings subsystem (ZMS backend) — settings_load/
|
|
562
|
+
// settings_save_one/settings_delete + the SETTINGS_STATIC_HANDLER_DEFINE
|
|
563
|
+
// macro. <cstring> backs the shim's memcpy/memmove/strncpy/strncmp (the
|
|
564
|
+
// core shim only includes <cstdio>/<cstdint>). <errno.h> for ENOENT in h_get.
|
|
565
|
+
if (uses('usesPreferences')) inc.push(
|
|
566
|
+
'<zephyr/settings/settings.h>', '<cstring>', '<errno.h>',
|
|
567
|
+
);
|
|
568
|
+
// std::string — Zephyr has no umbrella header that transitively pulls in
|
|
569
|
+
// <string> (unlike framework-arduino's <Arduino.h>), so a program that
|
|
570
|
+
// lowers a std::string parameter/variable must request it explicitly. Uses
|
|
571
|
+
// <string>, not <string.h>: the latter is the C flat-string header.
|
|
572
|
+
if (uses('usesStdString')) inc.push('<string>');
|
|
573
|
+
return inc;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
symbolAliases(): Record<string, string> {
|
|
577
|
+
return {};
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
|
|
581
|
+
/**
|
|
582
|
+
* Detect async-runtime usage: a program needs the Promise/microtask runtime
|
|
583
|
+
* if it declares an async function OR references an async-runtime symbol
|
|
584
|
+
* (`__cuttlefish_async_`) — e.g. Async.sleep()/.then() called from a non-async
|
|
585
|
+
* function. Mirrors Arduino's programUsesAsyncRuntime walk (that helper is
|
|
586
|
+
* private to framework-arduino and not exported from cuttlefish, so we walk
|
|
587
|
+
* here). The token appears in `raw` expr nodes and in `hal-expr`/`hal-op`
|
|
588
|
+
* nodes whose resolved `raw` code references it.
|
|
589
|
+
*/
|
|
590
|
+
private programUsesAsyncRuntime(program?: ProgramIR): boolean {
|
|
591
|
+
if (!program) return false;
|
|
592
|
+
const TOKEN = '__cuttlefish_async_';
|
|
593
|
+
let found = false;
|
|
594
|
+
const visit = (node: any): void => {
|
|
595
|
+
if (found || !node || typeof node !== 'object') return;
|
|
596
|
+
// raw expression node
|
|
597
|
+
if (node.kind === 'raw' && typeof node.value === 'string' && node.value.includes(TOKEN)) {
|
|
598
|
+
found = true; return;
|
|
599
|
+
}
|
|
600
|
+
// hal-expr / hal-op node whose operation is a raw op carrying code
|
|
601
|
+
if (node.operation && typeof node.operation === 'object'
|
|
602
|
+
&& node.operation.operation === 'raw'
|
|
603
|
+
&& typeof node.operation.code === 'string'
|
|
604
|
+
&& node.operation.code.includes(TOKEN)) {
|
|
605
|
+
found = true; return;
|
|
606
|
+
}
|
|
607
|
+
for (const v of Object.values(node)) {
|
|
608
|
+
if (typeof v === 'string' && v.includes(TOKEN)) { found = true; return; }
|
|
609
|
+
if (Array.isArray(v)) { for (const item of v) visit(item); }
|
|
610
|
+
else if (v && typeof v === 'object') visit(v);
|
|
611
|
+
}
|
|
612
|
+
};
|
|
613
|
+
visit(program);
|
|
614
|
+
return found;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
/** Pins referenced by gpio.* hal-ops in the program IR. lowerGpio routes a
|
|
618
|
+
* pin to its devicetree spec by pin NUMBER, so the structured hal-op pins
|
|
619
|
+
* are the authoritative signal for which __tc_dt_* specs are needed —
|
|
620
|
+
* regardless of when the final call text is rendered.
|
|
621
|
+
*
|
|
622
|
+
* A gpio op interpolated into another HAL call's template literal
|
|
623
|
+
* (`USB0.writeLine(\`led: ${led.get()}\`)`) never becomes an IR node —
|
|
624
|
+
* resolveHALExprToText lowers it to C++ text while building the IR and bakes
|
|
625
|
+
* it into an __EMIT__ snprintf prelude, so the walk cannot see its pin. The
|
|
626
|
+
* spec NAME it references is still in that text; collectRawMatches finds it
|
|
627
|
+
* there, or the spec goes undeclared and the build fails on `__tc_dt_led0
|
|
628
|
+
* was not declared in this scope`. */
|
|
629
|
+
private collectGpioPinUsage(program?: ProgramIR): Set<number> {
|
|
630
|
+
const pins = new Set<number>();
|
|
631
|
+
if (!program) return pins;
|
|
632
|
+
const visit = (node: any): void => {
|
|
633
|
+
if (!node || typeof node !== 'object') return;
|
|
634
|
+
if (node.operation && typeof node.operation === 'object'
|
|
635
|
+
&& typeof node.operation.operation === 'string'
|
|
636
|
+
&& node.operation.operation.startsWith('gpio.')
|
|
637
|
+
&& typeof node.operation.pin === 'number') {
|
|
638
|
+
pins.add(node.operation.pin);
|
|
639
|
+
}
|
|
640
|
+
for (const v of Object.values(node)) {
|
|
641
|
+
if (Array.isArray(v)) { for (const item of v) visit(item); }
|
|
642
|
+
else if (v && typeof v === 'object') visit(v);
|
|
643
|
+
}
|
|
644
|
+
};
|
|
645
|
+
visit(program);
|
|
646
|
+
visitResolvedHalOps(program, visit);
|
|
647
|
+
return pins;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
/** Run `re` (global) against every raw string in the IR — raw expression
|
|
651
|
+
* values plus raw hal-op codes — returning capture group 1 of each match
|
|
652
|
+
* (the full match when the regex has no group). This is how references the
|
|
653
|
+
* text scanners must see but that never appear as IR call nodes are
|
|
654
|
+
* discovered: a rawCpp() escape hatch naming `__tc_dt_sw0` directly, and —
|
|
655
|
+
* the reason every string LEAF is scanned, not just raw nodes — the
|
|
656
|
+
* pre-lowered C++ text of a HAL call inlined into another HAL call's
|
|
657
|
+
* template literal, which the IR builder bakes into plain `string` payloads
|
|
658
|
+
* (__EMIT__ snprintf preludes, op data fields) rather than raw nodes. */
|
|
659
|
+
private collectRawMatches(program: ProgramIR | undefined, re: RegExp): Set<string> {
|
|
660
|
+
const found = new Set<string>();
|
|
661
|
+
if (!program) return found;
|
|
662
|
+
const scan = (text: string): void => {
|
|
663
|
+
for (const m of text.matchAll(re)) found.add(m[1] ?? m[0]);
|
|
664
|
+
};
|
|
665
|
+
const visit = (node: any): void => {
|
|
666
|
+
if (!node || typeof node !== 'object') return;
|
|
667
|
+
for (const v of Object.values(node)) {
|
|
668
|
+
if (typeof v === 'string') scan(v);
|
|
669
|
+
else if (Array.isArray(v)) { for (const item of v) visit(item); }
|
|
670
|
+
else if (v && typeof v === 'object') visit(v);
|
|
671
|
+
}
|
|
672
|
+
};
|
|
673
|
+
visit(program);
|
|
674
|
+
return found;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
/** Whether the wiring-compat GPIO read surface (__tc_gpio_read definition,
|
|
678
|
+
* __tc_gpio_dev dispatcher, and the wiring_compat polyfill's digitalRead /
|
|
679
|
+
* HIGH / LOW macros) must be emitted. Consumers: user digitalRead() calls
|
|
680
|
+
* (usesDigitalRead), the @typecad/safety voter (calls __tc_gpio_read
|
|
681
|
+
* directly via lowered raw text), and the UI runtime header's
|
|
682
|
+
* unconditional digitalRead() poll (entryHasUI — build-global, so every TU
|
|
683
|
+
* in a UI build carries the macros). With no analysis present (capability
|
|
684
|
+
* query), default to emitting — same convention as the uses() helper. */
|
|
685
|
+
private needsGpioReadShim(program?: ProgramIR, ctx?: PlatformContext): boolean {
|
|
686
|
+
if (program && programUsesSafety(program)) return true;
|
|
687
|
+
if (entryHasUI()) return true;
|
|
688
|
+
const a = (ctx as any)?.analysis;
|
|
689
|
+
return a ? !!a.usesDigitalRead : true;
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
shimLines(program?: ProgramIR, ctx?: PlatformContext): string[] {
|
|
693
|
+
const chip = this.resolveChip(ctx, program);
|
|
694
|
+
const a = (ctx as any)?.analysis;
|
|
695
|
+
const uses = (f: string): boolean => (a ? !!a[f] : true);
|
|
696
|
+
const helpers = (a as { usedPolyfillHelpers?: Set<string> } | undefined)?.usedPolyfillHelpers;
|
|
697
|
+
|
|
698
|
+
// --- Core shim, gated item by item on actual use ------------------------
|
|
699
|
+
// A minimal program (blink) uses none of these, and its output carries no
|
|
700
|
+
// shim block at all. Everything up to the #endif composes into one guard
|
|
701
|
+
// body; the guard itself is only stamped when the body is non-empty.
|
|
702
|
+
const guardBody: string[] = [];
|
|
703
|
+
// CUTTLEFISH_UNDEFINED: needed when the file references null/undefined
|
|
704
|
+
// literals (usesNullish), emits nullish helper CALLS (usesNullishHelper),
|
|
705
|
+
// or has async functions (the async state machine uses the macro for
|
|
706
|
+
// default waitFor* timeouts — not visible to the nullish scanners).
|
|
707
|
+
if (uses('usesNullish') || uses('usesNullishHelper') || uses('hasAsync')) {
|
|
708
|
+
guardBody.push(
|
|
709
|
+
'#ifndef CUTTLEFISH_UNDEFINED',
|
|
710
|
+
'#define CUTTLEFISH_UNDEFINED 0',
|
|
711
|
+
'#endif',
|
|
712
|
+
);
|
|
713
|
+
}
|
|
714
|
+
// Nullish helpers: only when the file actually emits cuttlefish_nullish /
|
|
715
|
+
// cuttlefish_exists CALLS (?? / ?. lowering). A file that only references
|
|
716
|
+
// null/undefined literals needs just the macro above — the same
|
|
717
|
+
// distinction the setup emitter's strip filter documents.
|
|
718
|
+
if (uses('usesNullishHelper')) {
|
|
719
|
+
guardBody.push(
|
|
720
|
+
'template<typename T> inline bool cuttlefish_is_nullish(const T& v) { return false; }',
|
|
721
|
+
'inline bool cuttlefish_is_nullish(long long v) { return v == CUTTLEFISH_UNDEFINED; }',
|
|
722
|
+
'inline bool cuttlefish_is_nullish(int v) { return v == CUTTLEFISH_UNDEFINED; }',
|
|
723
|
+
'inline bool cuttlefish_is_nullish(double v) { return v == static_cast<double>(CUTTLEFISH_UNDEFINED); }',
|
|
724
|
+
'inline bool cuttlefish_is_nullish(bool v) { return v == false; }',
|
|
725
|
+
'template<typename T> inline bool cuttlefish_is_nullish(T* v) { return v == nullptr; }',
|
|
726
|
+
'template<typename T> inline bool cuttlefish_exists(const T& v) { return !cuttlefish_is_nullish(v); }',
|
|
727
|
+
'template<typename T, typename U> inline T cuttlefish_nullish(const T& a, U b) { return !cuttlefish_is_nullish(a) ? a : (T)b; }',
|
|
728
|
+
);
|
|
729
|
+
}
|
|
730
|
+
// The runtime clock contract: __tc_now_ms() is the monotonic ms clock
|
|
731
|
+
// the shared runtimes (UI per-frame tick, async timers, scheduler)
|
|
732
|
+
// and currentTimeMillis() lower onto. Emitted whenever anything could
|
|
733
|
+
// read the clock: explicit Time calls, async, the interval/timeout
|
|
734
|
+
// scheduler, or a mounted UI. uint32_t wraps every ~49.7 days — every
|
|
735
|
+
// consumer compares by subtraction, so the wrap is harmless.
|
|
736
|
+
if (uses('usesWallClock') || uses('hasAsync') || (!a || a.timerCallCount > 0)
|
|
737
|
+
|| this.programUsesAsyncRuntime(program) || entryHasUI()) {
|
|
738
|
+
guardBody.push(
|
|
739
|
+
'inline uint32_t __tc_now_ms(void) { return k_uptime_get_32(); }',
|
|
740
|
+
);
|
|
741
|
+
}
|
|
742
|
+
// Num fluent math (Num.abs/min/max) + the free math trio.
|
|
743
|
+
// On Zephyr newlib none of these
|
|
744
|
+
// names are macros, so the underscore-escape rewrite the
|
|
745
|
+
// strategy needs is not. abs(long)/min/max deliberately overload (not
|
|
746
|
+
// shadow) newlib's abs(int) — exact-match int calls still resolve to the
|
|
747
|
+
// libc overload.
|
|
748
|
+
if (uses('usesNum')) {
|
|
749
|
+
guardBody.push(
|
|
750
|
+
'struct __tc_Num {',
|
|
751
|
+
' static long abs(long x) { return x < 0 ? -x : x; }',
|
|
752
|
+
' static long min(long a, long b) { return a < b ? a : b; }',
|
|
753
|
+
' static long max(long a, long b) { return a > b ? a : b; }',
|
|
754
|
+
// The core emitter escapes abs/min/max member calls to a trailing
|
|
755
|
+
// underscore (C-library collision) — provide the escaped spellings so
|
|
756
|
+
// Num.abs_()/Num.min_()/Num.max_() resolve too.
|
|
757
|
+
' static long abs_(long x) { return x < 0 ? -x : x; }',
|
|
758
|
+
' static long min_(long a, long b) { return a < b ? a : b; }',
|
|
759
|
+
' static long max_(long a, long b) { return a > b ? a : b; }',
|
|
760
|
+
'} Num;',
|
|
761
|
+
'inline long abs(long v) { return v < 0 ? -v : v; }',
|
|
762
|
+
'inline long min(long a, long b) { return a < b ? a : b; }',
|
|
763
|
+
'inline long max(long a, long b) { return a > b ? a : b; }',
|
|
764
|
+
// Escaped free-function spellings — the escape applies to bare call
|
|
765
|
+
// sites on some paths (abs_(-42)); cover both.
|
|
766
|
+
'inline long abs_(long v) { return v < 0 ? -v : v; }',
|
|
767
|
+
'inline long min_(long a, long b) { return a < b ? a : b; }',
|
|
768
|
+
'inline long max_(long a, long b) { return a > b ? a : b; }',
|
|
769
|
+
);
|
|
770
|
+
}
|
|
771
|
+
// Random PRNG shim: the Random namespace lowers to the __tc_rand_* PRNG.
|
|
772
|
+
// Emitted when the program actually uses the random.* ops, so a minimal
|
|
773
|
+
// program's shim stays empty. The dispatcher + PRNG helpers must precede
|
|
774
|
+
// the shims that call them (the guard block emits in push order, and
|
|
775
|
+
// C++ needs the definitions before use).
|
|
776
|
+
if (this.needsGpioReadShim(program, ctx)) {
|
|
777
|
+
guardBody.push(...emitGpioDevDispatcher(chip));
|
|
778
|
+
}
|
|
779
|
+
if (uses('usesRandom')) {
|
|
780
|
+
guardBody.push(...randomInitLines());
|
|
781
|
+
}
|
|
782
|
+
// PROGMEM note: only some UI runtime headers reference it.
|
|
783
|
+
if (entryHasUI()) {
|
|
784
|
+
guardBody.push(
|
|
785
|
+
'#ifndef PROGMEM', '#define PROGMEM', '#endif',
|
|
786
|
+
);
|
|
787
|
+
}
|
|
788
|
+
// Test-runner console helpers: the test-runner's Zephyr shim calls these
|
|
789
|
+
// for protocol output. Overloaded for string (const char*) and numeric
|
|
790
|
+
// (double) so the same call site works for markers and test values.
|
|
791
|
+
// The fs lowering bakes __tc_println into its error paths too, so the
|
|
792
|
+
// overloads are emitted unconditionally — ctx.analysis can be absent or
|
|
793
|
+
// stale on UI programs, and missing definitions would be a link error.
|
|
794
|
+
//
|
|
795
|
+
// The numeric form formats via INTEGER conversions only: libc float
|
|
796
|
+
// printf is not dependable across SDKs — the 0.17.5 toolchain swapped
|
|
797
|
+
// newlib for picolibc, whose default build silently prints NOTHING for
|
|
798
|
+
// %g (the same trap as newlib-nano's -u _printf_float), which emptied
|
|
799
|
+
// every [TC:EXPECT:...:value:] line. Integer %lld works in every libc
|
|
800
|
+
// configuration, and the host parser accepts plain fixed-point.
|
|
801
|
+
{
|
|
802
|
+
guardBody.push(
|
|
803
|
+
'inline void __tc_print(const char* s) { printf("%s", s); }',
|
|
804
|
+
[
|
|
805
|
+
'static inline char* __tc_fmt_num_buf(double v, char* out, size_t cap) {',
|
|
806
|
+
' if (v != v) { snprintf(out, cap, "nan"); return out; }',
|
|
807
|
+
' double a = v < 0 ? -v : v;',
|
|
808
|
+
' long long ip = (long long)a;',
|
|
809
|
+
' long long fr = (long long)((a - (double)ip) * 1000000.0 + 0.5);',
|
|
810
|
+
' if (fr >= 1000000LL) { ip += 1LL; fr = 0LL; }',
|
|
811
|
+
' int used = 0;',
|
|
812
|
+
' if (v < 0 && (ip != 0LL || fr != 0LL)) { out[used++] = \'-\'; out[used] = \'\\0\'; }',
|
|
813
|
+
' if (fr == 0LL) { snprintf(out + used, cap - (size_t)used, "%lld", ip); return out; }',
|
|
814
|
+
' char fbuf[8];',
|
|
815
|
+
' int len = snprintf(fbuf, sizeof(fbuf), "%06lld", fr);',
|
|
816
|
+
' while (len > 0 && fbuf[len - 1] == \'0\') { fbuf[--len] = \'\\0\'; }',
|
|
817
|
+
' snprintf(out + used, cap - (size_t)used, "%lld.%s", ip, fbuf);',
|
|
818
|
+
' return out;',
|
|
819
|
+
'}',
|
|
820
|
+
'static void __tc_fmt_num(double v) { char __b[32]; printf("%s", __tc_fmt_num_buf(v, __b, sizeof(__b))); }',
|
|
821
|
+
].join('\n'),
|
|
822
|
+
'inline void __tc_print(double v) { __tc_fmt_num(v); }',
|
|
823
|
+
'inline void __tc_println(const char* s) { printf("%s\\n", s); }',
|
|
824
|
+
'inline void __tc_println(double v) { __tc_fmt_num(v); printf("\\n"); }',
|
|
825
|
+
);
|
|
826
|
+
}
|
|
827
|
+
// Serial-port write helper: writes a scalar to a UART/CDC device a byte at
|
|
828
|
+
// a time. Overloaded on const char* (strings, snprintf buffers) and double
|
|
829
|
+
// (numbers/booleans) so a single __tc_dev_put(dev, value) call site formats
|
|
830
|
+
// any writable scalar — the same overload contract __tc_print uses. Emitted
|
|
831
|
+
// when the program writes a UART or USB port (both include uart.h).
|
|
832
|
+
if (uses('usesUart') || uses('usesUsb')) {
|
|
833
|
+
guardBody.push(
|
|
834
|
+
'static inline void __tc_dev_put(const struct device* dev, const char* s) {',
|
|
835
|
+
' for (; *s != \'\\0\'; ++s) { uart_poll_out(dev, *s); }',
|
|
836
|
+
'}',
|
|
837
|
+
'static inline void __tc_dev_put(const struct device* dev, double v) {',
|
|
838
|
+
' char __b[32];',
|
|
839
|
+
' __tc_dev_put(dev, __tc_fmt_num_buf(v, __b, sizeof(__b)));',
|
|
840
|
+
'}',
|
|
841
|
+
);
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
// Per-peripheral bus state — gated on the same ctx.analysis.usesX flags as
|
|
845
|
+
// forcedIncludes, so an unused peripheral emits no state (and its header is
|
|
846
|
+
// not included). Mirrors framework-esp32's shimLines espInit block. Within
|
|
847
|
+
// a bus, only the instances the program drives get state — an unused
|
|
848
|
+
// declared controller's __tc_<bus>N_dev trips -Wunused-variable under
|
|
849
|
+
// Zephyr's -Werror (collectUsedBusIndices; probes with no program emit all).
|
|
850
|
+
const usedBuses = collectUsedBusIndices(program);
|
|
851
|
+
if (uses('usesI2C') && chip.i2c) {
|
|
852
|
+
for (let i = 0; i < chip.i2c.controllers.length; i++) {
|
|
853
|
+
if (usedBuses && !usedBuses.i2c.has(i)) continue;
|
|
854
|
+
guardBody.push(...i2cInitLines(chip, i));
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
if (uses('usesSPI') && chip.spi) {
|
|
858
|
+
for (let i = 0; i < chip.spi.controllers.length; i++) {
|
|
859
|
+
if (usedBuses && !usedBuses.spi.has(i)) continue;
|
|
860
|
+
guardBody.push(...spiInitLines(chip, i));
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
if (uses('usesUart') && chip.uart) {
|
|
864
|
+
for (let i = 0; i < chip.uart.controllers.length; i++) {
|
|
865
|
+
if (usedBuses && !usedBuses.uart.has(i)) continue;
|
|
866
|
+
guardBody.push(...uartInitLines(chip, i));
|
|
867
|
+
}
|
|
868
|
+
// Thin UART RX rings — one interrupt-drained ring per port the program
|
|
869
|
+
// reads (uart.rx_* ops). Emitted after the device handles it references.
|
|
870
|
+
const rings = collectUartRings(program);
|
|
871
|
+
if (rings) {
|
|
872
|
+
for (const r of rings.values()) {
|
|
873
|
+
guardBody.push(...uartRingStateLines(r.index, r.ring));
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
// DT-bound sensors — one state block per constructed part (device handle
|
|
878
|
+
// + sensor_value scratch). The lowering's __tc_sensor_* references and the
|
|
879
|
+
// overlay's DT child nodes derive from the same facts (lowering/sensor.ts).
|
|
880
|
+
if (uses('usesSensor')) {
|
|
881
|
+
const sensors = collectSensors(program);
|
|
882
|
+
if (sensors) {
|
|
883
|
+
for (const s of sensors.values()) {
|
|
884
|
+
guardBody.push(...sensorStateLines(s.part, s.bus, s.port, s.busKind, s.spiHz, s.spiMode, s.alertPin));
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
// Thin SPI targets — one spi_dt_spec per constructed target, against the
|
|
889
|
+
// DT child node the overlay emits. Same shared-facts discipline (the
|
|
890
|
+
// tc-spit-cfg comment is the overlay scanner's channel).
|
|
891
|
+
if (uses('usesSPI')) {
|
|
892
|
+
const targets = collectSpiTargets(program);
|
|
893
|
+
if (targets) {
|
|
894
|
+
for (const t of targets.values()) {
|
|
895
|
+
guardBody.push(...spiTargetStateLines(t.bus, t.cs, t.hz, t.mode));
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
// Thin Threads — one stack + k_thread + trampoline per started slot.
|
|
900
|
+
// Keyed on thread.start ops only: join() without a prior start() on the
|
|
901
|
+
// same index is a user error that surfaces as the undefined slot symbol.
|
|
902
|
+
{
|
|
903
|
+
const threads = collectThreads(program);
|
|
904
|
+
if (threads) {
|
|
905
|
+
for (const t of threads.values()) {
|
|
906
|
+
guardBody.push(...threadStateLines(t.instance, t.stackBytes));
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
if (uses('usesUsb') && chip.usb) {
|
|
911
|
+
guardBody.push(...usbdDeviceLines(chip));
|
|
912
|
+
for (let i = 0; i < chip.usb.cdcInstances; i++) guardBody.push(...usbInitLines(chip, i));
|
|
913
|
+
}
|
|
914
|
+
// PWM init also fires (with alias vars for the override pins) when a
|
|
915
|
+
// program drives ONLY inline-override channels on a chip with no pwm
|
|
916
|
+
// facts — the lowered calls reference those aliases.
|
|
917
|
+
{
|
|
918
|
+
const pwmUserSpecs = collectPwmOverrideSpecs(program);
|
|
919
|
+
if ((uses('usesPWM') && chip.pwm) || pwmUserSpecs.length > 0) {
|
|
920
|
+
guardBody.push(...pwmInitLines(chip, collectUsedPins(program, 'pwm', chip), pwmUserSpecs));
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
if (uses('usesDAC') && chip.dac) guardBody.push(...dacInitLines(chip));
|
|
924
|
+
if (uses('usesHwtimer') && chip.hwtimer) guardBody.push(...hwtimerInitLines(chip));
|
|
925
|
+
if (uses('usesInterrupts')) guardBody.push(...interruptInitLines(chip, program ? collectInterruptPins(program) : undefined));
|
|
926
|
+
if ((uses('usesWDT') || uses('usesWdt')) && chip.wdt) guardBody.push(...wdtInitLines(chip));
|
|
927
|
+
if (uses('usesBle')) guardBody.push(...bleInitLines());
|
|
928
|
+
// Display runtime (rect/text renderer): the DIRECT-call display path (user
|
|
929
|
+
// code calling screen.display.fillRect etc., no @typecad/ui). Emitted only
|
|
930
|
+
// when the program uses display.* but is NOT a UI program — the UI display
|
|
931
|
+
// adapter (emitted by cuttlefish's emitUIRuntime, solely under entryHasUI())
|
|
932
|
+
// defines the same display_init symbol, so emitting both would collide.
|
|
933
|
+
// `providesDisplayAdapter()` is a static capability (always true here) and
|
|
934
|
+
// does NOT track whether the adapter is actually emitted for THIS build, so
|
|
935
|
+
// the per-program UI signal (entryHasUI) is the correct gate. Without this,
|
|
936
|
+
// a direct display.* program has no definition for display_init/
|
|
937
|
+
// display_fill_rect/draw_rect/draw_text/flush (the gfx runtime was
|
|
938
|
+
// previously dead code).
|
|
939
|
+
if (uses('usesDisplay') && !entryHasUI()) {
|
|
940
|
+
const rt = buildDisplayRuntime(this._displayState.profile);
|
|
941
|
+
guardBody.push(...rt.stateLines);
|
|
942
|
+
guardBody.push(rt.fontTable);
|
|
943
|
+
guardBody.push(rt.helpers);
|
|
944
|
+
}
|
|
945
|
+
if (uses('usesWifi')) guardBody.push(...wifiInitLines());
|
|
946
|
+
if (uses('usesHttp')) guardBody.push(...httpInitLines());
|
|
947
|
+
if (uses('usesMqtt')) guardBody.push(...mqttInitLines());
|
|
948
|
+
if (uses('usesPreferences')) guardBody.push(...preferencesInitLines());
|
|
949
|
+
if (uses('usesFS')) guardBody.push(...fsInitLines());
|
|
950
|
+
// The ADC init block also fires (with the override devices' handles)
|
|
951
|
+
// when a program uses ONLY inline-override reads on a chip with no adc
|
|
952
|
+
// facts — otherwise those reads reference undeclared device handles.
|
|
953
|
+
{
|
|
954
|
+
const adcOverrideDevices = collectAdcOverrideDevices(program);
|
|
955
|
+
if ((uses('usesADC') && chip.adc) || adcOverrideDevices.size > 0) {
|
|
956
|
+
guardBody.push(...adcInitLines(chip, collectUsedPins(program, 'adc'), adcOverrideDevices));
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
// STM32F4: keep the core debug port alive across WFI sleep. The DBGMCU
|
|
960
|
+
// gates PPB access while the core sleeps unless DBGMCU_CR DBG_SLEEP/
|
|
961
|
+
// DBG_STOP/DBG_STANDBY are set — without them openocd cannot examine or
|
|
962
|
+
// halt the running target ("Failed to read memory at 0xe000ed04", "AP
|
|
963
|
+
// write error, reset will not halt"), and with no RST pad on boards like
|
|
964
|
+
// the Black Pill the only recovery is the BOOT0 bootloader. Zephyr's
|
|
965
|
+
// CONFIG_STM32_ENABLE_DEBUG_SLEEP_STOP sets only DBG_STOP on F4 (the
|
|
966
|
+
// soc_config.c F1/L1 branch is the one that sets DBG_SLEEP), so the bits
|
|
967
|
+
// are set here at boot, unconditionally for dev boards.
|
|
968
|
+
// (soc is empty on board-resolved chips — the SoC name rides in the
|
|
969
|
+
// qualified id's variant segment, e.g. 'blackpill_f411ce/stm32f411xe'.)
|
|
970
|
+
if (chip.soc.startsWith('stm32f4') || /stm32f4\d*/.test(chip.id)) {
|
|
971
|
+
guardBody.push(...stm32f4DbgmcuLines());
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
const lines: string[] = [];
|
|
975
|
+
if (guardBody.length > 0) {
|
|
976
|
+
lines.push(
|
|
977
|
+
'// cuttlefish runtime shim. Wrapped in a single include guard so the',
|
|
978
|
+
'// block is safe to emit into multiple headers and .cpp files within',
|
|
979
|
+
'// one translation unit (a .cpp may #include several headers that each',
|
|
980
|
+
'// carry the shim). The guard ensures the definitions are seen exactly',
|
|
981
|
+
'// once per TU.',
|
|
982
|
+
'#ifndef CUTTLEFISH_SHIM_DEFINED',
|
|
983
|
+
'#define CUTTLEFISH_SHIM_DEFINED',
|
|
984
|
+
...guardBody,
|
|
985
|
+
'#endif // CUTTLEFISH_SHIM_DEFINED',
|
|
986
|
+
);
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
// Devicetree specs — one per board-defined GPIO pin, but ONLY for pins the
|
|
990
|
+
// program actually addresses (lowerGpio routes by pin number; the
|
|
991
|
+
// structured gpio.* hal-op pins plus the spec names appearing in any IR
|
|
992
|
+
// string — including the pre-lowered text of a HAL call inlined into
|
|
993
|
+
// another HAL call's template literal — are visible here) and aliases
|
|
994
|
+
// named verbatim in raw code (rawCpp escape hatches). Emitted OUTSIDE the
|
|
995
|
+
// single CUTTLEFISH_SHIM_DEFINED guard with a per-symbol guard: per-file
|
|
996
|
+
// pin sets differ, and in a multi-header TU the first header's TU-wide
|
|
997
|
+
// guard would otherwise hide the second header's specs. Without a program
|
|
998
|
+
// (capability query), emit them all.
|
|
999
|
+
const usedPins = this.collectGpioPinUsage(program);
|
|
1000
|
+
const dtTextRefs = this.collectRawMatches(program, /__tc_dt_([A-Za-z0-9_]+)/g);
|
|
1001
|
+
for (const spec of chip.gpio.dtSpecs) {
|
|
1002
|
+
if (program && !usedPins.has(spec.pin) && !dtTextRefs.has(spec.dtSpec)) continue;
|
|
1003
|
+
const guard = `__TC_DT_${spec.dtSpec.replace(/[^A-Za-z0-9_]/g, '_').toUpperCase()}_SPEC`;
|
|
1004
|
+
lines.push(
|
|
1005
|
+
`#ifndef ${guard}`,
|
|
1006
|
+
`#define ${guard}`,
|
|
1007
|
+
`static const struct gpio_dt_spec __tc_dt_${spec.dtSpec} = GPIO_DT_SPEC_GET(DT_ALIAS(${spec.dtSpec}), gpios);`,
|
|
1008
|
+
`#endif // ${guard}`,
|
|
1009
|
+
);
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
|
|
1013
|
+
// --- Zephyr entrypoint ---------------------------------------------------
|
|
1014
|
+
// No bridge here: the synthesizer emits main() itself (it keys off
|
|
1015
|
+
// entrypointFunctionName()="main" and requiresLoopFunction()=false), with
|
|
1016
|
+
// the top-level statements inlined. Per-frame driving (ui_tick, microtask
|
|
1017
|
+
// pumping, async task .run()) is appended to main() by the driver
|
|
1018
|
+
// machinery via asyncDriverFunctionName()="main" + hostEventLoop() — see
|
|
1019
|
+
// asyncLoopInjection for the scheduler loop it closes over.
|
|
1020
|
+
|
|
1021
|
+
// GPIO read shim: emitted only when something actually reads a pin at
|
|
1022
|
+
// runtime — user digitalRead() calls, the @typecad/safety voter (calls
|
|
1023
|
+
// __tc_gpio_read directly), or the UI runtime header's digitalRead() poll
|
|
1024
|
+
// (init-press-input.ts). A program that only writes/toggles GPIO needs
|
|
1025
|
+
// neither the dispatcher nor the reader.
|
|
1026
|
+
//
|
|
1027
|
+
// The signature is `int` to match wiring_compat's forward declaration —
|
|
1028
|
+
// a uint32_t definition alongside it would leave the declared int
|
|
1029
|
+
// overload undefined (int wins overload resolution for small integer
|
|
1030
|
+
// arguments).
|
|
1031
|
+
//
|
|
1032
|
+
// The pin is a RUNTIME value here (the UI pin-watch table and safety's
|
|
1033
|
+
// voter pass whatever pin they were handed), so the controller cannot be
|
|
1034
|
+
// baked in as a single DT_NODELABEL on a multi-controller SoC (ESP32-S3:
|
|
1035
|
+
// pins 0–31 → gpio0, 32–48 → gpio1). Emit a tiny __tc_gpio_dev(pin)
|
|
1036
|
+
// dispatcher that resolves the owning controller's device per pin;
|
|
1037
|
+
// single-controller SoCs collapse it to a one-liner. Each DT_NODELABEL is
|
|
1038
|
+
// still compile-time-resolved per branch, so it is always statically valid.
|
|
1039
|
+
// (The dispatcher itself now lives at the top of the guard block — it must
|
|
1040
|
+
// precede the wiring-ambient shims and the PinGroup factory there; the
|
|
1041
|
+
// __tc_gpio_read body below is what still needs emitting here.)
|
|
1042
|
+
if (this.needsGpioReadShim(program, ctx)) {
|
|
1043
|
+
lines.push(
|
|
1044
|
+
'inline int __tc_gpio_read(int pin) { return gpio_pin_get_raw(__tc_gpio_dev(static_cast<uint32_t>(pin)), __tc_gpio_pin(static_cast<uint32_t>(pin))); }',
|
|
1045
|
+
);
|
|
1046
|
+
}
|
|
1047
|
+
// __tc_gpio_write / __tc_delay_us are only referenced via @typecad/safety
|
|
1048
|
+
// lowering, so they stay gated on it.
|
|
1049
|
+
if (program && programUsesSafety(program)) {
|
|
1050
|
+
lines.push(
|
|
1051
|
+
'inline void __tc_gpio_write(uint32_t pin, uint32_t value) { gpio_pin_set_raw(__tc_gpio_dev(pin), __tc_gpio_pin(pin), value); }',
|
|
1052
|
+
'#ifndef __TC_DELAY_US_DEFINED',
|
|
1053
|
+
'#define __TC_DELAY_US_DEFINED',
|
|
1054
|
+
'inline void __tc_delay_us(uint32_t us) { k_busy_wait(us); }',
|
|
1055
|
+
'#endif',
|
|
1056
|
+
);
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
return lines;
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
profileDiagnostics(program?: ProgramIR, ctx?: PlatformContext): Diagnostic[] {
|
|
1063
|
+
if (!program) return [];
|
|
1064
|
+
const chip = this.resolveChip(ctx, program);
|
|
1065
|
+
const a = (ctx as any)?.analysis ?? {};
|
|
1066
|
+
const diags: Diagnostic[] = [];
|
|
1067
|
+
|
|
1068
|
+
// Collect the pins the program uses for output config, ADC reads, and
|
|
1069
|
+
// interrupt attaches — deep-walking the IR the same way framework-esp32
|
|
1070
|
+
// does (its profileDiagnostics walks program to find gpio.set_mode /
|
|
1071
|
+
// adc.read nodes).
|
|
1072
|
+
const outputPins = new Set<number>();
|
|
1073
|
+
const adcReadPins = new Set<number>();
|
|
1074
|
+
const adcOverriddenPins = new Set<number>();
|
|
1075
|
+
const pwmOverriddenPins = new Set<number>();
|
|
1076
|
+
const interruptPins = new Set<number>();
|
|
1077
|
+
const wdtOps = new Set<string>();
|
|
1078
|
+
const dacPins = new Set<number>();
|
|
1079
|
+
const pwmPins = new Set<number>();
|
|
1080
|
+
const busInstances = { i2c: new Set<number>(), spi: new Set<number>(), uart: new Set<number>() };
|
|
1081
|
+
const hwtimerInstances = new Set<number>();
|
|
1082
|
+
let usesWifiOps = false;
|
|
1083
|
+
let usesHttpOps = false;
|
|
1084
|
+
let usesMqttOps = false;
|
|
1085
|
+
const visit = (node: any): void => {
|
|
1086
|
+
if (node && typeof node === 'object') {
|
|
1087
|
+
if (node.operation && typeof node.operation === 'object') {
|
|
1088
|
+
const op = node.operation;
|
|
1089
|
+
if (op.operation === 'gpio.set_mode'
|
|
1090
|
+
&& typeof op.mode === 'string'
|
|
1091
|
+
&& op.mode.toLowerCase() === 'output'
|
|
1092
|
+
&& typeof op.pin === 'number') {
|
|
1093
|
+
outputPins.add(op.pin);
|
|
1094
|
+
}
|
|
1095
|
+
if ((op.operation === 'adc.read' || op.operation === 'adc.read_voltage'
|
|
1096
|
+
|| op.operation === 'adc.read_raw' || op.operation === 'adc.read_mv')
|
|
1097
|
+
&& typeof op.pin === 'number') {
|
|
1098
|
+
adcReadPins.add(op.pin);
|
|
1099
|
+
// Inline routing overrides (construction opts): the user vouches
|
|
1100
|
+
// for the pin — the unavailable-pin diagnostic does not apply.
|
|
1101
|
+
if ((typeof op.channelOverride === 'number' && op.channelOverride >= 0)
|
|
1102
|
+
|| (typeof op.deviceOverride === 'string' && op.deviceOverride !== '')) {
|
|
1103
|
+
adcOverriddenPins.add(op.pin);
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
if (op.operation === 'interrupt.attach_flags' && typeof op.pin === 'number') {
|
|
1107
|
+
interruptPins.add(op.pin);
|
|
1108
|
+
}
|
|
1109
|
+
if (typeof op.operation === 'string' && op.operation.startsWith('wdt.')) {
|
|
1110
|
+
wdtOps.add(op.operation);
|
|
1111
|
+
}
|
|
1112
|
+
if ((op.operation === 'dac.write' || op.operation === 'dac.write_value')
|
|
1113
|
+
&& typeof op.pin === 'number') {
|
|
1114
|
+
dacPins.add(op.pin);
|
|
1115
|
+
}
|
|
1116
|
+
if ((op.operation === 'pwm.set_pulse' || op.operation === 'pwm.set_duty'
|
|
1117
|
+
|| op.operation === 'pwm.set_period')
|
|
1118
|
+
&& typeof op.pin === 'number') {
|
|
1119
|
+
pwmPins.add(op.pin);
|
|
1120
|
+
if ((typeof op.controllerOverride === 'string' && op.controllerOverride !== '')
|
|
1121
|
+
|| (typeof op.channelOverride === 'number' && op.channelOverride >= 0)) {
|
|
1122
|
+
pwmOverriddenPins.add(op.pin);
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
// Bus instance usage: which I2C/SPI/UART controller indexes the
|
|
1126
|
+
// program drives (the lowering resolves index N against
|
|
1127
|
+
// chip.<bus>.controllers[N]; an undeclared index emits references
|
|
1128
|
+
// to a state block that is never declared → link error).
|
|
1129
|
+
if (typeof op.operation === 'string') {
|
|
1130
|
+
const busMatch = op.operation.match(/^(i2c|spi|uart)\./);
|
|
1131
|
+
if (busMatch) {
|
|
1132
|
+
const bus = busMatch[1] as 'i2c' | 'spi' | 'uart';
|
|
1133
|
+
const id = typeof op.bus === 'string' ? op.bus
|
|
1134
|
+
: typeof op.port === 'string' ? op.port : undefined;
|
|
1135
|
+
const m = id?.match(/(\d+)$/);
|
|
1136
|
+
if (m) busInstances[bus].add(parseInt(m[1], 10));
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
if (typeof op.operation === 'string' && op.operation.startsWith('hwtimer.')) {
|
|
1140
|
+
const inst = typeof op.instance === 'number'
|
|
1141
|
+
? op.instance
|
|
1142
|
+
: parseInt(String(op.instance), 10);
|
|
1143
|
+
if (!isNaN(inst)) hwtimerInstances.add(inst);
|
|
1144
|
+
}
|
|
1145
|
+
if (typeof op.operation === 'string' && op.operation.startsWith('wifi.')) {
|
|
1146
|
+
usesWifiOps = true;
|
|
1147
|
+
}
|
|
1148
|
+
if (typeof op.operation === 'string' && op.operation.startsWith('http.')) {
|
|
1149
|
+
usesHttpOps = true;
|
|
1150
|
+
}
|
|
1151
|
+
if (typeof op.operation === 'string' && op.operation.startsWith('mqtt.')) {
|
|
1152
|
+
usesMqttOps = true;
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
for (const k of Object.keys(node)) {
|
|
1156
|
+
const v = node[k];
|
|
1157
|
+
if (Array.isArray(v)) v.forEach(visit);
|
|
1158
|
+
else if (typeof v === 'object' && v !== null) visit(v);
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
};
|
|
1162
|
+
visit(program);
|
|
1163
|
+
|
|
1164
|
+
// ── ADC pin validity ────────────────────────────────────────────────────
|
|
1165
|
+
// The SAADC lowering resolves the adc.read argument to a channel via the
|
|
1166
|
+
// chip descriptor's adc.channels map (pin-first, then channel-number
|
|
1167
|
+
// fallback for the Arduino-compat ADC.read(n) form — see
|
|
1168
|
+
// adcChannelForPin). A number resolving to neither emits
|
|
1169
|
+
// __tc_adc-1_setup() — an undefined symbol → link error. Flag it at
|
|
1170
|
+
// compile time with a clear message instead of an opaque link failure.
|
|
1171
|
+
const adcPins = new Set((chip.adc?.channels ?? []).map((c) => c.pin));
|
|
1172
|
+
for (const pin of adcReadPins) {
|
|
1173
|
+
if (adcOverriddenPins.has(pin)) continue;
|
|
1174
|
+
if (adcChannelForPin(chip, pin) < 0) {
|
|
1175
|
+
const valid = [...adcPins].sort((x, y) => x - y).join(', ');
|
|
1176
|
+
// Cross-peripheral suggestion: the facts know what this pin IS wired
|
|
1177
|
+
// to — a PWM-capable pin misread as analog is the classic mix-up.
|
|
1178
|
+
const pwmOnPin = (chip.pwm?.specs ?? []).find((s) => s.pin === pin);
|
|
1179
|
+
const mixup = pwmOnPin
|
|
1180
|
+
? ` GPIO ${pin} carries PWM on this board (${pwmOnPin.controller} ch ${pwmOnPin.channel}) — did you mean new PWM(${pin}, …)?`
|
|
1181
|
+
: '';
|
|
1182
|
+
diags.push({
|
|
1183
|
+
severity: 'error',
|
|
1184
|
+
code: 'zephyr-adc-pin-unavailable',
|
|
1185
|
+
message: `GPIO ${pin} is not a SAADC channel on ${chip.id} and cannot be read with adc.read.`,
|
|
1186
|
+
hint: valid
|
|
1187
|
+
? `Use an analog-capable pin. On ${chip.id} (SAADC): ${valid}.${mixup}`
|
|
1188
|
+
: `This target has no ADC channels mapped in its chip descriptor.${mixup}`,
|
|
1189
|
+
source: program.fileName,
|
|
1190
|
+
});
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
// ── Interrupt pin validity ──────────────────────────────────────────────
|
|
1195
|
+
// GPIO.onInterrupt works on every REAL GPIO: pins listed in the chip
|
|
1196
|
+
// descriptor's gpio.interruptPins wire through the DT-spec chain, any
|
|
1197
|
+
// other in-range pin through the raw-controller chain. What cannot work
|
|
1198
|
+
// is a pin number no declared controller range covers (e.g. 99 on a
|
|
1199
|
+
// 34-pin SoC) — that lowering is a comment (silent no-op). Flag only
|
|
1200
|
+
// that case.
|
|
1201
|
+
const intPins = new Set((chip.gpio.interruptPins ?? []).map((p) => p.pin));
|
|
1202
|
+
for (const pin of interruptPins) {
|
|
1203
|
+
if (intPins.has(pin)) continue;
|
|
1204
|
+
const inRange = !chip.gpioControllers || chip.gpioControllers.length === 0
|
|
1205
|
+
|| chip.gpioControllers.some((r) => pin >= r.minPin && pin <= r.maxPin);
|
|
1206
|
+
if (!inRange) {
|
|
1207
|
+
diags.push({
|
|
1208
|
+
severity: 'error',
|
|
1209
|
+
code: 'zephyr-interrupt-pin-unavailable',
|
|
1210
|
+
message: `GPIO ${pin} does not exist on ${chip.id}; onInterrupt is a no-op.`,
|
|
1211
|
+
hint: `Use a real GPIO on this board (controller ranges: ${chip.gpioControllers.map((r) => `${r.nodelabel} ${r.minPin}-${r.maxPin}`).join(', ')}).`,
|
|
1212
|
+
source: program.fileName,
|
|
1213
|
+
});
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
// ── DAC pin validity ────────────────────────────────────────────────────
|
|
1218
|
+
// dac.write resolves a HAL pin to a channel via the chip descriptor's
|
|
1219
|
+
// dac.channels map. A pin not in that map lowers to a comment (silent
|
|
1220
|
+
// no-op), and a chip without a `dac` entry (nRF52840, ESP32-S3) has no DAC
|
|
1221
|
+
// at all. Flag either case so the user gets a clear message instead of a
|
|
1222
|
+
// pin that silently does nothing.
|
|
1223
|
+
if (dacPins.size > 0) {
|
|
1224
|
+
const dacChannels = new Set((chip.dac?.channels ?? []).map((c) => c.pin));
|
|
1225
|
+
for (const pin of dacPins) {
|
|
1226
|
+
if (!dacChannels.has(pin)) {
|
|
1227
|
+
const adcCh = (chip.adc?.channels ?? []).find((c) => c.pin === pin);
|
|
1228
|
+
const mixup = adcCh
|
|
1229
|
+
? ` GPIO ${pin} is an ADC channel (${adcCh.controller ?? chip.adc?.nodeLabel ?? 'adc'} ch ${adcCh.channel}) — did you mean new ADC(${pin})?`
|
|
1230
|
+
: '';
|
|
1231
|
+
diags.push({
|
|
1232
|
+
severity: 'error',
|
|
1233
|
+
code: 'zephyr-dac-pin-unavailable',
|
|
1234
|
+
message: `GPIO ${pin} is not a DAC channel on ${chip.id} and cannot be driven with dac.write.`,
|
|
1235
|
+
hint: dacChannels.size > 0
|
|
1236
|
+
? `Use a DAC-capable pin. On ${chip.id}: ${[...dacChannels].sort((x, y) => x - y).join(', ')}.${mixup}`
|
|
1237
|
+
: `${chip.id} has no DAC. Use an esp32_devkitc target (ESP32 DAC on GPIO25/26).${mixup}`,
|
|
1238
|
+
source: program.fileName,
|
|
1239
|
+
});
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
// ── PWM pin validity ────────────────────────────────────────────────────
|
|
1245
|
+
// pwm.write resolves a HAL pin to a DT spec via the chip descriptor's
|
|
1246
|
+
// pwm.specs (static) or pwm.matrix (ESP32 LEDC: any matrix pin synthesizes
|
|
1247
|
+
// a spec at build time). A pin on neither lowers to a comment — the pin
|
|
1248
|
+
// silently never toggles. Flag it so the user knows (warning, not error:
|
|
1249
|
+
// boards legitimately ship partial PWM coverage, e.g. only the aliased LED
|
|
1250
|
+
// channel, and the rest of the program still works).
|
|
1251
|
+
const pwmMatrix = chip.pwm?.matrix;
|
|
1252
|
+
const pwmSpecPins = new Set([
|
|
1253
|
+
...(chip.pwm?.specs ?? []).map((s) => s.pin),
|
|
1254
|
+
...(pwmMatrix?.pins ?? []),
|
|
1255
|
+
]);
|
|
1256
|
+
const pwmValid = [...pwmSpecPins].sort((x, y) => x - y).join(', ');
|
|
1257
|
+
for (const pin of pwmPins) {
|
|
1258
|
+
if (pwmOverriddenPins.has(pin)) continue;
|
|
1259
|
+
if (!pwmSpecPins.has(pin)) {
|
|
1260
|
+
// Cross-peripheral suggestion: an analog pin driven as PWM is the
|
|
1261
|
+
// other classic mix-up.
|
|
1262
|
+
const adcCh = (chip.adc?.channels ?? []).find((c) => c.pin === pin);
|
|
1263
|
+
const mixup = adcCh
|
|
1264
|
+
? ` GPIO ${pin} is an ADC channel (${adcCh.controller ?? chip.adc?.nodeLabel ?? 'adc'} ch ${adcCh.channel}) — did you mean new ADC(${pin})?`
|
|
1265
|
+
: '';
|
|
1266
|
+
diags.push({
|
|
1267
|
+
severity: 'warning',
|
|
1268
|
+
code: 'zephyr-pwm-pin-unavailable',
|
|
1269
|
+
message: pwmMatrix
|
|
1270
|
+
? `pwm on GPIO ${pin} lowers to a no-op: the pin is outside ${chip.id}'s PWM-capable set (USB, flash/PSRAM, strapping, and console pads are excluded), so nothing is driven.`
|
|
1271
|
+
: `pwm on GPIO ${pin} lowers to a no-op: the pin has no PWM spec in ${chip.id}'s chip descriptor, so nothing is driven.`,
|
|
1272
|
+
hint: pwmValid
|
|
1273
|
+
? pwmMatrix
|
|
1274
|
+
? `PWM-capable pins on ${chip.id} (first ${pwmMatrix.channelCount} driven get channels): ${pwmValid}.${mixup}`
|
|
1275
|
+
: `PWM-capable pins on ${chip.id}: ${pwmValid}.${mixup}`
|
|
1276
|
+
: `${chip.id} maps no PWM channels in its chip descriptor — pwm.* are no-ops on this target.${mixup}`,
|
|
1277
|
+
source: program.fileName,
|
|
1278
|
+
});
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
// ── Bus instance validity ───────────────────────────────────────────────
|
|
1282
|
+
// The bus lowerings resolve instance N against chip.<bus>.controllers[N]
|
|
1283
|
+
// and emit `__tc_<bus>N_dev` references; the state block is only declared
|
|
1284
|
+
// for declared controllers, so an out-of-range instance is an undefined
|
|
1285
|
+
// symbol at link time. Flag it here with the board's real controller set
|
|
1286
|
+
// instead (mirrors how the ADC gate replaces an opaque link error).
|
|
1287
|
+
const busLabels: Record<'i2c' | 'spi' | 'uart', { name: string; label: string }> = {
|
|
1288
|
+
i2c: { name: 'I2C', label: 'Wire' },
|
|
1289
|
+
spi: { name: 'SPI', label: 'SPI' },
|
|
1290
|
+
uart: { name: 'UART', label: 'Serial' },
|
|
1291
|
+
};
|
|
1292
|
+
for (const bus of ['i2c', 'spi', 'uart'] as const) {
|
|
1293
|
+
const controllers = chip[bus]?.controllers ?? [];
|
|
1294
|
+
for (const inst of busInstances[bus]) {
|
|
1295
|
+
if (inst < 0 || inst >= controllers.length) {
|
|
1296
|
+
diags.push({
|
|
1297
|
+
severity: 'error',
|
|
1298
|
+
code: 'zephyr-bus-instance-unavailable',
|
|
1299
|
+
message: `${busLabels[bus].name}${inst} is used but ${chip.id} declares only ${controllers.length} ${busLabels[bus].name} controller(s) in its Zephyr chip data — the generated code would not link.`,
|
|
1300
|
+
hint: controllers.length > 0
|
|
1301
|
+
? `Use ${busLabels[bus].name}0 (the board's default-enabled controller: ${controllers.map((c) => c.nodeLabel).join(', ')}).`
|
|
1302
|
+
: `${chip.id} declares no ${busLabels[bus].name} controllers for Zephyr.`,
|
|
1303
|
+
source: program.fileName,
|
|
1304
|
+
});
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
// ── Hardware-timer instance validity ────────────────────────────────────
|
|
1310
|
+
// hwtimer.* resolves the instance index to a counter device via the chip
|
|
1311
|
+
// descriptor's hwtimer.controllers. A chip without that entry (or an
|
|
1312
|
+
// out-of-range instance) lowers to a comment — flag it so the user knows
|
|
1313
|
+
// the timer will never fire.
|
|
1314
|
+
if (hwtimerInstances.size > 0) {
|
|
1315
|
+
const controllerCount = chip.hwtimer?.controllers.length ?? 0;
|
|
1316
|
+
for (const inst of hwtimerInstances) {
|
|
1317
|
+
if (controllerCount === 0) {
|
|
1318
|
+
diags.push({
|
|
1319
|
+
severity: 'error',
|
|
1320
|
+
code: 'zephyr-hwtimer-unavailable',
|
|
1321
|
+
message: `Hardware timer instance ${inst} is used but ${chip.id} exposes no free counter device.`,
|
|
1322
|
+
hint: `${chip.id} declares no hwtimer.controllers. Use a target with a free counter (e.g. nRF RTC1).`,
|
|
1323
|
+
source: program.fileName,
|
|
1324
|
+
});
|
|
1325
|
+
} else if (inst < 0 || inst >= controllerCount) {
|
|
1326
|
+
diags.push({
|
|
1327
|
+
severity: 'error',
|
|
1328
|
+
code: 'zephyr-hwtimer-instance-out-of-range',
|
|
1329
|
+
message: `Hardware timer instance ${inst} is out of range on ${chip.id} (0..${controllerCount - 1}).`,
|
|
1330
|
+
source: program.fileName,
|
|
1331
|
+
});
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
// ── Watchdog availability ───────────────────────────────────────────────
|
|
1337
|
+
// wdt.* resolves the device via the chip descriptor's wdt.nodeLabel. A
|
|
1338
|
+
// chip without that entry (e.g. SAM D21 — Zephyr's samd21 dtsi exposes
|
|
1339
|
+
// no watchdog node) lowers to a comment — flag it so the user knows the
|
|
1340
|
+
// watchdog never arms.
|
|
1341
|
+
if (wdtOps.size > 0 && !chip.wdt) {
|
|
1342
|
+
diags.push({
|
|
1343
|
+
severity: 'error',
|
|
1344
|
+
code: 'zephyr-wdt-unavailable',
|
|
1345
|
+
message: `Watchdog ops are used but ${chip.id} exposes no watchdog device.`,
|
|
1346
|
+
hint: `${chip.id} declares no wdt.nodeLabel (Zephyr's devicetree for this SoC has no watchdog node). Use a target with a watchdog, or drop the wdt.* calls.`,
|
|
1347
|
+
source: program.fileName,
|
|
1348
|
+
});
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
// ── Radio presence: family-derived, not board-curated ───────────────────
|
|
1352
|
+
// Espressif ESP32 variants carry a 2.4GHz WiFi radio; every other Zephyr
|
|
1353
|
+
// family in the catalog is radioless (until an Ethernet/board-wifi fact
|
|
1354
|
+
// exists). Derived from the soc name — or, when no board module was
|
|
1355
|
+
// generated, from the raw build target the config carries.
|
|
1356
|
+
const familyTarget = `${chip.soc || ''} ${(ctx?.frameworkData as Record<string, unknown> | undefined)?.target ?? ''} ${(ctx?.frameworkData as Record<string, unknown> | undefined)?.buildTarget ?? ''}`.toLowerCase();
|
|
1357
|
+
const wifiSupported = chip.wifi?.supported
|
|
1358
|
+
?? familyTarget.split(/[^a-z0-9]+/).some((t) => t.startsWith('esp32'));
|
|
1359
|
+
|
|
1360
|
+
// ── WiFi target validity ────────────────────────────────────────────────
|
|
1361
|
+
// WiFi ops require a chip with a WiFi radio. The ESP32-S3 descriptor sets
|
|
1362
|
+
// wifi.supported; the XIAO nRF52840 omits it (no radio). Flag wifi usage on
|
|
1363
|
+
// a radioless chip so the user gets a clear "use an ESP32 target" message
|
|
1364
|
+
// instead of an opaque link/DT failure.
|
|
1365
|
+
if (usesWifiOps && !wifiSupported) {
|
|
1366
|
+
diags.push({
|
|
1367
|
+
severity: 'error',
|
|
1368
|
+
code: 'zephyr-wifi-unavailable-on-target',
|
|
1369
|
+
message: `WiFi ops are used but ${chip.id} has no WiFi radio.`,
|
|
1370
|
+
hint: `Use an esp32s3_devkitc or esp32_devkitc target (Espressif ESP32 variants have a 2.4GHz WiFi radio).`,
|
|
1371
|
+
source: program.fileName,
|
|
1372
|
+
});
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
// ── HTTP target validity ──────────────────────────────────────────────
|
|
1376
|
+
// HTTP needs a network transport. On Zephyr the only networked target is
|
|
1377
|
+
// the ESP32 (WiFi) — the nRF52840 has neither WiFi nor Ethernet wired in
|
|
1378
|
+
// its chip descriptor, so the shim's socket/connect calls would fail at
|
|
1379
|
+
// runtime. Flag http usage on a chip without a network radio so the user
|
|
1380
|
+
// gets a clear "use an ESP32 target" message instead of an opaque link or
|
|
1381
|
+
// runtime failure. (HTTP rides over WiFi here; an Ethernet target would
|
|
1382
|
+
// set wifi.supported via a different transport flag if/when added.)
|
|
1383
|
+
if (usesHttpOps && !wifiSupported) {
|
|
1384
|
+
diags.push({
|
|
1385
|
+
severity: 'error',
|
|
1386
|
+
code: 'zephyr-http-unavailable-on-target',
|
|
1387
|
+
message: `HTTP ops are used but ${chip.id} has no network stack available.`,
|
|
1388
|
+
hint: `Use an esp32s3_devkitc or esp32_devkitc target (HTTP needs a network transport; the ESP32 WiFi radio provides it).`,
|
|
1389
|
+
source: program.fileName,
|
|
1390
|
+
});
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
// ── MQTT target validity ─────────────────────────────────────────────
|
|
1394
|
+
// Same constraint as HTTP: MQTT needs a network transport to reach a broker.
|
|
1395
|
+
// Flag mqtt usage on a radioless chip so the user picks a networked target.
|
|
1396
|
+
if (usesMqttOps && !wifiSupported) {
|
|
1397
|
+
diags.push({
|
|
1398
|
+
severity: 'error',
|
|
1399
|
+
code: 'zephyr-mqtt-unavailable-on-target',
|
|
1400
|
+
message: `MQTT ops are used but ${chip.id} has no network stack available.`,
|
|
1401
|
+
hint: `Use an esp32s3_devkitc or esp32_devkitc target (MQTT needs a network transport; the ESP32 WiFi radio provides it).`,
|
|
1402
|
+
source: program.fileName,
|
|
1403
|
+
});
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
// ── Unused-analysis: surface a hint that this is a no-analysis probe ────
|
|
1407
|
+
// (intentionally minimal — esp32 has richer rules around strapping/RTC pins;
|
|
1408
|
+
// nRF52840 has fewer silicon foot-guns, so the rules above are the material
|
|
1409
|
+
// ones. Extend as constraints are identified.)
|
|
1410
|
+
|
|
1411
|
+
return diags;
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
/**
|
|
1415
|
+
* Strip Arduino library headers that @typecad/hal's class surfaces register
|
|
1416
|
+
* (I2CBus → <Wire.h>, SPIBus → <SPI.h>). The Zephyr
|
|
1417
|
+
* equivalents are usage-gated forcedIncludes (<zephyr/drivers/i2c.h>, …),
|
|
1418
|
+
* so an unstripped Wire.h reaches the west build and gcc rejects it
|
|
1419
|
+
* ("Wire.h: No such file or directory") — this hook is why I2C/SPI class
|
|
1420
|
+
* usage on Zephyr used to fail at compile time.
|
|
1421
|
+
*/
|
|
1422
|
+
filterRequiredIncludes(includes: string[]): string[] {
|
|
1423
|
+
// Arduino-core / AVR-only headers the HAL classes declare via __includes.
|
|
1424
|
+
// On Zephyr the watchdog lowers to the Zephyr driver (<zephyr/drivers/
|
|
1425
|
+
// watchdog.h>, forced under usesWDT) — <avr/wdt.h> does not exist there.
|
|
1426
|
+
const arduinoOnlyHeaders = new Set(['<Wire.h>', '<SPI.h>', '<avr/wdt.h>', '<avr/io.h>', '<avr/interrupt.h>', '<avr/sleep.h>', '<avr/power.h>']);
|
|
1427
|
+
return includes.filter((i) => !arduinoOnlyHeaders.has(i));
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
/**
|
|
1431
|
+
* Nothing to exclude: no shim or Zephyr core header forward-declares a user
|
|
1432
|
+
* function extern (the old setup()/loop() bridge was the only consumer of
|
|
1433
|
+
* this hook, excluding `loop`). The entrypoint itself (`main`) is already
|
|
1434
|
+
* excluded from the static forward-declaration path because it equals
|
|
1435
|
+
* entrypointFunctionName().
|
|
1436
|
+
*/
|
|
1437
|
+
forwardDeclarationExclusions(): string[] {
|
|
1438
|
+
return [];
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
// ── File shape ──────────────────────────────────────────────────────────
|
|
1442
|
+
|
|
1443
|
+
sourceExtension(): string {
|
|
1444
|
+
return 'cpp';
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1447
|
+
entrypointFunctionName(): string {
|
|
1448
|
+
return 'main';
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
requiresLoopFunction(): boolean {
|
|
1452
|
+
return false;
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
overrideBaseName(
|
|
1456
|
+
originalBaseName: string,
|
|
1457
|
+
outDirBaseName: string,
|
|
1458
|
+
isEntryFile: boolean,
|
|
1459
|
+
isNpmPackage: boolean,
|
|
1460
|
+
): string {
|
|
1461
|
+
// npm packages are library-style — don't rename. Entry files (non-npm) take
|
|
1462
|
+
// the out-dir name (mirrors the directory-name-must-match-entry rule).
|
|
1463
|
+
// Everything else passes through. (The manifest's entrypoint.overrideBaseName
|
|
1464
|
+
// field is dead — never read in src/ — so this method is the sole name source.)
|
|
1465
|
+
if (isNpmPackage) return originalBaseName;
|
|
1466
|
+
if (isEntryFile) return outDirBaseName;
|
|
1467
|
+
return originalBaseName;
|
|
1468
|
+
}
|
|
1469
|
+
|
|
1470
|
+
effectiveEmitMode(requestedMode: string, _isNpmPackage: boolean): string {
|
|
1471
|
+
// Zephyr always emits .cpp (no single-file entry equivalent to force away
|
|
1472
|
+
// from), so this is passthrough regardless of npm/app. The 2-param shape
|
|
1473
|
+
// interface and Arduino; behavior is identical across branches.
|
|
1474
|
+
return requestedMode;
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1477
|
+
// ── Type normalisation ──────────────────────────────────────────────────
|
|
1478
|
+
|
|
1479
|
+
normalizeCppType(typeName: string): string {
|
|
1480
|
+
if (typeName === 'auto') return 'auto';
|
|
1481
|
+
if (typeName === 'std::string') return 'const char*';
|
|
1482
|
+
return typeName;
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
defaultNumericType(compliance?: { isBanned(ruleId: string): boolean }): string {
|
|
1486
|
+
// Zephyr favors fixed-width types. Under AUTOSAR compliance (A3-9-1) emit
|
|
1487
|
+
// int32_t; otherwise the standard 'int'.
|
|
1488
|
+
return compliance?.isBanned('A3-9-1') ? 'int32_t' : 'int';
|
|
1489
|
+
}
|
|
1490
|
+
|
|
1491
|
+
mapReturnType(functionName: string, returnType: string): string {
|
|
1492
|
+
if (functionName === 'main') return 'int';
|
|
1493
|
+
return this.normalizeCppType(returnType);
|
|
1494
|
+
}
|
|
1495
|
+
|
|
1496
|
+
isStringLikeType(cppType: string): boolean {
|
|
1497
|
+
return cppType === 'std::string' || cppType === 'const char*' || cppType === 'char*';
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
isPointerType(cppType: string): boolean {
|
|
1501
|
+
return cppType.endsWith('*');
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
mapFunctionName(originalName: string): string {
|
|
1505
|
+
if (originalName === '__cuttlefish_entrypoint__') return 'main';
|
|
1506
|
+
return originalName;
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1509
|
+
// ── Expression rendering ────────────────────────────────────────────────
|
|
1510
|
+
|
|
1511
|
+
normalizeRawExpression(value: string): string {
|
|
1512
|
+
let prev = '';
|
|
1513
|
+
let v = value;
|
|
1514
|
+
while (prev !== v) {
|
|
1515
|
+
prev = v;
|
|
1516
|
+
v = v.replace(/\bundefined\b/g, 'CUTTLEFISH_UNDEFINED');
|
|
1517
|
+
v = v.replace(/\bnull\b/g, 'CUTTLEFISH_UNDEFINED');
|
|
1518
|
+
}
|
|
1519
|
+
// String-method lowering is shared across targets (see string-method-
|
|
1520
|
+
// registry). Zephyr's strings are const char*, so the __tc_* helpers this
|
|
1521
|
+
// rewrites to (defined by the string_methods polyfill) take const char*.
|
|
1522
|
+
// includes/startsWith lower to inline strstr/strncmp (matching framework-
|
|
1523
|
+
// arduino); everything else → a __tc_* helper call.
|
|
1524
|
+
v = applyStringMethodRewrites(v, {
|
|
1525
|
+
wrapReceiverFor: new Set(['indexOf']),
|
|
1526
|
+
special: {
|
|
1527
|
+
includes: (recv, args) => `(strstr(${recv}, ${args[0]}) != NULL)`,
|
|
1528
|
+
startsWith: (recv, args) => `(strncmp(${recv}, ${args[0]}, strlen(${args[0]})) == 0)`,
|
|
1529
|
+
},
|
|
1530
|
+
});
|
|
1531
|
+
return v;
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1534
|
+
nullValue(): string {
|
|
1535
|
+
return 'CUTTLEFISH_UNDEFINED';
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1538
|
+
wrapStringConcat(): string | undefined {
|
|
1539
|
+
return undefined;
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
wrapStringObject(value: string): string {
|
|
1543
|
+
return `std::to_string(${value})`;
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
useSnprintfForStrings(): boolean {
|
|
1547
|
+
return true;
|
|
1548
|
+
}
|
|
1549
|
+
|
|
1550
|
+
renameEnumMember(_enumName: string, memberName: string): string {
|
|
1551
|
+
return memberName;
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
private _largeEnumNames = new Set<string>();
|
|
1555
|
+
|
|
1556
|
+
setLargeEnumNames(names: ReadonlySet<string>): void {
|
|
1557
|
+
this._largeEnumNames = new Set(names);
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1560
|
+
enumCastType(_enumName: string): string | undefined {
|
|
1561
|
+
return undefined;
|
|
1562
|
+
}
|
|
1563
|
+
|
|
1564
|
+
renderBoardDefinitionAccess(
|
|
1565
|
+
chain: string[],
|
|
1566
|
+
boardConstants?: BoardConstants,
|
|
1567
|
+
): string | undefined {
|
|
1568
|
+
// Fold Board.definition.<path> / Pins.definition.<path> into the literal
|
|
1569
|
+
// board-constant value, mirroring framework-arduino. The renderer calls
|
|
1570
|
+
// this (expression-renderer.ts) with the populated boardConstants from the
|
|
1571
|
+
// loaded board/MCU package, so a known path resolves to its scalar value.
|
|
1572
|
+
// `board.resolve` HAL ops, by contrast, are constant-folded earlier at
|
|
1573
|
+
// IR-build time and never reach here; see lowering/board.ts.
|
|
1574
|
+
if (chain.length < 3) return undefined;
|
|
1575
|
+
if (chain[0] !== 'Board' && chain[0] !== 'Pins') return undefined;
|
|
1576
|
+
if (chain[1] !== 'definition') return undefined;
|
|
1577
|
+
if (!boardConstants) return undefined;
|
|
1578
|
+
const path = chain.slice(2).join('.');
|
|
1579
|
+
const val = boardConstants.get(path);
|
|
1580
|
+
return val !== undefined ? String(val) : undefined;
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
// ── Statement rendering ─────────────────────────────────────────────────
|
|
1584
|
+
|
|
1585
|
+
promotesArrayLiteralsToStaticArray(): boolean {
|
|
1586
|
+
// No std::vector in the minimal C++ lib — promote array literals to the
|
|
1587
|
+
// StaticArray wrapper (the embedded/generic default).
|
|
1588
|
+
return true;
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1591
|
+
renderThrow(_valueExpr: string): string {
|
|
1592
|
+
// Zephyr minimal config disables C++ exceptions (CONFIG_CPP_EXCEPTIONS=n).
|
|
1593
|
+
// A throw becomes an infinite halt loop instead of a real throw.
|
|
1594
|
+
return 'for (;;) { k_msleep(1000); }';
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
objectFieldInitializer(): string | undefined {
|
|
1598
|
+
return undefined;
|
|
1599
|
+
}
|
|
1600
|
+
|
|
1601
|
+
overrideClassFieldType(_fieldName: string, normalizedType: string): string {
|
|
1602
|
+
return normalizedType;
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1605
|
+
// ── Name guards ─────────────────────────────────────────────────────────
|
|
1606
|
+
|
|
1607
|
+
reservedNames(): ReadonlySet<string> {
|
|
1608
|
+
return new Set<string>();
|
|
1609
|
+
}
|
|
1610
|
+
|
|
1611
|
+
passthroughMacroNames(): ReadonlySet<string> {
|
|
1612
|
+
return new Set<string>();
|
|
1613
|
+
}
|
|
1614
|
+
|
|
1615
|
+
apiReservedEnumNames(): ReadonlySet<string> {
|
|
1616
|
+
return new Set<string>();
|
|
1617
|
+
}
|
|
1618
|
+
|
|
1619
|
+
apiReservedEnumGuard(): string {
|
|
1620
|
+
return '';
|
|
1621
|
+
}
|
|
1622
|
+
|
|
1623
|
+
// ── Interrupt safety ─────────────────────────────────────────────────────
|
|
1624
|
+
// Zephyr ISRs run above thread context: anything that sleeps (k_msleep),
|
|
1625
|
+
// pends, or takes a driver lock is illegal there (asserted by the kernel in
|
|
1626
|
+
// debug builds; corrupts scheduler state otherwise). The keys below are
|
|
1627
|
+
// matched by cuttlefish's interrupt-analysis pass in two shapes: bare/qualified
|
|
1628
|
+
// callee names (call statements) and resolved hal-op payloads — the analyzer
|
|
1629
|
+
// maps timing.delay/delay_microseconds/sleep/busy_wait_us ops back to the
|
|
1630
|
+
// bare timing keys, and matches the op payload's port/bus field (UART0,
|
|
1631
|
+
// I2C0, SPI0, …) against these keys directly. The same keys ArduinoStrategy
|
|
1632
|
+
// uses.
|
|
1633
|
+
isrUnsafeOperations(): Map<string, { reason: string; severity: 'warning' | 'info' }> {
|
|
1634
|
+
return new Map<string, { reason: string; severity: 'warning' | 'info' }>([
|
|
1635
|
+
['delay', {
|
|
1636
|
+
reason: 'delay() lowers to k_msleep(), which sleeps the calling thread — illegal in Zephyr interrupt context (submit a k_work item or arm a k_timer instead)',
|
|
1637
|
+
severity: 'warning',
|
|
1638
|
+
}],
|
|
1639
|
+
['delayMicroseconds', {
|
|
1640
|
+
reason: 'delayMicroseconds() busy-waits the CPU for the full delay, stalling every lower-priority interrupt and the scheduler for its duration',
|
|
1641
|
+
severity: 'warning',
|
|
1642
|
+
}],
|
|
1643
|
+
['I2C0', {
|
|
1644
|
+
reason: 'I2C transactions may sleep (driver locking + clock stretching) and are not callable from Zephyr interrupt context',
|
|
1645
|
+
severity: 'warning',
|
|
1646
|
+
}],
|
|
1647
|
+
['I2C1', {
|
|
1648
|
+
reason: 'I2C transactions may sleep (driver locking + clock stretching) and are not callable from Zephyr interrupt context',
|
|
1649
|
+
severity: 'warning',
|
|
1650
|
+
}],
|
|
1651
|
+
['SPI0', {
|
|
1652
|
+
reason: 'SPI transfers take driver locks and may wait on DMA completion — not safe in Zephyr interrupt context',
|
|
1653
|
+
severity: 'warning',
|
|
1654
|
+
}],
|
|
1655
|
+
['SPI1', {
|
|
1656
|
+
reason: 'SPI transfers take driver locks and may wait on DMA completion — not safe in Zephyr interrupt context',
|
|
1657
|
+
severity: 'warning',
|
|
1658
|
+
}],
|
|
1659
|
+
['UART0', {
|
|
1660
|
+
reason: 'UART output via uart_poll_out blocks until the TX FIFO has room — a full FIFO stalls the ISR',
|
|
1661
|
+
severity: 'info',
|
|
1662
|
+
}],
|
|
1663
|
+
['UART1', {
|
|
1664
|
+
reason: 'UART output via uart_poll_out blocks until the TX FIFO has room — a full FIFO stalls the ISR',
|
|
1665
|
+
severity: 'info',
|
|
1666
|
+
}],
|
|
1667
|
+
]);
|
|
1668
|
+
}
|
|
1669
|
+
|
|
1670
|
+
ambientTypeDeclarations(): string[] {
|
|
1671
|
+
// Preferences is the only HAL surface the framework lowers that is used as
|
|
1672
|
+
// a bare global (the HAL Preferences class is exported, but the canonical
|
|
1673
|
+
// usage — and the hal/tests/14-preferences hardware suite — references it
|
|
1674
|
+
// as an unqualified `Preferences.*`). Declaring it ambient lets those
|
|
1675
|
+
// programs type-check and resolve to the preferences.* ops the lowering
|
|
1676
|
+
// in src/lowering/preferences.ts handles (ZMS-backed settings). Mirrors
|
|
1677
|
+
// framework-arduino's ambient Preferences declaration.
|
|
1678
|
+
return [
|
|
1679
|
+
"",
|
|
1680
|
+
" // Persistent key/value store (ZMS-backed Zephyr settings — see",
|
|
1681
|
+
" // src/lowering/preferences.ts). begin/end carry the namespace prefix;",
|
|
1682
|
+
" // typed put/get round-trip through an in-RAM cache + settings_save_one.",
|
|
1683
|
+
" const Preferences: {",
|
|
1684
|
+
" begin(name: string, readOnly?: boolean): void;",
|
|
1685
|
+
" end(): void;",
|
|
1686
|
+
" clear(): void;",
|
|
1687
|
+
" putInt(key: string, value: number): void;",
|
|
1688
|
+
" getInt(key: string, defaultValue: number): number;",
|
|
1689
|
+
" putUInt(key: string, value: number): void;",
|
|
1690
|
+
" getUInt(key: string, defaultValue: number): number;",
|
|
1691
|
+
" putBool(key: string, value: boolean): void;",
|
|
1692
|
+
" getBool(key: string, defaultValue: boolean): boolean;",
|
|
1693
|
+
" putFloat(key: string, value: number): void;",
|
|
1694
|
+
" getFloat(key: string, defaultValue: number): number;",
|
|
1695
|
+
" putString(key: string, value: string): void;",
|
|
1696
|
+
" getString(key: string, defaultValue: string): string;",
|
|
1697
|
+
" remove(key: string): void;",
|
|
1698
|
+
" };",
|
|
1699
|
+
];
|
|
1700
|
+
}
|
|
1701
|
+
|
|
1702
|
+
// ── Includes ────────────────────────────────────────────────────────────
|
|
1703
|
+
|
|
1704
|
+
needsIostream(): boolean {
|
|
1705
|
+
return false;
|
|
1706
|
+
}
|
|
1707
|
+
|
|
1708
|
+
needsStdString(): boolean {
|
|
1709
|
+
// Zephyr's minimal C++ lib has no <string>. Set false so the transpiler
|
|
1710
|
+
// does not emit std::string-dependent code (e.g. the string-method
|
|
1711
|
+
// polyfills). A program that needs it must enable a full STL.
|
|
1712
|
+
return false;
|
|
1713
|
+
}
|
|
1714
|
+
|
|
1715
|
+
needsStdVector(): boolean {
|
|
1716
|
+
// No <vector> in the minimal C++ lib.
|
|
1717
|
+
return false;
|
|
1718
|
+
}
|
|
1719
|
+
|
|
1720
|
+
needsStdExcept(): boolean {
|
|
1721
|
+
return false;
|
|
1722
|
+
}
|
|
1723
|
+
|
|
1724
|
+
needsStdFunction(): boolean {
|
|
1725
|
+
// No <functional> in the minimal C++ lib.
|
|
1726
|
+
return false;
|
|
1727
|
+
}
|
|
1728
|
+
|
|
1729
|
+
mathHeader(): string {
|
|
1730
|
+
// <cmath>, not <math.h>: the shared lowering emits Math.<fn>() as
|
|
1731
|
+
// std::<fn>() (expression-to-ir), and picolibc's <math.h> declares the
|
|
1732
|
+
// C names in the global namespace only — std::round et al. fail to
|
|
1733
|
+
// compile. Builds run with CONFIG_REQUIRES_FULL_LIBCPP (real libstdc++),
|
|
1734
|
+
// whose <cmath> provides the std:: names.
|
|
1735
|
+
return '<cmath>';
|
|
1736
|
+
}
|
|
1737
|
+
|
|
1738
|
+
cstringHeader(): string {
|
|
1739
|
+
return '<cstring>';
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1742
|
+
needsLargeEnumUnderlying(): boolean {
|
|
1743
|
+
return false;
|
|
1744
|
+
}
|
|
1745
|
+
|
|
1746
|
+
// ── Struct field handling ───────────────────────────────────────────────
|
|
1747
|
+
|
|
1748
|
+
renameStructField(fieldName: string): string {
|
|
1749
|
+
return fieldName;
|
|
1750
|
+
}
|
|
1751
|
+
|
|
1752
|
+
structFieldInitializer(): string | undefined {
|
|
1753
|
+
return undefined;
|
|
1754
|
+
}
|
|
1755
|
+
|
|
1756
|
+
// ── Async ───────────────────────────────────────────────────────────────
|
|
1757
|
+
// Promises use the heap-free static runtime (generateStaticAsyncRuntime),
|
|
1758
|
+
// pumped cooperatively via cuttlefish_pump_microtasks(). There is no
|
|
1759
|
+
// __tc_timer_runtime.run() poll — periodic work is a Thread (k_thread) or a
|
|
1760
|
+
// Counter (hardware timer), never a cooperative timer queue.
|
|
1761
|
+
|
|
1762
|
+
getAsyncRuntimeConfig(): AsyncRuntimeConfig {
|
|
1763
|
+
return {
|
|
1764
|
+
queueCapacity: 64,
|
|
1765
|
+
scheduler: 'microtask',
|
|
1766
|
+
waitForPinEdge: 'stub',
|
|
1767
|
+
hasPromiseRuntime: true,
|
|
1768
|
+
hasTimers: false,
|
|
1769
|
+
// Static (heap-free) runtime — no STL headers required.
|
|
1770
|
+
requiredIncludes: [],
|
|
1771
|
+
};
|
|
1772
|
+
}
|
|
1773
|
+
|
|
1774
|
+
/**
|
|
1775
|
+
* Board module generation: join the Zephyr board data pack (extracted
|
|
1776
|
+
* from the pinned tree) with the curated soc descriptors. See
|
|
1777
|
+
* src/boardgen.ts.
|
|
1778
|
+
*/
|
|
1779
|
+
generateBoardModule(
|
|
1780
|
+
target: string,
|
|
1781
|
+
opts?: { factsJson?: string; asBuiltJson?: string },
|
|
1782
|
+
): { boardTs: string; boardJson: string; warnings?: readonly string[] } | undefined {
|
|
1783
|
+
try {
|
|
1784
|
+
const g = generateBoard(target, { factsJson: opts?.factsJson, asBuiltJson: opts?.asBuiltJson });
|
|
1785
|
+
return { boardTs: g.boardTs, boardJson: g.boardJson, ...(g.warnings ? { warnings: g.warnings } : {}) };
|
|
1786
|
+
} catch (err) {
|
|
1787
|
+
// A malformed facts/as-built file is the USER's error — surface it
|
|
1788
|
+
// verbatim instead of the generic "cannot generate" below.
|
|
1789
|
+
if (err instanceof Error && (err.message.includes('typecad-hal.facts.json') || err.message.includes('as-built.json'))) throw err;
|
|
1790
|
+
return undefined;
|
|
1791
|
+
}
|
|
1792
|
+
}
|
|
1793
|
+
|
|
1794
|
+
/**
|
|
1795
|
+
* Regenerate the board catalog overlay from the user's own Zephyr tree —
|
|
1796
|
+
* `typecad-hal board sync`. After a `west update`, this is how new/changed/
|
|
1797
|
+
* removed boards reach projects without a cuttlefish release. See
|
|
1798
|
+
* src/sdk/board-catalog-sync.ts.
|
|
1799
|
+
*/
|
|
1800
|
+
syncBoardCatalog(zephyrBase?: string): BoardCatalogSyncReport {
|
|
1801
|
+
return syncBoardCatalog(zephyrBase ? { zephyrBase } : {});
|
|
1802
|
+
}
|
|
1803
|
+
|
|
1804
|
+
/**
|
|
1805
|
+
* Refresh the overlay only when it is stale (provenance no longer matches
|
|
1806
|
+
* the tree) — the pre-step `typecad-hal board regen` runs so a regen after
|
|
1807
|
+
* `west update` picks up the tree's boards automatically. Tree walk only
|
|
1808
|
+
* happens when there is actual refreshing to do.
|
|
1809
|
+
*/
|
|
1810
|
+
ensureFreshBoardCatalog(): BoardCatalogEnsureResult {
|
|
1811
|
+
return ensureFreshBoardCatalog();
|
|
1812
|
+
}
|
|
1813
|
+
|
|
1814
|
+
asyncLoopInjection(taskVarNames: string[], config: AsyncRuntimeConfig): string[];
|
|
1815
|
+
asyncLoopInjection(taskVarNames: string[], hasPromiseRuntime: boolean, hasTimers: boolean): string[];
|
|
1816
|
+
asyncLoopInjection(
|
|
1817
|
+
taskVarNames: string[],
|
|
1818
|
+
configOrBool: AsyncRuntimeConfig | boolean,
|
|
1819
|
+
_hasTimers?: boolean,
|
|
1820
|
+
): string[] {
|
|
1821
|
+
const cfg =
|
|
1822
|
+
typeof configOrBool === 'boolean'
|
|
1823
|
+
? { hasPromiseRuntime: configOrBool, hasTimers: _hasTimers ?? false }
|
|
1824
|
+
: configOrBool;
|
|
1825
|
+
// Drive every async state-machine task once per scheduler iteration. The
|
|
1826
|
+
// task globals auto-start on their first .run() (constructor sets STATE_0,
|
|
1827
|
+
// which runs unconditionally), so this is both the start and the per-frame
|
|
1828
|
+
// advance. The state machine no-ops in its terminal/cyclic state, so
|
|
1829
|
+
// unconditional .run() is correct (mirrors framework-arduino). No
|
|
1830
|
+
// isComplete() gating.
|
|
1831
|
+
const work: string[] = [];
|
|
1832
|
+
if (cfg.hasPromiseRuntime) {
|
|
1833
|
+
work.push('cuttlefish_pump_microtasks();');
|
|
1834
|
+
}
|
|
1835
|
+
for (const n of taskVarNames) {
|
|
1836
|
+
work.push(`${n}.run();`);
|
|
1837
|
+
}
|
|
1838
|
+
// NOTE: no timer-queue poll — periodic work is a Thread (k_thread) or a
|
|
1839
|
+
// Counter (hardware timer), never a cooperative poll.
|
|
1840
|
+
//
|
|
1841
|
+
// main() runs once, so the per-frame work must close over its own loop.
|
|
1842
|
+
// Under a mounted UI the emitter's hostEventLoop() already wraps ui_tick +
|
|
1843
|
+
// these lines in `while (...) { ...; k_msleep(1); }` — return flat lines
|
|
1844
|
+
// there. Otherwise (async/timer programs with no UI) self-wrap the same
|
|
1845
|
+
// scheduler loop here. For a program whose top-level `while (true)` never
|
|
1846
|
+
// exits, the loop below it is unreachable — harmless, and identical to the
|
|
1847
|
+
// old bridge behavior (whose for(;;) around loop() was equally dead).
|
|
1848
|
+
if (entryHasUI()) {
|
|
1849
|
+
return work;
|
|
1850
|
+
}
|
|
1851
|
+
return ['for (;;) {', ...work.map((l) => ` ${l}`), ' k_msleep(1);', '}'];
|
|
1852
|
+
}
|
|
1853
|
+
|
|
1854
|
+
asyncDriverFunctionName(): string {
|
|
1855
|
+
return 'main';
|
|
1856
|
+
}
|
|
1857
|
+
|
|
1858
|
+
/**
|
|
1859
|
+
* Zephyr's scheduler loop for event-driven programs (UI mounted). The
|
|
1860
|
+
* emitter wraps the per-frame work — ui_tick plus the asyncLoopInjection
|
|
1861
|
+
* lines — in `while (__tc_zephyr_event_loop) { ...; k_msleep(1); }`: a
|
|
1862
|
+
* 1 ms cooperative yield to the kernel each frame, matching the cadence the
|
|
1863
|
+
* old setup()/loop() bridge gave loop(). The flag is never cleared — the
|
|
1864
|
+
* loop is the program's lifetime — but keeping the bool gives the condition
|
|
1865
|
+
* a used symbol (a bare `while (true)` would leave nothing to name, and an
|
|
1866
|
+
* unused flag would trip -Werror's -Wunused-variable).
|
|
1867
|
+
*/
|
|
1868
|
+
hostEventLoop(): {
|
|
1869
|
+
flagName: string;
|
|
1870
|
+
continueCondition: string;
|
|
1871
|
+
preIteration: string;
|
|
1872
|
+
postIteration: string;
|
|
1873
|
+
} | null {
|
|
1874
|
+
return {
|
|
1875
|
+
flagName: '__tc_zephyr_event_loop',
|
|
1876
|
+
continueCondition: '__tc_zephyr_event_loop',
|
|
1877
|
+
preIteration: '',
|
|
1878
|
+
postIteration: 'k_msleep(1);',
|
|
1879
|
+
};
|
|
1880
|
+
}
|
|
1881
|
+
|
|
1882
|
+
// ── Type aliases ────────────────────────────────────────────────────────
|
|
1883
|
+
|
|
1884
|
+
shouldSkipTypeAlias(): boolean {
|
|
1885
|
+
return false;
|
|
1886
|
+
}
|
|
1887
|
+
|
|
1888
|
+
// ── Diagnostics ─────────────────────────────────────────────────────────
|
|
1889
|
+
|
|
1890
|
+
emitDiagnostics(): Diagnostic[] {
|
|
1891
|
+
return [];
|
|
1892
|
+
}
|
|
1893
|
+
|
|
1894
|
+
currentTimeMillis(): string {
|
|
1895
|
+
return '__tc_now_ms()';
|
|
1896
|
+
}
|
|
1897
|
+
|
|
1898
|
+
// ── Build configuration ──────────────────────────────────────────────────
|
|
1899
|
+
|
|
1900
|
+
asyncQueueCapacity(): number {
|
|
1901
|
+
return 64;
|
|
1902
|
+
}
|
|
1903
|
+
|
|
1904
|
+
outputSubdirectory(_baseName: string): string {
|
|
1905
|
+
// Zephyr convention: application sources live under src/.
|
|
1906
|
+
return 'src';
|
|
1907
|
+
}
|
|
1908
|
+
|
|
1909
|
+
generateHeaderFile(): boolean {
|
|
1910
|
+
return true;
|
|
1911
|
+
}
|
|
1912
|
+
|
|
1913
|
+
enumApiGuard(_enumName: string): { open: string; close: string } | undefined {
|
|
1914
|
+
return undefined;
|
|
1915
|
+
}
|
|
1916
|
+
|
|
1917
|
+
getStdLibSupport(_architecture?: string): StdLibSupport {
|
|
1918
|
+
// Zephyr's minimal C++ support (lib/cpp/minimal) provides only <cstddef>,
|
|
1919
|
+
// <cstdint>, <new>. No <vector>, <string>, <iostream>, <functional>, no
|
|
1920
|
+
// exceptions, no RTTI. The blink MVP uses only GPIO + kernel timing, so
|
|
1921
|
+
// none of those are needed. Array/string literals are not promoted to the
|
|
1922
|
+
// STL containers; a future program needing them must enable a full STL
|
|
1923
|
+
// and update these flags.
|
|
1924
|
+
return {
|
|
1925
|
+
hasVector: false,
|
|
1926
|
+
hasString: false,
|
|
1927
|
+
hasIostream: false,
|
|
1928
|
+
hasExceptions: false,
|
|
1929
|
+
hasRTTI: false,
|
|
1930
|
+
recommendedArrayImpl: 'static_array',
|
|
1931
|
+
recommendedStringImpl: 'static_string',
|
|
1932
|
+
};
|
|
1933
|
+
}
|
|
1934
|
+
|
|
1935
|
+
// ── Polyfills ───────────────────────────────────────────────────────────
|
|
1936
|
+
// Zephyr is a no-STL target (hasVector/hasString = false), so array/string
|
|
1937
|
+
// literals lower to __tc_StaticArray / const char* and string methods lower
|
|
1938
|
+
// to __tc_* helpers — both need STL-free definitions emitted here (there is
|
|
1939
|
+
// no shared-runtime fallback; the pipeline sources 100% of polyfills from
|
|
1940
|
+
// generateNativePolyfills). Mirrors framework-arduino's AVR polyfills.
|
|
1941
|
+
|
|
1942
|
+
nativePolyfills(): Set<string> {
|
|
1943
|
+
// cuttlefish_halt: always (the runtime header may reference it).
|
|
1944
|
+
// string_methods / static_array: STL-free array + string helpers a no-STL
|
|
1945
|
+
// target needs (mutated/struct array literals + any string method).
|
|
1946
|
+
// async_runtime: heap-free static Promise/microtask runtime (no STL needed).
|
|
1947
|
+
return new Set<string>([
|
|
1948
|
+
'cuttlefish_halt', 'wiring_compat', 'string_methods', 'static_array',
|
|
1949
|
+
'async_runtime',
|
|
1950
|
+
]);
|
|
1951
|
+
}
|
|
1952
|
+
|
|
1953
|
+
generateNativePolyfills(program?: ProgramIR, ctx?: PlatformContext): RuntimePolyfillIR[] {
|
|
1954
|
+
// wiring_compat (digitalRead/HIGH/LOW macros + the __tc_gpio_read forward
|
|
1955
|
+
// declaration) is emitted only when something reads a pin: user
|
|
1956
|
+
// digitalRead() calls, the @typecad/safety voter, or the UI runtime
|
|
1957
|
+
// header's unconditional digitalRead() poll (init-press-input.ts — the
|
|
1958
|
+
// loop body is dead when no pin watchers are configured but must
|
|
1959
|
+
// compile). needsGpioReadShim defaults to true without analysis so
|
|
1960
|
+
// capability queries keep seeing it.
|
|
1961
|
+
const wiringCompat: RuntimePolyfillIR = {
|
|
1962
|
+
// Wiring-compatibility shims for symbols the UI runtime header
|
|
1963
|
+
// references unconditionally (e.g. init-press-input.ts polls pin
|
|
1964
|
+
// watchers via digitalRead/HIGH/LOW even when none are configured —
|
|
1965
|
+
// the loop body is dead but must compile). Zephyr lowers GPIO through
|
|
1966
|
+
// its __tc_gpio_* helpers (defined in shimLines); these macros route
|
|
1967
|
+
// the Wiring tokens to them.
|
|
1968
|
+
kind: 'polyfill',
|
|
1969
|
+
id: 'wiring_compat',
|
|
1970
|
+
domain: 'standard' as const,
|
|
1971
|
+
requiredIncludes: [],
|
|
1972
|
+
forwardDeclarations: [
|
|
1973
|
+
// Forward-declared so the digitalRead macro (below) can reference it
|
|
1974
|
+
// before the shim block defines the body. The shim emits the full
|
|
1975
|
+
// definition via gpio_pin_get_raw.
|
|
1976
|
+
'int __tc_gpio_read(int pin);',
|
|
1977
|
+
],
|
|
1978
|
+
helperStructs: [],
|
|
1979
|
+
helperFunctions: [],
|
|
1980
|
+
shimMacros: [
|
|
1981
|
+
'#ifndef HIGH',
|
|
1982
|
+
'#define HIGH 1',
|
|
1983
|
+
'#endif',
|
|
1984
|
+
'#ifndef LOW',
|
|
1985
|
+
'#define LOW 0',
|
|
1986
|
+
'#endif',
|
|
1987
|
+
'#ifndef digitalRead',
|
|
1988
|
+
'#define digitalRead(pin) __tc_gpio_read(pin)',
|
|
1989
|
+
'#endif',
|
|
1990
|
+
],
|
|
1991
|
+
dependencies: [],
|
|
1992
|
+
};
|
|
1993
|
+
const polyfills: RuntimePolyfillIR[] = [
|
|
1994
|
+
{
|
|
1995
|
+
kind: 'polyfill',
|
|
1996
|
+
id: 'cuttlefish_halt',
|
|
1997
|
+
domain: 'standard' as const,
|
|
1998
|
+
requiredIncludes: [],
|
|
1999
|
+
forwardDeclarations: [],
|
|
2000
|
+
helperStructs: [],
|
|
2001
|
+
helperFunctions: [
|
|
2002
|
+
'[[noreturn]] inline void cuttlefish_halt() { for (;;) { k_msleep(1000); } }',
|
|
2003
|
+
],
|
|
2004
|
+
shimMacros: [],
|
|
2005
|
+
dependencies: [],
|
|
2006
|
+
},
|
|
2007
|
+
...(this.needsGpioReadShim(program, ctx) ? [wiringCompat] : []),
|
|
2008
|
+
{
|
|
2009
|
+
// STL-free string-method polyfills. String methods (.toUpperCase(),
|
|
2010
|
+
// .includes(), .substring(), …) lower at IR level to __tc_* helpers for
|
|
2011
|
+
// every target; this supplies their definitions. Minimal-libc friendly:
|
|
2012
|
+
// only <cstring> primitives (no <cctype> — case conversion is inline
|
|
2013
|
+
// ASCII so the polyfill is self-contained). Mirrors framework-arduino.
|
|
2014
|
+
kind: 'polyfill',
|
|
2015
|
+
id: 'string_methods',
|
|
2016
|
+
domain: 'embedded' as const,
|
|
2017
|
+
requiredIncludes: ['<cstring>'],
|
|
2018
|
+
forwardDeclarations: [],
|
|
2019
|
+
helperStructs: [],
|
|
2020
|
+
helperFunctions: [`
|
|
2021
|
+
// TypeCAD string method polyfills (Zephyr, minimal-libc).
|
|
2022
|
+
#ifndef CUTTLEFISH_STR_BUF_SIZE
|
|
2023
|
+
#define CUTTLEFISH_STR_BUF_SIZE 64
|
|
2024
|
+
#endif
|
|
2025
|
+
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; }
|
|
2026
|
+
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; }
|
|
2027
|
+
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; }
|
|
2028
|
+
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; }
|
|
2029
|
+
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; }
|
|
2030
|
+
const char* __tc_substring1(const char* s, int start) { return __tc_substring2(s, start, strlen(s)); }
|
|
2031
|
+
const char* __tc_slice2(const char* s, int start, int end) { return __tc_substring2(s, start, end); }
|
|
2032
|
+
const char* __tc_slice1(const char* s, int start) { return __tc_substring2(s, start, strlen(s)); }
|
|
2033
|
+
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; }
|
|
2034
|
+
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]; }
|
|
2035
|
+
int __tc_charCodeAt(const char* s, int idx) { return static_cast<int>(static_cast<unsigned char>(s[idx])); }
|
|
2036
|
+
int __tc_indexOf(const char* s, const char* needle) { const char* p = strstr(s, needle); return p ? static_cast<int>(p - s) : -1; }
|
|
2037
|
+
`],
|
|
2038
|
+
shimMacros: [],
|
|
2039
|
+
dependencies: [],
|
|
2040
|
+
},
|
|
2041
|
+
{
|
|
2042
|
+
// STL-free fixed-size array wrapper. Mutated/struct-element array
|
|
2043
|
+
// literals and array methods (.push/.pop/.map/.filter) lower to
|
|
2044
|
+
// __tc_StaticArray<T,N>; this supplies the template. Idempotent guard
|
|
2045
|
+
// so a redefinition is a no-op. Mirrors framework-arduino.
|
|
2046
|
+
kind: 'polyfill',
|
|
2047
|
+
id: 'static_array',
|
|
2048
|
+
domain: 'embedded' as const,
|
|
2049
|
+
requiredIncludes: [],
|
|
2050
|
+
forwardDeclarations: [],
|
|
2051
|
+
helperStructs: [],
|
|
2052
|
+
helperFunctions: [`
|
|
2053
|
+
#ifndef __TC_STATIC_ARRAY_DEFINED
|
|
2054
|
+
#define __TC_STATIC_ARRAY_DEFINED
|
|
2055
|
+
template<typename T, int N>
|
|
2056
|
+
struct __tc_StaticArray {
|
|
2057
|
+
T data[N];
|
|
2058
|
+
int _size;
|
|
2059
|
+
__tc_StaticArray() : _size(0) {}
|
|
2060
|
+
int length() const { return _size; }
|
|
2061
|
+
int size() const { return _size; }
|
|
2062
|
+
void push(T val) { if (_size < N) data[_size++] = val; }
|
|
2063
|
+
T pop() { return (_size > 0) ? data[--_size] : T(); }
|
|
2064
|
+
int indexOf(T val) const { for (int i = 0; i < _size; i++) if (data[i] == val) return i; return -1; }
|
|
2065
|
+
T& operator[](int i) { return data[i]; }
|
|
2066
|
+
const T& operator[](int i) const { return data[i]; }
|
|
2067
|
+
T* begin() { return &data[0]; }
|
|
2068
|
+
T* end() { return &data[_size]; }
|
|
2069
|
+
const T* begin() const { return &data[0]; }
|
|
2070
|
+
const T* end() const { return &data[_size]; }
|
|
2071
|
+
};
|
|
2072
|
+
#endif
|
|
2073
|
+
`],
|
|
2074
|
+
shimMacros: [],
|
|
2075
|
+
dependencies: [],
|
|
2076
|
+
},
|
|
2077
|
+
];
|
|
2078
|
+
|
|
2079
|
+
// async_runtime — heap-free static Promise/microtask runtime. Emitted when
|
|
2080
|
+
// the program declares an async function OR references an async-runtime
|
|
2081
|
+
// symbol (Async.sleep/.then from a non-async fn). The static path requires
|
|
2082
|
+
// no STL headers, so it is safe under Zephyr's minimal C++ lib.
|
|
2083
|
+
const usesAsync = !!program
|
|
2084
|
+
&& (program.functions.some((fn: any) => fn && fn.isAsync) || this.programUsesAsyncRuntime(program));
|
|
2085
|
+
if (usesAsync) {
|
|
2086
|
+
polyfills.push({
|
|
2087
|
+
kind: 'polyfill',
|
|
2088
|
+
id: 'async_runtime',
|
|
2089
|
+
domain: 'embedded',
|
|
2090
|
+
requiredIncludes: [],
|
|
2091
|
+
// Polyfill definitions emit before shimLines, but the runtime's
|
|
2092
|
+
// timer bodies call __tc_now_ms() (defined in shimLines) — declare
|
|
2093
|
+
// it first so the polyfill compiles even for programs whose source
|
|
2094
|
+
// has no explicit timing call.
|
|
2095
|
+
forwardDeclarations: ['uint32_t __tc_now_ms(void);'],
|
|
2096
|
+
helperStructs: [generateStaticAsyncRuntime(8, this.getAsyncRuntimeConfig().waitForPinEdge)],
|
|
2097
|
+
helperFunctions: [],
|
|
2098
|
+
shimMacros: [],
|
|
2099
|
+
dependencies: [],
|
|
2100
|
+
hasPromiseRuntime: true,
|
|
2101
|
+
} as RuntimePolyfillIR);
|
|
2102
|
+
}
|
|
2103
|
+
return polyfills;
|
|
2104
|
+
}
|
|
2105
|
+
|
|
2106
|
+
// ── HAL ──────────────────────────────────────────────────────────────────
|
|
2107
|
+
|
|
2108
|
+
resolveHALOperation(op: HALOpIR): { code?: string; expression?: string } | undefined {
|
|
2109
|
+
return lowerHalOp(op);
|
|
2110
|
+
}
|
|
2111
|
+
|
|
2112
|
+
modelsGpio(): boolean {
|
|
2113
|
+
return true;
|
|
2114
|
+
}
|
|
2115
|
+
|
|
2116
|
+
// ── Atomic HAL primitives ─────────────────────────────────────────────────
|
|
2117
|
+
// Zephyr lowers GPIO through devicetree specs and its own __tc_gpio_* helpers
|
|
2118
|
+
// (defined in shimLines via gpio_pin_get_raw / gpio_pin_set_raw). Cuttlefish
|
|
2119
|
+
// asks these instead of emitting Wiring tokens by name. The async polling
|
|
2120
|
+
// path is gated to 'stub' on Zephyr (waitForPinEdge), so delayMs is unlikely
|
|
2121
|
+
// to be called here, but a busy-wait form is provided for completeness.
|
|
2122
|
+
readDigitalPin(pin: string): string {
|
|
2123
|
+
return `__tc_gpio_read(${pin})`;
|
|
2124
|
+
}
|
|
2125
|
+
readAnalogPin(pin: string): string {
|
|
2126
|
+
// Zephyr ADC is lowered through its own shim; this stub keeps cuttlefish
|
|
2127
|
+
// from emitting a Wiring analogRead token. Update if a __tc_adc_read helper
|
|
2128
|
+
// is introduced.
|
|
2129
|
+
return `/* adc lowering via zephyr shim */ 0`;
|
|
2130
|
+
}
|
|
2131
|
+
writeDigitalPin(pin: string, val: string): string {
|
|
2132
|
+
return `__tc_gpio_write(${pin}, ${val})`;
|
|
2133
|
+
}
|
|
2134
|
+
setPinMode(_pin: string, _mode: string): string {
|
|
2135
|
+
// Zephyr configures pin direction via devicetree, not a runtime pinMode.
|
|
2136
|
+
return `/* pin mode configured via devicetree */`;
|
|
2137
|
+
}
|
|
2138
|
+
delayMs(ms: string): string {
|
|
2139
|
+
return `k_msleep(${ms})`;
|
|
2140
|
+
}
|
|
2141
|
+
delayMicroseconds(us: string): string {
|
|
2142
|
+
return `__tc_delay_us(${us})`;
|
|
2143
|
+
}
|
|
2144
|
+
halCallNames(): ReadonlySet<string> {
|
|
2145
|
+
// Zephyr's HAL surface uses __tc_ prefixed helpers + the Zephyr API.
|
|
2146
|
+
return new Set<string>([
|
|
2147
|
+
"__tc_gpio_read", "__tc_gpio_write", "__tc_delay_us",
|
|
2148
|
+
"gpio_pin_get_raw", "gpio_pin_set_raw", "k_msleep", "k_busy_wait",
|
|
2149
|
+
]);
|
|
2150
|
+
}
|
|
2151
|
+
isHalCall(name: string): boolean {
|
|
2152
|
+
return this.halCallNames().has(name);
|
|
2153
|
+
}
|
|
2154
|
+
analogReadCallNames(): ReadonlySet<string> {
|
|
2155
|
+
return new Set<string>();
|
|
2156
|
+
}
|
|
2157
|
+
|
|
2158
|
+
// ── RTOS ─────────────────────────────────────────────────────────────────
|
|
2159
|
+
|
|
2160
|
+
isRtosTarget(): boolean {
|
|
2161
|
+
// Zephyr is a preemptive RTOS — delay()/k_msleep inside main() is the
|
|
2162
|
+
// expected cooperative yield, not an anti-pattern to warn about.
|
|
2163
|
+
return true;
|
|
2164
|
+
}
|
|
2165
|
+
|
|
2166
|
+
// Display state (mirrors Arduino's _displayCtx). Seeded on display.init; the
|
|
2167
|
+
// validator-probe path seeds the default profile lazily.
|
|
2168
|
+
private _displayState: DisplayState = newDisplayState();
|
|
2169
|
+
|
|
2170
|
+
// ── Graphics ──────────────────────────────────────────────────────────────
|
|
2171
|
+
// Generic <zephyr/drivers/display.h> + ported GFX primitives (see src/display/).
|
|
2172
|
+
// resolveDisplayOp delegates to resolveZephyrDisplayOp with the per-build
|
|
2173
|
+
// DisplayState; the GFX runtime (device handle + line buffer + helpers) is
|
|
2174
|
+
// emitted into shimLines when usesDisplay.
|
|
2175
|
+
|
|
2176
|
+
resolveDisplayOp(op: DisplayHALOp): { code?: string; expression?: string } | undefined {
|
|
2177
|
+
return resolveZephyrDisplayOp(op, this._displayState);
|
|
2178
|
+
}
|
|
2179
|
+
|
|
2180
|
+
supportedDisplayDrivers(): ReadonlySet<string> {
|
|
2181
|
+
return new Set<string>(Object.keys(ZEPHYR_DISPLAY_PROFILES));
|
|
2182
|
+
}
|
|
2183
|
+
|
|
2184
|
+
// ── Strategy-owned display/touch adapter seam ────────────────────────────
|
|
2185
|
+
// Zephyr owns its display + touch adapters: the UI display adapter bridges
|
|
2186
|
+
// the in-tree CuttlefishGFX class to the panel (per-controller init + wire
|
|
2187
|
+
// format, see src/display/ui-adapter.ts), and the touch adapters drive the
|
|
2188
|
+
// FT6336U (I2C capacitive) and XPT2046 (SPI resistive) controllers via
|
|
2189
|
+
// Zephyr's bus APIs (src/display/touch-adapter.ts). Both live in this
|
|
2190
|
+
// package so cuttlefish carries no Zephyr/Wiring-specific display or touch
|
|
2191
|
+
// knowledge. Mirrors ArduinoStrategy's provides*/resolve* pattern.
|
|
2192
|
+
|
|
2193
|
+
providesDisplayAdapter(): boolean { return true; }
|
|
2194
|
+
|
|
2195
|
+
resolveDisplayAdapter(display: ResolvedDisplay): DisplayAdapterCode | undefined {
|
|
2196
|
+
const code = zephyrDisplayAdapterGenerator(display);
|
|
2197
|
+
return code ?? undefined;
|
|
2198
|
+
}
|
|
2199
|
+
|
|
2200
|
+
providesTouchAdapter(): boolean { return true; }
|
|
2201
|
+
|
|
2202
|
+
resolveTouchAdapter(touch: TouchProfile): TouchAdapterCodegen | undefined {
|
|
2203
|
+
return zephyrTouchAdapter(touch);
|
|
2204
|
+
}
|
|
2205
|
+
|
|
2206
|
+
// Named display-profile registry: maps config `profile` values (e.g.
|
|
2207
|
+
// "st7796-zephyr") to the shared DisplayProfile shape so transpile.ts can
|
|
2208
|
+
// resolve them per-framework. The Zephyr profiles are DT-binding descriptors;
|
|
2209
|
+
// BUILT_IN_PROFILES (display/profiles.ts) is the single DT-binding →
|
|
2210
|
+
// shared-shape mapping, shared with the preview's registry loader.
|
|
2211
|
+
getProfileRegistry(): Map<string, DisplayProfile> {
|
|
2212
|
+
return new Map(Object.entries(BUILT_IN_PROFILES));
|
|
2213
|
+
}
|
|
2214
|
+
|
|
2215
|
+
colorFormat(): 'rgb565' | 'rgb666' | 'rgb888' | 'mono' {
|
|
2216
|
+
return 'rgb565';
|
|
2217
|
+
}
|
|
2218
|
+
|
|
2219
|
+
graphicsCapacity(): GraphicsCapacity {
|
|
2220
|
+
return {
|
|
2221
|
+
maxNodes: 256,
|
|
2222
|
+
maxBindings: 64,
|
|
2223
|
+
maxActiveTransitions: 32,
|
|
2224
|
+
nodeStorage: 'flash',
|
|
2225
|
+
};
|
|
2226
|
+
}
|
|
2227
|
+
|
|
2228
|
+
// ── Debug ─────────────────────────────────────────────────────────────────
|
|
2229
|
+
// Source-level debugging is the GDB flow only (the west-owned gdb server
|
|
2230
|
+
// serves F5 via cortex-debug's external-server mode — see
|
|
2231
|
+
// toolchain/debug-config.ts). The printf/Serial breakpoint instrumentation
|
|
2232
|
+
// pipeline was removed from the product.
|
|
2233
|
+
|
|
2234
|
+
debugMode(target?: string): 'gdb' | 'none' {
|
|
2235
|
+
// `target` is the Zephyr board id (optionally with a /qualifier suffix).
|
|
2236
|
+
// gdb capability is a FACT of the board: its probe-method table carries a
|
|
2237
|
+
// debug-capable entry (bootloaders mark debug:false; SWD/JTAG probes and
|
|
2238
|
+
// built-in USB-JTAG carry debug:true) — the same table west's runner
|
|
2239
|
+
// selection uses. Boards without a table (or unresolvable targets, e.g.
|
|
2240
|
+
// generated custom boards) report 'none', and --debug fails with an
|
|
2241
|
+
// explicit diagnostic (transpile.ts) rather than building silently.
|
|
2242
|
+
const t = (target ?? '').trim();
|
|
2243
|
+
if (!t) return 'none';
|
|
2244
|
+
const memo = debugModeMemo.get(t);
|
|
2245
|
+
if (memo) return memo;
|
|
2246
|
+
let mode: 'gdb' | 'none' = 'none';
|
|
2247
|
+
try {
|
|
2248
|
+
const g = generateBoard(t);
|
|
2249
|
+
const constants = JSON.parse(g.boardJson).constants as Record<string, unknown>;
|
|
2250
|
+
for (let i = 0; ; i++) {
|
|
2251
|
+
const id = constants[`zephyr.probeMethods.${i}.id`];
|
|
2252
|
+
if (typeof id !== 'string') break;
|
|
2253
|
+
if (constants[`zephyr.probeMethods.${i}.debug`] !== false) {
|
|
2254
|
+
mode = 'gdb';
|
|
2255
|
+
break;
|
|
2256
|
+
}
|
|
2257
|
+
}
|
|
2258
|
+
} catch {
|
|
2259
|
+
// Board not resolvable from facts — 'none' (never crash codegen over
|
|
2260
|
+
// the debug-capability gate).
|
|
2261
|
+
}
|
|
2262
|
+
debugModeMemo.set(t, mode);
|
|
2263
|
+
return mode;
|
|
2264
|
+
}
|
|
2265
|
+
}
|