@typecad/cuttlefish 0.1.0-alpha.2 → 1.0.0-alpha.10
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 +10 -10
- package/dist/api/board-types.d.ts +1 -1
- 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 +595 -0
- package/dist/api/shared/framework-manifest.js +193 -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 +764 -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 +119 -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/types.d.ts +8 -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-utils.d.ts +1 -0
- package/dist/cli-utils.js +3 -1
- package/dist/cli.js +249 -32
- 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/framework-catalog.d.ts +64 -0
- package/dist/create/framework-catalog.js +136 -0
- package/dist/create/index.d.ts +5 -1
- package/dist/create/index.js +3 -1
- package/dist/create/init-scaffold.d.ts +12 -3
- package/dist/create/init-scaffold.js +49 -14
- package/dist/create/init-templates.d.ts +4 -0
- package/dist/create/init-templates.js +263 -17
- package/dist/create/init-wizard.js +41 -27
- package/dist/create/install-deps.d.ts +32 -0
- package/dist/create/install-deps.js +46 -0
- 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 +372 -30
- 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/install/framework-catalog.d.ts +53 -0
- package/dist/install/framework-catalog.js +107 -0
- package/dist/install/handle-install.d.ts +35 -0
- package/dist/install/handle-install.js +177 -0
- package/dist/install/index.d.ts +4 -0
- package/dist/install/index.js +3 -0
- package/dist/ir/adc-range-validation.js +40 -26
- 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 +207 -6
- 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 +733 -1
- package/dist/ir/identifier-collector.js +35 -0
- package/dist/ir/interrupt-analysis.d.ts +5 -1
- package/dist/ir/interrupt-analysis.js +16 -19
- 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 +39 -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 +71 -32
- package/dist/ir/pin-mode-validation.d.ts +2 -2
- package/dist/ir/pin-mode-validation.js +54 -18
- package/dist/ir/pin-safety.d.ts +1 -1
- package/dist/ir/pin-safety.js +2 -1
- package/dist/ir/program-analysis.d.ts +81 -0
- package/dist/ir/program-analysis.js +429 -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/render-expr.js +11 -0
- 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 +61 -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 +137 -19
- package/dist/ir/try-catch-validation.js +2 -0
- package/dist/ir/type-resolution.js +20 -2
- package/dist/ir/ui-element-auto-wire.js +7 -5
- package/dist/ir/unit-suspicion-validation.js +9 -7
- 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 +15 -8
- 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 +8 -2
- package/dist/testing.js +9 -2
- package/dist/transpile.d.ts +3 -0
- package/dist/transpile.js +191 -49
- package/dist/types.d.ts +22 -3
- package/dist/ui-hook.d.ts +17 -3
- package/dist/utils/cli.js +122 -2
- package/dist/utils/fs.d.ts +13 -0
- package/dist/utils/fs.js +50 -0
- package/dist/utils/ui.js +3 -1
- package/package.json +23 -3
- 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/ir/heap-array-validation.d.ts +0 -24
- package/dist/ir/heap-array-validation.js +0 -29
|
@@ -12,20 +12,52 @@
|
|
|
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
|
+
// Derive the blocking delay call names from the loaded framework's
|
|
29
|
+
// ISR-unsafe table (delay, delayMicroseconds on Wiring-derived frameworks).
|
|
30
|
+
// Fall back to those two names when no framework is loaded.
|
|
31
|
+
const frameworkUnsafe = hasLoadedFramework()
|
|
32
|
+
? getLoadedFramework().strategy.isrUnsafeOperations?.()
|
|
33
|
+
: undefined;
|
|
34
|
+
const delayNames = frameworkUnsafe
|
|
35
|
+
? [...frameworkUnsafe.keys()].filter(k => k === 'delay' || k === 'delayMicroseconds')
|
|
36
|
+
: ['delay', 'delayMicroseconds'];
|
|
37
|
+
// Direct call: a blocking delay call by name.
|
|
20
38
|
if (stmt.kind === 'call' && typeof s.callee === 'string') {
|
|
21
|
-
if (
|
|
22
|
-
|
|
39
|
+
if (isRtos) {
|
|
40
|
+
// On RTOS targets, only delayMicroseconds is a busy-wait.
|
|
41
|
+
if (s.callee === 'delayMicroseconds')
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
if (delayNames.includes(s.callee))
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
23
48
|
}
|
|
24
49
|
// Hal-op: after HAL resolution, delay() becomes timing.delay /
|
|
25
50
|
// timing.delay_microseconds.
|
|
26
51
|
if (stmt.kind === 'hal-op' && s.operation?.operation) {
|
|
27
|
-
if (
|
|
28
|
-
|
|
52
|
+
if (isRtos) {
|
|
53
|
+
// On RTOS targets, only timing.delay_microseconds is blocking.
|
|
54
|
+
if (s.operation.operation === 'timing.delay_microseconds')
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
if (s.operation.operation === 'timing.delay' || s.operation.operation === 'timing.delay_microseconds') {
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
29
61
|
}
|
|
30
62
|
}
|
|
31
63
|
return false;
|
|
@@ -33,24 +65,40 @@ function isBlockingDelay(stmt) {
|
|
|
33
65
|
/**
|
|
34
66
|
* Detect blocking delay() calls inside loop()'s body. Emits a warning for
|
|
35
67
|
* each, explaining the cooperative-async alternative.
|
|
68
|
+
*
|
|
69
|
+
* On RTOS targets (ESP-IDF), timing.delay lowers to vTaskDelay which yields
|
|
70
|
+
* the CPU — it does NOT freeze the async queue or UI. Only delayMicroseconds
|
|
71
|
+
* (busy-wait) triggers the warning.
|
|
36
72
|
*/
|
|
37
|
-
export function validateBlockingDelayInLoop(program) {
|
|
73
|
+
export function validateBlockingDelayInLoop(program, strategy) {
|
|
38
74
|
const diagnostics = [];
|
|
75
|
+
// Detect RTOS targets where delay() yields rather than blocks.
|
|
76
|
+
// framework-esp32 uses FreeRTOS; vTaskDelay is a yielding delay.
|
|
77
|
+
const isRtos = strategy?.isRtosTarget?.() === true
|
|
78
|
+
|| (hasLoadedFramework() && getLoadedFramework().strategy.isRtosTarget?.() === true);
|
|
39
79
|
const loopFn = program.functions.find(fn => fn.originalName === 'loop');
|
|
40
80
|
if (!loopFn || !loopFn.statements)
|
|
41
81
|
return diagnostics;
|
|
42
82
|
const visit = (stmts) => {
|
|
43
83
|
for (const stmt of stmts) {
|
|
44
|
-
if (isBlockingDelay(stmt)) {
|
|
84
|
+
if (isBlockingDelay(stmt, isRtos)) {
|
|
45
85
|
const s = stmt;
|
|
86
|
+
const message = isRtos
|
|
87
|
+
? `Blocking delayMicroseconds() inside loop() is a busy-wait that wastes CPU cycles. ` +
|
|
88
|
+
`On RTOS targets (ESP-IDF), prefer timing.delay() (vTaskDelay) which yields the CPU to other tasks.`
|
|
89
|
+
: `Blocking delay() inside loop() freezes the async microtask queue and UI rendering ` +
|
|
90
|
+
`for the delay duration. This causes display tearing and makes the sketch unresponsive.`;
|
|
91
|
+
const hint = isRtos
|
|
92
|
+
? `Replace delayMicroseconds with delay() if the timing permits, or accept the brief busy-wait if sub-millisecond precision is required.`
|
|
93
|
+
: `Use the cooperative pattern instead: track elapsed time with millis() comparisons, ` +
|
|
94
|
+
`or use Async.sleep(ms) / Async.yield() to let other tasks run between checks.`;
|
|
46
95
|
diagnostics.push({
|
|
47
96
|
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.`,
|
|
97
|
+
message,
|
|
98
|
+
hint,
|
|
52
99
|
line: stmt.sourceSpan?.startLine,
|
|
53
100
|
column: stmt.sourceSpan?.startColumn,
|
|
101
|
+
filePath: stmt.sourceSpan?.filePath,
|
|
54
102
|
code: 'blocking-delay-in-loop',
|
|
55
103
|
source: 'timing-validation',
|
|
56
104
|
});
|
|
@@ -4,6 +4,7 @@ import { getCurrentIrTypeScope } from "../symbol-types.js";
|
|
|
4
4
|
import { extractNodeComments, makeSourceSpan } from "../ast-node-utils.js";
|
|
5
5
|
import { tryResolveHALMethod } from "./hal-call-resolver.js";
|
|
6
6
|
import { tryResolveUICall, isSignalName, resolveUIModuleImport, recordPressBinding, uiPressBindings, resolveNodeIndex, resolveNodeTag, watchPinSpecs, recordWatchPin, recordClickHandler, clickHandlers, pushUnknownElementDiagnostic } from "./ui-call-resolver.js";
|
|
7
|
+
import { hasSafetyHook, requireSafetyHook } from "../../safety-hook.js";
|
|
7
8
|
import { lowerCallbackStatements, resolveCallbackArg, getCanvasAmbientCtx, getCanvasAmbientDiagnostics, getCanvasAmbientSourceText, } from "./ui-callback-lowering.js";
|
|
8
9
|
import { rewriteCanvasCall } from "./canvas-lowering.js";
|
|
9
10
|
import { callbackContextLabel, unsupportedStatementHint } from "./callback-context-registry.js";
|
|
@@ -127,6 +128,64 @@ function lowerPreludeCallback(cbArg, fileName, sourceText, diagnostics, label, p
|
|
|
127
128
|
sourceSpan,
|
|
128
129
|
};
|
|
129
130
|
}
|
|
131
|
+
/** Detect `safe.<method>(...)` calls in statement position and lower them to
|
|
132
|
+
* a hal-op statement via the safety hook's resolveSemanticCall. Mirrors the
|
|
133
|
+
* UI call resolver pattern. Returns null when:
|
|
134
|
+
* - the safety engine is not loaded (hasSafetyHook() === false), or
|
|
135
|
+
* - the call is not a `safe.<method>(...)` shape, or
|
|
136
|
+
* - the hook declines to lower it (returns undefined).
|
|
137
|
+
*
|
|
138
|
+
* Expression-position safe.* calls (e.g. `const r = safe.read(pin)`) are
|
|
139
|
+
* handled separately in the variable-declaration transformer. */
|
|
140
|
+
function tryResolveSafetyCallStatement(call, fileName, sourceText) {
|
|
141
|
+
if (!hasSafetyHook())
|
|
142
|
+
return null;
|
|
143
|
+
// Shape: safe.<method>(args...) — PropertyAccessExpression on identifier "safe".
|
|
144
|
+
if (!ts.isPropertyAccessExpression(call.expression))
|
|
145
|
+
return null;
|
|
146
|
+
if (!ts.isIdentifier(call.expression.expression))
|
|
147
|
+
return null;
|
|
148
|
+
if (call.expression.expression.text !== "safe")
|
|
149
|
+
return null;
|
|
150
|
+
const method = call.expression.name.text;
|
|
151
|
+
// Extract simple literal/identifier arg values; non-literal args are passed
|
|
152
|
+
// as their rendered text so the hook can decide. The hook consumes
|
|
153
|
+
// safe.read (pin), safe.write (pin, value), and safe.pinMode (pin, mode).
|
|
154
|
+
// For safe.write, the value may be a property access (r.value), arithmetic
|
|
155
|
+
// expression, or variable — rendered to its C++ text form.
|
|
156
|
+
const argValues = call.arguments.map((a) => {
|
|
157
|
+
if (ts.isNumericLiteral(a))
|
|
158
|
+
return Number(a.text);
|
|
159
|
+
if (ts.isStringLiteral(a))
|
|
160
|
+
return a.text;
|
|
161
|
+
if (a.kind === ts.SyntaxKind.TrueKeyword)
|
|
162
|
+
return true;
|
|
163
|
+
if (a.kind === ts.SyntaxKind.FalseKeyword)
|
|
164
|
+
return false;
|
|
165
|
+
if (ts.isIdentifier(a)) {
|
|
166
|
+
const name = a.text;
|
|
167
|
+
if (name === "INPUT")
|
|
168
|
+
return 0;
|
|
169
|
+
if (name === "OUTPUT")
|
|
170
|
+
return 1;
|
|
171
|
+
if (name === "INPUT_PULLUP")
|
|
172
|
+
return 2;
|
|
173
|
+
return name;
|
|
174
|
+
}
|
|
175
|
+
// Property access (e.g. r.value), arithmetic, or other expression:
|
|
176
|
+
// render to the C++ expression text so the op can emit it inline.
|
|
177
|
+
return a.getText();
|
|
178
|
+
});
|
|
179
|
+
const op = requireSafetyHook().resolveSemanticCall?.(`safe.${method}`, argValues);
|
|
180
|
+
if (!op)
|
|
181
|
+
return null;
|
|
182
|
+
return {
|
|
183
|
+
kind: "hal-op",
|
|
184
|
+
operation: op,
|
|
185
|
+
returns_value: false,
|
|
186
|
+
sourceSpan: makeSourceSpan(call, fileName, sourceText),
|
|
187
|
+
};
|
|
188
|
+
}
|
|
130
189
|
export function callToStatement(statementNode, call, fileName, sourceText, diagnostics, pointerVars = new Map()) {
|
|
131
190
|
const comments = extractNodeComments(statementNode, sourceText);
|
|
132
191
|
// ── setInterval/setTimeout arrow-callback hoisting (BEFORE HAL) ────────
|
|
@@ -448,6 +507,52 @@ export function callToStatement(statementNode, call, fileName, sourceText, diagn
|
|
|
448
507
|
const uiResolved = tryResolveUICall(call, fileName, sourceText, diagnostics);
|
|
449
508
|
if (uiResolved)
|
|
450
509
|
return uiResolved;
|
|
510
|
+
// ---- safe.* — @typecad/safety authoring calls (statement position) ---
|
|
511
|
+
// Handles statement-position safe.* calls like safe.pinMode(pin, mode).
|
|
512
|
+
// The expression-position form (const r = safe.read(pin)) is handled in
|
|
513
|
+
// the variable-declaration transformer. Both produce hal-op IR nodes that
|
|
514
|
+
// routeHALOp() later dispatches to the safety hook.
|
|
515
|
+
const safetyResolved = tryResolveSafetyCallStatement(call, fileName, sourceText);
|
|
516
|
+
if (safetyResolved)
|
|
517
|
+
return safetyResolved;
|
|
518
|
+
// ---- safe.read/safe.write chained with .ok/.fail/.fault/.always (statement) ----
|
|
519
|
+
// A statement like `safe.read(pin).ok(r => {...}).fail(r => {...})` is a
|
|
520
|
+
// method chain on a safe.* result. The terminal safe.* interceptor above
|
|
521
|
+
// returns null for it (the shape isn't `safe.<method>` — it's `.fail` on a
|
|
522
|
+
// chain). Delegate to expressionToIR, which has tryResolveSafetyChain to
|
|
523
|
+
// build the structured nested method-call IR that preserves lambda args,
|
|
524
|
+
// then render to text for a __RAW_STMT__ statement.
|
|
525
|
+
if (hasSafetyHook() && ts.isPropertyAccessExpression(call.expression)) {
|
|
526
|
+
let walk = call;
|
|
527
|
+
let isSafeChain = false;
|
|
528
|
+
while (ts.isCallExpression(walk) && ts.isPropertyAccessExpression(walk.expression)) {
|
|
529
|
+
walk = walk.expression.expression;
|
|
530
|
+
if (ts.isCallExpression(walk) &&
|
|
531
|
+
ts.isPropertyAccessExpression(walk.expression) &&
|
|
532
|
+
ts.isIdentifier(walk.expression.expression) &&
|
|
533
|
+
walk.expression.expression.text === "safe") {
|
|
534
|
+
isSafeChain = true;
|
|
535
|
+
break;
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
if (isSafeChain) {
|
|
539
|
+
// Build the structured chain IR via expressionToIR (the chain resolver
|
|
540
|
+
// produces nested method-call nodes with receiverExpr so lambdas survive).
|
|
541
|
+
// Carry it as the arg of a __EXPR_STMT__ call statement; the statement
|
|
542
|
+
// renderer renders it via the EMIT-TIME expression renderer (which has
|
|
543
|
+
// renderLambda for inline [&](){...} lambdas), NOT via the build-time
|
|
544
|
+
// renderExprAsText (which flattens lambdas to /* __lambda__ */).
|
|
545
|
+
const exprIR = expressionToIR(call, sourceText, diagnostics, pointerVars);
|
|
546
|
+
return {
|
|
547
|
+
kind: "call",
|
|
548
|
+
sourceSpan: makeSourceSpan(call, fileName, sourceText),
|
|
549
|
+
leadingComments: comments.leadingComments,
|
|
550
|
+
trailingComments: comments.trailingComments,
|
|
551
|
+
callee: "__EXPR_STMT__",
|
|
552
|
+
args: [exprIR],
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
}
|
|
451
556
|
// Handle super() calls in constructors - transform to super_call IR for class emitter
|
|
452
557
|
if (call.expression.kind === ts.SyntaxKind.SuperKeyword) {
|
|
453
558
|
return {
|
|
@@ -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,13 +7,18 @@ 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 };
|
|
16
18
|
}
|
|
19
|
+
if (op.operation === "spi.read_buffer" && op.buffer === "__HAL_READ_BUF__") {
|
|
20
|
+
return { ...op, buffer: varName };
|
|
21
|
+
}
|
|
17
22
|
return op;
|
|
18
23
|
}
|
|
19
24
|
export function assignmentOperatorToString(kind) {
|
|
@@ -371,6 +376,56 @@ export function variableStatementToIR(statement, fileName, sourceText, diagnosti
|
|
|
371
376
|
localVariableTypes.set(varName, cppType);
|
|
372
377
|
continue;
|
|
373
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
|
+
}
|
|
374
429
|
if (ts.isNewExpression(declaration.initializer) && ts.isIdentifier(declaration.initializer.expression)) {
|
|
375
430
|
const className = declaration.initializer.expression.text;
|
|
376
431
|
const ctorArgs = declaration.initializer.arguments;
|
|
@@ -405,9 +460,26 @@ export function variableStatementToIR(statement, fileName, sourceText, diagnosti
|
|
|
405
460
|
}
|
|
406
461
|
}
|
|
407
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
|
+
}
|
|
408
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";
|
|
409
481
|
for (const arg of ctorArgs) {
|
|
410
|
-
if (ts.isIdentifier(arg) && className === "Pin") {
|
|
482
|
+
if (ts.isIdentifier(arg) && (className === "Pin" || firstFieldIsPin)) {
|
|
411
483
|
const existing = halInstances.get(arg.text);
|
|
412
484
|
if (existing && existing.fieldValues.has("_pin")) {
|
|
413
485
|
fieldValues.set("_pin", existing.fieldValues.get("_pin"));
|
|
@@ -430,6 +502,45 @@ export function variableStatementToIR(statement, fileName, sourceText, diagnosti
|
|
|
430
502
|
const isSingletonReceiver = ts.isIdentifier(receiver) && isHALSingleton(receiver.text);
|
|
431
503
|
if (result && (!isOwnershipMethod || isSingletonReceiver)) {
|
|
432
504
|
const isHalOpReturn = result.returnValue === "__hal_op_return__";
|
|
505
|
+
// A __TYPED_ARRAY__ return comes ONLY from a HAL method body
|
|
506
|
+
// (`return new Uint8Array(count)` in e.g. I2CDevice.readBytes /
|
|
507
|
+
// SPIDevice.readRegister). The method's side-effect HAL ops (the
|
|
508
|
+
// i2c.read_buffer / spi.read_buffer fill loops) write INTO this buffer
|
|
509
|
+
// via the __HAL_READ_BUF__ placeholder (rewritten to `varName`). So the
|
|
510
|
+
// buffer var_decl MUST precede the fill ops in emitted order — at top
|
|
511
|
+
// level the var_decl is hoisted to file scope which masks this, but a
|
|
512
|
+
// function-local `const data = ...readBytes()` would otherwise emit the
|
|
513
|
+
// fill loop referencing `data` before its declaration. Detect the typed
|
|
514
|
+
// array up front so we can emit its declaration first.
|
|
515
|
+
const isTypedArrayReturn = typeof result.returnValue === "string" && result.returnValue.startsWith("__TYPED_ARRAY__:");
|
|
516
|
+
if (isTypedArrayReturn && typeof result.returnValue === "string") {
|
|
517
|
+
const parts = result.returnValue.split(":");
|
|
518
|
+
const elementType = parts[1];
|
|
519
|
+
const size = parts[2];
|
|
520
|
+
// Force non-const storage (the buffer is written by the fill op) and
|
|
521
|
+
// synthesize a zero-init array initializer so the var_decl renderer
|
|
522
|
+
// emits `T data[] = { 0, 0, ... }` — a bare `const T data[N];` that
|
|
523
|
+
// is later written would fail to compile (assignment to const).
|
|
524
|
+
// Mirrors plain `new Uint8Array(N)` (expression-to-ir.ts).
|
|
525
|
+
const count = parseInt(size, 10);
|
|
526
|
+
const initElements = !isNaN(count) && count > 0 && count <= 256
|
|
527
|
+
? Array(count).fill(0).map(() => ({ kind: "number", value: 0 }))
|
|
528
|
+
: [];
|
|
529
|
+
lowered.push({
|
|
530
|
+
kind: "var_decl",
|
|
531
|
+
sourceSpan: makeSourceSpan(declaration, fileName, sourceText),
|
|
532
|
+
leadingComments: commentsAssigned ? [] : statementComments.leadingComments,
|
|
533
|
+
trailingComments: [],
|
|
534
|
+
name: varName,
|
|
535
|
+
storage: "let",
|
|
536
|
+
cppType: `${elementType}[${size}]`,
|
|
537
|
+
initializer: initElements.length > 0
|
|
538
|
+
? { kind: "array", elements: initElements, elementType }
|
|
539
|
+
: undefined,
|
|
540
|
+
});
|
|
541
|
+
activeCArrayVars.add(varName);
|
|
542
|
+
commentsAssigned = true;
|
|
543
|
+
}
|
|
433
544
|
if (result.halOps && result.halOps.length > 0) {
|
|
434
545
|
const sideEffectOps = isHalOpReturn ? result.halOps.slice(0, -1) : result.halOps;
|
|
435
546
|
const halStmts = sideEffectOps.map(op => ({
|
|
@@ -474,6 +585,19 @@ export function variableStatementToIR(statement, fileName, sourceText, diagnosti
|
|
|
474
585
|
}
|
|
475
586
|
}
|
|
476
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
|
+
}
|
|
477
601
|
halInstances.set(varName, {
|
|
478
602
|
className: result.returnClassName,
|
|
479
603
|
fieldValues,
|
|
@@ -527,24 +651,18 @@ export function variableStatementToIR(statement, fileName, sourceText, diagnosti
|
|
|
527
651
|
if (/\b\d+\.\d+\b/.test(result.returnValue)) {
|
|
528
652
|
registerFloatVariable(varName);
|
|
529
653
|
}
|
|
654
|
+
// __TYPED_ARRAY__ returns are handled above (declared BEFORE the
|
|
655
|
+
// fill ops). Everything else is a scalar/value return captured
|
|
656
|
+
// after the side-effect ops.
|
|
530
657
|
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 {
|
|
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) {
|
|
548
666
|
lowered.push({
|
|
549
667
|
kind: "var_decl",
|
|
550
668
|
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;
|