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

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 (167) 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 +125 -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/install/framework-catalog.d.ts +53 -0
  98. package/dist/install/framework-catalog.js +107 -0
  99. package/dist/install/handle-install.d.ts +35 -0
  100. package/dist/install/handle-install.js +177 -0
  101. package/dist/install/index.d.ts +4 -0
  102. package/dist/install/index.js +3 -0
  103. package/dist/ir/adc-range-validation.js +14 -1
  104. package/dist/ir/build-ir-state.d.ts +1 -0
  105. package/dist/ir/build-ir-state.js +14 -0
  106. package/dist/ir/build-ir.js +4 -4
  107. package/dist/ir/call-graph.js +16 -0
  108. package/dist/ir/expression-to-ir.js +150 -0
  109. package/dist/ir/function-builder.js +22 -0
  110. package/dist/ir/hal/hal-emitter.js +10 -6
  111. package/dist/ir/hal/hal-parser.js +16 -9
  112. package/dist/ir/hal/hal-plugins.js +164 -3
  113. package/dist/ir/identifier-collector.js +21 -4
  114. package/dist/ir/interrupt-analysis.d.ts +5 -1
  115. package/dist/ir/interrupt-analysis.js +8 -16
  116. package/dist/ir/ownership-analysis.js +6 -0
  117. package/dist/ir/pin-capability-validation.js +37 -0
  118. package/dist/ir/pin-mode-validation.d.ts +2 -2
  119. package/dist/ir/pin-mode-validation.js +49 -18
  120. package/dist/ir/program-analysis.d.ts +42 -0
  121. package/dist/ir/program-analysis.js +243 -6
  122. package/dist/ir/render-expr.js +11 -0
  123. package/dist/ir/timing-validation.js +11 -2
  124. package/dist/ir/transformers/call-statement.js +105 -0
  125. package/dist/ir/transformers/expressions.js +4 -0
  126. package/dist/ir/transformers/hal-call-resolver.js +21 -0
  127. package/dist/ir/transformers/namespace-methods.js +17 -12
  128. package/dist/ir/transformers/ui-reactive.js +2 -2
  129. package/dist/ir/transformers/variables.js +51 -0
  130. package/dist/ir/type-resolution.js +18 -0
  131. package/dist/ir/ui-element-auto-wire.js +7 -5
  132. package/dist/ir/utils/map-statements.d.ts +4 -0
  133. package/dist/ir/utils/map-statements.js +79 -0
  134. package/dist/ir/validation-orchestrator.js +6 -1
  135. package/dist/ir/worker-analysis.d.ts +10 -0
  136. package/dist/ir/worker-analysis.js +261 -0
  137. package/dist/orchestrator/graph-builder.js +8 -7
  138. package/dist/platform/coop-scheduler-runtime.d.ts +19 -0
  139. package/dist/platform/coop-scheduler-runtime.js +41 -0
  140. package/dist/platform/generic-strategy.d.ts +15 -3
  141. package/dist/platform/generic-strategy.js +49 -4
  142. package/dist/safety/safety-bridge.d.ts +11 -0
  143. package/dist/safety/safety-bridge.js +48 -0
  144. package/dist/safety/sidecar-bridge.d.ts +5 -0
  145. package/dist/safety/sidecar-bridge.js +22 -0
  146. package/dist/safety-hook.d.ts +79 -0
  147. package/dist/safety-hook.js +35 -0
  148. package/dist/stores/display-profile-store.d.ts +0 -1
  149. package/dist/stores/display-profile-store.js +0 -1
  150. package/dist/testing.d.ts +4 -0
  151. package/dist/testing.js +5 -0
  152. package/dist/transpile.js +124 -35
  153. package/dist/types.d.ts +22 -2
  154. package/dist/ui-hook.d.ts +0 -2
  155. package/dist/utils/cli.d.ts +2 -2
  156. package/dist/utils/cli.js +68 -3
  157. package/package.json +20 -5
  158. package/dist/api/shared/display-adapters/eink-mono.d.ts +0 -2
  159. package/dist/api/shared/display-adapters/eink-mono.js +0 -53
  160. package/dist/api/shared/display-adapters/ssd1309.d.ts +0 -2
  161. package/dist/api/shared/display-adapters/ssd1309.js +0 -136
  162. package/dist/api/shared/display-adapters/st7796.d.ts +0 -2
  163. package/dist/api/shared/display-adapters/st7796.js +0 -132
  164. package/dist/libdef/idf-discovery.d.ts +0 -7
  165. package/dist/libdef/idf-discovery.js +0 -59
  166. package/dist/licenses.d.ts +0 -185
  167. 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) {
@@ -240,6 +247,29 @@ export function emitFunctions(ctx) {
240
247
  ctx.sourceMapEntries = ctx.headerMapEntries;
241
248
  ctx.headerMapEntries = _swapMaps;
242
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
+ }
243
273
  emitCommentLines(fn.leadingComments, "", (line) => appendSourceLine(ctx, line));
244
274
  if (fn.typeParameters && fn.typeParameters.length > 0) {
245
275
  appendSourceLine(ctx, `template<typename ${fn.typeParameters.join(", typename ")}>`);
@@ -286,12 +316,12 @@ export function emitFunctions(ctx) {
286
316
  appendSourceLine(ctx, ` while (${loop.continueCondition}) {`);
287
317
  appendSourceLine(ctx, ` ${loop.preIteration}`);
288
318
  }
289
- appendSourceLine(ctx, ` uint32_t __tc_ui_now = (uint32_t)${strategy.currentTimeMillis()};`);
319
+ appendSourceLine(ctx, ` uint32_t __tc_ui_now = static_cast<uint32_t>(${strategy.currentTimeMillis()});`);
290
320
  appendSourceLine(ctx, " static uint32_t __tc_ui_last_tick = __tc_ui_now;");
291
321
  appendSourceLine(ctx, " uint32_t __tc_ui_delta = __tc_ui_now - __tc_ui_last_tick;");
292
322
  appendSourceLine(ctx, " __tc_ui_last_tick = __tc_ui_now;");
293
323
  appendSourceLine(ctx, " if (__tc_ui_delta > 250) __tc_ui_delta = 250;");
294
- appendSourceLine(ctx, " ui_tick((uint16_t)__tc_ui_delta);");
324
+ appendSourceLine(ctx, " ui_tick(static_cast<uint16_t>(__tc_ui_delta));");
295
325
  if (loop) {
296
326
  appendSourceLine(ctx, ` ${loop.postIteration}`);
297
327
  appendSourceLine(ctx, ` }`);
@@ -302,9 +332,30 @@ export function emitFunctions(ctx) {
302
332
  const asyncConfig = strategy.getAsyncRuntimeConfig();
303
333
  asyncConfig.hasPromiseRuntime = hasPromiseRuntime;
304
334
  asyncConfig.hasTimers = usesTimers;
305
- const injectionLines = strategy.asyncLoopInjection(taskNames, asyncConfig);
306
- for (const line of injectionLines) {
307
- appendSourceLine(ctx, ` ${line}`);
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);
346
+ }
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}`);
352
+ }
353
+ }
354
+ else {
355
+ const injectionLines = strategy.asyncLoopInjection(taskNames, asyncConfig);
356
+ for (const line of injectionLines) {
357
+ appendSourceLine(ctx, ` ${line}`);
358
+ }
308
359
  }
309
360
  }
310
361
  if (isLoopDriver && lastIsReturn) {
@@ -436,3 +487,42 @@ export function emitFunctionForwardDeclarations(ctx) {
436
487
  appendSourceLine(ctx, "");
437
488
  }
438
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,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
@@ -262,6 +265,82 @@ export function finalizeOutput(ctx) {
262
265
  : undefined;
263
266
  const diagnostics = [...program.diagnostics, ...ctx.profileDiagnostics, ...ctx.emitDiagnostics];
264
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
+ }
265
344
  return {
266
345
  headerPath: outputHeaderPath,
267
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;