@typecad/cuttlefish 0.1.0-alpha.2 → 1.0.0-alpha.11

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.
Files changed (224) hide show
  1. package/README.md +10 -10
  2. package/dist/api/board-types.d.ts +1 -1
  3. package/dist/api/config.d.ts +38 -2
  4. package/dist/api/index.d.ts +1 -1
  5. package/dist/api/schema/types.d.ts +15 -0
  6. package/dist/api/shared/async-runtime-static.d.ts +12 -1
  7. package/dist/api/shared/async-runtime-static.js +69 -24
  8. package/dist/api/shared/async-symbol-detector.d.ts +11 -0
  9. package/dist/api/shared/async-symbol-detector.js +140 -0
  10. package/dist/api/shared/async-types.d.ts +24 -0
  11. package/dist/api/shared/coop-scheduler.d.ts +60 -0
  12. package/dist/api/shared/coop-scheduler.js +149 -0
  13. package/dist/api/shared/display-adapter.d.ts +4 -3
  14. package/dist/api/shared/display-adapter.js +15 -118
  15. package/dist/api/shared/display-adapters/sdl.js +38 -31
  16. package/dist/api/shared/display-profile.d.ts +47 -25
  17. package/dist/api/shared/display-profile.js +31 -164
  18. package/dist/api/shared/framework-manifest-registry.d.ts +9 -0
  19. package/dist/api/shared/framework-manifest-registry.js +27 -0
  20. package/dist/api/shared/framework-manifest.d.ts +595 -0
  21. package/dist/api/shared/framework-manifest.js +193 -0
  22. package/dist/api/shared/glcdfont.d.ts +12 -0
  23. package/dist/api/shared/glcdfont.js +124 -0
  24. package/dist/api/shared/graphics-strategy.d.ts +37 -0
  25. package/dist/api/shared/hal-op-ir.d.ts +764 -1
  26. package/dist/api/shared/hal-op-ir.js +129 -1
  27. package/dist/api/shared/index.d.ts +22 -2
  28. package/dist/api/shared/index.js +26 -1
  29. package/dist/api/shared/ir-core.d.ts +4 -0
  30. package/dist/api/shared/ir-declarations.d.ts +6 -0
  31. package/dist/api/shared/native-display-op-resolver.d.ts +10 -0
  32. package/dist/api/shared/native-display-op-resolver.js +64 -0
  33. package/dist/api/shared/platform-strategy.d.ts +119 -2
  34. package/dist/api/shared/polyfill-helper-registry.js +18 -1
  35. package/dist/api/shared/promise-runtime.d.ts +1 -1
  36. package/dist/api/shared/promise-runtime.js +95 -13
  37. package/dist/api/shared/spdx-licenses.d.ts +139 -0
  38. package/dist/api/shared/spdx-licenses.js +385 -0
  39. package/dist/api/shared/toolchain-types.d.ts +17 -0
  40. package/dist/api/shared/types.d.ts +8 -0
  41. package/dist/api/shared/validate-framework-manifest.d.ts +28 -0
  42. package/dist/api/shared/validate-framework-manifest.js +494 -0
  43. package/dist/api/shared/worker-runtime-polyfill.d.ts +22 -0
  44. package/dist/api/shared/worker-runtime-polyfill.js +34 -0
  45. package/dist/api/shared/worker-runtime.d.ts +69 -0
  46. package/dist/api/shared/worker-runtime.js +163 -0
  47. package/dist/cli-utils.d.ts +1 -0
  48. package/dist/cli-utils.js +3 -1
  49. package/dist/cli.js +249 -32
  50. package/dist/config-loader.d.ts +7 -2
  51. package/dist/config-loader.js +23 -3
  52. package/dist/config-schema.d.ts +112 -70
  53. package/dist/config-schema.js +14 -0
  54. package/dist/contract/board-generator.d.ts +17 -0
  55. package/dist/contract/board-generator.js +57 -0
  56. package/dist/contract/contract-parser.d.ts +217 -0
  57. package/dist/contract/contract-parser.js +224 -0
  58. package/dist/contract/index.d.ts +22 -0
  59. package/dist/contract/index.js +68 -0
  60. package/dist/create/board-codegen.js +4 -4
  61. package/dist/create/board-generators.js +4 -5
  62. package/dist/create/board-spec.d.ts +72 -75
  63. package/dist/create/board-spec.js +0 -1
  64. package/dist/create/framework-catalog.d.ts +64 -0
  65. package/dist/create/framework-catalog.js +136 -0
  66. package/dist/create/index.d.ts +5 -1
  67. package/dist/create/index.js +3 -1
  68. package/dist/create/init-scaffold.d.ts +12 -3
  69. package/dist/create/init-scaffold.js +49 -14
  70. package/dist/create/init-templates.d.ts +4 -0
  71. package/dist/create/init-templates.js +271 -17
  72. package/dist/create/init-wizard.js +41 -27
  73. package/dist/create/install-deps.d.ts +32 -0
  74. package/dist/create/install-deps.js +46 -0
  75. package/dist/debug/preprocessor.js +178 -39
  76. package/dist/debug/types.d.ts +23 -0
  77. package/dist/diagnostics/mermaid-builder.d.ts +1 -1
  78. package/dist/diagnostics/mermaid-builder.js +34 -24
  79. package/dist/emit/compliance/arxml-writer.d.ts +11 -0
  80. package/dist/emit/compliance/arxml-writer.js +34 -0
  81. package/dist/emit/compliance/compliance-context.d.ts +57 -0
  82. package/dist/emit/compliance/compliance-context.js +113 -0
  83. package/dist/emit/compliance/deviation-ledger.d.ts +27 -0
  84. package/dist/emit/compliance/deviation-ledger.js +47 -0
  85. package/dist/emit/compliance/deviation-writer.d.ts +30 -0
  86. package/dist/emit/compliance/deviation-writer.js +37 -0
  87. package/dist/emit/compliance/index.d.ts +7 -0
  88. package/dist/emit/compliance/index.js +6 -0
  89. package/dist/emit/compliance/rule-engine.d.ts +13 -0
  90. package/dist/emit/compliance/rule-engine.js +101 -0
  91. package/dist/emit/compliance/rules.d.ts +16 -0
  92. package/dist/emit/compliance/rules.js +191 -0
  93. package/dist/emit/compliance/types.d.ts +59 -0
  94. package/dist/emit/compliance/types.js +8 -0
  95. package/dist/emit/cpp-emitter.js +4 -3
  96. package/dist/emit/emitters/class-emitter.js +6 -1
  97. package/dist/emit/emitters/emitter-context.d.ts +28 -1
  98. package/dist/emit/emitters/function-emitter-impl.js +153 -53
  99. package/dist/emit/emitters/line-appender.js +19 -0
  100. package/dist/emit/emitters/line-marker.d.ts +38 -0
  101. package/dist/emit/emitters/line-marker.js +39 -0
  102. package/dist/emit/emitters/output-finalizer.d.ts +6 -0
  103. package/dist/emit/emitters/output-finalizer.js +101 -12
  104. package/dist/emit/emitters/setup.d.ts +17 -0
  105. package/dist/emit/emitters/setup.js +372 -30
  106. package/dist/emit/emitters/top-level-prep.js +8 -0
  107. package/dist/emit/emitters/type-decl-emitter.js +28 -3
  108. package/dist/emit/emitters/ui-emitter.js +33 -9
  109. package/dist/emit/expression-renderer.d.ts +1 -1
  110. package/dist/emit/expression-renderer.js +91 -6
  111. package/dist/emit/route-hal-op.js +18 -5
  112. package/dist/emit/snprintf-helpers.js +15 -4
  113. package/dist/emit/statement-renderer.d.ts +10 -0
  114. package/dist/emit/statement-renderer.js +34 -5
  115. package/dist/emit/utils/async-state-machine.js +221 -125
  116. package/dist/emit/utils/hal-op-cpp-type.d.ts +6 -0
  117. package/dist/emit/utils/hal-op-cpp-type.js +40 -0
  118. package/dist/framework-package.js +2 -0
  119. package/dist/framework-registry.d.ts +17 -0
  120. package/dist/install/framework-catalog.d.ts +53 -0
  121. package/dist/install/framework-catalog.js +107 -0
  122. package/dist/install/handle-install.d.ts +35 -0
  123. package/dist/install/handle-install.js +177 -0
  124. package/dist/install/index.d.ts +4 -0
  125. package/dist/install/index.js +3 -0
  126. package/dist/ir/adc-range-validation.js +40 -26
  127. package/dist/ir/build-ir-state.d.ts +1 -0
  128. package/dist/ir/build-ir-state.js +14 -0
  129. package/dist/ir/build-ir.js +9 -5
  130. package/dist/ir/call-graph.js +16 -0
  131. package/dist/ir/expression-to-ir.js +207 -6
  132. package/dist/ir/feature-registry.js +7 -25
  133. package/dist/ir/function-builder.js +22 -0
  134. package/dist/ir/hal/hal-emitter.d.ts +5 -2
  135. package/dist/ir/hal/hal-emitter.js +50 -18
  136. package/dist/ir/hal/hal-parser.d.ts +6 -0
  137. package/dist/ir/hal/hal-parser.js +81 -0
  138. package/dist/ir/hal/hal-plugins.js +733 -1
  139. package/dist/ir/identifier-collector.js +35 -0
  140. package/dist/ir/interrupt-analysis.d.ts +5 -1
  141. package/dist/ir/interrupt-analysis.js +16 -19
  142. package/dist/ir/memory-budget-validation.js +1 -0
  143. package/dist/ir/network-validation.d.ts +4 -0
  144. package/dist/ir/network-validation.js +184 -0
  145. package/dist/ir/ownership-analysis.js +39 -1
  146. package/dist/ir/peripheral-ownership.js +5 -0
  147. package/dist/ir/peripheral-validation.d.ts +1 -1
  148. package/dist/ir/peripheral-validation.js +6 -3
  149. package/dist/ir/pin-alias-conflict.d.ts +1 -1
  150. package/dist/ir/pin-alias-conflict.js +2 -1
  151. package/dist/ir/pin-capability-validation.js +71 -32
  152. package/dist/ir/pin-mode-validation.d.ts +2 -2
  153. package/dist/ir/pin-mode-validation.js +54 -18
  154. package/dist/ir/pin-safety.d.ts +1 -1
  155. package/dist/ir/pin-safety.js +2 -1
  156. package/dist/ir/program-analysis.d.ts +81 -0
  157. package/dist/ir/program-analysis.js +429 -0
  158. package/dist/ir/pulldown-validation.d.ts +1 -1
  159. package/dist/ir/pulldown-validation.js +2 -1
  160. package/dist/ir/pwm-timer-sharing.d.ts +1 -1
  161. package/dist/ir/pwm-timer-sharing.js +2 -1
  162. package/dist/ir/render-expr.js +11 -0
  163. package/dist/ir/resource-analysis.js +2 -0
  164. package/dist/ir/timer0-pwm-timing-conflict.d.ts +1 -1
  165. package/dist/ir/timer0-pwm-timing-conflict.js +2 -1
  166. package/dist/ir/timing-validation.d.ts +6 -1
  167. package/dist/ir/timing-validation.js +61 -13
  168. package/dist/ir/transformers/call-statement.js +105 -0
  169. package/dist/ir/transformers/expressions.js +62 -0
  170. package/dist/ir/transformers/hal-call-resolver.js +21 -0
  171. package/dist/ir/transformers/hal-emit-helpers.js +1 -1
  172. package/dist/ir/transformers/namespace-methods.js +17 -12
  173. package/dist/ir/transformers/ui-reactive.js +2 -2
  174. package/dist/ir/transformers/variables.js +137 -19
  175. package/dist/ir/try-catch-validation.js +2 -0
  176. package/dist/ir/type-resolution.js +20 -2
  177. package/dist/ir/ui-element-auto-wire.js +7 -5
  178. package/dist/ir/unit-suspicion-validation.js +9 -7
  179. package/dist/ir/utils/map-statements.d.ts +4 -0
  180. package/dist/ir/utils/map-statements.js +79 -0
  181. package/dist/ir/validation-orchestrator.js +15 -8
  182. package/dist/ir/worker-analysis.d.ts +10 -0
  183. package/dist/ir/worker-analysis.js +261 -0
  184. package/dist/libdef/c-to-decl.d.ts +27 -0
  185. package/dist/libdef/c-to-decl.js +397 -0
  186. package/dist/libdef/component-decls.d.ts +2 -0
  187. package/dist/libdef/component-decls.js +6 -0
  188. package/dist/libdef/component-discovery.d.ts +43 -0
  189. package/dist/libdef/component-discovery.js +83 -0
  190. package/dist/libdef/cpp-to-decl.d.ts +9 -0
  191. package/dist/libdef/cpp-to-decl.js +72 -0
  192. package/dist/libdef/registry.js +5 -2
  193. package/dist/lint-cache.d.ts +59 -0
  194. package/dist/lint-cache.js +257 -0
  195. package/dist/orchestrator/graph-builder.js +14 -9
  196. package/dist/platform/coop-scheduler-runtime.d.ts +19 -0
  197. package/dist/platform/coop-scheduler-runtime.js +41 -0
  198. package/dist/platform/generic-strategy.d.ts +15 -3
  199. package/dist/platform/generic-strategy.js +49 -4
  200. package/dist/safety/safety-bridge.d.ts +11 -0
  201. package/dist/safety/safety-bridge.js +48 -0
  202. package/dist/safety/sidecar-bridge.d.ts +5 -0
  203. package/dist/safety/sidecar-bridge.js +22 -0
  204. package/dist/safety-hook.d.ts +79 -0
  205. package/dist/safety-hook.js +35 -0
  206. package/dist/testing.d.ts +8 -2
  207. package/dist/testing.js +9 -2
  208. package/dist/transpile.d.ts +3 -0
  209. package/dist/transpile.js +191 -49
  210. package/dist/types.d.ts +22 -3
  211. package/dist/ui-hook.d.ts +17 -3
  212. package/dist/utils/cli.js +123 -2
  213. package/dist/utils/fs.d.ts +13 -0
  214. package/dist/utils/fs.js +50 -0
  215. package/dist/utils/ui.js +3 -1
  216. package/package.json +23 -3
  217. package/dist/api/shared/display-adapters/eink-mono.d.ts +0 -2
  218. package/dist/api/shared/display-adapters/eink-mono.js +0 -53
  219. package/dist/api/shared/display-adapters/ssd1309.d.ts +0 -2
  220. package/dist/api/shared/display-adapters/ssd1309.js +0 -136
  221. package/dist/api/shared/display-adapters/st7796.d.ts +0 -2
  222. package/dist/api/shared/display-adapters/st7796.js +0 -132
  223. package/dist/ir/heap-array-validation.d.ts +0 -24
  224. package/dist/ir/heap-array-validation.js +0 -29
