@typecad/cuttlefish 1.0.0-alpha.6 → 1.0.0-alpha.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/config.d.ts +32 -0
- package/dist/api/index.d.ts +1 -1
- package/dist/api/schema/types.d.ts +15 -0
- package/dist/api/shared/async-runtime-static.d.ts +12 -1
- package/dist/api/shared/async-runtime-static.js +69 -24
- package/dist/api/shared/async-symbol-detector.d.ts +11 -0
- package/dist/api/shared/async-symbol-detector.js +140 -0
- package/dist/api/shared/async-types.d.ts +24 -0
- package/dist/api/shared/coop-scheduler.d.ts +60 -0
- package/dist/api/shared/coop-scheduler.js +149 -0
- package/dist/api/shared/display-adapter.d.ts +4 -3
- package/dist/api/shared/display-adapter.js +15 -118
- package/dist/api/shared/display-adapters/sdl.js +38 -31
- package/dist/api/shared/display-profile.d.ts +47 -25
- package/dist/api/shared/display-profile.js +31 -164
- package/dist/api/shared/framework-manifest-registry.d.ts +9 -0
- package/dist/api/shared/framework-manifest-registry.js +27 -0
- package/dist/api/shared/framework-manifest.d.ts +581 -0
- package/dist/api/shared/framework-manifest.js +182 -0
- package/dist/api/shared/glcdfont.d.ts +12 -0
- package/dist/api/shared/glcdfont.js +124 -0
- package/dist/api/shared/graphics-strategy.d.ts +37 -0
- package/dist/api/shared/hal-op-ir.d.ts +755 -1
- package/dist/api/shared/hal-op-ir.js +129 -1
- package/dist/api/shared/index.d.ts +20 -2
- package/dist/api/shared/index.js +25 -1
- package/dist/api/shared/ir-core.d.ts +4 -0
- package/dist/api/shared/ir-declarations.d.ts +6 -0
- package/dist/api/shared/native-display-op-resolver.d.ts +10 -0
- package/dist/api/shared/native-display-op-resolver.js +64 -0
- package/dist/api/shared/platform-strategy.d.ts +113 -2
- package/dist/api/shared/polyfill-helper-registry.js +18 -1
- package/dist/api/shared/promise-runtime.d.ts +1 -1
- package/dist/api/shared/promise-runtime.js +95 -13
- package/dist/api/shared/toolchain-types.d.ts +17 -0
- package/dist/api/shared/validate-framework-manifest.d.ts +28 -0
- package/dist/api/shared/validate-framework-manifest.js +494 -0
- package/dist/api/shared/worker-runtime-polyfill.d.ts +22 -0
- package/dist/api/shared/worker-runtime-polyfill.js +34 -0
- package/dist/api/shared/worker-runtime.d.ts +69 -0
- package/dist/api/shared/worker-runtime.js +163 -0
- package/dist/cli.js +186 -82
- package/dist/config-loader.d.ts +7 -2
- package/dist/config-loader.js +23 -3
- package/dist/config-schema.d.ts +112 -70
- package/dist/config-schema.js +14 -0
- package/dist/contract/board-generator.d.ts +17 -0
- package/dist/contract/board-generator.js +57 -0
- package/dist/contract/contract-parser.d.ts +217 -0
- package/dist/contract/contract-parser.js +224 -0
- package/dist/contract/index.d.ts +22 -0
- package/dist/contract/index.js +68 -0
- package/dist/create/board-codegen.js +4 -4
- package/dist/create/board-generators.js +4 -5
- package/dist/create/board-spec.d.ts +72 -75
- package/dist/create/board-spec.js +0 -1
- package/dist/create/init-scaffold.d.ts +9 -2
- package/dist/create/init-scaffold.js +0 -14
- package/dist/create/init-templates.d.ts +2 -0
- package/dist/create/init-templates.js +74 -8
- package/dist/create/init-wizard.js +31 -7
- package/dist/debug/preprocessor.js +178 -39
- package/dist/debug/types.d.ts +23 -0
- package/dist/diagnostics/mermaid-builder.d.ts +1 -1
- package/dist/diagnostics/mermaid-builder.js +34 -24
- package/dist/emit/compliance/arxml-writer.d.ts +11 -0
- package/dist/emit/compliance/arxml-writer.js +34 -0
- package/dist/emit/compliance/compliance-context.d.ts +57 -0
- package/dist/emit/compliance/compliance-context.js +113 -0
- package/dist/emit/compliance/deviation-ledger.d.ts +27 -0
- package/dist/emit/compliance/deviation-ledger.js +47 -0
- package/dist/emit/compliance/deviation-writer.d.ts +30 -0
- package/dist/emit/compliance/deviation-writer.js +37 -0
- package/dist/emit/compliance/index.d.ts +7 -0
- package/dist/emit/compliance/index.js +6 -0
- package/dist/emit/compliance/rule-engine.d.ts +13 -0
- package/dist/emit/compliance/rule-engine.js +101 -0
- package/dist/emit/compliance/rules.d.ts +16 -0
- package/dist/emit/compliance/rules.js +191 -0
- package/dist/emit/compliance/types.d.ts +59 -0
- package/dist/emit/compliance/types.js +8 -0
- package/dist/emit/cpp-emitter.js +4 -3
- package/dist/emit/emitters/class-emitter.js +6 -1
- package/dist/emit/emitters/emitter-context.d.ts +28 -1
- package/dist/emit/emitters/function-emitter-impl.js +153 -53
- package/dist/emit/emitters/line-appender.js +19 -0
- package/dist/emit/emitters/line-marker.d.ts +38 -0
- package/dist/emit/emitters/line-marker.js +39 -0
- package/dist/emit/emitters/output-finalizer.d.ts +6 -0
- package/dist/emit/emitters/output-finalizer.js +101 -12
- package/dist/emit/emitters/setup.d.ts +17 -0
- package/dist/emit/emitters/setup.js +257 -36
- package/dist/emit/emitters/top-level-prep.js +8 -0
- package/dist/emit/emitters/type-decl-emitter.js +28 -3
- package/dist/emit/emitters/ui-emitter.js +33 -9
- package/dist/emit/expression-renderer.d.ts +1 -1
- package/dist/emit/expression-renderer.js +91 -6
- package/dist/emit/route-hal-op.js +18 -5
- package/dist/emit/snprintf-helpers.js +15 -4
- package/dist/emit/statement-renderer.d.ts +10 -0
- package/dist/emit/statement-renderer.js +34 -5
- package/dist/emit/utils/async-state-machine.js +221 -125
- package/dist/emit/utils/hal-op-cpp-type.d.ts +6 -0
- package/dist/emit/utils/hal-op-cpp-type.js +40 -0
- package/dist/framework-package.js +2 -0
- package/dist/framework-registry.d.ts +17 -0
- package/dist/ir/adc-range-validation.js +14 -1
- package/dist/ir/build-ir-state.d.ts +1 -0
- package/dist/ir/build-ir-state.js +14 -0
- package/dist/ir/build-ir.js +9 -5
- package/dist/ir/call-graph.js +16 -0
- package/dist/ir/expression-to-ir.js +164 -0
- package/dist/ir/feature-registry.js +7 -25
- package/dist/ir/function-builder.js +22 -0
- package/dist/ir/hal/hal-emitter.d.ts +5 -2
- package/dist/ir/hal/hal-emitter.js +50 -18
- package/dist/ir/hal/hal-parser.d.ts +6 -0
- package/dist/ir/hal/hal-parser.js +81 -0
- package/dist/ir/hal/hal-plugins.js +723 -1
- package/dist/ir/identifier-collector.js +35 -0
- package/dist/ir/interrupt-analysis.d.ts +5 -1
- package/dist/ir/interrupt-analysis.js +8 -16
- package/dist/ir/network-validation.d.ts +4 -0
- package/dist/ir/network-validation.js +184 -0
- package/dist/ir/ownership-analysis.js +20 -1
- package/dist/ir/pin-capability-validation.js +37 -0
- package/dist/ir/pin-mode-validation.d.ts +2 -2
- package/dist/ir/pin-mode-validation.js +49 -18
- package/dist/ir/program-analysis.d.ts +65 -0
- package/dist/ir/program-analysis.js +318 -2
- package/dist/ir/render-expr.js +11 -0
- package/dist/ir/timing-validation.d.ts +6 -1
- package/dist/ir/timing-validation.js +60 -13
- package/dist/ir/transformers/call-statement.js +105 -0
- package/dist/ir/transformers/expressions.js +62 -0
- package/dist/ir/transformers/hal-call-resolver.js +21 -0
- package/dist/ir/transformers/hal-emit-helpers.js +1 -1
- package/dist/ir/transformers/namespace-methods.js +17 -12
- package/dist/ir/transformers/ui-reactive.js +2 -2
- package/dist/ir/transformers/variables.js +92 -3
- package/dist/ir/type-resolution.js +18 -0
- package/dist/ir/ui-element-auto-wire.js +7 -5
- package/dist/ir/utils/map-statements.d.ts +4 -0
- package/dist/ir/utils/map-statements.js +79 -0
- package/dist/ir/validation-orchestrator.js +9 -2
- package/dist/ir/worker-analysis.d.ts +10 -0
- package/dist/ir/worker-analysis.js +261 -0
- package/dist/libdef/c-to-decl.d.ts +27 -0
- package/dist/libdef/c-to-decl.js +397 -0
- package/dist/libdef/component-decls.d.ts +2 -0
- package/dist/libdef/component-decls.js +6 -0
- package/dist/libdef/component-discovery.d.ts +43 -0
- package/dist/libdef/component-discovery.js +83 -0
- package/dist/libdef/cpp-to-decl.d.ts +9 -0
- package/dist/libdef/cpp-to-decl.js +72 -0
- package/dist/libdef/registry.js +5 -2
- package/dist/lint-cache.d.ts +59 -0
- package/dist/lint-cache.js +257 -0
- package/dist/orchestrator/graph-builder.js +14 -9
- package/dist/platform/coop-scheduler-runtime.d.ts +19 -0
- package/dist/platform/coop-scheduler-runtime.js +41 -0
- package/dist/platform/generic-strategy.d.ts +15 -3
- package/dist/platform/generic-strategy.js +49 -4
- package/dist/safety/safety-bridge.d.ts +11 -0
- package/dist/safety/safety-bridge.js +48 -0
- package/dist/safety/sidecar-bridge.d.ts +5 -0
- package/dist/safety/sidecar-bridge.js +22 -0
- package/dist/safety-hook.d.ts +79 -0
- package/dist/safety-hook.js +35 -0
- package/dist/testing.d.ts +7 -1
- package/dist/testing.js +8 -1
- package/dist/transpile.d.ts +3 -0
- package/dist/transpile.js +175 -41
- package/dist/types.d.ts +13 -2
- package/dist/ui-hook.d.ts +17 -3
- package/dist/utils/cli.js +78 -4
- package/dist/utils/fs.d.ts +13 -0
- package/dist/utils/fs.js +50 -0
- package/package.json +24 -5
- package/dist/api/shared/display-adapters/eink-mono.d.ts +0 -2
- package/dist/api/shared/display-adapters/eink-mono.js +0 -53
- package/dist/api/shared/display-adapters/ssd1309.d.ts +0 -2
- package/dist/api/shared/display-adapters/ssd1309.js +0 -136
- package/dist/api/shared/display-adapters/st7796.d.ts +0 -2
- package/dist/api/shared/display-adapters/st7796.js +0 -132
- package/dist/licenses.d.ts +0 -185
- package/dist/licenses.js +0 -963
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Extracted from cpp-emitter.ts
|
|
5
5
|
*/
|
|
6
6
|
import { escapeCppStringLiteral } from "../../utils/strings.js";
|
|
7
|
+
import { routeHALOp } from "../route-hal-op.js";
|
|
7
8
|
/**
|
|
8
9
|
* Converts a string to PascalCase.
|
|
9
10
|
* Used for generating class names from function names.
|
|
@@ -53,6 +54,20 @@ export function generateAsyncTaskClass(fnName, fnStatements, strategy, knownFunc
|
|
|
53
54
|
segments.push({ preStatements: currentPre, awaitedCallee: stmt.callee, awaitedArgs: stmt.args });
|
|
54
55
|
currentPre = [];
|
|
55
56
|
}
|
|
57
|
+
else if (
|
|
58
|
+
// Chained HAL calls (e.g. `await Http.get(url).send()`) resolve to a
|
|
59
|
+
// block of hal-ops whose LAST statement is the awaited wait marker.
|
|
60
|
+
// Flatten it so the split sees the marker; the leading ops (begin,
|
|
61
|
+
// setters) run as pre-statements of the same segment.
|
|
62
|
+
stmt.kind === "block" &&
|
|
63
|
+
stmt.body.length > 0 &&
|
|
64
|
+
stmt.body[stmt.body.length - 1].kind === "call" &&
|
|
65
|
+
stmt.body[stmt.body.length - 1].isAwaited) {
|
|
66
|
+
const last = stmt.body[stmt.body.length - 1];
|
|
67
|
+
currentPre.push(...stmt.body.slice(0, -1));
|
|
68
|
+
segments.push({ preStatements: currentPre, awaitedCallee: last.callee, awaitedArgs: last.args });
|
|
69
|
+
currentPre = [];
|
|
70
|
+
}
|
|
56
71
|
else {
|
|
57
72
|
currentPre.push(stmt);
|
|
58
73
|
}
|
|
@@ -61,11 +76,23 @@ export function generateAsyncTaskClass(fnName, fnStatements, strategy, knownFunc
|
|
|
61
76
|
segments.push({ preStatements: currentPre, awaitedArgs: [] });
|
|
62
77
|
const awaitCount = segments.filter((s) => s.awaitedCallee !== undefined).length;
|
|
63
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.
|
|
64
83
|
const stateNames = [];
|
|
65
|
-
|
|
84
|
+
const qualifiedStateNames = [];
|
|
85
|
+
for (let i = 0; i < stateCount; i++) {
|
|
66
86
|
stateNames.push(`STATE_${i}`);
|
|
67
|
-
|
|
87
|
+
qualifiedStateNames.push(`State::STATE_${i}`);
|
|
88
|
+
}
|
|
89
|
+
if (!isCyclic) {
|
|
68
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";
|
|
69
96
|
// Helper: render a non-await statement as a single C++ line
|
|
70
97
|
const renderStmt = (stmt) => renderStatement(stmt, false, strategy, undefined, undefined, knownFunctionReturnTypes);
|
|
71
98
|
// Collect edge-detection markers from segments
|
|
@@ -101,16 +128,74 @@ export function generateAsyncTaskClass(fnName, fnStatements, strategy, knownFunc
|
|
|
101
128
|
tapMembers.set(member, i);
|
|
102
129
|
}
|
|
103
130
|
}
|
|
131
|
+
// Collect net-wait (WiFi/HTTP/HAL) markers: awaited hal-ops rewritten to
|
|
132
|
+
// __WIFI_WAIT__/__HTTP_WAIT__/__HAL_WAIT__ calls carrying the op as a
|
|
133
|
+
// hal-expr arg. The strategy lowers the op to start code + poll condition.
|
|
134
|
+
const netInfoMap = new Map();
|
|
135
|
+
for (let i = 0; i < segments.length; i++) {
|
|
136
|
+
const seg = segments[i];
|
|
137
|
+
if ((seg.awaitedCallee === "__WIFI_WAIT__" || seg.awaitedCallee === "__HTTP_WAIT__" || seg.awaitedCallee === "__BLE_WAIT__" || seg.awaitedCallee === "__HAL_WAIT__") &&
|
|
138
|
+
seg.awaitedArgs[0]?.kind === "hal-expr") {
|
|
139
|
+
const op = seg.awaitedArgs[0].operation;
|
|
140
|
+
netInfoMap.set(i, netWaitInfo(op, strategy));
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
// ── Render each state ──────────────────────────────────────────────────
|
|
144
|
+
// Decomposition: a state's ENTRY condition comes from the await that ended
|
|
145
|
+
// the PREVIOUS segment (edge / tap / net poll / timed deadline); once the
|
|
146
|
+
// condition fires, the segment body runs and the await ending THIS segment
|
|
147
|
+
// is armed (pin snapshot / tap snapshot / net start op / deadline).
|
|
148
|
+
// Lines that arm the await ending segment i and advance the state.
|
|
149
|
+
const armNextLines = (i, pad) => {
|
|
150
|
+
const seg = segments[i];
|
|
151
|
+
const lines = [];
|
|
152
|
+
if (seg.awaitedCallee === undefined) {
|
|
153
|
+
lines.push(`${pad}_state = ${isCyclic ? Q_0 : Q_DONE};`);
|
|
154
|
+
return lines;
|
|
155
|
+
}
|
|
156
|
+
const edge = edgeInfoMap.get(i);
|
|
157
|
+
const tap = tapInfoMap.get(i);
|
|
158
|
+
const net = netInfoMap.get(i);
|
|
159
|
+
if (edge) {
|
|
160
|
+
lines.push(`${pad}_edgePrev_p${edge.pin} = ${strategy.readDigitalPin?.(String(edge.pin)) ?? `digitalRead(${edge.pin})`};`);
|
|
161
|
+
if (edge.timeout !== null) {
|
|
162
|
+
lines.push(`${pad}_waitUntil = ${strategy.currentTimeMillis()} + ${edge.timeout};`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
else if (tap) {
|
|
166
|
+
lines.push(`${pad}_tapPrev_${i} = __ui_tap_seq;`);
|
|
167
|
+
}
|
|
168
|
+
else if (net) {
|
|
169
|
+
for (const startLine of net.startLines)
|
|
170
|
+
lines.push(`${pad}${startLine}`);
|
|
171
|
+
if (net.timeoutExpr !== null) {
|
|
172
|
+
lines.push(`${pad}_waitUntil = ${strategy.currentTimeMillis()} + ${net.timeoutExpr};`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
// Plain timed wait (await delay(ms)) — arm its deadline.
|
|
177
|
+
const ms = seg.awaitedArgs[0] ? renderExpression(seg.awaitedArgs[0], strategy) : "0";
|
|
178
|
+
lines.push(`${pad}_waitUntil = ${strategy.currentTimeMillis()} + ${ms};`);
|
|
179
|
+
}
|
|
180
|
+
lines.push(`${pad}_state = ${Q(i + 1)};`);
|
|
181
|
+
return lines;
|
|
182
|
+
};
|
|
104
183
|
const caseLines = [];
|
|
105
184
|
for (let i = 0; i < segments.length; i++) {
|
|
106
185
|
const seg = segments[i];
|
|
107
|
-
const stateName =
|
|
108
|
-
const isTerminal = seg.awaitedCallee === undefined;
|
|
186
|
+
const stateName = Q(i);
|
|
109
187
|
const body = [];
|
|
110
|
-
const edgeSetup = edgeInfoMap.get(i);
|
|
111
188
|
const edgePoll = i > 0 ? edgeInfoMap.get(i - 1) : undefined;
|
|
112
|
-
const tapSetup = tapInfoMap.get(i);
|
|
113
189
|
const tapPoll = i > 0 ? tapInfoMap.get(i - 1) : undefined;
|
|
190
|
+
const netPoll = i > 0 ? netInfoMap.get(i - 1) : undefined;
|
|
191
|
+
// Segment body + arming of the next await, at the given indent.
|
|
192
|
+
const runSegment = (pad) => {
|
|
193
|
+
const lines = [];
|
|
194
|
+
for (const stmt of seg.preStatements)
|
|
195
|
+
lines.push(`${pad}${renderStmt(stmt)}`);
|
|
196
|
+
lines.push(...armNextLines(i, pad));
|
|
197
|
+
return lines;
|
|
198
|
+
};
|
|
114
199
|
if (edgePoll) {
|
|
115
200
|
// Poll state: check pin transition via digitalRead
|
|
116
201
|
const prevVar = `_edgePrev_p${edgePoll.pin}`;
|
|
@@ -121,56 +206,15 @@ export function generateAsyncTaskClass(fnName, fnStatements, strategy, knownFunc
|
|
|
121
206
|
? `(${cond}) || ${strategy.currentTimeMillis()} >= _waitUntil`
|
|
122
207
|
: cond;
|
|
123
208
|
body.push(` {`);
|
|
124
|
-
body.push(` int _cur = digitalRead(${edgePoll.pin});`);
|
|
209
|
+
body.push(` int _cur = ${strategy.readDigitalPin?.(String(edgePoll.pin)) ?? `digitalRead(${edgePoll.pin})`};`);
|
|
125
210
|
body.push(` if (${fullCond}) {`);
|
|
126
211
|
body.push(` ${prevVar} = _cur;`);
|
|
127
|
-
|
|
128
|
-
body.push(` ${renderStmt(stmt)}`);
|
|
129
|
-
if (edgeSetup) {
|
|
130
|
-
// Next await is also edge detection — set up its pin tracking
|
|
131
|
-
body.push(` _edgePrev_p${edgeSetup.pin} = digitalRead(${edgeSetup.pin});`);
|
|
132
|
-
if (edgeSetup.timeout !== null) {
|
|
133
|
-
body.push(` _waitUntil = ${strategy.currentTimeMillis()} + ${edgeSetup.timeout};`);
|
|
134
|
-
}
|
|
135
|
-
body.push(` _state = STATE_${i + 1};`);
|
|
136
|
-
}
|
|
137
|
-
else if (!isTerminal) {
|
|
138
|
-
const ms = seg.awaitedArgs[0] ? renderExpression(seg.awaitedArgs[0], strategy) : "0";
|
|
139
|
-
body.push(` _waitUntil = ${strategy.currentTimeMillis()} + ${ms};`);
|
|
140
|
-
body.push(` _state = STATE_${i + 1};`);
|
|
141
|
-
}
|
|
142
|
-
else {
|
|
143
|
-
body.push(` _state = ${isCyclic ? "STATE_0" : "STATE_DONE"};`);
|
|
144
|
-
}
|
|
212
|
+
body.push(...runSegment(` `));
|
|
145
213
|
body.push(` } else {`);
|
|
146
214
|
body.push(` ${prevVar} = _cur;`);
|
|
147
215
|
body.push(` }`);
|
|
148
216
|
body.push(` }`);
|
|
149
217
|
}
|
|
150
|
-
else if (edgeSetup) {
|
|
151
|
-
// Setup state: capture initial pin state before polling
|
|
152
|
-
const prevVar = `_edgePrev_p${edgeSetup.pin}`;
|
|
153
|
-
if (i === 0) {
|
|
154
|
-
for (const stmt of seg.preStatements)
|
|
155
|
-
body.push(` ${renderStmt(stmt)}`);
|
|
156
|
-
body.push(` ${prevVar} = digitalRead(${edgeSetup.pin});`);
|
|
157
|
-
if (edgeSetup.timeout !== null) {
|
|
158
|
-
body.push(` _waitUntil = ${strategy.currentTimeMillis()} + ${edgeSetup.timeout};`);
|
|
159
|
-
}
|
|
160
|
-
body.push(` _state = STATE_${i + 1};`);
|
|
161
|
-
}
|
|
162
|
-
else {
|
|
163
|
-
body.push(` if (${strategy.currentTimeMillis()} >= _waitUntil) {`);
|
|
164
|
-
for (const stmt of seg.preStatements)
|
|
165
|
-
body.push(` ${renderStmt(stmt)}`);
|
|
166
|
-
body.push(` ${prevVar} = digitalRead(${edgeSetup.pin});`);
|
|
167
|
-
if (edgeSetup.timeout !== null) {
|
|
168
|
-
body.push(` _waitUntil = ${strategy.currentTimeMillis()} + ${edgeSetup.timeout};`);
|
|
169
|
-
}
|
|
170
|
-
body.push(` _state = STATE_${i + 1};`);
|
|
171
|
-
body.push(` }`);
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
218
|
else if (tapPoll) {
|
|
175
219
|
// Tap-poll state: a previous segment ended with `await ui.onTap()`.
|
|
176
220
|
// Wait until __ui_tap_seq bumps from the captured snapshot. A per-node
|
|
@@ -181,92 +225,42 @@ export function generateAsyncTaskClass(fnName, fnStatements, strategy, knownFunc
|
|
|
181
225
|
? `__ui_tap_seq != ${prevVar}`
|
|
182
226
|
: `(__ui_tap_seq != ${prevVar}) && (__ui_tap_node == ${nodeFilter})`;
|
|
183
227
|
body.push(` if (${cond}) {`);
|
|
184
|
-
|
|
185
|
-
body.push(` ${renderStmt(stmt)}`);
|
|
186
|
-
// What does the NEXT await (ending this segment) look like? Re-derive
|
|
187
|
-
// from the maps via a fresh lookup so TS doesn't narrow edgeSetup away.
|
|
188
|
-
const nextEdge = edgeInfoMap.get(i) ?? null;
|
|
189
|
-
if (tapSetup) {
|
|
190
|
-
// Next await is also a tap — capture its snapshot now.
|
|
191
|
-
body.push(` _tapPrev_${i} = __ui_tap_seq;`);
|
|
192
|
-
body.push(` _state = STATE_${i + 1};`);
|
|
193
|
-
}
|
|
194
|
-
else if (nextEdge) {
|
|
195
|
-
body.push(` _edgePrev_p${nextEdge.pin} = digitalRead(${nextEdge.pin});`);
|
|
196
|
-
if (nextEdge.timeout !== null) {
|
|
197
|
-
body.push(` _waitUntil = ${strategy.currentTimeMillis()} + ${nextEdge.timeout};`);
|
|
198
|
-
}
|
|
199
|
-
body.push(` _state = STATE_${i + 1};`);
|
|
200
|
-
}
|
|
201
|
-
else if (!isTerminal) {
|
|
202
|
-
// Next await is a normal timed wait (delay) — arm its deadline.
|
|
203
|
-
const ms = seg.awaitedArgs[0] ? renderExpression(seg.awaitedArgs[0], strategy) : "0";
|
|
204
|
-
body.push(` _waitUntil = ${strategy.currentTimeMillis()} + ${ms};`);
|
|
205
|
-
body.push(` _state = STATE_${i + 1};`);
|
|
206
|
-
}
|
|
207
|
-
else {
|
|
208
|
-
body.push(` _state = ${isCyclic ? "STATE_0" : "STATE_DONE"};`);
|
|
209
|
-
}
|
|
228
|
+
body.push(...runSegment(` `));
|
|
210
229
|
body.push(` }`);
|
|
211
230
|
}
|
|
212
|
-
else if (
|
|
213
|
-
//
|
|
214
|
-
//
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
body.push(` }`);
|
|
229
|
-
}
|
|
231
|
+
else if (netPoll) {
|
|
232
|
+
// Net-poll state: a previous segment ended with an awaited WiFi/HTTP
|
|
233
|
+
// op. Wait until its poll condition fires (or its deadline expires).
|
|
234
|
+
const deadline = `${strategy.currentTimeMillis()} >= _waitUntil`;
|
|
235
|
+
const cond = netPoll.pollCond === null
|
|
236
|
+
? deadline
|
|
237
|
+
: netPoll.timeoutExpr !== null
|
|
238
|
+
? `(${netPoll.pollCond}) || ${deadline}`
|
|
239
|
+
: netPoll.pollCond;
|
|
240
|
+
body.push(` if (${cond}) {`);
|
|
241
|
+
body.push(...runSegment(` `));
|
|
242
|
+
body.push(` }`);
|
|
243
|
+
}
|
|
244
|
+
else if (i === 0) {
|
|
245
|
+
// First state runs immediately.
|
|
246
|
+
body.push(...runSegment(` `));
|
|
230
247
|
}
|
|
231
248
|
else {
|
|
232
|
-
//
|
|
233
|
-
if (
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
if (!isTerminal) {
|
|
237
|
-
const ms = seg.awaitedArgs[0] ? renderExpression(seg.awaitedArgs[0], strategy) : "0";
|
|
238
|
-
body.push(` _waitUntil = ${strategy.currentTimeMillis()} + ${ms};`);
|
|
239
|
-
body.push(` _state = STATE_${i + 1};`);
|
|
240
|
-
}
|
|
241
|
-
else {
|
|
242
|
-
body.push(` _state = ${isCyclic ? "STATE_0" : "STATE_DONE"};`);
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
else {
|
|
246
|
-
body.push(` if (${strategy.currentTimeMillis()} >= _waitUntil) {`);
|
|
247
|
-
for (const stmt of seg.preStatements)
|
|
248
|
-
body.push(` ${renderStmt(stmt)}`);
|
|
249
|
-
if (!isTerminal) {
|
|
250
|
-
const ms = seg.awaitedArgs[0] ? renderExpression(seg.awaitedArgs[0], strategy) : "0";
|
|
251
|
-
body.push(` _waitUntil = ${strategy.currentTimeMillis()} + ${ms};`);
|
|
252
|
-
body.push(` _state = STATE_${i + 1};`);
|
|
253
|
-
}
|
|
254
|
-
else {
|
|
255
|
-
body.push(` _state = ${isCyclic ? "STATE_0" : "STATE_DONE"};`);
|
|
256
|
-
}
|
|
257
|
-
body.push(` }`);
|
|
258
|
-
}
|
|
249
|
+
// Previous await was a plain timed wait — poll its deadline.
|
|
250
|
+
body.push(` if (${strategy.currentTimeMillis()} >= _waitUntil) {`);
|
|
251
|
+
body.push(...runSegment(` `));
|
|
252
|
+
body.push(` }`);
|
|
259
253
|
}
|
|
260
254
|
caseLines.push(` case ${stateName}:`, ` {`, ...body, ` }`, ` break;`);
|
|
261
255
|
}
|
|
262
256
|
if (!isCyclic)
|
|
263
|
-
caseLines.push(` case
|
|
257
|
+
caseLines.push(` case ${Q_DONE}:`, ` break;`);
|
|
264
258
|
const stateEnumList = stateNames.join(", ");
|
|
265
|
-
const isCompleteExpr = isCyclic ? "false" :
|
|
259
|
+
const isCompleteExpr = isCyclic ? "false" : `_state == ${Q_DONE}`;
|
|
266
260
|
// Build constructor initializer list and edge/tap member declarations
|
|
267
261
|
const edgeMemberArr = Array.from(edgeMembers);
|
|
268
262
|
const tapMemberArr = Array.from(tapMembers.keys());
|
|
269
|
-
const inits = [`_state(
|
|
263
|
+
const inits = [`_state(${Q_0})`, `_waitUntil(0)`];
|
|
270
264
|
for (const m of edgeMemberArr)
|
|
271
265
|
inits.push(`${m}(LOW)`);
|
|
272
266
|
for (const m of tapMemberArr)
|
|
@@ -281,7 +275,7 @@ export function generateAsyncTaskClass(fnName, fnStatements, strategy, knownFunc
|
|
|
281
275
|
`// Async state machine for ${fnName}`,
|
|
282
276
|
`class ${className} {`,
|
|
283
277
|
`public:`,
|
|
284
|
-
` enum State { ${stateEnumList} };`,
|
|
278
|
+
` enum class State { ${stateEnumList} };`,
|
|
285
279
|
` ${className}() : ${ctorInitList} {}`,
|
|
286
280
|
` void run() {`,
|
|
287
281
|
` switch (_state) {`,
|
|
@@ -289,7 +283,7 @@ export function generateAsyncTaskClass(fnName, fnStatements, strategy, knownFunc
|
|
|
289
283
|
` }`,
|
|
290
284
|
` }`,
|
|
291
285
|
` bool isComplete() const { return ${isCompleteExpr}; }`,
|
|
292
|
-
` void reset() { _state =
|
|
286
|
+
` void reset() { _state = ${Q_0}; _waitUntil = 0;${edgeResetList}${tapResetList} }`,
|
|
293
287
|
`private:`,
|
|
294
288
|
` State _state;`,
|
|
295
289
|
` unsigned long _waitUntil;`,
|
|
@@ -331,6 +325,108 @@ function renderExpression(expr, strategy) {
|
|
|
331
325
|
return "/* complex expr */";
|
|
332
326
|
}
|
|
333
327
|
}
|
|
328
|
+
/** Numeric-or-rendered HAL field → C++ text. */
|
|
329
|
+
function ms(v) {
|
|
330
|
+
return String(v);
|
|
331
|
+
}
|
|
332
|
+
/** True when a timeout field is the literal 0 (wait forever, no deadline). */
|
|
333
|
+
function isZeroTimeout(v) {
|
|
334
|
+
return v === undefined || v === null || String(v).trim() === "0";
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Map an awaitable HAL op to its start statements + poll condition, lowering
|
|
338
|
+
* the start op and poll predicate through the platform strategy. Keep the set
|
|
339
|
+
* of handled ops in sync with AWAITABLE_HAL_OPS in ir/transformers/expressions.ts.
|
|
340
|
+
*
|
|
341
|
+
* Falls back to running the blocking form as the "start" with an immediate
|
|
342
|
+
* completion when the strategy can't lower the split ops.
|
|
343
|
+
*/
|
|
344
|
+
function netWaitInfo(op, strategy) {
|
|
345
|
+
const o = op;
|
|
346
|
+
const route = (routedOp) => routeHALOp(routedOp, strategy);
|
|
347
|
+
const expr = (operation) => route({ operation })?.expression ?? null;
|
|
348
|
+
switch (op.operation) {
|
|
349
|
+
case "timing.delay":
|
|
350
|
+
return { startLines: [], pollCond: null, timeoutExpr: ms(o.ms) };
|
|
351
|
+
case "wifi.connect": {
|
|
352
|
+
const start = route({ operation: "wifi.connect_start", ssid: o.ssid, password: o.password });
|
|
353
|
+
const poll = expr("wifi.is_connected");
|
|
354
|
+
if (start?.code && poll) {
|
|
355
|
+
return {
|
|
356
|
+
startLines: [start.code],
|
|
357
|
+
pollCond: poll,
|
|
358
|
+
timeoutExpr: isZeroTimeout(o.timeoutMs) ? null : ms(o.timeoutMs),
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
break;
|
|
362
|
+
}
|
|
363
|
+
case "wifi.wait_connected": {
|
|
364
|
+
const poll = expr("wifi.is_connected");
|
|
365
|
+
if (poll) {
|
|
366
|
+
return {
|
|
367
|
+
startLines: [],
|
|
368
|
+
pollCond: poll,
|
|
369
|
+
timeoutExpr: isZeroTimeout(o.timeoutMs) ? null : ms(o.timeoutMs),
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
break;
|
|
373
|
+
}
|
|
374
|
+
case "wifi.wait_disconnected": {
|
|
375
|
+
const poll = expr("wifi.is_connected");
|
|
376
|
+
if (poll) {
|
|
377
|
+
return { startLines: [], pollCond: `!${poll}`, timeoutExpr: null };
|
|
378
|
+
}
|
|
379
|
+
break;
|
|
380
|
+
}
|
|
381
|
+
case "wifi.scan": {
|
|
382
|
+
const start = route({ operation: "wifi.scan_start" });
|
|
383
|
+
const poll = expr("wifi.scan_done");
|
|
384
|
+
if (start?.code && poll) {
|
|
385
|
+
return { startLines: [start.code], pollCond: poll, timeoutExpr: null };
|
|
386
|
+
}
|
|
387
|
+
break;
|
|
388
|
+
}
|
|
389
|
+
case "http.send": {
|
|
390
|
+
const start = route({ operation: "http.send_start" });
|
|
391
|
+
const poll = expr("http.done");
|
|
392
|
+
if (start?.code && poll) {
|
|
393
|
+
return { startLines: [start.code], pollCond: poll, timeoutExpr: null };
|
|
394
|
+
}
|
|
395
|
+
break;
|
|
396
|
+
}
|
|
397
|
+
case "ble.until_connected": {
|
|
398
|
+
const start = route({ operation: "ble.until_connected_start" });
|
|
399
|
+
const poll = expr("ble.is_connected");
|
|
400
|
+
if (start?.code && poll) {
|
|
401
|
+
return {
|
|
402
|
+
startLines: [start.code],
|
|
403
|
+
pollCond: poll,
|
|
404
|
+
timeoutExpr: isZeroTimeout(o.timeoutMs) ? null : ms(o.timeoutMs),
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
break;
|
|
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
|
+
}
|
|
424
|
+
}
|
|
425
|
+
// Fallback: run the blocking form immediately and complete on the next tick.
|
|
426
|
+
const blocking = routeHALOp(op, strategy);
|
|
427
|
+
const line = blocking?.code ?? (blocking?.expression ? `${blocking.expression};` : `/* unhandled awaited hal-op: ${op.operation} */`);
|
|
428
|
+
return { startLines: [line], pollCond: null, timeoutExpr: "0" };
|
|
429
|
+
}
|
|
334
430
|
function parseEdgeMarker(argText) {
|
|
335
431
|
const match = argText.match(/^__EDGE_(RISING|FALLING)__([a-zA-Z0-9_]+)__T(.*)$/);
|
|
336
432
|
if (!match)
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Best-effort C++ return type for a HAL expression op.
|
|
3
|
+
* Used by snprintf format inference and expression type inference so
|
|
4
|
+
* string-returning ops (e.g. wifi.scan_ssid → const char*) use %s, not %d.
|
|
5
|
+
*/
|
|
6
|
+
export declare function cppTypeForHalOp(operation: string): string | undefined;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Best-effort C++ return type for a HAL expression op.
|
|
3
|
+
* Used by snprintf format inference and expression type inference so
|
|
4
|
+
* string-returning ops (e.g. wifi.scan_ssid → const char*) use %s, not %d.
|
|
5
|
+
*/
|
|
6
|
+
export function cppTypeForHalOp(operation) {
|
|
7
|
+
switch (operation) {
|
|
8
|
+
case "wifi.local_ip":
|
|
9
|
+
case "wifi.mac":
|
|
10
|
+
case "wifi.ap_ip":
|
|
11
|
+
case "wifi.scan_ssid":
|
|
12
|
+
case "http.body":
|
|
13
|
+
case "http.response_header":
|
|
14
|
+
return "const char*";
|
|
15
|
+
case "wifi.is_connected":
|
|
16
|
+
case "wifi.connect":
|
|
17
|
+
case "wifi.wait_connected":
|
|
18
|
+
case "wifi.scan_done":
|
|
19
|
+
case "wifi.ap_start":
|
|
20
|
+
case "wifi.connect_saved":
|
|
21
|
+
case "http.ok":
|
|
22
|
+
case "http.done":
|
|
23
|
+
return "bool";
|
|
24
|
+
case "wifi.status":
|
|
25
|
+
case "wifi.rssi":
|
|
26
|
+
case "wifi.scan":
|
|
27
|
+
case "wifi.scan_count":
|
|
28
|
+
case "wifi.scan_rssi":
|
|
29
|
+
case "wifi.scan_encryption":
|
|
30
|
+
case "wifi.scan_channel":
|
|
31
|
+
case "wifi.ap_client_count":
|
|
32
|
+
case "http.status":
|
|
33
|
+
case "http.content_length":
|
|
34
|
+
return "int";
|
|
35
|
+
case "http.send":
|
|
36
|
+
return "bool";
|
|
37
|
+
default:
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -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
|
@@ -144,7 +144,11 @@ export function buildProgramIR(fileName, sourceText, boardPackage, prebuiltClass
|
|
|
144
144
|
const parentStrategy = getContext().activeStrategy;
|
|
145
145
|
return contextStorage.run(new CompilationContext(), () => {
|
|
146
146
|
getContext().activeStrategy = parentStrategy;
|
|
147
|
-
|
|
147
|
+
// Ensure HAL modules are loaded. transpile.ts warms the HAL registry once
|
|
148
|
+
// per transpile run (before the per-file IR build), so this is normally a
|
|
149
|
+
// cheap no-op. The non-forced call is a safety net for direct/test callers
|
|
150
|
+
// of buildProgramIR that didn't warm the registry first.
|
|
151
|
+
loadHALModules();
|
|
148
152
|
const normalizedSourceText = normalizeEntrypointSyntax(sourceText);
|
|
149
153
|
const source = parseSource(fileName, normalizedSourceText);
|
|
150
154
|
const diagnostics = [];
|
|
@@ -439,14 +443,14 @@ export function buildProgramIR(fileName, sourceText, boardPackage, prebuiltClass
|
|
|
439
443
|
}
|
|
440
444
|
}
|
|
441
445
|
}
|
|
442
|
-
// Track HAL instances imported from board packages and framework
|
|
443
|
-
// 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.
|
|
444
448
|
// The `@typecad/board` virtual import resolves to the board package, so it
|
|
445
449
|
// also registers pin aliases (LED, etc.). Case-insensitive on the scope.
|
|
446
450
|
const lowerSpecifier = moduleSpecifier.toLowerCase();
|
|
447
451
|
const isHALSource = lowerSpecifier.startsWith('@typecad/board-')
|
|
448
|
-
|| lowerSpecifier === '@typecad/
|
|
449
|
-
|| lowerSpecifier
|
|
452
|
+
|| lowerSpecifier === '@typecad/board'
|
|
453
|
+
|| /^@typecad\/framework-[a-z0-9-]+\/(arduino|hal|gpio)$/.test(lowerSpecifier);
|
|
450
454
|
// UI authoring namespace: `import { ui } from "@typecad/ui"`. The `ui`
|
|
451
455
|
// value is a compile-time construct (its calls are intercepted by
|
|
452
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",
|