@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
@@ -108,8 +108,13 @@ export interface PlatformTypeStrategy {
108
108
  * when the enum members map directly to platform preprocessor macros.
109
109
  */
110
110
  passthroughEnumNames?(): ReadonlySet<string>;
111
- /** Default numeric type for the platform (e.g. "int" on hosted, "int32_t" on embedded). */
112
- defaultNumericType(): string;
111
+ /**
112
+ * Default numeric type for the platform (e.g. "int" on hosted, "int32_t" on embedded).
113
+ * Optional compliance context: when A3-9-1 is enforced, returns a fixed-width type.
114
+ */
115
+ defaultNumericType(compliance?: {
116
+ isBanned(ruleId: string): boolean;
117
+ }): string;
113
118
  /**
114
119
  * Inform the strategy which enums have values exceeding the signed 16-bit range.
115
120
  * The strategy can use this to choose an appropriate underlying type.
@@ -307,12 +312,32 @@ export interface PlatformBuildStrategy {
307
312
  /** Standard library support for the given architecture. */
308
313
  getStdLibSupport(architecture?: string): StdLibSupport;
309
314
  }
315
+ /**
316
+ * Coarse C++ type category carried per debug variable, so printf-based codegens
317
+ * (ESP-IDF) can pick the right format specifier. Inferred by the debug
318
+ * preprocessor from AST shape (no TypeChecker); `unknown` is the fallback.
319
+ * Mirrors `DebugCppType` in cuttlefish/src/debug/types.ts — duplicated here to
320
+ * avoid a cross-module import from the api surface.
321
+ */
322
+ export type DebugCppType = 'bool' | 'int' | 'long' | 'float' | 'string' | 'unknown';
310
323
  /**
311
324
  * Debug code generation sub-interface.
312
325
  * Frameworks implement these to provide platform-specific debug output
313
326
  * (e.g., Serial.println on embedded, std::cout on hosted targets).
314
327
  */
315
328
  export interface PlatformDebugStrategy {
329
+ /**
330
+ * Debug mode for the given build target. Controls whether `--debug` emits
331
+ * GDB-oriented output (`#line` directives + debug config artifacts, value
332
+ * `'gdb'`) or the legacy printf/Serial instrumentation (`'printf'`).
333
+ *
334
+ * Frameworks that support native source-level debugging for a chip return
335
+ * `'gdb'` for that target; everything else inherits the `'printf'` default
336
+ * and the existing instrumentation path is used unchanged.
337
+ *
338
+ * @param target the frameworkData.buildTarget value (e.g. 'esp32s3')
339
+ */
340
+ debugMode?(target?: string): 'gdb' | 'printf';
316
341
  /** Generate initialization code for the debug subsystem. */
317
342
  generateDebugInitCode?(): string[];
318
343
  /** Generate code for a breakpoint with optional condition and variable dump. */
@@ -323,8 +348,16 @@ export interface PlatformDebugStrategy {
323
348
  variables: Array<{
324
349
  name: string;
325
350
  isFunction?: boolean;
351
+ cppType?: DebugCppType;
326
352
  }>;
327
353
  normalizedCondition?: string;
354
+ /**
355
+ * Stable per-file ID assigned by the preprocessor. When present, the
356
+ * codegen emits a `static bool __tc_bp_disabled_<id>` flag and wraps the
357
+ * halt in `if (!__tc_bp_disabled_<id>)`, so the user can skip/disable this
358
+ * one breakpoint for the rest of the run (the 's' key sets the flag).
359
+ */
360
+ breakpointId?: number;
328
361
  }): string[];
329
362
  /** Generate code for a logpoint with interpolated message parts. */
330
363
  generateDebugLogpointCode?(params: {
@@ -337,6 +370,7 @@ export interface PlatformDebugStrategy {
337
370
  variables: Array<{
338
371
  name: string;
339
372
  isFunction?: boolean;
373
+ cppType?: DebugCppType;
340
374
  }>;
341
375
  }): string[];
342
376
  }
