@typecad/cuttlefish 1.0.0-alpha.3 → 1.0.0-alpha.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/dist/api/shared/display-adapter.d.ts +2 -1
- package/dist/api/shared/display-adapter.js +8 -1
- package/dist/api/shared/display-adapters/sdl.js +9 -2
- package/dist/api/shared/display-profile.d.ts +20 -3
- package/dist/api/shared/display-profile.js +21 -6
- package/dist/api/shared/framework-manifest-registry.d.ts +9 -0
- package/dist/api/shared/framework-manifest-registry.js +25 -0
- package/dist/api/shared/framework-manifest.d.ts +462 -0
- package/dist/api/shared/framework-manifest.js +149 -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 +28 -0
- package/dist/api/shared/hal-op-ir.d.ts +427 -1
- package/dist/api/shared/hal-op-ir.js +95 -1
- package/dist/api/shared/index.d.ts +11 -1
- package/dist/api/shared/index.js +14 -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 +9 -0
- package/dist/api/shared/promise-runtime.js +78 -0
- package/dist/api/shared/types.d.ts +8 -0
- package/dist/api/shared/validate-framework-manifest.d.ts +28 -0
- package/dist/api/shared/validate-framework-manifest.js +417 -0
- package/dist/cli-utils.d.ts +1 -0
- package/dist/cli-utils.js +3 -1
- package/dist/cli.js +175 -4
- package/dist/config-loader.js +20 -1
- package/dist/config-schema.d.ts +36 -36
- package/dist/create/board-spec.d.ts +4 -4
- package/dist/create/index.d.ts +1 -1
- package/dist/create/index.js +1 -1
- package/dist/create/init-scaffold.d.ts +3 -0
- package/dist/create/init-scaffold.js +74 -2
- package/dist/create/init-templates.d.ts +4 -0
- package/dist/create/init-templates.js +217 -17
- package/dist/create/init-wizard.js +20 -1
- package/dist/emit/cpp-emitter.js +4 -3
- package/dist/emit/emitters/emitter-context.d.ts +5 -0
- package/dist/emit/emitters/function-emitter-impl.js +69 -59
- package/dist/emit/emitters/output-finalizer.d.ts +6 -0
- package/dist/emit/emitters/output-finalizer.js +21 -11
- package/dist/emit/emitters/setup.js +155 -0
- package/dist/emit/emitters/top-level-prep.js +2 -0
- package/dist/emit/emitters/ui-emitter.js +23 -8
- package/dist/emit/expression-renderer.js +10 -1
- package/dist/emit/snprintf-helpers.js +8 -0
- package/dist/emit/statement-renderer.js +13 -0
- package/dist/emit/utils/async-state-machine.js +185 -116
- 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/ir/adc-range-validation.js +26 -25
- package/dist/ir/build-ir.js +5 -1
- package/dist/ir/expression-to-ir.js +57 -6
- package/dist/ir/feature-registry.js +7 -25
- package/dist/ir/hal/hal-emitter.d.ts +5 -2
- package/dist/ir/hal/hal-emitter.js +40 -12
- package/dist/ir/hal/hal-parser.d.ts +6 -0
- package/dist/ir/hal/hal-parser.js +74 -0
- package/dist/ir/hal/hal-plugins.js +571 -0
- package/dist/ir/identifier-collector.js +18 -0
- package/dist/ir/interrupt-analysis.js +8 -3
- package/dist/ir/memory-budget-validation.js +1 -0
- package/dist/ir/network-validation.d.ts +4 -0
- package/dist/ir/network-validation.js +184 -0
- package/dist/ir/ownership-analysis.js +33 -1
- package/dist/ir/peripheral-ownership.js +5 -0
- package/dist/ir/peripheral-validation.d.ts +1 -1
- package/dist/ir/peripheral-validation.js +6 -3
- package/dist/ir/pin-alias-conflict.d.ts +1 -1
- package/dist/ir/pin-alias-conflict.js +2 -1
- package/dist/ir/pin-capability-validation.js +34 -32
- package/dist/ir/pin-mode-validation.js +5 -0
- package/dist/ir/pin-safety.d.ts +1 -1
- package/dist/ir/pin-safety.js +2 -1
- package/dist/ir/program-analysis.d.ts +39 -0
- package/dist/ir/program-analysis.js +192 -0
- package/dist/ir/pulldown-validation.d.ts +1 -1
- package/dist/ir/pulldown-validation.js +2 -1
- package/dist/ir/pwm-timer-sharing.d.ts +1 -1
- package/dist/ir/pwm-timer-sharing.js +2 -1
- package/dist/ir/resource-analysis.js +2 -0
- package/dist/ir/timer0-pwm-timing-conflict.d.ts +1 -1
- package/dist/ir/timer0-pwm-timing-conflict.js +2 -1
- package/dist/ir/timing-validation.d.ts +6 -1
- package/dist/ir/timing-validation.js +51 -12
- package/dist/ir/transformers/expressions.js +58 -0
- package/dist/ir/transformers/hal-emit-helpers.js +1 -1
- package/dist/ir/transformers/variables.js +86 -19
- package/dist/ir/try-catch-validation.js +2 -0
- package/dist/ir/type-resolution.js +2 -2
- package/dist/ir/unit-suspicion-validation.js +9 -7
- package/dist/ir/validation-orchestrator.js +9 -7
- 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/idf-discovery.d.ts +7 -0
- package/dist/libdef/idf-discovery.js +59 -0
- package/dist/libdef/registry.js +5 -2
- package/dist/licenses.d.ts +185 -0
- package/dist/licenses.js +963 -0
- package/dist/lint-cache.d.ts +59 -0
- package/dist/lint-cache.js +257 -0
- package/dist/orchestrator/graph-builder.js +6 -2
- package/dist/stores/display-profile-store.d.ts +1 -0
- package/dist/stores/display-profile-store.js +1 -0
- package/dist/testing.d.ts +4 -2
- package/dist/testing.js +4 -2
- package/dist/transpile.d.ts +3 -0
- package/dist/transpile.js +78 -32
- package/dist/types.d.ts +5 -1
- package/dist/ui-hook.d.ts +17 -1
- package/dist/utils/cli.js +71 -1
- package/dist/utils/fs.d.ts +13 -0
- package/dist/utils/fs.js +50 -0
- package/package.json +9 -4
- package/dist/ir/heap-array-validation.d.ts +0 -24
- package/dist/ir/heap-array-validation.js +0 -29
|
@@ -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
|
}
|
|
@@ -101,16 +116,74 @@ export function generateAsyncTaskClass(fnName, fnStatements, strategy, knownFunc
|
|
|
101
116
|
tapMembers.set(member, i);
|
|
102
117
|
}
|
|
103
118
|
}
|
|
119
|
+
// Collect net-wait (WiFi/HTTP/HAL) markers: awaited hal-ops rewritten to
|
|
120
|
+
// __WIFI_WAIT__/__HTTP_WAIT__/__HAL_WAIT__ calls carrying the op as a
|
|
121
|
+
// hal-expr arg. The strategy lowers the op to start code + poll condition.
|
|
122
|
+
const netInfoMap = new Map();
|
|
123
|
+
for (let i = 0; i < segments.length; i++) {
|
|
124
|
+
const seg = segments[i];
|
|
125
|
+
if ((seg.awaitedCallee === "__WIFI_WAIT__" || seg.awaitedCallee === "__HTTP_WAIT__" || seg.awaitedCallee === "__BLE_WAIT__" || seg.awaitedCallee === "__HAL_WAIT__") &&
|
|
126
|
+
seg.awaitedArgs[0]?.kind === "hal-expr") {
|
|
127
|
+
const op = seg.awaitedArgs[0].operation;
|
|
128
|
+
netInfoMap.set(i, netWaitInfo(op, strategy));
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
// ── Render each state ──────────────────────────────────────────────────
|
|
132
|
+
// Decomposition: a state's ENTRY condition comes from the await that ended
|
|
133
|
+
// the PREVIOUS segment (edge / tap / net poll / timed deadline); once the
|
|
134
|
+
// condition fires, the segment body runs and the await ending THIS segment
|
|
135
|
+
// is armed (pin snapshot / tap snapshot / net start op / deadline).
|
|
136
|
+
// Lines that arm the await ending segment i and advance the state.
|
|
137
|
+
const armNextLines = (i, pad) => {
|
|
138
|
+
const seg = segments[i];
|
|
139
|
+
const lines = [];
|
|
140
|
+
if (seg.awaitedCallee === undefined) {
|
|
141
|
+
lines.push(`${pad}_state = ${isCyclic ? "STATE_0" : "STATE_DONE"};`);
|
|
142
|
+
return lines;
|
|
143
|
+
}
|
|
144
|
+
const edge = edgeInfoMap.get(i);
|
|
145
|
+
const tap = tapInfoMap.get(i);
|
|
146
|
+
const net = netInfoMap.get(i);
|
|
147
|
+
if (edge) {
|
|
148
|
+
lines.push(`${pad}_edgePrev_p${edge.pin} = digitalRead(${edge.pin});`);
|
|
149
|
+
if (edge.timeout !== null) {
|
|
150
|
+
lines.push(`${pad}_waitUntil = ${strategy.currentTimeMillis()} + ${edge.timeout};`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
else if (tap) {
|
|
154
|
+
lines.push(`${pad}_tapPrev_${i} = __ui_tap_seq;`);
|
|
155
|
+
}
|
|
156
|
+
else if (net) {
|
|
157
|
+
for (const startLine of net.startLines)
|
|
158
|
+
lines.push(`${pad}${startLine}`);
|
|
159
|
+
if (net.timeoutExpr !== null) {
|
|
160
|
+
lines.push(`${pad}_waitUntil = ${strategy.currentTimeMillis()} + ${net.timeoutExpr};`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
// Plain timed wait (await delay(ms)) — arm its deadline.
|
|
165
|
+
const ms = seg.awaitedArgs[0] ? renderExpression(seg.awaitedArgs[0], strategy) : "0";
|
|
166
|
+
lines.push(`${pad}_waitUntil = ${strategy.currentTimeMillis()} + ${ms};`);
|
|
167
|
+
}
|
|
168
|
+
lines.push(`${pad}_state = STATE_${i + 1};`);
|
|
169
|
+
return lines;
|
|
170
|
+
};
|
|
104
171
|
const caseLines = [];
|
|
105
172
|
for (let i = 0; i < segments.length; i++) {
|
|
106
173
|
const seg = segments[i];
|
|
107
174
|
const stateName = `STATE_${i}`;
|
|
108
|
-
const isTerminal = seg.awaitedCallee === undefined;
|
|
109
175
|
const body = [];
|
|
110
|
-
const edgeSetup = edgeInfoMap.get(i);
|
|
111
176
|
const edgePoll = i > 0 ? edgeInfoMap.get(i - 1) : undefined;
|
|
112
|
-
const tapSetup = tapInfoMap.get(i);
|
|
113
177
|
const tapPoll = i > 0 ? tapInfoMap.get(i - 1) : undefined;
|
|
178
|
+
const netPoll = i > 0 ? netInfoMap.get(i - 1) : undefined;
|
|
179
|
+
// Segment body + arming of the next await, at the given indent.
|
|
180
|
+
const runSegment = (pad) => {
|
|
181
|
+
const lines = [];
|
|
182
|
+
for (const stmt of seg.preStatements)
|
|
183
|
+
lines.push(`${pad}${renderStmt(stmt)}`);
|
|
184
|
+
lines.push(...armNextLines(i, pad));
|
|
185
|
+
return lines;
|
|
186
|
+
};
|
|
114
187
|
if (edgePoll) {
|
|
115
188
|
// Poll state: check pin transition via digitalRead
|
|
116
189
|
const prevVar = `_edgePrev_p${edgePoll.pin}`;
|
|
@@ -124,53 +197,12 @@ export function generateAsyncTaskClass(fnName, fnStatements, strategy, knownFunc
|
|
|
124
197
|
body.push(` int _cur = digitalRead(${edgePoll.pin});`);
|
|
125
198
|
body.push(` if (${fullCond}) {`);
|
|
126
199
|
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
|
-
}
|
|
200
|
+
body.push(...runSegment(` `));
|
|
145
201
|
body.push(` } else {`);
|
|
146
202
|
body.push(` ${prevVar} = _cur;`);
|
|
147
203
|
body.push(` }`);
|
|
148
204
|
body.push(` }`);
|
|
149
205
|
}
|
|
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
206
|
else if (tapPoll) {
|
|
175
207
|
// Tap-poll state: a previous segment ended with `await ui.onTap()`.
|
|
176
208
|
// Wait until __ui_tap_seq bumps from the captured snapshot. A per-node
|
|
@@ -181,81 +213,31 @@ export function generateAsyncTaskClass(fnName, fnStatements, strategy, knownFunc
|
|
|
181
213
|
? `__ui_tap_seq != ${prevVar}`
|
|
182
214
|
: `(__ui_tap_seq != ${prevVar}) && (__ui_tap_node == ${nodeFilter})`;
|
|
183
215
|
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
|
-
}
|
|
216
|
+
body.push(...runSegment(` `));
|
|
210
217
|
body.push(` }`);
|
|
211
218
|
}
|
|
212
|
-
else if (
|
|
213
|
-
//
|
|
214
|
-
//
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
body.push(` }`);
|
|
229
|
-
}
|
|
219
|
+
else if (netPoll) {
|
|
220
|
+
// Net-poll state: a previous segment ended with an awaited WiFi/HTTP
|
|
221
|
+
// op. Wait until its poll condition fires (or its deadline expires).
|
|
222
|
+
const deadline = `${strategy.currentTimeMillis()} >= _waitUntil`;
|
|
223
|
+
const cond = netPoll.pollCond === null
|
|
224
|
+
? deadline
|
|
225
|
+
: netPoll.timeoutExpr !== null
|
|
226
|
+
? `(${netPoll.pollCond}) || ${deadline}`
|
|
227
|
+
: netPoll.pollCond;
|
|
228
|
+
body.push(` if (${cond}) {`);
|
|
229
|
+
body.push(...runSegment(` `));
|
|
230
|
+
body.push(` }`);
|
|
231
|
+
}
|
|
232
|
+
else if (i === 0) {
|
|
233
|
+
// First state runs immediately.
|
|
234
|
+
body.push(...runSegment(` `));
|
|
230
235
|
}
|
|
231
236
|
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
|
-
}
|
|
237
|
+
// Previous await was a plain timed wait — poll its deadline.
|
|
238
|
+
body.push(` if (${strategy.currentTimeMillis()} >= _waitUntil) {`);
|
|
239
|
+
body.push(...runSegment(` `));
|
|
240
|
+
body.push(` }`);
|
|
259
241
|
}
|
|
260
242
|
caseLines.push(` case ${stateName}:`, ` {`, ...body, ` }`, ` break;`);
|
|
261
243
|
}
|
|
@@ -331,6 +313,93 @@ function renderExpression(expr, strategy) {
|
|
|
331
313
|
return "/* complex expr */";
|
|
332
314
|
}
|
|
333
315
|
}
|
|
316
|
+
/** Numeric-or-rendered HAL field → C++ text. */
|
|
317
|
+
function ms(v) {
|
|
318
|
+
return String(v);
|
|
319
|
+
}
|
|
320
|
+
/** True when a timeout field is the literal 0 (wait forever, no deadline). */
|
|
321
|
+
function isZeroTimeout(v) {
|
|
322
|
+
return v === undefined || v === null || String(v).trim() === "0";
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Map an awaitable HAL op to its start statements + poll condition, lowering
|
|
326
|
+
* the start op and poll predicate through the platform strategy. Keep the set
|
|
327
|
+
* of handled ops in sync with AWAITABLE_HAL_OPS in ir/transformers/expressions.ts.
|
|
328
|
+
*
|
|
329
|
+
* Falls back to running the blocking form as the "start" with an immediate
|
|
330
|
+
* completion when the strategy can't lower the split ops.
|
|
331
|
+
*/
|
|
332
|
+
function netWaitInfo(op, strategy) {
|
|
333
|
+
const o = op;
|
|
334
|
+
const route = (routedOp) => routeHALOp(routedOp, strategy);
|
|
335
|
+
const expr = (operation) => route({ operation })?.expression ?? null;
|
|
336
|
+
switch (op.operation) {
|
|
337
|
+
case "timing.delay":
|
|
338
|
+
return { startLines: [], pollCond: null, timeoutExpr: ms(o.ms) };
|
|
339
|
+
case "wifi.connect": {
|
|
340
|
+
const start = route({ operation: "wifi.connect_start", ssid: o.ssid, password: o.password });
|
|
341
|
+
const poll = expr("wifi.is_connected");
|
|
342
|
+
if (start?.code && poll) {
|
|
343
|
+
return {
|
|
344
|
+
startLines: [start.code],
|
|
345
|
+
pollCond: poll,
|
|
346
|
+
timeoutExpr: isZeroTimeout(o.timeoutMs) ? null : ms(o.timeoutMs),
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
break;
|
|
350
|
+
}
|
|
351
|
+
case "wifi.wait_connected": {
|
|
352
|
+
const poll = expr("wifi.is_connected");
|
|
353
|
+
if (poll) {
|
|
354
|
+
return {
|
|
355
|
+
startLines: [],
|
|
356
|
+
pollCond: poll,
|
|
357
|
+
timeoutExpr: isZeroTimeout(o.timeoutMs) ? null : ms(o.timeoutMs),
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
break;
|
|
361
|
+
}
|
|
362
|
+
case "wifi.wait_disconnected": {
|
|
363
|
+
const poll = expr("wifi.is_connected");
|
|
364
|
+
if (poll) {
|
|
365
|
+
return { startLines: [], pollCond: `!${poll}`, timeoutExpr: null };
|
|
366
|
+
}
|
|
367
|
+
break;
|
|
368
|
+
}
|
|
369
|
+
case "wifi.scan": {
|
|
370
|
+
const start = route({ operation: "wifi.scan_start" });
|
|
371
|
+
const poll = expr("wifi.scan_done");
|
|
372
|
+
if (start?.code && poll) {
|
|
373
|
+
return { startLines: [start.code], pollCond: poll, timeoutExpr: null };
|
|
374
|
+
}
|
|
375
|
+
break;
|
|
376
|
+
}
|
|
377
|
+
case "http.send": {
|
|
378
|
+
const start = route({ operation: "http.send_start" });
|
|
379
|
+
const poll = expr("http.done");
|
|
380
|
+
if (start?.code && poll) {
|
|
381
|
+
return { startLines: [start.code], pollCond: poll, timeoutExpr: null };
|
|
382
|
+
}
|
|
383
|
+
break;
|
|
384
|
+
}
|
|
385
|
+
case "ble.until_connected": {
|
|
386
|
+
const start = route({ operation: "ble.until_connected_start" });
|
|
387
|
+
const poll = expr("ble.is_connected");
|
|
388
|
+
if (start?.code && poll) {
|
|
389
|
+
return {
|
|
390
|
+
startLines: [start.code],
|
|
391
|
+
pollCond: poll,
|
|
392
|
+
timeoutExpr: isZeroTimeout(o.timeoutMs) ? null : ms(o.timeoutMs),
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
break;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
// Fallback: run the blocking form immediately and complete on the next tick.
|
|
399
|
+
const blocking = routeHALOp(op, strategy);
|
|
400
|
+
const line = blocking?.code ?? (blocking?.expression ? `${blocking.expression};` : `/* unhandled awaited hal-op: ${op.operation} */`);
|
|
401
|
+
return { startLines: [line], pollCond: null, timeoutExpr: "0" };
|
|
402
|
+
}
|
|
334
403
|
function parseEdgeMarker(argText) {
|
|
335
404
|
const match = argText.match(/^__EDGE_(RISING|FALLING)__([a-zA-Z0-9_]+)__T(.*)$/);
|
|
336
405
|
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
|
+
}
|
|
@@ -59,7 +59,7 @@ function extractNumericValue(expr) {
|
|
|
59
59
|
/**
|
|
60
60
|
* Check binary comparison expressions for ADC range issues.
|
|
61
61
|
*/
|
|
62
|
-
function checkComparisonForADCRange(left, operator, right, adcConfig, diagnostics) {
|
|
62
|
+
function checkComparisonForADCRange(left, operator, right, adcConfig, filePath, diagnostics) {
|
|
63
63
|
// Check if one side is an analog read and the other is a literal
|
|
64
64
|
let analogReadSide = null;
|
|
65
65
|
let literalValue = null;
|
|
@@ -83,6 +83,7 @@ function checkComparisonForADCRange(left, operator, right, adcConfig, diagnostic
|
|
|
83
83
|
severity: 'info',
|
|
84
84
|
message: `Comparison ${comparisonDesc} may never be true. ADC resolution is ${adcConfig.resolution}-bit (max ${adcConfig.maxValue}) on this board.`,
|
|
85
85
|
code: 'adc-range-warning',
|
|
86
|
+
filePath,
|
|
86
87
|
source: 'adc-range-validation',
|
|
87
88
|
});
|
|
88
89
|
}
|
|
@@ -90,7 +91,7 @@ function checkComparisonForADCRange(left, operator, right, adcConfig, diagnostic
|
|
|
90
91
|
/**
|
|
91
92
|
* Scan an expression for ADC range issues.
|
|
92
93
|
*/
|
|
93
|
-
function scanExpressionForADCRange(expr, adcConfig, diagnostics) {
|
|
94
|
+
function scanExpressionForADCRange(expr, adcConfig, filePath, diagnostics) {
|
|
94
95
|
if (!expr || typeof expr !== 'object')
|
|
95
96
|
return;
|
|
96
97
|
// Check binary comparisons
|
|
@@ -98,59 +99,59 @@ function scanExpressionForADCRange(expr, adcConfig, diagnostics) {
|
|
|
98
99
|
const bin = expr;
|
|
99
100
|
const comparisonOps = ['>', '>=', '<', '<=', '===', '==', '!==', '!='];
|
|
100
101
|
if (comparisonOps.includes(bin.operator)) {
|
|
101
|
-
checkComparisonForADCRange(bin.left, bin.operator, bin.right, adcConfig, diagnostics);
|
|
102
|
+
checkComparisonForADCRange(bin.left, bin.operator, bin.right, adcConfig, filePath, diagnostics);
|
|
102
103
|
}
|
|
103
104
|
// Recursively scan both sides
|
|
104
|
-
scanExpressionForADCRange(bin.left, adcConfig, diagnostics);
|
|
105
|
-
scanExpressionForADCRange(bin.right, adcConfig, diagnostics);
|
|
105
|
+
scanExpressionForADCRange(bin.left, adcConfig, filePath, diagnostics);
|
|
106
|
+
scanExpressionForADCRange(bin.right, adcConfig, filePath, diagnostics);
|
|
106
107
|
}
|
|
107
108
|
// Check ternary conditions
|
|
108
109
|
if (expr.kind === 'ternary') {
|
|
109
110
|
const ternary = expr;
|
|
110
|
-
scanExpressionForADCRange(ternary.condition, adcConfig, diagnostics);
|
|
111
|
-
scanExpressionForADCRange(ternary.whenTrue, adcConfig, diagnostics);
|
|
112
|
-
scanExpressionForADCRange(ternary.whenFalse, adcConfig, diagnostics);
|
|
111
|
+
scanExpressionForADCRange(ternary.condition, adcConfig, filePath, diagnostics);
|
|
112
|
+
scanExpressionForADCRange(ternary.whenTrue, adcConfig, filePath, diagnostics);
|
|
113
|
+
scanExpressionForADCRange(ternary.whenFalse, adcConfig, filePath, diagnostics);
|
|
113
114
|
}
|
|
114
115
|
// Check property access
|
|
115
116
|
if (expr.kind === 'property-access') {
|
|
116
117
|
const pa = expr;
|
|
117
|
-
scanExpressionForADCRange(pa.object, adcConfig, diagnostics);
|
|
118
|
+
scanExpressionForADCRange(pa.object, adcConfig, filePath, diagnostics);
|
|
118
119
|
}
|
|
119
120
|
}
|
|
120
121
|
/**
|
|
121
122
|
* Scan a statement for ADC range issues.
|
|
122
123
|
*/
|
|
123
|
-
function scanStatementForADCRange(stmt, adcConfig, diagnostics) {
|
|
124
|
+
function scanStatementForADCRange(stmt, adcConfig, filePath, diagnostics) {
|
|
124
125
|
if (!stmt || typeof stmt !== 'object')
|
|
125
126
|
return;
|
|
126
127
|
switch (stmt.kind) {
|
|
127
128
|
case 'var_decl': {
|
|
128
129
|
const varDecl = stmt;
|
|
129
130
|
if (varDecl.initializer) {
|
|
130
|
-
scanExpressionForADCRange(varDecl.initializer, adcConfig, diagnostics);
|
|
131
|
+
scanExpressionForADCRange(varDecl.initializer, adcConfig, filePath, diagnostics);
|
|
131
132
|
}
|
|
132
133
|
break;
|
|
133
134
|
}
|
|
134
135
|
case 'assign': {
|
|
135
136
|
const assign = stmt;
|
|
136
137
|
if (assign.value) {
|
|
137
|
-
scanExpressionForADCRange(assign.value, adcConfig, diagnostics);
|
|
138
|
+
scanExpressionForADCRange(assign.value, adcConfig, filePath, diagnostics);
|
|
138
139
|
}
|
|
139
140
|
break;
|
|
140
141
|
}
|
|
141
142
|
case 'if': {
|
|
142
143
|
const ifStmt = stmt;
|
|
143
144
|
if (ifStmt.condition) {
|
|
144
|
-
scanExpressionForADCRange(ifStmt.condition, adcConfig, diagnostics);
|
|
145
|
+
scanExpressionForADCRange(ifStmt.condition, adcConfig, filePath, diagnostics);
|
|
145
146
|
}
|
|
146
147
|
if (ifStmt.thenBranch) {
|
|
147
148
|
for (const s of ifStmt.thenBranch) {
|
|
148
|
-
scanStatementForADCRange(s, adcConfig, diagnostics);
|
|
149
|
+
scanStatementForADCRange(s, adcConfig, filePath, diagnostics);
|
|
149
150
|
}
|
|
150
151
|
}
|
|
151
152
|
if (ifStmt.elseBranch) {
|
|
152
153
|
for (const s of ifStmt.elseBranch) {
|
|
153
|
-
scanStatementForADCRange(s, adcConfig, diagnostics);
|
|
154
|
+
scanStatementForADCRange(s, adcConfig, filePath, diagnostics);
|
|
154
155
|
}
|
|
155
156
|
}
|
|
156
157
|
break;
|
|
@@ -158,11 +159,11 @@ function scanStatementForADCRange(stmt, adcConfig, diagnostics) {
|
|
|
158
159
|
case 'while': {
|
|
159
160
|
const whileStmt = stmt;
|
|
160
161
|
if (whileStmt.condition) {
|
|
161
|
-
scanExpressionForADCRange(whileStmt.condition, adcConfig, diagnostics);
|
|
162
|
+
scanExpressionForADCRange(whileStmt.condition, adcConfig, filePath, diagnostics);
|
|
162
163
|
}
|
|
163
164
|
if (whileStmt.body) {
|
|
164
165
|
for (const s of whileStmt.body) {
|
|
165
|
-
scanStatementForADCRange(s, adcConfig, diagnostics);
|
|
166
|
+
scanStatementForADCRange(s, adcConfig, filePath, diagnostics);
|
|
166
167
|
}
|
|
167
168
|
}
|
|
168
169
|
break;
|
|
@@ -170,11 +171,11 @@ function scanStatementForADCRange(stmt, adcConfig, diagnostics) {
|
|
|
170
171
|
case 'for': {
|
|
171
172
|
const forStmt = stmt;
|
|
172
173
|
if (forStmt.condition) {
|
|
173
|
-
scanExpressionForADCRange(forStmt.condition, adcConfig, diagnostics);
|
|
174
|
+
scanExpressionForADCRange(forStmt.condition, adcConfig, filePath, diagnostics);
|
|
174
175
|
}
|
|
175
176
|
if (forStmt.body) {
|
|
176
177
|
for (const s of forStmt.body) {
|
|
177
|
-
scanStatementForADCRange(s, adcConfig, diagnostics);
|
|
178
|
+
scanStatementForADCRange(s, adcConfig, filePath, diagnostics);
|
|
178
179
|
}
|
|
179
180
|
}
|
|
180
181
|
break;
|
|
@@ -182,7 +183,7 @@ function scanStatementForADCRange(stmt, adcConfig, diagnostics) {
|
|
|
182
183
|
case 'return': {
|
|
183
184
|
const retStmt = stmt;
|
|
184
185
|
if (retStmt.value) {
|
|
185
|
-
scanExpressionForADCRange(retStmt.value, adcConfig, diagnostics);
|
|
186
|
+
scanExpressionForADCRange(retStmt.value, adcConfig, filePath, diagnostics);
|
|
186
187
|
}
|
|
187
188
|
break;
|
|
188
189
|
}
|
|
@@ -190,7 +191,7 @@ function scanStatementForADCRange(stmt, adcConfig, diagnostics) {
|
|
|
190
191
|
const call = stmt;
|
|
191
192
|
if (call.args) {
|
|
192
193
|
for (const arg of call.args) {
|
|
193
|
-
scanExpressionForADCRange(arg, adcConfig, diagnostics);
|
|
194
|
+
scanExpressionForADCRange(arg, adcConfig, filePath, diagnostics);
|
|
194
195
|
}
|
|
195
196
|
}
|
|
196
197
|
break;
|
|
@@ -214,7 +215,7 @@ export function validateADCRange(program, boardConstants) {
|
|
|
214
215
|
// Scan top-level statements
|
|
215
216
|
if (program.topLevelStatements) {
|
|
216
217
|
for (const stmt of program.topLevelStatements) {
|
|
217
|
-
scanStatementForADCRange(stmt, adcConfig, diagnostics);
|
|
218
|
+
scanStatementForADCRange(stmt, adcConfig, program.fileName, diagnostics);
|
|
218
219
|
}
|
|
219
220
|
}
|
|
220
221
|
// Scan function bodies
|
|
@@ -222,7 +223,7 @@ export function validateADCRange(program, boardConstants) {
|
|
|
222
223
|
for (const fn of program.functions) {
|
|
223
224
|
if (fn.statements) {
|
|
224
225
|
for (const stmt of fn.statements) {
|
|
225
|
-
scanStatementForADCRange(stmt, adcConfig, diagnostics);
|
|
226
|
+
scanStatementForADCRange(stmt, adcConfig, program.fileName, diagnostics);
|
|
226
227
|
}
|
|
227
228
|
}
|
|
228
229
|
}
|
|
@@ -234,14 +235,14 @@ export function validateADCRange(program, boardConstants) {
|
|
|
234
235
|
for (const method of cls.methods) {
|
|
235
236
|
if (method.statements) {
|
|
236
237
|
for (const stmt of method.statements) {
|
|
237
|
-
scanStatementForADCRange(stmt, adcConfig, diagnostics);
|
|
238
|
+
scanStatementForADCRange(stmt, adcConfig, program.fileName, diagnostics);
|
|
238
239
|
}
|
|
239
240
|
}
|
|
240
241
|
}
|
|
241
242
|
}
|
|
242
243
|
if (cls.constructor?.statements) {
|
|
243
244
|
for (const stmt of cls.constructor.statements) {
|
|
244
|
-
scanStatementForADCRange(stmt, adcConfig, diagnostics);
|
|
245
|
+
scanStatementForADCRange(stmt, adcConfig, program.fileName, diagnostics);
|
|
245
246
|
}
|
|
246
247
|
}
|
|
247
248
|
}
|
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 = [];
|