@typecad/cuttlefish 1.0.0-alpha.7 → 1.0.0-alpha.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (160) hide show
  1. package/dist/api/config.d.ts +32 -0
  2. package/dist/api/index.d.ts +1 -1
  3. package/dist/api/schema/types.d.ts +15 -0
  4. package/dist/api/shared/async-runtime-static.d.ts +12 -1
  5. package/dist/api/shared/async-runtime-static.js +69 -24
  6. package/dist/api/shared/async-symbol-detector.d.ts +11 -0
  7. package/dist/api/shared/async-symbol-detector.js +140 -0
  8. package/dist/api/shared/async-types.d.ts +24 -0
  9. package/dist/api/shared/coop-scheduler.d.ts +60 -0
  10. package/dist/api/shared/coop-scheduler.js +149 -0
  11. package/dist/api/shared/display-adapter.d.ts +2 -2
  12. package/dist/api/shared/display-adapter.js +9 -119
  13. package/dist/api/shared/display-adapters/sdl.js +29 -29
  14. package/dist/api/shared/display-profile.d.ts +38 -33
  15. package/dist/api/shared/display-profile.js +22 -170
  16. package/dist/api/shared/framework-manifest-registry.d.ts +1 -1
  17. package/dist/api/shared/framework-manifest-registry.js +8 -6
  18. package/dist/api/shared/framework-manifest.d.ts +147 -28
  19. package/dist/api/shared/framework-manifest.js +35 -2
  20. package/dist/api/shared/graphics-strategy.d.ts +15 -6
  21. package/dist/api/shared/hal-op-ir.d.ts +340 -3
  22. package/dist/api/shared/hal-op-ir.js +34 -0
  23. package/dist/api/shared/index.d.ts +9 -1
  24. package/dist/api/shared/index.js +11 -1
  25. package/dist/api/shared/ir-core.d.ts +4 -0
  26. package/dist/api/shared/ir-declarations.d.ts +6 -0
  27. package/dist/api/shared/platform-strategy.d.ts +104 -2
  28. package/dist/api/shared/polyfill-helper-registry.js +18 -1
  29. package/dist/api/shared/promise-runtime.d.ts +1 -1
  30. package/dist/api/shared/promise-runtime.js +17 -13
  31. package/dist/api/shared/toolchain-types.d.ts +17 -0
  32. package/dist/api/shared/validate-framework-manifest.js +77 -0
  33. package/dist/api/shared/worker-runtime-polyfill.d.ts +22 -0
  34. package/dist/api/shared/worker-runtime-polyfill.js +34 -0
  35. package/dist/api/shared/worker-runtime.d.ts +69 -0
  36. package/dist/api/shared/worker-runtime.js +163 -0
  37. package/dist/cli.js +120 -121
  38. package/dist/config-loader.d.ts +7 -2
  39. package/dist/config-loader.js +20 -19
  40. package/dist/config-schema.d.ts +107 -65
  41. package/dist/config-schema.js +14 -0
  42. package/dist/contract/board-generator.d.ts +17 -0
  43. package/dist/contract/board-generator.js +57 -0
  44. package/dist/contract/contract-parser.d.ts +217 -0
  45. package/dist/contract/contract-parser.js +224 -0
  46. package/dist/contract/index.d.ts +22 -0
  47. package/dist/contract/index.js +68 -0
  48. package/dist/create/board-codegen.js +4 -4
  49. package/dist/create/board-generators.js +4 -5
  50. package/dist/create/board-spec.d.ts +74 -77
  51. package/dist/create/board-spec.js +0 -1
  52. package/dist/create/init-scaffold.d.ts +7 -3
  53. package/dist/create/init-scaffold.js +0 -62
  54. package/dist/create/init-templates.js +62 -16
  55. package/dist/create/init-wizard.js +32 -27
  56. package/dist/debug/preprocessor.js +178 -39
  57. package/dist/debug/types.d.ts +23 -0
  58. package/dist/diagnostics/mermaid-builder.d.ts +1 -1
  59. package/dist/diagnostics/mermaid-builder.js +34 -24
  60. package/dist/emit/compliance/arxml-writer.d.ts +11 -0
  61. package/dist/emit/compliance/arxml-writer.js +34 -0
  62. package/dist/emit/compliance/compliance-context.d.ts +57 -0
  63. package/dist/emit/compliance/compliance-context.js +113 -0
  64. package/dist/emit/compliance/deviation-ledger.d.ts +27 -0
  65. package/dist/emit/compliance/deviation-ledger.js +47 -0
  66. package/dist/emit/compliance/deviation-writer.d.ts +30 -0
  67. package/dist/emit/compliance/deviation-writer.js +37 -0
  68. package/dist/emit/compliance/index.d.ts +7 -0
  69. package/dist/emit/compliance/index.js +6 -0
  70. package/dist/emit/compliance/rule-engine.d.ts +13 -0
  71. package/dist/emit/compliance/rule-engine.js +101 -0
  72. package/dist/emit/compliance/rules.d.ts +16 -0
  73. package/dist/emit/compliance/rules.js +191 -0
  74. package/dist/emit/compliance/types.d.ts +59 -0
  75. package/dist/emit/compliance/types.js +8 -0
  76. package/dist/emit/emitters/class-emitter.js +6 -1
  77. package/dist/emit/emitters/emitter-context.d.ts +23 -1
  78. package/dist/emit/emitters/function-emitter-impl.js +96 -6
  79. package/dist/emit/emitters/line-appender.js +19 -0
  80. package/dist/emit/emitters/line-marker.d.ts +38 -0
  81. package/dist/emit/emitters/line-marker.js +39 -0
  82. package/dist/emit/emitters/output-finalizer.js +80 -1
  83. package/dist/emit/emitters/setup.d.ts +17 -0
  84. package/dist/emit/emitters/setup.js +217 -36
  85. package/dist/emit/emitters/top-level-prep.js +6 -0
  86. package/dist/emit/emitters/type-decl-emitter.js +28 -3
  87. package/dist/emit/emitters/ui-emitter.js +17 -8
  88. package/dist/emit/expression-renderer.d.ts +1 -1
  89. package/dist/emit/expression-renderer.js +81 -5
  90. package/dist/emit/route-hal-op.js +18 -5
  91. package/dist/emit/snprintf-helpers.js +7 -4
  92. package/dist/emit/statement-renderer.d.ts +10 -0
  93. package/dist/emit/statement-renderer.js +21 -5
  94. package/dist/emit/utils/async-state-machine.js +39 -12
  95. package/dist/framework-package.js +2 -0
  96. package/dist/framework-registry.d.ts +17 -0
  97. package/dist/ir/adc-range-validation.js +14 -1
  98. package/dist/ir/build-ir-state.d.ts +1 -0
  99. package/dist/ir/build-ir-state.js +14 -0
  100. package/dist/ir/build-ir.js +4 -4
  101. package/dist/ir/call-graph.js +16 -0
  102. package/dist/ir/expression-to-ir.js +150 -0
  103. package/dist/ir/function-builder.js +22 -0
  104. package/dist/ir/hal/hal-emitter.js +10 -6
  105. package/dist/ir/hal/hal-parser.js +16 -9
  106. package/dist/ir/hal/hal-plugins.js +164 -3
  107. package/dist/ir/identifier-collector.js +21 -4
  108. package/dist/ir/interrupt-analysis.d.ts +5 -1
  109. package/dist/ir/interrupt-analysis.js +8 -16
  110. package/dist/ir/ownership-analysis.js +6 -0
  111. package/dist/ir/pin-capability-validation.js +37 -0
  112. package/dist/ir/pin-mode-validation.d.ts +2 -2
  113. package/dist/ir/pin-mode-validation.js +49 -18
  114. package/dist/ir/program-analysis.d.ts +42 -0
  115. package/dist/ir/program-analysis.js +243 -6
  116. package/dist/ir/render-expr.js +11 -0
  117. package/dist/ir/timing-validation.js +11 -2
  118. package/dist/ir/transformers/call-statement.js +105 -0
  119. package/dist/ir/transformers/expressions.js +4 -0
  120. package/dist/ir/transformers/hal-call-resolver.js +21 -0
  121. package/dist/ir/transformers/namespace-methods.js +17 -12
  122. package/dist/ir/transformers/ui-reactive.js +2 -2
  123. package/dist/ir/transformers/variables.js +51 -0
  124. package/dist/ir/type-resolution.js +18 -0
  125. package/dist/ir/ui-element-auto-wire.js +7 -5
  126. package/dist/ir/utils/map-statements.d.ts +4 -0
  127. package/dist/ir/utils/map-statements.js +79 -0
  128. package/dist/ir/validation-orchestrator.js +6 -1
  129. package/dist/ir/worker-analysis.d.ts +10 -0
  130. package/dist/ir/worker-analysis.js +261 -0
  131. package/dist/orchestrator/graph-builder.js +8 -7
  132. package/dist/platform/coop-scheduler-runtime.d.ts +19 -0
  133. package/dist/platform/coop-scheduler-runtime.js +41 -0
  134. package/dist/platform/generic-strategy.d.ts +15 -3
  135. package/dist/platform/generic-strategy.js +49 -4
  136. package/dist/safety/safety-bridge.d.ts +11 -0
  137. package/dist/safety/safety-bridge.js +48 -0
  138. package/dist/safety/sidecar-bridge.d.ts +5 -0
  139. package/dist/safety/sidecar-bridge.js +22 -0
  140. package/dist/safety-hook.d.ts +79 -0
  141. package/dist/safety-hook.js +35 -0
  142. package/dist/stores/display-profile-store.d.ts +0 -1
  143. package/dist/stores/display-profile-store.js +0 -1
  144. package/dist/testing.d.ts +4 -0
  145. package/dist/testing.js +5 -0
  146. package/dist/transpile.js +124 -35
  147. package/dist/types.d.ts +13 -2
  148. package/dist/ui-hook.d.ts +0 -2
  149. package/dist/utils/cli.js +51 -3
  150. package/package.json +20 -5
  151. package/dist/api/shared/display-adapters/eink-mono.d.ts +0 -2
  152. package/dist/api/shared/display-adapters/eink-mono.js +0 -53
  153. package/dist/api/shared/display-adapters/ssd1309.d.ts +0 -2
  154. package/dist/api/shared/display-adapters/ssd1309.js +0 -136
  155. package/dist/api/shared/display-adapters/st7796.d.ts +0 -2
  156. package/dist/api/shared/display-adapters/st7796.js +0 -132
  157. package/dist/libdef/idf-discovery.d.ts +0 -7
  158. package/dist/libdef/idf-discovery.js +0 -59
  159. package/dist/licenses.d.ts +0 -185
  160. package/dist/licenses.js +0 -963