@@ -371,6 +405,27 @@ export interface PlatformAsyncStrategy {
371
405
  preIteration: string;
372
406
  postIteration: string;
373
407
  } | null;
408
+ /**
409
+ * C++ statements that spawn the worker for slot `handleId`, running
410
+ * `fn(arg)`. Emitted as the body of __tc_worker_submit. Return undefined
411
+ * to declare worker offload unsupported on this framework.
412
+ *
413
+ * The shared runtime has already recorded `waiter`/set `done=false` before
414
+ * this runs; the hook need only spawn. `fn` and `arg` are C++ expressions
415
+ * (a function pointer and a void*).
416
+ */
417
+ workerSpawnLines?(handleId: number, fn: string, arg: string): string[] | undefined;
418
+ /**
419
+ * C++ expression the worker calls AFTER completing its work and writing its
420
+ * outputs, to signal completion to the waiter. Emitted inside the worker
421
+ * trampoline. Must issue a memory barrier. Return undefined if unsupported.
422
+ */
423
+ workerSignalDoneExpr?(handleId: number): string | undefined;
424
+ /**
425
+ * C++ boolean expression polled by __tc_worker_done to test completion.
426
+ * Must observe the signal's memory barrier. Return undefined if unsupported.
427
+ */
428
+ workerIsDoneExpr?(handleId: number): string | undefined;
374
429
  }
375
430
  /**
376
431
  * Strategy for resolving semantic HAL operations to framework-specific C++.
@@ -403,6 +458,53 @@ export interface PlatformHALStrategy {
403
458
  * Default true (preserves existing behavior for hardware strategies).
404
459
  */
405
460
  modelsGpio?(): boolean;
461
+ /**
462
+ * C++ expression that reads the digital level of a pin (HIGH/LOW).
463
+ * ArduinoStrategy returns `digitalRead(${pin})`. GenericStrategy returns a
464
+ * documented no-op stub (no GPIO on host).
465
+ */
466
+ readDigitalPin?(pin: string): string;
467
+ /**
468
+ * C++ expression that reads a raw analog value from an ADC pin.
469
+ * ArduinoStrategy returns `analogRead(${pin})`. GenericStrategy stubs.
470
+ */
471
+ readAnalogPin?(pin: string): string;
472
+ /**
473
+ * C++ statement that writes a digital level to a pin.
474
+ * ArduinoStrategy returns `digitalWrite(${pin}, ${val})`. Cuttlefish does not
475
+ * call this in the current emit path (display reset moves to the framework),
476
+ * but it is part of the atomic surface for completeness/future use.
477
+ */
478
+ writeDigitalPin?(pin: string, val: string): string;
479
+ /**
480
+ * C++ statement that configures a pin's mode.
481
+ * ArduinoStrategy returns `pinMode(${pin}, ${mode})`.
482
+ */
483
+ setPinMode?(pin: string, mode: string): string;
484
+ /**
485
+ * C++ statement that blocks for a number of milliseconds.
486
+ * ArduinoStrategy returns `delay(${ms})`.
487
+ */
488
+ delayMs?(ms: string): string;
489
+ /**
490
+ * C++ statement that blocks for a number of microseconds.
491
+ * ArduinoStrategy returns `delayMicroseconds(${us})`.
492
+ */
493
+ delayMicroseconds?(us: string): string;
494
+ /**
495
+ * The full set of HAL call names this framework's emitted code may contain
496
+ * (digitalRead, analogRead, Serial, tone, millis, …). Used to recognize HAL
497
+ * calls in rendered C++ text where no structured HAL-IR node is available.
498
+ */
499
+ halCallNames?(): ReadonlySet<string>;
500
+ /** Membership test against {@link halCallNames}. Default false on generic. */
501
+ isHalCall?(name: string): boolean;
502
+ /**
503
+ * The subset of {@link halCallNames} that return an analog-read integer
504
+ * value (e.g. analogRead). Used by adc-range-validation to detect ADC reads
505
+ * in lowered raw text.
506
+ */
507
+ analogReadCallNames?(): ReadonlySet<string>;
406
508
  }
