@typecad/cuttlefish 0.1.0-alpha.2 → 1.0.0-alpha.11
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/README.md +10 -10
- package/dist/api/board-types.d.ts +1 -1
- package/dist/api/config.d.ts +38 -2
- 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 +595 -0
- package/dist/api/shared/framework-manifest.js +193 -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 +764 -1
- package/dist/api/shared/hal-op-ir.js +129 -1
- package/dist/api/shared/index.d.ts +22 -2
- package/dist/api/shared/index.js +26 -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 +119 -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/spdx-licenses.d.ts +139 -0
- package/dist/api/shared/spdx-licenses.js +385 -0
- package/dist/api/shared/toolchain-types.d.ts +17 -0
- package/dist/api/shared/types.d.ts +8 -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-utils.d.ts +1 -0
- package/dist/cli-utils.js +3 -1
- package/dist/cli.js +249 -32
- 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/framework-catalog.d.ts +64 -0
- package/dist/create/framework-catalog.js +136 -0
- package/dist/create/index.d.ts +5 -1
- package/dist/create/index.js +3 -1
- package/dist/create/init-scaffold.d.ts +12 -3
- package/dist/create/init-scaffold.js +49 -14
- package/dist/create/init-templates.d.ts +4 -0
- package/dist/create/init-templates.js +271 -17
- package/dist/create/init-wizard.js +41 -27
- package/dist/create/install-deps.d.ts +32 -0
- package/dist/create/install-deps.js +46 -0
- 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 +372 -30
- 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/install/framework-catalog.d.ts +53 -0
- package/dist/install/framework-catalog.js +107 -0
- package/dist/install/handle-install.d.ts +35 -0
- package/dist/install/handle-install.js +177 -0
- package/dist/install/index.d.ts +4 -0
- package/dist/install/index.js +3 -0
- package/dist/ir/adc-range-validation.js +40 -26
- 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 +207 -6
- 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 +733 -1
- package/dist/ir/identifier-collector.js +35 -0
- package/dist/ir/interrupt-analysis.d.ts +5 -1
- package/dist/ir/interrupt-analysis.js +16 -19
- package/dist/ir/memory-budget-validation.js +1 -0
- package/dist/ir/network-validation.d.ts +4 -0
- package/dist/ir/network-validation.js +184 -0
- package/dist/ir/ownership-analysis.js +39 -1
- package/dist/ir/peripheral-ownership.js +5 -0
- package/dist/ir/peripheral-validation.d.ts +1 -1
- package/dist/ir/peripheral-validation.js +6 -3
- package/dist/ir/pin-alias-conflict.d.ts +1 -1
- package/dist/ir/pin-alias-conflict.js +2 -1
- package/dist/ir/pin-capability-validation.js +71 -32
- package/dist/ir/pin-mode-validation.d.ts +2 -2
- package/dist/ir/pin-mode-validation.js +54 -18
- package/dist/ir/pin-safety.d.ts +1 -1
- package/dist/ir/pin-safety.js +2 -1
- package/dist/ir/program-analysis.d.ts +81 -0
- package/dist/ir/program-analysis.js +429 -0
- package/dist/ir/pulldown-validation.d.ts +1 -1
- package/dist/ir/pulldown-validation.js +2 -1
- package/dist/ir/pwm-timer-sharing.d.ts +1 -1
- package/dist/ir/pwm-timer-sharing.js +2 -1
- package/dist/ir/render-expr.js +11 -0
- package/dist/ir/resource-analysis.js +2 -0
- package/dist/ir/timer0-pwm-timing-conflict.d.ts +1 -1
- package/dist/ir/timer0-pwm-timing-conflict.js +2 -1
- package/dist/ir/timing-validation.d.ts +6 -1
- package/dist/ir/timing-validation.js +61 -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 +137 -19
- package/dist/ir/try-catch-validation.js +2 -0
- package/dist/ir/type-resolution.js +20 -2
- package/dist/ir/ui-element-auto-wire.js +7 -5
- package/dist/ir/unit-suspicion-validation.js +9 -7
- 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 +15 -8
- 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 +8 -2
- package/dist/testing.js +9 -2
- package/dist/transpile.d.ts +3 -0
- package/dist/transpile.js +191 -49
- package/dist/types.d.ts +22 -3
- package/dist/ui-hook.d.ts +17 -3
- package/dist/utils/cli.js +123 -2
- package/dist/utils/fs.d.ts +13 -0
- package/dist/utils/fs.js +50 -0
- package/dist/utils/ui.js +3 -1
- package/package.json +23 -3
- 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/ir/heap-array-validation.d.ts +0 -24
- package/dist/ir/heap-array-validation.js +0 -29
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { filterPolyfillHelpers, isStringEnum } from "../../api/shared/index.js";
|
|
3
|
+
import { hasSafetyHook, requireSafetyHook } from "../../safety-hook.js";
|
|
3
4
|
import { analyzeProgram } from "../../ir/program-analysis.js";
|
|
4
5
|
import { collectStatementIdentifiers } from "../../ir/identifier-collector.js";
|
|
5
6
|
import { ensureDir } from "../../utils/fs.js";
|
|
6
7
|
import { resolveImport } from "../../libdef/registry.js";
|
|
7
8
|
import { emitPolyfillBoilerplate } from "../native-helpers-emitter.js";
|
|
8
9
|
import { resolveStrategy } from "../../platform/registry.js";
|
|
10
|
+
import { buildCoopSchedulerPolyfill } from "../../platform/coop-scheduler-runtime.js";
|
|
9
11
|
import { getLoadedFramework } from "../../framework-registry.js";
|
|
10
12
|
import { entryHasUI } from "../../ui-hook.js";
|
|
13
|
+
import { ComplianceContext } from "../compliance/compliance-context.js";
|
|
11
14
|
import { createEmissionScopeState, statementNeedsSnprintf, } from "../snprintf-helpers.js";
|
|
12
15
|
import { ExpressionRenderer } from "../expression-renderer.js";
|
|
13
16
|
import { StatementRenderer } from "../statement-renderer.js";
|
|
@@ -32,12 +35,183 @@ function filterShimBlock(lines, startMarker, endMarker) {
|
|
|
32
35
|
return filtered;
|
|
33
36
|
}
|
|
34
37
|
/**
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
38
|
+
* Detect whether a program uses the watchdog timer. The HAL resolver lowers
|
|
39
|
+
* `WDT.enable/reset/disable` calls to structured `wdt.*` hal-ops, which the
|
|
40
|
+
* setup emitter then renders as bare `wdt_enable/wdt_reset/wdt_disable` calls
|
|
41
|
+
* (or constant-folded `wdt_enable(WDTO_*)` macros). All of those require
|
|
42
|
+
* `<avr/wdt.h>`, so the include is gated on this check instead of being forced
|
|
43
|
+
* into every AVR program.
|
|
44
|
+
*
|
|
45
|
+
* `programAnalysis.usesWDT` is NOT used here: it scans `WDT.`-prefixed
|
|
46
|
+
* callees and raw-code hal-ops, but the structured `wdt.*` ops carry a typed
|
|
47
|
+
* operation name with no raw code, so the analysis misses them.
|
|
48
|
+
*
|
|
49
|
+
* The recursion mirrors `collectStatementIdentifiers` (identifier-collector.ts)
|
|
50
|
+
* — the canonical IR walker — so every compound statement shape is covered.
|
|
40
51
|
*/
|
|
52
|
+
function programUsesWdt(program) {
|
|
53
|
+
const visitStatement = (stmt) => {
|
|
54
|
+
if (stmt.kind === "hal-op") {
|
|
55
|
+
const opName = stmt.operation?.operation;
|
|
56
|
+
if (typeof opName === "string" && opName.startsWith("wdt."))
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
switch (stmt.kind) {
|
|
60
|
+
case "block":
|
|
61
|
+
case "labeled":
|
|
62
|
+
return visit(stmt.body);
|
|
63
|
+
case "if":
|
|
64
|
+
return visit(stmt.thenBranch) || visit(stmt.elseBranch ?? []);
|
|
65
|
+
case "for":
|
|
66
|
+
return visit(stmt.body)
|
|
67
|
+
|| (stmt.initializer ? visitStatement(stmt.initializer) : false);
|
|
68
|
+
case "while":
|
|
69
|
+
case "do_while":
|
|
70
|
+
case "for_of":
|
|
71
|
+
case "for_in":
|
|
72
|
+
return visit(stmt.body);
|
|
73
|
+
case "switch":
|
|
74
|
+
return stmt.cases.some((c) => visit(c.body ?? []));
|
|
75
|
+
case "try":
|
|
76
|
+
return visit(stmt.tryBlock) || visit(stmt.catchBlock ?? []) || visit(stmt.finallyBlock ?? []);
|
|
77
|
+
default:
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
const visit = (statements) => {
|
|
82
|
+
if (!statements)
|
|
83
|
+
return false;
|
|
84
|
+
for (const stmt of statements) {
|
|
85
|
+
if (visitStatement(stmt))
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
return false;
|
|
89
|
+
};
|
|
90
|
+
if (visit(program.topLevelStatements))
|
|
91
|
+
return true;
|
|
92
|
+
for (const fn of program.functions ?? []) {
|
|
93
|
+
if (visit(fn.statements))
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
for (const cls of program.classes ?? []) {
|
|
97
|
+
for (const m of cls.methods ?? [])
|
|
98
|
+
if (visit(m.statements))
|
|
99
|
+
return true;
|
|
100
|
+
for (const g of cls.getters ?? [])
|
|
101
|
+
if (visit(g.statements))
|
|
102
|
+
return true;
|
|
103
|
+
for (const s of cls.setters ?? [])
|
|
104
|
+
if (visit(s.statements))
|
|
105
|
+
return true;
|
|
106
|
+
if (cls.constructor && visit(cls.constructor.statements))
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Detect whether a program uses @typecad/safety. The safety transform pass
|
|
113
|
+
* injects safety.* ops when in use; strategies consult this to decide
|
|
114
|
+
* whether to emit the __tc_gpio_read shim that the safety voter calls.
|
|
115
|
+
* Mirrors programUsesWdt() above (same inline walker pattern).
|
|
116
|
+
*
|
|
117
|
+
* `hal-expr` (the expression-position safety.read_safe lowering produced
|
|
118
|
+
* by `const r = safe.read(pin)`) lives in expression trees, not at statement
|
|
119
|
+
* level — but it's carried inside a var_decl statement whose initializer is
|
|
120
|
+
* the hal-expr, and the var_decl gets visited as a top-level statement. We
|
|
121
|
+
* detect both: statement-level `hal-op` operations, and any statement whose
|
|
122
|
+
* rendered initializer text mentions safety.* (a defensive catch for the
|
|
123
|
+
* hal-expr-in-initializer case). The latter is rarely needed because the
|
|
124
|
+
* voter's read_safe op is also captured at the call site, but kept for
|
|
125
|
+
* robustness.
|
|
126
|
+
*/
|
|
127
|
+
export function programUsesSafety(program) {
|
|
128
|
+
// Defensive: some test fixtures and partial-program callers pass a minimal
|
|
129
|
+
// object (e.g. `{} as any`). Treat a missing iterable field as "no safety
|
|
130
|
+
// ops" rather than crashing — matches how the existing ArduinoStrategy path
|
|
131
|
+
// behaves when given a partial program.
|
|
132
|
+
if (!program)
|
|
133
|
+
return false;
|
|
134
|
+
const visitStatement = (stmt) => {
|
|
135
|
+
if (stmt.kind === "hal-op") {
|
|
136
|
+
const opName = stmt.operation?.operation;
|
|
137
|
+
if (typeof opName === "string" && opName.startsWith("safety."))
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
140
|
+
switch (stmt.kind) {
|
|
141
|
+
case "block":
|
|
142
|
+
case "labeled":
|
|
143
|
+
return visit(stmt.body);
|
|
144
|
+
case "if":
|
|
145
|
+
return visit(stmt.thenBranch) || visit(stmt.elseBranch ?? []);
|
|
146
|
+
case "for":
|
|
147
|
+
return visit(stmt.body)
|
|
148
|
+
|| (stmt.initializer ? visitStatement(stmt.initializer) : false);
|
|
149
|
+
case "while":
|
|
150
|
+
case "do_while":
|
|
151
|
+
case "for_of":
|
|
152
|
+
case "for_in":
|
|
153
|
+
return visit(stmt.body);
|
|
154
|
+
case "switch":
|
|
155
|
+
return stmt.cases.some((c) => visit(c.body ?? []));
|
|
156
|
+
case "try":
|
|
157
|
+
return visit(stmt.tryBlock) || visit(stmt.catchBlock ?? []) || visit(stmt.finallyBlock ?? []);
|
|
158
|
+
default:
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
const visit = (statements) => {
|
|
163
|
+
if (!statements)
|
|
164
|
+
return false;
|
|
165
|
+
for (const stmt of statements) {
|
|
166
|
+
if (visitStatement(stmt))
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
return false;
|
|
170
|
+
};
|
|
171
|
+
if (visit(program.topLevelStatements))
|
|
172
|
+
return true;
|
|
173
|
+
for (const fn of program.functions ?? []) {
|
|
174
|
+
if (visit(fn.statements))
|
|
175
|
+
return true;
|
|
176
|
+
}
|
|
177
|
+
for (const cls of program.classes ?? []) {
|
|
178
|
+
for (const m of cls.methods ?? [])
|
|
179
|
+
if (visit(m.statements))
|
|
180
|
+
return true;
|
|
181
|
+
for (const g of cls.getters ?? [])
|
|
182
|
+
if (visit(g.statements))
|
|
183
|
+
return true;
|
|
184
|
+
for (const s of cls.setters ?? [])
|
|
185
|
+
if (visit(s.statements))
|
|
186
|
+
return true;
|
|
187
|
+
if (cls.constructor && visit(cls.constructor.statements))
|
|
188
|
+
return true;
|
|
189
|
+
}
|
|
190
|
+
// SafeInt<T> / SafeVariable<T> usage does not produce safety.* hal-ops
|
|
191
|
+
// (they are plain C++ template types), so the op-walk above misses them.
|
|
192
|
+
// Detect via the var_decl cppType: any declaration whose type starts with
|
|
193
|
+
// "SafeInt" or "SafeVariable" needs the corresponding polyfill to ship.
|
|
194
|
+
const usesSafeWrapperType = (statements) => {
|
|
195
|
+
if (!statements)
|
|
196
|
+
return false;
|
|
197
|
+
for (const stmt of statements) {
|
|
198
|
+
if (stmt.kind === "var_decl") {
|
|
199
|
+
const cppType = stmt.cppType;
|
|
200
|
+
if (typeof cppType === "string" && (cppType.startsWith("SafeInt") || cppType.startsWith("SafeVariable"))) {
|
|
201
|
+
return true;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return false;
|
|
206
|
+
};
|
|
207
|
+
if (usesSafeWrapperType(program.topLevelStatements))
|
|
208
|
+
return true;
|
|
209
|
+
for (const fn of program.functions ?? []) {
|
|
210
|
+
if (usesSafeWrapperType(fn.statements))
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
41
215
|
function extractShimMacro(lines, macroName) {
|
|
42
216
|
const ifndefIdx = lines.findIndex(l => {
|
|
43
217
|
const trimmed = l.trim();
|
|
@@ -100,9 +274,29 @@ export function buildEmitterContext(program, options) {
|
|
|
100
274
|
stringEnumNames.add(name);
|
|
101
275
|
}
|
|
102
276
|
}
|
|
277
|
+
// When @typecad/safety is active, register its enum names so the expression
|
|
278
|
+
// renderer uses :: (C++ enum-class scope resolution) instead of . (TS dot
|
|
279
|
+
// access) for member access like SafetyFaultCategory.Configuration. The
|
|
280
|
+
// graph-builder skips @typecad/safety (its source isn't parsed), so these
|
|
281
|
+
// enum names never enter program.enums — we register them manually here.
|
|
282
|
+
if (hasSafetyHook()) {
|
|
283
|
+
enumNames.add("SafetyFaultCategory");
|
|
284
|
+
enumNames.add("SafetyFaultCode");
|
|
285
|
+
enumNames.add("SafetyStatus");
|
|
286
|
+
}
|
|
103
287
|
const strategy = options.strategy ?? resolveStrategy(options.target ?? "generic");
|
|
104
288
|
strategy.setLargeEnumNames?.(largeEnumNames);
|
|
105
|
-
|
|
289
|
+
// When @typecad/safety is active, `safe` is a compile-time-only construct
|
|
290
|
+
// (its methods are intercepted at IR-build time). Its top-level var_decl
|
|
291
|
+
// (from the parsed safety module source) must NOT be emitted as a C++
|
|
292
|
+
// _safe_t struct — add it to reservedNames so the top-level-prep filter
|
|
293
|
+
// strips it. Mirrors how platform reserved names (HIGH, LOW, etc.) are
|
|
294
|
+
// kept out of user-code emission.
|
|
295
|
+
let reservedNames = new Set(strategy.reservedNames());
|
|
296
|
+
if (hasSafetyHook()) {
|
|
297
|
+
reservedNames = new Set(reservedNames);
|
|
298
|
+
reservedNames.add("safe");
|
|
299
|
+
}
|
|
106
300
|
ensureDir(options.outDir);
|
|
107
301
|
const classNameMap = (() => {
|
|
108
302
|
try {
|
|
@@ -118,6 +312,14 @@ export function buildEmitterContext(program, options) {
|
|
|
118
312
|
})();
|
|
119
313
|
const programAnalysis = analyzeProgram(program, strategy);
|
|
120
314
|
if (options.platformContext) {
|
|
315
|
+
// The UI runtime's per-frame tick calls millis() (injected by the emitter,
|
|
316
|
+
// not present in user source), so a mounted UI needs the native millis ISR
|
|
317
|
+
// even when usesNativeTiming is false. OR entryHasUI() into the flag the
|
|
318
|
+
// AVR strategy reads when gating native_millis, so framework-avr doesn't
|
|
319
|
+
// drop the Timer0 ISR from a UI program.
|
|
320
|
+
if (!programAnalysis.usesNativeTiming && entryHasUI()) {
|
|
321
|
+
programAnalysis.usesNativeTiming = true;
|
|
322
|
+
}
|
|
121
323
|
options.platformContext.analysis = programAnalysis;
|
|
122
324
|
if (!options.platformContext.architecture && program.boardConstants) {
|
|
123
325
|
options.platformContext.architecture = program.boardConstants.get("architecture");
|
|
@@ -144,14 +346,50 @@ export function buildEmitterContext(program, options) {
|
|
|
144
346
|
? (strategy.generateNativePolyfills?.(program, options.platformContext) ?? [])
|
|
145
347
|
: (strategy.generateNativePolyfills?.(program, options.platformContext) ?? []);
|
|
146
348
|
let filteredNativePolyfills = filterPolyfillHelpers(nativePolyfills, programAnalysis.usedPolyfillHelpers);
|
|
147
|
-
|
|
349
|
+
// cuttlefish_halt: keep when the program throws (throw lowers to cuttlefish_halt)
|
|
350
|
+
// OR explicitly calls cuttlefish_halt in lowered raw text.
|
|
351
|
+
if (!programAnalysis.hasThrowStatements && !programAnalysis.usesHalt) {
|
|
148
352
|
filteredNativePolyfills = filteredNativePolyfills.filter(p => p.id !== "cuttlefish_halt");
|
|
149
353
|
}
|
|
354
|
+
// static_array: the __tc_StaticArray struct has no __tc_*() call pattern, so
|
|
355
|
+
// filterPolyfillHelpers' per-function tree-shaker can't drop it (it sees no
|
|
356
|
+
// extractable name). Gate at the polyfill-id level instead: keep only when
|
|
357
|
+
// __tc_StaticArray genuinely appears in used code.
|
|
358
|
+
if (!programAnalysis.usedPolyfillHelpers.has('__tc_StaticArray')) {
|
|
359
|
+
filteredNativePolyfills = filteredNativePolyfills.filter(p => p.id !== "static_array");
|
|
360
|
+
}
|
|
150
361
|
const usesTimers = programAnalysis.usedPolyfillHelpers.has('__tc_setInterval') ||
|
|
151
362
|
programAnalysis.usedPolyfillHelpers.has('__tc_setTimeout');
|
|
152
363
|
if (!usesTimers) {
|
|
153
364
|
filteredNativePolyfills = filteredNativePolyfills.filter(p => p.id !== "timer_methods");
|
|
154
365
|
}
|
|
366
|
+
// Safety polyfills (mode table + voter + SafeVariable + SafeInt). Gated on
|
|
367
|
+
// programUsesSafety(program) so they ONLY appear when the sketch actually
|
|
368
|
+
// references a safety construct — emitting them unconditionally (just because
|
|
369
|
+
// @typecad/safety is installed and registered its hook) leaks ~200 lines of
|
|
370
|
+
// polyfill into every sketch, and SafeInt's `return *this` chaining methods
|
|
371
|
+
// tripped byte-identity / lowering assertions that expect no `this` token.
|
|
372
|
+
// programUsesSafety walks the IR for any safety.* hal-op.
|
|
373
|
+
if (isEntryFile && hasSafetyHook() && programUsesSafety(program)) {
|
|
374
|
+
const safetyPolyfills = filterPolyfillHelpers(requireSafetyHook().buildPolyfills?.() ?? [], programAnalysis.usedPolyfillHelpers);
|
|
375
|
+
filteredNativePolyfills = [...filteredNativePolyfills, ...safetyPolyfills];
|
|
376
|
+
}
|
|
377
|
+
// Cooperative scheduler polyfill (Phase 0). Injected centrally when the
|
|
378
|
+
// strategy opts into priority/time-budget scheduling AND there is per-frame
|
|
379
|
+
// work to schedule (async tasks, the std::function microtask pump, or the
|
|
380
|
+
// cooperative timer pump). The polyfill carries only the no-STL CoopSched
|
|
381
|
+
// namespace; per-program trampoline registration is emitted by the function
|
|
382
|
+
// emitter around the driver's loop injection. STL-free so it links on
|
|
383
|
+
// minimal-libc targets (Zephyr) where the Promise runtime does not.
|
|
384
|
+
const coopConfig = strategy.getAsyncRuntimeConfig?.();
|
|
385
|
+
const hasCoopWork = program.functions.some(fn => fn.isAsync) ||
|
|
386
|
+
programAnalysis.usedPolyfillHelpers.has('__tc_setInterval') ||
|
|
387
|
+
programAnalysis.usedPolyfillHelpers.has('__tc_setTimeout');
|
|
388
|
+
if (isEntryFile && coopConfig && hasCoopWork) {
|
|
389
|
+
const coop = buildCoopSchedulerPolyfill(program, coopConfig, strategy.currentTimeMillis());
|
|
390
|
+
if (coop)
|
|
391
|
+
filteredNativePolyfills = [...filteredNativePolyfills, coop];
|
|
392
|
+
}
|
|
155
393
|
const allPolyfills = filteredNativePolyfills;
|
|
156
394
|
const emittedPolyfills = allPolyfills.length > 0
|
|
157
395
|
? emitPolyfillBoilerplate(allPolyfills)
|
|
@@ -161,23 +399,15 @@ export function buildEmitterContext(program, options) {
|
|
|
161
399
|
if (!isNpmPackage) {
|
|
162
400
|
includes.push(...strategy.forcedIncludes(program, options.platformContext));
|
|
163
401
|
Object.assign(symbolMap, strategy.symbolAliases(program, options.platformContext));
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
// the analysis runs before the expression renderer, which can
|
|
174
|
-
// introduce cuttlefish_nullish calls (e.g. for default-param
|
|
175
|
-
// destructuring, or `??` inside class methods on abstract bases)
|
|
176
|
-
// that the analysis pass didn't see, leaving the header referencing
|
|
177
|
-
// an undeclared helper. The shim is fully idempotent (#ifndef
|
|
178
|
-
// guards), so emitting it unconditionally is safe and cheap.
|
|
179
|
-
shimLines = [...strategy.shimLines(program, options.platformContext)];
|
|
180
|
-
}
|
|
402
|
+
// Both entry and non-entry files get the strategy's shim lines, then the
|
|
403
|
+
// Layer B filters below strip unused blocks based on programAnalysis. For
|
|
404
|
+
// non-entry (split-file) headers, the per-file pre-render analysis can miss
|
|
405
|
+
// renderer-introduced calls (e.g. cuttlefish_nullish from `??` lowering);
|
|
406
|
+
// output-finalizer.ts handles that case with a post-render text scan that
|
|
407
|
+
// re-injects the nullish helper shim into a header whose emitted lines
|
|
408
|
+
// actually contain a cuttlefish_nullish( call. So gating here is safe — the
|
|
409
|
+
// post-render injection covers the one unreliable pre-render signal.
|
|
410
|
+
shimLines = [...strategy.shimLines(program, options.platformContext)];
|
|
181
411
|
if (!programAnalysis.usesStringConversion) {
|
|
182
412
|
shimLines = shimLines.filter(l => !l.includes('std::string String('));
|
|
183
413
|
}
|
|
@@ -223,6 +453,87 @@ export function buildEmitterContext(program, options) {
|
|
|
223
453
|
if (!programAnalysis.usesStrPtr) {
|
|
224
454
|
shimLines = filterShimBlock(shimLines, '#ifndef CUTTLEFISH_STR_BUF_SIZE', 'inline size_t (strlen)(const __tc_str_ptr& s) { return ::strlen(s.buf); }');
|
|
225
455
|
}
|
|
456
|
+
// framework-avr native peripheral driver shims. Each block carries a
|
|
457
|
+
// CUTTLEFISH_*_BEGIN/END marker pair; strip the ones the program doesn't
|
|
458
|
+
// use. framework-arduino's shimLines contain none of these markers, so
|
|
459
|
+
// these filters are no-ops there. The strategies also self-gate on the
|
|
460
|
+
// same flags; this is the defensive backstop (mirrors how usesWDT/etc.
|
|
461
|
+
// backstop the strategy-side gating above).
|
|
462
|
+
// Keep the UART block when console calls are present (console_log etc.
|
|
463
|
+
// call _uart_* functions) even if no direct uart.* HAL ops are used.
|
|
464
|
+
if (!programAnalysis.usesUart && !programAnalysis.hasConsoleCalls) {
|
|
465
|
+
shimLines = filterShimBlock(shimLines, '// CUTTLEFISH_UART_BEGIN', '// CUTTLEFISH_UART_END');
|
|
466
|
+
shimLines = filterShimBlock(shimLines, '// CUTTLEFISH_UART_EXT_BEGIN', '// CUTTLEFISH_UART_EXT_END');
|
|
467
|
+
}
|
|
468
|
+
if (!programAnalysis.usesSPI) {
|
|
469
|
+
shimLines = filterShimBlock(shimLines, '// CUTTLEFISH_SPI_BEGIN', '// CUTTLEFISH_SPI_END');
|
|
470
|
+
}
|
|
471
|
+
if (!programAnalysis.usesI2C) {
|
|
472
|
+
shimLines = filterShimBlock(shimLines, '// CUTTLEFISH_TWI_BEGIN', '// CUTTLEFISH_TWI_END');
|
|
473
|
+
}
|
|
474
|
+
if (!programAnalysis.usesEEPROM && !programAnalysis.usesPreferences) {
|
|
475
|
+
shimLines = filterShimBlock(shimLines, '// CUTTLEFISH_EEPROM_BEGIN', '// CUTTLEFISH_EEPROM_END');
|
|
476
|
+
}
|
|
477
|
+
if (!programAnalysis.usesTone) {
|
|
478
|
+
shimLines = filterShimBlock(shimLines, '// CUTTLEFISH_TONE_BEGIN', '// CUTTLEFISH_TONE_END');
|
|
479
|
+
}
|
|
480
|
+
// framework-esp32 native peripheral driver shims. Same defensive-backstop
|
|
481
|
+
// pattern as the UART/SPI/TWI/EEPROM/tone blocks above. framework-arduino's
|
|
482
|
+
// and framework-avr's shimLines contain none of these markers, so these
|
|
483
|
+
// filters are no-ops there.
|
|
484
|
+
if (!programAnalysis.usesGPIO) {
|
|
485
|
+
shimLines = filterShimBlock(shimLines, '// CUTTLEFISH_GPIO_BEGIN', '// CUTTLEFISH_GPIO_END');
|
|
486
|
+
}
|
|
487
|
+
if (!programAnalysis.usesPWM) {
|
|
488
|
+
shimLines = filterShimBlock(shimLines, '// CUTTLEFISH_PWM_BEGIN', '// CUTTLEFISH_PWM_END');
|
|
489
|
+
}
|
|
490
|
+
if (!programAnalysis.usesADC) {
|
|
491
|
+
shimLines = filterShimBlock(shimLines, '// CUTTLEFISH_ADC_BEGIN', '// CUTTLEFISH_ADC_END');
|
|
492
|
+
}
|
|
493
|
+
if (!programAnalysis.usesDAC) {
|
|
494
|
+
shimLines = filterShimBlock(shimLines, '// CUTTLEFISH_DAC_BEGIN', '// CUTTLEFISH_DAC_END');
|
|
495
|
+
}
|
|
496
|
+
if (!programAnalysis.usesPower) {
|
|
497
|
+
shimLines = filterShimBlock(shimLines, '// CUTTLEFISH_POWER_BEGIN', '// CUTTLEFISH_POWER_END');
|
|
498
|
+
}
|
|
499
|
+
if (!programAnalysis.usesWdt) {
|
|
500
|
+
shimLines = filterShimBlock(shimLines, '// CUTTLEFISH_WDT_BEGIN', '// CUTTLEFISH_WDT_END');
|
|
501
|
+
}
|
|
502
|
+
if (!programAnalysis.usesInterrupts) {
|
|
503
|
+
shimLines = filterShimBlock(shimLines, '// CUTTLEFISH_INTR_BEGIN', '// CUTTLEFISH_INTR_END');
|
|
504
|
+
}
|
|
505
|
+
if (!programAnalysis.usesPulse) {
|
|
506
|
+
shimLines = filterShimBlock(shimLines, '// CUTTLEFISH_PULSE_BEGIN', '// CUTTLEFISH_PULSE_END');
|
|
507
|
+
}
|
|
508
|
+
if (!programAnalysis.usesShift) {
|
|
509
|
+
shimLines = filterShimBlock(shimLines, '// CUTTLEFISH_SHIFT_BEGIN', '// CUTTLEFISH_SHIFT_END');
|
|
510
|
+
}
|
|
511
|
+
if (!programAnalysis.usesWifi) {
|
|
512
|
+
shimLines = filterShimBlock(shimLines, '// CUTTLEFISH_WIFI_BEGIN', '// CUTTLEFISH_WIFI_END');
|
|
513
|
+
}
|
|
514
|
+
else {
|
|
515
|
+
if (!programAnalysis.usesWifiConnect) {
|
|
516
|
+
shimLines = filterShimBlock(shimLines, '// CUTTLEFISH_WIFI_CONNECT_BEGIN', '// CUTTLEFISH_WIFI_CONNECT_END');
|
|
517
|
+
}
|
|
518
|
+
if (!programAnalysis.usesWifiConnectBlocking) {
|
|
519
|
+
shimLines = filterShimBlock(shimLines, '// CUTTLEFISH_WIFI_CONNECT_BLOCKING_BEGIN', '// CUTTLEFISH_WIFI_CONNECT_BLOCKING_END');
|
|
520
|
+
}
|
|
521
|
+
if (!programAnalysis.usesWifiQuery) {
|
|
522
|
+
shimLines = filterShimBlock(shimLines, '// CUTTLEFISH_WIFI_QUERY_BEGIN', '// CUTTLEFISH_WIFI_QUERY_END');
|
|
523
|
+
}
|
|
524
|
+
if (!programAnalysis.usesWifiScan) {
|
|
525
|
+
shimLines = filterShimBlock(shimLines, '// CUTTLEFISH_WIFI_SCAN_BEGIN', '// CUTTLEFISH_WIFI_SCAN_END');
|
|
526
|
+
}
|
|
527
|
+
if (!programAnalysis.usesWifiConfig) {
|
|
528
|
+
shimLines = filterShimBlock(shimLines, '// CUTTLEFISH_WIFI_CONFIG_BEGIN', '// CUTTLEFISH_WIFI_CONFIG_END');
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
if (!programAnalysis.usesHttp) {
|
|
532
|
+
shimLines = filterShimBlock(shimLines, '// CUTTLEFISH_HTTP_BEGIN', '// CUTTLEFISH_HTTP_END');
|
|
533
|
+
}
|
|
534
|
+
if (!programAnalysis.usesPreferences) {
|
|
535
|
+
shimLines = filterShimBlock(shimLines, '// CUTTLEFISH_PREFERENCES_BEGIN', '// CUTTLEFISH_PREFERENCES_END');
|
|
536
|
+
}
|
|
226
537
|
profileDiagnostics = [...strategy.profileDiagnostics(program, options.platformContext)];
|
|
227
538
|
}
|
|
228
539
|
for (const imported of program.imports) {
|
|
@@ -258,11 +569,11 @@ export function buildEmitterContext(program, options) {
|
|
|
258
569
|
symbolMap[defaultName] = defaultName;
|
|
259
570
|
continue;
|
|
260
571
|
}
|
|
261
|
-
// Compile-time-only packages
|
|
262
|
-
//
|
|
263
|
-
//
|
|
264
|
-
|
|
265
|
-
|
|
572
|
+
// Compile-time-only packages: their calls are intercepted at IR-build
|
|
573
|
+
// time; the package emits NO C++ module/header. Register the imported
|
|
574
|
+
// symbols so references resolve, but skip the #include generation.
|
|
575
|
+
if (imported.moduleSpecifier === "@typecad/ui" ||
|
|
576
|
+
imported.moduleSpecifier === "@typecad/safety") {
|
|
266
577
|
for (const symbol of imported.namedImports) {
|
|
267
578
|
symbolMap[symbol] = symbol;
|
|
268
579
|
}
|
|
@@ -615,6 +926,10 @@ export function buildEmitterContext(program, options) {
|
|
|
615
926
|
knownTopLevelObjectTypes,
|
|
616
927
|
diagnostics: emitDiagnostics,
|
|
617
928
|
});
|
|
929
|
+
// Construct the compliance context early so it can be threaded into the
|
|
930
|
+
// renderers (StatementRenderer needs it for A3-9-1 fixed-width integer
|
|
931
|
+
// default). It's also returned in the EmitterContext at the end.
|
|
932
|
+
const compliance = new ComplianceContext(options.autosar ?? "off");
|
|
618
933
|
const statementRenderer = new StatementRenderer({
|
|
619
934
|
strategy,
|
|
620
935
|
boardConstants: program.boardConstants,
|
|
@@ -633,6 +948,7 @@ export function buildEmitterContext(program, options) {
|
|
|
633
948
|
interfaceFieldTypes,
|
|
634
949
|
crossModuleClassNames: classNames,
|
|
635
950
|
diagnostics: emitDiagnostics,
|
|
951
|
+
compliance,
|
|
636
952
|
});
|
|
637
953
|
const asyncFunctionOriginalNames = new Set(program.functions.filter((fn) => fn.isAsync).map((fn) => fn.originalName));
|
|
638
954
|
const mapFunctionNameLocal = (originalName) => statementRenderer.mapFunctionName(originalName);
|
|
@@ -715,7 +1031,13 @@ export function buildEmitterContext(program, options) {
|
|
|
715
1031
|
includes.push(...emittedPolyfills.includes.map((include) => normalizeInclude(include)));
|
|
716
1032
|
}
|
|
717
1033
|
if (program.requiredIncludes) {
|
|
718
|
-
|
|
1034
|
+
let reqIncludes = [...program.requiredIncludes];
|
|
1035
|
+
// Let the strategy strip stale includes (e.g. framework-avr removes
|
|
1036
|
+
// Arduino library headers it doesn't need — Wire.h, SPI.h, etc.).
|
|
1037
|
+
if (strategy.filterRequiredIncludes) {
|
|
1038
|
+
reqIncludes = strategy.filterRequiredIncludes(reqIncludes);
|
|
1039
|
+
}
|
|
1040
|
+
includes.push(...reqIncludes);
|
|
719
1041
|
}
|
|
720
1042
|
// UI text bindings lower to snprintf bodies that need <stdio.h>. Pushed here
|
|
721
1043
|
// (in buildEmitterContext) rather than in emitUIRuntime because emitPreamble
|
|
@@ -739,6 +1061,17 @@ export function buildEmitterContext(program, options) {
|
|
|
739
1061
|
if (stringEnumNames.size > 0) {
|
|
740
1062
|
includes.push(strategy.cstringHeader());
|
|
741
1063
|
}
|
|
1064
|
+
// `<avr/wdt.h>` is only needed when the program actually uses the watchdog.
|
|
1065
|
+
// The HAL resolver lowers WDT.enable/reset/disable to bare wdt_*() calls
|
|
1066
|
+
// (or constant-folded wdt_enable(WDTO_*) macros), all of which require the
|
|
1067
|
+
// header. Detect the structured `wdt.*` hal-ops directly here rather than
|
|
1068
|
+
// relying on programAnalysis.usesWDT (which misses these ops — they carry
|
|
1069
|
+
// a typed operation name, not raw code the analysis scans) or on a forced
|
|
1070
|
+
// include in the AVR profile (which leaked the header into every AVR
|
|
1071
|
+
// program, even ones that never touch the watchdog like `led.toggle()`).
|
|
1072
|
+
if (programUsesWdt(program)) {
|
|
1073
|
+
includes.push("<avr/wdt.h>");
|
|
1074
|
+
}
|
|
742
1075
|
// Placeholder defaults for fields that are computed later by other phases
|
|
743
1076
|
const noopFixPointer = (c) => c;
|
|
744
1077
|
// Demo #18 Finding B: collect the names of non-exported free functions that
|
|
@@ -830,6 +1163,14 @@ export function buildEmitterContext(program, options) {
|
|
|
830
1163
|
headerLines: ["#pragma once", ""],
|
|
831
1164
|
sourceMapEntries: [],
|
|
832
1165
|
headerMapEntries: [],
|
|
1166
|
+
// GDB debug mode: emit #line markers at appendSourceLine when 'gdb'.
|
|
1167
|
+
// Resolved once from strategy.debugMode(buildTarget); defaults to 'printf'
|
|
1168
|
+
// so all existing paths keep their pre-marker behavior.
|
|
1169
|
+
debugMode: (() => {
|
|
1170
|
+
const buildTarget = options.platformContext?.frameworkData?.buildTarget;
|
|
1171
|
+
return strategy.debugMode?.(buildTarget) ?? 'printf';
|
|
1172
|
+
})(),
|
|
1173
|
+
lastEmittedSource: null,
|
|
833
1174
|
cArrayVarNames: new Set(),
|
|
834
1175
|
fnCArrayVarNames: new Map(),
|
|
835
1176
|
globalPointerVarTypes,
|
|
@@ -847,5 +1188,6 @@ export function buildEmitterContext(program, options) {
|
|
|
847
1188
|
templateInterfaceNames,
|
|
848
1189
|
interfaceNamespaceMap,
|
|
849
1190
|
interfaceFieldTypes,
|
|
1191
|
+
compliance,
|
|
850
1192
|
};
|
|
851
1193
|
}
|
|
@@ -187,6 +187,12 @@ function collectCallbackFromExpression(expr, callbackFunctions, isrPrefix, count
|
|
|
187
187
|
function collectCallbacks(statements, callbackFunctions, isrPrefix, counter) {
|
|
188
188
|
for (const stmt of statements) {
|
|
189
189
|
if (stmt.kind === "call") {
|
|
190
|
+
// __EXPR_STMT__ statements carry an expression IR (args[0]) that the
|
|
191
|
+
// statement renderer renders at EMIT time via the expression renderer
|
|
192
|
+
// (which handles lambdas inline via renderLambda). Do NOT hoist lambdas
|
|
193
|
+
// from these — they must stay inline to preserve closure captures.
|
|
194
|
+
if (stmt.callee === "__EXPR_STMT__")
|
|
195
|
+
continue;
|
|
190
196
|
for (const arg of stmt.args) {
|
|
191
197
|
collectCallbackFromExpression(arg, callbackFunctions, isrPrefix, counter);
|
|
192
198
|
}
|
|
@@ -597,6 +603,8 @@ export function runTopLevelPreprocessing(ctx) {
|
|
|
597
603
|
statements: callbackIR.statements ?? callbackIR.body ?? [],
|
|
598
604
|
debounceMs: callbackIR.debounceMs,
|
|
599
605
|
isInterruptHandler: callbackIR.isInterruptHandler,
|
|
606
|
+
...(callbackIR.returnType ? { returnType: callbackIR.returnType } : {}),
|
|
607
|
+
...(callbackIR.typedParams ? { typedParams: callbackIR.typedParams } : {}),
|
|
600
608
|
});
|
|
601
609
|
replacePlaceholderInAllStatements(filteredTopLevelExecutables, rc.placeholderName, callbackName);
|
|
602
610
|
for (const fn of mappedFunctions) {
|
|
@@ -5,7 +5,27 @@ import { isStringEnum } from "../../api/shared/index.js";
|
|
|
5
5
|
import { resolveEnumValues, narrowestEnumUnderlying } from "../utils/cpp-helpers.js";
|
|
6
6
|
export function emitTypeDeclarations(ctx) {
|
|
7
7
|
const { program, strategy, effectiveEmitMode, reservedNames, emittedTopLevelStatements, topLevelScope } = ctx;
|
|
8
|
-
const normalizeCppTypeForTarget = (cppType) =>
|
|
8
|
+
const normalizeCppTypeForTarget = (cppType) => {
|
|
9
|
+
// A3-9-1: resolve "auto" and substitute "int" with the fixed-width
|
|
10
|
+
// default — but ONLY when autosar is active. When off, fall through to
|
|
11
|
+
// the strategy's normalizeCppType (Arduino returns "auto" for "auto").
|
|
12
|
+
const autosarOn = ctx.compliance.isEnabled() && ctx.compliance.isBanned("A3-9-1");
|
|
13
|
+
if (autosarOn && (cppType === "auto" || cppType === "int")) {
|
|
14
|
+
return strategy.defaultNumericType(ctx.compliance);
|
|
15
|
+
}
|
|
16
|
+
// Also apply the int->int32_t substitution to the inner template argument
|
|
17
|
+
// of safety wrappers (SafeInt<int> -> SafeInt<int32_t>, SafeVariable<int>
|
|
18
|
+
// -> SafeVariable<int32_t>). The whole-type check above misses these
|
|
19
|
+
// because the full string is "Name<int>", not "int".
|
|
20
|
+
if (autosarOn) {
|
|
21
|
+
const wrapperMatch = cppType.match(/^(Safe(?:Int|Variable))<int>$/);
|
|
22
|
+
if (wrapperMatch) {
|
|
23
|
+
const fixedWidth = strategy.defaultNumericType(ctx.compliance);
|
|
24
|
+
return `${wrapperMatch[1]}<${fixedWidth}>`;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return strategy.normalizeCppType(cppType);
|
|
28
|
+
};
|
|
9
29
|
// Split-mode header include + forward declarations
|
|
10
30
|
if (effectiveEmitMode === "split") {
|
|
11
31
|
appendSourceLine(ctx, `#include \"${ctx.baseName}.h\"`);
|
|
@@ -222,7 +242,12 @@ export function emitTypeDeclarations(ctx) {
|
|
|
222
242
|
cppType = strategy.normalizeCppType(resolved);
|
|
223
243
|
}
|
|
224
244
|
const isConst = statement.storage === "const";
|
|
225
|
-
const
|
|
245
|
+
// A const-qualified top-level variable whose cppType already begins with
|
|
246
|
+
// `const` (e.g. a string-literal global lowered as `const char*`) would
|
|
247
|
+
// otherwise emit `extern const const char* X;` — a duplicate-const error.
|
|
248
|
+
// Don't prepend the storage qualifier when the type already carries it.
|
|
249
|
+
const typeAlreadyConst = cppType.trimStart().startsWith("const");
|
|
250
|
+
const constPrefix = isConst && !typeAlreadyConst ? "const " : "";
|
|
226
251
|
const varName = escapeCppKeyword(statement.name, platformReservedNames);
|
|
227
252
|
// Array-typed top-level consts (e.g. `export const ARR: T[] = [...]`)
|
|
228
253
|
// lower with cppType "auto" and a `{ kind: "array", elementType }`
|
|
@@ -234,7 +259,7 @@ export function emitTypeDeclarations(ctx) {
|
|
|
234
259
|
if (cppType === "auto" && statement.initializer && statement.initializer.kind === "array") {
|
|
235
260
|
const elemType = statement.initializer.elementType && statement.initializer.elementType !== "auto"
|
|
236
261
|
? strategy.normalizeCppType(statement.initializer.elementType)
|
|
237
|
-
: strategy.defaultNumericType();
|
|
262
|
+
: strategy.defaultNumericType(ctx.compliance.isEnabled() ? ctx.compliance : undefined);
|
|
238
263
|
appendHeaderLine(ctx, `extern ${constPrefix}${elemType} ${varName}[];`);
|
|
239
264
|
emitted = true;
|
|
240
265
|
continue;
|