@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
@@ -4,6 +4,7 @@
4
4
  * Extracted from cpp-emitter.ts
5
5
  */
6
6
  import { escapeCppStringLiteral } from "../../utils/strings.js";
7
+ import { routeHALOp } from "../route-hal-op.js";
7
8
  /**
8
9
  * Converts a string to PascalCase.
9
10
  * Used for generating class names from function names.
@@ -53,6 +54,20 @@ export function generateAsyncTaskClass(fnName, fnStatements, strategy, knownFunc
53
54
  segments.push({ preStatements: currentPre, awaitedCallee: stmt.callee, awaitedArgs: stmt.args });
54
55
  currentPre = [];
55
56
  }
57
+ else if (
58
+ // Chained HAL calls (e.g. `await Http.get(url).send()`) resolve to a
59
+ // block of hal-ops whose LAST statement is the awaited wait marker.
60
+ // Flatten it so the split sees the marker; the leading ops (begin,
61
+ // setters) run as pre-statements of the same segment.
62
+ stmt.kind === "block" &&
63
+ stmt.body.length > 0 &&
64
+ stmt.body[stmt.body.length - 1].kind === "call" &&
65
+ stmt.body[stmt.body.length - 1].isAwaited) {
66
+ const last = stmt.body[stmt.body.length - 1];
67
+ currentPre.push(...stmt.body.slice(0, -1));
68
+ segments.push({ preStatements: currentPre, awaitedCallee: last.callee, awaitedArgs: last.args });
69
+ currentPre = [];
70
+ }
56
71
  else {
57
72
  currentPre.push(stmt);
58
73
  }
@@ -61,11 +76,23 @@ export function generateAsyncTaskClass(fnName, fnStatements, strategy, knownFunc
61
76
  segments.push({ preStatements: currentPre, awaitedArgs: [] });
62
77
  const awaitCount = segments.filter((s) => s.awaitedCallee !== undefined).length;
63
78
  const stateCount = awaitCount + 1; // STATE_0 … STATE_{awaitCount}; cyclic loops back, linear adds STATE_DONE
79
+ // State enum is emitted as `enum class State` (AUTOSAR A7-2-1), so all
80
+ // references must be scope-qualified as `State::STATE_X`. The stateEnumList
81
+ // (used inside `enum class State { ... }`) uses the bare names; everywhere
82
+ // else (assignments, case labels, comparisons) uses the qualified form.
64
83
  const stateNames = [];
65
- for (let i = 0; i < stateCount; i++)
84
+ const qualifiedStateNames = [];
85
+ for (let i = 0; i < stateCount; i++) {
66
86
  stateNames.push(`STATE_${i}`);
67
- if (!isCyclic)
87
+ qualifiedStateNames.push(`State::STATE_${i}`);
88
+ }
89
+ if (!isCyclic) {
68
90
  stateNames.push("STATE_DONE");
91
+ qualifiedStateNames.push("State::STATE_DONE");
92
+ }
93
+ const Q = (i) => qualifiedStateNames[i];
94
+ const Q_DONE = "State::STATE_DONE";
95
+ const Q_0 = "State::STATE_0";
69
96
  // Helper: render a non-await statement as a single C++ line
70
97
  const renderStmt = (stmt) => renderStatement(stmt, false, strategy, undefined, undefined, knownFunctionReturnTypes);
71
98
  // Collect edge-detection markers from segments
@@ -101,16 +128,74 @@ export function generateAsyncTaskClass(fnName, fnStatements, strategy, knownFunc
101
128
  tapMembers.set(member, i);
102
129
  }
103
130
  }
131
+ // Collect net-wait (WiFi/HTTP/HAL) markers: awaited hal-ops rewritten to
132
+ // __WIFI_WAIT__/__HTTP_WAIT__/__HAL_WAIT__ calls carrying the op as a
133
+ // hal-expr arg. The strategy lowers the op to start code + poll condition.
134
+ const netInfoMap = new Map();
135
+ for (let i = 0; i < segments.length; i++) {
136
+ const seg = segments[i];
137
+ if ((seg.awaitedCallee === "__WIFI_WAIT__" || seg.awaitedCallee === "__HTTP_WAIT__" || seg.awaitedCallee === "__BLE_WAIT__" || seg.awaitedCallee === "__HAL_WAIT__") &&
138
+ seg.awaitedArgs[0]?.kind === "hal-expr") {
139
+ const op = seg.awaitedArgs[0].operation;
140
+ netInfoMap.set(i, netWaitInfo(op, strategy));
141
+ }
142
+ }
143
+ // ── Render each state ──────────────────────────────────────────────────
144
+ // Decomposition: a state's ENTRY condition comes from the await that ended
145
+ // the PREVIOUS segment (edge / tap / net poll / timed deadline); once the
146
+ // condition fires, the segment body runs and the await ending THIS segment
147
+ // is armed (pin snapshot / tap snapshot / net start op / deadline).
148
+ // Lines that arm the await ending segment i and advance the state.
149
+ const armNextLines = (i, pad) => {
150
+ const seg = segments[i];
151
+ const lines = [];
152
+ if (seg.awaitedCallee === undefined) {
153
+ lines.push(`${pad}_state = ${isCyclic ? Q_0 : Q_DONE};`);
154
+ return lines;
155
+ }
156
+ const edge = edgeInfoMap.get(i);
157
+ const tap = tapInfoMap.get(i);
158
+ const net = netInfoMap.get(i);
159
+ if (edge) {
160
+ lines.push(`${pad}_edgePrev_p${edge.pin} = ${strategy.readDigitalPin?.(String(edge.pin)) ?? `digitalRead(${edge.pin})`};`);
161
+ if (edge.timeout !== null) {
162
+ lines.push(`${pad}_waitUntil = ${strategy.currentTimeMillis()} + ${edge.timeout};`);
163
+ }
164
+ }
165
+ else if (tap) {
166
+ lines.push(`${pad}_tapPrev_${i} = __ui_tap_seq;`);
167
+ }
168
+ else if (net) {
169
+ for (const startLine of net.startLines)
170
+ lines.push(`${pad}${startLine}`);
171
+ if (net.timeoutExpr !== null) {
172
+ lines.push(`${pad}_waitUntil = ${strategy.currentTimeMillis()} + ${net.timeoutExpr};`);
173
+ }
174
+ }
175
+ else {
176
+ // Plain timed wait (await delay(ms)) — arm its deadline.
177
+ const ms = seg.awaitedArgs[0] ? renderExpression(seg.awaitedArgs[0], strategy) : "0";
178
+ lines.push(`${pad}_waitUntil = ${strategy.currentTimeMillis()} + ${ms};`);
179
+ }
180
+ lines.push(`${pad}_state = ${Q(i + 1)};`);
181
+ return lines;
182
+ };
104
183
  const caseLines = [];
105
184
  for (let i = 0; i < segments.length; i++) {
106
185
  const seg = segments[i];
107
- const stateName = `STATE_${i}`;
108
- const isTerminal = seg.awaitedCallee === undefined;
186
+ const stateName = Q(i);
109
187
  const body = [];
110
- const edgeSetup = edgeInfoMap.get(i);
111
188
  const edgePoll = i > 0 ? edgeInfoMap.get(i - 1) : undefined;
112
- const tapSetup = tapInfoMap.get(i);
113
189
  const tapPoll = i > 0 ? tapInfoMap.get(i - 1) : undefined;
190
+ const netPoll = i > 0 ? netInfoMap.get(i - 1) : undefined;
191
+ // Segment body + arming of the next await, at the given indent.
192
+ const runSegment = (pad) => {
193
+ const lines = [];
194
+ for (const stmt of seg.preStatements)
195
+ lines.push(`${pad}${renderStmt(stmt)}`);
196
+ lines.push(...armNextLines(i, pad));
197
+ return lines;
198
+ };
114
199
  if (edgePoll) {
115
200
  // Poll state: check pin transition via digitalRead
116
201
  const prevVar = `_edgePrev_p${edgePoll.pin}`;
@@ -121,56 +206,15 @@ export function generateAsyncTaskClass(fnName, fnStatements, strategy, knownFunc
121
206
  ? `(${cond}) || ${strategy.currentTimeMillis()} >= _waitUntil`
122
207
  : cond;
123
208
  body.push(` {`);
124
- body.push(` int _cur = digitalRead(${edgePoll.pin});`);
209
+ body.push(` int _cur = ${strategy.readDigitalPin?.(String(edgePoll.pin)) ?? `digitalRead(${edgePoll.pin})`};`);
125
210
  body.push(` if (${fullCond}) {`);
126
211
  body.push(` ${prevVar} = _cur;`);
127
- for (const stmt of seg.preStatements)
128
- body.push(` ${renderStmt(stmt)}`);
129
- if (edgeSetup) {
130
- // Next await is also edge detection — set up its pin tracking
131
- body.push(` _edgePrev_p${edgeSetup.pin} = digitalRead(${edgeSetup.pin});`);
132
- if (edgeSetup.timeout !== null) {
133
- body.push(` _waitUntil = ${strategy.currentTimeMillis()} + ${edgeSetup.timeout};`);
134
- }
135
- body.push(` _state = STATE_${i + 1};`);
136
- }
137
- else if (!isTerminal) {
138
- const ms = seg.awaitedArgs[0] ? renderExpression(seg.awaitedArgs[0], strategy) : "0";
139
- body.push(` _waitUntil = ${strategy.currentTimeMillis()} + ${ms};`);
140
- body.push(` _state = STATE_${i + 1};`);
141
- }
142
- else {
143
- body.push(` _state = ${isCyclic ? "STATE_0" : "STATE_DONE"};`);
144
- }
212
+ body.push(...runSegment(` `));
145
213
  body.push(` } else {`);
146
214
  body.push(` ${prevVar} = _cur;`);
147
215
  body.push(` }`);
148
216
  body.push(` }`);
149
217
  }
150
- else if (edgeSetup) {
151
- // Setup state: capture initial pin state before polling
152
- const prevVar = `_edgePrev_p${edgeSetup.pin}`;
153
- if (i === 0) {
154
- for (const stmt of seg.preStatements)
155
- body.push(` ${renderStmt(stmt)}`);
156
- body.push(` ${prevVar} = digitalRead(${edgeSetup.pin});`);
157
- if (edgeSetup.timeout !== null) {
158
- body.push(` _waitUntil = ${strategy.currentTimeMillis()} + ${edgeSetup.timeout};`);
159
- }
160
- body.push(` _state = STATE_${i + 1};`);
161
- }
162
- else {
163
- body.push(` if (${strategy.currentTimeMillis()} >= _waitUntil) {`);
164
- for (const stmt of seg.preStatements)
165
- body.push(` ${renderStmt(stmt)}`);
166
- body.push(` ${prevVar} = digitalRead(${edgeSetup.pin});`);
167
- if (edgeSetup.timeout !== null) {
168
- body.push(` _waitUntil = ${strategy.currentTimeMillis()} + ${edgeSetup.timeout};`);
169
- }
170
- body.push(` _state = STATE_${i + 1};`);
171
- body.push(` }`);
172
- }
173
- }
174
218
  else if (tapPoll) {
175
219
  // Tap-poll state: a previous segment ended with `await ui.onTap()`.
176
220
  // Wait until __ui_tap_seq bumps from the captured snapshot. A per-node
@@ -181,92 +225,42 @@ export function generateAsyncTaskClass(fnName, fnStatements, strategy, knownFunc
181
225
  ? `__ui_tap_seq != ${prevVar}`
182
226
  : `(__ui_tap_seq != ${prevVar}) && (__ui_tap_node == ${nodeFilter})`;
183
227
  body.push(` if (${cond}) {`);
184
- for (const stmt of seg.preStatements)
185
- body.push(` ${renderStmt(stmt)}`);
186
- // What does the NEXT await (ending this segment) look like? Re-derive
187
- // from the maps via a fresh lookup so TS doesn't narrow edgeSetup away.
188
- const nextEdge = edgeInfoMap.get(i) ?? null;
189
- if (tapSetup) {
190
- // Next await is also a tap — capture its snapshot now.
191
- body.push(` _tapPrev_${i} = __ui_tap_seq;`);
192
- body.push(` _state = STATE_${i + 1};`);
193
- }
194
- else if (nextEdge) {
195
- body.push(` _edgePrev_p${nextEdge.pin} = digitalRead(${nextEdge.pin});`);
196
- if (nextEdge.timeout !== null) {
197
- body.push(` _waitUntil = ${strategy.currentTimeMillis()} + ${nextEdge.timeout};`);
198
- }
199
- body.push(` _state = STATE_${i + 1};`);
200
- }
201
- else if (!isTerminal) {
202
- // Next await is a normal timed wait (delay) — arm its deadline.
203
- const ms = seg.awaitedArgs[0] ? renderExpression(seg.awaitedArgs[0], strategy) : "0";
204
- body.push(` _waitUntil = ${strategy.currentTimeMillis()} + ${ms};`);
205
- body.push(` _state = STATE_${i + 1};`);
206
- }
207
- else {
208
- body.push(` _state = ${isCyclic ? "STATE_0" : "STATE_DONE"};`);
209
- }
228
+ body.push(...runSegment(` `));
210
229
  body.push(` }`);
211
230
  }
212
- else if (tapSetup && !edgePoll) {
213
- // Tap-setup state: this segment ends with `await ui.onTap()`. Capture the
214
- // current tap counter so the poll state can detect the NEXT bump.
215
- // (edgePoll takes precedence when the previous await was an edge.)
216
- if (i === 0) {
217
- for (const stmt of seg.preStatements)
218
- body.push(` ${renderStmt(stmt)}`);
219
- body.push(` _tapPrev_${i} = __ui_tap_seq;`);
220
- body.push(` _state = STATE_${i + 1};`);
221
- }
222
- else {
223
- body.push(` if (${strategy.currentTimeMillis()} >= _waitUntil) {`);
224
- for (const stmt of seg.preStatements)
225
- body.push(` ${renderStmt(stmt)}`);
226
- body.push(` _tapPrev_${i} = __ui_tap_seq;`);
227
- body.push(` _state = STATE_${i + 1};`);
228
- body.push(` }`);
229
- }
231
+ else if (netPoll) {
232
+ // Net-poll state: a previous segment ended with an awaited WiFi/HTTP
233
+ // op. Wait until its poll condition fires (or its deadline expires).
234
+ const deadline = `${strategy.currentTimeMillis()} >= _waitUntil`;
235
+ const cond = netPoll.pollCond === null
236
+ ? deadline
237
+ : netPoll.timeoutExpr !== null
238
+ ? `(${netPoll.pollCond}) || ${deadline}`
239
+ : netPoll.pollCond;
240
+ body.push(` if (${cond}) {`);
241
+ body.push(...runSegment(` `));
242
+ body.push(` }`);
243
+ }
244
+ else if (i === 0) {
245
+ // First state runs immediately.
246
+ body.push(...runSegment(` `));
230
247
  }
231
248
  else {
232
- // Normal timed-wait state
233
- if (i === 0) {
234
- for (const stmt of seg.preStatements)
235
- body.push(` ${renderStmt(stmt)}`);
236
- if (!isTerminal) {
237
- const ms = seg.awaitedArgs[0] ? renderExpression(seg.awaitedArgs[0], strategy) : "0";
238
- body.push(` _waitUntil = ${strategy.currentTimeMillis()} + ${ms};`);
239
- body.push(` _state = STATE_${i + 1};`);
240
- }
241
- else {
242
- body.push(` _state = ${isCyclic ? "STATE_0" : "STATE_DONE"};`);
243
- }
244
- }
245
- else {
246
- body.push(` if (${strategy.currentTimeMillis()} >= _waitUntil) {`);
247
- for (const stmt of seg.preStatements)
248
- body.push(` ${renderStmt(stmt)}`);
249
- if (!isTerminal) {
250
- const ms = seg.awaitedArgs[0] ? renderExpression(seg.awaitedArgs[0], strategy) : "0";
251
- body.push(` _waitUntil = ${strategy.currentTimeMillis()} + ${ms};`);
252
- body.push(` _state = STATE_${i + 1};`);
253
- }
254
- else {
255
- body.push(` _state = ${isCyclic ? "STATE_0" : "STATE_DONE"};`);
256
- }
257
- body.push(` }`);
258
- }
249
+ // Previous await was a plain timed wait — poll its deadline.
250
+ body.push(` if (${strategy.currentTimeMillis()} >= _waitUntil) {`);
251
+ body.push(...runSegment(` `));
252
+ body.push(` }`);
259
253
  }
260
254
  caseLines.push(` case ${stateName}:`, ` {`, ...body, ` }`, ` break;`);
261
255
  }
262
256
  if (!isCyclic)
263
- caseLines.push(` case STATE_DONE:`, ` break;`);
257
+ caseLines.push(` case ${Q_DONE}:`, ` break;`);
264
258
  const stateEnumList = stateNames.join(", ");
265
- const isCompleteExpr = isCyclic ? "false" : "_state == STATE_DONE";
259
+ const isCompleteExpr = isCyclic ? "false" : `_state == ${Q_DONE}`;
266
260
  // Build constructor initializer list and edge/tap member declarations
267
261
  const edgeMemberArr = Array.from(edgeMembers);
268
262
  const tapMemberArr = Array.from(tapMembers.keys());
269
- const inits = [`_state(STATE_0)`, `_waitUntil(0)`];
263
+ const inits = [`_state(${Q_0})`, `_waitUntil(0)`];
270
264
  for (const m of edgeMemberArr)
271
265
  inits.push(`${m}(LOW)`);
272
266
  for (const m of tapMemberArr)
@@ -281,7 +275,7 @@ export function generateAsyncTaskClass(fnName, fnStatements, strategy, knownFunc
281
275
  `// Async state machine for ${fnName}`,
282
276
  `class ${className} {`,
283
277
  `public:`,
284
- ` enum State { ${stateEnumList} };`,
278
+ ` enum class State { ${stateEnumList} };`,
285
279
  ` ${className}() : ${ctorInitList} {}`,
286
280
  ` void run() {`,
287
281
  ` switch (_state) {`,
@@ -289,7 +283,7 @@ export function generateAsyncTaskClass(fnName, fnStatements, strategy, knownFunc
289
283
  ` }`,
290
284
  ` }`,
291
285
  ` bool isComplete() const { return ${isCompleteExpr}; }`,
292
- ` void reset() { _state = STATE_0; _waitUntil = 0;${edgeResetList}${tapResetList} }`,
286
+ ` void reset() { _state = ${Q_0}; _waitUntil = 0;${edgeResetList}${tapResetList} }`,
293
287
  `private:`,
294
288
  ` State _state;`,
295
289
  ` unsigned long _waitUntil;`,
@@ -331,6 +325,108 @@ function renderExpression(expr, strategy) {
331
325
  return "/* complex expr */";