@@ -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)) {
@@ -102,6 +134,30 @@ function analyzeExpression(expr, result, strategy) {
102
134
  if (expr.callee.startsWith("WDT.") || expr.callee === "WDT") {
103
135
  result.usesWDT = true;
104
136
  }
137
+ // Native AVR peripheral usage from namespace-prefixed method calls
138
+ // (Serial.* / SPI.* / Wire.* / EEPROM.*). The HAL resolver lowers these
139
+ // to structured hal-ops (detected in analyzeStatement) OR to bare
140
+ // lowered calls; these checks cover the pre-lowering and direct forms.
141
+ if (expr.callee.startsWith("Serial.") || expr.callee === "Serial") {
142
+ result.usesUart = true;
143
+ }
144
+ if (expr.callee.startsWith("SPI.") || expr.callee === "SPI") {
145
+ result.usesSPI = true;
146
+ }
147
+ if (expr.callee.startsWith("Wire.") || expr.callee === "Wire") {
148
+ result.usesI2C = true;
149
+ }
150
+ if (expr.callee.startsWith("EEPROM.") || expr.callee === "EEPROM") {
151
+ result.usesEEPROM = true;
152
+ }
153
+ // map()/constrain() Arduino-API calls appear as method-call exprs
154
+ // (callee "map"/"constrain"). framework-avr lowers these to _native_map/
155
+ // _native_constrain via symbol aliases, so the helpers are dead code
156
+ // unless the program actually calls them.
157
+ if (expr.callee === "map")
158
+ result.usesMap = true;
159
+ if (expr.callee === "constrain")
160
+ result.usesConstrain = true;
105
161
  // Count setInterval/setTimeout call sites (post-rename callee names) so
106
162
  // __tc_TimerRuntime::MAX_TIMERS can be sized to the observed count.
107
163
  if (expr.callee === "__tc_setInterval" || expr.callee === "__tc_setTimeout"
@@ -192,6 +248,108 @@ function analyzeExpression(expr, result, strategy) {
192
248
  analyzeExpression(expr.expression, result, strategy);
193
249
  }
194
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;
195
353
  }
196
354
  }
197
355
  /**
@@ -218,6 +376,11 @@ function analyzeStatement(statement, result, strategy) {
218
376
  if (statement.callee === "millis" || statement.callee === "delay") {
219
377
  result.usesMillis = true;
220
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
+ }
221
384
  // Namespace-qualified polyfill entry points used as bare call statements
222
385
  // (e.g. `Timing.delay(5);`). The expression-level analyzer (case
223
386
  // "method-call") already checks these prefixes, but a statement-form call
@@ -234,6 +397,47 @@ function analyzeStatement(statement, result, strategy) {
234
397
  if (statement.callee.startsWith("WDT.") || statement.callee === "WDT") {
235
398
  result.usesWDT = true;
236
399
  }
400
+ // Native AVR peripheral usage from statement-form calls. tone()/noTone()
401
+ // are bare Arduino-API calls; console.* / Serial.* drive UART; the
402
+ // namespace prefixes mirror the method-call checks above.
403
+ if (statement.callee === "tone" || statement.callee === "noTone") {
404
+ result.usesTone = true;
405
+ }
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_")) {
410
+ result.usesUart = true;
411
+ }
412
+ if (statement.callee.startsWith("Serial.") || statement.callee === "Serial") {
413
+ result.usesUart = true;
414
+ }
415
+ if (statement.callee.startsWith("SPI.") || statement.callee === "SPI") {
416
+ result.usesSPI = true;
417
+ }
418
+ if (statement.callee.startsWith("Wire.") || statement.callee === "Wire") {
419
+ result.usesI2C = true;
420
+ }
421
+ if (statement.callee.startsWith("EEPROM.") || statement.callee === "EEPROM") {
422
+ result.usesEEPROM = true;
423
+ }
424
+ // Statement-form map()/constrain() mirror the method-call checks above.
425
+ if (statement.callee === "map")
426
+ result.usesMap = true;
427
+ if (statement.callee === "constrain")
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
+ }
237
441
  // The HAL resolver lowers WDT.*/Timing.* namespace calls to bare AVR
238
442
  // library functions (WDT.reset() → wdt_reset(), Timing.delay() → delay(),
239
443
  // Timing.millis() → millis()). When that happens the `WDT.`/`Timing.`
@@ -261,6 +465,27 @@ function analyzeStatement(statement, result, strategy) {
261
465
  }
262
466
  }
