@typecad/cuttlefish 1.0.0-alpha.7 → 1.0.0-alpha.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +2 -2
- package/dist/api/shared/display-adapter.js +9 -119
- package/dist/api/shared/display-adapters/sdl.js +29 -29
- package/dist/api/shared/display-profile.d.ts +38 -33
- package/dist/api/shared/display-profile.js +22 -170
- package/dist/api/shared/framework-manifest-registry.d.ts +1 -1
- package/dist/api/shared/framework-manifest-registry.js +8 -6
- package/dist/api/shared/framework-manifest.d.ts +147 -28
- package/dist/api/shared/framework-manifest.js +35 -2
- package/dist/api/shared/graphics-strategy.d.ts +15 -6
- package/dist/api/shared/hal-op-ir.d.ts +340 -3
- package/dist/api/shared/hal-op-ir.js +34 -0
- package/dist/api/shared/index.d.ts +9 -1
- package/dist/api/shared/index.js +11 -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/platform-strategy.d.ts +104 -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 +17 -13
- package/dist/api/shared/toolchain-types.d.ts +17 -0
- package/dist/api/shared/validate-framework-manifest.js +77 -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 +120 -121
- package/dist/config-loader.d.ts +7 -2
- package/dist/config-loader.js +20 -19
- package/dist/config-schema.d.ts +107 -65
- 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 +74 -77
- package/dist/create/board-spec.js +0 -1
- package/dist/create/init-scaffold.d.ts +7 -3
- package/dist/create/init-scaffold.js +0 -62
- package/dist/create/init-templates.js +62 -16
- package/dist/create/init-wizard.js +32 -27
- 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/emitters/class-emitter.js +6 -1
- package/dist/emit/emitters/emitter-context.d.ts +23 -1
- package/dist/emit/emitters/function-emitter-impl.js +96 -6
- 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.js +80 -1
- package/dist/emit/emitters/setup.d.ts +17 -0
- package/dist/emit/emitters/setup.js +217 -36
- package/dist/emit/emitters/top-level-prep.js +6 -0
- package/dist/emit/emitters/type-decl-emitter.js +28 -3
- package/dist/emit/emitters/ui-emitter.js +17 -8
- package/dist/emit/expression-renderer.d.ts +1 -1
- package/dist/emit/expression-renderer.js +81 -5
- package/dist/emit/route-hal-op.js +18 -5
- package/dist/emit/snprintf-helpers.js +7 -4
- package/dist/emit/statement-renderer.d.ts +10 -0
- package/dist/emit/statement-renderer.js +21 -5
- package/dist/emit/utils/async-state-machine.js +39 -12
- 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 +4 -4
- package/dist/ir/call-graph.js +16 -0
- package/dist/ir/expression-to-ir.js +150 -0
- package/dist/ir/function-builder.js +22 -0
- package/dist/ir/hal/hal-emitter.js +10 -6
- package/dist/ir/hal/hal-parser.js +16 -9
- package/dist/ir/hal/hal-plugins.js +164 -3
- package/dist/ir/identifier-collector.js +21 -4
- package/dist/ir/interrupt-analysis.d.ts +5 -1
- package/dist/ir/interrupt-analysis.js +8 -16
- package/dist/ir/ownership-analysis.js +6 -0
- 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 +42 -0
- package/dist/ir/program-analysis.js +243 -6
- package/dist/ir/render-expr.js +11 -0
- package/dist/ir/timing-validation.js +11 -2
- package/dist/ir/transformers/call-statement.js +105 -0
- package/dist/ir/transformers/expressions.js +4 -0
- package/dist/ir/transformers/hal-call-resolver.js +21 -0
- package/dist/ir/transformers/namespace-methods.js +17 -12
- package/dist/ir/transformers/ui-reactive.js +2 -2
- package/dist/ir/transformers/variables.js +51 -0
- 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 +6 -1
- package/dist/ir/worker-analysis.d.ts +10 -0
- package/dist/ir/worker-analysis.js +261 -0
- package/dist/orchestrator/graph-builder.js +8 -7
- 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/stores/display-profile-store.d.ts +0 -1
- package/dist/stores/display-profile-store.js +0 -1
- package/dist/testing.d.ts +4 -0
- package/dist/testing.js +5 -0
- package/dist/transpile.js +124 -35
- package/dist/types.d.ts +13 -2
- package/dist/ui-hook.d.ts +0 -2
- package/dist/utils/cli.js +51 -3
- package/package.json +20 -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/libdef/idf-discovery.d.ts +0 -7
- package/dist/libdef/idf-discovery.js +0 -59
- package/dist/licenses.d.ts +0 -185
- package/dist/licenses.js +0 -963
|
@@ -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";
|
|
@@ -86,18 +89,18 @@ function programUsesWdt(program) {
|
|
|
86
89
|
};
|
|
87
90
|
if (visit(program.topLevelStatements))
|
|
88
91
|
return true;
|
|
89
|
-
for (const fn of program.functions) {
|
|
92
|
+
for (const fn of program.functions ?? []) {
|
|
90
93
|
if (visit(fn.statements))
|
|
91
94
|
return true;
|
|
92
95
|
}
|
|
93
|
-
for (const cls of program.classes) {
|
|
94
|
-
for (const m of cls.methods)
|
|
96
|
+
for (const cls of program.classes ?? []) {
|
|
97
|
+
for (const m of cls.methods ?? [])
|
|
95
98
|
if (visit(m.statements))
|
|
96
99
|
return true;
|
|
97
|
-
for (const g of cls.getters)
|
|
100
|
+
for (const g of cls.getters ?? [])
|
|
98
101
|
if (visit(g.statements))
|
|
99
102
|
return true;
|
|
100
|
-
for (const s of cls.setters)
|
|
103
|
+
for (const s of cls.setters ?? [])
|
|
101
104
|
if (visit(s.statements))
|
|
102
105
|
return true;
|
|
103
106
|
if (cls.constructor && visit(cls.constructor.statements))
|
|
@@ -106,12 +109,109 @@ function programUsesWdt(program) {
|
|
|
106
109
|
return false;
|
|
107
110
|
}
|
|
108
111
|
/**
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
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.
|
|
114
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
|
+
}
|
|
115
215
|
function extractShimMacro(lines, macroName) {
|
|
116
216
|
const ifndefIdx = lines.findIndex(l => {
|
|
117
217
|
const trimmed = l.trim();
|
|
@@ -174,9 +274,29 @@ export function buildEmitterContext(program, options) {
|
|
|
174
274
|
stringEnumNames.add(name);
|
|
175
275
|
}
|
|
176
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
|
+
}
|
|
177
287
|
const strategy = options.strategy ?? resolveStrategy(options.target ?? "generic");
|
|
178
288
|
strategy.setLargeEnumNames?.(largeEnumNames);
|
|
179
|
-
|
|
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
|
+
}
|
|
180
300
|
ensureDir(options.outDir);
|
|
181
301
|
const classNameMap = (() => {
|
|
182
302
|
try {
|
|
@@ -226,14 +346,50 @@ export function buildEmitterContext(program, options) {
|
|
|
226
346
|
? (strategy.generateNativePolyfills?.(program, options.platformContext) ?? [])
|
|
227
347
|
: (strategy.generateNativePolyfills?.(program, options.platformContext) ?? []);
|
|
228
348
|
let filteredNativePolyfills = filterPolyfillHelpers(nativePolyfills, programAnalysis.usedPolyfillHelpers);
|
|
229
|
-
|
|
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) {
|
|
230
352
|
filteredNativePolyfills = filteredNativePolyfills.filter(p => p.id !== "cuttlefish_halt");
|
|
231
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
|
+
}
|
|
232
361
|
const usesTimers = programAnalysis.usedPolyfillHelpers.has('__tc_setInterval') ||
|
|
233
362
|
programAnalysis.usedPolyfillHelpers.has('__tc_setTimeout');
|
|
234
363
|
if (!usesTimers) {
|
|
235
364
|
filteredNativePolyfills = filteredNativePolyfills.filter(p => p.id !== "timer_methods");
|
|
236
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
|
+
}
|
|
237
393
|
const allPolyfills = filteredNativePolyfills;
|
|
238
394
|
const emittedPolyfills = allPolyfills.length > 0
|
|
239
395
|
? emitPolyfillBoilerplate(allPolyfills)
|
|
@@ -243,23 +399,15 @@ export function buildEmitterContext(program, options) {
|
|
|
243
399
|
if (!isNpmPackage) {
|
|
244
400
|
includes.push(...strategy.forcedIncludes(program, options.platformContext));
|
|
245
401
|
Object.assign(symbolMap, strategy.symbolAliases(program, options.platformContext));
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
// the analysis runs before the expression renderer, which can
|
|
256
|
-
// introduce cuttlefish_nullish calls (e.g. for default-param
|
|
257
|
-
// destructuring, or `??` inside class methods on abstract bases)
|
|
258
|
-
// that the analysis pass didn't see, leaving the header referencing
|
|
259
|
-
// an undeclared helper. The shim is fully idempotent (#ifndef
|
|
260
|
-
// guards), so emitting it unconditionally is safe and cheap.
|
|
261
|
-
shimLines = [...strategy.shimLines(program, options.platformContext)];
|
|
262
|
-
}
|
|
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)];
|
|
263
411
|
if (!programAnalysis.usesStringConversion) {
|
|
264
412
|
shimLines = shimLines.filter(l => !l.includes('std::string String('));
|
|
265
413
|
}
|
|
@@ -311,7 +459,9 @@ export function buildEmitterContext(program, options) {
|
|
|
311
459
|
// these filters are no-ops there. The strategies also self-gate on the
|
|
312
460
|
// same flags; this is the defensive backstop (mirrors how usesWDT/etc.
|
|
313
461
|
// backstop the strategy-side gating above).
|
|
314
|
-
|
|
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) {
|
|
315
465
|
shimLines = filterShimBlock(shimLines, '// CUTTLEFISH_UART_BEGIN', '// CUTTLEFISH_UART_END');
|
|
316
466
|
shimLines = filterShimBlock(shimLines, '// CUTTLEFISH_UART_EXT_BEGIN', '// CUTTLEFISH_UART_EXT_END');
|
|
317
467
|
}
|
|
@@ -321,7 +471,7 @@ export function buildEmitterContext(program, options) {
|
|
|
321
471
|
if (!programAnalysis.usesI2C) {
|
|
322
472
|
shimLines = filterShimBlock(shimLines, '// CUTTLEFISH_TWI_BEGIN', '// CUTTLEFISH_TWI_END');
|
|
323
473
|
}
|
|
324
|
-
if (!programAnalysis.usesEEPROM) {
|
|
474
|
+
if (!programAnalysis.usesEEPROM && !programAnalysis.usesPreferences) {
|
|
325
475
|
shimLines = filterShimBlock(shimLines, '// CUTTLEFISH_EEPROM_BEGIN', '// CUTTLEFISH_EEPROM_END');
|
|
326
476
|
}
|
|
327
477
|
if (!programAnalysis.usesTone) {
|
|
@@ -361,6 +511,23 @@ export function buildEmitterContext(program, options) {
|
|
|
361
511
|
if (!programAnalysis.usesWifi) {
|
|
362
512
|
shimLines = filterShimBlock(shimLines, '// CUTTLEFISH_WIFI_BEGIN', '// CUTTLEFISH_WIFI_END');
|
|
363
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
|
+
}
|
|
364
531
|
if (!programAnalysis.usesHttp) {
|
|
365
532
|
shimLines = filterShimBlock(shimLines, '// CUTTLEFISH_HTTP_BEGIN', '// CUTTLEFISH_HTTP_END');
|
|
366
533
|
}
|
|
@@ -402,11 +569,11 @@ export function buildEmitterContext(program, options) {
|
|
|
402
569
|
symbolMap[defaultName] = defaultName;
|
|
403
570
|
continue;
|
|
404
571
|
}
|
|
405
|
-
// Compile-time-only packages
|
|
406
|
-
//
|
|
407
|
-
//
|
|
408
|
-
|
|
409
|
-
|
|
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") {
|
|
410
577
|
for (const symbol of imported.namedImports) {
|
|
411
578
|
symbolMap[symbol] = symbol;
|
|
412
579
|
}
|
|
@@ -759,6 +926,10 @@ export function buildEmitterContext(program, options) {
|
|
|
759
926
|
knownTopLevelObjectTypes,
|
|
760
927
|
diagnostics: emitDiagnostics,
|
|
761
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");
|
|
762
933
|
const statementRenderer = new StatementRenderer({
|
|
763
934
|
strategy,
|
|
764
935
|
boardConstants: program.boardConstants,
|
|
@@ -777,6 +948,7 @@ export function buildEmitterContext(program, options) {
|
|
|
777
948
|
interfaceFieldTypes,
|
|
778
949
|
crossModuleClassNames: classNames,
|
|
779
950
|
diagnostics: emitDiagnostics,
|
|
951
|
+
compliance,
|
|
780
952
|
});
|
|
781
953
|
const asyncFunctionOriginalNames = new Set(program.functions.filter((fn) => fn.isAsync).map((fn) => fn.originalName));
|
|
782
954
|
const mapFunctionNameLocal = (originalName) => statementRenderer.mapFunctionName(originalName);
|
|
@@ -991,6 +1163,14 @@ export function buildEmitterContext(program, options) {
|
|
|
991
1163
|
headerLines: ["#pragma once", ""],
|
|
992
1164
|
sourceMapEntries: [],
|
|
993
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,
|
|
994
1174
|
cArrayVarNames: new Set(),
|
|
995
1175
|
fnCArrayVarNames: new Map(),
|
|
996
1176
|
globalPointerVarTypes,
|
|
@@ -1008,5 +1188,6 @@ export function buildEmitterContext(program, options) {
|
|
|
1008
1188
|
templateInterfaceNames,
|
|
1009
1189
|
interfaceNamespaceMap,
|
|
1010
1190
|
interfaceFieldTypes,
|
|
1191
|
+
compliance,
|
|
1011
1192
|
};
|
|
1012
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
|
}
|
|
@@ -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;
|
|
@@ -169,12 +169,21 @@ export function emitUIRuntime(ctx) {
|
|
|
169
169
|
ctx.sourceLines.push(is888
|
|
170
170
|
? "#define UI_COLOR_DEPTH 888\n#define UI_COLOR_T uint32_t\n#define UI_DIM_MASK 0x7F7F7Fu"
|
|
171
171
|
: "#define UI_COLOR_DEPTH 565\n#define UI_COLOR_T uint16_t\n#define UI_DIM_MASK 0x7BEFu");
|
|
172
|
-
//
|
|
173
|
-
//
|
|
174
|
-
//
|
|
175
|
-
//
|
|
176
|
-
|
|
177
|
-
|
|
172
|
+
// The native CuttlefishGFX/CuttlefishCanvas16 class slice is emitted ONLY
|
|
173
|
+
// for adapters that instantiate CuttlefishGFX by value (the planned
|
|
174
|
+
// framework-avr/framework-esp32 native panel drivers). Adapters that alias
|
|
175
|
+
// the token via macro — the Adafruit path (`#define CuttlefishCanvas16
|
|
176
|
+
// GFXcanvas16`) and the SDL path (`#define CuttlefishCanvas16 SdlGfxCanvas`)
|
|
177
|
+
// — own the canvas type; emitting the class slice alongside the macro makes
|
|
178
|
+
// `class CuttlefishCanvas16` macro-expand into a redefinition of Adafruit's
|
|
179
|
+
// GFXcanvas16. Detect the macro-alias path from the adapter's own includes
|
|
180
|
+
// and suppress the slice there. The previous gate reused
|
|
181
|
+
// providesDisplayAdapter(), which merely means "the strategy owns its
|
|
182
|
+
// adapters" (true for Arduino/Adafruit) and is unrelated to whether the
|
|
183
|
+
// native GFX class is wanted — it wrongly emitted the slice on every
|
|
184
|
+
// Arduino build, breaking compilation.
|
|
185
|
+
const macroAliasesCanvas16 = /^\s*#\s*define\s+CuttlefishCanvas16\b/m.test(adapter.includes);
|
|
186
|
+
if (!macroAliasesCanvas16) {
|
|
178
187
|
ctx.sourceLines.push(ui.emitCuttlefishGfx(true));
|
|
179
188
|
}
|
|
180
189
|
ctx.sourceLines.push(adapter.declaration);
|
|
@@ -265,8 +274,8 @@ export function emitUIRuntime(ctx) {
|
|
|
265
274
|
// header so its #ifndef guards adopt them. Source of truth:
|
|
266
275
|
// resolveScrollConfig(profile.scroll). Defaults derive from the declared
|
|
267
276
|
// touch hardware, so the demo (XPT2046) gets resistive+full with no config.
|
|
268
|
-
const scroll = resolveScrollConfig(profile
|
|
269
|
-
ctx.sourceLines.push(`#define UI_SCROLL_MAX_OVERSCROLL ${scroll.maxOverscroll}`, `#define UI_SCROLL_STIFFNESS_X10 ${Math.round(scroll.stiffness * 10)}`, `#define UI_SCROLL_EDGE_SNAP_PX ${scroll.edgeSnapPx}`, `#define UI_SCROLL_DRAG_SCALE_X10 ${Math.round(scroll.dragScale * 10)}`, `#define UI_SCROLL_INPUT_TIER_CAPACITIVE ${scroll.inputTier === "capacitive" ? 1 : 0}`, `#define UI_SCROLL_INPUT_TIER_RESISTIVE ${scroll.inputTier === "resistive" ? 1 : 0}`, `#define UI_SCROLL_INPUT_TIER_NONE ${scroll.inputTier === "none" ? 1 : 0}`, `#define UI_SCROLL_RENDER_TIER_FULL ${scroll.renderTier === "full" ? 1 : 0}`, `#define UI_SCROLL_RENDER_TIER_CONSTRAINED ${scroll.renderTier === "constrained" ? 1 : 0}`,
|
|
277
|
+
const scroll = resolveScrollConfig(profile);
|
|
278
|
+
ctx.sourceLines.push(`#define UI_SCROLL_MAX_OVERSCROLL ${scroll.maxOverscroll}`, `#define UI_SCROLL_STIFFNESS_X10 ${Math.round(scroll.stiffness * 10)}`, `#define UI_SCROLL_EDGE_SNAP_PX ${scroll.edgeSnapPx}`, `#define UI_SCROLL_DRAG_SCALE_X10 ${Math.round(scroll.dragScale * 10)}`, `#define UI_SCROLL_INPUT_TIER_CAPACITIVE ${scroll.inputTier === "capacitive" ? 1 : 0}`, `#define UI_SCROLL_INPUT_TIER_RESISTIVE ${scroll.inputTier === "resistive" ? 1 : 0}`, `#define UI_SCROLL_INPUT_TIER_NONE ${scroll.inputTier === "none" ? 1 : 0}`, `#define UI_SCROLL_RENDER_TIER_FULL ${scroll.renderTier === "full" ? 1 : 0}`, `#define UI_SCROLL_RENDER_TIER_CONSTRAINED ${scroll.renderTier === "constrained" ? 1 : 0}`, ...(scroll.debug ? [`#define UI_SCROLL_DEBUG 1`] : []));
|
|
270
279
|
// Forward-declare __ui_kb_set_onchange before the runtime header: the
|
|
271
280
|
// header's keyboard-open function calls it, but the definition is emitted
|
|
272
281
|
// later (section 8b). On native (single TU) this must precede the header.
|
|
@@ -88,7 +88,7 @@ export declare class ExpressionRenderer {
|
|
|
88
88
|
private readonly stringEnumNames;
|
|
89
89
|
private readonly largeEnumNames;
|
|
90
90
|
private readonly knownFunctionReturnTypes?;
|
|
91
|
-
private
|
|
91
|
+
private knownVariableTypes?;
|
|
92
92
|
private readonly pointerVarTypes?;
|
|
93
93
|
private readonly globalPointerVarTypes?;
|
|
94
94
|
private readonly stringVarNames?;
|
|
@@ -80,6 +80,18 @@ export class ExpressionRenderer {
|
|
|
80
80
|
if (!expr || typeof expr !== 'object' || !expr.kind) {
|
|
81
81
|
return "/* invalid expression */";
|
|
82
82
|
}
|
|
83
|
+
// When a per-scope knownVariableTypes is passed (e.g. a function's child
|
|
84
|
+
// emission scope carrying its parameters), install it on the instance for
|
|
85
|
+
// the duration of this render so every recursive render() call and
|
|
86
|
+
// renderIdentifier() reads the merged scope (globals + params). Without
|
|
87
|
+
// this, recursive paths that don't forward knownVariableTypes (ternary
|
|
88
|
+
// branches, call args, array elements, …) would resolve identifiers
|
|
89
|
+
// against only the top-level scope, missing function parameters — which
|
|
90
|
+
// breaks reserved-name escaping for params like `min`/`max` on Arduino.
|
|
91
|
+
const prevKnownVariableTypes = this.knownVariableTypes;
|
|
92
|
+
if (knownVariableTypes) {
|
|
93
|
+
this.knownVariableTypes = knownVariableTypes;
|
|
94
|
+
}
|
|
83
95
|
let rendered;
|
|
84
96
|
switch (expr.kind) {
|
|
85
97
|
case "number": {
|
|
@@ -100,7 +112,7 @@ export class ExpressionRenderer {
|
|
|
100
112
|
rendered = expr.value ? "true" : "false";
|
|
101
113
|
break;
|
|
102
114
|
case "identifier":
|
|
103
|
-
rendered = this.renderIdentifier(expr.value);
|
|
115
|
+
rendered = this.renderIdentifier(expr.value, knownVariableTypes);
|
|
104
116
|
break;
|
|
105
117
|
case "raw":
|
|
106
118
|
rendered = this.renderRaw(expr.value, exprTransformer);
|
|
@@ -150,6 +162,18 @@ export class ExpressionRenderer {
|
|
|
150
162
|
case "method-call":
|
|
151
163
|
rendered = this.renderMethodCall(expr, exprTransformer);
|
|
152
164
|
break;
|
|
165
|
+
case "call": {
|
|
166
|
+
// Bare call expression: `Name(args)` with no receiver (e.g. a
|
|
167
|
+
// constructor call `SafeInt(x)` written inline, or a free function).
|
|
168
|
+
// Method-on-receiver calls lower as "method-call"; this arm is for the
|
|
169
|
+
// receiverless form. The callee is escaped against C++ reserved names;
|
|
170
|
+
// when a target type is in scope, renderValueForTarget (the caller)
|
|
171
|
+
// wraps this with template args (SafeInt(x) -> SafeInt<int32_t>(x)).
|
|
172
|
+
const callArgs = expr.args.map(a => this.render(a, exprTransformer)).join(", ");
|
|
173
|
+
const callCallee = escapeCppKeyword(exprTransformer ? exprTransformer(expr.callee) : expr.callee, this.strategy.reservedNames());
|
|
174
|
+
rendered = `${callCallee}(${callArgs})`;
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
153
177
|
case "element-access":
|
|
154
178
|
rendered = this.renderElementAccess(expr, exprTransformer, knownVariableTypes);
|
|
155
179
|
break;
|
|
@@ -192,9 +216,10 @@ export class ExpressionRenderer {
|
|
|
192
216
|
`This is a transpiler bug; the renderer is missing a case for this kind.`);
|
|
193
217
|
}
|
|
194
218
|
}
|
|
219
|
+
this.knownVariableTypes = prevKnownVariableTypes;
|
|
195
220
|
return normalizeRawExpression(rendered, this.strategy, this.classNameMap);
|
|
196
221
|
}
|
|
197
|
-
renderIdentifier(value) {
|
|
222
|
+
renderIdentifier(value, scopeKnownVariableTypes) {
|
|
198
223
|
const nullVal = this.strategy.nullValue();
|
|
199
224
|
// expression-to-ir lowers the TS `null` literal to the identifier sentinel
|
|
200
225
|
// "nullptr" and `undefined` to "CUTTLEFISH_UNDEFINED". Both must route
|
|
@@ -210,7 +235,27 @@ export class ExpressionRenderer {
|
|
|
210
235
|
if (this.strategy.passthroughMacroNames().has(value)) {
|
|
211
236
|
return value;
|
|
212
237
|
}
|
|
213
|
-
|
|
238
|
+
// Platform-reserved names (e.g. Arduino `Serial`, or the `min`/`max` math
|
|
239
|
+
// macros) are globals/macros provided by the framework headers. Escaping
|
|
240
|
+
// them would break references to those globals — UNLESS the name is a
|
|
241
|
+
// user-declared variable/parameter that shadows and would collide. Check
|
|
242
|
+
// both the instance field (globals/locals seen so far) AND the per-scope
|
|
243
|
+
// map passed by the caller (function parameters, which live in the child
|
|
244
|
+
// emission scope and aren't on the instance field). Declaration sites
|
|
245
|
+
// already escape user vars (renderParameters → renderTypedName), so the
|
|
246
|
+
// body references must escape to match.
|
|
247
|
+
const reservedNames = this.strategy.reservedNames();
|
|
248
|
+
if (reservedNames.has(value)) {
|
|
249
|
+
const isUserVar = (this.knownVariableTypes !== undefined && this.knownVariableTypes.has(value)) ||
|
|
250
|
+
(scopeKnownVariableTypes !== undefined && scopeKnownVariableTypes.has(value)) ||
|
|
251
|
+
(this.pointerVarTypes !== undefined && this.pointerVarTypes.has(value)) ||
|
|
252
|
+
(this.globalPointerVarTypes !== undefined && this.globalPointerVarTypes.has(value)) ||
|
|
253
|
+
(this.stringVarNames !== undefined && this.stringVarNames.has(value));
|
|
254
|
+
if (!isUserVar) {
|
|
255
|
+
return value;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return escapeCppKeyword(value, reservedNames);
|
|
214
259
|
}
|
|
215
260
|
renderRaw(value, exprTransformer) {
|
|
216
261
|
const effectiveClassNameMap = this.classNameMap;
|
|
@@ -887,10 +932,27 @@ export class ExpressionRenderer {
|
|
|
887
932
|
* (safer than a wrong cast).
|
|
888
933
|
*/
|
|
889
934
|
renderValueForTarget(expr, targetType, knownVariableTypes, exprTransformer) {
|
|
890
|
-
const rendered = this.render(expr, exprTransformer, knownVariableTypes);
|
|
891
935
|
if (!targetType)
|
|
892
|
-
return
|
|
936
|
+
return this.render(expr, exprTransformer, knownVariableTypes);
|
|
893
937
|
const normalizedTarget = targetType.trim();
|
|
938
|
+
// Inject template args for constructor calls on pre-C++17 toolchains
|
|
939
|
+
// (e.g. AVR without CTAD). SafeVariable(0) → SafeVariable<int32_t>(0).
|
|
940
|
+
// Handles both `method-call` (when the constructor appears as a standalone
|
|
941
|
+
// initializer) and `call` (when it appears on the RHS of an assignment,
|
|
942
|
+
// which lowers as `T x = {}; x = T(args);`). The callee must match the
|
|
943
|
+
// template name and the target must be a `Name<...>` instantiation, so a
|
|
944
|
+
// regular function call assigned to a non-templated type never matches.
|
|
945
|
+
const tmplMatch = normalizedTarget.match(/^([A-Za-z_]\w*)<(.+)>/);
|
|
946
|
+
const exprAny = expr;
|
|
947
|
+
const isCtorCall = tmplMatch != null
|
|
948
|
+
&& (exprAny.kind === "method-call" || exprAny.kind === "call")
|
|
949
|
+
&& exprAny.callee === tmplMatch[1]
|
|
950
|
+
&& exprAny.callee != null && !exprAny.callee.includes("<");
|
|
951
|
+
if (isCtorCall && exprAny.args) {
|
|
952
|
+
const argsText = exprAny.args.map(a => this.render(a, exprTransformer, knownVariableTypes)).join(", ");
|
|
953
|
+
return `${tmplMatch[1]}<${tmplMatch[2]}>(${argsText})`;
|
|
954
|
+
}
|
|
955
|
+
const rendered = this.render(expr, exprTransformer, knownVariableTypes);
|
|
894
956
|
// Never cast into string-like storage — enums that lower to const char*
|
|
895
957
|
// (string enums) already match, and a string target is never an enum boundary.
|
|
896
958
|
if (this.isStringLikeCppType(normalizedTarget))
|
|
@@ -1359,6 +1421,8 @@ export class ExpressionRenderer {
|
|
|
1359
1421
|
renderMethodCall(expr, exprTransformer) {
|
|
1360
1422
|
const argsText = expr.args.map(a => this.render(a, exprTransformer)).join(", ");
|
|
1361
1423
|
let callee = exprTransformer ? exprTransformer(expr.callee) : expr.callee;
|
|
1424
|
+
const rExpr = expr.receiverExpr;
|
|
1425
|
+
const mName = expr.methodName;
|
|
1362
1426
|
// Convert . to :: for static or namespace method calls if the IR flagged them.
|
|
1363
1427
|
if (expr.isStatic || expr.isNamespace) {
|
|
1364
1428
|
callee = callee.replace(/\./g, "::");
|
|
@@ -1389,6 +1453,18 @@ export class ExpressionRenderer {
|
|
|
1389
1453
|
return callee;
|
|
1390
1454
|
}
|
|
1391
1455
|
callee = this.escapeFinalMemberName(callee);
|
|
1456
|
+
// Structured-receiver path: when the method-call IR carries a receiverExpr
|
|
1457
|
+
// (the safe.read().ok().fail() chain builder sets this), render the receiver
|
|
1458
|
+
// via the full renderer so nested lambda args survive (the flat callee string
|
|
1459
|
+
// would have destroyed them as /* __lambda__ */ placeholders). The methodName
|
|
1460
|
+
// is escaped against reserved names (e.g. div -> div_).
|
|
1461
|
+
const receiverExpr = expr.receiverExpr;
|
|
1462
|
+
const methodName = expr.methodName;
|
|
1463
|
+
if (receiverExpr !== undefined && methodName !== undefined) {
|
|
1464
|
+
const receiverText = this.render(receiverExpr, exprTransformer);
|
|
1465
|
+
const escapedMethod = escapeCppKeyword(methodName, this.strategy.reservedNames());
|
|
1466
|
+
return `${receiverText}.${escapedMethod}(${argsText})`;
|
|
1467
|
+
}
|
|
1392
1468
|
return `${callee}(${argsText})`;
|
|
1393
1469
|
}
|
|
1394
1470
|
escapeFinalMemberName(callee) {
|