@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,26 @@
|
|
|
1
1
|
// ---------------------------------------------------------------------------
|
|
2
2
|
// Op routing — dispatch a HALOpIR to the right strategy resolver.
|
|
3
3
|
//
|
|
4
|
-
// display.* ops go to resolveDisplayOp (graphics-specific)
|
|
5
|
-
//
|
|
6
|
-
//
|
|
4
|
+
// display.* ops go to resolveDisplayOp (graphics-specific).
|
|
5
|
+
// safety.* ops go to the safety hook's resolveSafetyOp (when @typecad/safety
|
|
6
|
+
// is loaded) — NEVER to a per-target strategy, because the safety
|
|
7
|
+
// package is MCU-agnostic by construction.
|
|
8
|
+
// everything else goes to resolveHALOperation (the existing generic seam).
|
|
9
|
+
// Keeps the consumer sites (expression-renderer, statement-renderer,
|
|
10
|
+
// render-expr) DRY.
|
|
7
11
|
// ---------------------------------------------------------------------------
|
|
12
|
+
import { getSafetyHook } from "../safety-hook.js";
|
|
8
13
|
export function routeHALOp(op, strategy) {
|
|
9
|
-
if (typeof op.operation === "string"
|
|
10
|
-
|
|
14
|
+
if (typeof op.operation === "string") {
|
|
15
|
+
if (op.operation.startsWith("display.")) {
|
|
16
|
+
return strategy.resolveDisplayOp?.(op);
|
|
17
|
+
}
|
|
18
|
+
if (op.operation.startsWith("safety.")) {
|
|
19
|
+
// Safety ops are resolved by the @typecad/safety package via hook.
|
|
20
|
+
// Returns undefined if the package is not loaded (caller emits an
|
|
21
|
+
// unhandled-op warning).
|
|
22
|
+
return getSafetyHook()?.resolveSafetyOp?.(op);
|
|
23
|
+
}
|
|
11
24
|
}
|
|
12
25
|
return strategy.resolveHALOperation?.(op);
|
|
13
26
|
}
|
|
@@ -236,11 +236,14 @@ export function inferSnprintfArg(expr, strategy, scopeState, renderExpression, p
|
|
|
236
236
|
return { format: "%d", arg: renderExpression(expr), estimatedLength: 12, preludeLines: [] };
|
|
237
237
|
}
|
|
238
238
|
}
|
|
239
|
-
const
|
|
240
|
-
|
|
241
|
-
|
|
239
|
+
const halIntFunctions = new Set([
|
|
240
|
+
...(strategy?.halCallNames?.() ?? new Set()),
|
|
241
|
+
// pulseIn/pulseInLong and the Wire_available/Serial_available forms
|
|
242
|
+
// are framework-specific suffix conventions; frameworks may add them
|
|
243
|
+
// to halCallNames. Kept as a fallback here until all frameworks do.
|
|
244
|
+
"pulseIn", "pulseInLong", "Wire_available", "Serial_available",
|
|
242
245
|
]);
|
|
243
|
-
if (
|
|
246
|
+
if (halIntFunctions.has(funcName)) {
|
|
244
247
|
return { format: "%d", arg: renderExpression(expr), estimatedLength: 12, preludeLines: [] };
|
|
245
248
|
}
|
|
246
249
|
}
|
|
@@ -63,6 +63,14 @@ interface StatementRendererContext {
|
|
|
63
63
|
* list. Optional so ad-hoc/test constructions still work.
|
|
64
64
|
*/
|
|
65
65
|
diagnostics?: Diagnostic[];
|
|
66
|
+
/**
|
|
67
|
+
* Optional compliance context for A3-9-1 (fixed-width integer default).
|
|
68
|
+
* When provided and A3-9-1 is enforced, defaultNumericType() returns a
|
|
69
|
+
* fixed-width type instead of the legacy 'int'.
|
|
70
|
+
*/
|
|
71
|
+
compliance?: {
|
|
72
|
+
isBanned(ruleId: string): boolean;
|
|
73
|
+
};
|
|
66
74
|
}
|
|
67
75
|
/**
|
|
68
76
|
* Renders StatementIR nodes to C++ code strings.
|
|
@@ -83,6 +91,8 @@ export declare class StatementRenderer {
|
|
|
83
91
|
private readonly interfaceFieldTypes;
|
|
84
92
|
/** Shared emit-time diagnostics sink (see StatementRendererContext.diagnostics). */
|
|
85
93
|
private readonly _diagnostics;
|
|
94
|
+
/** Compliance context for A3-9-1 fixed-width integer default. */
|
|
95
|
+
private readonly _compliance?;
|
|
86
96
|
constructor(context: StatementRendererContext);
|
|
87
97
|
/**
|
|
88
98
|
* Gets the underlying expression renderer for direct use.
|
|
@@ -116,6 +116,7 @@ export class StatementRenderer {
|
|
|
116
116
|
this.namespaceNames = context.namespaceNames ?? new Set();
|
|
117
117
|
this.interfaceFieldTypes = context.interfaceFieldTypes ?? new Map();
|
|
118
118
|
this._diagnostics = context.diagnostics ?? [];
|
|
119
|
+
this._compliance = context.compliance;
|
|
119
120
|
// Create expression renderer with shared context
|
|
120
121
|
this.expressionRenderer = new ExpressionRenderer({
|
|
121
122
|
strategy: context.strategy,
|
|
@@ -324,7 +325,7 @@ export class StatementRenderer {
|
|
|
324
325
|
this.expressionRenderer.pushPrelude([
|
|
325
326
|
`const char* ${idxVar}_keys[] = { ${keysArr} };`,
|
|
326
327
|
]);
|
|
327
|
-
return `for (${this.strategy.defaultNumericType()} ${idxVar} = 0; ${idxVar} < ${statement.keys.length}; ${idxVar}++)`;
|
|
328
|
+
return `for (${this.strategy.defaultNumericType(this._compliance)} ${idxVar} = 0; ${idxVar} < ${statement.keys.length}; ${idxVar}++)`;
|
|
328
329
|
}
|
|
329
330
|
const varDecl = statement.variable;
|
|
330
331
|
if (varDecl.kind === "var_decl") {
|
|
@@ -485,6 +486,15 @@ export class StatementRenderer {
|
|
|
485
486
|
const rawStmt = statement.callee.slice('__RAW_STMT__'.length);
|
|
486
487
|
return forHeader ? rawStmt : `${rawStmt.endsWith(';') ? rawStmt : rawStmt + ';'}`;
|
|
487
488
|
}
|
|
489
|
+
// Handle expression statements whose IR must render at EMIT time (not the
|
|
490
|
+
// build-time renderExprAsText). The safe.read().ok().fail() chain uses this:
|
|
491
|
+
// its lambda args can only render via the emit-time renderLambda (which
|
|
492
|
+
// produces [&](){...}), so the structured chain IR is carried as args[0]
|
|
493
|
+
// and rendered here via the expression renderer.
|
|
494
|
+
if (statement.callee === '__EXPR_STMT__' && statement.args.length >= 1) {
|
|
495
|
+
const exprText = this.expressionRenderer.render(statement.args[0], undefined, knownVariableTypes);
|
|
496
|
+
return forHeader ? exprText : `${exprText};`;
|
|
497
|
+
}
|
|
488
498
|
// Awaited network markers (__WIFI_WAIT__/__HTTP_WAIT__) reaching the
|
|
489
499
|
// plain renderer means the await sits outside an async state machine
|
|
490
500
|
// (top-level await, or a position the state-machine splitter doesn't
|
|
@@ -658,13 +668,13 @@ export class StatementRenderer {
|
|
|
658
668
|
// that don't support std::vector → emit as a plain C-style array.
|
|
659
669
|
// Mutable arrays (.push/.pop/.indexOf) are rewritten to StaticArray<int> in
|
|
660
670
|
// the IR builder (via mutableArrayVars) and never reach this branch.
|
|
661
|
-
const elementType = parsedElementString(rawType) ?? this.strategy.defaultNumericType();
|
|
671
|
+
const elementType = parsedElementString(rawType) ?? this.strategy.defaultNumericType(this._compliance);
|
|
662
672
|
return forHeader
|
|
663
673
|
? `${elementType} ${safeArrName}[] = { ${elements} }`
|
|
664
674
|
: `${elementType} ${safeArrName}[] = { ${elements} };`;
|
|
665
675
|
}
|
|
666
676
|
// Use "int" for "auto" element type since C arrays need explicit types
|
|
667
|
-
const arrayType = statement.initializer.elementType === "auto" ? this.strategy.defaultNumericType() : statement.initializer.elementType;
|
|
677
|
+
const arrayType = statement.initializer.elementType === "auto" ? this.strategy.defaultNumericType(this._compliance) : statement.initializer.elementType;
|
|
668
678
|
return forHeader
|
|
669
679
|
? `${arrayType} ${safeArrName}[] = { ${elements} }`
|
|
670
680
|
: `${arrayType} ${safeArrName}[] = { ${elements} };`;
|
|
@@ -739,7 +749,7 @@ export class StatementRenderer {
|
|
|
739
749
|
}
|
|
740
750
|
// Handle spread array initializers
|
|
741
751
|
if (statement.initializer.kind === "spread_array") {
|
|
742
|
-
let arrayType = statement.initializer.elementType === "auto" ? this.strategy.defaultNumericType() : statement.initializer.elementType;
|
|
752
|
+
let arrayType = statement.initializer.elementType === "auto" ? this.strategy.defaultNumericType(this._compliance) : statement.initializer.elementType;
|
|
743
753
|
const spreadName = this.expressionRenderer.render(statement.initializer.spreadExpr, calleeTransformer, knownVariableTypes);
|
|
744
754
|
if (statement.initializer.elementType === "auto" && knownVariableTypes && statement.initializer.spreadExpr.kind === "identifier") {
|
|
745
755
|
const srcInfo = knownVariableTypes.get(statement.initializer.spreadExpr.value);
|
|
@@ -892,11 +902,17 @@ export class StatementRenderer {
|
|
|
892
902
|
if (this.stringEnumNames.has(typeName)) {
|
|
893
903
|
return this.strategy.normalizeCppType("const char*");
|
|
894
904
|
}
|
|
905
|
+
// A3-9-1: under autosar, substitute the platform's default numeric type
|
|
906
|
+
// for the legacy "int" spelling. The IR layer hardcodes "int" for number
|
|
907
|
+
// literals, so we catch it here at the renderer boundary.
|
|
908
|
+
if (typeName === "int" && this._compliance?.isBanned("A3-9-1")) {
|
|
909
|
+
return this.strategy.defaultNumericType(this._compliance);
|
|
910
|
+
}
|
|
895
911
|
return this.strategy.normalizeCppType(typeName);
|
|
896
912
|
}
|
|
897
913
|
inferFieldType(value, parentName, fieldName) {
|
|
898
914
|
return inferObjectFieldType(value, this.pointerVarTypes, this.knownFunctionReturnTypes, undefined, undefined, undefined, // largeEnumNames - would need to pass through
|
|
899
|
-
parentName, fieldName, this.strategy.defaultNumericType(), (o, n) => this.strategy.resolvePinType?.(o, n));
|
|
915
|
+
parentName, fieldName, this.strategy.defaultNumericType(this._compliance), (o, n) => this.strategy.resolvePinType?.(o, n));
|
|
900
916
|
}
|
|
901
917
|
/**
|
|
902
918
|
* Transform console.log/error/warn calls based on target platform.
|
|
@@ -76,11 +76,23 @@ export function generateAsyncTaskClass(fnName, fnStatements, strategy, knownFunc
|
|
|
76
76
|
segments.push({ preStatements: currentPre, awaitedArgs: [] });
|
|
77
77
|
const awaitCount = segments.filter((s) => s.awaitedCallee !== undefined).length;
|
|
78
78
|
const stateCount = awaitCount + 1; // STATE_0 … STATE_{awaitCount}; cyclic loops back, linear adds STATE_DONE
|
|
79
|
+
// State enum is emitted as `enum class State` (AUTOSAR A7-2-1), so all
|
|
80
|
+
// references must be scope-qualified as `State::STATE_X`. The stateEnumList
|
|
81
|
+
// (used inside `enum class State { ... }`) uses the bare names; everywhere
|
|
82
|
+
// else (assignments, case labels, comparisons) uses the qualified form.
|
|
79
83
|
const stateNames = [];
|
|
80
|
-
|
|
84
|
+
const qualifiedStateNames = [];
|
|
85
|
+
for (let i = 0; i < stateCount; i++) {
|
|
81
86
|
stateNames.push(`STATE_${i}`);
|
|
82
|
-
|
|
87
|
+
qualifiedStateNames.push(`State::STATE_${i}`);
|
|
88
|
+
}
|
|
89
|
+
if (!isCyclic) {
|
|
83
90
|
stateNames.push("STATE_DONE");
|
|
91
|
+
qualifiedStateNames.push("State::STATE_DONE");
|
|
92
|
+
}
|
|
93
|
+
const Q = (i) => qualifiedStateNames[i];
|
|
94
|
+
const Q_DONE = "State::STATE_DONE";
|
|
95
|
+
const Q_0 = "State::STATE_0";
|
|
84
96
|
// Helper: render a non-await statement as a single C++ line
|
|
85
97
|
const renderStmt = (stmt) => renderStatement(stmt, false, strategy, undefined, undefined, knownFunctionReturnTypes);
|
|
86
98
|
// Collect edge-detection markers from segments
|
|
@@ -138,14 +150,14 @@ export function generateAsyncTaskClass(fnName, fnStatements, strategy, knownFunc
|
|
|
138
150
|
const seg = segments[i];
|
|
139
151
|
const lines = [];
|
|
140
152
|
if (seg.awaitedCallee === undefined) {
|
|
141
|
-
lines.push(`${pad}_state = ${isCyclic ?
|
|
153
|
+
lines.push(`${pad}_state = ${isCyclic ? Q_0 : Q_DONE};`);
|
|
142
154
|
return lines;
|
|
143
155
|
}
|
|
144
156
|
const edge = edgeInfoMap.get(i);
|
|
145
157
|
const tap = tapInfoMap.get(i);
|
|
146
158
|
const net = netInfoMap.get(i);
|
|
147
159
|
if (edge) {
|
|
148
|
-
lines.push(`${pad}_edgePrev_p${edge.pin} = digitalRead(${edge.pin});`);
|
|
160
|
+
lines.push(`${pad}_edgePrev_p${edge.pin} = ${strategy.readDigitalPin?.(String(edge.pin)) ?? `digitalRead(${edge.pin})`};`);
|
|
149
161
|
if (edge.timeout !== null) {
|
|
150
162
|
lines.push(`${pad}_waitUntil = ${strategy.currentTimeMillis()} + ${edge.timeout};`);
|
|
151
163
|
}
|
|
@@ -165,13 +177,13 @@ export function generateAsyncTaskClass(fnName, fnStatements, strategy, knownFunc
|
|
|
165
177
|
const ms = seg.awaitedArgs[0] ? renderExpression(seg.awaitedArgs[0], strategy) : "0";
|
|
166
178
|
lines.push(`${pad}_waitUntil = ${strategy.currentTimeMillis()} + ${ms};`);
|
|
167
179
|
}
|
|
168
|
-
lines.push(`${pad}_state =
|
|
180
|
+
lines.push(`${pad}_state = ${Q(i + 1)};`);
|
|
169
181
|
return lines;
|
|
170
182
|
};
|
|
171
183
|
const caseLines = [];
|
|
172
184
|
for (let i = 0; i < segments.length; i++) {
|
|
173
185
|
const seg = segments[i];
|
|
174
|
-
const stateName =
|
|
186
|
+
const stateName = Q(i);
|
|
175
187
|
const body = [];
|
|
176
188
|
const edgePoll = i > 0 ? edgeInfoMap.get(i - 1) : undefined;
|
|
177
189
|
const tapPoll = i > 0 ? tapInfoMap.get(i - 1) : undefined;
|
|
@@ -194,7 +206,7 @@ export function generateAsyncTaskClass(fnName, fnStatements, strategy, knownFunc
|
|
|
194
206
|
? `(${cond}) || ${strategy.currentTimeMillis()} >= _waitUntil`
|
|
195
207
|
: cond;
|
|
196
208
|
body.push(` {`);
|
|
197
|
-
body.push(` int _cur = digitalRead(${edgePoll.pin});`);
|
|
209
|
+
body.push(` int _cur = ${strategy.readDigitalPin?.(String(edgePoll.pin)) ?? `digitalRead(${edgePoll.pin})`};`);
|
|
198
210
|
body.push(` if (${fullCond}) {`);
|
|
199
211
|
body.push(` ${prevVar} = _cur;`);
|
|
200
212
|
body.push(...runSegment(` `));
|
|
@@ -242,13 +254,13 @@ export function generateAsyncTaskClass(fnName, fnStatements, strategy, knownFunc
|
|
|
242
254
|
caseLines.push(` case ${stateName}:`, ` {`, ...body, ` }`, ` break;`);
|
|
243
255
|
}
|
|
244
256
|
if (!isCyclic)
|
|
245
|
-
caseLines.push(` case
|
|
257
|
+
caseLines.push(` case ${Q_DONE}:`, ` break;`);
|
|
246
258
|
const stateEnumList = stateNames.join(", ");
|
|
247
|
-
const isCompleteExpr = isCyclic ? "false" :
|
|
259
|
+
const isCompleteExpr = isCyclic ? "false" : `_state == ${Q_DONE}`;
|
|
248
260
|
// Build constructor initializer list and edge/tap member declarations
|
|
249
261
|
const edgeMemberArr = Array.from(edgeMembers);
|
|
250
262
|
const tapMemberArr = Array.from(tapMembers.keys());
|
|
251
|
-
const inits = [`_state(
|
|
263
|
+
const inits = [`_state(${Q_0})`, `_waitUntil(0)`];
|
|
252
264
|
for (const m of edgeMemberArr)
|
|
253
265
|
inits.push(`${m}(LOW)`);
|
|
254
266
|
for (const m of tapMemberArr)
|
|
@@ -263,7 +275,7 @@ export function generateAsyncTaskClass(fnName, fnStatements, strategy, knownFunc
|
|
|
263
275
|
`// Async state machine for ${fnName}`,
|
|
264
276
|
`class ${className} {`,
|
|
265
277
|
`public:`,
|
|
266
|
-
` enum State { ${stateEnumList} };`,
|
|
278
|
+
` enum class State { ${stateEnumList} };`,
|
|
267
279
|
` ${className}() : ${ctorInitList} {}`,
|
|
268
280
|
` void run() {`,
|
|
269
281
|
` switch (_state) {`,
|
|
@@ -271,7 +283,7 @@ export function generateAsyncTaskClass(fnName, fnStatements, strategy, knownFunc
|
|
|
271
283
|
` }`,
|
|
272
284
|
` }`,
|
|
273
285
|
` bool isComplete() const { return ${isCompleteExpr}; }`,
|
|
274
|
-
` void reset() { _state =
|
|
286
|
+
` void reset() { _state = ${Q_0}; _waitUntil = 0;${edgeResetList}${tapResetList} }`,
|
|
275
287
|
`private:`,
|
|
276
288
|
` State _state;`,
|
|
277
289
|
` unsigned long _waitUntil;`,
|
|
@@ -394,6 +406,21 @@ function netWaitInfo(op, strategy) {
|
|
|
394
406
|
}
|
|
395
407
|
break;
|
|
396
408
|
}
|
|
409
|
+
case "worker.submit": {
|
|
410
|
+
// Worker offload (Phase 3). `await worker.submit(fn, arg)` rewrites to:
|
|
411
|
+
// submit the worker (start), then poll worker.done on the same handle.
|
|
412
|
+
// The submit op is emitted verbatim as the start line; the poll predicate
|
|
413
|
+
// is worker.done(handle) carrying the SAME handleId. This is a predicate-
|
|
414
|
+
// poll resume (same per-frame cost as net/timer awaits), NOT an ISR-driven
|
|
415
|
+
// resume — that defers with DMA-backed awaits.
|
|
416
|
+
const submit = routeHALOp(op, strategy);
|
|
417
|
+
// Carry the handle through to the poll so submit + done agree on the slot.
|
|
418
|
+
const poll = route({ operation: "worker.done", handleId: o.handleId })?.expression ?? null;
|
|
419
|
+
if (submit?.code && poll) {
|
|
420
|
+
return { startLines: [submit.code], pollCond: poll, timeoutExpr: null };
|
|
421
|
+
}
|
|
422
|
+
break;
|
|
423
|
+
}
|
|
397
424
|
}
|
|
398
425
|
// Fallback: run the blocking form immediately and complete on the next tick.
|
|
399
426
|
const blocking = routeHALOp(op, strategy);
|
|
@@ -50,6 +50,8 @@ export function loadFrameworkPackage(packageName, fromDir = process.cwd()) {
|
|
|
50
50
|
} : undefined,
|
|
51
51
|
classNameMapBuilder: typeof mod.buildClassNameMap === "function" ? mod.buildClassNameMap : undefined,
|
|
52
52
|
libDeclGenerator: typeof mod.tryGenerateLibDecl === "function" ? mod.tryGenerateLibDecl : undefined,
|
|
53
|
+
doctor: typeof mod.doctor === "function" ? mod.doctor : undefined,
|
|
54
|
+
licenses: typeof mod.licenses === "function" ? mod.licenses : undefined,
|
|
53
55
|
};
|
|
54
56
|
setLoadedFramework(framework);
|
|
55
57
|
registerPlatformStrategy(strategy);
|
|
@@ -14,6 +14,16 @@ export interface LoadedFramework {
|
|
|
14
14
|
classNameMapBuilder?: (imports: any[]) => Map<string, string>;
|
|
15
15
|
/** Framework library .d.ts declaration generation */
|
|
16
16
|
libDeclGenerator?: (modulePath: string, file: string) => string | undefined;
|
|
17
|
+
/**
|
|
18
|
+
* Optional subcommand presenters owned by the framework. Cuttlefish dispatches
|
|
19
|
+
* `cuttlefish doctor` / `cuttlefish licenses` to these when the loaded
|
|
20
|
+
* framework provides them; otherwise it prints a no-support message. Each
|
|
21
|
+
* framework decides what (if anything) these do — e.g. framework-arduino
|
|
22
|
+
* checks arduino-cli + board core (doctor) and scans Arduino library licenses
|
|
23
|
+
* (licenses).
|
|
24
|
+
*/
|
|
25
|
+
doctor?: () => void;
|
|
26
|
+
licenses?: (strict: boolean, all: boolean) => void;
|
|
17
27
|
}
|
|
18
28
|
/**
|
|
19
29
|
* Resolves framework-specific library imports (e.g., Arduino libraries).
|
|
@@ -32,6 +42,13 @@ export interface FrameworkToolchain {
|
|
|
32
42
|
compile(options: ToolchainOptions): CompileResult;
|
|
33
43
|
upload?(options: ToolchainOptions): UploadResult;
|
|
34
44
|
monitor?(options: ToolchainOptions): void;
|
|
45
|
+
/**
|
|
46
|
+
* Optional: launch an interactive debugger session for the last build
|
|
47
|
+
* (e.g. `west debug`). Not invoked by the standard build/compile flow;
|
|
48
|
+
* powers an explicit debug-attach entry point. Frameworks that support
|
|
49
|
+
* source-level debugging implement this to spawn their native debugger.
|
|
50
|
+
*/
|
|
51
|
+
debug?(options: ToolchainOptions): void;
|
|
35
52
|
}
|
|
36
53
|
export declare function setLoadedFramework(framework: LoadedFramework): void;
|
|
37
54
|
export declare function getLoadedFramework(): LoadedFramework;
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
// Detects comparisons against analogRead() values that exceed the board's
|
|
5
5
|
// ADC resolution (e.g., comparing to > 1023 on a 10-bit Arduino Uno).
|
|
6
6
|
// ---------------------------------------------------------------------------
|
|
7
|
+
import { hasLoadedFramework, getLoadedFramework } from '../framework-registry.js';
|
|
7
8
|
/**
|
|
8
9
|
* Get ADC configuration from board constants.
|
|
9
10
|
*/
|
|
@@ -41,7 +42,19 @@ function isAnalogRead(expr) {
|
|
|
41
42
|
return op === 'adc.read' || op === 'adc.read_voltage';
|
|
42
43
|
}
|
|
43
44
|
if (expr.kind === 'raw') {
|
|
44
|
-
|
|
45
|
+
const value = expr.value;
|
|
46
|
+
// Ask the loaded framework which call names produce an ADC read; fall back
|
|
47
|
+
// to the Wiring-derived analogRead when no framework is loaded.
|
|
48
|
+
const frameworkNames = hasLoadedFramework()
|
|
49
|
+
? getLoadedFramework().strategy.analogReadCallNames?.()
|
|
50
|
+
: undefined;
|
|
51
|
+
const names = frameworkNames ?? new Set(['analogRead']);
|
|
52
|
+
for (const name of names) {
|
|
53
|
+
const re = new RegExp(`${name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\s*\\(`);
|
|
54
|
+
if (re.test(value))
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
return false;
|
|
45
58
|
}
|
|
46
59
|
return false;
|
|
47
60
|
}
|
|
@@ -78,6 +78,7 @@ export declare class CompilationContext {
|
|
|
78
78
|
floatVariables: Set<string>;
|
|
79
79
|
snprintfCounter: number;
|
|
80
80
|
callbackPlaceholderCounter: number;
|
|
81
|
+
bleCharCounter: number;
|
|
81
82
|
activeStrategy: PlatformStrategy | null;
|
|
82
83
|
restParamFunctions: Map<string, string>;
|
|
83
84
|
/**
|
|
@@ -65,6 +65,12 @@ export class CompilationContext {
|
|
|
65
65
|
this.floatVariables = new Set();
|
|
66
66
|
this.snprintfCounter = 0;
|
|
67
67
|
this.callbackPlaceholderCounter = 0;
|
|
68
|
+
// BLE characteristic index counter — persists across separate Ble.server()
|
|
69
|
+
// calls so a multi-characteristic server (one server() per char, the common
|
|
70
|
+
// ble-demo pattern) gets unique sequential indices instead of every char
|
|
71
|
+
// clobbering slot 0. Read by the server()/characteristic() resolver branches
|
|
72
|
+
// in hal-parser.ts. Reset per file in hal-emitter.ts with the other counters.
|
|
73
|
+
this.bleCharCounter = 0;
|
|
68
74
|
this.activeStrategy = null;
|
|
69
75
|
this.restParamFunctions = new Map();
|
|
70
76
|
/**
|
|
@@ -246,6 +252,14 @@ export function setCurrentBoardConstants(v) {
|
|
|
246
252
|
pinAliasMap.set(name, num);
|
|
247
253
|
mcuPinForwardMap.set(name, num);
|
|
248
254
|
mcuPinReverseMap.set(num, name);
|
|
255
|
+
// Register an identifier-safe variant so that pins whose canonical
|
|
256
|
+
// name is not a legal JS identifier (e.g. nRF52840 "P0.28") can be
|
|
257
|
+
// imported under their underscore form (P0_28). See "Pin Naming
|
|
258
|
+
// Conventions" in the root AGENTS.md.
|
|
259
|
+
const identName = name.replace(/[.\s-]/g, "_");
|
|
260
|
+
if (identName !== name) {
|
|
261
|
+
pinAliasMap.set(identName, num);
|
|
262
|
+
}
|
|
249
263
|
}
|
|
250
264
|
}
|
|
251
265
|
}
|
package/dist/ir/build-ir.js
CHANGED
|
@@ -443,14 +443,14 @@ export function buildProgramIR(fileName, sourceText, boardPackage, prebuiltClass
|
|
|
443
443
|
}
|
|
444
444
|
}
|
|
445
445
|
}
|
|
446
|
-
// Track HAL instances imported from board packages and framework
|
|
447
|
-
// so the HAL resolver can resolve them to
|
|
446
|
+
// Track HAL instances imported from board packages and framework HAL
|
|
447
|
+
// subpaths so the HAL resolver can resolve them to framework C++ names.
|
|
448
448
|
// The `@typecad/board` virtual import resolves to the board package, so it
|
|
449
449
|
// also registers pin aliases (LED, etc.). Case-insensitive on the scope.
|
|
450
450
|
const lowerSpecifier = moduleSpecifier.toLowerCase();
|
|
451
451
|
const isHALSource = lowerSpecifier.startsWith('@typecad/board-')
|
|
452
|
-
|| lowerSpecifier === '@typecad/
|
|
453
|
-
|| lowerSpecifier
|
|
452
|
+
|| lowerSpecifier === '@typecad/board'
|
|
453
|
+
|| /^@typecad\/framework-[a-z0-9-]+\/(arduino|hal|gpio)$/.test(lowerSpecifier);
|
|
454
454
|
// UI authoring namespace: `import { ui } from "@typecad/ui"`. The `ui`
|
|
455
455
|
// value is a compile-time construct (its calls are intercepted by
|
|
456
456
|
// tryResolveUICall); it must NOT be emitted as a C++ struct/value.
|
package/dist/ir/call-graph.js
CHANGED
|
@@ -159,6 +159,22 @@ export function buildCallGraph(program) {
|
|
|
159
159
|
}
|
|
160
160
|
}
|
|
161
161
|
}
|
|
162
|
+
// Registered callbacks from the HAL resolver (e.g. the arrow function passed
|
|
163
|
+
// to `Ble.characteristic(...).onRead(() => readTemp())`) are NOT present in
|
|
164
|
+
// top-level statements — they ride in `program.registeredCallbacks` and are
|
|
165
|
+
// later hoisted to file-scope `*_isr_*` functions by top-level-prep. The
|
|
166
|
+
// placeholder name (`__CALLBACK_N__`) is what surfaces in top-level deps,
|
|
167
|
+
// not the identifiers the callback body references (e.g. a free function
|
|
168
|
+
// like `readTemp`). Without this scan those references are invisible to the
|
|
169
|
+
// call graph: the function survives the hoist (its call sits inside the
|
|
170
|
+
// emitted ISR) but is tree-shaken as unreachable, and g++ later reports it
|
|
171
|
+
// "not declared in this scope". Attach their body identifiers to
|
|
172
|
+
// __top_level__ since the callbacks execute from the top-level entry path.
|
|
173
|
+
for (const rc of (program.registeredCallbacks ?? [])) {
|
|
174
|
+
for (const id of collectExpressionIdentifiers(rc.callbackIR)) {
|
|
175
|
+
topLevelDependencies.add(id);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
162
178
|
nodes.set("__top_level__", {
|
|
163
179
|
name: "__top_level__",
|
|
164
180
|
kind: "variable",
|
|
@@ -12,6 +12,7 @@ import { tryLowerRegisterRead } from "./transformers/register-assignment.js";
|
|
|
12
12
|
import { tryLowerArrayAndStringMethods } from "./transformers/array-methods.js";
|
|
13
13
|
import { collectReturns, inferExprCppType, typeNodeToCppType } from "./type-resolution.js";
|
|
14
14
|
import { parseCppType, elementOf, renderCppType, bareType, parsedIsPointer, parsedIsVector, parsedIsMap, parsedIsSet, parsedIsTuple, parsedIsStdString, parsedElementString, parsedBareString, isContainer } from "../api/shared/cpp-type-ir.js";
|
|
15
|
+
import { hasSafetyHook, requireSafetyHook } from "../safety-hook.js";
|
|
15
16
|
function resolveExprCppType(expr) {
|
|
16
17
|
if (ts.isNonNullExpression(expr) || ts.isParenthesizedExpression(expr)) {
|
|
17
18
|
return resolveExprCppType(expr.expression);
|
|
@@ -184,6 +185,143 @@ function resolveCallReturnTypeForNullGuard(call, sourceText) {
|
|
|
184
185
|
}
|
|
185
186
|
return undefined;
|
|
186
187
|
}
|
|
188
|
+
/** The SafeReadResult/SafeWriteResult chain methods whose lambda args must
|
|
189
|
+
* render inline (preserving closure captures). Detected on the TS chain to
|
|
190
|
+
* decide whether to take the structured-receiver path. */
|
|
191
|
+
const SAFETY_RESULT_CHAIN_METHODS = new Set(["ok", "fail", "fault", "always"]);
|
|
192
|
+
/** Detect `safe.<read|write>(...).<ok|fail|fault|always>(handler)...` chains
|
|
193
|
+
* and build them as nested method-call IR nodes with a STRUCTURED receiverExpr
|
|
194
|
+
* (not a flattened callee string). This preserves inner lambda args so the
|
|
195
|
+
* emit pipeline can render them inline via renderLambda.
|
|
196
|
+
*
|
|
197
|
+
* Returns null when the expression is not a safe.* call chained with at least
|
|
198
|
+
* one result method (terminal safe.read/safe.write are handled by the
|
|
199
|
+
* statement/var-decl interceptors; non-safe chains use the generic path). */
|
|
200
|
+
function tryResolveSafetyChain(expr, sourceText, diagnostics, pointerVars) {
|
|
201
|
+
if (!hasSafetyHook())
|
|
202
|
+
return null;
|
|
203
|
+
const chain = [];
|
|
204
|
+
let cur = expr;
|
|
205
|
+
while (ts.isCallExpression(cur) && ts.isPropertyAccessExpression(cur.expression)) {
|
|
206
|
+
chain.push({ methodName: cur.expression.name.text, callNode: cur });
|
|
207
|
+
cur = cur.expression.expression;
|
|
208
|
+
}
|
|
209
|
+
// After the loop, `cur` is the innermost receiver. For a safe.* chain, the
|
|
210
|
+
// last-pushed chain element is the safe.read/safe.write root (its
|
|
211
|
+
// callNode.expression.expression is the identifier `safe`). Identify it.
|
|
212
|
+
if (chain.length === 0)
|
|
213
|
+
return null;
|
|
214
|
+
const root = chain[chain.length - 1];
|
|
215
|
+
const rootCall = root.callNode;
|
|
216
|
+
// rootCall.expression is `safe.read` (PropertyAccess); rootCall.expression.expression
|
|
217
|
+
// must be the identifier `safe` for this to be a safe.* root.
|
|
218
|
+
// The root method is read/write; the links above it (chain[0..length-2]) must
|
|
219
|
+
// include at least one result-chain method (ok/fail/fault/always).
|
|
220
|
+
const resultLinks = chain.slice(0, chain.length - 1);
|
|
221
|
+
const hasChainMethod = resultLinks.some((link) => SAFETY_RESULT_CHAIN_METHODS.has(link.methodName));
|
|
222
|
+
if (!hasChainMethod)
|
|
223
|
+
return null;
|
|
224
|
+
// Resolve the safe.<method>(...) root to a hal-expr (the same way variables.ts
|
|
225
|
+
// does for `const r = safe.read(pin)`). This becomes the innermost receiver.
|
|
226
|
+
const rootPropAccess = rootCall.expression;
|
|
227
|
+
const safeMethod = rootPropAccess.name.text;
|
|
228
|
+
const argValues = rootCall.arguments.map((a) => {
|
|
229
|
+
if (ts.isNumericLiteral(a))
|
|
230
|
+
return Number(a.text);
|
|
231
|
+
if (ts.isStringLiteral(a))
|
|
232
|
+
return a.text;
|
|
233
|
+
if (a.kind === ts.SyntaxKind.TrueKeyword)
|
|
234
|
+
return true;
|
|
235
|
+
if (a.kind === ts.SyntaxKind.FalseKeyword)
|
|
236
|
+
return false;
|
|
237
|
+
if (ts.isIdentifier(a))
|
|
238
|
+
return a.text;
|
|
239
|
+
return a.getText();
|
|
240
|
+
});
|
|
241
|
+
const op = requireSafetyHook().resolveSemanticCall?.(`safe.${safeMethod}`, argValues);
|
|
242
|
+
// If the hook couldn't resolve the pin (halInstances not yet populated for
|
|
243
|
+
// this identifier at this point in IR building), fall back to rendering the
|
|
244
|
+
// root verbatim. The chain structure (with inline lambdas) is still correct;
|
|
245
|
+
// only the root's HAL resolution is deferred. This happens for multi-link
|
|
246
|
+
// chains where the resolver runs before the pin's HAL instance is registered.
|
|
247
|
+
let receiver;
|
|
248
|
+
if (op) {
|
|
249
|
+
receiver = { kind: "hal-expr", operation: op };
|
|
250
|
+
}
|
|
251
|
+
else {
|
|
252
|
+
// Best-effort: resolve the pin number directly from halInstances (the hook's
|
|
253
|
+
// resolvePinArg does the same lookup, but calling it directly here avoids the
|
|
254
|
+
// hook's early-return on unresolved pins).
|
|
255
|
+
const pinArg = argValues[0];
|
|
256
|
+
let pinNum;
|
|
257
|
+
if (typeof pinArg === "string") {
|
|
258
|
+
const inst = halInstances.get(pinArg);
|
|
259
|
+
if (inst) {
|
|
260
|
+
const pv = inst.fieldValues.get("_pin") ?? inst.fieldValues.get("pin");
|
|
261
|
+
if (pv !== undefined)
|
|
262
|
+
pinNum = Number(pv);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
else if (typeof pinArg === "number") {
|
|
266
|
+
pinNum = pinArg;
|
|
267
|
+
}
|
|
268
|
+
if (pinNum !== undefined) {
|
|
269
|
+
// Build the op manually now that we have the pin.
|
|
270
|
+
const fallbackOp = safeMethod === "read"
|
|
271
|
+
? { operation: "safety.read_safe", pin: pinNum }
|
|
272
|
+
: { operation: "safety.write_verify", pin: pinNum, value: argValues[1] ?? 0 };
|
|
273
|
+
receiver = { kind: "hal-expr", operation: fallbackOp };
|
|
274
|
+
}
|
|
275
|
+
else {
|
|
276
|
+
// Last resort: verbatim text. The chain renders but the root won't have
|
|
277
|
+
// resolved to __tc_safety::read_safe — acceptable degradation.
|
|
278
|
+
receiver = { kind: "raw", value: rootCall.getText() };
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
// The root renders as __tc_safety::read_safe(pin) — render it to text so the
|
|
282
|
+
// callee string (used for non-receiverExpr consumers like type inference) is
|
|
283
|
+
// correct.
|
|
284
|
+
const rootText = renderExprAsText(receiver);
|
|
285
|
+
// Build nested method-call nodes bottom-up over the RESULT links only
|
|
286
|
+
// (chain[0..length-2], excluding the root at chain[length-1]). chain[0] is the
|
|
287
|
+
// outermost call (e.g. .fail); we build from the innermost result link outward.
|
|
288
|
+
for (let i = resultLinks.length - 1; i >= 0; i--) {
|
|
289
|
+
const link = resultLinks[i];
|
|
290
|
+
// Render this link's args as IR (lambdas become lambda IR nodes that
|
|
291
|
+
// renderLambda handles at emit time).
|
|
292
|
+
const argIRs = link.callNode.arguments.map((a) => expressionToIR(a, sourceText, diagnostics, pointerVars));
|
|
293
|
+
// Set concrete param types on lambda args so renderLambda emits
|
|
294
|
+
// `const SafeReadResult& r` instead of `auto r`. Generic lambdas (auto
|
|
295
|
+
// params) require C++14; AVR's gnu++11 doesn't support them. The chain
|
|
296
|
+
// knows the result type from the root (read -> SafeReadResult,
|
|
297
|
+
// write -> SafeWriteResult).
|
|
298
|
+
const resultType = safeMethod === "read" ? "SafeReadResult" : "SafeWriteResult";
|
|
299
|
+
for (const argIR of argIRs) {
|
|
300
|
+
if (argIR.kind === "lambda" && argIR.params) {
|
|
301
|
+
for (const p of argIR.params) {
|
|
302
|
+
if (!p.cppType || p.cppType === "auto") {
|
|
303
|
+
p.cppType = `const ${resultType}&`;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
// Build a callee string for backward-compat with consumers that read
|
|
309
|
+
// method-call.callee. The receiver text is rootText for the innermost link,
|
|
310
|
+
// or the prior receiver's rendered text for outer links.
|
|
311
|
+
const priorCallee = i === resultLinks.length - 1
|
|
312
|
+
? rootText
|
|
313
|
+
: renderExprAsText(receiver);
|
|
314
|
+
const callee = `${priorCallee}.${escapeCppKeyword(link.methodName, new Set())}`;
|
|
315
|
+
receiver = {
|
|
316
|
+
kind: "method-call",
|
|
317
|
+
callee,
|
|
318
|
+
args: argIRs,
|
|
319
|
+
methodName: link.methodName,
|
|
320
|
+
receiverExpr: receiver,
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
return receiver;
|
|
324
|
+
}
|
|
187
325
|
export function expressionToIR(expr, sourceText, diagnostics, pointerVars = new Map()) {
|
|
188
326
|
function emitUnsupportedExpression(message) {
|
|
189
327
|
diagnostics.push(makeDiagnostic(sourceText, expr.pos, message, "error", "TS2CPP_UNSUPPORTED_EXPR"));
|
|
@@ -953,6 +1091,18 @@ export function expressionToIR(expr, sourceText, diagnostics, pointerVars = new
|
|
|
953
1091
|
};
|
|
954
1092
|
}
|
|
955
1093
|
if (ts.isCallExpression(expr)) {
|
|
1094
|
+
// ---- safe.read/safe.write chained with .ok/.fail/.fault/.always ----
|
|
1095
|
+
// Method chains like `safe.read(pin).ok(r => {...}).fail(r => {...})` cannot
|
|
1096
|
+
// use the generic method-call builder because it flattens the receiver into
|
|
1097
|
+
// a callee STRING (expression-to-ir.ts ~line 1475), destroying inner lambda
|
|
1098
|
+
// args (they become `/* __lambda__ */` placeholders baked into the callee
|
|
1099
|
+
// text). This resolver detects a safe.* call followed by a chain of fluent
|
|
1100
|
+
// result methods and builds it bottom-up as nested method-call nodes with a
|
|
1101
|
+
// STRUCTURED receiverExpr, so the emit pipeline renders each lambda arg
|
|
1102
|
+
// inline via renderLambda (preserving closure captures).
|
|
1103
|
+
const safetyChain = tryResolveSafetyChain(expr, sourceText, diagnostics, pointerVars);
|
|
1104
|
+
if (safetyChain)
|
|
1105
|
+
return safetyChain;
|
|
956
1106
|
// ---- rawCppExpr() — compile-time C++ injection in expression context ----
|
|
957
1107
|
// Mirrors the statement-position rawCpp()/__EMIT__ path, but emits the raw
|
|
958
1108
|
// text as an expression (e.g. for IDF macros like WIFI_INIT_CONFIG_DEFAULT()
|
|
@@ -163,6 +163,7 @@ export function functionDeclarationToIR(node, fileName, sourceText, diagnostics,
|
|
|
163
163
|
const elementType = restParam.cppType.replace(/^(?:const\s+)?std::vector<(.+)>&?$/, "$1");
|
|
164
164
|
restParamFunctions.set(node.name.text, elementType);
|
|
165
165
|
}
|
|
166
|
+
const fnDecorators = extractAsilFromComments(node, sourceText);
|
|
166
167
|
return {
|
|
167
168
|
originalName: node.name.text,
|
|
168
169
|
isAsync,
|
|
@@ -174,8 +175,29 @@ export function functionDeclarationToIR(node, fileName, sourceText, diagnostics,
|
|
|
174
175
|
...(fnTypeParams && fnTypeParams.length > 0 ? { typeParameters: fnTypeParams } : {}),
|
|
175
176
|
...(isGenerator ? { isGenerator: true } : {}),
|
|
176
177
|
...(isExported ? { isExported: true } : {}),
|
|
178
|
+
...(fnDecorators && fnDecorators.length > 0 ? { decorators: fnDecorators } : {}),
|
|
177
179
|
};
|
|
178
180
|
}
|
|
181
|
+
/** Extract ASIL annotations from leading comments (e.g. `// @asilD`).
|
|
182
|
+
*
|
|
183
|
+
* Uses comment-based annotations instead of TypeScript decorators because
|
|
184
|
+
* TS decorators on function declarations produce TS1206 errors in the VS Code
|
|
185
|
+
* language server and the @typescript-eslint/parser (both reject decorators
|
|
186
|
+
* on functions — only class/method/property decorators are supported).
|
|
187
|
+
* Comment annotations are valid TypeScript in any context, any editor, and
|
|
188
|
+
* any toolchain — no false-positive errors. */
|
|
189
|
+
function extractAsilFromComments(node, sourceText) {
|
|
190
|
+
const comments = extractNodeComments(node, sourceText);
|
|
191
|
+
const names = [];
|
|
192
|
+
for (const comment of comments.leadingComments) {
|
|
193
|
+
// Match `// @asilD`, `// @asilC`, `// @asilB`, `// @asilA`, `// @asilQM`
|
|
194
|
+
const match = comment.match(/@(asil[DCBAQM])/i);
|
|
195
|
+
if (match) {
|
|
196
|
+
names.push(match[1]);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return names.length > 0 ? names : undefined;
|
|
200
|
+
}
|
|
179
201
|
export function variableAsFunctionToIR(node, fileName, sourceText, diagnostics, functionReturnTypes, typeAliasNodes, boilerplates, pointerVars = new Map(), lowerStatementList) {
|
|
180
202
|
const functionExpressionDeclarations = node.declarationList.declarations.filter((declaration) => {
|
|
181
203
|
if (!ts.isIdentifier(declaration.name) || !declaration.initializer) {
|