407
509
  /**
408
510
  * Full platform strategy composed from focused sub-interfaces.
@@ -62,6 +62,22 @@ export const POLYFILL_HELPER_MAP = {
62
62
  // JSON helpers
63
63
  '__tc_jsonStringify(': ['__tc_jsonStringify'],
64
64
  '__tc_jsonParse(': ['__tc_jsonParse'],
65
+ // Safety helpers — emitted by @typecad/safety's polyfills as global-scope
66
+ // __tc_safety_* free functions (the polyfill-helper-registry extractor
67
+ // expects __tc_* immediately followed by `(`, so namespaced names would be
68
+ // missed). The call-pattern keys are the substrings program analysis scans
69
+ // source for; the values are the __tc_safety_* names that
70
+ // filterPolyfillHelpers matches in helperFunctions text.
71
+ //
72
+ // Two key forms per helper: the user-facing safe.* source spelling (which
73
+ // program analysis sees in raw IR before lowering) and the lowered
74
+ // __tc_safety_* spelling (which appears after the safety call-statement
75
+ // transformer emits hal-op resolution text). Both map to the same helper.
76
+ 'safe.read(': ['__tc_safety_read_safe'],
77
+ 'safe.write(': ['__tc_safety_write_verify'],
78
+ '__tc_safety_record_pin_mode(': ['__tc_safety_record_pin_mode'],
79
+ '__tc_safety_read_safe(': ['__tc_safety_read_safe'],
80
+ '__tc_safety_write_verify(': ['__tc_safety_write_verify'],
65
81
  };
66
82
  function extractHelperFunctionNames(funcDef) {
67
83
  const matches = funcDef.matchAll(/\b(__tc_[A-Za-z0-9_]+)\s*\(/g);
@@ -82,5 +98,6 @@ export function filterPolyfillHelpers(polyfills, usedHelpers) {
82
98
  }),
83
99
  })).filter(polyfill => polyfill.helperFunctions.length > 0 ||
84
100
  polyfill.forwardDeclarations.length > 0 ||
85
- polyfill.helperStructs.length > 0);
101
+ polyfill.helperStructs.length > 0 ||
102
+ polyfill.shimMacros.length > 0);
86
103
  }
@@ -7,4 +7,4 @@
7
7
  * @param includeWaitForPinEdge Whether to include the `waitForPinEdge` stub
8
8
  * (used by Arduino targets).
9
9
  */
10
- export declare function generatePromiseRuntime(queueCapacity: number, includeWaitForPinEdge?: boolean): string;
10
+ export declare function generatePromiseRuntime(queueCapacity: number, includeWaitForPinEdge?: boolean, strategy?: import("./platform-strategy.js").PlatformStrategy): string;
@@ -20,7 +20,11 @@
20
20
  * @param includeWaitForPinEdge Whether to include the `waitForPinEdge` stub
21
21
  * (used by Arduino targets).
22
22
  */