@@ -19,6 +19,26 @@ interface CuttlefishOutputConfig {
19
19
  /** Extra compiler flags forwarded verbatim. */
20
20
  extraFlags?: string[];
21
21
  }
22
+ /**
23
+ * Zephyr-specific configuration.
24
+ *
25
+ * When `framework` is `'@typecad/framework-zephyr'`, this section controls
26
+ * Kconfig symbols merged into prj.conf, extra CMake arguments forwarded to
27
+ * `west build`, and the flash runner override.
28
+ */
29
+ export interface CuttlefishZephyrConfig {
30
+ /**
31
+ * Additional Kconfig symbols merged into the generated prj.conf.
32
+ *
33
+ * Example:
34
+ * kconfig: { CONFIG_ESP32_USE_UNSUPPORTED_REVISION: 'y' }
35
+ */
36
+ kconfig?: Record<string, string>;
37
+ /** Extra arguments forwarded to `west build`. */
38
+ cmakeArgs?: string[];
39
+ /** Override the west flash runner (e.g. 'nrfjprog', 'jlink'). */
40
+ runner?: string;
41
+ }
22
42
  /**
23
43
  * Root configuration object exported from `cuttlefish.config.ts`.
24
44
  */
@@ -45,6 +65,13 @@ export interface CuttlefishConfig {
45
65
  * Framework package for code generation strategy.
46
66
  */
47
67
  framework?: string;
68
+ /**
69
+ * ESP32 PSRAM type. When set, the framework emits the PSRAM-enabling
70
+ * Kconfig (Zephyr) / define + FQBN option (Arduino) so large canvas
71
+ * allocations (scroll viewports, lists) prefer external RAM. No effect
72
+ * on boards without PSRAM.
73
+ */
74
+ psram?: 'opi' | 'quad';
48
75
  /** Output / build options. */
49
76
  output?: CuttlefishOutputConfig;
50
77
  /**
@@ -78,6 +105,11 @@ export interface CuttlefishConfig {
78
105
  * display: { profile: 'ili9341-spi', cs: 5, dc: 21, rst: 22 }
79
106
  */
80
107
  display?: DisplayConfig;
108
+ /**
109
+ * Zephyr-specific configuration. Only used when the framework is
110
+ * '@typecad/framework-zephyr'.
111
+ */
112
+ zephyr?: CuttlefishZephyrConfig;
81
113
  }
