@typecad/cuttlefish 1.0.0-alpha.6 → 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/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 +418 -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/validate-framework-manifest.d.ts +28 -0
- package/dist/api/shared/validate-framework-manifest.js +417 -0
- package/dist/cli.js +109 -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/init-scaffold.d.ts +3 -0
- package/dist/create/init-scaffold.js +48 -0
- package/dist/create/init-templates.d.ts +2 -0
- package/dist/create/init-templates.js +28 -8
- 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 +40 -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/build-ir.js +5 -1
- package/dist/ir/expression-to-ir.js +14 -0
- 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 +561 -0
- package/dist/ir/identifier-collector.js +18 -0
- package/dist/ir/network-validation.d.ts +4 -0
- package/dist/ir/network-validation.js +184 -0
- package/dist/ir/ownership-analysis.js +14 -1
- package/dist/ir/program-analysis.d.ts +23 -0
- package/dist/ir/program-analysis.js +81 -2
- package/dist/ir/timing-validation.d.ts +6 -1
- package/dist/ir/timing-validation.js +50 -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 +41 -3
- package/dist/ir/validation-orchestrator.js +3 -1
- 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/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 +3 -1
- package/dist/testing.js +3 -1
- package/dist/transpile.d.ts +3 -0
- package/dist/transpile.js +72 -27
- package/dist/ui-hook.d.ts +17 -1
- package/dist/utils/cli.js +27 -1
- package/dist/utils/fs.d.ts +13 -0
- package/dist/utils/fs.js +50 -0
- package/package.json +8 -4
|
@@ -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
|
+
}
|
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 = [];
|
|
@@ -953,6 +953,20 @@ export function expressionToIR(expr, sourceText, diagnostics, pointerVars = new
|
|
|
953
953
|
};
|
|
954
954
|
}
|
|
955
955
|
if (ts.isCallExpression(expr)) {
|
|
956
|
+
// ---- rawCppExpr() — compile-time C++ injection in expression context ----
|
|
957
|
+
// Mirrors the statement-position rawCpp()/__EMIT__ path, but emits the raw
|
|
958
|
+
// text as an expression (e.g. for IDF macros like WIFI_INIT_CONFIG_DEFAULT()
|
|
959
|
+
// that produce struct values). The type parameter is a TS hint only — the
|
|
960
|
+
// transpiler emits the raw text verbatim.
|
|
961
|
+
if (ts.isIdentifier(expr.expression) && expr.expression.text === "rawCppExpr") {
|
|
962
|
+
const arg = expr.arguments[0];
|
|
963
|
+
if (arg && ts.isStringLiteral(arg)) {
|
|
964
|
+
return { kind: "raw", value: arg.text };
|
|
965
|
+
}
|
|
966
|
+
if (arg && ts.isNoSubstitutionTemplateLiteral(arg)) {
|
|
967
|
+
return { kind: "raw", value: arg.text };
|
|
968
|
+
}
|
|
969
|
+
}
|
|
956
970
|
// ---- board() / boardResolve() — compile-time board constant lookup ----
|
|
957
971
|
if (ts.isIdentifier(expr.expression) && (expr.expression.text === "board" || expr.expression.text === "boardResolve")) {
|
|
958
972
|
const pathArg = expr.arguments[0];
|
|
@@ -198,11 +198,13 @@ function kindBasedLintRules() {
|
|
|
198
198
|
// `no-restricted-syntax` selector for real-time editor warnings. Kept here so
|
|
199
199
|
// that all editor-time selectors live in one place.
|
|
200
200
|
//
|
|
201
|
-
// NOTE on async/
|
|
202
|
-
//
|
|
203
|
-
//
|
|
204
|
-
//
|
|
205
|
-
//
|
|
201
|
+
// NOTE on async/await: the transpiler fully supports these — each `async`
|
|
202
|
+
// function is lowered to a cooperative state-machine task driven from
|
|
203
|
+
// `loop()` (see `emit/utils/async-state-machine.ts` and the
|
|
204
|
+
// `async-runtime-static.ts` microtask pump). `await expr` lowers to the task
|
|
205
|
+
// yielding between segments, so blocking waits become polls. They are
|
|
206
|
+
// therefore intentionally NOT linted here. Generators/yield and
|
|
207
|
+
// `for await...of` still have no lowering and remain flagged.
|
|
206
208
|
const CONTEXT_LINT_RULES = [
|
|
207
209
|
{
|
|
208
210
|
selector: "ForOfStatement[await=true]",
|
|
@@ -244,26 +246,6 @@ const CONTEXT_LINT_RULES = [
|
|
|
244
246
|
message: "[transpiler] .then() on a Promise is not supported (no promise runtime). Use synchronous return values or callbacks.",
|
|
245
247
|
source: "context",
|
|
246
248
|
},
|
|
247
|
-
{
|
|
248
|
-
selector: "FunctionDeclaration[async=true]",
|
|
249
|
-
message: "[transpiler] async functions are recognized for parse-compatibility but cannot produce correct embedded behavior (no event loop). Use a synchronous function.",
|
|
250
|
-
source: "context",
|
|
251
|
-
},
|
|
252
|
-
{
|
|
253
|
-
selector: "FunctionExpression[async=true]",
|
|
254
|
-
message: "[transpiler] async function expressions are recognized for parse-compatibility but cannot produce correct embedded behavior (no event loop). Use a synchronous function.",
|
|
255
|
-
source: "context",
|
|
256
|
-
},
|
|
257
|
-
{
|
|
258
|
-
selector: "ArrowFunctionExpression[async=true]",
|
|
259
|
-
message: "[transpiler] async arrow functions are recognized for parse-compatibility but cannot produce correct embedded behavior (no event loop). Use a synchronous arrow function.",
|
|
260
|
-
source: "context",
|
|
261
|
-
},
|
|
262
|
-
{
|
|
263
|
-
selector: "AwaitExpression",
|
|
264
|
-
message: "[transpiler] await is stripped to an inline expression — semantics are approximate and there is no event loop on bare metal. Avoid in firmware.",
|
|
265
|
-
source: "context",
|
|
266
|
-
},
|
|
267
249
|
{
|
|
268
250
|
selector: "FunctionDeclaration[generator=true]",
|
|
269
251
|
message: "[transpiler] generator functions (function*) have no coroutine runtime on bare metal and are effectively unusable. Avoid.",
|
|
@@ -19,9 +19,12 @@ export declare function maybeEscapeResolvedText(text: string): string;
|
|
|
19
19
|
export declare function stripCStrAfterStringLiteral(followingLiteralText: string, resolvedValue: string): string;
|
|
20
20
|
/** Resolve an arbitrary expression to its text form, with this/param substitution. */
|
|
21
21
|
export declare function resolveExpressionText(expr: ts.Expression, instance: HALInstance, paramNames: string[], callArgTexts: string[], paramDefaults?: Map<string, string>): string | null;
|
|
22
|
+
/** True when a HAL semantic call name wraps a GPIO/hardware ISR callback. */
|
|
23
|
+
export declare function semanticCallUsesIsrCallback(fnName: string): boolean;
|
|
22
24
|
/** Scan an expression AST for callback() calls, extract callback IR from callArgs,
|
|
23
|
-
* register them, and patch callArgTexts with placeholder names.
|
|
24
|
-
|
|
25
|
+
* register them, and patch callArgTexts with placeholder names.
|
|
26
|
+
* @param isInterruptHandler — only true for GPIO interrupt attach paths. */
|
|
27
|
+
export declare function extractAndRegisterCallbacks(expr: ts.Expression, paramNames: string[], callArgs: ExpressionIR[], callArgTexts: string[], isInterruptHandler?: boolean): void;
|
|
25
28
|
/** Process a HAL method body, resolving emit()/include()/semantic calls.
|
|
26
29
|
* Returns { emitLines, halOps, returnValue, returnClassName } or null if unresolvable.
|
|
27
30
|
*
|
|
@@ -5,6 +5,7 @@ import { renderExprAsText } from "../render-expr.js";
|
|
|
5
5
|
import { escapeCppKeyword } from "../../utils/strings.js";
|
|
6
6
|
import { halClassRegistry, halGlobalFunctions } from "./hal-parser.js";
|
|
7
7
|
import { tryResolveSemanticCall, tryResolveBoardResolveArg, tryResolveCompoundSemanticReturn, resolveConcatPath } from "./hal-plugins.js";
|
|
8
|
+
import { cppTypeForHalOp } from "../../emit/utils/hal-op-cpp-type.js";
|
|
8
9
|
/** Escape C++ keywords in resolved text, but only when the text looks like a
|
|
9
10
|
* variable reference (not a literal like "false", "true", "42", or a string). */
|
|
10
11
|
export function maybeEscapeResolvedText(text) {
|
|
@@ -275,9 +276,22 @@ export function resolveExpressionText(expr, instance, paramNames, callArgTexts,
|
|
|
275
276
|
}
|
|
276
277
|
return expr.getText ? expr.getText() : null;
|
|
277
278
|
}
|
|
279
|
+
/** Semantic HAL helpers whose callbacks run in true ISR context (need IRAM_ATTR).
|
|
280
|
+
* WiFi event handlers, timers, setInterval, etc. use callback() too but run in
|
|
281
|
+
* a task / event loop — marking those as ISRs puts printf in IRAM and, on
|
|
282
|
+
* ESP-IDF, trips -Werror=attributes when IRAM_ATTR's __COUNTER__ disagrees
|
|
283
|
+
* between forward declaration and definition. */
|
|
284
|
+
const ISR_CALLBACK_SEMANTIC_FNS = new Set([
|
|
285
|
+
"interruptAttach",
|
|
286
|
+
]);
|
|
287
|
+
/** True when a HAL semantic call name wraps a GPIO/hardware ISR callback. */
|
|
288
|
+
export function semanticCallUsesIsrCallback(fnName) {
|
|
289
|
+
return ISR_CALLBACK_SEMANTIC_FNS.has(fnName);
|
|
290
|
+
}
|
|
278
291
|
/** Scan an expression AST for callback() calls, extract callback IR from callArgs,
|
|
279
|
-
* register them, and patch callArgTexts with placeholder names.
|
|
280
|
-
|
|
292
|
+
* register them, and patch callArgTexts with placeholder names.
|
|
293
|
+
* @param isInterruptHandler — only true for GPIO interrupt attach paths. */
|
|
294
|
+
export function extractAndRegisterCallbacks(expr, paramNames, callArgs, callArgTexts, isInterruptHandler = false) {
|
|
281
295
|
function scan(node) {
|
|
282
296
|
if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && node.expression.text === "callback") {
|
|
283
297
|
const cbArg = node.arguments[0];
|
|
@@ -292,6 +306,8 @@ export function extractAndRegisterCallbacks(expr, paramNames, callArgs, callArgT
|
|
|
292
306
|
kind: "callback",
|
|
293
307
|
params: callbackIR.params.map((p) => p.name),
|
|
294
308
|
statements: callbackIR.body,
|
|
309
|
+
...(callbackIR.returnType && callbackIR.returnType !== "void" ? { returnType: callbackIR.returnType } : {}),
|
|
310
|
+
...(callbackIR.params ? { typedParams: callbackIR.params.map((p) => ({ name: p.name, cppType: p.cppType })).filter((p) => p.cppType && p.cppType !== "void") } : {}),
|
|
295
311
|
sourceSpan: callbackIR.body[0]?.sourceSpan ?? {
|
|
296
312
|
filePath: "",
|
|
297
313
|
startLine: 0,
|
|
@@ -301,10 +317,12 @@ export function extractAndRegisterCallbacks(expr, paramNames, callArgs, callArgT
|
|
|
301
317
|
startOffset: 0,
|
|
302
318
|
endOffset: 0,
|
|
303
319
|
},
|
|
304
|
-
isInterruptHandler: true,
|
|
320
|
+
...(isInterruptHandler ? { isInterruptHandler: true } : {}),
|
|
305
321
|
}
|
|
306
322
|
: callbackIR;
|
|
307
|
-
|
|
323
|
+
if (isInterruptHandler) {
|
|
324
|
+
normalized.isInterruptHandler = true;
|
|
325
|
+
}
|
|
308
326
|
registeredCallbacks.push({ placeholderName: placeholder, callbackIR: normalized });
|
|
309
327
|
callArgTexts[paramIdx] = placeholder;
|
|
310
328
|
}
|
|
@@ -399,8 +417,9 @@ export function processHALMethodBody(instance, methodName, callArgs) {
|
|
|
399
417
|
if (ts.isIdentifier(call.expression)) {
|
|
400
418
|
// Extract callbacks from semantic call arguments and patch callArgTexts
|
|
401
419
|
// with placeholder names before resolving (mirrors the emit() path).
|
|
420
|
+
const isIsr = semanticCallUsesIsrCallback(call.expression.text);
|
|
402
421
|
for (const arg of call.arguments) {
|
|
403
|
-
extractAndRegisterCallbacks(arg, paramNames, callArgs, callArgTexts);
|
|
422
|
+
extractAndRegisterCallbacks(arg, paramNames, callArgs, callArgTexts, isIsr);
|
|
404
423
|
}
|
|
405
424
|
const semanticOp = tryResolveSemanticCall(call.expression.text, call.arguments, instance, paramNames, callArgTexts, paramDefaults, callArgs);
|
|
406
425
|
if (semanticOp) {
|
|
@@ -468,8 +487,9 @@ export function processHALMethodBody(instance, methodName, callArgs) {
|
|
|
468
487
|
actualRetExpr = actualRetExpr.expression;
|
|
469
488
|
}
|
|
470
489
|
if (ts.isCallExpression(actualRetExpr) && ts.isIdentifier(actualRetExpr.expression)) {
|
|
490
|
+
const isIsr = semanticCallUsesIsrCallback(actualRetExpr.expression.text);
|
|
471
491
|
for (const arg of actualRetExpr.arguments) {
|
|
472
|
-
extractAndRegisterCallbacks(arg, paramNames, callArgs, callArgTexts);
|
|
492
|
+
extractAndRegisterCallbacks(arg, paramNames, callArgs, callArgTexts, isIsr);
|
|
473
493
|
}
|
|
474
494
|
const semanticOp = tryResolveSemanticCall(actualRetExpr.expression.text, actualRetExpr.arguments, instance, paramNames, callArgTexts, paramDefaults, callArgs);
|
|
475
495
|
if (semanticOp) {
|
|
@@ -594,8 +614,9 @@ export function processStatementList(stmts, instance, paramNames, callArgTexts,
|
|
|
594
614
|
}
|
|
595
615
|
else {
|
|
596
616
|
// R3: Try semantic HAL function call
|
|
617
|
+
const isIsr = semanticCallUsesIsrCallback(call.expression.text);
|
|
597
618
|
for (const arg of call.arguments) {
|
|
598
|
-
extractAndRegisterCallbacks(arg, paramNames, callArgs, callArgTexts);
|
|
619
|
+
extractAndRegisterCallbacks(arg, paramNames, callArgs, callArgTexts, isIsr);
|
|
599
620
|
}
|
|
600
621
|
const semanticOp = tryResolveSemanticCall(call.expression.text, call.arguments, instance, paramNames, callArgTexts, paramDefaults, callArgs);
|
|
601
622
|
if (semanticOp) {
|
|
@@ -696,14 +717,21 @@ export function buildSnprintfFromConcat(expr) {
|
|
|
696
717
|
else if (part.kind === "template_string" && part.expression.kind === "hal-expr") {
|
|
697
718
|
// HAL expression inside template literal — resolve via strategy
|
|
698
719
|
const resolved = resolveHALExprToText(part.expression);
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
720
|
+
const argText = resolved !== null ? resolved : text;
|
|
721
|
+
const cppType = cppTypeForHalOp(part.expression.operation.operation);
|
|
722
|
+
if (cppType === "const char*" || (cppType !== undefined && /char\s*\*$/.test(cppType))) {
|
|
723
|
+
formatString += "%s";
|
|
724
|
+
args.push(argText);
|
|
725
|
+
estimatedLength += 32;
|
|
726
|
+
}
|
|
727
|
+
else if (cppType === "bool") {
|
|
728
|
+
formatString += "%s";
|
|
729
|
+
args.push(`(${argText} ? "true" : "false")`);
|
|
730
|
+
estimatedLength += 5;
|
|
703
731
|
}
|
|
704
732
|
else {
|
|
705
733
|
formatString += "%d";
|
|
706
|
-
args.push(
|
|
734
|
+
args.push(argText);
|
|
707
735
|
estimatedLength += 12;
|
|
708
736
|
}
|
|
709
737
|
}
|
|
@@ -50,6 +50,12 @@ export declare function extractMethods(cls: ts.ClassDeclaration): Map<string, HA
|
|
|
50
50
|
export declare function loadHALModules(force?: boolean): void;
|
|
51
51
|
/** Get constructor includes for a HAL class. */
|
|
52
52
|
export declare function getCtorIncludes(className: string): string[];
|
|
53
|
+
/** Map an HttpClass factory method name to its HTTP verb, or null. */
|
|
54
|
+
export declare function httpFactoryVerb(methodName: string): string | null;
|
|
55
|
+
/** Render an Http factory URL argument as C++ expression text (string
|
|
56
|
+
* literals quoted, identifiers/member accesses verbatim), or null when the
|
|
57
|
+
* argument shape can't be resolved at compile time. */
|
|
58
|
+
export declare function httpUrlArgText(arg: ts.Expression): string | null;
|
|
53
59
|
/** Resolve a call receiver to a tracked HAL instance. */
|
|
54
60
|
export declare function resolveHALReceiver(receiver: ts.Expression): HALInstance | null;
|
|
55
61
|
/** Build field values from constructor arguments using the field mapping.
|