332
326
  }
333
327
  }
328
+ /** Numeric-or-rendered HAL field → C++ text. */
329
+ function ms(v) {
330
+ return String(v);
331
+ }
332
+ /** True when a timeout field is the literal 0 (wait forever, no deadline). */
333
+ function isZeroTimeout(v) {
334
+ return v === undefined || v === null || String(v).trim() === "0";
335
+ }
336
+ /**
337
+ * Map an awaitable HAL op to its start statements + poll condition, lowering
338
+ * the start op and poll predicate through the platform strategy. Keep the set
339
+ * of handled ops in sync with AWAITABLE_HAL_OPS in ir/transformers/expressions.ts.
340
+ *
341
+ * Falls back to running the blocking form as the "start" with an immediate
342
+ * completion when the strategy can't lower the split ops.
343
+ */
344
+ function netWaitInfo(op, strategy) {
345
+ const o = op;
346
+ const route = (routedOp) => routeHALOp(routedOp, strategy);
347
+ const expr = (operation) => route({ operation })?.expression ?? null;
348
+ switch (op.operation) {
349
+ case "timing.delay":
350
+ return { startLines: [], pollCond: null, timeoutExpr: ms(o.ms) };
351
+ case "wifi.connect": {
352
+ const start = route({ operation: "wifi.connect_start", ssid: o.ssid, password: o.password });
353
+ const poll = expr("wifi.is_connected");
354
+ if (start?.code && poll) {
355
+ return {
356
+ startLines: [start.code],
357
+ pollCond: poll,
358
+ timeoutExpr: isZeroTimeout(o.timeoutMs) ? null : ms(o.timeoutMs),
359
+ };
360
+ }
361
+ break;
362
+ }
363
+ case "wifi.wait_connected": {
364
+ const poll = expr("wifi.is_connected");
365
+ if (poll) {
366
+ return {
367
+ startLines: [],
368
+ pollCond: poll,
369
+ timeoutExpr: isZeroTimeout(o.timeoutMs) ? null : ms(o.timeoutMs),
370
+ };
371
+ }
372
+ break;
373
+ }
374
+ case "wifi.wait_disconnected": {
375
+ const poll = expr("wifi.is_connected");
376
+ if (poll) {
377
+ return { startLines: [], pollCond: `!${poll}`, timeoutExpr: null };
378
+ }
379
+ break;
380
+ }
381
+ case "wifi.scan": {
382
+ const start = route({ operation: "wifi.scan_start" });
383
+ const poll = expr("wifi.scan_done");
384
+ if (start?.code && poll) {
385
+ return { startLines: [start.code], pollCond: poll, timeoutExpr: null };
386
+ }
387
+ break;
388
+ }
389
+ case "http.send": {
390
+ const start = route({ operation: "http.send_start" });
391
+ const poll = expr("http.done");
392
+ if (start?.code && poll) {
393
+ return { startLines: [start.code], pollCond: poll, timeoutExpr: null };
394
+ }
395
+ break;
396
+ }
397
+ case "ble.until_connected": {
398
+ const start = route({ operation: "ble.until_connected_start" });
399
+ const poll = expr("ble.is_connected");
400
+ if (start?.code && poll) {
401
+ return {
402
+ startLines: [start.code],
403
+ pollCond: poll,
404
+ timeoutExpr: isZeroTimeout(o.timeoutMs) ? null : ms(o.timeoutMs),
405
+ };
406
+ }
407
+ break;
408
+ }
409
+ case "worker.submit": {
410
+ // Worker offload (Phase 3). `await worker.submit(fn, arg)` rewrites to:
411
+ // submit the worker (start), then poll worker.done on the same handle.
412
+ // The submit op is emitted verbatim as the start line; the poll predicate
413
+ // is worker.done(handle) carrying the SAME handleId. This is a predicate-
414
+ // poll resume (same per-frame cost as net/timer awaits), NOT an ISR-driven
415
+ // resume — that defers with DMA-backed awaits.
416
+ const submit = routeHALOp(op, strategy);
417
+ // Carry the handle through to the poll so submit + done agree on the slot.
418
+ const poll = route({ operation: "worker.done", handleId: o.handleId })?.expression ?? null;
419
+ if (submit?.code && poll) {
420
+ return { startLines: [submit.code], pollCond: poll, timeoutExpr: null };
421
+ }
422
+ break;
423
+ }
424
+ }
425
+ // Fallback: run the blocking form immediately and complete on the next tick.
426
+ const blocking = routeHALOp(op, strategy);
427
+ const line = blocking?.code ?? (blocking?.expression ? `${blocking.expression};` : `/* unhandled awaited hal-op: ${op.operation} */`);
428
+ return { startLines: [line], pollCond: null, timeoutExpr: "0" };
429
+ }
334
430
  function parseEdgeMarker(argText) {
335
431
  const match = argText.match(/^__EDGE_(RISING|FALLING)__([a-zA-Z0-9_]+)__T(.*)$/);
336
432
  if (!match)
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Best-effort C++ return type for a HAL expression op.
3
+ * Used by snprintf format inference and expression type inference so
4
+ * string-returning ops (e.g. wifi.scan_ssid → const char*) use %s, not %d.
5
+ */
6
+ export declare function cppTypeForHalOp(operation: string): string | undefined;
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Best-effort C++ return type for a HAL expression op.
3
+ * Used by snprintf format inference and expression type inference so
4
+ * string-returning ops (e.g. wifi.scan_ssid → const char*) use %s, not %d.
5
+ */
6
+ export function cppTypeForHalOp(operation) {
7
+ switch (operation) {
8
+ case "wifi.local_ip":
9
+ case "wifi.mac":
10
+ case "wifi.ap_ip":
11
+ case "wifi.scan_ssid":
12
+ case "http.body":
13
+ case "http.response_header":
14
+ return "const char*";
15
+ case "wifi.is_connected":
16
+ case "wifi.connect":
17
+ case "wifi.wait_connected":
18
+ case "wifi.scan_done":
19
+ case "wifi.ap_start":
20
+ case "wifi.connect_saved":
21
+ case "http.ok":
22
+ case "http.done":
23
+ return "bool";
24
+ case "wifi.status":
25
+ case "wifi.rssi":
26
+ case "wifi.scan":
27
+ case "wifi.scan_count":
28
+ case "wifi.scan_rssi":
29
+ case "wifi.scan_encryption":
30
+ case "wifi.scan_channel":
31
+ case "wifi.ap_client_count":
32
+ case "http.status":
33
+ case "http.content_length":
34
+ return "int";
35
+ case "http.send":
36
+ return "bool";
37
+ default:
38
+ return undefined;
39
+ }
40
+ }
@@ -50,6 +50,8 @@ export function loadFrameworkPackage(packageName, fromDir = process.cwd()) {
50
50
  } : undefined,
51
51
  classNameMapBuilder: typeof mod.buildClassNameMap === "function" ? mod.buildClassNameMap : undefined,
52
52
  libDeclGenerator: typeof mod.tryGenerateLibDecl === "function" ? mod.tryGenerateLibDecl : undefined,
53
+ doctor: typeof mod.doctor === "function" ? mod.doctor : undefined,
54
+ licenses: typeof mod.licenses === "function" ? mod.licenses : undefined,
53
55
  };
