@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.
Files changed (167) hide show
  1. package/dist/api/config.d.ts +32 -0
  2. package/dist/api/index.d.ts +1 -1
  3. package/dist/api/schema/types.d.ts +15 -0
  4. package/dist/api/shared/async-runtime-static.d.ts +12 -1
  5. package/dist/api/shared/async-runtime-static.js +69 -24
  6. package/dist/api/shared/async-symbol-detector.d.ts +11 -0
  7. package/dist/api/shared/async-symbol-detector.js +140 -0
  8. package/dist/api/shared/async-types.d.ts +24 -0
  9. package/dist/api/shared/coop-scheduler.d.ts +60 -0
  10. package/dist/api/shared/coop-scheduler.js +149 -0
  11. package/dist/api/shared/display-adapter.d.ts +2 -2
  12. package/dist/api/shared/display-adapter.js +9 -119
  13. package/dist/api/shared/display-adapters/sdl.js +29 -29
  14. package/dist/api/shared/display-profile.d.ts +38 -33
  15. package/dist/api/shared/display-profile.js +22 -170
  16. package/dist/api/shared/framework-manifest-registry.d.ts +1 -1
  17. package/dist/api/shared/framework-manifest-registry.js +8 -6
  18. package/dist/api/shared/framework-manifest.d.ts +147 -28
  19. package/dist/api/shared/framework-manifest.js +35 -2
  20. package/dist/api/shared/graphics-strategy.d.ts +15 -6
  21. package/dist/api/shared/hal-op-ir.d.ts +340 -3
  22. package/dist/api/shared/hal-op-ir.js +34 -0
  23. package/dist/api/shared/index.d.ts +9 -1
  24. package/dist/api/shared/index.js +11 -1
  25. package/dist/api/shared/ir-core.d.ts +4 -0
  26. package/dist/api/shared/ir-declarations.d.ts +6 -0
  27. package/dist/api/shared/platform-strategy.d.ts +104 -2
  28. package/dist/api/shared/polyfill-helper-registry.js +18 -1
  29. package/dist/api/shared/promise-runtime.d.ts +1 -1
  30. package/dist/api/shared/promise-runtime.js +17 -13
  31. package/dist/api/shared/toolchain-types.d.ts +17 -0
  32. package/dist/api/shared/validate-framework-manifest.js +77 -0
  33. package/dist/api/shared/worker-runtime-polyfill.d.ts +22 -0
  34. package/dist/api/shared/worker-runtime-polyfill.js +34 -0
  35. package/dist/api/shared/worker-runtime.d.ts +69 -0
  36. package/dist/api/shared/worker-runtime.js +163 -0
  37. package/dist/cli.js +125 -121
  38. package/dist/config-loader.d.ts +7 -2
  39. package/dist/config-loader.js +20 -19
  40. package/dist/config-schema.d.ts +107 -65
  41. package/dist/config-schema.js +14 -0
  42. package/dist/contract/board-generator.d.ts +17 -0
  43. package/dist/contract/board-generator.js +57 -0
  44. package/dist/contract/contract-parser.d.ts +217 -0
  45. package/dist/contract/contract-parser.js +224 -0
  46. package/dist/contract/index.d.ts +22 -0
  47. package/dist/contract/index.js +68 -0
  48. package/dist/create/board-codegen.js +4 -4
  49. package/dist/create/board-generators.js +4 -5
  50. package/dist/create/board-spec.d.ts +74 -77
  51. package/dist/create/board-spec.js +0 -1
  52. package/dist/create/init-scaffold.d.ts +7 -3
  53. package/dist/create/init-scaffold.js +0 -62
  54. package/dist/create/init-templates.js +62 -16
  55. package/dist/create/init-wizard.js +32 -27
  56. package/dist/debug/preprocessor.js +178 -39
  57. package/dist/debug/types.d.ts +23 -0
  58. package/dist/diagnostics/mermaid-builder.d.ts +1 -1
  59. package/dist/diagnostics/mermaid-builder.js +34 -24
  60. package/dist/emit/compliance/arxml-writer.d.ts +11 -0
  61. package/dist/emit/compliance/arxml-writer.js +34 -0
  62. package/dist/emit/compliance/compliance-context.d.ts +57 -0
  63. package/dist/emit/compliance/compliance-context.js +113 -0
  64. package/dist/emit/compliance/deviation-ledger.d.ts +27 -0
  65. package/dist/emit/compliance/deviation-ledger.js +47 -0
  66. package/dist/emit/compliance/deviation-writer.d.ts +30 -0
  67. package/dist/emit/compliance/deviation-writer.js +37 -0
  68. package/dist/emit/compliance/index.d.ts +7 -0
  69. package/dist/emit/compliance/index.js +6 -0
  70. package/dist/emit/compliance/rule-engine.d.ts +13 -0
  71. package/dist/emit/compliance/rule-engine.js +101 -0
  72. package/dist/emit/compliance/rules.d.ts +16 -0
  73. package/dist/emit/compliance/rules.js +191 -0
  74. package/dist/emit/compliance/types.d.ts +59 -0
  75. package/dist/emit/compliance/types.js +8 -0
  76. package/dist/emit/emitters/class-emitter.js +6 -1
  77. package/dist/emit/emitters/emitter-context.d.ts +23 -1
  78. package/dist/emit/emitters/function-emitter-impl.js +96 -6
  79. package/dist/emit/emitters/line-appender.js +19 -0
  80. package/dist/emit/emitters/line-marker.d.ts +38 -0
  81. package/dist/emit/emitters/line-marker.js +39 -0
  82. package/dist/emit/emitters/output-finalizer.js +80 -1
  83. package/dist/emit/emitters/setup.d.ts +17 -0
  84. package/dist/emit/emitters/setup.js +217 -36
  85. package/dist/emit/emitters/top-level-prep.js +6 -0
  86. package/dist/emit/emitters/type-decl-emitter.js +28 -3
  87. package/dist/emit/emitters/ui-emitter.js +17 -8
  88. package/dist/emit/expression-renderer.d.ts +1 -1
  89. package/dist/emit/expression-renderer.js +81 -5
  90. package/dist/emit/route-hal-op.js +18 -5
  91. package/dist/emit/snprintf-helpers.js +7 -4
  92. package/dist/emit/statement-renderer.d.ts +10 -0
  93. package/dist/emit/statement-renderer.js +21 -5
  94. package/dist/emit/utils/async-state-machine.js +39 -12
  95. package/dist/framework-package.js +2 -0
  96. package/dist/framework-registry.d.ts +17 -0
  97. package/dist/install/framework-catalog.d.ts +53 -0
  98. package/dist/install/framework-catalog.js +107 -0
  99. package/dist/install/handle-install.d.ts +35 -0
  100. package/dist/install/handle-install.js +177 -0
  101. package/dist/install/index.d.ts +4 -0
  102. package/dist/install/index.js +3 -0
  103. package/dist/ir/adc-range-validation.js +14 -1
  104. package/dist/ir/build-ir-state.d.ts +1 -0
  105. package/dist/ir/build-ir-state.js +14 -0
  106. package/dist/ir/build-ir.js +4 -4
  107. package/dist/ir/call-graph.js +16 -0
  108. package/dist/ir/expression-to-ir.js +150 -0
  109. package/dist/ir/function-builder.js +22 -0
  110. package/dist/ir/hal/hal-emitter.js +10 -6
  111. package/dist/ir/hal/hal-parser.js +16 -9
  112. package/dist/ir/hal/hal-plugins.js +164 -3
  113. package/dist/ir/identifier-collector.js +21 -4
  114. package/dist/ir/interrupt-analysis.d.ts +5 -1
  115. package/dist/ir/interrupt-analysis.js +8 -16
  116. package/dist/ir/ownership-analysis.js +6 -0
  117. package/dist/ir/pin-capability-validation.js +37 -0
  118. package/dist/ir/pin-mode-validation.d.ts +2 -2
  119. package/dist/ir/pin-mode-validation.js +49 -18
  120. package/dist/ir/program-analysis.d.ts +42 -0
  121. package/dist/ir/program-analysis.js +243 -6
  122. package/dist/ir/render-expr.js +11 -0
  123. package/dist/ir/timing-validation.js +11 -2
  124. package/dist/ir/transformers/call-statement.js +105 -0
  125. package/dist/ir/transformers/expressions.js +4 -0
  126. package/dist/ir/transformers/hal-call-resolver.js +21 -0
  127. package/dist/ir/transformers/namespace-methods.js +17 -12
  128. package/dist/ir/transformers/ui-reactive.js +2 -2
  129. package/dist/ir/transformers/variables.js +51 -0
  130. package/dist/ir/type-resolution.js +18 -0
  131. package/dist/ir/ui-element-auto-wire.js +7 -5
  132. package/dist/ir/utils/map-statements.d.ts +4 -0
  133. package/dist/ir/utils/map-statements.js +79 -0
  134. package/dist/ir/validation-orchestrator.js +6 -1
  135. package/dist/ir/worker-analysis.d.ts +10 -0
  136. package/dist/ir/worker-analysis.js +261 -0
  137. package/dist/orchestrator/graph-builder.js +8 -7
  138. package/dist/platform/coop-scheduler-runtime.d.ts +19 -0
  139. package/dist/platform/coop-scheduler-runtime.js +41 -0
  140. package/dist/platform/generic-strategy.d.ts +15 -3
  141. package/dist/platform/generic-strategy.js +49 -4
  142. package/dist/safety/safety-bridge.d.ts +11 -0
  143. package/dist/safety/safety-bridge.js +48 -0
  144. package/dist/safety/sidecar-bridge.d.ts +5 -0
  145. package/dist/safety/sidecar-bridge.js +22 -0
  146. package/dist/safety-hook.d.ts +79 -0
  147. package/dist/safety-hook.js +35 -0
  148. package/dist/stores/display-profile-store.d.ts +0 -1
  149. package/dist/stores/display-profile-store.js +0 -1
  150. package/dist/testing.d.ts +4 -0
  151. package/dist/testing.js +5 -0
  152. package/dist/transpile.js +124 -35
  153. package/dist/types.d.ts +22 -2
  154. package/dist/ui-hook.d.ts +0 -2
  155. package/dist/utils/cli.d.ts +2 -2
  156. package/dist/utils/cli.js +68 -3
  157. package/package.json +20 -5
  158. package/dist/api/shared/display-adapters/eink-mono.d.ts +0 -2
  159. package/dist/api/shared/display-adapters/eink-mono.js +0 -53
  160. package/dist/api/shared/display-adapters/ssd1309.d.ts +0 -2
  161. package/dist/api/shared/display-adapters/ssd1309.js +0 -136
  162. package/dist/api/shared/display-adapters/st7796.d.ts +0 -2
  163. package/dist/api/shared/display-adapters/st7796.js +0 -132
  164. package/dist/libdef/idf-discovery.d.ts +0 -7
  165. package/dist/libdef/idf-discovery.js +0 -59
  166. package/dist/licenses.d.ts +0 -185
  167. package/dist/licenses.js +0 -963
