@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.
- package/dist/api/config.d.ts +32 -0
- package/dist/api/index.d.ts +1 -1
- package/dist/api/schema/types.d.ts +15 -0
- package/dist/api/shared/async-runtime-static.d.ts +12 -1
- package/dist/api/shared/async-runtime-static.js +69 -24
- package/dist/api/shared/async-symbol-detector.d.ts +11 -0
- package/dist/api/shared/async-symbol-detector.js +140 -0
- package/dist/api/shared/async-types.d.ts +24 -0
- package/dist/api/shared/coop-scheduler.d.ts +60 -0
- package/dist/api/shared/coop-scheduler.js +149 -0
- package/dist/api/shared/display-adapter.d.ts +4 -3
- package/dist/api/shared/display-adapter.js +15 -118
- package/dist/api/shared/display-adapters/sdl.js +38 -31
- package/dist/api/shared/display-profile.d.ts +47 -25
- package/dist/api/shared/display-profile.js +31 -164
- package/dist/api/shared/framework-manifest-registry.d.ts +9 -0
- package/dist/api/shared/framework-manifest-registry.js +27 -0
- package/dist/api/shared/framework-manifest.d.ts +581 -0
- package/dist/api/shared/framework-manifest.js +182 -0
- package/dist/api/shared/glcdfont.d.ts +12 -0
- package/dist/api/shared/glcdfont.js +124 -0
- package/dist/api/shared/graphics-strategy.d.ts +37 -0
- package/dist/api/shared/hal-op-ir.d.ts +755 -1
- package/dist/api/shared/hal-op-ir.js +129 -1
- package/dist/api/shared/index.d.ts +20 -2
- package/dist/api/shared/index.js +25 -1
- package/dist/api/shared/ir-core.d.ts +4 -0
- package/dist/api/shared/ir-declarations.d.ts +6 -0
- package/dist/api/shared/native-display-op-resolver.d.ts +10 -0
- package/dist/api/shared/native-display-op-resolver.js +64 -0
- package/dist/api/shared/platform-strategy.d.ts +113 -2
- package/dist/api/shared/polyfill-helper-registry.js +18 -1
- package/dist/api/shared/promise-runtime.d.ts +1 -1
- package/dist/api/shared/promise-runtime.js +95 -13
- package/dist/api/shared/toolchain-types.d.ts +17 -0
- package/dist/api/shared/validate-framework-manifest.d.ts +28 -0
- package/dist/api/shared/validate-framework-manifest.js +494 -0
- package/dist/api/shared/worker-runtime-polyfill.d.ts +22 -0
- package/dist/api/shared/worker-runtime-polyfill.js +34 -0
- package/dist/api/shared/worker-runtime.d.ts +69 -0
- package/dist/api/shared/worker-runtime.js +163 -0
- package/dist/cli.js +186 -82
- package/dist/config-loader.d.ts +7 -2
- package/dist/config-loader.js +23 -3
- package/dist/config-schema.d.ts +112 -70
- package/dist/config-schema.js +14 -0
- package/dist/contract/board-generator.d.ts +17 -0
- package/dist/contract/board-generator.js +57 -0
- package/dist/contract/contract-parser.d.ts +217 -0
- package/dist/contract/contract-parser.js +224 -0
- package/dist/contract/index.d.ts +22 -0
- package/dist/contract/index.js +68 -0
- package/dist/create/board-codegen.js +4 -4
- package/dist/create/board-generators.js +4 -5
- package/dist/create/board-spec.d.ts +72 -75
- package/dist/create/board-spec.js +0 -1
- package/dist/create/init-scaffold.d.ts +9 -2
- package/dist/create/init-scaffold.js +0 -14
- package/dist/create/init-templates.d.ts +2 -0
- package/dist/create/init-templates.js +74 -8
- package/dist/create/init-wizard.js +31 -7
- package/dist/debug/preprocessor.js +178 -39
- package/dist/debug/types.d.ts +23 -0
- package/dist/diagnostics/mermaid-builder.d.ts +1 -1
- package/dist/diagnostics/mermaid-builder.js +34 -24
- package/dist/emit/compliance/arxml-writer.d.ts +11 -0
- package/dist/emit/compliance/arxml-writer.js +34 -0
- package/dist/emit/compliance/compliance-context.d.ts +57 -0
- package/dist/emit/compliance/compliance-context.js +113 -0
- package/dist/emit/compliance/deviation-ledger.d.ts +27 -0
- package/dist/emit/compliance/deviation-ledger.js +47 -0
- package/dist/emit/compliance/deviation-writer.d.ts +30 -0
- package/dist/emit/compliance/deviation-writer.js +37 -0
- package/dist/emit/compliance/index.d.ts +7 -0
- package/dist/emit/compliance/index.js +6 -0
- package/dist/emit/compliance/rule-engine.d.ts +13 -0
- package/dist/emit/compliance/rule-engine.js +101 -0
- package/dist/emit/compliance/rules.d.ts +16 -0
- package/dist/emit/compliance/rules.js +191 -0
- package/dist/emit/compliance/types.d.ts +59 -0
- package/dist/emit/compliance/types.js +8 -0
- package/dist/emit/cpp-emitter.js +4 -3
- package/dist/emit/emitters/class-emitter.js +6 -1
- package/dist/emit/emitters/emitter-context.d.ts +28 -1
- package/dist/emit/emitters/function-emitter-impl.js +153 -53
- package/dist/emit/emitters/line-appender.js +19 -0
- package/dist/emit/emitters/line-marker.d.ts +38 -0
- package/dist/emit/emitters/line-marker.js +39 -0
- package/dist/emit/emitters/output-finalizer.d.ts +6 -0
- package/dist/emit/emitters/output-finalizer.js +101 -12
- package/dist/emit/emitters/setup.d.ts +17 -0
- package/dist/emit/emitters/setup.js +257 -36
- package/dist/emit/emitters/top-level-prep.js +8 -0
- package/dist/emit/emitters/type-decl-emitter.js +28 -3
- package/dist/emit/emitters/ui-emitter.js +33 -9
- package/dist/emit/expression-renderer.d.ts +1 -1
- package/dist/emit/expression-renderer.js +91 -6
- package/dist/emit/route-hal-op.js +18 -5
- package/dist/emit/snprintf-helpers.js +15 -4
- package/dist/emit/statement-renderer.d.ts +10 -0
- package/dist/emit/statement-renderer.js +34 -5
- package/dist/emit/utils/async-state-machine.js +221 -125
- package/dist/emit/utils/hal-op-cpp-type.d.ts +6 -0
- package/dist/emit/utils/hal-op-cpp-type.js +40 -0
- package/dist/framework-package.js +2 -0
- package/dist/framework-registry.d.ts +17 -0
- package/dist/ir/adc-range-validation.js +14 -1
- package/dist/ir/build-ir-state.d.ts +1 -0
- package/dist/ir/build-ir-state.js +14 -0
- package/dist/ir/build-ir.js +9 -5
- package/dist/ir/call-graph.js +16 -0
- package/dist/ir/expression-to-ir.js +164 -0
- package/dist/ir/feature-registry.js +7 -25
- package/dist/ir/function-builder.js +22 -0
- package/dist/ir/hal/hal-emitter.d.ts +5 -2
- package/dist/ir/hal/hal-emitter.js +50 -18
- package/dist/ir/hal/hal-parser.d.ts +6 -0
- package/dist/ir/hal/hal-parser.js +81 -0
- package/dist/ir/hal/hal-plugins.js +723 -1
- package/dist/ir/identifier-collector.js +35 -0
- package/dist/ir/interrupt-analysis.d.ts +5 -1
- package/dist/ir/interrupt-analysis.js +8 -16
- package/dist/ir/network-validation.d.ts +4 -0
- package/dist/ir/network-validation.js +184 -0
- package/dist/ir/ownership-analysis.js +20 -1
- package/dist/ir/pin-capability-validation.js +37 -0
- package/dist/ir/pin-mode-validation.d.ts +2 -2
- package/dist/ir/pin-mode-validation.js +49 -18
- package/dist/ir/program-analysis.d.ts +65 -0
- package/dist/ir/program-analysis.js +318 -2
- package/dist/ir/render-expr.js +11 -0
- package/dist/ir/timing-validation.d.ts +6 -1
- package/dist/ir/timing-validation.js +60 -13
- package/dist/ir/transformers/call-statement.js +105 -0
- package/dist/ir/transformers/expressions.js +62 -0
- package/dist/ir/transformers/hal-call-resolver.js +21 -0
- package/dist/ir/transformers/hal-emit-helpers.js +1 -1
- package/dist/ir/transformers/namespace-methods.js +17 -12
- package/dist/ir/transformers/ui-reactive.js +2 -2
- package/dist/ir/transformers/variables.js +92 -3
- package/dist/ir/type-resolution.js +18 -0
- package/dist/ir/ui-element-auto-wire.js +7 -5
- package/dist/ir/utils/map-statements.d.ts +4 -0
- package/dist/ir/utils/map-statements.js +79 -0
- package/dist/ir/validation-orchestrator.js +9 -2
- package/dist/ir/worker-analysis.d.ts +10 -0
- package/dist/ir/worker-analysis.js +261 -0
- package/dist/libdef/c-to-decl.d.ts +27 -0
- package/dist/libdef/c-to-decl.js +397 -0
- package/dist/libdef/component-decls.d.ts +2 -0
- package/dist/libdef/component-decls.js +6 -0
- package/dist/libdef/component-discovery.d.ts +43 -0
- package/dist/libdef/component-discovery.js +83 -0
- package/dist/libdef/cpp-to-decl.d.ts +9 -0
- package/dist/libdef/cpp-to-decl.js +72 -0
- package/dist/libdef/registry.js +5 -2
- package/dist/lint-cache.d.ts +59 -0
- package/dist/lint-cache.js +257 -0
- package/dist/orchestrator/graph-builder.js +14 -9
- package/dist/platform/coop-scheduler-runtime.d.ts +19 -0
- package/dist/platform/coop-scheduler-runtime.js +41 -0
- package/dist/platform/generic-strategy.d.ts +15 -3
- package/dist/platform/generic-strategy.js +49 -4
- package/dist/safety/safety-bridge.d.ts +11 -0
- package/dist/safety/safety-bridge.js +48 -0
- package/dist/safety/sidecar-bridge.d.ts +5 -0
- package/dist/safety/sidecar-bridge.js +22 -0
- package/dist/safety-hook.d.ts +79 -0
- package/dist/safety-hook.js +35 -0
- package/dist/testing.d.ts +7 -1
- package/dist/testing.js +8 -1
- package/dist/transpile.d.ts +3 -0
- package/dist/transpile.js +175 -41
- package/dist/types.d.ts +13 -2
- package/dist/ui-hook.d.ts +17 -3
- package/dist/utils/cli.js +78 -4
- package/dist/utils/fs.d.ts +13 -0
- package/dist/utils/fs.js +50 -0
- package/package.json +24 -5
- package/dist/api/shared/display-adapters/eink-mono.d.ts +0 -2
- package/dist/api/shared/display-adapters/eink-mono.js +0 -53
- package/dist/api/shared/display-adapters/ssd1309.d.ts +0 -2
- package/dist/api/shared/display-adapters/ssd1309.js +0 -136
- package/dist/api/shared/display-adapters/st7796.d.ts +0 -2
- package/dist/api/shared/display-adapters/st7796.js +0 -132
- package/dist/licenses.d.ts +0 -185
- 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
|
-
|
|
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, `${
|
|
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.
|
|
250
|
-
|
|
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
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
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
|
-
|
|
259
|
-
ctx
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
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
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
//
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
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
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
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
|
-
|
|
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
|
-
|
|
305
|
-
|
|
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
|
-
//
|
|
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, `${
|
|
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,
|
|
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
|
-
|
|
77
|
-
|
|
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;
|