@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
|
@@ -43,7 +43,7 @@ function getTimer0PwmPins(boardConstants) {
|
|
|
43
43
|
}
|
|
44
44
|
return timer0Pins;
|
|
45
45
|
}
|
|
46
|
-
export function validateTimer0PWMTimingConflict(usage, boardConstants) {
|
|
46
|
+
export function validateTimer0PWMTimingConflict(usage, boardConstants, filePath) {
|
|
47
47
|
if (!usage.timer0 || usage.pwmPinsUsed.size === 0) {
|
|
48
48
|
return [];
|
|
49
49
|
}
|
|
@@ -65,6 +65,7 @@ export function validateTimer0PWMTimingConflict(usage, boardConstants) {
|
|
|
65
65
|
return [{
|
|
66
66
|
severity: 'info',
|
|
67
67
|
code: 'timer0-pwm-timing-conflict',
|
|
68
|
+
filePath,
|
|
68
69
|
source: 'timer0-pwm-timing-conflict',
|
|
69
70
|
message: `${pinReferences.join(', ')} use Timer0 PWM${boardLabel}, and your program also relies on Timer0-backed timing APIs such as delay(), millis(), or micros(). This coupling is common on AVR boards, so prefer non-Timer0 PWM pins when you want PWM behavior isolated from core timing.`,
|
|
70
71
|
}];
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import type { ProgramIR } from '../api/index.js';
|
|
2
2
|
import type { Diagnostic } from '../types.js';
|
|
3
|
+
import type { PlatformStrategy } from '../api/shared/index.js';
|
|
3
4
|
/**
|
|
4
5
|
* Detect blocking delay() calls inside loop()'s body. Emits a warning for
|
|
5
6
|
* each, explaining the cooperative-async alternative.
|
|
7
|
+
*
|
|
8
|
+
* On RTOS targets (ESP-IDF), timing.delay lowers to vTaskDelay which yields
|
|
9
|
+
* the CPU — it does NOT freeze the async queue or UI. Only delayMicroseconds
|
|
10
|
+
* (busy-wait) triggers the warning.
|
|
6
11
|
*/
|
|
7
|
-
export declare function validateBlockingDelayInLoop(program: ProgramIR): Diagnostic[];
|
|
12
|
+
export declare function validateBlockingDelayInLoop(program: ProgramIR, strategy?: PlatformStrategy): Diagnostic[];
|
|
@@ -12,20 +12,43 @@
|
|
|
12
12
|
// g++ sees only an ordinary function. This validator flags blocking delays in
|
|
13
13
|
// loop()'s direct body so the user can replace them with the cooperative
|
|
14
14
|
// Async.sleep() / millis()-comparison pattern.
|
|
15
|
+
//
|
|
16
|
+
// IMPORTANT: on RTOS targets (ESP-IDF / FreeRTOS), delay() lowers to
|
|
17
|
+
// vTaskDelay which YIELDS the CPU — it does not freeze other tasks, UI
|
|
18
|
+
// rendering, or the async queue. Only delayMicroseconds (esp_rom_delay_us)
|
|
19
|
+
// is a true busy-wait. So for RTOS targets, only delay_microseconds triggers
|
|
20
|
+
// the warning; delay is safe.
|
|
15
21
|
// ---------------------------------------------------------------------------
|
|
16
|
-
|
|
17
|
-
|
|
22
|
+
import { hasLoadedFramework, getLoadedFramework } from '../framework-registry.js';
|
|
23
|
+
/** Check a single statement for a blocking delay call or hal-op.
|
|
24
|
+
* On RTOS targets, timing.delay (vTaskDelay) is NOT blocking — only
|
|
25
|
+
* timing.delay_microseconds (esp_rom_delay_us) is. */
|
|
26
|
+
function isBlockingDelay(stmt, isRtos) {
|
|
18
27
|
const s = stmt;
|
|
19
28
|
// Direct call: delay(...) or delayMicroseconds(...)
|
|
20
29
|
if (stmt.kind === 'call' && typeof s.callee === 'string') {
|
|
21
|
-
if (
|
|
22
|
-
|
|
30
|
+
if (isRtos) {
|
|
31
|
+
// On RTOS targets, only delayMicroseconds is a busy-wait.
|
|
32
|
+
if (s.callee === 'delayMicroseconds')
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
if (s.callee === 'delay' || s.callee === 'delayMicroseconds')
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
23
39
|
}
|
|
24
40
|
// Hal-op: after HAL resolution, delay() becomes timing.delay /
|
|
25
41
|
// timing.delay_microseconds.
|
|
26
42
|
if (stmt.kind === 'hal-op' && s.operation?.operation) {
|
|
27
|
-
if (
|
|
28
|
-
|
|
43
|
+
if (isRtos) {
|
|
44
|
+
// On RTOS targets, only timing.delay_microseconds is blocking.
|
|
45
|
+
if (s.operation.operation === 'timing.delay_microseconds')
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
if (s.operation.operation === 'timing.delay' || s.operation.operation === 'timing.delay_microseconds') {
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
29
52
|
}
|
|
30
53
|
}
|
|
31
54
|
return false;
|
|
@@ -33,24 +56,40 @@ function isBlockingDelay(stmt) {
|
|
|
33
56
|
/**
|
|
34
57
|
* Detect blocking delay() calls inside loop()'s body. Emits a warning for
|
|
35
58
|
* each, explaining the cooperative-async alternative.
|
|
59
|
+
*
|
|
60
|
+
* On RTOS targets (ESP-IDF), timing.delay lowers to vTaskDelay which yields
|
|
61
|
+
* the CPU — it does NOT freeze the async queue or UI. Only delayMicroseconds
|
|
62
|
+
* (busy-wait) triggers the warning.
|
|
36
63
|
*/
|
|
37
|
-
export function validateBlockingDelayInLoop(program) {
|
|
64
|
+
export function validateBlockingDelayInLoop(program, strategy) {
|
|
38
65
|
const diagnostics = [];
|
|
66
|
+
// Detect RTOS targets where delay() yields rather than blocks.
|
|
67
|
+
// framework-esp32 uses FreeRTOS; vTaskDelay is a yielding delay.
|
|
68
|
+
const isRtos = strategy?.isRtosTarget?.() === true
|
|
69
|
+
|| (hasLoadedFramework() && getLoadedFramework().strategy.isRtosTarget?.() === true);
|
|
39
70
|
const loopFn = program.functions.find(fn => fn.originalName === 'loop');
|
|
40
71
|
if (!loopFn || !loopFn.statements)
|
|
41
72
|
return diagnostics;
|
|
42
73
|
const visit = (stmts) => {
|
|
43
74
|
for (const stmt of stmts) {
|
|
44
|
-
if (isBlockingDelay(stmt)) {
|
|
75
|
+
if (isBlockingDelay(stmt, isRtos)) {
|
|
45
76
|
const s = stmt;
|
|
77
|
+
const message = isRtos
|
|
78
|
+
? `Blocking delayMicroseconds() inside loop() is a busy-wait that wastes CPU cycles. ` +
|
|
79
|
+
`On RTOS targets (ESP-IDF), prefer timing.delay() (vTaskDelay) which yields the CPU to other tasks.`
|
|
80
|
+
: `Blocking delay() inside loop() freezes the async microtask queue and UI rendering ` +
|
|
81
|
+
`for the delay duration. This causes display tearing and makes the sketch unresponsive.`;
|
|
82
|
+
const hint = isRtos
|
|
83
|
+
? `Replace delayMicroseconds with delay() if the timing permits, or accept the brief busy-wait if sub-millisecond precision is required.`
|
|
84
|
+
: `Use the cooperative pattern instead: track elapsed time with millis() comparisons, ` +
|
|
85
|
+
`or use Async.sleep(ms) / Async.yield() to let other tasks run between checks.`;
|
|
46
86
|
diagnostics.push({
|
|
47
87
|
severity: 'warning',
|
|
48
|
-
message
|
|
49
|
-
|
|
50
|
-
hint: `Use the cooperative pattern instead: track elapsed time with millis() comparisons, ` +
|
|
51
|
-
`or use Async.sleep(ms) / Async.yield() to let other tasks run between checks.`,
|
|
88
|
+
message,
|
|
89
|
+
hint,
|
|
52
90
|
line: stmt.sourceSpan?.startLine,
|
|
53
91
|
column: stmt.sourceSpan?.startColumn,
|
|
92
|
+
filePath: stmt.sourceSpan?.filePath,
|
|
54
93
|
code: 'blocking-delay-in-loop',
|
|
55
94
|
source: 'timing-validation',
|
|
56
95
|
});
|
|
@@ -16,6 +16,17 @@ export function expressionStatementToIR(statement, fileName, sourceText, diagnos
|
|
|
16
16
|
if (callStmt && callStmt.kind === "call") {
|
|
17
17
|
return { ...callStmt, isAwaited: true };
|
|
18
18
|
}
|
|
19
|
+
// Awaited network HAL ops (WiFi.connect / WiFi.untilConnected / Http.send
|
|
20
|
+
// / ...) resolve to hal-op statements, which would otherwise lower to the
|
|
21
|
+
// BLOCKING shim call even inside an async state machine. Rewrite them to
|
|
22
|
+
// an awaited __WIFI_WAIT__/__HTTP_WAIT__ marker call carrying the original
|
|
23
|
+
// op; the async state-machine generator turns it into a start + poll state
|
|
24
|
+
// pair, and the statement renderer falls back to the blocking form when
|
|
25
|
+
// the marker is rendered outside a state machine (top-level await, awaits
|
|
26
|
+
// nested in unsupported positions).
|
|
27
|
+
const netMarker = awaitedNetMarker(callStmt);
|
|
28
|
+
if (netMarker)
|
|
29
|
+
return netMarker;
|
|
19
30
|
return callStmt;
|
|
20
31
|
}
|
|
21
32
|
// ── Register bit-field write ────────────────────────────────────────
|
|
@@ -404,3 +415,50 @@ export function expressionStatementToIR(statement, fileName, sourceText, diagnos
|
|
|
404
415
|
}
|
|
405
416
|
return undefined;
|
|
406
417
|
}
|
|
418
|
+
/** HAL ops with a start/poll split available in the async state machine
|
|
419
|
+
* (see async-state-machine.ts netWaitInfo — keep the two in sync).
|
|
420
|
+
* timing.delay is included because `delay()` from @typecad/hal resolves to a
|
|
421
|
+
* hal-op, dropping the isAwaited flag the state machine keys on. */
|
|
422
|
+
const AWAITABLE_HAL_OPS = new Set([
|
|
423
|
+
"timing.delay",
|
|
424
|
+
"wifi.connect",
|
|
425
|
+
"wifi.wait_connected",
|
|
426
|
+
"wifi.wait_disconnected",
|
|
427
|
+
"wifi.scan",
|
|
428
|
+
"http.send",
|
|
429
|
+
"ble.until_connected",
|
|
430
|
+
]);
|
|
431
|
+
/**
|
|
432
|
+
* Rewrite an awaited hal-op statement (or a block whose LAST statement is
|
|
433
|
+
* one — chained HAL calls resolve to blocks of hal-ops) into an awaited
|
|
434
|
+
* `__WIFI_WAIT__` / `__HTTP_WAIT__` / `__HAL_WAIT__` marker call carrying the
|
|
435
|
+
* original op as a hal-expr argument. Returns undefined when the statement is
|
|
436
|
+
* not an awaitable HAL op.
|
|
437
|
+
*/
|
|
438
|
+
function awaitedNetMarker(stmt) {
|
|
439
|
+
if (!stmt)
|
|
440
|
+
return undefined;
|
|
441
|
+
if (stmt.kind === "hal-op" && AWAITABLE_HAL_OPS.has(stmt.operation.operation)) {
|
|
442
|
+
const opName = stmt.operation.operation;
|
|
443
|
+
const callee = opName.startsWith("http.") ? "__HTTP_WAIT__"
|
|
444
|
+
: opName.startsWith("wifi.") ? "__WIFI_WAIT__"
|
|
445
|
+
: opName.startsWith("ble.") ? "__BLE_WAIT__"
|
|
446
|
+
: "__HAL_WAIT__";
|
|
447
|
+
return {
|
|
448
|
+
kind: "call",
|
|
449
|
+
sourceSpan: stmt.sourceSpan,
|
|
450
|
+
leadingComments: stmt.leadingComments,
|
|
451
|
+
trailingComments: stmt.trailingComments,
|
|
452
|
+
callee,
|
|
453
|
+
args: [{ kind: "hal-expr", operation: stmt.operation }],
|
|
454
|
+
isAwaited: true,
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
if (stmt.kind === "block" && stmt.body.length > 0) {
|
|
458
|
+
const last = awaitedNetMarker(stmt.body[stmt.body.length - 1]);
|
|
459
|
+
if (last) {
|
|
460
|
+
return { ...stmt, body: [...stmt.body.slice(0, -1), last] };
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
return undefined;
|
|
464
|
+
}
|
|
@@ -27,7 +27,7 @@ export function collectChainedHALEmits(expr, sourceText, diagnostics, pointerVar
|
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
// Continue recursion to collect deeper chain levels
|
|
30
|
-
if (ts.isCallExpression(innerReceiver) && ts.isPropertyAccessExpression(innerReceiver)) {
|
|
30
|
+
if (ts.isCallExpression(innerReceiver) && ts.isPropertyAccessExpression(innerReceiver.expression)) {
|
|
31
31
|
collectChainedHALEmits(innerReceiver, sourceText, diagnostics, pointerVars, emitLines, halOps);
|
|
32
32
|
}
|
|
33
33
|
}
|
|
@@ -7,13 +7,17 @@ import { getCurrentIrTypeScope, setScopeLocalType } from "../symbol-types.js";
|
|
|
7
7
|
import { renderExprAsText } from "../render-expr.js";
|
|
8
8
|
import { expressionToIR } from "../expression-to-ir.js";
|
|
9
9
|
import { buildInlineForLoop } from "./array-methods.js";
|
|
10
|
-
import { isKnownHALClass, getCtorIncludes, registerFloatVariable, resolveHALReceiver, isHALSingleton, } from "../hal-resolver.js";
|
|
10
|
+
import { isKnownHALClass, getCtorIncludes, getHALCtorFieldMap, registerFloatVariable, resolveHALReceiver, isHALSingleton, } from "../hal-resolver.js";
|
|
11
|
+
import { httpFactoryVerb, httpUrlArgText } from "../hal/hal-parser.js";
|
|
11
12
|
import { resolveHALCallForVarInit } from "./hal-call-resolver.js";
|
|
12
13
|
import { recordSignal } from "./ui-call-resolver.js";
|
|
13
14
|
function replaceHalReadBufferPlaceholder(op, varName) {
|
|
14
15
|
if (op.operation === "i2c.read_buffer" && op.buffer === "__HAL_READ_BUF__") {
|
|
15
16
|
return { ...op, buffer: varName };
|
|
16
17
|
}
|
|
18
|
+
if (op.operation === "spi.read_buffer" && op.buffer === "__HAL_READ_BUF__") {
|
|
19
|
+
return { ...op, buffer: varName };
|
|
20
|
+
}
|
|
17
21
|
return op;
|
|
18
22
|
}
|
|
19
23
|
export function assignmentOperatorToString(kind) {
|
|
@@ -405,9 +409,26 @@ export function variableStatementToIR(statement, fileName, sourceText, diagnosti
|
|
|
405
409
|
}
|
|
406
410
|
}
|
|
407
411
|
}
|
|
412
|
+
// new HttpRequest(method, url) — positional ctor fields. The URL is
|
|
413
|
+
// stored as C++ expression text (quoted literal or identifier).
|
|
414
|
+
if (className === "HttpRequest" && ctorArgs && ctorArgs.length >= 2) {
|
|
415
|
+
const methodArg = ctorArgs[0];
|
|
416
|
+
if (ts.isStringLiteral(methodArg))
|
|
417
|
+
fieldValues.set("_method", methodArg.text.toUpperCase());
|
|
418
|
+
const urlText = httpUrlArgText(ctorArgs[1]);
|
|
419
|
+
if (urlText)
|
|
420
|
+
fieldValues.set("_url", urlText);
|
|
421
|
+
}
|
|
408
422
|
if (ctorArgs) {
|
|
423
|
+
// Resolve a Pin-identifier ctor arg to its _pin number, for any HAL
|
|
424
|
+
// class whose first constructor field is _pin (Pin itself, plus
|
|
425
|
+
// pin-bearing wrappers like RmtChannel). Mirrors the Pin branch
|
|
426
|
+
// above but keyed off the class's registered ctor field map so new
|
|
427
|
+
// pin-keyed classes work without a per-class branch here.
|
|
428
|
+
const ctorFieldMap = getHALCtorFieldMap(className);
|
|
429
|
+
const firstFieldIsPin = ctorFieldMap && Array.from(ctorFieldMap.keys())[0] === "_pin";
|
|
409
430
|
for (const arg of ctorArgs) {
|
|
410
|
-
if (ts.isIdentifier(arg) && className === "Pin") {
|
|
431
|
+
if (ts.isIdentifier(arg) && (className === "Pin" || firstFieldIsPin)) {
|
|
411
432
|
const existing = halInstances.get(arg.text);
|
|
412
433
|
if (existing && existing.fieldValues.has("_pin")) {
|
|
413
434
|
fieldValues.set("_pin", existing.fieldValues.get("_pin"));
|
|
@@ -430,6 +451,45 @@ export function variableStatementToIR(statement, fileName, sourceText, diagnosti
|
|
|
430
451
|
const isSingletonReceiver = ts.isIdentifier(receiver) && isHALSingleton(receiver.text);
|
|
431
452
|
if (result && (!isOwnershipMethod || isSingletonReceiver)) {
|
|
432
453
|
const isHalOpReturn = result.returnValue === "__hal_op_return__";
|
|
454
|
+
// A __TYPED_ARRAY__ return comes ONLY from a HAL method body
|
|
455
|
+
// (`return new Uint8Array(count)` in e.g. I2CDevice.readBytes /
|
|
456
|
+
// SPIDevice.readRegister). The method's side-effect HAL ops (the
|
|
457
|
+
// i2c.read_buffer / spi.read_buffer fill loops) write INTO this buffer
|
|
458
|
+
// via the __HAL_READ_BUF__ placeholder (rewritten to `varName`). So the
|
|
459
|
+
// buffer var_decl MUST precede the fill ops in emitted order — at top
|
|
460
|
+
// level the var_decl is hoisted to file scope which masks this, but a
|
|
461
|
+
// function-local `const data = ...readBytes()` would otherwise emit the
|
|
462
|
+
// fill loop referencing `data` before its declaration. Detect the typed
|
|
463
|
+
// array up front so we can emit its declaration first.
|
|
464
|
+
const isTypedArrayReturn = typeof result.returnValue === "string" && result.returnValue.startsWith("__TYPED_ARRAY__:");
|
|
465
|
+
if (isTypedArrayReturn && typeof result.returnValue === "string") {
|
|
466
|
+
const parts = result.returnValue.split(":");
|
|
467
|
+
const elementType = parts[1];
|
|
468
|
+
const size = parts[2];
|
|
469
|
+
// Force non-const storage (the buffer is written by the fill op) and
|
|
470
|
+
// synthesize a zero-init array initializer so the var_decl renderer
|
|
471
|
+
// emits `T data[] = { 0, 0, ... }` — a bare `const T data[N];` that
|
|
472
|
+
// is later written would fail to compile (assignment to const).
|
|
473
|
+
// Mirrors plain `new Uint8Array(N)` (expression-to-ir.ts).
|
|
474
|
+
const count = parseInt(size, 10);
|
|
475
|
+
const initElements = !isNaN(count) && count > 0 && count <= 256
|
|
476
|
+
? Array(count).fill(0).map(() => ({ kind: "number", value: 0 }))
|
|
477
|
+
: [];
|
|
478
|
+
lowered.push({
|
|
479
|
+
kind: "var_decl",
|
|
480
|
+
sourceSpan: makeSourceSpan(declaration, fileName, sourceText),
|
|
481
|
+
leadingComments: commentsAssigned ? [] : statementComments.leadingComments,
|
|
482
|
+
trailingComments: [],
|
|
483
|
+
name: varName,
|
|
484
|
+
storage: "let",
|
|
485
|
+
cppType: `${elementType}[${size}]`,
|
|
486
|
+
initializer: initElements.length > 0
|
|
487
|
+
? { kind: "array", elements: initElements, elementType }
|
|
488
|
+
: undefined,
|
|
489
|
+
});
|
|
490
|
+
activeCArrayVars.add(varName);
|
|
491
|
+
commentsAssigned = true;
|
|
492
|
+
}
|
|
433
493
|
if (result.halOps && result.halOps.length > 0) {
|
|
434
494
|
const sideEffectOps = isHalOpReturn ? result.halOps.slice(0, -1) : result.halOps;
|
|
435
495
|
const halStmts = sideEffectOps.map(op => ({
|
|
@@ -474,6 +534,19 @@ export function variableStatementToIR(statement, fileName, sourceText, diagnosti
|
|
|
474
534
|
}
|
|
475
535
|
}
|
|
476
536
|
}
|
|
537
|
+
// For Http factory calls (const req = Http.get(url)), record the
|
|
538
|
+
// HTTP verb + URL so req.send() can resolve this._method/this._url.
|
|
539
|
+
if (result.returnClassName === "HttpRequest") {
|
|
540
|
+
const verb = httpFactoryVerb(init.expression.name.text);
|
|
541
|
+
if (verb)
|
|
542
|
+
fieldValues.set("_method", verb);
|
|
543
|
+
const urlArg = init.arguments?.[0];
|
|
544
|
+
if (urlArg) {
|
|
545
|
+
const urlText = httpUrlArgText(urlArg);
|
|
546
|
+
if (urlText)
|
|
547
|
+
fieldValues.set("_url", urlText);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
477
550
|
halInstances.set(varName, {
|
|
478
551
|
className: result.returnClassName,
|
|
479
552
|
fieldValues,
|
|
@@ -527,24 +600,18 @@ export function variableStatementToIR(statement, fileName, sourceText, diagnosti
|
|
|
527
600
|
if (/\b\d+\.\d+\b/.test(result.returnValue)) {
|
|
528
601
|
registerFloatVariable(varName);
|
|
529
602
|
}
|
|
603
|
+
// __TYPED_ARRAY__ returns are handled above (declared BEFORE the
|
|
604
|
+
// fill ops). Everything else is a scalar/value return captured
|
|
605
|
+
// after the side-effect ops.
|
|
530
606
|
const isTypedArray = result.returnValue.startsWith("__TYPED_ARRAY__:");
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
trailingComments: [],
|
|
540
|
-
name: varName,
|
|
541
|
-
storage,
|
|
542
|
-
cppType: `${elementType}[${size}]`,
|
|
543
|
-
initializer: undefined,
|
|
544
|
-
});
|
|
545
|
-
activeCArrayVars.add(varName);
|
|
546
|
-
}
|
|
547
|
-
else {
|
|
607
|
+
// A factory returning a compile-time HAL instance (e.g.
|
|
608
|
+
// `const req = Http.get(url)` → returnValue `new HttpRequest(...)`)
|
|
609
|
+
// is fully tracked via halInstances — every later method call on
|
|
610
|
+
// the variable resolves at compile time. Emitting the raw
|
|
611
|
+
// `new HttpRequest(...)` would reference a class that doesn't
|
|
612
|
+
// exist in the C++ output.
|
|
613
|
+
const isHalInstanceReturn = !!result.returnClassName && /^new\s/.test(result.returnValue.trim());
|
|
614
|
+
if (!isTypedArray && !isHalInstanceReturn) {
|
|
548
615
|
lowered.push({
|
|
549
616
|
kind: "var_decl",
|
|
550
617
|
sourceSpan: makeSourceSpan(declaration, fileName, sourceText),
|
|
@@ -39,6 +39,7 @@ export function validateTryCatch(program, boardConstants, strategy) {
|
|
|
39
39
|
hint: `function readSensor(): number | null {\n const data = sensor.read();\n if (!data) return null; // error path\n return data.value; // success path\n}`,
|
|
40
40
|
line: stmt.sourceSpan?.startLine,
|
|
41
41
|
column: stmt.sourceSpan?.startColumn,
|
|
42
|
+
filePath: stmt.sourceSpan?.filePath,
|
|
42
43
|
source: 'try-catch-validation',
|
|
43
44
|
});
|
|
44
45
|
// Still recurse into nested blocks to find other try/catch
|
|
@@ -57,6 +58,7 @@ export function validateTryCatch(program, boardConstants, strategy) {
|
|
|
57
58
|
hint: `return null; // or return an error code`,
|
|
58
59
|
line: stmt.sourceSpan?.startLine,
|
|
59
60
|
column: stmt.sourceSpan?.startColumn,
|
|
61
|
+
filePath: stmt.sourceSpan?.filePath,
|
|
60
62
|
source: 'try-catch-validation',
|
|
61
63
|
});
|
|
62
64
|
return;
|
|
@@ -33,9 +33,9 @@ const PIN_INTERFACE_TYPE_NAMES = new Set([
|
|
|
33
33
|
"PinMode", "InterruptMode",
|
|
34
34
|
]);
|
|
35
35
|
const BUS_INTERFACE_TYPE_NAMES = new Set([
|
|
36
|
-
"II2CBus", "ISPIBus", "ISerialPort",
|
|
36
|
+
"II2CBus", "ISPIBus", "ISerialPort",
|
|
37
37
|
"I2CConfig", "SPIConfig", "UARTConfig",
|
|
38
|
-
"I2CAddress", "
|
|
38
|
+
"I2CAddress", "SPITransferOptions",
|
|
39
39
|
]);
|
|
40
40
|
const STRATEGY_TYPE_NAMES = new Set([
|
|
41
41
|
"NativeStrategy", "ArduinoStrategy", "BoardStrategy",
|
|
@@ -130,7 +130,7 @@ function checkSPIFrequency(value) {
|
|
|
130
130
|
* baud rate used as SPI freq, etc.). Only literal numbers are checked —
|
|
131
131
|
* expressions/variables are opaque to this validator.
|
|
132
132
|
*/
|
|
133
|
-
function checkConfigValue(kind, rawValue, diagnostics) {
|
|
133
|
+
function checkConfigValue(kind, rawValue, filePath, diagnostics) {
|
|
134
134
|
// HAL ops carry resolved numeric values as `number` when the source arg was
|
|
135
135
|
// a literal, or as `string` expression text otherwise. Only check numbers.
|
|
136
136
|
const value = typeof rawValue === 'number' ? rawValue
|
|
@@ -146,6 +146,7 @@ function checkConfigValue(kind, rawValue, diagnostics) {
|
|
|
146
146
|
severity: 'warning',
|
|
147
147
|
message,
|
|
148
148
|
code: 'unit-suspicion',
|
|
149
|
+
filePath,
|
|
149
150
|
source: 'unit-suspicion-validation',
|
|
150
151
|
});
|
|
151
152
|
}
|
|
@@ -160,15 +161,16 @@ function scanStatement(stmt, diagnostics) {
|
|
|
160
161
|
if (!stmt || typeof stmt !== 'object')
|
|
161
162
|
return;
|
|
162
163
|
const s = stmt;
|
|
164
|
+
const filePath = s.sourceSpan?.filePath;
|
|
163
165
|
// HAL-op statements carry structured operations with resolved values.
|
|
164
166
|
if (stmt.kind === 'hal-op' && s.operation) {
|
|
165
167
|
const op = s.operation;
|
|
166
168
|
switch (op.operation) {
|
|
167
169
|
case 'i2c.set_clock':
|
|
168
|
-
checkConfigValue('i2c', op.hz, diagnostics);
|
|
170
|
+
checkConfigValue('i2c', op.hz, filePath, diagnostics);
|
|
169
171
|
break;
|
|
170
172
|
case 'uart.begin':
|
|
171
|
-
checkConfigValue('baud', op.baud, diagnostics);
|
|
173
|
+
checkConfigValue('baud', op.baud, filePath, diagnostics);
|
|
172
174
|
break;
|
|
173
175
|
// SPI frequency flows through SPISettings construction text, which the
|
|
174
176
|
// HAL op carries as a string — the numeric extraction in
|
|
@@ -178,7 +180,7 @@ function scanStatement(stmt, diagnostics) {
|
|
|
178
180
|
// SPISettings({freq}, ...) — try to extract the leading frequency.
|
|
179
181
|
const m = op.settings.match(/^\s*(\d+)/);
|
|
180
182
|
if (m)
|
|
181
|
-
checkConfigValue('spi', parseInt(m[1], 10), diagnostics);
|
|
183
|
+
checkConfigValue('spi', parseInt(m[1], 10), filePath, diagnostics);
|
|
182
184
|
}
|
|
183
185
|
break;
|
|
184
186
|
}
|
|
@@ -188,18 +190,18 @@ function scanStatement(stmt, diagnostics) {
|
|
|
188
190
|
if (stmt.kind === 'call' && typeof s.callee === 'string' && Array.isArray(s.args)) {
|
|
189
191
|
const method = s.callee.split('.').pop() ?? s.callee;
|
|
190
192
|
if (method === 'setClock' && s.args.length >= 2) {
|
|
191
|
-
checkConfigValue('i2c', extractNumericValue(s.args[1]), diagnostics);
|
|
193
|
+
checkConfigValue('i2c', extractNumericValue(s.args[1]), filePath, diagnostics);
|
|
192
194
|
}
|
|
193
195
|
else if (method === 'setBaudRate' || method === 'begin') {
|
|
194
196
|
// Serial.begin(baud) / setBaudRate(baud) — last numeric arg is the baud.
|
|
195
197
|
for (const arg of s.args) {
|
|
196
198
|
const v = extractNumericValue(arg);
|
|
197
199
|
if (v !== undefined)
|
|
198
|
-
checkConfigValue('baud', v, diagnostics);
|
|
200
|
+
checkConfigValue('baud', v, filePath, diagnostics);
|
|
199
201
|
}
|
|
200
202
|
}
|
|
201
203
|
else if (method === 'setFrequency' && s.args.length >= 2) {
|
|
202
|
-
checkConfigValue('spi', extractNumericValue(s.args[1]), diagnostics);
|
|
204
|
+
checkConfigValue('spi', extractNumericValue(s.args[1]), filePath, diagnostics);
|
|
203
205
|
}
|
|
204
206
|
}
|
|
205
207
|
// Recurse into nested statements
|
|
@@ -18,18 +18,19 @@ import { validateBlockingDelayInLoop } from "./timing-validation.js";
|
|
|
18
18
|
import { validateUnitSuspicion } from "./unit-suspicion-validation.js";
|
|
19
19
|
import { validateOwnership } from "./ownership-analysis.js";
|
|
20
20
|
import { validatePinCapabilities } from "./pin-capability-validation.js";
|
|
21
|
+
import { validateNetworkUsage } from "./network-validation.js";
|
|
21
22
|
export function runProgramValidations(program, strategy) {
|
|
22
23
|
const resolvedStrategy = strategy ?? (hasLoadedFramework() ? getLoadedFramework().strategy : resolveStrategy('generic'));
|
|
23
24
|
const diagnostics = [];
|
|
24
25
|
const peripheralUsage = program.peripheralUsage ?? createEmptyPeripheralUsage();
|
|
25
26
|
diagnostics.push(...validatePinCapabilities(program));
|
|
26
|
-
diagnostics.push(...validatePeripherals(peripheralUsage, program.boardConstants));
|
|
27
|
-
diagnostics.push(...validateUnsafePins(peripheralUsage, program.boardConstants));
|
|
27
|
+
diagnostics.push(...validatePeripherals(peripheralUsage, program.boardConstants, program.fileName));
|
|
28
|
+
diagnostics.push(...validateUnsafePins(peripheralUsage, program.boardConstants, program.fileName));
|
|
28
29
|
diagnostics.push(...analyzeResources(program, resolvedStrategy));
|
|
29
|
-
diagnostics.push(...validatePinAliasConflicts(peripheralUsage, program.boardConstants));
|
|
30
|
-
diagnostics.push(...validatePWMTimerSharing(peripheralUsage, program.boardConstants));
|
|
31
|
-
diagnostics.push(...validateTimer0PWMTimingConflict(peripheralUsage, program.boardConstants));
|
|
32
|
-
diagnostics.push(...validatePulldownSupport(peripheralUsage, program.boardConstants));
|
|
30
|
+
diagnostics.push(...validatePinAliasConflicts(peripheralUsage, program.boardConstants, program.fileName));
|
|
31
|
+
diagnostics.push(...validatePWMTimerSharing(peripheralUsage, program.boardConstants, program.fileName));
|
|
32
|
+
diagnostics.push(...validateTimer0PWMTimingConflict(peripheralUsage, program.boardConstants, program.fileName));
|
|
33
|
+
diagnostics.push(...validatePulldownSupport(peripheralUsage, program.boardConstants, program.fileName));
|
|
33
34
|
diagnostics.push(...analyzeInterruptSafety(program, peripheralUsage));
|
|
34
35
|
inferVolatileForIsrSharedVars(program, diagnostics);
|
|
35
36
|
detectReentrancyRisk(program, diagnostics);
|
|
@@ -40,6 +41,7 @@ export function runProgramValidations(program, strategy) {
|
|
|
40
41
|
diagnostics.push(...validateOwnership(program));
|
|
41
42
|
diagnostics.push(...validateTryCatch(program, program.boardConstants, resolvedStrategy));
|
|
42
43
|
diagnostics.push(...validateMemoryBudget(program, program.boardConstants));
|
|
43
|
-
diagnostics.push(...validateBlockingDelayInLoop(program));
|
|
44
|
+
diagnostics.push(...validateBlockingDelayInLoop(program, resolvedStrategy));
|
|
45
|
+
diagnostics.push(...validateNetworkUsage(program));
|
|
44
46
|
return diagnostics;
|
|
45
47
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* C-header to TypeScript declaration generator.
|
|
3
|
+
*
|
|
4
|
+
* Complements cpp-to-decl.ts (which is class-focused). ESP-IDF components
|
|
5
|
+
* are mostly C: free functions, opaque handles, typedef'd enums and structs.
|
|
6
|
+
*
|
|
7
|
+
* EMISSION POLICY — names match the C header 1-to-1.
|
|
8
|
+
*
|
|
9
|
+
* esp_err_t esp_wifi_init(const wifi_config_t *config);
|
|
10
|
+
*
|
|
11
|
+
* becomes
|
|
12
|
+
*
|
|
13
|
+
* export declare function esp_wifi_init(config: number): esp_err_t;
|
|
14
|
+
*
|
|
15
|
+
* not `esp_wifi.init(...)`. ESP-IDF examples call `esp_wifi_init`, never
|
|
16
|
+
* `esp_wifi.init`; the dotted form has no C++ representation (there is no
|
|
17
|
+
* `esp_wifi` object or namespace in the real header) and would not link.
|
|
18
|
+
* Mirroring the C names verbatim means the transpiler lowers TS calls
|
|
19
|
+
* directly to valid C with zero translation.
|
|
20
|
+
*
|
|
21
|
+
* Spec: docs/superpowers/specs/2026-07-19-demo-wifi-design.md
|
|
22
|
+
*/
|
|
23
|
+
/**
|
|
24
|
+
* Top-level entry: read a header file, write `<header>.d.ts` alongside it.
|
|
25
|
+
* Returns the output path, or null if no declarations could be extracted.
|
|
26
|
+
*/
|
|
27
|
+
export declare function generateCDecl(filePath: string, outputPath?: string): string | null;
|