@@ -1,7 +1,8 @@
1
1
  import ts from "typescript";
2
- import { getCurrentBoardConstants, halInstances } from "../build-ir-state.js";
2
+ import { getCurrentBoardConstants, halInstances, getContext } from "../build-ir-state.js";
3
3
  import { resolveExpressionText } from "./hal-emitter.js";
4
4
  import { renderExprAsText } from "../render-expr.js";
5
+ import { hasSafetyHook, requireSafetyHook } from "../../safety-hook.js";
5
6
  /**
6
7
  * Split a comma-joined argument list back into individual arguments, respecting
7
8
  * nesting (parens/brackets/braces) and string literals so a comma inside one of
@@ -303,6 +304,17 @@ export function tryResolveCompoundSemanticReturn(expr, instance, paramNames, cal
303
304
  * Returns the HALOpIR if the function name is recognized, or null.
304
305
  */
305
306
  export function tryResolveSemanticCall(fnName, args, instance, paramNames, callArgTexts, paramDefaults, callArgs) {
307
+ // ── Safety (@typecad/safety — safe.read) ────────────────────────────────
308
+ // The safety package owns lowering for `safe.*` callees (v2: only safe.read;
309
+ // safe.pinMode was removed — mode config is the HAL's job via Pin.asInput()
310
+ // etc.). Check the hook before the switch so safety calls never fall
311
+ // through to GPIO/etc. hasSafetyHook() guards the call site (no-op when
312
+ // @typecad/safety absent).
313
+ if (hasSafetyHook()) {
314
+ const op = requireSafetyHook().resolveSemanticCall?.(fnName, callArgs ?? []);
315
+ if (op)
316
+ return op;
317
+ }
306
318
  // Extract MCU port name from instance (set by Pin.fromPort())
307
319
  const port = portFromInstance(instance);
308
320
  switch (fnName) {
@@ -774,7 +786,18 @@ export function tryResolveSemanticCall(fnName, args, instance, paramNames, callA
774
786
  return { operation: "ble.add_service", uuid: quoteNonIdentifier(uuid) };
775
787
  }
776
788
  case "bleAddChar": {
777
- const index = resolveNumericOrExpression(args, 0, instance, paramNames, callArgTexts, paramDefaults);
789
+ // The characteristic index is assigned HERE from a per-file counter, not
790
+ // resolved from `this._charCount`. resolveHALReceiver re-resolves the
791
+ // receiver (Ble.server(name)) freshly for each chain level when emitting,
792
+ // so a value stamped onto the instance field in the resolver would be
793
+ // dropped — leaving every characteristic on slot 0, clobbering the
794
+ // previous on_read/on_write handler. Consuming the counter at the single
795
+ // emit site (this plugin, called once per characteristic in declaration
796
+ // order) makes indices sequential (0,1,2,…) and stable. The counter is
797
+ // per-file (reset in resetHALResolver). onRead/onWrite/onConnect don't
798
+ // need a compile-time index — the C++ shim keys them off
799
+ // __tc_ble.current_char, which __tc_ble_add_char sets at runtime from
800
+ // this index.
778
801
  const uuid = resolveSemanticArg(args, 1, instance, paramNames, callArgTexts, paramDefaults);
779
802
  const type = resolveSemanticArg(args, 2, instance, paramNames, callArgTexts, paramDefaults);
780
803
  const perms = resolveNumericOrExpression(args, 3, instance, paramNames, callArgTexts, paramDefaults);
@@ -792,7 +815,7 @@ export function tryResolveSemanticCall(fnName, args, instance, paramNames, callA
792
815
  const resolvedPerms = resolveBlePermExpr(perms);
793
816
  return {
794
817
  operation: "ble.add_char",
795
- index: index ?? 0,
818
+ index: getContext().bleCharCounter++,
796
819
  uuid: quoteNonIdentifier(uuid),
797
820
  type: resolvedType,
798
821
  perms: resolvedPerms,
@@ -944,6 +967,144 @@ export function tryResolveSemanticCall(fnName, args, instance, paramNames, callA
944
967
  return null;
945
968
  return { operation: "preferences.get_string", key: quoteNonIdentifier(key), defaultValue: quoteNonIdentifier(def) };
946
969
  }
970
+ // ── FS (filesystem) ──
971
+ case "fsBegin":
972
+ return { operation: "fs.begin" };
973
+ case "fsReadText": {
974
+ const path = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
975
+ if (path === null)
976
+ return null;
977
+ return { operation: "fs.read_text", path };
978
+ }
979
+ case "fsWriteText": {
980
+ const path = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
981
+ const content = resolveSemanticArg(args, 1, instance, paramNames, callArgTexts, paramDefaults);
982
+ if (path === null || content === null)
983
+ return null;
984
+ return { operation: "fs.write_text", path, content };
985
+ }
986
+ case "fsExists": {
987
+ const path = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
988
+ if (path === null)
989
+ return null;
990
+ return { operation: "fs.exists", path };
991
+ }
992
+ case "fsRemove": {
993
+ const path = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
994
+ if (path === null)
995
+ return null;
996
+ return { operation: "fs.remove", path };
997
+ }
998
+ // ── mDNS ──
999
+ case "mdnsStart": {
1000
+ const hostname = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
1001
+ if (hostname === null)
1002
+ return null;
1003
+ return { operation: "mdns.start", hostname };
1004
+ }
1005
+ case "mdnsSetHostname": {
1006
+ const name = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
1007
+ if (name === null)
1008
+ return null;
1009
+ return { operation: "mdns.set_hostname", name };
1010
+ }
1011
+ case "mdnsAddService": {
1012
+ const instanceName = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
1013
+ const proto = resolveSemanticArg(args, 1, instance, paramNames, callArgTexts, paramDefaults);
1014
+ const port = resolveNumericOrExpression(args, 2, instance, paramNames, callArgTexts, paramDefaults);
1015
+ if (instanceName === null || proto === null || port === null)
1016
+ return null;
1017
+ return { operation: "mdns.add_service", instance: instanceName, proto, port };
1018
+ }
1019
+ case "mdnsAnnounce":
1020
+ return { operation: "mdns.announce" };
1021
+ case "mdnsStop":
1022
+ return { operation: "mdns.stop" };
1023
+ // ── MQTT ──
1024
+ case "mqttConnect": {
1025
+ const brokerUri = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
1026
+ const clientId = resolveSemanticArg(args, 1, instance, paramNames, callArgTexts, paramDefaults);
1027
+ if (brokerUri === null || clientId === null)
1028
+ return null;
1029
+ return { operation: "mqtt.connect", brokerUri, clientId };
1030
+ }
1031
+ case "mqttOnMessage": {
1032
+ const handler = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
1033
+ if (handler === null)
1034
+ return null;
1035
+ return { operation: "mqtt.on_message", handler };
1036
+ }
1037
+ case "mqttSubscribe": {
1038
+ const topic = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
1039
+ if (topic === null)
1040
+ return null;
1041
+ return { operation: "mqtt.subscribe", topic };
1042
+ }
1043
+ case "mqttPublish": {
1044
+ const topic = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
1045
+ const data = resolveSemanticArg(args, 1, instance, paramNames, callArgTexts, paramDefaults);
1046
+ if (topic === null || data === null)
1047
+ return null;
1048
+ return { operation: "mqtt.publish", topic, data };
1049
+ }
1050
+ case "mqttConnected":
1051
+ return { operation: "mqtt.connected" };
1052
+ case "mqttDisconnect":
1053
+ return { operation: "mqtt.disconnect" };
1054
+ // ── OTA ──
1055
+ case "otaFromUrl": {
1056
+ const url = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
1057
+ if (url === null)
1058
+ return null;
1059
+ return { operation: "ota.from_url", url };
1060
+ }
1061
+ case "otaBegin":
1062
+ return { operation: "ota.begin" };
1063
+ case "otaWrite": {
1064
+ const chunk = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
1065
+ if (chunk === null)
1066
+ return null;
1067
+ return { operation: "ota.write", chunk };
1068
+ }
1069
+ case "otaApply":
1070
+ return { operation: "ota.apply" };
1071
+ // ── Temperature (die temp) ──
1072
+ case "tempRead":
1073
+ return { operation: "temp.read" };
1074
+ // ── Hardware timer (GPTimer) ──
1075
+ case "hwtimerSetFrequency": {
1076
+ const inst = resolveNumericOrExpression(args, 0, instance, paramNames, callArgTexts, paramDefaults);
1077
+ const hz = resolveNumericOrExpression(args, 1, instance, paramNames, callArgTexts, paramDefaults);
1078
+ if (inst === null || hz === null)
1079
+ return null;
1080
+ return { operation: "hwtimer.set_frequency", instance: inst, hz };
1081
+ }
1082
+ case "hwtimerOnOverflow": {
1083
+ const inst = resolveNumericOrExpression(args, 0, instance, paramNames, callArgTexts, paramDefaults);
1084
+ const handler = resolveSemanticArg(args, 1, instance, paramNames, callArgTexts, paramDefaults);
1085
+ if (inst === null || handler === null)
1086
+ return null;
1087
+ return { operation: "hwtimer.on_overflow", instance: inst, handler };
1088
+ }
1089
+ case "hwtimerStart": {
1090
+ const inst = resolveNumericOrExpression(args, 0, instance, paramNames, callArgTexts, paramDefaults);
1091
+ if (inst === null)
1092
+ return null;
1093
+ return { operation: "hwtimer.start", instance: inst };
1094
+ }
1095
+ case "hwtimerStop": {
1096
+ const inst = resolveNumericOrExpression(args, 0, instance, paramNames, callArgTexts, paramDefaults);
1097
+ if (inst === null)
1098
+ return null;
1099
+ return { operation: "hwtimer.stop", instance: inst };
1100
+ }
1101
+ // ── Capacitive touch pins ──
1102
+ case "capacitiveRead": {
1103
+ const pin = resolveNumericArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
1104
+ if (pin === null)
1105
+ return null;
1106
+ return { operation: "capacitive.read", port, pin };
1107
+ }
947
1108
  // ── Interrupts ──
948
1109
  case "interruptAttach": {
949
1110
  const pin = resolveNumericArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
@@ -423,6 +423,22 @@ function collectHALOpIdentifiers(op) {
423
423
  // handler is a resolved C++ function name (e.g. "myIsr" or a placeholder)
424
424
  identifiers.add(op.handler);
425
425
  break;
426
+ case "mqtt.on_message":
427
+ // handler is the user's onMessage callback name (callback() resolves it to
428
+ // a string). Without this the tree-shaker drops the function declaration —
429
+ // the mqtt.on_message op only carries the name, so the function has no
430
+ // other reference and looks unreachable. Same shape as interrupt.attach.
431
+ identifiers.add(op.handler);
432
+ break;
433
+ case "wifi.on_event":
434
+ identifiers.add(op.handler);
435
+ break;
436
+ case "ble.on_read":
437
+ case "ble.on_write":
438
+ case "ble.on_connect":
439
+ case "ble.on_disconnect":
440
+ identifiers.add(op.handler);
441
+ break;
426
442
  case "raw": {
427
443
  // Raw C++ code may reference user-defined identifiers
428
444
  const matches = op.code.match(/[A-Za-z_][A-Za-z0-9_]*/g);
@@ -435,11 +451,12 @@ function collectHALOpIdentifiers(op) {
435
451
  }
436
452
  // Other HAL ops have only numeric/literal fields — no identifier references
437
453
  }
438
- // wifi.* / http.* ops carry resolved C++ expression texts in their string
439
- // fields (e.g. wifi.connect ssid: `WIFI_SSID` — a top-level const, or a
440
- // quoted literal). Scan every string field for identifiers so referenced
454
+ // wifi.* / http.* / mqtt.* ops carry resolved C++ expression texts in their
455
+ // string fields (e.g. wifi.connect ssid: `WIFI_SSID` — a top-level const, or
456
+ // a quoted literal). Scan every string field for identifiers so referenced
441
457
  // globals survive tree-shaking; quoted literals are skipped.
442
- if (op.operation.startsWith("wifi.") || op.operation.startsWith("http.")) {
458
+ if (op.operation.startsWith("wifi.") || op.operation.startsWith("http.")
459
+ || op.operation.startsWith("mqtt.")) {
443
460
  for (const value of Object.values(op)) {
444
461
  if (typeof value !== "string" || value === op.operation)
445
462
  continue;
@@ -1,4 +1,4 @@
1
- import type { ProgramIR } from '../api/index.js';
1
+ import type { ProgramIR, StatementIR } from '../api/index.js';
2
2
  import type { PeripheralUsage } from './peripheral-usage.js';
3
3
  import type { Diagnostic } from '../types.js';
4
4
  export { walkNestedStatements as scanNestedStatements } from './utils/walk-ir.js';
@@ -24,6 +24,10 @@ export interface InterruptHandlerInfo {
24
24
  * @returns Array of diagnostics for interrupt-related issues
25
25
  */
26
26
  export declare function analyzeInterruptSafety(program: ProgramIR, usage: PeripheralUsage, isrUnsafeOps?: Map<string, IsrUnsafeOp>): Diagnostic[];
27
+ /** Collect variable names assigned (written) within a statement body. */
28
+ export declare function collectAssignedNames(stmts: readonly StatementIR[], names: Set<string>): void;
29
+ /** Collect variable names read (referenced) within a statement body. */
30
+ export declare function collectReadNames(stmts: readonly StatementIR[], names: Set<string>): void;
27
31
  /**
28
32
  * Infer `volatile` for global variables shared between ISR callbacks and main
29
33
  * code. Mutates the IR (sets isVolatile=true on qualifying var_decls) and
@@ -7,21 +7,13 @@
7
7
  import { walkNestedStatements, walkExpressionsInExpression } from './utils/walk-ir.js';
8
8
  export { walkNestedStatements as scanNestedStatements } from './utils/walk-ir.js';
9
9
  /**
10
- * Default ISR-unsafe operations used when no platform-specific map is provided.
11
- * Platforms should supply their own list via PlatformStrategy.isrUnsafeOperations().
10
+ * Fallback ISR-unsafe operations used when no platform-specific map is provided.
11
+ * Empty by design the Wiring-derived specifics (delay, Serial.*, I2C*, SPI*)
12
+ * live in ArduinoStrategy.isrUnsafeOperations(). A generic target has no known
13
+ * ISR-unsafe surface. The orchestrator passes the loaded framework's map; this
14
+ * default only applies when none is supplied.
12
15
  */
13
- const DEFAULT_ISR_UNSAFE_OPERATIONS = new Map([
14
- ['delay', { reason: 'delay() blocks the CPU and should not be used in interrupt context', severity: 'warning' }],
15
- ['delayMicroseconds', { reason: 'delayMicroseconds() blocks and should be avoided in ISRs', severity: 'warning' }],
16
- ['Serial.print', { reason: 'Serial.print() may not work correctly in interrupt context', severity: 'info' }],
17
- ['Serial.println', { reason: 'Serial.println() may not work correctly in interrupt context', severity: 'info' }],
18
- ['Serial.write', { reason: 'Serial.write() may not work correctly in interrupt context', severity: 'info' }],
19
- ['Serial.read', { reason: 'Serial.read() may not work correctly in interrupt context', severity: 'info' }],
20
- ['I2C0', { reason: 'I2C operations can cause lockups in interrupt context', severity: 'warning' }],
21
- ['I2C1', { reason: 'I2C operations can cause lockups in interrupt context', severity: 'warning' }],
22
- ['SPI0', { reason: 'SPI operations may cause issues in interrupt context', severity: 'info' }],
23
- ['SPI1', { reason: 'SPI operations may cause issues in interrupt context', severity: 'info' }],
24
- ]);
16
+ const DEFAULT_ISR_UNSAFE_OPERATIONS = new Map();
25
17
  /**
26
18
  * Analyze a program for interrupt handler issues.
27
19
  *
@@ -243,7 +235,7 @@ function scanExpressionForCallbacks(expr, callback, parentExpr) {
243
235
  // emission path already handles isVolatile (statement-renderer.ts).
244
236
  // ---------------------------------------------------------------------------
245
237
  /** Collect variable names assigned (written) within a statement body. */
246
- function collectAssignedNames(stmts, names) {
238
+ export function collectAssignedNames(stmts, names) {
247
239
  for (const stmt of stmts) {
248
240
  const s = stmt;
249
241
  if (stmt.kind === 'assign') {
@@ -265,7 +257,7 @@ function collectAssignedNames(stmts, names) {
265
257
  }
266
258
  }
267
259
  /** Collect variable names read (referenced) within a statement body. */
268
- function collectReadNames(stmts, names) {
260
+ export function collectReadNames(stmts, names) {
269
261
  for (const stmt of stmts) {
270
262
  walkExpressionsInStatement(stmt, (expr) => {
271
263
  if (expr.kind === 'identifier' && typeof expr.value === 'string') {
@@ -765,6 +765,12 @@ function analyzeExpression(expr, scope, diagnostics, fallbackSpan) {
765
765
  analyzeExpression(expr.inner, scope, diagnostics, span);
766
766
  break;
767
767
  }
768
+ case 'call': {
769
+ for (const arg of expr.args) {
770
+ analyzeExpression(arg, scope, diagnostics, span);
771
+ }
772
+ break;
773
+ }
768
774
  case 'number':
769
775
  case 'string':
770
776
  case 'boolean':
@@ -77,6 +77,35 @@ function findPinsWithCapability(capability, boardConstants) {
77
77
  }
78
78
  return result;
79
79
  }
80
+ /**
81
+ * Does the board declare ANY per-pin `type:'pwm'` function entry anywhere?
82
+ *
83
+ * AVR-family boards (atmega328p, etc.) hardwire PWM to specific timer-output
84
+ * pins, so their MCU descriptors list `{ type:'pwm', ... }` per pin and the
85
+ * capability check correctly rejects PWM on non-timer pins. ESP32-family
86
+ * boards route LEDC to ANY output GPIO via the GPIO matrix, so their MCU
87
+ * descriptors carry zero `type:'pwm'` entries — PWM is a board-wide property
88
+ * (the `pins.pwm` collection), not a per-pin function. On such boards the
89
+ * per-pin capability check would false-positive on every pin, so the caller
90
+ * skips it when this returns false. This distinguishes the two families
91
+ * cleanly (AVR has entries, ESP32 has none) without special-casing arch names.
92
+ */
93
+ function boardHasAnyPwmFunctionEntries(boardConstants) {
94
+ if (!boardConstants)
95
+ return false;
96
+ for (let i = 0; i < 100; i++) {
97
+ if (boardConstants.get(`pins.all.${i}.name`) === undefined)
98
+ continue;
99
+ for (let j = 0; j < 10; j++) {
100
+ const type = boardConstants.get(`pins.all.${i}.functions.${j}.type`);
101
+ if (type === undefined)
102
+ break;
103
+ if (String(type) === 'pwm')
104
+ return true;
105
+ }
106
+ }
107
+ return false;
108
+ }
80
109
  // ---------------------------------------------------------------------------
81
110
  // Capability check for a single HAL operation
82
111
  // ---------------------------------------------------------------------------
@@ -88,6 +117,13 @@ function checkCapability(operation, sourceLine, sourceCol, filePath, boardConsta
88
117
  const capability = operationToCapability(op);
89
118
  if (!capability)
90
119
  return; // GPIO/timing — always available.
120
+ // ESP32-family boards route PWM (LEDC) to any output GPIO via the GPIO
121
+ // matrix, so they carry zero per-pin `type:'pwm'` entries (unlike AVR,
122
+ // which hardwires PWM to timer-output pins). On such boards the per-pin PWM
123
+ // capability check would reject every pin; skip it and let the framework's
124
+ // LEDC driver handle routing. This does NOT weaken AVR, which has entries.
125
+ if (capability === 'pwm' && !boardHasAnyPwmFunctionEntries(boardConstants))
126
+ return;
91
127
  const pin = operation.pin;
92
128
  if (typeof pin !== 'number' || pin < 0)
93
129
  return;
@@ -250,6 +286,7 @@ function scanExpression(expr, boardConstants, parentLine, parentCol, parentFileP
250
286
  case 'identifier':
251
287
  case 'raw':
252
288
  case 'tuple-access':
289
+ case 'call':
253
290
  break;
254
291
  default:
255
292
  assertNever(expr);
@@ -4,7 +4,7 @@ import type { Diagnostic } from '../types.js';
4
4
  * Validate that GPIO I/O operations are preceded by mode configuration.
5
5
  *
6
6
  * - Read without prior mode → **warning** (undefined behavior on floating pin)
7
- * - Write without prior mode → **info** (Arduino implicitly sets OUTPUT, but
8
- * explicit configuration is recommended)
7
+ * - Write without prior mode → **info** (Wiring-derived frameworks implicitly
8
+ * set OUTPUT, but explicit configuration is recommended)
9
9
  */
10
10
  export declare function validatePinModeConfig(program: ProgramIR): Diagnostic[];
@@ -5,6 +5,7 @@
5
5
  // configuration. Generates warnings for reads and info for writes.
6
6
  // ---------------------------------------------------------------------------
7
7
  import { scanNestedStatements } from './interrupt-analysis.js';
8
+ import { hasLoadedFramework, getLoadedFramework } from '../framework-registry.js';
8
9
  /** Methods that configure pin mode. */
9
10
  const MODE_SET_METHODS = new Set([
10
11
  'inputPullUp', 'inputPullDown', 'outputOpenDrain',
@@ -14,7 +15,7 @@ const MODE_SET_METHODS = new Set([
14
15
  const READ_METHODS = new Set([
15
16
  'read', 'isHigh', 'isLow', 'readAnalog', 'readVoltage',
16
17
  ]);
17
- /** Write operations that implicitly set OUTPUT mode on Arduino. */
18
+ /** Write operations that implicitly set OUTPUT mode on Wiring-derived frameworks. */
18
19
  const WRITE_METHODS = new Set([
19
20
  'write', 'high', 'low', 'toggle', 'pulse', 'pwm', 'tone',
20
21
  ]);
@@ -26,8 +27,8 @@ const PIN_RECEIVER_KINDS = new Set([
26
27
  * Validate that GPIO I/O operations are preceded by mode configuration.
27
28
  *
28
29
  * - Read without prior mode → **warning** (undefined behavior on floating pin)
29
- * - Write without prior mode → **info** (Arduino implicitly sets OUTPUT, but
30
- * explicit configuration is recommended)
30
+ * - Write without prior mode → **info** (Wiring-derived frameworks implicitly
31
+ * set OUTPUT, but explicit configuration is recommended)
31
32
  */
32
33
  export function validatePinModeConfig(program) {
33
34
  const diagnostics = [];
@@ -65,7 +66,7 @@ export function validatePinModeConfig(program) {
65
66
  diagnostics.push({
66
67
  severity: 'info',
67
68
  message: `Pin '${receiver}' written via '${method}()' without explicit mode configuration. ` +
68
- `Arduino implicitly sets OUTPUT, but explicit ${receiver}.asOutput() is recommended.`,
69
+ `The target framework implicitly sets OUTPUT, but explicit ${receiver}.asOutput() is recommended.`,
69
70
  filePath: program.fileName,
70
71
  code: 'pin-mode-not-set',
71
72
  source: 'pin-mode-validation',
@@ -112,7 +113,7 @@ export function validatePinModeConfig(program) {
112
113
  diagnostics.push({
113
114
  severity: 'info',
114
115
  message: `Pin ${op.pin} written without explicit mode configuration. ` +
115
- `Arduino implicitly sets OUTPUT, but explicit asOutput() is recommended.`,
116
+ `The target framework implicitly sets OUTPUT, but explicit asOutput() is recommended.`,
116
117
  filePath: program.fileName,
117
118
  code: 'pin-mode-not-set',
118
119
  source: 'pin-mode-validation',
@@ -148,21 +149,51 @@ export function validatePinModeConfig(program) {
148
149
  // Raw nodes: after full HAL resolution, some pin ops land as their C++ text
149
150
  // (e.g. `digitalRead(2)`, `digitalWrite(2, 1)`) rather than structured
150
151
  // hal-expr nodes. Extract the pin number and apply the same mode check.
151
- // This mirrors how peripheral-usage.ts detects gpio via emitted text.
152
+ // Detect GPIO ops in lowered raw C++ text. Build the read/write name
153
+ // patterns from the loaded framework's HAL vocabulary (halCallNames),
154
+ // classifying names containing "Read" as reads and "Write" as writes (the
155
+ // Wiring-derived convention). Seed with digitalRead/digitalWrite as the
156
+ // canonical Wiring forms so the validator works even when no framework is
157
+ // loaded. Non-Wiring frameworks that emit other call forms register them
158
+ // via halCallNames; this loop picks them up.
152
159
  if (e.kind === 'raw' && typeof e.value === 'string') {
153
- const readMatch = e.value.match(/digitalRead\((\d+)\)/);
154
- const writeMatch = e.value.match(/digitalWrite\((\d+)/);
155
- const toggleMatch = e.value.match(/digitalWrite\((\d+),\s*digitalRead/);
156
- const pin = readMatch ? parseInt(readMatch[1], 10)
157
- : writeMatch ? parseInt(writeMatch[1], 10)
158
- : null;
159
- if (pin !== null) {
160
- const op = toggleMatch
161
- ? { operation: 'gpio.toggle', pin }
162
- : readMatch ? { operation: 'gpio.read', pin }
163
- : { operation: 'gpio.write', pin };
164
- checkGpioHalOp(op);
160
+ const frameworkHalNames = hasLoadedFramework()
161
+ ? getLoadedFramework().strategy.halCallNames?.()
162
+ : undefined;
163
+ const halNames = frameworkHalNames ?? new Set(['digitalRead', 'digitalWrite']);
164
+ const readNames = [...halNames].filter(n => /read/i.test(n));
165
+ const writeNames = [...halNames].filter(n => /write/i.test(n));
166
+ let pin = null;
167
+ let op = null;
168
+ for (const rn of readNames) {
169
+ const esc = rn.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
170
+ const m = e.value.match(new RegExp(`${esc}\\((\\d+)\\)`));
171
+ if (m) {
172
+ pin = parseInt(m[1], 10);
173
+ op = { operation: 'gpio.read', pin };
174
+ break;
175
+ }
176
+ }
177
+ if (!op) {
178
+ for (const wn of writeNames) {
179
+ const esc = wn.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
180
+ const m = e.value.match(new RegExp(`${esc}\\((\\d+)`));
181
+ if (m) {
182
+ pin = parseInt(m[1], 10);
183
+ op = { operation: 'gpio.write', pin };
184
+ break;
185
+ }
186
+ }
187
+ }
188
+ // toggle: a write call whose second arg is a read call on the same pin.
189
+ if (!op) {
190
+ const tm = e.value.match(/\((\d+),\s*\w*[Rr]ead/);
191
+ if (tm) {
192
+ op = { operation: 'gpio.toggle', pin: parseInt(tm[1], 10) };
193
+ }
165
194
  }
195
+ if (op)
196
+ checkGpioHalOp(op);
166
197
  }
167
198
  // Recurse into nested expressions
168
199
  if (e.args && Array.isArray(e.args)) {
@@ -70,11 +70,53 @@ export interface ProgramAnalysisResult {
70
70
  * __tc_ble runtime shims and their esp_wifi/esp_http_client/nimble includes
71
71
  * on these. Detected from wifi.* / http.* / ble.* HAL-op operation names. */
72
72
  usesWifi: boolean;
73
+ /** Fine-grained WiFi sub-feature tracking — gates individual shim sub-blocks
74
+ * so that unused static helper functions don't trigger -Wunused-function. */
75
+ usesWifiConnect: boolean;
76
+ usesWifiConnectBlocking: boolean;
77
+ usesWifiQuery: boolean;
78
+ usesWifiScan: boolean;
79
+ usesWifiConfig: boolean;
73
80
  usesHttp: boolean;
74
81
  usesBle: boolean;
75
82
  /** Preferences (NVS) usage — framework-esp32 gates the __tc_prefs runtime shim
76
83
  * and its nvs_flash/nvs includes on this. Detected from preferences.* ops. */
77
84
  usesPreferences: boolean;
85
+ /** Random usage — frameworks gate their PRNG runtime/include on this
86
+ * (e.g. framework-esp32 includes <esp_random.h>). Detected from random.* ops. */
87
+ usesRandom: boolean;
88
+ /** FS (filesystem) usage — frameworks gate their VFS/SD runtime shim on this.
89
+ * Detected from fs.* ops. */
90
+ usesFS: boolean;
91
+ /** mDNS usage — frameworks gate the esp_mdns runtime/include on this. */
92
+ usesMdns: boolean;
93
+ /** MQTT usage — frameworks gate the esp_mqtt runtime/include on this. */
94
+ usesMqtt: boolean;
95
+ /** OTA usage — frameworks gate the esp_https_ota runtime/include on this. */
96
+ usesOta: boolean;
97
+ /** Temperature (die temp) usage. Detected from temp.* ops. */
98
+ usesTemp: boolean;
99
+ /** Hardware timer (GPTimer) usage. Detected from hwtimer.* ops. */
100
+ usesHwtimer: boolean;
101
+ /** Capacitive touch pins usage. Detected from capacitive.* ops. */
102
+ usesCapacitive: boolean;
103
+ /** Worker offload usage. Detected from worker.* ops. Frameworks gate the
104
+ * worker_runtime polyfill (and its per-framework backing) on this. */
105
+ usesWorker: boolean;
106
+ /** Native/desktop: std::chrono / steady_clock usage (gates <chrono>). */
107
+ usesChrono: boolean;
108
+ /** Native/desktop: std::set usage (gates <set>). */
109
+ usesSet: boolean;
110
+ /** Native/desktop: std::algorithm usage (std::sort/find/transform etc., gates <algorithm>). */
111
+ usesAlgorithm: boolean;
112
+ /** Native/desktop: cstdio usage (printf/fprintf/snprintf, gates <cstdio>). */
113
+ usesCstdio: boolean;
114
+ /** digitalRead() in lowered raw text or gpio.read hal-op (gates the native digitalRead shim). */
115
+ usesDigitalRead: boolean;
116
+ /** display.* hal-op present, or a UI is mounted (replaces ad-hoc programUsesDisplay walks). */
117
+ usesDisplay: boolean;
118
+ /** cuttlefish_halt() in lowered raw text (gates the cuttlefish_halt macro polyfill). */
119
+ usesHalt: boolean;
78
120
  }
79
121
  /**
80
122
  * Analyze a program IR in a single pass to detect all features.