@typecad/cuttlefish 1.0.0-alpha.6 → 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 (187) 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 +4 -3
  12. package/dist/api/shared/display-adapter.js +15 -118
  13. package/dist/api/shared/display-adapters/sdl.js +38 -31
  14. package/dist/api/shared/display-profile.d.ts +47 -25
  15. package/dist/api/shared/display-profile.js +31 -164
  16. package/dist/api/shared/framework-manifest-registry.d.ts +9 -0
  17. package/dist/api/shared/framework-manifest-registry.js +27 -0
  18. package/dist/api/shared/framework-manifest.d.ts +581 -0
  19. package/dist/api/shared/framework-manifest.js +182 -0
  20. package/dist/api/shared/glcdfont.d.ts +12 -0
  21. package/dist/api/shared/glcdfont.js +124 -0
  22. package/dist/api/shared/graphics-strategy.d.ts +37 -0
  23. package/dist/api/shared/hal-op-ir.d.ts +755 -1
  24. package/dist/api/shared/hal-op-ir.js +129 -1
  25. package/dist/api/shared/index.d.ts +20 -2
  26. package/dist/api/shared/index.js +25 -1
  27. package/dist/api/shared/ir-core.d.ts +4 -0
  28. package/dist/api/shared/ir-declarations.d.ts +6 -0
  29. package/dist/api/shared/native-display-op-resolver.d.ts +10 -0
  30. package/dist/api/shared/native-display-op-resolver.js +64 -0
  31. package/dist/api/shared/platform-strategy.d.ts +113 -2
  32. package/dist/api/shared/polyfill-helper-registry.js +18 -1
  33. package/dist/api/shared/promise-runtime.d.ts +1 -1
  34. package/dist/api/shared/promise-runtime.js +95 -13
  35. package/dist/api/shared/toolchain-types.d.ts +17 -0
  36. package/dist/api/shared/validate-framework-manifest.d.ts +28 -0
  37. package/dist/api/shared/validate-framework-manifest.js +494 -0
  38. package/dist/api/shared/worker-runtime-polyfill.d.ts +22 -0
  39. package/dist/api/shared/worker-runtime-polyfill.js +34 -0
  40. package/dist/api/shared/worker-runtime.d.ts +69 -0
  41. package/dist/api/shared/worker-runtime.js +163 -0
  42. package/dist/cli.js +186 -82
  43. package/dist/config-loader.d.ts +7 -2
  44. package/dist/config-loader.js +23 -3
  45. package/dist/config-schema.d.ts +112 -70
  46. package/dist/config-schema.js +14 -0
  47. package/dist/contract/board-generator.d.ts +17 -0
  48. package/dist/contract/board-generator.js +57 -0
  49. package/dist/contract/contract-parser.d.ts +217 -0
  50. package/dist/contract/contract-parser.js +224 -0
  51. package/dist/contract/index.d.ts +22 -0
  52. package/dist/contract/index.js +68 -0
  53. package/dist/create/board-codegen.js +4 -4
  54. package/dist/create/board-generators.js +4 -5
  55. package/dist/create/board-spec.d.ts +72 -75
  56. package/dist/create/board-spec.js +0 -1
  57. package/dist/create/init-scaffold.d.ts +9 -2
  58. package/dist/create/init-scaffold.js +0 -14
  59. package/dist/create/init-templates.d.ts +2 -0
  60. package/dist/create/init-templates.js +74 -8
  61. package/dist/create/init-wizard.js +31 -7
  62. package/dist/debug/preprocessor.js +178 -39
  63. package/dist/debug/types.d.ts +23 -0
  64. package/dist/diagnostics/mermaid-builder.d.ts +1 -1
  65. package/dist/diagnostics/mermaid-builder.js +34 -24
  66. package/dist/emit/compliance/arxml-writer.d.ts +11 -0
  67. package/dist/emit/compliance/arxml-writer.js +34 -0
  68. package/dist/emit/compliance/compliance-context.d.ts +57 -0
  69. package/dist/emit/compliance/compliance-context.js +113 -0
  70. package/dist/emit/compliance/deviation-ledger.d.ts +27 -0
  71. package/dist/emit/compliance/deviation-ledger.js +47 -0
  72. package/dist/emit/compliance/deviation-writer.d.ts +30 -0
  73. package/dist/emit/compliance/deviation-writer.js +37 -0
  74. package/dist/emit/compliance/index.d.ts +7 -0
  75. package/dist/emit/compliance/index.js +6 -0
  76. package/dist/emit/compliance/rule-engine.d.ts +13 -0
  77. package/dist/emit/compliance/rule-engine.js +101 -0
  78. package/dist/emit/compliance/rules.d.ts +16 -0
  79. package/dist/emit/compliance/rules.js +191 -0
  80. package/dist/emit/compliance/types.d.ts +59 -0
  81. package/dist/emit/compliance/types.js +8 -0
  82. package/dist/emit/cpp-emitter.js +4 -3
  83. package/dist/emit/emitters/class-emitter.js +6 -1
  84. package/dist/emit/emitters/emitter-context.d.ts +28 -1
  85. package/dist/emit/emitters/function-emitter-impl.js +153 -53
  86. package/dist/emit/emitters/line-appender.js +19 -0
  87. package/dist/emit/emitters/line-marker.d.ts +38 -0
  88. package/dist/emit/emitters/line-marker.js +39 -0
  89. package/dist/emit/emitters/output-finalizer.d.ts +6 -0
  90. package/dist/emit/emitters/output-finalizer.js +101 -12
  91. package/dist/emit/emitters/setup.d.ts +17 -0
  92. package/dist/emit/emitters/setup.js +257 -36
  93. package/dist/emit/emitters/top-level-prep.js +8 -0
  94. package/dist/emit/emitters/type-decl-emitter.js +28 -3
  95. package/dist/emit/emitters/ui-emitter.js +33 -9
  96. package/dist/emit/expression-renderer.d.ts +1 -1
  97. package/dist/emit/expression-renderer.js +91 -6
  98. package/dist/emit/route-hal-op.js +18 -5
  99. package/dist/emit/snprintf-helpers.js +15 -4
  100. package/dist/emit/statement-renderer.d.ts +10 -0
  101. package/dist/emit/statement-renderer.js +34 -5
  102. package/dist/emit/utils/async-state-machine.js +221 -125
  103. package/dist/emit/utils/hal-op-cpp-type.d.ts +6 -0
  104. package/dist/emit/utils/hal-op-cpp-type.js +40 -0
  105. package/dist/framework-package.js +2 -0
  106. package/dist/framework-registry.d.ts +17 -0
  107. package/dist/ir/adc-range-validation.js +14 -1
  108. package/dist/ir/build-ir-state.d.ts +1 -0
  109. package/dist/ir/build-ir-state.js +14 -0
  110. package/dist/ir/build-ir.js +9 -5
  111. package/dist/ir/call-graph.js +16 -0
  112. package/dist/ir/expression-to-ir.js +164 -0
  113. package/dist/ir/feature-registry.js +7 -25
  114. package/dist/ir/function-builder.js +22 -0
  115. package/dist/ir/hal/hal-emitter.d.ts +5 -2
  116. package/dist/ir/hal/hal-emitter.js +50 -18
  117. package/dist/ir/hal/hal-parser.d.ts +6 -0
  118. package/dist/ir/hal/hal-parser.js +81 -0
  119. package/dist/ir/hal/hal-plugins.js +723 -1
  120. package/dist/ir/identifier-collector.js +35 -0
  121. package/dist/ir/interrupt-analysis.d.ts +5 -1
  122. package/dist/ir/interrupt-analysis.js +8 -16
  123. package/dist/ir/network-validation.d.ts +4 -0
  124. package/dist/ir/network-validation.js +184 -0
  125. package/dist/ir/ownership-analysis.js +20 -1
  126. package/dist/ir/pin-capability-validation.js +37 -0
  127. package/dist/ir/pin-mode-validation.d.ts +2 -2
  128. package/dist/ir/pin-mode-validation.js +49 -18
  129. package/dist/ir/program-analysis.d.ts +65 -0
  130. package/dist/ir/program-analysis.js +318 -2
  131. package/dist/ir/render-expr.js +11 -0
  132. package/dist/ir/timing-validation.d.ts +6 -1
  133. package/dist/ir/timing-validation.js +60 -13
  134. package/dist/ir/transformers/call-statement.js +105 -0
  135. package/dist/ir/transformers/expressions.js +62 -0
  136. package/dist/ir/transformers/hal-call-resolver.js +21 -0
  137. package/dist/ir/transformers/hal-emit-helpers.js +1 -1
  138. package/dist/ir/transformers/namespace-methods.js +17 -12
  139. package/dist/ir/transformers/ui-reactive.js +2 -2
  140. package/dist/ir/transformers/variables.js +92 -3
  141. package/dist/ir/type-resolution.js +18 -0
  142. package/dist/ir/ui-element-auto-wire.js +7 -5
  143. package/dist/ir/utils/map-statements.d.ts +4 -0
  144. package/dist/ir/utils/map-statements.js +79 -0
  145. package/dist/ir/validation-orchestrator.js +9 -2
  146. package/dist/ir/worker-analysis.d.ts +10 -0
  147. package/dist/ir/worker-analysis.js +261 -0
  148. package/dist/libdef/c-to-decl.d.ts +27 -0
  149. package/dist/libdef/c-to-decl.js +397 -0
  150. package/dist/libdef/component-decls.d.ts +2 -0
  151. package/dist/libdef/component-decls.js +6 -0
  152. package/dist/libdef/component-discovery.d.ts +43 -0
  153. package/dist/libdef/component-discovery.js +83 -0
  154. package/dist/libdef/cpp-to-decl.d.ts +9 -0
  155. package/dist/libdef/cpp-to-decl.js +72 -0
  156. package/dist/libdef/registry.js +5 -2
  157. package/dist/lint-cache.d.ts +59 -0
  158. package/dist/lint-cache.js +257 -0
  159. package/dist/orchestrator/graph-builder.js +14 -9
  160. package/dist/platform/coop-scheduler-runtime.d.ts +19 -0
  161. package/dist/platform/coop-scheduler-runtime.js +41 -0
  162. package/dist/platform/generic-strategy.d.ts +15 -3
  163. package/dist/platform/generic-strategy.js +49 -4
  164. package/dist/safety/safety-bridge.d.ts +11 -0
  165. package/dist/safety/safety-bridge.js +48 -0
  166. package/dist/safety/sidecar-bridge.d.ts +5 -0
  167. package/dist/safety/sidecar-bridge.js +22 -0
  168. package/dist/safety-hook.d.ts +79 -0
  169. package/dist/safety-hook.js +35 -0
  170. package/dist/testing.d.ts +7 -1
  171. package/dist/testing.js +8 -1
  172. package/dist/transpile.d.ts +3 -0
  173. package/dist/transpile.js +175 -41
  174. package/dist/types.d.ts +13 -2
  175. package/dist/ui-hook.d.ts +17 -3
  176. package/dist/utils/cli.js +78 -4
  177. package/dist/utils/fs.d.ts +13 -0
  178. package/dist/utils/fs.js +50 -0
  179. package/package.json +24 -5
  180. package/dist/api/shared/display-adapters/eink-mono.d.ts +0 -2
  181. package/dist/api/shared/display-adapters/eink-mono.js +0 -53
  182. package/dist/api/shared/display-adapters/ssd1309.d.ts +0 -2
  183. package/dist/api/shared/display-adapters/ssd1309.js +0 -136
  184. package/dist/api/shared/display-adapters/st7796.d.ts +0 -2
  185. package/dist/api/shared/display-adapters/st7796.js +0 -132
  186. package/dist/licenses.d.ts +0 -185
  187. package/dist/licenses.js +0 -963