263
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
+ }
264
489
  for (const arg of statement.args) {
265
490
  analyzeExpression(arg, result, strategy);
266
491
  }
@@ -357,6 +582,122 @@ function analyzeStatement(statement, result, strategy) {
357
582
  }
358
583
  break;
359
584
  case "hal-op":
585
+ // Structured HAL ops carry a typed operation name (e.g. "spi.begin",
586
+ // "i2c.read_byte", "tone.play", "uart.write") rather than raw code.
587
+ // Detect peripheral usage here so framework-avr's driver shims can be
588
+ // gated on actual use. Without this, SPI0.begin() → spi.begin hal-op
589
+ // would be invisible (no raw code to scan) and the SPI driver would
590
+ // always be emitted. Same blind spot the wdt.* fix below the raw-code
591
+ // block addresses for the watchdog.
592
+ if (statement.operation && typeof statement.operation.operation === "string") {
593
+ const opName = statement.operation.operation;
594
+ if (opName.startsWith("spi."))
595
+ result.usesSPI = true;
596
+ if (opName.startsWith("i2c."))
597
+ result.usesI2C = true;
598
+ if (opName.startsWith("tone."))
599
+ result.usesTone = true;
600
+ if (opName.startsWith("uart."))
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;
691
+ // Timing HAL ops (timing.delay/millis/micros) carry a typed operation
692
+ // name, not raw code, so the regex scans below miss them. Mirror the
693
+ // raw-code timing detection here so usesMillis/usesTiming (and thus
694
+ // usesNativeTiming) fire for `delay()`/`millis()` on AVR.
695
+ if (opName === "timing.delay" || opName === "timing.delay_microseconds"
696
+ || opName === "timing.millis" || opName === "timing.micros") {
697
+ result.usesTiming = true;
698
+ result.usesMillis = true;
699
+ }
700
+ }
360
701
  // Scan raw C++ code in HAL ops for polyfill helper usage