82
114
  /**
83
115
  * Toolchain configuration for compile and upload operations.
@@ -1,5 +1,5 @@
1
1
  export * from './shared/index.js';
2
2
  export type { ArchitectureIdentifier } from './board-types.js';
3
3
  export type { PinCapabilityFlags } from './capabilities.js';
4
- export type { CuttlefishConfig } from './config.js';
4
+ export type { CuttlefishConfig, CuttlefishZephyrConfig } from './config.js';
5
5
  export * from './schema/index.js';
@@ -252,6 +252,21 @@ export interface BoardDefinition {
252
252
  peripherals: PeripheralDefinitions;
253
253
  /** Build configuration (overrides/extends MCU build). */
254
254
  build: BuildConfig;
255
+ /**
256
+ * Optional @typecad/framework-zephyr chip data for this board.
257
+ *
258
+ * Opaque (`Record<string, unknown>`) here so the core schema does not import
259
+ * framework-zephyr's typed descriptor. framework-zephyr reads the flattened
260
+ * board constants under `zephyr.*` to reconstruct its `ZephyrChipDescriptor`
261
+ * — e.g. `zephyr.gpio.dtSpecs` maps GPIO pins to devicetree aliases (`led0`,
262
+ * `sw0`), so the GPIO lowering emits `gpio_pin_*_dt()` (polarity-correct via
263
+ * the DT's `GPIO_ACTIVE_LOW` flag) instead of the raw-controller fallback.
264
+ *
265
+ * Only literal object/array data is honored (the board-constants flattener
266
+ * walks it recursively); `as const` on nested values defeats the walker, so
267
+ * nested objects/arrays must be plain literals.
268
+ */
269
+ zephyr?: Record<string, unknown>;
255
270
  /** Related board variants. */
256
271
  variants?: string[];
257
272
  }
@@ -4,5 +4,16 @@
4
4
  * @param capacity Fixed number of timer/task slots. Keep small on constrained
5
5
  * targets (e.g. 8 on AVR). Caps the number of simultaneously pending
6
6
  * Async.sleep / sleepUntil / yield operations.
7
+ * @param waitForPinEdge How `__cuttlefish_wait_pin_edge` (the HAL gpio
8
+ * waitForRising/waitForFalling lowering target) is implemented:
9
+ * - "polling" (default): a busy-wait loop using digitalRead/delay/millis +
10
+ * the RISING/FALLING/HIGH/LOW symbols. For Arduino-style targets where
11
+ * those are part of the core API.
12
+ * - "stub": resolve immediately, emitting NO Arduino symbols in the body.
13
+ * Also defines RISING/FALLING (guarded) so the call site
14
+ * `__cuttlefish_wait_pin_edge(pin, RISING, t)` compiles on targets that
15
+ * don't define them (e.g. Zephyr). For testing/debug or targets where edge
16
+ * waits are not yet wired.
17
+ * - "interrupt": omit entirely — the strategy/ISR layer provides the symbol.
7
18
  */
8
- export declare function generateStaticAsyncRuntime(capacity: number): string;
19
+ export declare function generateStaticAsyncRuntime(capacity: number, waitForPinEdge?: "interrupt" | "polling" | "stub", strategy?: import("./platform-strategy.js").PlatformStrategy): string;
@@ -29,8 +29,23 @@
29
29
  * @param capacity Fixed number of timer/task slots. Keep small on constrained
30
30
  * targets (e.g. 8 on AVR). Caps the number of simultaneously pending
31
31
  * Async.sleep / sleepUntil / yield operations.
