@typecad/cuttlefish 1.0.0-alpha.6 → 1.0.0-alpha.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/config.d.ts +32 -0
- package/dist/api/index.d.ts +1 -1
- package/dist/api/schema/types.d.ts +15 -0
- package/dist/api/shared/async-runtime-static.d.ts +12 -1
- package/dist/api/shared/async-runtime-static.js +69 -24
- package/dist/api/shared/async-symbol-detector.d.ts +11 -0
- package/dist/api/shared/async-symbol-detector.js +140 -0
- package/dist/api/shared/async-types.d.ts +24 -0
- package/dist/api/shared/coop-scheduler.d.ts +60 -0
- package/dist/api/shared/coop-scheduler.js +149 -0
- package/dist/api/shared/display-adapter.d.ts +4 -3
- package/dist/api/shared/display-adapter.js +15 -118
- package/dist/api/shared/display-adapters/sdl.js +38 -31
- package/dist/api/shared/display-profile.d.ts +47 -25
- package/dist/api/shared/display-profile.js +31 -164
- package/dist/api/shared/framework-manifest-registry.d.ts +9 -0
- package/dist/api/shared/framework-manifest-registry.js +27 -0
- package/dist/api/shared/framework-manifest.d.ts +581 -0
- package/dist/api/shared/framework-manifest.js +182 -0
- package/dist/api/shared/glcdfont.d.ts +12 -0
- package/dist/api/shared/glcdfont.js +124 -0
- package/dist/api/shared/graphics-strategy.d.ts +37 -0
- package/dist/api/shared/hal-op-ir.d.ts +755 -1
- package/dist/api/shared/hal-op-ir.js +129 -1
- package/dist/api/shared/index.d.ts +20 -2
- package/dist/api/shared/index.js +25 -1
- package/dist/api/shared/ir-core.d.ts +4 -0
- package/dist/api/shared/ir-declarations.d.ts +6 -0
- package/dist/api/shared/native-display-op-resolver.d.ts +10 -0
- package/dist/api/shared/native-display-op-resolver.js +64 -0
- package/dist/api/shared/platform-strategy.d.ts +113 -2
- package/dist/api/shared/polyfill-helper-registry.js +18 -1
- package/dist/api/shared/promise-runtime.d.ts +1 -1
- package/dist/api/shared/promise-runtime.js +95 -13
- package/dist/api/shared/toolchain-types.d.ts +17 -0
- package/dist/api/shared/validate-framework-manifest.d.ts +28 -0
- package/dist/api/shared/validate-framework-manifest.js +494 -0
- package/dist/api/shared/worker-runtime-polyfill.d.ts +22 -0
- package/dist/api/shared/worker-runtime-polyfill.js +34 -0
- package/dist/api/shared/worker-runtime.d.ts +69 -0
- package/dist/api/shared/worker-runtime.js +163 -0
- package/dist/cli.js +186 -82
- package/dist/config-loader.d.ts +7 -2
- package/dist/config-loader.js +23 -3
- package/dist/config-schema.d.ts +112 -70
- package/dist/config-schema.js +14 -0
- package/dist/contract/board-generator.d.ts +17 -0
- package/dist/contract/board-generator.js +57 -0
- package/dist/contract/contract-parser.d.ts +217 -0
- package/dist/contract/contract-parser.js +224 -0
- package/dist/contract/index.d.ts +22 -0
- package/dist/contract/index.js +68 -0
- package/dist/create/board-codegen.js +4 -4
- package/dist/create/board-generators.js +4 -5
- package/dist/create/board-spec.d.ts +72 -75
- package/dist/create/board-spec.js +0 -1
- package/dist/create/init-scaffold.d.ts +9 -2
- package/dist/create/init-scaffold.js +0 -14
- package/dist/create/init-templates.d.ts +2 -0
- package/dist/create/init-templates.js +74 -8
- package/dist/create/init-wizard.js +31 -7
- package/dist/debug/preprocessor.js +178 -39
- package/dist/debug/types.d.ts +23 -0
- package/dist/diagnostics/mermaid-builder.d.ts +1 -1
- package/dist/diagnostics/mermaid-builder.js +34 -24
- package/dist/emit/compliance/arxml-writer.d.ts +11 -0
- package/dist/emit/compliance/arxml-writer.js +34 -0
- package/dist/emit/compliance/compliance-context.d.ts +57 -0
- package/dist/emit/compliance/compliance-context.js +113 -0
- package/dist/emit/compliance/deviation-ledger.d.ts +27 -0
- package/dist/emit/compliance/deviation-ledger.js +47 -0
- package/dist/emit/compliance/deviation-writer.d.ts +30 -0
- package/dist/emit/compliance/deviation-writer.js +37 -0
- package/dist/emit/compliance/index.d.ts +7 -0
- package/dist/emit/compliance/index.js +6 -0
- package/dist/emit/compliance/rule-engine.d.ts +13 -0
- package/dist/emit/compliance/rule-engine.js +101 -0
- package/dist/emit/compliance/rules.d.ts +16 -0
- package/dist/emit/compliance/rules.js +191 -0
- package/dist/emit/compliance/types.d.ts +59 -0
- package/dist/emit/compliance/types.js +8 -0
- package/dist/emit/cpp-emitter.js +4 -3
- package/dist/emit/emitters/class-emitter.js +6 -1
- package/dist/emit/emitters/emitter-context.d.ts +28 -1
- package/dist/emit/emitters/function-emitter-impl.js +153 -53
- package/dist/emit/emitters/line-appender.js +19 -0
- package/dist/emit/emitters/line-marker.d.ts +38 -0
- package/dist/emit/emitters/line-marker.js +39 -0
- package/dist/emit/emitters/output-finalizer.d.ts +6 -0
- package/dist/emit/emitters/output-finalizer.js +101 -12
- package/dist/emit/emitters/setup.d.ts +17 -0
- package/dist/emit/emitters/setup.js +257 -36
- package/dist/emit/emitters/top-level-prep.js +8 -0
- package/dist/emit/emitters/type-decl-emitter.js +28 -3
- package/dist/emit/emitters/ui-emitter.js +33 -9
- package/dist/emit/expression-renderer.d.ts +1 -1
- package/dist/emit/expression-renderer.js +91 -6
- package/dist/emit/route-hal-op.js +18 -5
- package/dist/emit/snprintf-helpers.js +15 -4
- package/dist/emit/statement-renderer.d.ts +10 -0
- package/dist/emit/statement-renderer.js +34 -5
- package/dist/emit/utils/async-state-machine.js +221 -125
- package/dist/emit/utils/hal-op-cpp-type.d.ts +6 -0
- package/dist/emit/utils/hal-op-cpp-type.js +40 -0
- package/dist/framework-package.js +2 -0
- package/dist/framework-registry.d.ts +17 -0
- package/dist/ir/adc-range-validation.js +14 -1
- package/dist/ir/build-ir-state.d.ts +1 -0
- package/dist/ir/build-ir-state.js +14 -0
- package/dist/ir/build-ir.js +9 -5
- package/dist/ir/call-graph.js +16 -0
- package/dist/ir/expression-to-ir.js +164 -0
- package/dist/ir/feature-registry.js +7 -25
- package/dist/ir/function-builder.js +22 -0
- package/dist/ir/hal/hal-emitter.d.ts +5 -2
- package/dist/ir/hal/hal-emitter.js +50 -18
- package/dist/ir/hal/hal-parser.d.ts +6 -0
- package/dist/ir/hal/hal-parser.js +81 -0
- package/dist/ir/hal/hal-plugins.js +723 -1
- package/dist/ir/identifier-collector.js +35 -0
- package/dist/ir/interrupt-analysis.d.ts +5 -1
- package/dist/ir/interrupt-analysis.js +8 -16
- package/dist/ir/network-validation.d.ts +4 -0
- package/dist/ir/network-validation.js +184 -0
- package/dist/ir/ownership-analysis.js +20 -1
- package/dist/ir/pin-capability-validation.js +37 -0
- package/dist/ir/pin-mode-validation.d.ts +2 -2
- package/dist/ir/pin-mode-validation.js +49 -18
- package/dist/ir/program-analysis.d.ts +65 -0
- package/dist/ir/program-analysis.js +318 -2
- package/dist/ir/render-expr.js +11 -0
- package/dist/ir/timing-validation.d.ts +6 -1
- package/dist/ir/timing-validation.js +60 -13
- package/dist/ir/transformers/call-statement.js +105 -0
- package/dist/ir/transformers/expressions.js +62 -0
- package/dist/ir/transformers/hal-call-resolver.js +21 -0
- package/dist/ir/transformers/hal-emit-helpers.js +1 -1
- package/dist/ir/transformers/namespace-methods.js +17 -12
- package/dist/ir/transformers/ui-reactive.js +2 -2
- package/dist/ir/transformers/variables.js +92 -3
- package/dist/ir/type-resolution.js +18 -0
- package/dist/ir/ui-element-auto-wire.js +7 -5
- package/dist/ir/utils/map-statements.d.ts +4 -0
- package/dist/ir/utils/map-statements.js +79 -0
- package/dist/ir/validation-orchestrator.js +9 -2
- package/dist/ir/worker-analysis.d.ts +10 -0
- package/dist/ir/worker-analysis.js +261 -0
- package/dist/libdef/c-to-decl.d.ts +27 -0
- package/dist/libdef/c-to-decl.js +397 -0
- package/dist/libdef/component-decls.d.ts +2 -0
- package/dist/libdef/component-decls.js +6 -0
- package/dist/libdef/component-discovery.d.ts +43 -0
- package/dist/libdef/component-discovery.js +83 -0
- package/dist/libdef/cpp-to-decl.d.ts +9 -0
- package/dist/libdef/cpp-to-decl.js +72 -0
- package/dist/libdef/registry.js +5 -2
- package/dist/lint-cache.d.ts +59 -0
- package/dist/lint-cache.js +257 -0
- package/dist/orchestrator/graph-builder.js +14 -9
- package/dist/platform/coop-scheduler-runtime.d.ts +19 -0
- package/dist/platform/coop-scheduler-runtime.js +41 -0
- package/dist/platform/generic-strategy.d.ts +15 -3
- package/dist/platform/generic-strategy.js +49 -4
- package/dist/safety/safety-bridge.d.ts +11 -0
- package/dist/safety/safety-bridge.js +48 -0
- package/dist/safety/sidecar-bridge.d.ts +5 -0
- package/dist/safety/sidecar-bridge.js +22 -0
- package/dist/safety-hook.d.ts +79 -0
- package/dist/safety-hook.js +35 -0
- package/dist/testing.d.ts +7 -1
- package/dist/testing.js +8 -1
- package/dist/transpile.d.ts +3 -0
- package/dist/transpile.js +175 -41
- package/dist/types.d.ts +13 -2
- package/dist/ui-hook.d.ts +17 -3
- package/dist/utils/cli.js +78 -4
- package/dist/utils/fs.d.ts +13 -0
- package/dist/utils/fs.js +50 -0
- package/package.json +24 -5
- package/dist/api/shared/display-adapters/eink-mono.d.ts +0 -2
- package/dist/api/shared/display-adapters/eink-mono.js +0 -53
- package/dist/api/shared/display-adapters/ssd1309.d.ts +0 -2
- package/dist/api/shared/display-adapters/ssd1309.js +0 -136
- package/dist/api/shared/display-adapters/st7796.d.ts +0 -2
- package/dist/api/shared/display-adapters/st7796.js +0 -132
- package/dist/licenses.d.ts +0 -185
- package/dist/licenses.js +0 -963
|
@@ -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,54 @@ 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
|
+
// Worker offload: `await worker.submit(...)` rewrites to submit + poll
|
|
431
|
+
// worker.done — mirrors `await http.send(...)`. The start op is worker.submit
|
|
432
|
+
// (emitted by the previous state segment); the poll predicate is worker.done.
|
|
433
|
+
"worker.submit",
|
|
434
|
+
]);
|
|
435
|
+
/**
|
|
436
|
+
* Rewrite an awaited hal-op statement (or a block whose LAST statement is
|
|
437
|
+
* one — chained HAL calls resolve to blocks of hal-ops) into an awaited
|
|
438
|
+
* `__WIFI_WAIT__` / `__HTTP_WAIT__` / `__HAL_WAIT__` marker call carrying the
|
|
439
|
+
* original op as a hal-expr argument. Returns undefined when the statement is
|
|
440
|
+
* not an awaitable HAL op.
|
|
441
|
+
*/
|
|
442
|
+
function awaitedNetMarker(stmt) {
|
|
443
|
+
if (!stmt)
|
|
444
|
+
return undefined;
|
|
445
|
+
if (stmt.kind === "hal-op" && AWAITABLE_HAL_OPS.has(stmt.operation.operation)) {
|
|
446
|
+
const opName = stmt.operation.operation;
|
|
447
|
+
const callee = opName.startsWith("http.") ? "__HTTP_WAIT__"
|
|
448
|
+
: opName.startsWith("wifi.") ? "__WIFI_WAIT__"
|
|
449
|
+
: opName.startsWith("ble.") ? "__BLE_WAIT__"
|
|
450
|
+
: "__HAL_WAIT__";
|
|
451
|
+
return {
|
|
452
|
+
kind: "call",
|
|
453
|
+
sourceSpan: stmt.sourceSpan,
|
|
454
|
+
leadingComments: stmt.leadingComments,
|
|
455
|
+
trailingComments: stmt.trailingComments,
|
|
456
|
+
callee,
|
|
457
|
+
args: [{ kind: "hal-expr", operation: stmt.operation }],
|
|
458
|
+
isAwaited: true,
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
if (stmt.kind === "block" && stmt.body.length > 0) {
|
|
462
|
+
const last = awaitedNetMarker(stmt.body[stmt.body.length - 1]);
|
|
463
|
+
if (last) {
|
|
464
|
+
return { ...stmt, body: [...stmt.body.slice(0, -1), last] };
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
return undefined;
|
|
468
|
+
}
|
|
@@ -270,5 +270,26 @@ export function tryResolveHALExpression(call, sourceText, diagnostics, pointerVa
|
|
|
270
270
|
}
|
|
271
271
|
}
|
|
272
272
|
}
|
|
273
|
+
// Namespace method fallbacks (Pulse, Shift, Random) in expression context.
|
|
274
|
+
// Value-returning namespace methods (e.g. Random.upTo/between/int) lower to
|
|
275
|
+
// HAL ops; surface the last op as a hal-expr so the strategy lowers it to the
|
|
276
|
+
// platform PRNG (Arduino random(), ESP-IDF esp_random()). Without this, an
|
|
277
|
+
// inline `if (Random.upTo(5) > 2)` would fall through to a 0 placeholder.
|
|
278
|
+
if (ts.isIdentifier(receiver)) {
|
|
279
|
+
const ns = receiver.text;
|
|
280
|
+
const nsResult = resolveNamespaceMethodCall(ns, method, argIRs);
|
|
281
|
+
if (nsResult) {
|
|
282
|
+
if (nsResult.halOps.length > 0) {
|
|
283
|
+
const lastOp = nsResult.halOps[nsResult.halOps.length - 1];
|
|
284
|
+
return {
|
|
285
|
+
ir: { kind: "hal-expr", operation: lastOp },
|
|
286
|
+
sideEffects: nsResult.emitLines,
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
if (nsResult.returnValue && nsResult.returnValue !== "__hal_op_return__") {
|
|
290
|
+
return { ir: { kind: "raw", value: nsResult.returnValue }, sideEffects: nsResult.emitLines };
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
273
294
|
return null;
|
|
274
295
|
}
|
|
@@ -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
|
}
|
|
@@ -81,29 +81,34 @@ export function resolveNamespaceMethodCall(ns, method, argIRs) {
|
|
|
81
81
|
}
|
|
82
82
|
}
|
|
83
83
|
if (ns === "Random") {
|
|
84
|
+
// Random.* lowers to the random.* HAL op family, which frameworks lower to
|
|
85
|
+
// their platform PRNG (Arduino random()/randomSeed(), ESP-IDF esp_random()).
|
|
86
|
+
// Previously these emitted bare `random()`/`randomSeed()` calls, which only
|
|
87
|
+
// resolve to symbols on Arduino-core frameworks — on ESP-IDF they were
|
|
88
|
+
// undefined and failed at C++ link time.
|
|
84
89
|
if (method === "seed") {
|
|
85
|
-
return { emitLines: [
|
|
90
|
+
return { emitLines: [], halOps: [{ operation: "random.seed", seed: argText(0) }] };
|
|
86
91
|
}
|
|
87
92
|
if (method === "number") {
|
|
88
93
|
const min = argText(0);
|
|
89
94
|
const max = argText(1);
|
|
90
|
-
|
|
91
|
-
emitLines: [],
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
};
|
|
95
|
+
if (max) {
|
|
96
|
+
return { emitLines: [], halOps: [{ operation: "random.range", min, max }], returnValue: "__hal_op_return__" };
|
|
97
|
+
}
|
|
98
|
+
// Single-arg form: Random.number(max) → [0, max-1]
|
|
99
|
+
return { emitLines: [], halOps: [{ operation: "random.range", min: "0", max: min }], returnValue: "__hal_op_return__" };
|
|
95
100
|
}
|
|
96
|
-
//
|
|
101
|
+
// Random.upTo(max) → [0, max-1]
|
|
97
102
|
if (method === "upTo") {
|
|
98
|
-
return { emitLines: [], halOps: [
|
|
103
|
+
return { emitLines: [], halOps: [{ operation: "random.range", min: "0", max: argText(0) }], returnValue: "__hal_op_return__" };
|
|
99
104
|
}
|
|
100
|
-
// Random.between(min, max) →
|
|
105
|
+
// Random.between(min, max) → [min, max-1]
|
|
101
106
|
if (method === "between") {
|
|
102
|
-
return { emitLines: [], halOps: [
|
|
107
|
+
return { emitLines: [], halOps: [{ operation: "random.range", min: argText(0), max: argText(1) }], returnValue: "__hal_op_return__" };
|
|
103
108
|
}
|
|
104
|
-
// Random.int() →
|
|
109
|
+
// Random.int() → non-negative 31-bit integer
|
|
105
110
|
if (method === "int") {
|
|
106
|
-
return { emitLines: [], halOps: [], returnValue:
|
|
111
|
+
return { emitLines: [], halOps: [{ operation: "random.int" }], returnValue: "__hal_op_return__" };
|
|
107
112
|
}
|
|
108
113
|
}
|
|
109
114
|
return null;
|
|
@@ -30,9 +30,9 @@ function propEnum(property) {
|
|
|
30
30
|
export function emitBindingEntry(spec) {
|
|
31
31
|
// Text bindings wire to textFn (void fill-style); color bindings wire to fn.
|
|
32
32
|
if (spec.property === "text") {
|
|
33
|
-
return ` { .node=${spec.nodeIndex}, .prop=${propEnum(spec.property)}, .fn=nullptr, .textFn=${spec.fnName} },`;
|
|
33
|
+
return ` { .node=${spec.nodeIndex}, .prop=${propEnum(spec.property)}, .fn=nullptr, .textFn=${spec.fnName}, .lastValue=0, .initialized=0 },`;
|
|
34
34
|
}
|
|
35
|
-
return ` { .node=${spec.nodeIndex}, .prop=${propEnum(spec.property)}, .fn=${spec.fnName}, .textFn=nullptr },`;
|
|
35
|
+
return ` { .node=${spec.nodeIndex}, .prop=${propEnum(spec.property)}, .fn=${spec.fnName}, .textFn=nullptr, .lastValue=0, .initialized=0 },`;
|
|
36
36
|
}
|
|
37
37
|
/** Emit a full binding table from a list of specs. */
|
|
38
38
|
export function emitBindingTable(specs) {
|
|
@@ -7,9 +7,11 @@ 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";
|
|
14
|
+
import { hasSafetyHook, requireSafetyHook } from "../../safety-hook.js";
|
|
13
15
|
function replaceHalReadBufferPlaceholder(op, varName) {
|
|
14
16
|
if (op.operation === "i2c.read_buffer" && op.buffer === "__HAL_READ_BUF__") {
|
|
15
17
|
return { ...op, buffer: varName };
|
|
@@ -374,6 +376,56 @@ export function variableStatementToIR(statement, fileName, sourceText, diagnosti
|
|
|
374
376
|
localVariableTypes.set(varName, cppType);
|
|
375
377
|
continue;
|
|
376
378
|
}
|
|
379
|
+
// ── safe.<method>(...) — @typecad/safety call as initializer.
|
|
380
|
+
// Handles expression-position safe.* calls like `const r = safe.read(pin)`.
|
|
381
|
+
// The statement-position form (safe.pinMode(...)) is handled in the
|
|
382
|
+
// call-statement transformer. Both produce hal-op IR nodes that
|
|
383
|
+
// routeHALOp() later dispatches to the safety hook.
|
|
384
|
+
if (hasSafetyHook() &&
|
|
385
|
+
ts.isCallExpression(declaration.initializer) &&
|
|
386
|
+
ts.isPropertyAccessExpression(declaration.initializer.expression) &&
|
|
387
|
+
ts.isIdentifier(declaration.initializer.expression.expression) &&
|
|
388
|
+
declaration.initializer.expression.expression.text === "safe") {
|
|
389
|
+
const method = declaration.initializer.expression.name.text;
|
|
390
|
+
const argValues = declaration.initializer.arguments.map((a) => {
|
|
391
|
+
if (ts.isNumericLiteral(a))
|
|
392
|
+
return Number(a.text);
|
|
393
|
+
if (ts.isStringLiteral(a))
|
|
394
|
+
return a.text;
|
|
395
|
+
if (a.kind === ts.SyntaxKind.TrueKeyword)
|
|
396
|
+
return true;
|
|
397
|
+
if (a.kind === ts.SyntaxKind.FalseKeyword)
|
|
398
|
+
return false;
|
|
399
|
+
if (ts.isIdentifier(a)) {
|
|
400
|
+
const name = a.text;
|
|
401
|
+
if (name === "INPUT")
|
|
402
|
+
return 0;
|
|
403
|
+
if (name === "OUTPUT")
|
|
404
|
+
return 1;
|
|
405
|
+
if (name === "INPUT_PULLUP")
|
|
406
|
+
return 2;
|
|
407
|
+
return name;
|
|
408
|
+
}
|
|
409
|
+
return undefined;
|
|
410
|
+
});
|
|
411
|
+
const op = requireSafetyHook().resolveSemanticCall?.(`safe.${method}`, argValues);
|
|
412
|
+
if (op) {
|
|
413
|
+
// Emit a var_decl initialized by a hal-expr carrying the safety op.
|
|
414
|
+
// routeHALOp() resolves it to the C++ expression at emit time.
|
|
415
|
+
const initExpr = { kind: "hal-expr", operation: op };
|
|
416
|
+
lowered.push({
|
|
417
|
+
kind: "var_decl",
|
|
418
|
+
name: varName,
|
|
419
|
+
storage,
|
|
420
|
+
cppType: "auto",
|
|
421
|
+
initializer: initExpr,
|
|
422
|
+
sourceSpan: makeSourceSpan(declaration, fileName, sourceText),
|
|
423
|
+
});
|
|
424
|
+
// auto-deduced to SafeReadResult on the C++ side.
|
|
425
|
+
localVariableTypes.set(varName, "auto");
|
|
426
|
+
continue;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
377
429
|
if (ts.isNewExpression(declaration.initializer) && ts.isIdentifier(declaration.initializer.expression)) {
|
|
378
430
|
const className = declaration.initializer.expression.text;
|
|
379
431
|
const ctorArgs = declaration.initializer.arguments;
|
|
@@ -408,9 +460,26 @@ export function variableStatementToIR(statement, fileName, sourceText, diagnosti
|
|
|
408
460
|
}
|
|
409
461
|
}
|
|
410
462
|
}
|
|
463
|
+
// new HttpRequest(method, url) — positional ctor fields. The URL is
|
|
464
|
+
// stored as C++ expression text (quoted literal or identifier).
|
|
465
|
+
if (className === "HttpRequest" && ctorArgs && ctorArgs.length >= 2) {
|
|
466
|
+
const methodArg = ctorArgs[0];
|
|
467
|
+
if (ts.isStringLiteral(methodArg))
|
|
468
|
+
fieldValues.set("_method", methodArg.text.toUpperCase());
|
|
469
|
+
const urlText = httpUrlArgText(ctorArgs[1]);
|
|
470
|
+
if (urlText)
|
|
471
|
+
fieldValues.set("_url", urlText);
|
|
472
|
+
}
|
|
411
473
|
if (ctorArgs) {
|
|
474
|
+
// Resolve a Pin-identifier ctor arg to its _pin number, for any HAL
|
|
475
|
+
// class whose first constructor field is _pin (Pin itself, plus
|
|
476
|
+
// pin-bearing wrappers like RmtChannel). Mirrors the Pin branch
|
|
477
|
+
// above but keyed off the class's registered ctor field map so new
|
|
478
|
+
// pin-keyed classes work without a per-class branch here.
|
|
479
|
+
const ctorFieldMap = getHALCtorFieldMap(className);
|
|
480
|
+
const firstFieldIsPin = ctorFieldMap && Array.from(ctorFieldMap.keys())[0] === "_pin";
|
|
412
481
|
for (const arg of ctorArgs) {
|
|
413
|
-
if (ts.isIdentifier(arg) && className === "Pin") {
|
|
482
|
+
if (ts.isIdentifier(arg) && (className === "Pin" || firstFieldIsPin)) {
|
|
414
483
|
const existing = halInstances.get(arg.text);
|
|
415
484
|
if (existing && existing.fieldValues.has("_pin")) {
|
|
416
485
|
fieldValues.set("_pin", existing.fieldValues.get("_pin"));
|
|
@@ -516,6 +585,19 @@ export function variableStatementToIR(statement, fileName, sourceText, diagnosti
|
|
|
516
585
|
}
|
|
517
586
|
}
|
|
518
587
|
}
|
|
588
|
+
// For Http factory calls (const req = Http.get(url)), record the
|
|
589
|
+
// HTTP verb + URL so req.send() can resolve this._method/this._url.
|
|
590
|
+
if (result.returnClassName === "HttpRequest") {
|
|
591
|
+
const verb = httpFactoryVerb(init.expression.name.text);
|
|
592
|
+
if (verb)
|
|
593
|
+
fieldValues.set("_method", verb);
|
|
594
|
+
const urlArg = init.arguments?.[0];
|
|
595
|
+
if (urlArg) {
|
|
596
|
+
const urlText = httpUrlArgText(urlArg);
|
|
597
|
+
if (urlText)
|
|
598
|
+
fieldValues.set("_url", urlText);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
519
601
|
halInstances.set(varName, {
|
|
520
602
|
className: result.returnClassName,
|
|
521
603
|
fieldValues,
|
|
@@ -573,7 +655,14 @@ export function variableStatementToIR(statement, fileName, sourceText, diagnosti
|
|
|
573
655
|
// fill ops). Everything else is a scalar/value return captured
|
|
574
656
|
// after the side-effect ops.
|
|
575
657
|
const isTypedArray = result.returnValue.startsWith("__TYPED_ARRAY__:");
|
|
576
|
-
|
|
658
|
+
// A factory returning a compile-time HAL instance (e.g.
|
|
659
|
+
// `const req = Http.get(url)` → returnValue `new HttpRequest(...)`)
|
|
660
|
+
// is fully tracked via halInstances — every later method call on
|
|
661
|
+
// the variable resolves at compile time. Emitting the raw
|
|
662
|
+
// `new HttpRequest(...)` would reference a class that doesn't
|
|
663
|
+
// exist in the C++ output.
|
|
664
|
+
const isHalInstanceReturn = !!result.returnClassName && /^new\s/.test(result.returnValue.trim());
|
|
665
|
+
if (!isTypedArray && !isHalInstanceReturn) {
|
|
577
666
|
lowered.push({
|
|
578
667
|
kind: "var_decl",
|
|
579
668
|
sourceSpan: makeSourceSpan(declaration, fileName, sourceText),
|
|
@@ -48,6 +48,14 @@ const BOARD_CONSTANT_TYPE_NAMES = new Set([
|
|
|
48
48
|
const OWNERSHIP_WRAPPER_TYPE_NAMES = new Set([
|
|
49
49
|
"Owned", "Shared", "Mutable",
|
|
50
50
|
]);
|
|
51
|
+
/** Safety wrapper types: like ownership wrappers (phantom TS types that carry
|
|
52
|
+
* a type parameter), but instead of stripping the wrapper, the C++ keeps it
|
|
53
|
+
* as a template instantiation: SafeVariable<number> → SafeVariable<int32_t>.
|
|
54
|
+
* The C++ template definition is provided by the safety polyfill. */
|
|
55
|
+
const SAFE_WRAPPER_TYPE_NAMES = new Set([
|
|
56
|
+
"SafeVariable",
|
|
57
|
+
"SafeInt",
|
|
58
|
+
]);
|
|
51
59
|
export function resolveAliasedTypeNode(node, typeAliases, visited = new Set()) {
|
|
52
60
|
if (!node || !typeAliases) {
|
|
53
61
|
return node;
|
|
@@ -147,6 +155,16 @@ export function typeNodeToCppType(node, typeAliases, typeParametersInScope) {
|
|
|
147
155
|
const innerTypeNode = node.typeArguments?.[0];
|
|
148
156
|
return typeNodeToCppType(innerTypeNode, typeAliases, typeParametersInScope);
|
|
149
157
|
}
|
|
158
|
+
// SafeVariable<T> is a safety wrapper: unlike ownership wrappers (which
|
|
159
|
+
// strip the wrapper name entirely), SafeVariable keeps the wrapper name
|
|
160
|
+
// in the C++ type because the polyfill provides a template definition.
|
|
161
|
+
// SafeVariable<number> → SafeVariable<int> (the template is emitted by
|
|
162
|
+
// the safety polyfill's helperStructs).
|
|
163
|
+
if (SAFE_WRAPPER_TYPE_NAMES.has(wrapperName)) {
|
|
164
|
+
const innerTypeNode = node.typeArguments?.[0];
|
|
165
|
+
const innerCppType = typeNodeToCppType(innerTypeNode, typeAliases, typeParametersInScope);
|
|
166
|
+
return `${wrapperName}<${innerCppType}>`;
|
|
167
|
+
}
|
|
150
168
|
}
|
|
151
169
|
const resolvedNode = resolveAliasedTypeNode(node, typeAliases) ?? node;
|
|
152
170
|
// If the resolved node is an object literal type (e.g. type X = { a: number }),
|
|
@@ -81,7 +81,7 @@ function autoWireNode(treeName, node, nodeIndex) {
|
|
|
81
81
|
nodeIndex,
|
|
82
82
|
property: "text",
|
|
83
83
|
fnName: `__ui_bindtext_${nodeIndex}`,
|
|
84
|
-
cppBody: `snprintf(buf, size, "%s", ${sig});`,
|
|
84
|
+
cppBody: `snprintf(buf, static_cast<size_t>(size), "%s", ${sig});`,
|
|
85
85
|
});
|
|
86
86
|
// Write: keyboard commit → signal.set(text).
|
|
87
87
|
recordInputBinding({
|
|
@@ -156,13 +156,15 @@ function autoWireNode(treeName, node, nodeIndex) {
|
|
|
156
156
|
fnName: `__ui_${node.id}_autoclick`,
|
|
157
157
|
callbackBody: `__ui_nodes[${nodeIndex}].value = (__ui_nodes[${nodeIndex}].value + 1) % ${count};`,
|
|
158
158
|
});
|
|
159
|
-
// Auto-bind text to show the current option via snprintf if/else chain
|
|
159
|
+
// Auto-bind text to show the current option via snprintf if/else chain.
|
|
160
|
+
// Cast size to size_t to satisfy -Wformat (snprintf's n param is size_t;
|
|
161
|
+
// the textFn signature uses uint8_t).
|
|
160
162
|
const branches = options.map((opt, i) => {
|
|
161
163
|
if (i === 0)
|
|
162
|
-
return `if (__ui_nodes[${nodeIndex}].value == 0) { snprintf(buf, size, "%s", "${opt}"); }`;
|
|
163
|
-
return `else if (__ui_nodes[${nodeIndex}].value == ${i}) { snprintf(buf, size, "%s", "${opt}"); }`;
|
|
164
|
+
return `if (__ui_nodes[${nodeIndex}].value == 0) { snprintf(buf, static_cast<size_t>(size), "%s", "${opt}"); }`;
|
|
165
|
+
return `else if (__ui_nodes[${nodeIndex}].value == ${i}) { snprintf(buf, static_cast<size_t>(size), "%s", "${opt}"); }`;
|
|
164
166
|
}).join(" ");
|
|
165
|
-
const elseBranch = `else { snprintf(buf, size, "%s", "${options[0] || ""}"); }`;
|
|
167
|
+
const elseBranch = `else { snprintf(buf, static_cast<size_t>(size), "%s", "${options[0] || ""}"); }`;
|
|
166
168
|
recordBinding({
|
|
167
169
|
nodeIndex,
|
|
168
170
|
property: "text",
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ProgramIR, StatementIR } from "../../api/shared/index.js";
|
|
2
|
+
/** Map each statement in the program to zero or more statements. The result
|
|
3
|
+
* is a new ProgramIR; the input is not modified. */
|
|
4
|
+
export declare function mapProgramStatements(program: ProgramIR, fn: (stmt: StatementIR) => StatementIR[]): ProgramIR;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// mapProgramStatements — produce a NEW ProgramIR with statements mapped by fn.
|
|
3
|
+
//
|
|
4
|
+
// The existing walkers in walk-ir.ts are read-only; filterProgramIR
|
|
5
|
+
// (ir/filter.ts) produces a new IR but only filters, never injects. This
|
|
6
|
+
// helper extends the "produce-new-IR" idiom to "map each statement to N
|
|
7
|
+
// statements," which is what the safety pinMode-intercept pass needs to
|
|
8
|
+
// inject companion ops.
|
|
9
|
+
//
|
|
10
|
+
// Non-mutating: every statement container is rebuilt; the input program is
|
|
11
|
+
// untouched. The shape mirrors walkProgramIR's enumeration of containers
|
|
12
|
+
// (walk-ir.ts:107-134): topLevelStatements, functions, classes
|
|
13
|
+
// (constructor/methods/getters/setters), namespaces (recursively).
|
|
14
|
+
//
|
|
15
|
+
// KNOWN LIMITATION (Part A scope): does NOT recurse into nested statement
|
|
16
|
+
// bodies (if/for/while/switch/cases). Statements at the top level of a
|
|
17
|
+
// function body, class method, namespace function, or the file's top level
|
|
18
|
+
// ARE mapped. pinMode calls inside `if (...) { pinMode(...); }` are not. The
|
|
19
|
+
// safety package's design (docs/superpowers/specs/2026-07-27-safety-package-
|
|
20
|
+
// part-a-design.md) explicitly bounds Part A to this. A future iteration can
|
|
21
|
+
// thread mapping into nested bodies by recursing into thenBranch/elseBranch/
|
|
22
|
+
// body/initializer/increment/cases — see walkNestedStatements (walk-ir.ts:3).
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
/** Map each statement in the program to zero or more statements. The result
|
|
25
|
+
* is a new ProgramIR; the input is not modified. */
|
|
26
|
+
export function mapProgramStatements(program, fn) {
|
|
27
|
+
const mapList = (stmts) => {
|
|
28
|
+
const out = [];
|
|
29
|
+
for (const s of stmts) {
|
|
30
|
+
const mapped = fn(s);
|
|
31
|
+
for (const m of mapped)
|
|
32
|
+
out.push(m);
|
|
33
|
+
}
|
|
34
|
+
return out;
|
|
35
|
+
};
|
|
36
|
+
const newFunctions = program.functions.map((f) => ({
|
|
37
|
+
...f,
|
|
38
|
+
statements: mapList(f.statements),
|
|
39
|
+
}));
|
|
40
|
+
const newClasses = program.classes.map((cls) => ({
|
|
41
|
+
...cls,
|
|
42
|
+
constructor: cls.constructor
|
|
43
|
+
? { ...cls.constructor, statements: mapList(cls.constructor.statements) }
|
|
44
|
+
: cls.constructor,
|
|
45
|
+
methods: cls.methods.map((m) => ({ ...m, statements: mapList(m.statements) })),
|
|
46
|
+
getters: cls.getters.map((g) => ({ ...g, statements: mapList(g.statements) })),
|
|
47
|
+
setters: cls.setters.map((s) => ({ ...s, statements: mapList(s.statements) })),
|
|
48
|
+
}));
|
|
49
|
+
const newNamespaces = program.namespaces.map((ns) => mapNamespace(ns, fn));
|
|
50
|
+
return {
|
|
51
|
+
...program,
|
|
52
|
+
topLevelStatements: mapList(program.topLevelStatements),
|
|
53
|
+
functions: newFunctions,
|
|
54
|
+
classes: newClasses,
|
|
55
|
+
namespaces: newNamespaces,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function mapNamespace(ns, fn) {
|
|
59
|
+
const mapList = (stmts) => {
|
|
60
|
+
const out = [];
|
|
61
|
+
for (const s of stmts)
|
|
62
|
+
out.push(...fn(s));
|
|
63
|
+
return out;
|
|
64
|
+
};
|
|
65
|
+
return {
|
|
66
|
+
...ns,
|
|
67
|
+
functions: ns.functions.map((f) => ({ ...f, statements: mapList(f.statements) })),
|
|
68
|
+
classes: ns.classes.map((cls) => ({
|
|
69
|
+
...cls,
|
|
70
|
+
constructor: cls.constructor
|
|
71
|
+
? { ...cls.constructor, statements: mapList(cls.constructor.statements) }
|
|
72
|
+
: cls.constructor,
|
|
73
|
+
methods: cls.methods.map((m) => ({ ...m, statements: mapList(m.statements) })),
|
|
74
|
+
getters: cls.getters.map((g) => ({ ...g, statements: mapList(g.statements) })),
|
|
75
|
+
setters: cls.setters.map((s) => ({ ...s, statements: mapList(s.statements) })),
|
|
76
|
+
})),
|
|
77
|
+
children: ns.children ? ns.children.map((c) => mapNamespace(c, fn)) : ns.children,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { resolveStrategy } from "../platform/registry.js";
|
|
2
2
|
import { hasLoadedFramework, getLoadedFramework } from "../framework-registry.js";
|
|
3
3
|
import { analyzeInterruptSafety, inferVolatileForIsrSharedVars, detectReentrancyRisk } from "./interrupt-analysis.js";
|
|
4
|
+
import { analyzeWorkerIsolation } from "./worker-analysis.js";
|
|
4
5
|
import { validateADCRange } from "./adc-range-validation.js";
|
|
5
6
|
import { createEmptyPeripheralUsage } from "./peripheral-usage.js";
|
|
6
7
|
import { validatePeripherals } from "./peripheral-validation.js";
|
|
@@ -18,6 +19,7 @@ import { validateBlockingDelayInLoop } from "./timing-validation.js";
|
|
|
18
19
|
import { validateUnitSuspicion } from "./unit-suspicion-validation.js";
|
|
19
20
|
import { validateOwnership } from "./ownership-analysis.js";
|
|
20
21
|
import { validatePinCapabilities } from "./pin-capability-validation.js";
|
|
22
|
+
import { validateNetworkUsage } from "./network-validation.js";
|
|
21
23
|
export function runProgramValidations(program, strategy) {
|
|
22
24
|
const resolvedStrategy = strategy ?? (hasLoadedFramework() ? getLoadedFramework().strategy : resolveStrategy('generic'));
|
|
23
25
|
const diagnostics = [];
|
|
@@ -30,9 +32,13 @@ export function runProgramValidations(program, strategy) {
|
|
|
30
32
|
diagnostics.push(...validatePWMTimerSharing(peripheralUsage, program.boardConstants, program.fileName));
|
|
31
33
|
diagnostics.push(...validateTimer0PWMTimingConflict(peripheralUsage, program.boardConstants, program.fileName));
|
|
32
34
|
diagnostics.push(...validatePulldownSupport(peripheralUsage, program.boardConstants, program.fileName));
|
|
33
|
-
diagnostics.push(...analyzeInterruptSafety(program, peripheralUsage));
|
|
35
|
+
diagnostics.push(...analyzeInterruptSafety(program, peripheralUsage, resolvedStrategy.isrUnsafeOperations?.()));
|
|
34
36
|
inferVolatileForIsrSharedVars(program, diagnostics);
|
|
35
37
|
detectReentrancyRisk(program, diagnostics);
|
|
38
|
+
// Worker isolation: bus access in workers is a hard error; worker-shared
|
|
39
|
+
// globals get volatile promotion (info) or a data-race warning. Only fires
|
|
40
|
+
// when worker.submit ops are present, so non-worker programs are unaffected.
|
|
41
|
+
analyzeWorkerIsolation(program, diagnostics);
|
|
36
42
|
diagnostics.push(...validateADCRange(program, program.boardConstants));
|
|
37
43
|
diagnostics.push(...validateUnitSuspicion(program));
|
|
38
44
|
diagnostics.push(...validatePinModeConfig(program));
|
|
@@ -40,6 +46,7 @@ export function runProgramValidations(program, strategy) {
|
|
|
40
46
|
diagnostics.push(...validateOwnership(program));
|
|
41
47
|
diagnostics.push(...validateTryCatch(program, program.boardConstants, resolvedStrategy));
|
|
42
48
|
diagnostics.push(...validateMemoryBudget(program, program.boardConstants));
|
|
43
|
-
diagnostics.push(...validateBlockingDelayInLoop(program));
|
|
49
|
+
diagnostics.push(...validateBlockingDelayInLoop(program, resolvedStrategy));
|
|
50
|
+
diagnostics.push(...validateNetworkUsage(program));
|
|
44
51
|
return diagnostics;
|
|
45
52
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ProgramIR } from '../api/index.js';
|
|
2
|
+
import type { Diagnostic } from '../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Analyze worker isolation: (a) promote volatile for worker-shared globals,
|
|
5
|
+
* (b) flag shared-mutable globals, (c) hard-error on bus access in workers.
|
|
6
|
+
*
|
|
7
|
+
* Mutates the IR for (a) (sets isVolatile=true on qualifying var_decls) and
|
|
8
|
+
* pushes diagnostics for all three tiers. Returns the diagnostics it added.
|
|
9
|
+
*/
|
|
10
|
+
export declare function analyzeWorkerIsolation(program: ProgramIR, diagnostics: Diagnostic[]): Diagnostic[];
|