@typecad/cuttlefish 1.0.0-alpha.7 → 1.0.0-alpha.9
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 +2 -2
- package/dist/api/shared/display-adapter.js +9 -119
- package/dist/api/shared/display-adapters/sdl.js +29 -29
- package/dist/api/shared/display-profile.d.ts +38 -33
- package/dist/api/shared/display-profile.js +22 -170
- package/dist/api/shared/framework-manifest-registry.d.ts +1 -1
- package/dist/api/shared/framework-manifest-registry.js +8 -6
- package/dist/api/shared/framework-manifest.d.ts +147 -28
- package/dist/api/shared/framework-manifest.js +35 -2
- package/dist/api/shared/graphics-strategy.d.ts +15 -6
- package/dist/api/shared/hal-op-ir.d.ts +340 -3
- package/dist/api/shared/hal-op-ir.js +34 -0
- package/dist/api/shared/index.d.ts +9 -1
- package/dist/api/shared/index.js +11 -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/platform-strategy.d.ts +104 -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 +17 -13
- package/dist/api/shared/toolchain-types.d.ts +17 -0
- package/dist/api/shared/validate-framework-manifest.js +77 -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 +125 -121
- package/dist/config-loader.d.ts +7 -2
- package/dist/config-loader.js +20 -19
- package/dist/config-schema.d.ts +107 -65
- 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 +74 -77
- package/dist/create/board-spec.js +0 -1
- package/dist/create/init-scaffold.d.ts +7 -3
- package/dist/create/init-scaffold.js +0 -62
- package/dist/create/init-templates.js +62 -16
- package/dist/create/init-wizard.js +32 -27
- 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/emitters/class-emitter.js +6 -1
- package/dist/emit/emitters/emitter-context.d.ts +23 -1
- package/dist/emit/emitters/function-emitter-impl.js +96 -6
- 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.js +80 -1
- package/dist/emit/emitters/setup.d.ts +17 -0
- package/dist/emit/emitters/setup.js +217 -36
- package/dist/emit/emitters/top-level-prep.js +6 -0
- package/dist/emit/emitters/type-decl-emitter.js +28 -3
- package/dist/emit/emitters/ui-emitter.js +17 -8
- package/dist/emit/expression-renderer.d.ts +1 -1
- package/dist/emit/expression-renderer.js +81 -5
- package/dist/emit/route-hal-op.js +18 -5
- package/dist/emit/snprintf-helpers.js +7 -4
- package/dist/emit/statement-renderer.d.ts +10 -0
- package/dist/emit/statement-renderer.js +21 -5
- package/dist/emit/utils/async-state-machine.js +39 -12
- 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 +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 +4 -4
- package/dist/ir/call-graph.js +16 -0
- package/dist/ir/expression-to-ir.js +150 -0
- package/dist/ir/function-builder.js +22 -0
- package/dist/ir/hal/hal-emitter.js +10 -6
- package/dist/ir/hal/hal-parser.js +16 -9
- package/dist/ir/hal/hal-plugins.js +164 -3
- package/dist/ir/identifier-collector.js +21 -4
- package/dist/ir/interrupt-analysis.d.ts +5 -1
- package/dist/ir/interrupt-analysis.js +8 -16
- package/dist/ir/ownership-analysis.js +6 -0
- 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 +42 -0
- package/dist/ir/program-analysis.js +243 -6
- package/dist/ir/render-expr.js +11 -0
- package/dist/ir/timing-validation.js +11 -2
- package/dist/ir/transformers/call-statement.js +105 -0
- package/dist/ir/transformers/expressions.js +4 -0
- package/dist/ir/transformers/hal-call-resolver.js +21 -0
- package/dist/ir/transformers/namespace-methods.js +17 -12
- package/dist/ir/transformers/ui-reactive.js +2 -2
- package/dist/ir/transformers/variables.js +51 -0
- 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 +6 -1
- package/dist/ir/worker-analysis.d.ts +10 -0
- package/dist/ir/worker-analysis.js +261 -0
- package/dist/orchestrator/graph-builder.js +8 -7
- 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/stores/display-profile-store.d.ts +0 -1
- package/dist/stores/display-profile-store.js +0 -1
- package/dist/testing.d.ts +4 -0
- package/dist/testing.js +5 -0
- package/dist/transpile.js +124 -35
- package/dist/types.d.ts +22 -2
- package/dist/ui-hook.d.ts +0 -2
- package/dist/utils/cli.d.ts +2 -2
- package/dist/utils/cli.js +68 -3
- package/package.json +20 -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/libdef/idf-discovery.d.ts +0 -7
- package/dist/libdef/idf-discovery.js +0 -59
- package/dist/licenses.d.ts +0 -185
- package/dist/licenses.js +0 -963
|
@@ -38,6 +38,20 @@ function analyzeExpression(expr, result, strategy) {
|
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
|
+
// Timer polyfill sizing — Timing.setInterval/Timing.setTimeout in
|
|
42
|
+
// expression position (e.g. `const id = Timing.setInterval(...)`) lower to
|
|
43
|
+
// a raw expression whose value contains `__tc_setInterval(...)`. The
|
|
44
|
+
// callee-based counter in the "call" case only fires for bare timer
|
|
45
|
+
// calls; without this the timer_methods polyfill is gated off and the
|
|
46
|
+
// link fails: "'__tc_setInterval' was not declared in this scope".
|
|
47
|
+
{
|
|
48
|
+
const intervals = expr.value.match(/__tc_setInterval\s*\(/g);
|
|
49
|
+
if (intervals)
|
|
50
|
+
result.timerCallCount += intervals.length;
|
|
51
|
+
const timeouts = expr.value.match(/__tc_setTimeout\s*\(/g);
|
|
52
|
+
if (timeouts)
|
|
53
|
+
result.timerCallCount += timeouts.length;
|
|
54
|
+
}
|
|
41
55
|
if (/\bString\s*\(/.test(expr.value)) {
|
|
42
56
|
result.usesStringConversion = true;
|
|
43
57
|
}
|
|
@@ -63,6 +77,24 @@ function analyzeExpression(expr, result, strategy) {
|
|
|
63
77
|
if (expr.value.includes('__tc_str_ptr')) {
|
|
64
78
|
result.usesStrPtr = true;
|
|
65
79
|
}
|
|
80
|
+
if (/std::chrono|steady_clock/.test(expr.value)) {
|
|
81
|
+
result.usesChrono = true;
|
|
82
|
+
}
|
|
83
|
+
if (/std::set\s*</.test(expr.value)) {
|
|
84
|
+
result.usesSet = true;
|
|
85
|
+
}
|
|
86
|
+
if (/std::(sort|find|transform|copy|fill|remove|count|reverse|accumulate)\s*\(/.test(expr.value)) {
|
|
87
|
+
result.usesAlgorithm = true;
|
|
88
|
+
}
|
|
89
|
+
if (/\b(printf|fprintf|sprintf|snprintf)\s*\(/.test(expr.value)) {
|
|
90
|
+
result.usesCstdio = true;
|
|
91
|
+
}
|
|
92
|
+
if (/\bdigitalRead\s*\(/.test(expr.value)) {
|
|
93
|
+
result.usesDigitalRead = true;
|
|
94
|
+
}
|
|
95
|
+
if (/cuttlefish_halt\s*\(/.test(expr.value)) {
|
|
96
|
+
result.usesHalt = true;
|
|
97
|
+
}
|
|
66
98
|
break;
|
|
67
99
|
case "method-call":
|
|
68
100
|
if (strategy.isConsoleCall(expr.callee)) {
|
|
@@ -217,20 +249,105 @@ function analyzeExpression(expr, result, strategy) {
|
|
|
217
249
|
}
|
|
218
250
|
break;
|
|
219
251
|
case "hal-expr":
|
|
220
|
-
// Value-position HAL ops (e.g. `const
|
|
221
|
-
// hal-expr initializer
|
|
222
|
-
//
|
|
223
|
-
//
|
|
252
|
+
// Value-position HAL ops (e.g. `const v = D32.readAnalog()` lowers to an
|
|
253
|
+
// adc.read hal-expr initializer; `const ip = WiFi.localIP()` to a wifi
|
|
254
|
+
// hal-expr) never pass through the statement-level hal-op detection
|
|
255
|
+
// below, so mirror the FULL peripheral-flag detection here. Without this,
|
|
256
|
+
// a value-returning HAL op used in a var-init or expression context fails
|
|
257
|
+
// to set its analysis flag — the framework then drops the matching init
|
|
258
|
+
// block + forced includes (e.g. __tc_adc_read was emitted but the
|
|
259
|
+
// CUTTLEFISH_ADC init block and adc_oneshot.h include were not). Keep this
|
|
260
|
+
// list in sync with the hal-op statement case at the bottom of this file.
|
|
224
261
|
if (expr.operation && typeof expr.operation.operation === "string") {
|
|
225
262
|
const opName = expr.operation.operation;
|
|
226
|
-
if (opName.startsWith("
|
|
263
|
+
if (opName.startsWith("display.")) {
|
|
264
|
+
result.usesGPIO = true;
|
|
265
|
+
result.usesDisplay = true;
|
|
266
|
+
}
|
|
267
|
+
if (opName.startsWith("gpio."))
|
|
268
|
+
result.usesGPIO = true;
|
|
269
|
+
if (opName === "gpio.read")
|
|
270
|
+
result.usesDigitalRead = true;
|
|
271
|
+
if (opName.startsWith("pwm."))
|
|
272
|
+
result.usesPWM = true;
|
|
273
|
+
if (opName.startsWith("rmt."))
|
|
274
|
+
result.usesRmt = true;
|
|
275
|
+
if (opName.startsWith("adc."))
|
|
276
|
+
result.usesADC = true;
|
|
277
|
+
if (opName.startsWith("dac."))
|
|
278
|
+
result.usesDAC = true;
|
|
279
|
+
if (opName.startsWith("power."))
|
|
280
|
+
result.usesPower = true;
|
|
281
|
+
if (opName.startsWith("wdt."))
|
|
282
|
+
result.usesWdt = true;
|
|
283
|
+
// Safety HAL ops (@typecad/safety) — map each op to the
|
|
284
|
+
// __tc_safety_* polyfill helper it triggers, so the mode-table and
|
|
285
|
+
// voter polyfills survive tree-shaking when actually used. Matches
|
|
286
|
+
// the resolveSafetyOp output naming. Cast through string because the
|
|
287
|
+
// HALOpIR operation union is closed and doesn't include safety.* ops
|
|
288
|
+
// (those are owned by the optional @typecad/safety package).
|
|
289
|
+
const safetyOpName = opName;
|
|
290
|
+
if (safetyOpName === "safety.record_pin_mode") {
|
|
291
|
+
result.usedPolyfillHelpers.add("__tc_safety_record_pin_mode");
|
|
292
|
+
}
|
|
293
|
+
if (safetyOpName === "safety.read_safe") {
|
|
294
|
+
result.usedPolyfillHelpers.add("__tc_safety_read_safe");
|
|
295
|
+
}
|
|
296
|
+
if (safetyOpName === "safety.write_verify") {
|
|
297
|
+
result.usedPolyfillHelpers.add("__tc_safety_write_verify");
|
|
298
|
+
}
|
|
299
|
+
if (opName.startsWith("interrupt."))
|
|
300
|
+
result.usesInterrupts = true;
|
|
301
|
+
if (opName.startsWith("pulse."))
|
|
302
|
+
result.usesPulse = true;
|
|
303
|
+
if (opName.startsWith("shift."))
|
|
304
|
+
result.usesShift = true;
|
|
305
|
+
if (opName.startsWith("tone."))
|
|
306
|
+
result.usesTone = true;
|
|
307
|
+
if (opName.startsWith("i2c."))
|
|
308
|
+
result.usesI2C = true;
|
|
309
|
+
if (opName.startsWith("spi."))
|
|
310
|
+
result.usesSPI = true;
|
|
311
|
+
if (opName.startsWith("uart."))
|
|
312
|
+
result.usesUart = true;
|
|
313
|
+
if (opName.startsWith("wifi.")) {
|
|
227
314
|
result.usesWifi = true;
|
|
315
|
+
if (opName === "wifi.connect" || opName === "wifi.connect_start" || opName === "wifi.disconnect") {
|
|
316
|
+
result.usesWifiConnect = true;
|
|
317
|
+
if (opName === "wifi.connect")
|
|
318
|
+
result.usesWifiConnectBlocking = true;
|
|
319
|
+
}
|
|
320
|
+
else if (opName.startsWith("wifi.scan"))
|
|
321
|
+
result.usesWifiScan = true;
|
|
322
|
+
else if (opName === "wifi.status" || opName === "wifi.local_ip" || opName === "wifi.rssi" || opName === "wifi.mac")
|
|
323
|
+
result.usesWifiQuery = true;
|
|
324
|
+
else if (opName === "wifi.set_hostname")
|
|
325
|
+
result.usesWifiConfig = true;
|
|
326
|
+
}
|
|
228
327
|
if (opName.startsWith("http."))
|
|
229
328
|
result.usesHttp = true;
|
|
230
329
|
if (opName.startsWith("ble."))
|
|
231
330
|
result.usesBle = true;
|
|
232
331
|
if (opName.startsWith("preferences."))
|
|
233
332
|
result.usesPreferences = true;
|
|
333
|
+
if (opName.startsWith("random."))
|
|
334
|
+
result.usesRandom = true;
|
|
335
|
+
if (opName.startsWith("fs."))
|
|
336
|
+
result.usesFS = true;
|
|
337
|
+
if (opName.startsWith("mdns."))
|
|
338
|
+
result.usesMdns = true;
|
|
339
|
+
if (opName.startsWith("mqtt."))
|
|
340
|
+
result.usesMqtt = true;
|
|
341
|
+
if (opName.startsWith("ota."))
|
|
342
|
+
result.usesOta = true;
|
|
343
|
+
if (opName.startsWith("temp."))
|
|
344
|
+
result.usesTemp = true;
|
|
345
|
+
if (opName.startsWith("hwtimer."))
|
|
346
|
+
result.usesHwtimer = true;
|
|
347
|
+
if (opName.startsWith("capacitive."))
|
|
348
|
+
result.usesCapacitive = true;
|
|
349
|
+
if (opName.startsWith("worker."))
|
|
350
|
+
result.usesWorker = true;
|
|
234
351
|
}
|
|
235
352
|
break;
|
|
236
353
|
}
|
|
@@ -309,6 +426,18 @@ function analyzeStatement(statement, result, strategy) {
|
|
|
309
426
|
result.usesMap = true;
|
|
310
427
|
if (statement.callee === "constrain")
|
|
311
428
|
result.usesConstrain = true;
|
|
429
|
+
// Statement-form setInterval/setTimeout mirror the expression-level
|
|
430
|
+
// timer-count (analyzeExpression). A top-level `setInterval(...)` call
|
|
431
|
+
// stays a `call` statement (never becomes a method-call expression — see
|
|
432
|
+
// the comment above re: Timing./Num./WDT.), so without this mirror the
|
|
433
|
+
// timer polyfill gate (timerCallCount) stays 0 and __tc_setInterval is
|
|
434
|
+
// emitted as a call but never defined (demo: "'__tc_setInterval' was not
|
|
435
|
+
// declared in this scope"). The arrow-callback hoister renames the callee
|
|
436
|
+
// to __tc_setInterval/__tc_setTimeout, so check both pre- and post-rename.
|
|
437
|
+
if (statement.callee === "setInterval" || statement.callee === "setTimeout"
|
|
438
|
+
|| statement.callee === "__tc_setInterval" || statement.callee === "__tc_setTimeout") {
|
|
439
|
+
result.timerCallCount++;
|
|
440
|
+
}
|
|
312
441
|
// The HAL resolver lowers WDT.*/Timing.* namespace calls to bare AVR
|
|
313
442
|
// library functions (WDT.reset() → wdt_reset(), Timing.delay() → delay(),
|
|
314
443
|
// Timing.millis() → millis()). When that happens the `WDT.`/`Timing.`
|
|
@@ -336,6 +465,27 @@ function analyzeStatement(statement, result, strategy) {
|
|
|
336
465
|
}
|
|
337
466
|
}
|
|
338
467
|
}
|
|
468
|
+
// Timer polyfill sizing for __EMIT__ statements (callee "__EMIT__"):
|
|
469
|
+
// Timing.setInterval/Timing.setTimeout can lower to an __EMIT__ call whose
|
|
470
|
+
// single string arg is the raw C++ payload, e.g.
|
|
471
|
+
// `return __tc_setInterval(<cb>, 500)`. The callee-based timer counter
|
|
472
|
+
// above only fires for bare setInterval/__tc_setInterval calls, so without
|
|
473
|
+
// this the timer_methods polyfill is gated off and the link fails:
|
|
474
|
+
// "'__tc_setInterval' was not declared in this scope". Count call sites
|
|
475
|
+
// embedded in the raw payload. (The hal-op raw-code path has a parallel
|
|
476
|
+
// counter below.)
|
|
477
|
+
if (statement.callee === "__EMIT__") {
|
|
478
|
+
for (const arg of statement.args) {
|
|
479
|
+
if (arg && arg.kind === "string" && typeof arg.value === "string") {
|
|
480
|
+
const matches = arg.value.match(/__tc_setInterval\s*\(/g);
|
|
481
|
+
if (matches)
|
|
482
|
+
result.timerCallCount += matches.length;
|
|
483
|
+
const timeouts = arg.value.match(/__tc_setTimeout\s*\(/g);
|
|
484
|
+
if (timeouts)
|
|
485
|
+
result.timerCallCount += timeouts.length;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
339
489
|
for (const arg of statement.args) {
|
|
340
490
|
analyzeExpression(arg, result, strategy);
|
|
341
491
|
}
|
|
@@ -457,12 +607,15 @@ function analyzeStatement(statement, result, strategy) {
|
|
|
457
607
|
// display's transport type can't be determined from the op alone.
|
|
458
608
|
if (opName.startsWith("display.")) {
|
|
459
609
|
result.usesGPIO = true;
|
|
610
|
+
result.usesDisplay = true;
|
|
460
611
|
}
|
|
461
612
|
// ESP32 peripheral usage — framework-esp32 gates IDF driver blocks
|
|
462
613
|
// and forced includes on these. No-op for other frameworks (their
|
|
463
614
|
// shimLines emit no CUTTLEFISH_* blocks with these marker names).
|
|
464
615
|
if (opName.startsWith("gpio."))
|
|
465
616
|
result.usesGPIO = true;
|
|
617
|
+
if (opName === "gpio.read")
|
|
618
|
+
result.usesDigitalRead = true;
|
|
466
619
|
if (opName.startsWith("pwm."))
|
|
467
620
|
result.usesPWM = true;
|
|
468
621
|
if (opName.startsWith("rmt."))
|
|
@@ -475,20 +628,66 @@ function analyzeStatement(statement, result, strategy) {
|
|
|
475
628
|
result.usesPower = true;
|
|
476
629
|
if (opName.startsWith("wdt."))
|
|
477
630
|
result.usesWdt = true;
|
|
631
|
+
// Safety HAL ops (@typecad/safety) — map each op to the
|
|
632
|
+
// __tc_safety_* polyfill helper it triggers, so the mode-table and
|
|
633
|
+
// voter polyfills survive tree-shaking when actually used. Matches
|
|
634
|
+
// the resolveSafetyOp output naming. Cast through string because the
|
|
635
|
+
// HALOpIR operation union is closed and doesn't include safety.* ops
|
|
636
|
+
// (those are owned by the optional @typecad/safety package).
|
|
637
|
+
const safetyOpName = opName;
|
|
638
|
+
if (safetyOpName === "safety.record_pin_mode") {
|
|
639
|
+
result.usedPolyfillHelpers.add("__tc_safety_record_pin_mode");
|
|
640
|
+
}
|
|
641
|
+
if (safetyOpName === "safety.read_safe") {
|
|
642
|
+
result.usedPolyfillHelpers.add("__tc_safety_read_safe");
|
|
643
|
+
}
|
|
644
|
+
if (safetyOpName === "safety.write_verify") {
|
|
645
|
+
result.usedPolyfillHelpers.add("__tc_safety_write_verify");
|
|
646
|
+
}
|
|
478
647
|
if (opName.startsWith("interrupt."))
|
|
479
648
|
result.usesInterrupts = true;
|
|
480
649
|
if (opName.startsWith("pulse."))
|
|
481
650
|
result.usesPulse = true;
|
|
482
651
|
if (opName.startsWith("shift."))
|
|
483
652
|
result.usesShift = true;
|
|
484
|
-
if (opName.startsWith("wifi."))
|
|
653
|
+
if (opName.startsWith("wifi.")) {
|
|
485
654
|
result.usesWifi = true;
|
|
655
|
+
if (opName === "wifi.connect" || opName === "wifi.connect_start" || opName === "wifi.disconnect") {
|
|
656
|
+
result.usesWifiConnect = true;
|
|
657
|
+
if (opName === "wifi.connect")
|
|
658
|
+
result.usesWifiConnectBlocking = true;
|
|
659
|
+
}
|
|
660
|
+
else if (opName.startsWith("wifi.scan"))
|
|
661
|
+
result.usesWifiScan = true;
|
|
662
|
+
else if (opName === "wifi.status" || opName === "wifi.local_ip" || opName === "wifi.rssi" || opName === "wifi.mac")
|
|
663
|
+
result.usesWifiQuery = true;
|
|
664
|
+
else if (opName === "wifi.set_hostname")
|
|
665
|
+
result.usesWifiConfig = true;
|
|
666
|
+
}
|
|
486
667
|
if (opName.startsWith("http."))
|
|
487
668
|
result.usesHttp = true;
|
|
488
669
|
if (opName.startsWith("ble."))
|
|
489
670
|
result.usesBle = true;
|
|
490
671
|
if (opName.startsWith("preferences."))
|
|
491
672
|
result.usesPreferences = true;
|
|
673
|
+
if (opName.startsWith("random."))
|
|
674
|
+
result.usesRandom = true;
|
|
675
|
+
if (opName.startsWith("fs."))
|
|
676
|
+
result.usesFS = true;
|
|
677
|
+
if (opName.startsWith("mdns."))
|
|
678
|
+
result.usesMdns = true;
|
|
679
|
+
if (opName.startsWith("mqtt."))
|
|
680
|
+
result.usesMqtt = true;
|
|
681
|
+
if (opName.startsWith("ota."))
|
|
682
|
+
result.usesOta = true;
|
|
683
|
+
if (opName.startsWith("temp."))
|
|
684
|
+
result.usesTemp = true;
|
|
685
|
+
if (opName.startsWith("hwtimer."))
|
|
686
|
+
result.usesHwtimer = true;
|
|
687
|
+
if (opName.startsWith("capacitive."))
|
|
688
|
+
result.usesCapacitive = true;
|
|
689
|
+
if (opName.startsWith("worker."))
|
|
690
|
+
result.usesWorker = true;
|
|
492
691
|
// Timing HAL ops (timing.delay/millis/micros) carry a typed operation
|
|
493
692
|
// name, not raw code, so the regex scans below miss them. Mirror the
|
|
494
693
|
// raw-code timing detection here so usesMillis/usesTiming (and thus
|
|
@@ -526,6 +725,23 @@ function analyzeStatement(statement, result, strategy) {
|
|
|
526
725
|
result.usesTiming = true;
|
|
527
726
|
result.usesMillis = true;
|
|
528
727
|
}
|
|
728
|
+
// Timer polyfill sizing — `Timing.setInterval(...)` / `Timing.setTimeout(...)`
|
|
729
|
+
// are resolved by the HAL method resolver, which reads TimingClass's
|
|
730
|
+
// rawCpp body and emits a raw hal-op whose code is e.g.
|
|
731
|
+
// `return __tc_setInterval(<cb>, 500)`. The callee-based counter below
|
|
732
|
+
// (statement.callee === "__tc_setInterval") never fires for these — the
|
|
733
|
+
// statement is a hal-op, not a bare timer call — so without counting
|
|
734
|
+
// here the timer_methods polyfill is gated off (timerCallCount == 0)
|
|
735
|
+
// and the link fails: "'__tc_setInterval' was not declared in this
|
|
736
|
+
// scope". Count each __tc_setInterval/__tc_setTimeout call site in the
|
|
737
|
+
// raw code. The optional `return ` prefix is emitted by value-returning
|
|
738
|
+
// HAL methods (statement context), so match with or without it.
|
|
739
|
+
const intervalSites = code.match(/__tc_setInterval\s*\(/g);
|
|
740
|
+
if (intervalSites)
|
|
741
|
+
result.timerCallCount += intervalSites.length;
|
|
742
|
+
const timeoutSites = code.match(/__tc_setTimeout\s*\(/g);
|
|
743
|
+
if (timeoutSites)
|
|
744
|
+
result.timerCallCount += timeoutSites.length;
|
|
529
745
|
// Native AVR peripheral usage inside raw hal-op code (e.g. the
|
|
530
746
|
// EEPROM namespace lowers to `EEPROM.write(...)` in a raw hal-op;
|
|
531
747
|
// Serial/SPI/Wire may appear as lowered library calls too).
|
|
@@ -611,9 +827,30 @@ export function analyzeProgram(program, strategy) {
|
|
|
611
827
|
usesPulse: false,
|
|
612
828
|
usesShift: false,
|
|
613
829
|
usesWifi: false,
|
|
830
|
+
usesWifiConnect: false,
|
|
831
|
+
usesWifiConnectBlocking: false,
|
|
832
|
+
usesWifiQuery: false,
|
|
833
|
+
usesWifiScan: false,
|
|
834
|
+
usesWifiConfig: false,
|
|
614
835
|
usesHttp: false,
|
|
615
836
|
usesBle: false,
|
|
616
837
|
usesPreferences: false,
|
|
838
|
+
usesRandom: false,
|
|
839
|
+
usesFS: false,
|
|
840
|
+
usesMdns: false,
|
|
841
|
+
usesMqtt: false,
|
|
842
|
+
usesOta: false,
|
|
843
|
+
usesTemp: false,
|
|
844
|
+
usesHwtimer: false,
|
|
845
|
+
usesCapacitive: false,
|
|
846
|
+
usesWorker: false,
|
|
847
|
+
usesChrono: false,
|
|
848
|
+
usesSet: false,
|
|
849
|
+
usesAlgorithm: false,
|
|
850
|
+
usesCstdio: false,
|
|
851
|
+
usesDigitalRead: false,
|
|
852
|
+
usesDisplay: false,
|
|
853
|
+
usesHalt: false,
|
|
617
854
|
};
|
|
618
855
|
// Analyze type aliases
|
|
619
856
|
for (const typeAlias of program.typeAliases) {
|
package/dist/ir/render-expr.js
CHANGED
|
@@ -85,6 +85,17 @@ export function renderExprAsText(expr) {
|
|
|
85
85
|
case "lambda":
|
|
86
86
|
return `/* __lambda__ */`;
|
|
87
87
|
case "method-call": {
|
|
88
|
+
// Structured-receiver path: when the method-call IR carries a receiverExpr
|
|
89
|
+
// (set by the safe.read().ok().fail() chain builder), render the receiver
|
|
90
|
+
// recursively so nested lambda args are NOT flattened into the callee
|
|
91
|
+
// string as /* __lambda__ */ placeholders. The flat callee path below
|
|
92
|
+
// destroys lambdas; this preserves them.
|
|
93
|
+
const receiverExpr = expr.receiverExpr;
|
|
94
|
+
const methodName = expr.methodName;
|
|
95
|
+
if (receiverExpr !== undefined && methodName !== undefined) {
|
|
96
|
+
const argsText = expr.args.map(a => renderExprAsText(a)).join(", ");
|
|
97
|
+
return `${renderExprAsText(receiverExpr)}.${methodName}(${argsText})`;
|
|
98
|
+
}
|
|
88
99
|
const argsText = expr.args.map(a => renderExprAsText(a)).join(", ");
|
|
89
100
|
return `${expr.callee}(${argsText})`;
|
|
90
101
|
}
|
|
@@ -25,7 +25,16 @@ import { hasLoadedFramework, getLoadedFramework } from '../framework-registry.js
|
|
|
25
25
|
* timing.delay_microseconds (esp_rom_delay_us) is. */
|
|
26
26
|
function isBlockingDelay(stmt, isRtos) {
|
|
27
27
|
const s = stmt;
|
|
28
|
-
//
|
|
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.
|
|
29
38
|
if (stmt.kind === 'call' && typeof s.callee === 'string') {
|
|
30
39
|
if (isRtos) {
|
|
31
40
|
// On RTOS targets, only delayMicroseconds is a busy-wait.
|
|
@@ -33,7 +42,7 @@ function isBlockingDelay(stmt, isRtos) {
|
|
|
33
42
|
return true;
|
|
34
43
|
}
|
|
35
44
|
else {
|
|
36
|
-
if (
|
|
45
|
+
if (delayNames.includes(s.callee))
|
|
37
46
|
return true;
|
|
38
47
|
}
|
|
39
48
|
}
|
|
@@ -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 {
|
|
@@ -427,6 +427,10 @@ const AWAITABLE_HAL_OPS = new Set([
|
|
|
427
427
|
"wifi.scan",
|
|
428
428
|
"http.send",
|
|
429
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",
|
|
430
434
|
]);
|
|
431
435
|
/**
|
|
432
436
|
* Rewrite an awaited hal-op statement (or a block whose LAST statement is
|
|
@@ -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
|
}
|
|
@@ -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) {
|