32
+ * @param waitForPinEdge How `__cuttlefish_wait_pin_edge` (the HAL gpio
33
+ * waitForRising/waitForFalling lowering target) is implemented:
34
+ * - "polling" (default): a busy-wait loop using digitalRead/delay/millis +
35
+ * the RISING/FALLING/HIGH/LOW symbols. For Arduino-style targets where
36
+ * those are part of the core API.
37
+ * - "stub": resolve immediately, emitting NO Arduino symbols in the body.
38
+ * Also defines RISING/FALLING (guarded) so the call site
39
+ * `__cuttlefish_wait_pin_edge(pin, RISING, t)` compiles on targets that
40
+ * don't define them (e.g. Zephyr). For testing/debug or targets where edge
41
+ * waits are not yet wired.
42
+ * - "interrupt": omit entirely — the strategy/ISR layer provides the symbol.
32
43
  */
33
- export function generateStaticAsyncRuntime(capacity) {
44
+ export function generateStaticAsyncRuntime(capacity, waitForPinEdge = "polling", strategy) {
45
+ // The current-time expression (millis() on Wiring-derived frameworks,
46
+ // std::chrono on generic). Falling back to millis() preserves the historical
47
+ // behavior when no strategy is supplied.
48
+ const now = strategy?.currentTimeMillis?.() ?? "millis()";
34
49
  return `
35
50
  // TypeCAD static (heap-free) async runtime — for targets without <vector>.
36
51
  namespace typecad_async_static {
@@ -67,7 +82,7 @@ public:
67
82
  if (!_timers[i].active) {
68
83
  _timers[i].active = true;
69
84
  _timers[i].repeat = false;
70
- _timers[i].deadline = millis() + ms;
85
+ _timers[i].deadline = ${now} + ms;
71
86
  _timers[i].periodMs = 0;
72
87
  return true;
73
88
  }
@@ -83,7 +98,7 @@ public:
83
98
  if (!_timers[i].active) {
84
99
  _timers[i].active = true;
85
100
  _timers[i].repeat = true;
86
- _timers[i].deadline = millis() + ms;
101
+ _timers[i].deadline = ${now} + ms;
87
102
  _timers[i].periodMs = ms;
88
103
  return true;
89
104
  }
@@ -109,12 +124,12 @@ public:
109
124
  // Advance the runtime: expire due timers and run ready tasks. Called from
110
125
  // cuttlefish_pump_microtasks(), which the transpiler injects into loop().
111
126
  void pump() {
112
- const unsigned long now = millis();
127
+ const unsigned long now = ${now};
113
128
  // Timers: one-shot slots deactivate on expiry; periodic slots re-arm.
114
129
  for (int i = 0; i < CAP; ++i) {
115
130
  if (_timers[i].active) {
116
131
  // (now - deadline) handles the millis() 32-bit wrap correctly
117
- if ((long)(now - _timers[i].deadline) >= 0) {
132
+ if (static_cast<long>(now - _timers[i].deadline) >= 0) {
118
133
  if (_timers[i].repeat) {
119
134
  _timers[i].deadline = now + _timers[i].periodMs;
120
135
  } else {
@@ -178,24 +193,54 @@ inline void cuttlefish_pump_microtasks() {
178
193
  typecad_async_static::StaticAsyncRuntime::instance().pump();
179
194
  }
180
195
 
181
- // HAL-level wait for pin edge polling-based implementation for static
182
- // (heap-free) targets. Blocks the current task until the pin edge is detected
183
- // or the timeout elapses. An edge is a transition: for RISING, the pin must
184
- // first be LOW then go HIGH; for FALLING, first HIGH then go LOW.
185
- inline void __cuttlefish_wait_pin_edge(int pin, int mode, long timeout) {
186
- int targetState = (mode == RISING) ? HIGH : LOW;
187
- int idleState = (mode == RISING) ? LOW : HIGH;
188
- unsigned long start = millis();
189
- // Phase 1: wait for the pin to be in the idle state (the "before" level)
190
- while (digitalRead(pin) != idleState) {
191
- if (timeout >= 0 && (millis() - start >= (unsigned long)timeout)) return;
192
- delay(1);
193
- }
194
- // Phase 2: wait for the transition to the target state (the actual edge)
195
- while (digitalRead(pin) != targetState) {
196
- if (timeout >= 0 && (millis() - start >= (unsigned long)timeout)) return;
197
- delay(1);
198
- }
199
- }
196
+ // HAL-level wait for pin edge. The body depends on the strategy's
197
+ // waitForPinEdge mode see generateStaticAsyncRuntime()'s doc comment.
198
+ ${waitPinEdgeForMode(waitForPinEdge, strategy, now)}
200
199
  `;
201
200
  }
201
+ /** Emit `__cuttlefish_wait_pin_edge` per the strategy's waitForPinEdge mode. */
202
+ function waitPinEdgeForMode(mode, strategy, now = "millis()") {
203
+ if (mode === "interrupt") {
204
+ // The strategy/ISR layer provides the symbol; emit nothing here.
205
+ return "// __cuttlefish_wait_pin_edge is provided by the strategy (interrupt mode).";
206
+ }
207
+ if (mode === "stub") {
208
+ // Resolve immediately. Define RISING/FALLING (guarded) so the call site
209
+ // `__cuttlefish_wait_pin_edge(pin, RISING, t)` compiles on targets that
210
+ // don't define them (Zephyr). No digitalRead/delay/millis in the body.
211
+ return [
212
+ "#ifndef RISING",
213
+ "#define RISING 1",
214
+ "#endif",
215
+ "#ifndef FALLING",
216
+ "#define FALLING 2",
217
+ "#endif",
218
+ "// Stub: edge waits resolve immediately (waitForPinEdge=\"stub\").",
219
+ "inline void __cuttlefish_wait_pin_edge(int /*pin*/, int /*mode*/, long /*timeout*/) {",
220
+ "}",
221
+ ].join("\n");
222
+ }
223
+ // polling: busy-wait using Arduino-style digitalRead/delay/millis + RISING/
224
+ // FALLING/HIGH/LOW. For targets whose core API defines those (Arduino).
225
+ return [
226
+ "// HAL-level wait for pin edge — polling-based implementation for static",
227
+ "// (heap-free) targets. Blocks the current task until the pin edge is detected",
228
+ "// or the timeout elapses. An edge is a transition: for RISING, the pin must",
229
+ "// first be LOW then go HIGH; for FALLING, first HIGH then go LOW.",
230
+ "inline void __cuttlefish_wait_pin_edge(int pin, int mode, long timeout) {",
231
+ " int targetState = (mode == RISING) ? HIGH : LOW;",
232
+ " int idleState = (mode == RISING) ? LOW : HIGH;",
233
+ ` unsigned long start = ${now};`,
234
+ " // Phase 1: wait for the pin to be in the idle state (the \"before\" level)",
235
+ ` while (${strategy?.readDigitalPin?.("pin") ?? "digitalRead(pin)"} != idleState) {`,
236
+ ` if (timeout >= 0 && (${now} - start >= static_cast<unsigned long>(timeout))) return;`,
237
+ ` ${strategy?.delayMs?.("1") ?? "delay(1)"};`,
238
+ " }",
239
+ " // Phase 2: wait for the transition to the target state (the actual edge)",
240
+ ` while (${strategy?.readDigitalPin?.("pin") ?? "digitalRead(pin)"} != targetState) {`,
241
+ ` if (timeout >= 0 && (${now} - start >= static_cast<unsigned long>(timeout))) return;`,
242
+ ` ${strategy?.delayMs?.("1") ?? "delay(1)"};`,
243
+ " }",
244
+ "}",
245
+ ].join("\n");
246
+ }
@@ -0,0 +1,11 @@
1
+ import type { ProgramIR } from "../index.js";
2
+ /**
3
+ * Walk a program's IR and return true if ANY statement or expression references
4
+ * a cooperative-runtime symbol. Short-circuits on the first hit.
5
+ *
6
+ * Inspects the same node shapes the duplicated framework-strategy walkers did:
7
+ * `raw` expressions, `hal-expr`/`hal-op` nodes whose operation carries raw
8
+ * `code`, and any `raw`-kind subtree. Covers both the statement and expression
9
+ * IR trees.
10
+ */
11
+ export declare function programUsesAsyncRuntimeSymbols(program: ProgramIR): boolean;
@@ -0,0 +1,140 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Shared async-runtime symbol detector
3
+ //
4
+ // Returns true when a program actually references a symbol provided by the
5
+ // cooperative Promise/microtask runtime (MicrotaskQueue, Promise<T>,
6
+ // enqueueMicrotask, cuttlefish_pump_microtasks, the __cuttlefish_async_*
7
+ // HAL helpers, __cuttlefish_wait_pin_edge). This is the precise gate for
8
+ // emitting the `async_runtime` polyfill.
9
+ //
10
+ // It is intentionally NARROWER than "does the program have an async function?"
11
+ // An `async function` whose body only does `await delay(ms)` lowers to a
12
+ // pure millis()-polling state machine (see emit/utils/async-state-machine.ts)
13
+ // that touches NONE of these symbols — so it needs the AsyncTaskClass (gated
14
+ // separately on fn.isAsync) but NOT the Promise runtime. Conflating the two
15
+ // pulls ~250 lines of dead Promise/microtask code into every such sketch.
16
+ //
17
+ // Both framework strategies (Arduino, Zephyr) previously carried a private
18
+ // copy of this walk; this shared version de-duplicates them and is also used
19
+ // centrally by emit/emitters/setup.ts to filter the polyfill id.
20
+ // ---------------------------------------------------------------------------
21
+ /** Tokens that only exist in the cooperative Promise/microtask runtime. */
22
+ const ASYNC_RUNTIME_TOKENS = [
23
+ "__cuttlefish_async_", // Async.sleep / Async.yield / Async.sleepUntil / Async.currentTask
24
+ "__cuttlefish_wait_pin_edge", // HAL edge-wait helper (polling-based Promise impl)
25
+ "enqueueMicrotask", // MicrotaskQueue::enqueue alias
26
+ "cuttlefish_pump_microtasks", // loop() injection target
27
+ "typecad_async::", // direct namespace qualification
28
+ "Promise<", // Promise<T> / Promise<void> instantiation
29
+ ];
30
+ /** True if `text` references any async-runtime symbol. */
31
+ function referencesAsyncSymbol(text) {
32
+ for (const tok of ASYNC_RUNTIME_TOKENS) {
33
+ if (text.includes(tok))
34
+ return true;
35
+ }
36
+ return false;
37
+ }
38
+ /**
39
+ * Walk a program's IR and return true if ANY statement or expression references
40
+ * a cooperative-runtime symbol. Short-circuits on the first hit.
41
+ *
42
+ * Inspects the same node shapes the duplicated framework-strategy walkers did:
43
+ * `raw` expressions, `hal-expr`/`hal-op` nodes whose operation carries raw
44
+ * `code`, and any `raw`-kind subtree. Covers both the statement and expression
45
+ * IR trees.
46
+ */
47
+ export function programUsesAsyncRuntimeSymbols(program) {
48
+ const FOUND = {};
49
+ const seen = new Set();
50
+ const walkExpr = (expr) => {
51
+ if (!expr || typeof expr !== "object")
52
+ return;
53
+ if (seen.has(expr))
54
+ return;
55
+ seen.add(expr);
56
+ const e = expr;
57
+ if (e.kind === "raw" && typeof e.value === "string" && referencesAsyncSymbol(e.value)) {
58
+ throw FOUND;
59
+ }
60
+ // HAL method calls that lower via rawCpp() become `hal-expr` nodes whose
61
+ // `operation` is { operation: "raw", code: "..." }. The Async singleton's
62
+ // methods (sleep/yield/sleepUntil/currentTask) land here.
63
+ if (e.kind === "hal-expr" && e.operation && typeof e.operation === "object") {
64
+ const op = e.operation;
65
+ if (op.operation === "raw" && typeof op.code === "string" && referencesAsyncSymbol(op.code)) {
66
+ throw FOUND;
67
+ }
68
+ }
69
+ for (const v of Object.values(e)) {
70
+ if (Array.isArray(v)) {
71
+ for (const item of v) {
72
+ if (item && typeof item === "object") {
73
+ if ("kind" in item && typeof item.kind === "string") {
74
+ walkExpr(item);
75
+ }
76
+ else {
77
+ walkStmt(item);
78
+ }
79
+ }
80
+ }
81
+ }
82
+ else if (v && typeof v === "object" && "kind" in v && typeof v.kind === "string") {
83
+ walkExpr(v);
84
+ }
85
+ }
86
+ };
87
+ const walkStmt = (stmt) => {
88
+ if (!stmt || typeof stmt !== "object")
89
+ return;
90
+ if (seen.has(stmt))
91
+ return;
92
+ seen.add(stmt);
93
+ const s = stmt;
94
+ // HAL method calls that lower via rawCpp() become `hal-op` statements whose
95
+ // `operation` is { operation: "raw", code: "..." }. The Async singleton's
96
+ // methods land here at statement level.
97
+ if (s.kind === "hal-op" && s.operation && typeof s.operation === "object") {
98
+ const op = s.operation;
99
+ if (op.operation === "raw" && typeof op.code === "string" && referencesAsyncSymbol(op.code)) {
100
+ throw FOUND;
101
+ }
102
+ }
103
+ for (const v of Object.values(s)) {
104
+ if (Array.isArray(v)) {
105
+ for (const item of v) {
106
+ if (item && typeof item === "object") {
107
+ if ("body" in item || "statements" in item || "thenBranch" in item || "cases" in item) {
108
+ walkStmt(item);
109
+ }
110
+ else if ("kind" in item && typeof item.kind === "string") {
111
+ walkExpr(item);
112
+ }
113
+ }
114
+ }
115
+ }
116
+ else if (v && typeof v === "object" && "kind" in v && typeof v.kind === "string") {
117
+ walkExpr(v);
118
+ }
119
+ else if (v && typeof v === "object") {
120
+ walkStmt(v);
121
+ }
122
+ }
123
+ };
124
+ try {
125
+ for (const fn of program.functions) {
126
+ for (const stmt of fn.statements)
127
+ walkStmt(stmt);
128
+ }
129
+ // Top-level statements (outside any function) — e.g. a bare Async.sleep()
130
+ // called from setup-level code.
131
+ for (const stmt of program.topLevelStatements)
132
+ walkStmt(stmt);
133
+ }
134
+ catch (e) {
135
+ if (e === FOUND)
136
+ return true;
137
+ throw e;
138
+ }
139
+ return false;
140
+ }
@@ -39,4 +39,28 @@ export interface AsyncRuntimeConfig {
39
39
  * e.g. ["<functional>", "<vector>", "<utility>", "<string>"]
40
40
  */
41
41
  requiredIncludes: string[];
42
+ /**
43
+ * Enable priority-ordered dispatch of per-frame work units.
44
+ * Default false → legacy flat pump sequence. When true, higher-priority
45
+ * units drain before lower-priority ones each frame.
46
+ */
47
+ enablePriority?: boolean;
48
+ /**
49
+ * Enable mid-batch yielding when the per-frame time budget is exceeded.
50
+ * Default false. When true, CoopSched::run() breaks out of its dispatch
51
+ * loop once `timeBudgetMs` has elapsed, leaving remaining units for the
52
+ * next frame. This bounds worst-case latency for background RTOS work and
53
+ * prevents a single greedy unit from starving others.
54
+ */
55
+ enableTimeBudget?: boolean;
56
+ /**
57
+ * Per-frame wall-clock budget in milliseconds. Only consulted when
58
+ * `enableTimeBudget` is true. Default 5.
59
+ */
60
+ timeBudgetMs?: number;
61
+ /**
62
+ * Number of distinct priority levels CoopSched should model. Default 2
63
+ * (high/low). Only consulted when `enablePriority` is true.
64
+ */
65
+ schedulerPriorities?: number;
42
66
  }
@@ -0,0 +1,60 @@
1
+ /** A per-frame work unit to be driven by CoopSched. */
2
+ export interface CoopWorkUnit {
3
+ /**
4
+ * Stable identifier for the trampoline (used to form the C++ trampoline and
5
+ * registration function names). Must be a valid C++ identifier suffix.
6
+ */
7
+ name: string;
8
+ /** C++ statement to execute when this unit runs, e.g. `fooTask.run();`. */
9
+ body: string;
10
+ /** Dispatch priority (higher runs first). Default 0. */
11
+ priority?: number;
12
+ }
13
+ export interface CoopSchedOptions {
14
+ /** Maximum number of registered work units. Default 8. */
15
+ capacity?: number;
16
+ /** Number of distinct priority levels. Default 2. */
17
+ priorities?: number;
18
+ /** Whether to break mid-batch once timeBudgetMs elapses. Default false. */
19
+ enableTimeBudget?: boolean;
20
+ /** Per-frame wall-clock budget in ms. Only consulted when budgeting. Default 5. */
21
+ timeBudgetMs?: number;
22
+ /**
23
+ * C++ expression yielding the current monotonic time in ms, used for the
24
+ * budget check (e.g. `millis()`, `k_uptime_get_32()`). Required only when
25
+ * enableTimeBudget is true.
26
+ */
27
+ currentTimeExpr?: string;
28
+ }
29
+ /**
30
+ * Generate the CoopSched C++ runtime (a self-contained namespace + global
31
+ * `CoopSched::run()` alias). The scheduler is independent of which work units
32
+ * are registered; units are registered per-program via buildCoopSchedInjection().
33
+ *
34
+ * AUTOSAR C++14 notes:
35
+ * - Uses int32_t/uint32_t/int8_t (no bare int).
36
+ * - No STL: function-pointer + void* trampolines, static fixed-size arrays.
37
+ * - `final` on the scheduler class (nothing inherits it).
38
+ * - static_cast for all primitive conversions; reinterpret_cast for the
39
+ * void* <-> trampoline boundary is unnecessary (we pass ctx through verbatim).
40
+ */
41
+ export declare function generateCoopScheduler(options?: CoopSchedOptions): string;
42
+ /**
43
+ * Build the loop()/main() injection lines that register the given work units
44
+ * into CoopSched (once, guarded) and dispatch one frame. The returned lines
45
+ * are meant to be emitted verbatim by a strategy's asyncLoopInjection().
46
+ *
47
+ * Each work unit gets a static trampoline `__tc_coop_<name>(void*)` that runs
48
+ * its body; registration is idempotent (guarded by CoopSched::isRegistered())
49
+ * so the same loop() body can safely contain it every frame at zero cost
50
+ * after the first.
51
+ *
52
+ * Trampoline definitions are returned as the first element of the tuple so the
53
+ * caller can place them in the file's shim/declarations region (they must
54
+ * appear before the loop body). The per-frame injection lines are the second
55
+ * element.
56
+ */
57
+ export declare function buildCoopSchedInjection(units: CoopWorkUnit[]): {
58
+ trampolines: string[];
59
+ injection: string[];
60
+ };
@@ -0,0 +1,149 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Shared cooperative scheduler C++ code generator (no-STL)
3
+ //
4
+ // Generates a priority-ordered, time-budget-bounded cooperative scheduler that
5
+ // drives the per-frame async pumps (async task .run(), microtask pump, timer
6
+ // pump) in loop()/main(). It is deliberately STL-free: function-pointer +
7
+ // void* trampolines over a statically-allocated fixed slot table. This lets it
8
+ // compile on targets whose minimal libc lacks std::function/std::vector (e.g.
9
+ // Zephyr), giving those targets full cooperative-scheduling parity with the
10
+ // std::function-based MicrotaskQueue used on Arduino/ESP32/native.
11
+ //
12
+ // The scheduler is emitted as a polyfill (id: 'coop_scheduler') only when a
13
+ // strategy opts in via AsyncRuntimeConfig.enablePriority/enableTimeBudget.
14
+ // Per-framework asyncLoopInjection() builds the work-unit registrations and
15
+ // the single CoopSched::run() call (see buildCoopSchedInjection()).
16
+ // ---------------------------------------------------------------------------
17
+ /**
18
+ * Generate the CoopSched C++ runtime (a self-contained namespace + global
19
+ * `CoopSched::run()` alias). The scheduler is independent of which work units
20
+ * are registered; units are registered per-program via buildCoopSchedInjection().
21
+ *
22
+ * AUTOSAR C++14 notes:
23
+ * - Uses int32_t/uint32_t/int8_t (no bare int).
24
+ * - No STL: function-pointer + void* trampolines, static fixed-size arrays.
25
+ * - `final` on the scheduler class (nothing inherits it).
26
+ * - static_cast for all primitive conversions; reinterpret_cast for the
27
+ * void* <-> trampoline boundary is unnecessary (we pass ctx through verbatim).
28
+ */
29
+ export function generateCoopScheduler(options = {}) {
30
+ const capacity = options.capacity ?? 8;
31
+ const priorities = options.priorities ?? 2;
32
+ const enableTimeBudget = options.enableTimeBudget ?? false;
33
+ const timeBudgetMs = options.timeBudgetMs ?? 5;
34
+ const currentTimeExpr = options.currentTimeExpr ?? "millis()";
35
+ // Budget check snippet, only included when time-budgeting is enabled.
36
+ const budgetCheck = enableTimeBudget
37
+ ? ` if ((${currentTimeExpr} - _start) >= static_cast<uint32_t>(${timeBudgetMs})) { break; }`
38
+ : ` (void)_start;`;
39
+ return `// ── Cooperative scheduler (no-STL: priority-ordered, budget-bounded) ──────
40
+ // Drives the per-frame async pumps. Emitted only when a strategy opts in via
41
+ // AsyncRuntimeConfig.enablePriority / enableTimeBudget. STL-free so it links
42
+ // on minimal-libc targets (Zephyr). See coop-scheduler.ts.
43
+ namespace typecad_coop {
44
+ using WorkFn = void (*)(void*);
45
+
46
+ struct WorkUnit final {
47
+ int8_t priority; // higher runs first
48
+ WorkFn fn;
49
+ void* ctx;
50
+ };
51
+
52
+ class CoopSched final {
53
+ public:
54
+ static CoopSched& instance() {
55
+ static CoopSched s;
56
+ return s;
57
+ }
58
+
59
+ // Register a work unit. Called once per unit (idempotent registration is
60
+ // driven by begin()/runFirst() below). Returns false if the slot table is
61
+ // full.
62
+ bool registerUnit(int8_t priority, WorkFn fn, void* ctx) {
63
+ if (_count >= static_cast<int32_t>(${capacity})) { return false; }
64
+ _slots[_count].priority = priority;
65
+ _slots[_count].fn = fn;
66
+ _slots[_count].ctx = ctx;
67
+ ++_count;
68
+ return true;
69
+ }
70
+
71
+ bool isRegistered() const { return _registered; }
72
+ void markRegistered() { _registered = true; }
73
+
74
+ // Dispatch one frame. Runs registered units in descending-priority order.
75
+ // Newly-registered units during dispatch are NOT run this frame (the count
76
+ // is snapshotted at entry) — mirroring the microtask pump's anti-recursion
77
+ // semantics. When time-budgeting is enabled, dispatch breaks early once
78
+ // the per-frame budget elapses; remaining units run next frame.
79
+ void run() {
80
+ const int32_t total = _count;
81
+ const uint32_t _start = ${currentTimeExpr};
82
+ for (int8_t level = static_cast<int8_t>(${priorities - 1}); level >= 0; --level) {
83
+ for (int32_t i = 0; i < total; ++i) {
84
+ if (_slots[i].priority == level && _slots[i].fn != nullptr) {
85
+ _slots[i].fn(_slots[i].ctx);
86
+ ${budgetCheck}
87
+ }
88
+ }
89
+ }
90
+ }
91
+
92
+ private:
93
+ CoopSched() : _count(0), _registered(false) {
94
+ for (int32_t i = 0; i < static_cast<int32_t>(${capacity}); ++i) {
95
+ _slots[i].priority = 0;
96
+ _slots[i].fn = nullptr;
97
+ _slots[i].ctx = nullptr;
98
+ }
99
+ }
100
+
101
+ WorkUnit _slots[${capacity}];
102
+ int32_t _count;
103
+ bool _registered;
104
+ };
105
+
106
+ inline void run() { CoopSched::instance().run(); }
107
+ } // namespace typecad_coop
108
+
109
+ // Global-scope alias so HAL/loop() emit resolves.
110
+ inline void CoopSched_run() { typecad_coop::run(); }
111
+ `;
112
+ }
113
+ /**
114
+ * Build the loop()/main() injection lines that register the given work units
115
+ * into CoopSched (once, guarded) and dispatch one frame. The returned lines
116
+ * are meant to be emitted verbatim by a strategy's asyncLoopInjection().
117
+ *
118
+ * Each work unit gets a static trampoline `__tc_coop_<name>(void*)` that runs
119
+ * its body; registration is idempotent (guarded by CoopSched::isRegistered())
120
+ * so the same loop() body can safely contain it every frame at zero cost
121
+ * after the first.
122
+ *
123
+ * Trampoline definitions are returned as the first element of the tuple so the
124
+ * caller can place them in the file's shim/declarations region (they must
125
+ * appear before the loop body). The per-frame injection lines are the second
126
+ * element.
127
+ */
128
+ export function buildCoopSchedInjection(units) {
129
+ const trampolines = [];
130
+ const injection = [];
131
+ for (const unit of units) {
132
+ const prio = unit.priority ?? 0;
133
+ const trampName = `__tc_coop_${unit.name}`;
134
+ // Trampoline definition (file scope, before loop()).
135
+ trampolines.push(`static void ${trampName}(void* /*ctx*/) { ${unit.body} }`);
136
+ }
137
+ // Idempotent registration guarded by isRegistered(); then one dispatch.
138
+ // Emitted into the loop body.
139
+ injection.push(` if (!typecad_coop::CoopSched::instance().isRegistered()) {`);
140
+ for (const unit of units) {
141
+ const prio = unit.priority ?? 0;
142
+ const trampName = `__tc_coop_${unit.name}`;
143
+ injection.push(` typecad_coop::CoopSched::instance().registerUnit(static_cast<int8_t>(${prio}), ${trampName}, nullptr);`);
144
+ }
145
+ injection.push(` typecad_coop::CoopSched::instance().markRegistered();`);
146
+ injection.push(` }`);
147
+ injection.push(` CoopSched_run();`);
148
+ return { trampolines, injection };
149
+ }
@@ -1,9 +1,9 @@
1
1
  import type { ResolvedDisplay } from "./display-profile.js";
2
2
  import type { PlatformStrategy } from "./platform-strategy.js";
3
3
  export interface DisplayAdapterCode {
4
- /** C++ #include lines (e.g. "#include <Adafruit_ILI9341.h>"). */
4
+ /** C++ #include lines (e.g. "#include <SDL2/SDL.h>"). */
5
5
  includes: string;
6
- /** C++ display object declaration (e.g. "Adafruit_ILI9341 __tc_display = ..."). */
6
+ /** C++ display object declaration (e.g. the framework-specific display object). */
7
7
  declaration: string;
8
8
  /** C++ static inline adapter functions (display_init, display_fillScreen, etc.). */
9
9
  functions: string;