361
702
  if (statement.operation && statement.operation.operation === "raw" && typeof statement.operation.code === "string") {
362
703
  const code = statement.operation.code;
@@ -384,6 +725,38 @@ function analyzeStatement(statement, result, strategy) {
384
725
  result.usesTiming = true;
385
726
  result.usesMillis = true;
386
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;
745
+ // Native AVR peripheral usage inside raw hal-op code (e.g. the
746
+ // EEPROM namespace lowers to `EEPROM.write(...)` in a raw hal-op;
747
+ // Serial/SPI/Wire may appear as lowered library calls too).
748
+ if (/\bEEPROM\b/.test(code) || /\beeprom_(read|write|update)_byte\b/.test(code)) {
749
+ result.usesEEPROM = true;
750
+ }
751
+ if (/\bSerial\b/.test(code)) {
752
+ result.usesUart = true;
753
+ }
754
+ if (/\bSPI\b/.test(code)) {
755
+ result.usesSPI = true;
756
+ }
757
+ if (/\bWire\b/.test(code)) {
758
+ result.usesI2C = true;
759
+ }
387
760
  }
388
761
  break;
389
762
  case "update":
@@ -431,10 +804,53 @@ export function analyzeProgram(program, strategy) {
431
804
  usesTiming: false,
432
805
  usesWDT: false,
433
806
  usesStrPtr: false,
807
+ usesUart: false,
808
+ usesSPI: false,
809
+ usesI2C: false,
810
+ usesEEPROM: false,
811
+ usesTone: false,
812
+ usesMap: false,
813
+ usesConstrain: false,
814
+ usesNativeTiming: false,
434
815
  hasSerialBegin: false,
435
816
  hasGenerators: false,
436
817
  usesStdMap: false,
437
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,
438
854
  };
439
855
  // Analyze type aliases
440
856
  for (const typeAlias of program.typeAliases) {
@@ -585,5 +1001,18 @@ export function analyzeProgram(program, strategy) {
585
1001
  if (loweredConsoleInCallback()) {
586
1002
  result.hasConsoleCalls = true;
587
1003
  }
1004
+ // Derive the comprehensive native-timing gate for the AVR millis() Timer0
1005
+ // ISR. The ISR is needed whenever the program touches the soft clock
1006
+ // directly (millis/delay/micros) OR has a hidden consumer: setInterval/
1007
+ // setTimeout (the scheduler polls millis), or async functions (the runtime
1008
+ // polls millis). The per-frame UI tick is injected by the emitter, not
1009
+ // present in user source — the setup emitter ORs entryHasUI() in at the
1010
+ // consume site. Without this gate, every AVR program pulled in the Timer0
1011
+ // ISR even when it never uses timing.
1012
+ const hasAsync = program.functions.some(fn => fn.isAsync);
1013
+ result.usesNativeTiming = result.usesMillis
1014
+ || result.usesTiming
1015
+ || result.timerCallCount > 0
1016
+ || hasAsync;
588
1017
  return result;
589
1018
  }
@@ -4,4 +4,4 @@ import type { BoardConstants } from './board-resolver.js';
4
4
  /**
5
5
  * Validate that pins used with inputPullDown() support pulldown on this board.
6
6
  */
7
- export declare function validatePulldownSupport(usage: PeripheralUsage, boardConstants: BoardConstants | undefined): Diagnostic[];
7
+ export declare function validatePulldownSupport(usage: PeripheralUsage, boardConstants: BoardConstants | undefined, filePath: string): Diagnostic[];
@@ -7,7 +7,7 @@
7
7
  /**
8
8
  * Validate that pins used with inputPullDown() support pulldown on this board.
9
9
  */
10
- export function validatePulldownSupport(usage, boardConstants) {
10
+ export function validatePulldownSupport(usage, boardConstants, filePath) {
11
11
  const diagnostics = [];
12
12
  if (usage.inputPulldownPins.size === 0) {
13
13
  return diagnostics;
@@ -22,6 +22,7 @@ export function validatePulldownSupport(usage, boardConstants) {
22
22
  diagnostics.push({
23
23
  code: 'pulldown-not-supported',
24
24
  message: `${pinName ?? `pin ${pinNumber}`} does not support hardware pulldown${arch ? ` on ${arch.toUpperCase()} boards` : ''}. Use ${pinName ?? 'pin'}.asInput() or ${pinName ?? 'pin'}.inputPullUp() instead.`,
25
+ filePath,
25
26
  source: 'pulldown-validation',
26
27
  severity: 'error',
27
28
  });
@@ -1,4 +1,4 @@
1
1
  import type { Diagnostic } from '../types.js';
2
2
  import type { BoardConstants } from './board-resolver.js';
3
3
  import type { PeripheralUsage } from './peripheral-usage.js';
4
- export declare function validatePWMTimerSharing(usage: PeripheralUsage, boardConstants: BoardConstants | undefined): Diagnostic[];
4
+ export declare function validatePWMTimerSharing(usage: PeripheralUsage, boardConstants: BoardConstants | undefined, filePath: string): Diagnostic[];
@@ -52,7 +52,7 @@ function getPwmTimerPins(boardConstants) {
52
52
  }
53
53
  return Array.from(pinsByIndex.values()).filter(pin => pin.timerId.length > 0);
54
54
  }
55
- export function validatePWMTimerSharing(usage, boardConstants) {
55
+ export function validatePWMTimerSharing(usage, boardConstants, filePath) {
56
56
  if (usage.pwmPinsUsed.size < 2 || !boardConstants) {
57
57
  return [];
58
58
  }
@@ -85,6 +85,7 @@ export function validatePWMTimerSharing(usage, boardConstants) {
85
85
  diagnostics.push({
86
86
  severity: 'info',
87
87
  code: 'pwm-timer-sharing',
88
+ filePath,
88
89
  source: 'pwm-timer-sharing',
89
90
  message: `${references.join(', ')} share ${timerId}${boardLabel}. Duty cycle can differ per pin, but timer-wide PWM settings are shared across that group. Prefer pins on different timer groups if you need independent PWM timing behavior.`,
90
91
  });
@@ -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
  }
@@ -34,6 +34,7 @@ export function analyzeResources(program, strategy) {
34
34
  diagnostics.push({
35
35
  severity: 'warning',
36
36
  message: `${pinRef} is configured as ${peripheralName} ${func.role.toUpperCase()}. Using it as GPIO may interfere with communication.\n → ${suggestion}`,
37
+ filePath: program.fileName,
37
38
  code: 'peripheral-pin-conflict',
38
39
  source: 'resource-analysis',
39
40
  });
@@ -58,6 +59,7 @@ export function analyzeResources(program, strategy) {
58
59
  severity: 'error',
59
60
  message: `Hardware Conflict: Pin '${pinName}' is required by both ${peripheralName} and ${other}.`,
60
61
  hint: `Move one of the peripherals to different pins if your board supports remapping, or avoid using both simultaneously.`,
62
+ filePath: program.fileName,
61
63
  code: 'peripheral-peripheral-conflict',
62
64
  source: 'resource-analysis',
63
65
  });
@@ -1,4 +1,4 @@
1
1
  import type { Diagnostic } from '../types.js';
2
2
  import type { BoardConstants } from './board-resolver.js';
3
3
  import type { PeripheralUsage } from './peripheral-usage.js';
4
- export declare function validateTimer0PWMTimingConflict(usage: PeripheralUsage, boardConstants: BoardConstants | undefined): Diagnostic[];
4
+ export declare function validateTimer0PWMTimingConflict(usage: PeripheralUsage, boardConstants: BoardConstants | undefined, filePath: string): Diagnostic[];
@@ -43,7 +43,7 @@ function getTimer0PwmPins(boardConstants) {
43
43
  }
44
44
  return timer0Pins;
45
45
  }
46
- export function validateTimer0PWMTimingConflict(usage, boardConstants) {
46
+ export function validateTimer0PWMTimingConflict(usage, boardConstants, filePath) {
47
47
  if (!usage.timer0 || usage.pwmPinsUsed.size === 0) {
48
48
  return [];
49
49
  }
@@ -65,6 +65,7 @@ export function validateTimer0PWMTimingConflict(usage, boardConstants) {
65
65
  return [{
66
66
  severity: 'info',
67
67
  code: 'timer0-pwm-timing-conflict',
68
+ filePath,
68
69
  source: 'timer0-pwm-timing-conflict',
69
70
  message: `${pinReferences.join(', ')} use Timer0 PWM${boardLabel}, and your program also relies on Timer0-backed timing APIs such as delay(), millis(), or micros(). This coupling is common on AVR boards, so prefer non-Timer0 PWM pins when you want PWM behavior isolated from core timing.`,
70
71
  }];
@@ -1,7 +1,12 @@
1
1
  import type { ProgramIR } from '../api/index.js';
2
2
  import type { Diagnostic } from '../types.js';
3
+ import type { PlatformStrategy } from '../api/shared/index.js';
3
4
  /**
4
5
  * Detect blocking delay() calls inside loop()'s body. Emits a warning for
5
6
  * each, explaining the cooperative-async alternative.
7
+ *
8
+ * On RTOS targets (ESP-IDF), timing.delay lowers to vTaskDelay which yields
9
+ * the CPU — it does NOT freeze the async queue or UI. Only delayMicroseconds
10
+ * (busy-wait) triggers the warning.
6
11
  */
7
- export declare function validateBlockingDelayInLoop(program: ProgramIR): Diagnostic[];
12
+ export declare function validateBlockingDelayInLoop(program: ProgramIR, strategy?: PlatformStrategy): Diagnostic[];