@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
|
@@ -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)) {
|
|
@@ -216,6 +248,108 @@ function analyzeExpression(expr, result, strategy) {
|
|
|
216
248
|
analyzeExpression(expr.expression, result, strategy);
|
|
217
249
|
}
|
|
218
250
|
break;
|
|
251
|
+
case "hal-expr":
|
|
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.
|
|
261
|
+
if (expr.operation && typeof expr.operation.operation === "string") {
|
|
262
|
+
const opName = expr.operation.operation;
|
|
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.")) {
|
|
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
|
+
}
|
|
327
|
+
if (opName.startsWith("http."))
|
|
328
|
+
result.usesHttp = true;
|
|
329
|
+
if (opName.startsWith("ble."))
|
|
330
|
+
result.usesBle = true;
|
|
331
|
+
if (opName.startsWith("preferences."))
|
|
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;
|
|
351
|
+
}
|
|
352
|
+
break;
|
|
219
353
|
}
|
|
220
354
|
}
|
|
221
355
|
/**
|
|
@@ -242,6 +376,11 @@ function analyzeStatement(statement, result, strategy) {
|
|
|
242
376
|
if (statement.callee === "millis" || statement.callee === "delay") {
|
|
243
377
|
result.usesMillis = true;
|
|
244
378
|
}
|
|
379
|
+
// Awaited HAL wait markers become async state-machine poll states that
|
|
380
|
+
// arm deadlines via currentTimeMillis().
|
|
381
|
+
if (statement.callee === "__WIFI_WAIT__" || statement.callee === "__HTTP_WAIT__" || statement.callee === "__HAL_WAIT__") {
|
|
382
|
+
result.usesMillis = true;
|
|
383
|
+
}
|
|
245
384
|
// Namespace-qualified polyfill entry points used as bare call statements
|
|
246
385
|
// (e.g. `Timing.delay(5);`). The expression-level analyzer (case
|
|
247
386
|
// "method-call") already checks these prefixes, but a statement-form call
|
|
@@ -264,7 +403,10 @@ function analyzeStatement(statement, result, strategy) {
|
|
|
264
403
|
if (statement.callee === "tone" || statement.callee === "noTone") {
|
|
265
404
|
result.usesTone = true;
|
|
266
405
|
}
|
|
267
|
-
|
|
406
|
+
// console.* is NOT UART: on ESP-IDF it lowers to printf, on Arduino to
|
|
407
|
+
// Serial via hasConsoleCalls. Only real UART HAL / Serial peripheral
|
|
408
|
+
// usage should gate the uart shim (avoids unused __tc_uart*_init).
|
|
409
|
+
if (statement.callee.startsWith("_uart_")) {
|
|
268
410
|
result.usesUart = true;
|
|
269
411
|
}
|
|
270
412
|
if (statement.callee.startsWith("Serial.") || statement.callee === "Serial") {
|
|
@@ -284,6 +426,18 @@ function analyzeStatement(statement, result, strategy) {
|
|
|
284
426
|
result.usesMap = true;
|
|
285
427
|
if (statement.callee === "constrain")
|
|
286
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
|
+
}
|
|
287
441
|
// The HAL resolver lowers WDT.*/Timing.* namespace calls to bare AVR
|
|
288
442
|
// library functions (WDT.reset() → wdt_reset(), Timing.delay() → delay(),
|
|
289
443
|
// Timing.millis() → millis()). When that happens the `WDT.`/`Timing.`
|
|
@@ -311,6 +465,27 @@ function analyzeStatement(statement, result, strategy) {
|
|
|
311
465
|
}
|
|
312
466
|
}
|
|
313
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
|
+
}
|
|
314
489
|
for (const arg of statement.args) {
|
|
315
490
|
analyzeExpression(arg, result, strategy);
|
|
316
491
|
}
|
|
@@ -424,6 +599,95 @@ function analyzeStatement(statement, result, strategy) {
|
|
|
424
599
|
result.usesTone = true;
|
|
425
600
|
if (opName.startsWith("uart."))
|
|
426
601
|
result.usesUart = true;
|
|
602
|
+
// Display HAL ops (display.init from ui.mount, display.flush per frame)
|
|
603
|
+
// always imply GPIO usage (CS/DC/RST pins). Native SPI/I2C display
|
|
604
|
+
// adapters emit their own transport #includes (driver/spi_master.h,
|
|
605
|
+
// driver/i2c_master.h) — we don't force usesSPI/usesI2C here because
|
|
606
|
+
// that would pull headers even for non-display SPI/I2C code, and the
|
|
607
|
+
// display's transport type can't be determined from the op alone.
|
|
608
|
+
if (opName.startsWith("display.")) {
|
|
609
|
+
result.usesGPIO = true;
|
|
610
|
+
result.usesDisplay = true;
|
|
611
|
+
}
|
|
612
|
+
// ESP32 peripheral usage — framework-esp32 gates IDF driver blocks
|
|
613
|
+
// and forced includes on these. No-op for other frameworks (their
|
|
614
|
+
// shimLines emit no CUTTLEFISH_* blocks with these marker names).
|
|
615
|
+
if (opName.startsWith("gpio."))
|
|
616
|
+
result.usesGPIO = true;
|
|
617
|
+
if (opName === "gpio.read")
|
|
618
|
+
result.usesDigitalRead = true;
|
|
619
|
+
if (opName.startsWith("pwm."))
|
|
620
|
+
result.usesPWM = true;
|
|
621
|
+
if (opName.startsWith("rmt."))
|
|
622
|
+
result.usesRmt = true;
|
|
623
|
+
if (opName.startsWith("adc."))
|
|
624
|
+
result.usesADC = true;
|
|
625
|
+
if (opName.startsWith("dac."))
|
|
626
|
+
result.usesDAC = true;
|
|
627
|
+
if (opName.startsWith("power."))
|
|
628
|
+
result.usesPower = true;
|
|
629
|
+
if (opName.startsWith("wdt."))
|
|
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
|
+
}
|
|
647
|
+
if (opName.startsWith("interrupt."))
|
|
648
|
+
result.usesInterrupts = true;
|
|
649
|
+
if (opName.startsWith("pulse."))
|
|
650
|
+
result.usesPulse = true;
|
|
651
|
+
if (opName.startsWith("shift."))
|
|
652
|
+
result.usesShift = true;
|
|
653
|
+
if (opName.startsWith("wifi.")) {
|
|
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
|
+
}
|
|
667
|
+
if (opName.startsWith("http."))
|
|
668
|
+
result.usesHttp = true;
|
|
669
|
+
if (opName.startsWith("ble."))
|
|
670
|
+
result.usesBle = true;
|
|
671
|
+
if (opName.startsWith("preferences."))
|
|
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;
|
|
427
691
|
// Timing HAL ops (timing.delay/millis/micros) carry a typed operation
|
|
428
692
|
// name, not raw code, so the regex scans below miss them. Mirror the
|
|
429
693
|
// raw-code timing detection here so usesMillis/usesTiming (and thus
|
|
@@ -461,13 +725,30 @@ function analyzeStatement(statement, result, strategy) {
|
|
|
461
725
|
result.usesTiming = true;
|
|
462
726
|
result.usesMillis = true;
|
|
463
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;
|
|
464
745
|
// Native AVR peripheral usage inside raw hal-op code (e.g. the
|
|
465
746
|
// EEPROM namespace lowers to `EEPROM.write(...)` in a raw hal-op;
|
|
466
747
|
// Serial/SPI/Wire may appear as lowered library calls too).
|
|
467
748
|
if (/\bEEPROM\b/.test(code) || /\beeprom_(read|write|update)_byte\b/.test(code)) {
|
|
468
749
|
result.usesEEPROM = true;
|
|
469
750
|
}
|
|
470
|
-
if (/\
|
|
751
|
+
if (/\bSerial\b/.test(code)) {
|
|
471
752
|
result.usesUart = true;
|
|
472
753
|
}
|
|
473
754
|
if (/\bSPI\b/.test(code)) {
|
|
@@ -535,6 +816,41 @@ export function analyzeProgram(program, strategy) {
|
|
|
535
816
|
hasGenerators: false,
|
|
536
817
|
usesStdMap: false,
|
|
537
818
|
timerCallCount: 0,
|
|
819
|
+
usesGPIO: false,
|
|
820
|
+
usesPWM: false,
|
|
821
|
+
usesRmt: false,
|
|
822
|
+
usesADC: false,
|
|
823
|
+
usesDAC: false,
|
|
824
|
+
usesPower: false,
|
|
825
|
+
usesWdt: false,
|
|
826
|
+
usesInterrupts: false,
|
|
827
|
+
usesPulse: false,
|
|
828
|
+
usesShift: false,
|
|
829
|
+
usesWifi: false,
|
|
830
|
+
usesWifiConnect: false,
|
|
831
|
+
usesWifiConnectBlocking: false,
|
|
832
|
+
usesWifiQuery: false,
|
|
833
|
+
usesWifiScan: false,
|
|
834
|
+
usesWifiConfig: false,
|
|
835
|
+
usesHttp: false,
|
|
836
|
+
usesBle: false,
|
|
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,
|
|
538
854
|
};
|
|
539
855
|
// Analyze type aliases
|
|
540
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
|
}
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import type { ProgramIR } from '../api/index.js';
|
|
2
2
|
import type { Diagnostic } from '../types.js';
|
|
3
|
+
import type { PlatformStrategy } from '../api/shared/index.js';
|
|
3
4
|
/**
|
|
4
5
|
* Detect blocking delay() calls inside loop()'s body. Emits a warning for
|
|
5
6
|
* each, explaining the cooperative-async alternative.
|
|
7
|
+
*
|
|
8
|
+
* On RTOS targets (ESP-IDF), timing.delay lowers to vTaskDelay which yields
|
|
9
|
+
* the CPU — it does NOT freeze the async queue or UI. Only delayMicroseconds
|
|
10
|
+
* (busy-wait) triggers the warning.
|
|
6
11
|
*/
|
|
7
|
-
export declare function validateBlockingDelayInLoop(program: ProgramIR): Diagnostic[];
|
|
12
|
+
export declare function validateBlockingDelayInLoop(program: ProgramIR, strategy?: PlatformStrategy): Diagnostic[];
|
|
@@ -12,20 +12,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,22 +65,37 @@ 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,
|
|
54
101
|
filePath: stmt.sourceSpan?.filePath,
|
|
@@ -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 {
|