23
- export function generatePromiseRuntime(queueCapacity, includeWaitForPinEdge = false) {
23
+ export function generatePromiseRuntime(queueCapacity, includeWaitForPinEdge = false, strategy) {
24
+ // The current-time expression (millis() on Wiring-derived frameworks,
25
+ // std::chrono on generic). Falling back to millis() preserves the historical
26
+ // behavior when no strategy is supplied.
27
+ const now = strategy?.currentTimeMillis?.() ?? "millis()";
24
28
  const waitForPinEdge = includeWaitForPinEdge ? `
25
29
  // HAL-level wait for pin edge — polling-based implementation.
26
30
  // Detects an actual transition (idle→target), not just the current level.
@@ -31,18 +35,18 @@ export function generatePromiseRuntime(queueCapacity, includeWaitForPinEdge = fa
31
35
  return Promise<void>([pin, mode, timeout](std::function<void(const void*)> resolve, std::function<void(const std::string&)> reject) {
32
36
  int targetState = (mode == RISING) ? HIGH : LOW;
33
37
  int idleState = (mode == RISING) ? LOW : HIGH;
34
- unsigned long start = millis();
38
+ unsigned long start = ${now};
35
39
  // Phase 2 poller: waits for the pin to reach the target state (the edge).
36
40
  auto pollTarget = [pin, targetState, timeout, start, resolve]() {
37
- if (digitalRead(pin) == targetState) {
41
+ if (${strategy?.readDigitalPin?.("pin") ?? "digitalRead(pin)"} == targetState) {
38
42
  resolve(nullptr);
39
- } else if (timeout >= 0 && (millis() - start >= (unsigned long)timeout)) {
43
+ } else if (timeout >= 0 && (${now} - start >= static_cast<unsigned long>(timeout))) {
40
44
  resolve(nullptr);
41
45
  } else {
42
46
  enqueueMicrotask([pin, targetState, timeout, start, resolve]() {
43
- if (digitalRead(pin) == targetState) {
47
+ if (${strategy?.readDigitalPin?.("pin") ?? "digitalRead(pin)"} == targetState) {
44
48
  resolve(nullptr);
45
- } else if (timeout >= 0 && (millis() - start >= (unsigned long)timeout)) {
49
+ } else if (timeout >= 0 && (${now} - start >= static_cast<unsigned long>(timeout))) {
46
50
  resolve(nullptr);
47
51
  } else {
48
52
  enqueueMicrotask([pin, targetState, timeout, start, resolve]() {});
@@ -52,9 +56,9 @@ export function generatePromiseRuntime(queueCapacity, includeWaitForPinEdge = fa
52
56
  };
53
57
  // Phase 1: wait for idle state before watching for the edge.
54
58
  enqueueMicrotask([pin, idleState, timeout, start, resolve, pollTarget]() {
55
- if (digitalRead(pin) == idleState) {
59
+ if (${strategy?.readDigitalPin?.("pin") ?? "digitalRead(pin)"} == idleState) {
56
60
  pollTarget();
57
- } else if (timeout >= 0 && (millis() - start >= (unsigned long)timeout)) {
61
+ } else if (timeout >= 0 && (${now} - start >= static_cast<unsigned long>(timeout))) {
58
62
  resolve(nullptr);
59
63
  } else {
60
64
  enqueueMicrotask([pin, idleState, timeout, start, resolve, pollTarget]() {});
@@ -264,12 +268,12 @@ namespace typecad_async {
264
268
  // Async.sleep() — cooperative delay using millis polling
265
269
  inline Promise<void> __cuttlefish_async_sleep(unsigned long ms) {
266
270
  return Promise<void>([ms](std::function<void(const void*)> resolve, std::function<void(const std::string&)> reject) {
267
- unsigned long start = millis();
268
- if (millis() - start >= ms) {
271
+ unsigned long start = ${now};
272
+ if (${now} - start >= ms) {
269
273
  resolve(nullptr);
270
274
  } else {
271
275
  enqueueMicrotask([ms, start, resolve]() {
272
- if (millis() - start >= ms) {
276
+ if (${now} - start >= ms) {
273
277
  resolve(nullptr);
274
278
  } else {
275
279
  enqueueMicrotask([ms, start, resolve]() { /* will be re-checked next cycle */ });
@@ -289,9 +293,9 @@ namespace typecad_async {
289
293
  // Async.sleepUntil() — poll condition every interval ms
290
294
  inline Promise<void> __cuttlefish_async_sleep_until(unsigned long pollIntervalMs) {
291
295
  return Promise<void>([pollIntervalMs](std::function<void(const void*)> resolve, std::function<void(const std::string&)> reject) {
292
- unsigned long start = millis();
296
+ unsigned long start = ${now};
293
297
  enqueueMicrotask([pollIntervalMs, start, resolve]() {
294
- if (millis() - start >= pollIntervalMs) {
298
+ if (${now} - start >= pollIntervalMs) {
295
299
  resolve(nullptr); // caller re-checks condition
296
300
  } else {
297
301
  enqueueMicrotask([pollIntervalMs, start, resolve]() { /* re-check next cycle */ });
@@ -31,11 +31,28 @@ export interface ToolchainOptions {
31
31
  optimize?: string;
32
32
  extraFlags?: string[];
33
33
  defines?: Record<string, string>;
34
+ /** ESP32 PSRAM type ('opi' | 'quad') when the target board has PSRAM. */
35
+ psram?: 'opi' | 'quad';
34
36
  /**
35
37
  * Framework-specific config from `cuttlefish.config.ts`.
36
38
  * Each framework casts this to its own typed interface.
37
39
  */
38
40
  frameworkConfig?: Record<string, unknown>;
41
+ /**
42
+ * Zephyr-specific config from `cuttlefish.config.ts` (the `zephyr` section).
43
+ */
44
+ zephyrConfig?: Record<string, unknown>;
45
+ /**
46
+ * Display config from `cuttlefish.config.ts` (the `display` section).
47
+ * Frameworks use cs/dc/rst/spiFrequency to generate devicetree wiring.
48
+ */
49
+ display?: Record<string, unknown>;
50
+ /**
51
+ * True when `--debug` is active. Frameworks read this to generate debug
52
+ * artifacts (e.g. VS Code launch configs, debug Kconfig symbols) and to
53
+ * select their debug mode (gdb vs printf) via PlatformDebugStrategy.
54
+ */
55
+ debug?: boolean;
39
56
  }
40
57
  /**
41
58
  * Generic compile result. All frameworks return this shape.
@@ -29,6 +29,7 @@ class Accumulator {
29
29
  const CATEGORY_PREFIXES = {
30
30
  gpio: ['gpio.'],
31
31
  pwm: ['pwm.'],
32
+ rmt: ['rmt.'],
32
33
  adc: ['adc.'],
33
34
  dac: ['dac.'],
34
35
  interrupts: ['interrupt.'],
@@ -46,6 +47,32 @@ const CATEGORY_PREFIXES = {
46
47
  http: ['http.'],
47
48
  display: ['display.'],
48
49
  preferences: ['preferences.'],
50
+ ble: ['ble.'],
51
+ random: ['random.'],
52
+ fs: ['fs.'],
53
+ mdns: ['mdns.'],
54
+ mqtt: ['mqtt.'],
55
+ ota: ['ota.'],
56
+ temp: ['temp.'],
57
+ hwtimer: ['hwtimer.'],
58
+ capacitive: ['capacitive.'],
59
+ // Unimplemented-but-recognized categories: each framework may declare these
60
+ // as unsupported in its manifest so the coverage matrix records them as a
61
+ // roadmap. The validator probes the resolver and confirms it does NOT lower
62
+ // them (consistent with the 'unsupported' status).
63
+ i2s: ['i2s.'],
64
+ twai: ['twai.'],
65
+ usb: ['usb.'],
66
+ eth: ['eth.'],
67
+ espnow: ['espnow.'],
68
+ crypto: ['crypto.'],
69
+ pcnt: ['pcnt.'],
70
+ mcpwm: ['mcpwm.'],
71
+ // Worker offload + snprintf raw-escape: recognized categories. Frameworks
72
+ // that lower worker.* declare it supported; snprintf.emit is a raw escape
73
+ // hatch (declared unsupported by frameworks that don't special-case it).
74
+ worker: ['worker.'],
75
+ snprintf: ['snprintf.'],
49
76
  };
50
77
  function opKindsForCategory(category) {
51
78
  const prefixes = CATEGORY_PREFIXES[category] ?? [];
@@ -78,6 +105,36 @@ const OP_PROBE_PAYLOADS = {
78
105
  'display.draw_text': { x: 0, y: 0, text: 'x', font: '8x16', color: 0xffff },
79
106
  'display.draw_rect': { x: 0, y: 0, w: 10, h: 10, color: 0xffff },
80
107
  'display.flush': { rects: [{ x: 0, y: 0, w: 10, h: 10 }] },
108
+ // fs.* require a path (and content for write_text).
109
+ 'fs.read_text': { path: '/sdcard/x.txt' },
110
+ 'fs.write_text': { path: '/sdcard/x.txt', content: 'hi' },
111
+ 'fs.exists': { path: '/sdcard/x.txt' },
112
+ 'fs.remove': { path: '/sdcard/x.txt' },
113
+ // random.range requires min/max.
114
+ 'random.range': { min: 0, max: 10 },
115
+ // mdns.add_service requires instance/proto/port.
116
+ 'mdns.start': { hostname: 'h' },
117
+ 'mdns.set_hostname': { name: 'h' },
118
+ 'mdns.add_service': { instance: 'i', proto: '_tcp', port: 80 },
119
+ // mqtt.connect requires brokerUri/clientId; mqtt.publish requires topic/data.
120
+ 'mqtt.connect': { brokerUri: 'mqtt://b', clientId: 'c' },
121
+ 'mqtt.on_message': { handler: 'cb' },
122
+ 'mqtt.subscribe': { topic: 't' },
123
+ 'mqtt.publish': { topic: 't', data: 'd' },
124
+ // wifi.on_event requires an event + handler. Probe with 'connect'.
125
+ // Without a payload the probe sends event=undefined and a 'supported'
126
+ // declaration would false-fail as "resolver returned undefined".
127
+ 'wifi.on_event': { event: 'connect', handler: 'cb' },
128
+ // ota.from_url requires url; ota.write requires chunk.
129
+ 'ota.from_url': { url: 'https://x' },
130
+ 'ota.write': { chunk: 'buf' },
131
+ // hwtimer.* require an instance (and hz/handler).
132
+ 'hwtimer.set_frequency': { instance: 0, hz: 1000 },
133
+ 'hwtimer.on_overflow': { instance: 0, handler: 'cb' },
134
+ 'hwtimer.start': { instance: 0 },
135
+ 'hwtimer.stop': { instance: 0 },
136
+ // capacitive.read requires pin.
137
+ 'capacitive.read': { pin: 4 },
81
138
  };
82
139
  // Builds a HALOpIR probe. Uses OP_PROBE_PAYLOADS when available so resolvers
83
140
  // that destructure required fields don't throw on the probe itself.
@@ -232,6 +289,25 @@ function validateHalCoverage(manifest, ctx, acc) {
232
289
  }
233
290
  }
234
291
  // ---------------------------------------------------------------------------
292
+ // Category C2 — HAL completeness
293
+ // ---------------------------------------------------------------------------
294
+ /**
295
+ * Verify every known HAL category appears in manifest.hal. validateHalCoverage
296
+ * only audits categories the manifest declares; a category omitted entirely is
297
+ * invisible to it (and to the per-op check). This step closes that hole: each
298
+ * category in CATEGORY_PREFIXES must be accounted for, even as unsupported.
299
+ * `raw` is excluded — it is the schema-level escape hatch, not a real category,
300
+ * and is not in CATEGORY_PREFIXES.
301
+ */
302
+ function validateHalCompleteness(manifest, _ctx, acc) {
303
+ const declared = new Set(Object.keys(manifest.hal));
304
+ for (const category of Object.keys(CATEGORY_PREFIXES)) {
305
+ if (!declared.has(category)) {
306
+ acc.error(`hal/${category}/category-undeclared`, `manifest.hal.${category}`, `manifest.hal is missing the "${category}" category. Add a block declaring it (supported: true/false with ops, or unsupported with unsupportedReason).`, category);
307
+ }
308
+ }
309
+ }
310
+ // ---------------------------------------------------------------------------
235
311
  // Category D — polyfills
236
312
  // ---------------------------------------------------------------------------
237
313
  function validatePolyfills(manifest, ctx, acc) {
@@ -404,6 +480,7 @@ export function validateFrameworkManifest(manifest, ctx) {
404
480
  validateIdentity(manifest, ctx, acc);
405
481
  validateEntrypoint(manifest, ctx, acc);
406
482
  validateHalCoverage(manifest, ctx, acc);
483
+ validateHalCompleteness(manifest, ctx, acc);
407
484
  validatePolyfills(manifest, ctx, acc);
408
485
  validateToolchain(manifest, ctx, acc);
409
486
  validateLibraryResolution(manifest, ctx, acc);
@@ -0,0 +1,22 @@
1
+ import type { ProgramIR } from "../index.js";
2
+ import type { RuntimePolyfillIR } from "./index.js";
3
+ import type { PlatformStrategy } from "./platform-strategy.js";
4
+ import type { WorkerBacking } from "./worker-runtime.js";
5
+ /**
6
+ * Build a RuntimePolyfillIR for the worker-offload runtime, when the framework
7
+ * supplies worker backing via its PlatformAsyncStrategy hooks
8
+ * (workerSpawnLines / workerSignalDoneExpr / workerIsDoneExpr).
9
+ *
10
+ * The polyfill carries only the slot table + __tc_worker_submit/done contract
11
+ * with the per-framework primitives injected from the strategy hooks. STL-free
12
+ * so it links on minimal-libc targets (Zephyr). Returns null if the strategy
13
+ * supplies no backing (worker offload unsupported there) — in which case
14
+ * worker.* ops resolve to unsupported.
15
+ *
16
+ * Callers gate this on the program actually using worker.* ops
17
+ * (ProgramAnalysisResult.usesWorker); the producer does not re-scan.
18
+ */
19
+ export declare function buildWorkerRuntimePolyfill(_program: ProgramIR, strategy: PlatformStrategy, backing: WorkerBacking, options?: {
20
+ poolSize?: number;
21
+ stackSize?: number;
22
+ }): RuntimePolyfillIR | null;
@@ -0,0 +1,34 @@
1
+ import { generateWorkerRuntime } from "./worker-runtime.js";
2
+ /**
3
+ * Build a RuntimePolyfillIR for the worker-offload runtime, when the framework
4
+ * supplies worker backing via its PlatformAsyncStrategy hooks
5
+ * (workerSpawnLines / workerSignalDoneExpr / workerIsDoneExpr).
6
+ *
7
+ * The polyfill carries only the slot table + __tc_worker_submit/done contract
8
+ * with the per-framework primitives injected from the strategy hooks. STL-free
9
+ * so it links on minimal-libc targets (Zephyr). Returns null if the strategy
10
+ * supplies no backing (worker offload unsupported there) — in which case
11
+ * worker.* ops resolve to unsupported.
12
+ *
13
+ * Callers gate this on the program actually using worker.* ops
14
+ * (ProgramAnalysisResult.usesWorker); the producer does not re-scan.
15
+ */
16
+ export function buildWorkerRuntimePolyfill(_program, strategy, backing, options = {}) {
17
+ // The backing is authoritative: it is constructed by the framework from its
18
+ // own hooks. If a framework supplies no backing (worker offload unsupported),
19
+ // it should not call this producer.
20
+ const runtime = generateWorkerRuntime(backing, options);
21
+ if (!runtime)
22
+ return null;
23
+ return {
24
+ kind: "polyfill",
25
+ id: "worker_runtime",
26
+ domain: "embedded",
27
+ requiredIncludes: backing.requiredIncludes,
28
+ forwardDeclarations: backing.declarations ?? [],
29
+ helperStructs: [runtime],
30
+ helperFunctions: [],
31
+ shimMacros: [],
32
+ dependencies: [],
33
+ };
34
+ }
@@ -0,0 +1,69 @@
1
+ export interface WorkerRuntimeOptions {
2
+ /** Number of fixed worker slots in the pool. Default 4. */
3
+ poolSize?: number;
4
+ /** Per-slot worker stack size in bytes (ESP32). Default 8192. */
5
+ stackSize?: number;
6
+ }
7
+ /**
8
+ * Per-framework backing primitives injected into the shared contract.
9
+ * Undefined fields mean "unsupported" and the runtime is not emitted.
10
+ */
11
+ export interface WorkerBacking {
12
+ /**
13
+ * Statements that spawn the worker trampoline for slot `handleId`.
14
+ *
15
+ * `trampolineName` is the C++ symbol of the per-slot worker function the
16
+ * shared runtime generated (it reads fn/arg from the slot, runs them, then
17
+ * signals completion). `waiterExpr` is the C++ lvalue the backing should set
18
+ * to the current waiter handle before spawning (the shared runtime reads it
19
+ * via signalDoneExpr/isDoneExpr).
20
+ *
21
+ * The shared runtime has already stored the submitted fn/arg into the slot
22
+ * and cleared `done` before this runs; the backing need only record the
23
+ * waiter and spawn `trampolineName`.
24
+ */
25
+ spawnLines: (handleId: number, trampolineName: string, waiterExpr: string) => string[];
26
+ /** Expression the worker calls after finishing + writing outputs (barrier). */
27
+ signalDoneExpr: (handleId: number) => string;
28
+ /** Boolean expression polled by __tc_worker_done (observes the barrier). */
29
+ isDoneExpr: (handleId: number) => string;
30
+ /** C++ headers required by the primitives (e.g. '<freertos/FreeRTOS.h>'). */
31
+ requiredIncludes: string[];
32
+ /** Any backing declarations (e.g. semaphore objects) emitted before the pool. */
33
+ declarations?: string[];
34
+ /**
35
+ * Per-slot backing declarations emitted inside the namespace, one call per
36
+ * slot, before the submit/done functions. Used by backings that need a named
37
+ * per-slot object bound to the trampoline at definition time — e.g. Zephyr's
38
+ * `K_WORK_DEFINE(_work_<i>, __tc_worker_fn_<i>)` + a per-slot semaphore.
39
+ * `trampolineName` is the per-slot worker symbol the shared runtime emitted.
40
+ */
41
+ slotDeclarations?: (handleId: number, trampolineName: string) => string[];
42
+ }
43
+ /**
44
+ * Generate the worker-offload runtime C++ for a framework that supplies a
45
+ * backing. Returns null if `backing` is null (unsupported on this framework).
46
+ *
47
+ * The emitted code defines, for each slot i in [0, poolSize):
48
+ * - a worker trampoline __tc_worker_fn_i(void* arg) that runs the recorded
49
+ * fn and then signals completion via signalDoneExpr,
50
+ * - __tc_worker_submit(i, fn, arg) — records the waiter, clears done, spawns,
51
+ * - __tc_worker_done(i) — poll predicate.
52
+ *
53
+ * AUTOSAR C++14: fixed-width types, static_cast, final structs, no STL, no heap.
54
+ */
55
+ export declare function generateWorkerRuntime(backing: WorkerBacking | null, options?: WorkerRuntimeOptions): string | null;
56
+ /**
57
+ * Resolve a HAL worker.* op to C++ against the shared contract.
58
+ * Returns undefined for ops the caller did not lower (unsupported).
59
+ *
60
+ * Note: the worker function itself (fnRef) and its argument (argRef) come from
61
+ * the IR as flat-scalar strings (per the hal-op-ir flat-fields contract). The
62
+ * lowering emits a straight call into __tc_worker_submit / __tc_worker_done.
63
+ */
64
+ export declare function lowerWorkerOp(op: {
65
+ operation: string;
66
+ }): {
67
+ code?: string;
68
+ expression?: string;
69
+ } | undefined;