54
56
  setLoadedFramework(framework);
55
57
  registerPlatformStrategy(strategy);
@@ -14,6 +14,16 @@ export interface LoadedFramework {
14
14
  classNameMapBuilder?: (imports: any[]) => Map<string, string>;
15
15
  /** Framework library .d.ts declaration generation */
16
16
  libDeclGenerator?: (modulePath: string, file: string) => string | undefined;
17
+ /**
18
+ * Optional subcommand presenters owned by the framework. Cuttlefish dispatches
19
+ * `cuttlefish doctor` / `cuttlefish licenses` to these when the loaded
20
+ * framework provides them; otherwise it prints a no-support message. Each
21
+ * framework decides what (if anything) these do — e.g. framework-arduino
22
+ * checks arduino-cli + board core (doctor) and scans Arduino library licenses
23
+ * (licenses).
24
+ */
25
+ doctor?: () => void;
26
+ licenses?: (strict: boolean, all: boolean) => void;
17
27
  }
18
28
  /**
19
29
  * Resolves framework-specific library imports (e.g., Arduino libraries).
@@ -32,6 +42,13 @@ export interface FrameworkToolchain {
32
42
  compile(options: ToolchainOptions): CompileResult;
33
43
  upload?(options: ToolchainOptions): UploadResult;
34
44
  monitor?(options: ToolchainOptions): void;
45
+ /**
46
+ * Optional: launch an interactive debugger session for the last build
47
+ * (e.g. `west debug`). Not invoked by the standard build/compile flow;
48
+ * powers an explicit debug-attach entry point. Frameworks that support
49
+ * source-level debugging implement this to spawn their native debugger.
50
+ */
51
+ debug?(options: ToolchainOptions): void;
35
52
  }