@@ -5,6 +5,7 @@ import { escapeCppKeyword } from "../../utils/strings.js";
5
5
  import { parsedIsPlainStructType } from "../../api/shared/cpp-type-ir.js";
6
6
  import { entryHasUI } from "../../ui-hook.js";
7
7
  import { activeNamespaceNames } from "../../ir/build-ir-state.js";
8
+ import { buildCoopSchedInjection } from "../../api/shared/coop-scheduler.js";
8
9
  export function emitPostClassDeclarations(ctx) {
9
10
  const { strategy, effectiveEmitMode, mappedFunctions, isEntryFile, topLevelScope } = ctx;
10
11
  const platformReservedNames = strategy.reservedNames();
@@ -31,6 +32,12 @@ export function emitPostClassDeclarations(ctx) {
31
32
  if (ctx.promotedVarDecls.size > 0) {
32
33
  for (const [varName, info] of ctx.promotedVarDecls) {
33
34
  appendSourceLine(ctx, `${info.cppType} ${escapeCppKeyword(varName, platformReservedNames)} = {};`);
35
+ // Seed the top-level scope's type map so subsequent assign rendering
36
+ // (e.g. the deferred `c = SafeInt(0)` initializer) can resolve the
37
+ // variable's type and inject template args / casts via
38
+ // renderValueForTarget. Without this, type-aware rendering of promoted
39
+ // vars is unreachable (inferLvalueCppType returns undefined).
40
+ ctx.topLevelScope.knownVariableTypes.set(varName, { cppType: info.cppType });
34
41
  }
35
42
  appendSourceLine(ctx, "");
36
43
  }
@@ -62,7 +69,7 @@ export function emitPostClassDeclarations(ctx) {
62
69
  // Still register the field types so downstream rendering (e.g. string-concat
63
70
  // wrapping via interfaceFieldTypes) can resolve property accesses on the var.
64
71
  const fieldTypeEntries = statement.initializer.fields.map((field) => {
65
- const inferred = inferObjectFieldType(field.value, ctx.globalPointerVarTypes, ctx.knownFunctionReturnTypes, ctx.knownTopLevelObjectTypes, ctx.knownTopLevelObjectFields, ctx.largeEnumNames, statement.name, field.name, strategy.defaultNumericType(), (o, n) => strategy.resolvePinType?.(o, n));
72
+ const inferred = inferObjectFieldType(field.value, ctx.globalPointerVarTypes, ctx.knownFunctionReturnTypes, ctx.knownTopLevelObjectTypes, ctx.knownTopLevelObjectFields, ctx.largeEnumNames, statement.name, field.name, strategy.defaultNumericType(ctx.compliance.isEnabled() ? ctx.compliance : undefined), (o, n) => strategy.resolvePinType?.(o, n));
66
73
  return [field.name, inferred];
67
74
  });
68
75
  if (hasExplicitNamedType) {
@@ -128,7 +135,12 @@ export function emitCallbackFunctions(ctx) {
128
135
  appendSourceLine(ctx, `const unsigned long ${callback.name}_debounce = ${callback.debounceMs};`);
129
136
  appendSourceLine(ctx, "");
130
137
  }
131
- appendSourceLine(ctx, `${strategy.isrFunctionAttribute?.() ?? ""}${renderCallbackSignature(callback)} {`);
138
+ // IRAM_ATTR only on true ISR definitions. ESP-IDF's IRAM_ATTR uses
139
+ // __COUNTER__, so putting it on both forward decl and definition assigns
140
+ // conflicting .iram1.N sections (-Werror=attributes). Non-ISR callbacks
141
+ // (WiFi events, timers) must not be IRAM-placed either — they call printf.
142
+ const isrAttr = callback.isInterruptHandler ? (strategy.isrFunctionAttribute?.() ?? "") : "";
143
+ appendSourceLine(ctx, `${isrAttr}${renderCallbackSignature(callback)} {`);
132
144
  if (callback.debounceMs !== undefined && callback.debounceMs > 0) {
133
145
  appendSourceLine(ctx, ` volatile unsigned long now = ${strategy.currentTimeMillis()};`);
134
146
  appendSourceLine(ctx, ` if (now - ${callback.name}_lastTime < ${callback.name}_debounce) return;`);
@@ -141,10 +153,11 @@ export function emitCallbackFunctions(ctx) {
141
153
  appendSourceLine(ctx, "}");
142
154
  appendSourceLine(ctx, "");
143
155
  }
144
- // Split-mode ISR callback forward declarations in header
156
+ // Split-mode ISR callback forward declarations in header (no IRAM_ATTR —
157
+ // attribute belongs on the definition only; see comment above).
145
158
  if (effectiveEmitMode === "split") {
146
159
  for (const callback of ctx.callbackFunctions) {
147
- appendHeaderLine(ctx, `${strategy.isrFunctionAttribute?.() ?? ""}${renderCallbackSignature(callback)};`);
160
+ appendHeaderLine(ctx, `${renderCallbackSignature(callback)};`);
148
161
  }
149
162
  }
150
163
  }
@@ -172,6 +185,10 @@ export function emitFunctions(ctx) {
172
185
  // exported functions (which ride along with their definition below).
173
186
  for (let fi = 0; fi < mappedFunctions.length; fi++) {
174
187
  const fn = mappedFunctions[fi];
188
+ // Async functions become cooperative *Task state machines (emitAsyncTaskClasses).
189
+ // Emitting an empty stub here triggers -Wunused-function with no value.
190
+ if (fn.isAsync && ctx.hasAsyncRuntime)
191
+ continue;
175
192
  const declarationParameterList = ctx.statementRenderer.renderParameters(fn.parameters, true);
176
193
  const definitionParameterList = ctx.statementRenderer.renderParameters(fn.parameters, false);
177
194
  const readonlyPrefix = fn.isReadonlyReturnType ? "const " : "";
@@ -230,6 +247,29 @@ export function emitFunctions(ctx) {
230
247
  ctx.sourceMapEntries = ctx.headerMapEntries;
231
248
  ctx.headerMapEntries = _swapMaps;
232
249
  }
250
+ // Cooperative scheduler trampolines (Phase 0).
251
+ //
252
+ // When the strategy opts into priority/time-budget scheduling, the driver
253
+ // function's per-frame pumps are routed through the no-STL CoopSched. The
254
+ // trampolines (file-scope `static void __tc_coop_*(void*)`) must appear
255
+ // before the driver function definition so CoopSched can call them; they
256
+ // reference only global/static names (task instances, pump functions) so
257
+ // no captures are needed. Emitted once, ahead of the driver only.
258
+ const asyncDriverFnForTrampolines = strategy.asyncDriverFunctionName();
259
+ if (fn.name === asyncDriverFnForTrampolines &&
260
+ (hasPromiseRuntime || asyncTaskClasses.length > 0 || usesTimers)) {
261
+ const coopConfig = strategy.getAsyncRuntimeConfig();
262
+ if (coopConfig.enablePriority || coopConfig.enableTimeBudget) {
263
+ const coopUnits = buildDriverCoopUnits(asyncTaskClasses.map(t => t.taskVarName), hasPromiseRuntime, usesTimers);
264
+ if (coopUnits.length > 0) {
265
+ const { trampolines } = buildCoopSchedInjection(coopUnits);
266
+ appendSourceLine(ctx, "// ── CoopSched trampolines (priority/time-budget scheduling) ──");
267
+ for (const t of trampolines)
268
+ appendSourceLine(ctx, t);
269
+ appendSourceLine(ctx, "");
270
+ }
271
+ }
272
+ }
233
273
  emitCommentLines(fn.leadingComments, "", (line) => appendSourceLine(ctx, line));
234
274
  if (fn.typeParameters && fn.typeParameters.length > 0) {
235
275
  appendSourceLine(ctx, `template<typename ${fn.typeParameters.join(", typename ")}>`);
@@ -246,64 +286,80 @@ export function emitFunctions(ctx) {
246
286
  if (hasPromiseRuntime && fn.name === asyncDriverFn) {
247
287
  appendSourceLine(ctx, " cuttlefish_pump_microtasks();");
248
288
  }
249
- if (fn.isAsync && ctx.hasAsyncRuntime) {
250
- appendSourceLine(ctx, ` // driven as cooperative task in ${asyncDriverFn}()`);
289
+ if (fn.typeParameterConstraints) {
290
+ for (const [param, expr] of fn.typeParameterConstraints) {
291
+ appendSourceLine(ctx, ` static_assert(${expr}, "${param} constraint violated");`);
292
+ }
251
293
  }
252
- else {
253
- if (fn.typeParameterConstraints) {
254
- for (const [param, expr] of fn.typeParameterConstraints) {
255
- appendSourceLine(ctx, ` static_assert(${expr}, "${param} constraint violated");`);
256
- }
294
+ const functionScope = createChildEmissionScope(topLevelScope, fn.parameters);
295
+ ctx.cArrayVarNames = ctx.fnCArrayVarNames.get(String(fi)) ?? new Set();
296
+ const isLoopDriver = fn.name === asyncDriverFn;
297
+ const lastStmt = fn.statements.length > 0 ? fn.statements[fn.statements.length - 1] : null;
298
+ const lastIsReturn = lastStmt?.kind === "return";
299
+ const stmtsToEmit = (isLoopDriver && lastIsReturn)
300
+ ? fn.statements.slice(0, -1)
301
+ : fn.statements;
302
+ for (const statement of stmtsToEmit) {
303
+ appendRenderedStatement(ctx, statement, " ", functionScope);
304
+ }
305
+ // Drive the UI runtime each frame. Fires only in the driver function when
306
+ // a UI is mounted (entryHasUI). Uses a separate gate from the async pump
307
+ // so a pure-UI program (no async/timers) still animates. When the strategy
308
+ // provides hostEventLoop, the per-frame work runs inside a while loop so a
309
+ // host target (SDL) can pump events and present between ticks. Emitted
310
+ // AFTER the function's init statements (ui_init/display_init/touch_init)
311
+ // so initialization precedes the loop.
312
+ if (entryHasUI() && fn.name === asyncDriverFn) {
313
+ const loop = strategy.hostEventLoop?.();
314
+ if (loop) {
315
+ appendSourceLine(ctx, ` bool ${loop.flagName} = true;`);
316
+ appendSourceLine(ctx, ` while (${loop.continueCondition}) {`);
317
+ appendSourceLine(ctx, ` ${loop.preIteration}`);
257
318
  }
258
- const functionScope = createChildEmissionScope(topLevelScope, fn.parameters);
259
- ctx.cArrayVarNames = ctx.fnCArrayVarNames.get(String(fi)) ?? new Set();
260
- const isLoopDriver = fn.name === asyncDriverFn;
261
- const lastStmt = fn.statements.length > 0 ? fn.statements[fn.statements.length - 1] : null;
262
- const lastIsReturn = lastStmt?.kind === "return";
263
- const stmtsToEmit = (isLoopDriver && lastIsReturn)
264
- ? fn.statements.slice(0, -1)
265
- : fn.statements;
266
- for (const statement of stmtsToEmit) {
267
- appendRenderedStatement(ctx, statement, " ", functionScope);
319
+ appendSourceLine(ctx, ` uint32_t __tc_ui_now = static_cast<uint32_t>(${strategy.currentTimeMillis()});`);
320
+ appendSourceLine(ctx, " static uint32_t __tc_ui_last_tick = __tc_ui_now;");
321
+ appendSourceLine(ctx, " uint32_t __tc_ui_delta = __tc_ui_now - __tc_ui_last_tick;");
322
+ appendSourceLine(ctx, " __tc_ui_last_tick = __tc_ui_now;");
323
+ appendSourceLine(ctx, " if (__tc_ui_delta > 250) __tc_ui_delta = 250;");
324
+ appendSourceLine(ctx, " ui_tick(static_cast<uint16_t>(__tc_ui_delta));");
325
+ if (loop) {
326
+ appendSourceLine(ctx, ` ${loop.postIteration}`);
327
+ appendSourceLine(ctx, ` }`);
268
328
  }
269
- // Drive the UI runtime each frame. Fires only in the driver function when
270
- // a UI is mounted (entryHasUI). Uses a separate gate from the async pump
271
- // so a pure-UI program (no async/timers) still animates. When the strategy
272
- // provides hostEventLoop, the per-frame work runs inside a while loop so a
273
- // host target (SDL) can pump events and present between ticks. Emitted
274
- // AFTER the function's init statements (ui_init/display_init/touch_init)
275
- // so initialization precedes the loop.
276
- if (entryHasUI() && fn.name === asyncDriverFn) {
277
- const loop = strategy.hostEventLoop?.();
278
- if (loop) {
279
- appendSourceLine(ctx, ` bool ${loop.flagName} = true;`);
280
- appendSourceLine(ctx, ` while (${loop.continueCondition}) {`);
281
- appendSourceLine(ctx, ` ${loop.preIteration}`);
329
+ }
330
+ if (isLoopDriver && (hasPromiseRuntime || asyncTaskClasses.length > 0 || usesTimers)) {
331
+ const taskNames = asyncTaskClasses.map(t => t.taskVarName);
332
+ const asyncConfig = strategy.getAsyncRuntimeConfig();
333
+ asyncConfig.hasPromiseRuntime = hasPromiseRuntime;
334
+ asyncConfig.hasTimers = usesTimers;
335
+ // Cooperative scheduler path (Phase 0): when opted in, replace the flat
336
+ // per-frame pump sequence with a single CoopSched dispatch. The work
337
+ // units mirror exactly what the strategy's asyncLoopInjection() would
338
+ // have emitted (so Zephyr, which omits the microtask pump, still omits
339
+ // it), but they are dispatched in priority order and budget-bounded.
340
+ if (asyncConfig.enablePriority || asyncConfig.enableTimeBudget) {
341
+ const coopUnits = buildDriverCoopUnits(taskNames, hasPromiseRuntime, usesTimers);
342
+ if (coopUnits.length > 0) {
343
+ const { injection } = buildCoopSchedInjection(coopUnits);
344
+ for (const line of injection)
345
+ appendSourceLine(ctx, line);
282
346
  }
283
- appendSourceLine(ctx, ` uint32_t __tc_ui_now = (uint32_t)${strategy.currentTimeMillis()};`);
284
- appendSourceLine(ctx, " static uint32_t __tc_ui_last_tick = __tc_ui_now;");
285
- appendSourceLine(ctx, " uint32_t __tc_ui_delta = __tc_ui_now - __tc_ui_last_tick;");
286
- appendSourceLine(ctx, " __tc_ui_last_tick = __tc_ui_now;");
287
- appendSourceLine(ctx, " if (__tc_ui_delta > 250) __tc_ui_delta = 250;");
288
- appendSourceLine(ctx, " ui_tick((uint16_t)__tc_ui_delta);");
289
- if (loop) {
290
- appendSourceLine(ctx, ` ${loop.postIteration}`);
291
- appendSourceLine(ctx, ` }`);
347
+ else {
348
+ // No work units: fall back to the strategy's own injection.
349
+ const injectionLines = strategy.asyncLoopInjection(taskNames, asyncConfig);
350
+ for (const line of injectionLines)
351
+ appendSourceLine(ctx, ` ${line}`);
292
352
  }
293
353
  }
294
- if (isLoopDriver && (hasPromiseRuntime || asyncTaskClasses.length > 0 || usesTimers)) {
295
- const taskNames = asyncTaskClasses.map(t => t.taskVarName);
296
- const asyncConfig = strategy.getAsyncRuntimeConfig();
297
- asyncConfig.hasPromiseRuntime = hasPromiseRuntime;
298
- asyncConfig.hasTimers = usesTimers;
354
+ else {
299
355
  const injectionLines = strategy.asyncLoopInjection(taskNames, asyncConfig);
300
356
  for (const line of injectionLines) {
301
357
  appendSourceLine(ctx, ` ${line}`);
302
358
  }
303
359
  }
304
- if (isLoopDriver && lastIsReturn) {
305
- appendRenderedStatement(ctx, lastStmt, " ", functionScope);
306
- }
360
+ }
361
+ if (isLoopDriver && lastIsReturn) {
362
+ appendRenderedStatement(ctx, lastStmt, " ", functionScope);
307
363
  }
308
364
  appendSourceLine(ctx, "}");
309
365
  emitCommentLines(fn.trailingComments, "", (line) => appendSourceLine(ctx, line));
@@ -338,16 +394,21 @@ export function emitFunctions(ctx) {
338
394
  export function emitFunctionForwardDeclarations(ctx) {
339
395
  const { strategy, effectiveEmitMode } = ctx;
340
396
  const excludedNames = new Set(strategy.forwardDeclarationExclusions?.() ?? []);
341
- // ISR callback forward declarations.
397
+ // Callback forward declarations (no IRAM_ATTR — ESP-IDF's attribute uses
398
+ // __COUNTER__, so decl+def would get conflicting .iram1.N sections).
342
399
  if (effectiveEmitMode !== "split") {
343
400
  for (const callback of ctx.callbackFunctions) {
344
- appendSourceLine(ctx, `${strategy.isrFunctionAttribute?.() ?? ""}${renderCallbackSignature(callback)};`);
401
+ appendSourceLine(ctx, `${renderCallbackSignature(callback)};`);
345
402
  }
346
403
  }
347
404
  let emittedAnyFn = false;
348
405
  for (const fn of ctx.mappedFunctions) {
349
406
  if (excludedNames.has(fn.name))
350
407
  continue;
408
+ // Async functions become *Task classes; calls lower to `fooTask.run()`.
409
+ // Forward-declaring empty stubs triggers -Wunused-function.
410
+ if (fn.isAsync && ctx.hasAsyncRuntime)
411
+ continue;
351
412
  const isExported = fn.isExported === true;
352
413
  // A function is an entrypoint if it is the strategy's primary entrypoint
353
414
  // (e.g. `setup` on Arduino, `main` on native) OR one of the platform's
@@ -426,3 +487,42 @@ export function emitFunctionForwardDeclarations(ctx) {
426
487
  appendSourceLine(ctx, "");
427
488
  }
428
489
  }
490
+ /**
491
+ * Build the CoopSched work units for the driver function's per-frame pumps.
492
+ *
493
+ * The units mirror what a strategy's asyncLoopInjection() emits as a flat
494
+ * sequence, so the cooperative scheduler preserves each strategy's decisions
495
+ * about WHICH pumps run (e.g. Zephyr omits the std::function microtask pump
496
+ * because its async path is state-machine-based). Default priorities:
497
+ * - async task .run() → priority 1 (latency-sensitive: drives awaits)
498
+ * - microtask pump → priority 1 (continuations of resolved promises)
499
+ * - cooperative timer pump → priority 0 (less latency-critical)
500
+ * Both are tunable via AsyncRuntimeConfig.schedulerPriorities; the scheduler
501
+ * bucket-walks from the highest level down to 0.
502
+ */
503
+ function buildDriverCoopUnits(taskVarNames, hasPromiseRuntime, hasTimers) {
504
+ const units = [];
505
+ let taskIdx = 0;
506
+ for (const name of taskVarNames) {
507
+ units.push({
508
+ name: `task_${taskIdx++}`,
509
+ body: `${name}.run();`,
510
+ priority: 1,
511
+ });
512
+ }
513
+ if (hasPromiseRuntime) {
514
+ units.push({
515
+ name: "microtasks",
516
+ body: "cuttlefish_pump_microtasks();",
517
+ priority: 1,
518
+ });
519
+ }
520
+ if (hasTimers) {
521
+ units.push({
522
+ name: "timers",
523
+ body: "__tc_timer_runtime.run();",
524
+ priority: 0,
525
+ });
526
+ }
527
+ return units;
528
+ }
@@ -1,7 +1,17 @@
1
1
  import { cloneEmissionScopeState, recordVariableType } from "../snprintf-helpers.js";
2
2
  import { emitCommentLines } from "../utils/index.js";
3
3
  import { escapeCppKeyword } from "../../utils/strings.js";
4
+ import { formatLinemarker, shouldEmitMarker, sourceKey } from "./line-marker.js";
4
5
  export function appendSourceLine(ctx, line, entry) {
6
+ // GDB debug mode: emit a linemarker before lines whose source span transitions.
7
+ // Runs only when an entry is present (raw braces/whitespace carry no span).
8
+ if (ctx.debugMode === 'gdb' && entry) {
9
+ const next = sourceKey(entry.tsSpan);
10
+ if (shouldEmitMarker(ctx.lastEmittedSource, next)) {
11
+ ctx.sourceLines.push(formatLinemarker(entry.tsSpan.filePath, entry.tsSpan.startLine));
12
+ }
13
+ ctx.lastEmittedSource = next;
14
+ }
5
15
  ctx.sourceLines.push(line);
6
16
  if (!entry)
7
17
  return;
@@ -17,6 +27,15 @@ export function appendSourceLine(ctx, line, entry) {
17
27
  });
18
28
  }
19
29
  export function appendHeaderLine(ctx, line, entry) {
30
+ // GDB debug mode: same transition logic as appendSourceLine, against the
31
+ // shared lastEmittedSource slot. See line-marker.ts for rationale.
32
+ if (ctx.debugMode === 'gdb' && entry) {
33
+ const next = sourceKey(entry.tsSpan);
34
+ if (shouldEmitMarker(ctx.lastEmittedSource, next)) {
35
+ ctx.headerLines.push(formatLinemarker(entry.tsSpan.filePath, entry.tsSpan.startLine));
36
+ }
37
+ ctx.lastEmittedSource = next;
38
+ }
20
39
  ctx.headerLines.push(line);
21
40
  if (!entry)
22
41
  return;
@@ -0,0 +1,38 @@
1
+ import type { SourceSpan } from '../../types.js';
2
+ /**
3
+ * A previously-emitted source location, used for transition detection so we
4
+ * emit a linemarker only when the source file or line actually changes.
5
+ */
6
+ export interface EmittedSource {
7
+ filePath: string;
8
+ /** 0-based source line (matches SourceSpan.startLine). */
9
+ line: number;
10
+ }
11
+ /**
12
+ * Format a GCC linemarker for the given path and 0-based source line.
13
+ * Output form: `# <line+1> "<path>"`.
14
+ *
15
+ * The path is normalized to forward slashes (Windows backslashes break GDB
16
+ * source resolution from the ESP-IDF build directory) and resolved to an
17
+ * absolute path so GDB finds the .ts file regardless of its cwd.
18
+ *
19
+ * GCC accepts both `#line N "F"` and the linemarker form `# N "F"`; we use
20
+ * the latter because it's what GCC emits in preprocessed output and it flows
21
+ * cleanly into DWARF.
22
+ */
23
+ export declare function formatLinemarker(filePath: string, line: number): string;
24
+ /**
25
+ * Decide whether a linemarker should be emitted before the next source line,
26
+ * given the previously-emitted marker (or null for the first one).
27
+ *
28
+ * Returns true only on file or line transition — this keeps the emitted C++
29
+ * clean (one marker per source-span change, not per emitted C++ line, which
30
+ * would be noisy and slow).
31
+ */
32
+ export declare function shouldEmitMarker(prev: EmittedSource | null, next: EmittedSource): boolean;
33
+ /**
34
+ * Extract the {filePath, line} for transition tracking from a SourceSpan.
35
+ * SourceSpan.startLine is 0-based; kept 0-based here so shouldEmitMarker
36
+ * compares apples to apples (formatLinemarker does the +1 at format time).
37
+ */
38
+ export declare function sourceKey(span: SourceSpan): EmittedSource;
@@ -0,0 +1,39 @@
1
+ import { resolve } from 'node:path';
2
+ /**
3
+ * Format a GCC linemarker for the given path and 0-based source line.
4
+ * Output form: `# <line+1> "<path>"`.
5
+ *
6
+ * The path is normalized to forward slashes (Windows backslashes break GDB
7
+ * source resolution from the ESP-IDF build directory) and resolved to an
8
+ * absolute path so GDB finds the .ts file regardless of its cwd.
9
+ *
10
+ * GCC accepts both `#line N "F"` and the linemarker form `# N "F"`; we use
11
+ * the latter because it's what GCC emits in preprocessed output and it flows
12
+ * cleanly into DWARF.
13
+ */
14
+ export function formatLinemarker(filePath, line) {
15
+ const absolute = resolve(filePath);
16
+ const normalized = absolute.split(/[\\/]/).join('/');
17
+ return `# ${line + 1} "${normalized}"`;
18
+ }
19
+ /**
20
+ * Decide whether a linemarker should be emitted before the next source line,
21
+ * given the previously-emitted marker (or null for the first one).
22
+ *
23
+ * Returns true only on file or line transition — this keeps the emitted C++
24
+ * clean (one marker per source-span change, not per emitted C++ line, which
25
+ * would be noisy and slow).
26
+ */
27
+ export function shouldEmitMarker(prev, next) {
28
+ if (prev === null)
29
+ return true;
30
+ return prev.filePath !== next.filePath || prev.line !== next.line;
31
+ }
32
+ /**
33
+ * Extract the {filePath, line} for transition tracking from a SourceSpan.
34
+ * SourceSpan.startLine is 0-based; kept 0-based here so shouldEmitMarker
35
+ * compares apples to apples (formatLinemarker does the +1 at format time).
36
+ */
37
+ export function sourceKey(span) {
38
+ return { filePath: span.filePath, line: span.startLine };
39
+ }
@@ -1,4 +1,10 @@
1
1
  import type { GeneratedOutputs } from "../../types.js";
2
2
  import type { EmitterContext } from "./emitter-context.js";
3
3
  export declare function emitPreamble(ctx: EmitterContext): void;
4
+ /**
5
+ * Emit cooperative async state-machine classes. Must run AFTER top-level
6
+ * globals (emitTypeDeclarations) so identifiers like WIFI_SSID referenced
7
+ * from task bodies are already declared.
8
+ */
9
+ export declare function emitAsyncTaskClasses(ctx: EmitterContext): void;
4
10
  export declare function finalizeOutput(ctx: EmitterContext): GeneratedOutputs;
@@ -1,8 +1,11 @@
1
1
  import path from "node:path";
2
- import { writeText } from "../../utils/fs.js";
2
+ import { writeText, readText } from "../../utils/fs.js";
3
3
  import { makeGeneratedMap, writeSourceMap } from "../../mapping/source-map.js";
4
4
  import { dedupe, hasConsoleCalls, resolveTranspiledModuleInclude } from "../utils/index.js";
5
5
  import { appendHeaderLine } from "./line-appender.js";
6
+ import { runSelfCheck } from "../compliance/rule-engine.js";
7
+ import { renderRegistryJson } from "../compliance/deviation-writer.js";
8
+ import { renderArxml } from "../compliance/arxml-writer.js";
6
9
  /** Derives a unique C preprocessor guard name from a source file path. */
7
10
  function sanitizeGuardName(filePath) {
8
11
  // Use the basename minus extension, uppercased, with non-alphanumeric
@@ -26,7 +29,7 @@ function polyfillDefinitionGuard(definition) {
26
29
  return null;
27
30
  }
28
31
  export function emitPreamble(ctx) {
29
- const { strategy, effectiveEmitMode, program, shimLines, emittedPolyfills, asyncTaskClasses, includes, programAnalysis } = ctx;
32
+ const { strategy, effectiveEmitMode, program, shimLines, emittedPolyfills, includes, programAnalysis } = ctx;
30
33
  for (const include of dedupe(includes)) {
31
34
  appendSourceLineLocal(ctx, `#include ${include}`);
32
35
  }
@@ -73,16 +76,8 @@ export function emitPreamble(ctx) {
73
76
  }
74
77
  appendSourceLineLocal(ctx, "");
75
78
  }
76
- if (asyncTaskClasses.length > 0) {
77
- for (const { classDef, instanceDecl } of asyncTaskClasses) {
78
- for (const line of classDef.split("\n")) {
79
- appendSourceLineLocal(ctx, line);
80
- }
81
- appendSourceLineLocal(ctx, "");
82
- appendSourceLineLocal(ctx, instanceDecl);
83
- appendSourceLineLocal(ctx, "");
84
- }
85
- }
79
+ // Async task classes are emitted later (emitAsyncTaskClasses) AFTER top-level
80
+ // globals so references like WIFI_SSID inside the state machine compile.
86
81
  if (strategy.needsVectorOverload() && hasConsoleCalls(program, strategy) && (programAnalysis.usesVectorTypes || programAnalysis.hasArrayInObjectLiteral)) {
87
82
  appendSourceLineLocal(ctx, "template <typename T>");
88
83
  appendSourceLineLocal(ctx, "std::ostream& operator<<(std::ostream& os, const std::vector<T>& values)");
@@ -136,6 +131,24 @@ export function emitPreamble(ctx) {
136
131
  appendSourceLineLocal(ctx, "");
137
132
  }
138
133
  }
134
+ /**
135
+ * Emit cooperative async state-machine classes. Must run AFTER top-level
136
+ * globals (emitTypeDeclarations) so identifiers like WIFI_SSID referenced
137
+ * from task bodies are already declared.
138
+ */
139
+ export function emitAsyncTaskClasses(ctx) {
140
+ const { asyncTaskClasses } = ctx;
141
+ if (asyncTaskClasses.length === 0)
142
+ return;
143
+ for (const { classDef, instanceDecl } of asyncTaskClasses) {
144
+ for (const line of classDef.split("\n")) {
145
+ appendSourceLineLocal(ctx, line);
146
+ }
147
+ appendSourceLineLocal(ctx, "");
148
+ appendSourceLineLocal(ctx, instanceDecl);
149
+ appendSourceLineLocal(ctx, "");
150
+ }
151
+ }
139
152
  export function finalizeOutput(ctx) {
140
153
  const { program, strategy, options, effectiveEmitMode, isNpmPackage, baseName, includes, shimLines, emittedPolyfills } = ctx;
141
154
  const outDir = options.outDir;
@@ -252,6 +265,82 @@ export function finalizeOutput(ctx) {
252
265
  : undefined;
253
266
  const diagnostics = [...program.diagnostics, ...ctx.profileDiagnostics, ...ctx.emitDiagnostics];
254
267
  diagnostics.push(...strategy.emitDiagnostics(options.emitMode));
268
+ // ── AUTOSAR compliance self-check + sidecar ────────────────────────────
269
+ // Runs only when --autosar is warn or strict. Emits an AUTOSAR_<ruleId>
270
+ // diagnostic per finding (severity: error for required unrecorded
271
+ // violations in strict mode, otherwise warning) and writes the sidecar
272
+ // deviation registry next to the emitted artifact.
273
+ if (ctx.compliance.isEnabled()) {
274
+ // Scan the ACTUAL file content (post-writeText) rather than ctx.sourceLines,
275
+ // because some ctx.sourceLines entries contain embedded newlines (e.g. ESP32
276
+ // shim blocks pushed as multi-line strings), making array indices != file
277
+ // line numbers. Reading the written file gives correct 1:1 line numbers.
278
+ const actualSourceLines = outputHeaderPath
279
+ ? readText(sourcePath).split("\n")
280
+ : readText(sourcePath).split("\n");
281
+ const actualHeaderLines = outputHeaderPath
282
+ ? readText(outputHeaderPath).split("\n")
283
+ : [];
284
+ const findings = runSelfCheck(ctx.compliance, actualSourceLines, actualHeaderLines);
285
+ const mode = ctx.compliance.mode();
286
+ // Helper: map a C++ generated line back to its originating TS source
287
+ // line via the source map, so diagnostics point at the user's code.
288
+ const mapCppLineToTs = (cppLine) => {
289
+ // Find the source-map entry whose generated range covers cppLine.
290
+ // Entries are ordered by generatedStartLine; find the last one whose
291
+ // start is ≤ cppLine.
292
+ let best = null;
293
+ for (const entry of ctx.sourceMapEntries) {
294
+ if (entry.generatedStartLine <= cppLine) {
295
+ if (!best || entry.generatedStartLine > best.generatedStartLine) {
296
+ best = entry;
297
+ }
298
+ }
299
+ }
300
+ if (best) {
301
+ return { filePath: best.tsSpan.filePath, line: best.tsSpan.startLine };
302
+ }
303
+ return null;
304
+ };
305
+ for (const f of findings) {
306
+ const isError = mode === "strict" &&
307
+ f.kind === "unrecorded-violation" &&
308
+ f.severity === "required";
309
+ // Map the C++ line back to TS so the diagnostic points at user code.
310
+ const tsLoc = mapCppLineToTs(f.line);
311
+ const emittedFile = f.file === "header" ? `${baseName}.h` : path.basename(sourcePath);
312
+ const cppRef = `${emittedFile}:${f.line}`;
313
+ const tsRef = tsLoc ? `${path.basename(tsLoc.filePath)}:${tsLoc.line}` : null;
314
+ const locationDesc = tsRef
315
+ ? `${tsRef} (emitted at ${cppRef})`
316
+ : cppRef;
317
+ const diag = {
318
+ severity: isError ? "error" : "warning",
319
+ code: `AUTOSAR_${f.ruleId}`,
320
+ message: `AUTOSAR ${f.ruleId} ${f.kind} at ${locationDesc}: ${f.snippet}`,
321
+ // Point the diagnostic at the TS source line (if mapped) so the
322
+ // CLI's file:line:col display is meaningful to the user. If the
323
+ // violation is in framework/runtime shim code with no TS origin,
324
+ // point at the emitted C++ artifact instead.
325
+ filePath: tsLoc?.filePath ?? sourcePath,
326
+ line: tsLoc?.line ?? f.line,
327
+ column: 1,
328
+ };
329
+ diagnostics.push(diag);
330
+ }
331
+ // Sidecar deviation registry — written for both warn and strict modes
332
+ // alongside the emitted artifact (mirrors how .thcppmap.json sits next
333
+ // to the .cpp/.ino today).
334
+ const toolVersion = options.toolVersion ?? "unknown";
335
+ const registryPath = path.join(outDir, `${baseName}.autosar-deviations.json`);
336
+ writeText(registryPath, renderRegistryJson(ctx.compliance, path.basename(sourcePath), toolVersion));
337
+ // Optional ARXML projection (Artop/DaVinci tooling). Gated behind
338
+ // --autosar-arxml so projects that don't need it pay no cost.
339
+ if (options.autosarArxml) {
340
+ const arxmlPath = path.join(outDir, `${baseName}.autosar-deviations.arxml`);
341
+ writeText(arxmlPath, renderArxml(ctx.compliance, path.basename(sourcePath)));
342
+ }
343
+ }
255
344
  return {
256
345
  headerPath: outputHeaderPath,
257
346
  sourcePath,
@@ -1,3 +1,20 @@
1
1
  import type { ProgramIR } from "../../api/index.js";
2
2
  import type { EmitterContext, EmitterOptions } from "./emitter-context.js";
3
+ /**
4
+ * Detect whether a program uses @typecad/safety. The safety transform pass
5
+ * injects safety.* ops when in use; strategies consult this to decide
6
+ * whether to emit the __tc_gpio_read shim that the safety voter calls.
7
+ * Mirrors programUsesWdt() above (same inline walker pattern).
8
+ *
9
+ * `hal-expr` (the expression-position safety.read_safe lowering produced
10
+ * by `const r = safe.read(pin)`) lives in expression trees, not at statement
11
+ * level — but it's carried inside a var_decl statement whose initializer is
12
+ * the hal-expr, and the var_decl gets visited as a top-level statement. We
13
+ * detect both: statement-level `hal-op` operations, and any statement whose
14
+ * rendered initializer text mentions safety.* (a defensive catch for the
15
+ * hal-expr-in-initializer case). The latter is rarely needed because the
16
+ * voter's read_safe op is also captured at the call site, but kept for
17
+ * robustness.
18
+ */
19
+ export declare function programUsesSafety(program: ProgramIR): boolean;
3
20
  export declare function buildEmitterContext(program: ProgramIR, options: EmitterOptions): EmitterContext;