36
53
  export declare function setLoadedFramework(framework: LoadedFramework): void;
37
54
  export declare function getLoadedFramework(): LoadedFramework;
@@ -0,0 +1,53 @@
1
+ /** A framework family that ships as an installable @typecad/framework-* package. */
2
+ export interface FrameworkCatalogEntry {
3
+ /** Short id, e.g. "arduino". Matches the suffix of @typecad/framework-<id>. */
4
+ id: string;
5
+ /** Full npm package name, e.g. "@typecad/framework-arduino". */
6
+ packageName: string;
7
+ /** Human-readable label shown in the install prompt. */
8
+ label: string;
9
+ /**
10
+ * Whether a real package exists on the registry. Used to filter the prompt:
11
+ * "esp-idf" is a known family with no published package yet, so it must not
12
+ * be offered for install (it would fail at the package-manager step).
13
+ */
14
+ installable: boolean;
15
+ }
16
+ /**
17
+ * The installable frameworks. Kept aligned with the packages/ directory:
18
+ * framework-arduino, framework-native, framework-zephyr all ship real packages.
19
+ * esp-idf is intentionally absent (no published package in this repo).
20
+ */
21
+ export declare const FRAMEWORK_CATALOG: readonly FrameworkCatalogEntry[];
22
+ /** Structural shape we need from a board/target. Keeps this module decoupled
23
+ * from the create/ module's KnownTarget (and trivially testable with literals). */
24
+ export interface BoardLike {
25
+ isNative?: boolean;
26
+ architecture?: string;
27
+ }
28
+ /** Look up a catalog entry by framework id (e.g. "arduino"). */
29
+ export declare function frameworkCatalogEntry(id: string): FrameworkCatalogEntry | undefined;
30
+ /**
31
+ * The framework ids compatible with a board. Native boards map to ["native"];
32
+ * embedded boards map via ARCHITECTURE_FRAMEWORKS (falling back to arduino for
33
+ * unknown architectures). Order is preserved as the catalog order so the most
34
+ * common framework is offered first in the prompt.
35
+ */
36
+ export declare function frameworksForTarget(target: BoardLike): FrameworkCatalogEntry[];
37
+ export type PackageManager = "npm" | "pnpm" | "yarn";
38
+ /**
39
+ * Detect the package manager for a directory. Priority:
40
+ * 1. package.json#packageManager field (the strongest signal, Corepack-style)
41
+ * 2. lockfile presence (pnpm-lock.yaml / yarn.lock → otherwise npm)
42
+ * 3. default "npm"
43
+ * Never throws — unreadable/missing files fall through to the next signal.
44
+ */
45
+ export declare function detectPackageManager(cwd: string): PackageManager;
46
+ /**
47
+ * Build the package-manager invocation that installs `packageName` into the
48
+ * current project (project-local, never global — per the install command spec).
49
+ */
50
+ export declare function buildInstallCommand(pm: PackageManager, packageName: string): {
51
+ bin: string;
52
+ args: string[];
53
+ };