@typecad/cuttlefish 0.1.0-alpha.1 → 1.0.0-alpha.10

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 (225) 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 +32 -0
  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 +20 -2
  28. package/dist/api/shared/index.js +25 -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/toolchain-types.d.ts +17 -0
  38. package/dist/api/shared/types.d.ts +8 -0
  39. package/dist/api/shared/validate-framework-manifest.d.ts +28 -0
  40. package/dist/api/shared/validate-framework-manifest.js +494 -0
  41. package/dist/api/shared/worker-runtime-polyfill.d.ts +22 -0
  42. package/dist/api/shared/worker-runtime-polyfill.js +34 -0
  43. package/dist/api/shared/worker-runtime.d.ts +69 -0
  44. package/dist/api/shared/worker-runtime.js +163 -0
  45. package/dist/cli-utils.d.ts +1 -0
  46. package/dist/cli-utils.js +3 -1
  47. package/dist/cli.js +260 -35
  48. package/dist/config-loader.d.ts +7 -2
  49. package/dist/config-loader.js +23 -3
  50. package/dist/config-schema.d.ts +112 -70
  51. package/dist/config-schema.js +14 -0
  52. package/dist/contract/board-generator.d.ts +17 -0
  53. package/dist/contract/board-generator.js +57 -0
  54. package/dist/contract/contract-parser.d.ts +217 -0
  55. package/dist/contract/contract-parser.js +224 -0
  56. package/dist/contract/index.d.ts +22 -0
  57. package/dist/contract/index.js +68 -0
  58. package/dist/create/board-codegen.js +4 -4
  59. package/dist/create/board-generators.js +4 -5
  60. package/dist/create/board-spec.d.ts +72 -75
  61. package/dist/create/board-spec.js +0 -1
  62. package/dist/create/framework-catalog.d.ts +64 -0
  63. package/dist/create/framework-catalog.js +136 -0
  64. package/dist/create/index.d.ts +5 -1
  65. package/dist/create/index.js +3 -1
  66. package/dist/create/init-scaffold.d.ts +12 -3
  67. package/dist/create/init-scaffold.js +49 -14
  68. package/dist/create/init-templates.d.ts +4 -0
  69. package/dist/create/init-templates.js +263 -17
  70. package/dist/create/init-wizard.js +41 -27
  71. package/dist/create/install-deps.d.ts +32 -0
  72. package/dist/create/install-deps.js +46 -0
  73. package/dist/debug/preprocessor.js +178 -39
  74. package/dist/debug/types.d.ts +23 -0
  75. package/dist/diagnostics/mermaid-builder.d.ts +1 -1
  76. package/dist/diagnostics/mermaid-builder.js +34 -24
  77. package/dist/emit/compliance/arxml-writer.d.ts +11 -0
  78. package/dist/emit/compliance/arxml-writer.js +34 -0
  79. package/dist/emit/compliance/compliance-context.d.ts +57 -0
  80. package/dist/emit/compliance/compliance-context.js +113 -0
  81. package/dist/emit/compliance/deviation-ledger.d.ts +27 -0
  82. package/dist/emit/compliance/deviation-ledger.js +47 -0
  83. package/dist/emit/compliance/deviation-writer.d.ts +30 -0
  84. package/dist/emit/compliance/deviation-writer.js +37 -0
  85. package/dist/emit/compliance/index.d.ts +7 -0
  86. package/dist/emit/compliance/index.js +6 -0
  87. package/dist/emit/compliance/rule-engine.d.ts +13 -0
  88. package/dist/emit/compliance/rule-engine.js +101 -0
  89. package/dist/emit/compliance/rules.d.ts +16 -0
  90. package/dist/emit/compliance/rules.js +191 -0
  91. package/dist/emit/compliance/types.d.ts +59 -0
  92. package/dist/emit/compliance/types.js +8 -0
  93. package/dist/emit/cpp-emitter.js +4 -3
  94. package/dist/emit/emitters/class-emitter.js +6 -1
  95. package/dist/emit/emitters/emitter-context.d.ts +28 -1
  96. package/dist/emit/emitters/function-emitter-impl.js +153 -53
  97. package/dist/emit/emitters/line-appender.js +19 -0
  98. package/dist/emit/emitters/line-marker.d.ts +38 -0
  99. package/dist/emit/emitters/line-marker.js +39 -0
  100. package/dist/emit/emitters/output-finalizer.d.ts +6 -0
  101. package/dist/emit/emitters/output-finalizer.js +101 -12
  102. package/dist/emit/emitters/setup.d.ts +17 -0
  103. package/dist/emit/emitters/setup.js +372 -30
  104. package/dist/emit/emitters/top-level-prep.js +8 -0
  105. package/dist/emit/emitters/type-decl-emitter.js +28 -3
  106. package/dist/emit/emitters/ui-emitter.js +33 -9
  107. package/dist/emit/expression-renderer.d.ts +1 -1
  108. package/dist/emit/expression-renderer.js +91 -6
  109. package/dist/emit/route-hal-op.js +18 -5
  110. package/dist/emit/snprintf-helpers.js +15 -4
  111. package/dist/emit/statement-renderer.d.ts +10 -0
  112. package/dist/emit/statement-renderer.js +34 -5
  113. package/dist/emit/utils/async-state-machine.js +221 -125
  114. package/dist/emit/utils/hal-op-cpp-type.d.ts +6 -0
  115. package/dist/emit/utils/hal-op-cpp-type.js +40 -0
  116. package/dist/framework-package.js +2 -0
  117. package/dist/framework-registry.d.ts +17 -0
  118. package/dist/install/framework-catalog.d.ts +53 -0
  119. package/dist/install/framework-catalog.js +107 -0
  120. package/dist/install/handle-install.d.ts +35 -0
  121. package/dist/install/handle-install.js +177 -0
  122. package/dist/install/index.d.ts +4 -0
  123. package/dist/install/index.js +3 -0
  124. package/dist/ir/adc-range-validation.js +40 -26
  125. package/dist/ir/build-ir-state.d.ts +1 -0
  126. package/dist/ir/build-ir-state.js +14 -0
  127. package/dist/ir/build-ir.js +9 -5
  128. package/dist/ir/call-graph.js +16 -0
  129. package/dist/ir/expression-to-ir.js +207 -6
  130. package/dist/ir/feature-registry.js +7 -25
  131. package/dist/ir/function-builder.js +22 -0
  132. package/dist/ir/hal/hal-emitter.d.ts +5 -2
  133. package/dist/ir/hal/hal-emitter.js +50 -18
  134. package/dist/ir/hal/hal-parser.d.ts +6 -0
  135. package/dist/ir/hal/hal-parser.js +81 -0
  136. package/dist/ir/hal/hal-plugins.js +733 -1
  137. package/dist/ir/identifier-collector.js +35 -0
  138. package/dist/ir/interrupt-analysis.d.ts +5 -1
  139. package/dist/ir/interrupt-analysis.js +16 -19
  140. package/dist/ir/memory-budget-validation.js +1 -0
  141. package/dist/ir/network-validation.d.ts +4 -0
  142. package/dist/ir/network-validation.js +184 -0
  143. package/dist/ir/ownership-analysis.js +39 -1
  144. package/dist/ir/peripheral-ownership.js +5 -0
  145. package/dist/ir/peripheral-validation.d.ts +1 -1
  146. package/dist/ir/peripheral-validation.js +6 -3
  147. package/dist/ir/pin-alias-conflict.d.ts +1 -1
  148. package/dist/ir/pin-alias-conflict.js +2 -1
  149. package/dist/ir/pin-capability-validation.js +71 -32
  150. package/dist/ir/pin-mode-validation.d.ts +2 -2
  151. package/dist/ir/pin-mode-validation.js +54 -18
  152. package/dist/ir/pin-safety.d.ts +1 -1
  153. package/dist/ir/pin-safety.js +2 -1
  154. package/dist/ir/program-analysis.d.ts +81 -0
  155. package/dist/ir/program-analysis.js +429 -0
  156. package/dist/ir/pulldown-validation.d.ts +1 -1
  157. package/dist/ir/pulldown-validation.js +2 -1
  158. package/dist/ir/pwm-timer-sharing.d.ts +1 -1
  159. package/dist/ir/pwm-timer-sharing.js +2 -1
  160. package/dist/ir/render-expr.js +11 -0
  161. package/dist/ir/resource-analysis.js +2 -0
  162. package/dist/ir/timer0-pwm-timing-conflict.d.ts +1 -1
  163. package/dist/ir/timer0-pwm-timing-conflict.js +2 -1
  164. package/dist/ir/timing-validation.d.ts +6 -1
  165. package/dist/ir/timing-validation.js +61 -13
  166. package/dist/ir/transformers/call-statement.js +105 -0
  167. package/dist/ir/transformers/expressions.js +62 -0
  168. package/dist/ir/transformers/hal-call-resolver.js +21 -0
  169. package/dist/ir/transformers/hal-emit-helpers.js +1 -1
  170. package/dist/ir/transformers/namespace-methods.js +17 -12
  171. package/dist/ir/transformers/ui-reactive.js +2 -2
  172. package/dist/ir/transformers/variables.js +137 -19
  173. package/dist/ir/try-catch-validation.js +2 -0
  174. package/dist/ir/type-resolution.js +20 -2
  175. package/dist/ir/ui-element-auto-wire.js +7 -5
  176. package/dist/ir/unit-suspicion-validation.js +9 -7
  177. package/dist/ir/utils/map-statements.d.ts +4 -0
  178. package/dist/ir/utils/map-statements.js +79 -0
  179. package/dist/ir/validation-orchestrator.js +15 -8
  180. package/dist/ir/worker-analysis.d.ts +10 -0
  181. package/dist/ir/worker-analysis.js +261 -0
  182. package/dist/libdef/c-to-decl.d.ts +27 -0
  183. package/dist/libdef/c-to-decl.js +397 -0
  184. package/dist/libdef/component-decls.d.ts +2 -0
  185. package/dist/libdef/component-decls.js +6 -0
  186. package/dist/libdef/component-discovery.d.ts +43 -0
  187. package/dist/libdef/component-discovery.js +83 -0
  188. package/dist/libdef/cpp-to-decl.d.ts +9 -0
  189. package/dist/libdef/cpp-to-decl.js +72 -0
  190. package/dist/libdef/registry.js +5 -2
  191. package/dist/lint-cache.d.ts +59 -0
  192. package/dist/lint-cache.js +257 -0
  193. package/dist/orchestrator/graph-builder.js +14 -9
  194. package/dist/orchestrator/type-checker.js +4 -2
  195. package/dist/platform/coop-scheduler-runtime.d.ts +19 -0
  196. package/dist/platform/coop-scheduler-runtime.js +41 -0
  197. package/dist/platform/generic-strategy.d.ts +15 -3
  198. package/dist/platform/generic-strategy.js +49 -4
  199. package/dist/safety/safety-bridge.d.ts +11 -0
  200. package/dist/safety/safety-bridge.js +48 -0
  201. package/dist/safety/sidecar-bridge.d.ts +5 -0
  202. package/dist/safety/sidecar-bridge.js +22 -0
  203. package/dist/safety-hook.d.ts +79 -0
  204. package/dist/safety-hook.js +35 -0
  205. package/dist/testing.d.ts +9 -2
  206. package/dist/testing.js +11 -2
  207. package/dist/transpile.d.ts +3 -0
  208. package/dist/transpile.js +202 -54
  209. package/dist/types.d.ts +22 -3
  210. package/dist/ui/ui-bridge.d.ts +12 -0
  211. package/dist/ui/ui-bridge.js +18 -0
  212. package/dist/ui-hook.d.ts +17 -3
  213. package/dist/utils/cli.js +122 -2
  214. package/dist/utils/fs.d.ts +13 -0
  215. package/dist/utils/fs.js +50 -0
  216. package/dist/utils/ui.js +3 -1
  217. package/package.json +147 -127
  218. package/dist/api/shared/display-adapters/eink-mono.d.ts +0 -2
  219. package/dist/api/shared/display-adapters/eink-mono.js +0 -53
  220. package/dist/api/shared/display-adapters/ssd1309.d.ts +0 -2
  221. package/dist/api/shared/display-adapters/ssd1309.js +0 -136
  222. package/dist/api/shared/display-adapters/st7796.d.ts +0 -2
  223. package/dist/api/shared/display-adapters/st7796.js +0 -132
  224. package/dist/ir/heap-array-validation.d.ts +0 -24
  225. package/dist/ir/heap-array-validation.js +0 -29
@@ -34,7 +34,7 @@ import { getRadioGroups } from "../../ir/ui-element-auto-wire.js";
34
34
  // minPressure gate is skipped for them. Resistive controllers (XPT2046,
35
35
  // STMPE610, Adafruit_TouchScreen) keep the gate.
36
36
  // ---------------------------------------------------------------------------
37
- const CAPACITIVE_TOUCH_LIBS = new Set(["FT6336U", "sdl"]);
37
+ const CAPACITIVE_TOUCH_LIBS = new Set(["FT6336U", "GT911", "CST816S", "sdl"]);
38
38
  /** Generate the ui_poll_touch() body, or null for the no-touch path. */
39
39
  export function generateTouchPollBody(input) {
40
40
  const { library, minPressure, calibration, profile } = input;
@@ -141,9 +141,14 @@ export function emitUIRuntime(ctx) {
141
141
  // display-adapter registry. Must precede the runtime header so the
142
142
  // adapter functions (__tc_display, display_init, etc.) are available.
143
143
  const profile = getDisplayProfile();
144
- // 0. SPI-bus display drivers (ILI9341, ST7796, …) need the SPI library.
145
- // Host render targets (sdl, native-preview) have no SPI bus, so skip it.
146
- const spiDriver = profile?.driver !== "sdl" && profile?.driver !== "native-preview";
144
+ // 0. SPI-bus display drivers (ILI9341, ST7796, …) need the Arduino SPI
145
+ // library but ONLY on the Adafruit path. Native adapters (AVR, ESP32)
146
+ // emit their own bus setup and would break on <SPI.h>. Host render targets
147
+ // (sdl, native-preview) have no SPI bus at all.
148
+ const nativeAdapter = ctx.strategy?.providesDisplayAdapter?.() ?? false;
149
+ const spiDriver = profile?.driver !== "sdl"
150
+ && profile?.driver !== "native-preview"
151
+ && !nativeAdapter;
147
152
  if (spiDriver && !ctx.includes.includes("<SPI.h>")) {
148
153
  ctx.includes.push("<SPI.h>");
149
154
  }
@@ -151,7 +156,7 @@ export function emitUIRuntime(ctx) {
151
156
  // buildEmitterContext (setup.ts), NOT here — emitPreamble runs before this
152
157
  // function, so a push here would be too late to land in the output.
153
158
  if (profile) {
154
- const adapter = generateDisplayAdapter(profile);
159
+ const adapter = generateDisplayAdapter(profile, ctx.strategy);
155
160
  // Prepend includes to the very front — Arduino's auto-prototyper scans
156
161
  // the whole .ino and generates prototypes that reference GFXcanvas16
157
162
  // etc. before any #include, so the GFX header must come first.
@@ -164,6 +169,23 @@ export function emitUIRuntime(ctx) {
164
169
  ctx.sourceLines.push(is888
165
170
  ? "#define UI_COLOR_DEPTH 888\n#define UI_COLOR_T uint32_t\n#define UI_DIM_MASK 0x7F7F7Fu"
166
171
  : "#define UI_COLOR_DEPTH 565\n#define UI_COLOR_T uint16_t\n#define UI_DIM_MASK 0x7BEFu");
172
+ // The native CuttlefishGFX/CuttlefishCanvas16 class slice is emitted ONLY
173
+ // for adapters that instantiate CuttlefishGFX by value (the planned
174
+ // framework-avr/framework-esp32 native panel drivers). Adapters that alias
175
+ // the token via macro — the Adafruit path (`#define CuttlefishCanvas16
176
+ // GFXcanvas16`) and the SDL path (`#define CuttlefishCanvas16 SdlGfxCanvas`)
177
+ // — own the canvas type; emitting the class slice alongside the macro makes
178
+ // `class CuttlefishCanvas16` macro-expand into a redefinition of Adafruit's
179
+ // GFXcanvas16. Detect the macro-alias path from the adapter's own includes
180
+ // and suppress the slice there. The previous gate reused
181
+ // providesDisplayAdapter(), which merely means "the strategy owns its
182
+ // adapters" (true for Arduino/Adafruit) and is unrelated to whether the
183
+ // native GFX class is wanted — it wrongly emitted the slice on every
184
+ // Arduino build, breaking compilation.
185
+ const macroAliasesCanvas16 = /^\s*#\s*define\s+CuttlefishCanvas16\b/m.test(adapter.includes);
186
+ if (!macroAliasesCanvas16) {
187
+ ctx.sourceLines.push(ui.emitCuttlefishGfx(true));
188
+ }
167
189
  ctx.sourceLines.push(adapter.declaration);
168
190
  ctx.sourceLines.push(adapter.functions);
169
191
  }
@@ -173,7 +195,7 @@ export function emitUIRuntime(ctx) {
173
195
  if (profile.touch) {
174
196
  const t = profile.touch;
175
197
  if (t.library) {
176
- touchAdapter = generateTouchAdapter(t);
198
+ touchAdapter = generateTouchAdapter(t, ctx.strategy);
177
199
  // Prepend includes to the front (Arduino auto-prototyper safety).
178
200
  ctx.sourceLines.unshift(touchAdapter.includes[0]);
179
201
  for (let i = 1; i < touchAdapter.includes.length; i++) {
@@ -252,13 +274,15 @@ export function emitUIRuntime(ctx) {
252
274
  // header so its #ifndef guards adopt them. Source of truth:
253
275
  // resolveScrollConfig(profile.scroll). Defaults derive from the declared
254
276
  // touch hardware, so the demo (XPT2046) gets resistive+full with no config.
255
- const scroll = resolveScrollConfig(profile, { buildTarget: profile._buildTarget });
256
- ctx.sourceLines.push(`#define UI_SCROLL_MAX_OVERSCROLL ${scroll.maxOverscroll}`, `#define UI_SCROLL_STIFFNESS_X10 ${Math.round(scroll.stiffness * 10)}`, `#define UI_SCROLL_EDGE_SNAP_PX ${scroll.edgeSnapPx}`, `#define UI_SCROLL_DRAG_SCALE_X10 ${Math.round(scroll.dragScale * 10)}`, `#define UI_SCROLL_INPUT_TIER_CAPACITIVE ${scroll.inputTier === "capacitive" ? 1 : 0}`, `#define UI_SCROLL_INPUT_TIER_RESISTIVE ${scroll.inputTier === "resistive" ? 1 : 0}`, `#define UI_SCROLL_INPUT_TIER_NONE ${scroll.inputTier === "none" ? 1 : 0}`, `#define UI_SCROLL_RENDER_TIER_FULL ${scroll.renderTier === "full" ? 1 : 0}`, `#define UI_SCROLL_RENDER_TIER_CONSTRAINED ${scroll.renderTier === "constrained" ? 1 : 0}`, `#define UI_SCROLL_CANVAS_BUDGET_BYTES ${scroll.scrollCanvasBudgetBytes}`, ...(scroll.debug ? [`#define UI_SCROLL_DEBUG 1`] : []));
277
+ const scroll = resolveScrollConfig(profile);
278
+ ctx.sourceLines.push(`#define UI_SCROLL_MAX_OVERSCROLL ${scroll.maxOverscroll}`, `#define UI_SCROLL_STIFFNESS_X10 ${Math.round(scroll.stiffness * 10)}`, `#define UI_SCROLL_EDGE_SNAP_PX ${scroll.edgeSnapPx}`, `#define UI_SCROLL_DRAG_SCALE_X10 ${Math.round(scroll.dragScale * 10)}`, `#define UI_SCROLL_INPUT_TIER_CAPACITIVE ${scroll.inputTier === "capacitive" ? 1 : 0}`, `#define UI_SCROLL_INPUT_TIER_RESISTIVE ${scroll.inputTier === "resistive" ? 1 : 0}`, `#define UI_SCROLL_INPUT_TIER_NONE ${scroll.inputTier === "none" ? 1 : 0}`, `#define UI_SCROLL_RENDER_TIER_FULL ${scroll.renderTier === "full" ? 1 : 0}`, `#define UI_SCROLL_RENDER_TIER_CONSTRAINED ${scroll.renderTier === "constrained" ? 1 : 0}`, ...(scroll.debug ? [`#define UI_SCROLL_DEBUG 1`] : []));
257
279
  // Forward-declare __ui_kb_set_onchange before the runtime header: the
258
280
  // header's keyboard-open function calls it, but the definition is emitted
259
281
  // later (section 8b). On native (single TU) this must precede the header.
260
282
  ctx.sourceLines.push(`void __ui_kb_set_onchange();`);
261
- ctx.sourceLines.push(emitRuntimeHeader());
283
+ ctx.sourceLines.push(emitRuntimeHeader({
284
+ nativeDisplayActive: ctx.strategy?.providesDisplayAdapter?.() ?? false,
285
+ }));
262
286
  // 1.5. Touch poll function (uses the adapter pattern). Body is generated by
263
287
  // generateTouchPollBody — extracted for unit-testability of the capacitive
264
288
  // vs resistive branch (FT6336U/SDL skip the minPressure gate).
@@ -88,7 +88,7 @@ export declare class ExpressionRenderer {
88
88
  private readonly stringEnumNames;
89
89
  private readonly largeEnumNames;
90
90
  private readonly knownFunctionReturnTypes?;
91
- private readonly knownVariableTypes?;
91
+ private knownVariableTypes?;
92
92
  private readonly pointerVarTypes?;
93
93
  private readonly globalPointerVarTypes?;
94
94
  private readonly stringVarNames?;
@@ -10,6 +10,7 @@ import { accessorGetterName } from "./utils/cpp-helpers.js";
10
10
  import { INTEGRAL_CPP_TYPE_RE } from "./utils/cpp-helpers.js";
11
11
  import { renderPeripheralProperty } from "../mapping/peripheral-names.js";
12
12
  import { parseCppType, renderCppType, bareType, parsedIsPointer, parsedIsVector, needsCStrForStringLike } from "../api/shared/cpp-type-ir.js";
13
+ import { cppTypeForHalOp } from "./utils/hal-op-cpp-type.js";
13
14
  /**
14
15
  * Renders ExpressionIR nodes to C++ code strings.
15
16
  */
@@ -79,6 +80,18 @@ export class ExpressionRenderer {
79
80
  if (!expr || typeof expr !== 'object' || !expr.kind) {
80
81
  return "/* invalid expression */";
81
82
  }
83
+ // When a per-scope knownVariableTypes is passed (e.g. a function's child
84
+ // emission scope carrying its parameters), install it on the instance for
85
+ // the duration of this render so every recursive render() call and
86
+ // renderIdentifier() reads the merged scope (globals + params). Without
87
+ // this, recursive paths that don't forward knownVariableTypes (ternary
88
+ // branches, call args, array elements, …) would resolve identifiers
89
+ // against only the top-level scope, missing function parameters — which
90
+ // breaks reserved-name escaping for params like `min`/`max` on Arduino.
91
+ const prevKnownVariableTypes = this.knownVariableTypes;
92
+ if (knownVariableTypes) {
93
+ this.knownVariableTypes = knownVariableTypes;
94
+ }
82
95
  let rendered;
83
96
  switch (expr.kind) {
84
97
  case "number": {
@@ -99,7 +112,7 @@ export class ExpressionRenderer {
99
112
  rendered = expr.value ? "true" : "false";
100
113
  break;
101
114
  case "identifier":
102
- rendered = this.renderIdentifier(expr.value);
115
+ rendered = this.renderIdentifier(expr.value, knownVariableTypes);
103
116
  break;
104
117
  case "raw":
105
118
  rendered = this.renderRaw(expr.value, exprTransformer);
@@ -149,6 +162,18 @@ export class ExpressionRenderer {
149
162
  case "method-call":
150
163
  rendered = this.renderMethodCall(expr, exprTransformer);
151
164
  break;
165
+ case "call": {
166
+ // Bare call expression: `Name(args)` with no receiver (e.g. a
167
+ // constructor call `SafeInt(x)` written inline, or a free function).
168
+ // Method-on-receiver calls lower as "method-call"; this arm is for the
169
+ // receiverless form. The callee is escaped against C++ reserved names;
170
+ // when a target type is in scope, renderValueForTarget (the caller)
171
+ // wraps this with template args (SafeInt(x) -> SafeInt<int32_t>(x)).
172
+ const callArgs = expr.args.map(a => this.render(a, exprTransformer)).join(", ");
173
+ const callCallee = escapeCppKeyword(exprTransformer ? exprTransformer(expr.callee) : expr.callee, this.strategy.reservedNames());
174
+ rendered = `${callCallee}(${callArgs})`;
175
+ break;
176
+ }
152
177
  case "element-access":
153
178
  rendered = this.renderElementAccess(expr, exprTransformer, knownVariableTypes);
154
179
  break;
@@ -191,9 +216,10 @@ export class ExpressionRenderer {
191
216
  `This is a transpiler bug; the renderer is missing a case for this kind.`);
192
217
  }
193
218
  }
219
+ this.knownVariableTypes = prevKnownVariableTypes;
194
220
  return normalizeRawExpression(rendered, this.strategy, this.classNameMap);
195
221
  }
196
- renderIdentifier(value) {
222
+ renderIdentifier(value, scopeKnownVariableTypes) {
197
223
  const nullVal = this.strategy.nullValue();
198
224
  // expression-to-ir lowers the TS `null` literal to the identifier sentinel
199
225
  // "nullptr" and `undefined` to "CUTTLEFISH_UNDEFINED". Both must route
@@ -209,7 +235,27 @@ export class ExpressionRenderer {
209
235
  if (this.strategy.passthroughMacroNames().has(value)) {
210
236
  return value;
211
237
  }
212
- return escapeCppKeyword(value, this.strategy.reservedNames());
238
+ // Platform-reserved names (e.g. Arduino `Serial`, or the `min`/`max` math
239
+ // macros) are globals/macros provided by the framework headers. Escaping
240
+ // them would break references to those globals — UNLESS the name is a
241
+ // user-declared variable/parameter that shadows and would collide. Check
242
+ // both the instance field (globals/locals seen so far) AND the per-scope
243
+ // map passed by the caller (function parameters, which live in the child
244
+ // emission scope and aren't on the instance field). Declaration sites
245
+ // already escape user vars (renderParameters → renderTypedName), so the
246
+ // body references must escape to match.
247
+ const reservedNames = this.strategy.reservedNames();
248
+ if (reservedNames.has(value)) {
249
+ const isUserVar = (this.knownVariableTypes !== undefined && this.knownVariableTypes.has(value)) ||
250
+ (scopeKnownVariableTypes !== undefined && scopeKnownVariableTypes.has(value)) ||
251
+ (this.pointerVarTypes !== undefined && this.pointerVarTypes.has(value)) ||
252
+ (this.globalPointerVarTypes !== undefined && this.globalPointerVarTypes.has(value)) ||
253
+ (this.stringVarNames !== undefined && this.stringVarNames.has(value));
254
+ if (!isUserVar) {
255
+ return value;
256
+ }
257
+ }
258
+ return escapeCppKeyword(value, reservedNames);
213
259
  }
214
260
  renderRaw(value, exprTransformer) {
215
261
  const effectiveClassNameMap = this.classNameMap;
@@ -403,6 +449,8 @@ export class ExpressionRenderer {
403
449
  }
404
450
  case "unary":
405
451
  return expr.operator === "!" ? "bool" : this.inferExpressionCppType(expr.operand, knownVariableTypes);
452
+ case "hal-expr":
453
+ return cppTypeForHalOp(expr.operation.operation);
406
454
  default:
407
455
  return undefined;
408
456
  }
@@ -714,10 +762,16 @@ export class ExpressionRenderer {
714
762
  }
715
763
  case "hal-expr": {
716
764
  const rendered = this.render(expr, exprTransformer);
717
- // HAL expressions that resolve to string-like outputs use %s
718
765
  if (rendered.startsWith('"') || this.stringVarNames?.has(rendered)) {
719
766
  return { format: "%s", arg: rendered, estimatedLength: 32 };
720
767
  }
768
+ const cppType = cppTypeForHalOp(expr.operation.operation);
769
+ if (cppType && this.isStringLikeCppType(cppType)) {
770
+ return { format: "%s", arg: rendered, estimatedLength: 32 };
771
+ }
772
+ if (cppType === "bool") {
773
+ return { format: "%s", arg: `(${rendered} ? "true" : "false")`, estimatedLength: 5 };
774
+ }
721
775
  return { format: "%d", arg: rendered, estimatedLength: 12 };
722
776
  }
723
777
  case "method-call":
@@ -878,10 +932,27 @@ export class ExpressionRenderer {
878
932
  * (safer than a wrong cast).
879
933
  */
880
934
  renderValueForTarget(expr, targetType, knownVariableTypes, exprTransformer) {
881
- const rendered = this.render(expr, exprTransformer, knownVariableTypes);
882
935
  if (!targetType)
883
- return rendered;
936
+ return this.render(expr, exprTransformer, knownVariableTypes);
884
937
  const normalizedTarget = targetType.trim();
938
+ // Inject template args for constructor calls on pre-C++17 toolchains
939
+ // (e.g. AVR without CTAD). SafeVariable(0) → SafeVariable<int32_t>(0).
940
+ // Handles both `method-call` (when the constructor appears as a standalone
941
+ // initializer) and `call` (when it appears on the RHS of an assignment,
942
+ // which lowers as `T x = {}; x = T(args);`). The callee must match the
943
+ // template name and the target must be a `Name<...>` instantiation, so a
944
+ // regular function call assigned to a non-templated type never matches.
945
+ const tmplMatch = normalizedTarget.match(/^([A-Za-z_]\w*)<(.+)>/);
946
+ const exprAny = expr;
947
+ const isCtorCall = tmplMatch != null
948
+ && (exprAny.kind === "method-call" || exprAny.kind === "call")
949
+ && exprAny.callee === tmplMatch[1]
950
+ && exprAny.callee != null && !exprAny.callee.includes("<");
951
+ if (isCtorCall && exprAny.args) {
952
+ const argsText = exprAny.args.map(a => this.render(a, exprTransformer, knownVariableTypes)).join(", ");
953
+ return `${tmplMatch[1]}<${tmplMatch[2]}>(${argsText})`;
954
+ }
955
+ const rendered = this.render(expr, exprTransformer, knownVariableTypes);
885
956
  // Never cast into string-like storage — enums that lower to const char*
886
957
  // (string enums) already match, and a string target is never an enum boundary.
887
958
  if (this.isStringLikeCppType(normalizedTarget))
@@ -1350,6 +1421,8 @@ export class ExpressionRenderer {
1350
1421
  renderMethodCall(expr, exprTransformer) {
1351
1422
  const argsText = expr.args.map(a => this.render(a, exprTransformer)).join(", ");
1352
1423
  let callee = exprTransformer ? exprTransformer(expr.callee) : expr.callee;
1424
+ const rExpr = expr.receiverExpr;
1425
+ const mName = expr.methodName;
1353
1426
  // Convert . to :: for static or namespace method calls if the IR flagged them.
1354
1427
  if (expr.isStatic || expr.isNamespace) {
1355
1428
  callee = callee.replace(/\./g, "::");
@@ -1380,6 +1453,18 @@ export class ExpressionRenderer {
1380
1453
  return callee;
1381
1454
  }
1382
1455
  callee = this.escapeFinalMemberName(callee);
1456
+ // Structured-receiver path: when the method-call IR carries a receiverExpr
1457
+ // (the safe.read().ok().fail() chain builder sets this), render the receiver
1458
+ // via the full renderer so nested lambda args survive (the flat callee string
1459
+ // would have destroyed them as /* __lambda__ */ placeholders). The methodName
1460
+ // is escaped against reserved names (e.g. div -> div_).
1461
+ const receiverExpr = expr.receiverExpr;
1462
+ const methodName = expr.methodName;
1463
+ if (receiverExpr !== undefined && methodName !== undefined) {
1464
+ const receiverText = this.render(receiverExpr, exprTransformer);
1465
+ const escapedMethod = escapeCppKeyword(methodName, this.strategy.reservedNames());
1466
+ return `${receiverText}.${escapedMethod}(${argsText})`;
1467
+ }
1383
1468
  return `${callee}(${argsText})`;
1384
1469
  }
1385
1470
  escapeFinalMemberName(callee) {
@@ -1,13 +1,26 @@
1
1
  // ---------------------------------------------------------------------------
2
2
  // Op routing — dispatch a HALOpIR to the right strategy resolver.
3
3
  //
4
- // display.* ops go to resolveDisplayOp (graphics-specific); everything else
5
- // goes to resolveHALOperation (the existing generic seam). Keeps the consumer
6
- // sites (expression-renderer, statement-renderer, render-expr) DRY.
4
+ // display.* ops go to resolveDisplayOp (graphics-specific).
5
+ // safety.* ops go to the safety hook's resolveSafetyOp (when @typecad/safety
6
+ // is loaded) — NEVER to a per-target strategy, because the safety
7
+ // package is MCU-agnostic by construction.
8
+ // everything else goes to resolveHALOperation (the existing generic seam).
9
+ // Keeps the consumer sites (expression-renderer, statement-renderer,
10
+ // render-expr) DRY.
7
11
  // ---------------------------------------------------------------------------
12
+ import { getSafetyHook } from "../safety-hook.js";
8
13
  export function routeHALOp(op, strategy) {
9
- if (typeof op.operation === "string" && op.operation.startsWith("display.")) {
10
- return strategy.resolveDisplayOp?.(op);
14
+ if (typeof op.operation === "string") {
15
+ if (op.operation.startsWith("display.")) {
16
+ return strategy.resolveDisplayOp?.(op);
17
+ }
18
+ if (op.operation.startsWith("safety.")) {
19
+ // Safety ops are resolved by the @typecad/safety package via hook.
20
+ // Returns undefined if the package is not loaded (caller emits an
21
+ // unhandled-op warning).
22
+ return getSafetyHook()?.resolveSafetyOp?.(op);
23
+ }
11
24
  }
12
25
  return strategy.resolveHALOperation?.(op);
13
26
  }
@@ -8,6 +8,7 @@
8
8
  // Types are imported from @typecad/cuttlefish/api/shared.
9
9
  // ---------------------------------------------------------------------------
10
10
  import { escapeCppStringLiteral, escapeSnprintfFormatFragment } from "../utils/strings.js";
11
+ import { cppTypeForHalOp } from "./utils/hal-op-cpp-type.js";
11
12
  import { parsedElementString, parsedIsStringLike } from "../api/shared/cpp-type-ir.js";
12
13
  // ---------------------------------------------------------------------------
13
14
  // Scope state management
@@ -235,11 +236,14 @@ export function inferSnprintfArg(expr, strategy, scopeState, renderExpression, p
235
236
  return { format: "%d", arg: renderExpression(expr), estimatedLength: 12, preludeLines: [] };
236
237
  }
237
238
  }
238
- const arduinoIntFunctions = new Set([
239
- "digitalRead", "analogRead", "pulseIn", "pulseInLong",
240
- "Wire_available", "Serial_available",
239
+ const halIntFunctions = new Set([
240
+ ...(strategy?.halCallNames?.() ?? new Set()),
241
+ // pulseIn/pulseInLong and the Wire_available/Serial_available forms
242
+ // are framework-specific suffix conventions; frameworks may add them
243
+ // to halCallNames. Kept as a fallback here until all frameworks do.
244
+ "pulseIn", "pulseInLong", "Wire_available", "Serial_available",
241
245
  ]);
242
- if (arduinoIntFunctions.has(funcName)) {
246
+ if (halIntFunctions.has(funcName)) {
243
247
  return { format: "%d", arg: renderExpression(expr), estimatedLength: 12, preludeLines: [] };
244
248
  }
245
249
  }
@@ -369,6 +373,13 @@ export function inferSnprintfArg(expr, strategy, scopeState, renderExpression, p
369
373
  if (rendered.startsWith('"') || stringVarNames?.has(rendered)) {
370
374
  return { format: "%s", arg: rendered, estimatedLength: 32, preludeLines: [] };
371
375
  }
376
+ const cppType = cppTypeForHalOp(expr.operation.operation);
377
+ if (cppType && strategy.isStringLikeType(cppType)) {
378
+ return { format: "%s", arg: rendered, estimatedLength: 32, preludeLines: [] };
379
+ }
380
+ if (cppType === "bool") {
381
+ return { format: "%s", arg: `(${rendered} ? "true" : "false")`, estimatedLength: 5, preludeLines: [] };
382
+ }
372
383
  return { format: "%d", arg: rendered, estimatedLength: 12, preludeLines: [] };
373
384
  }
374
385
  default:
@@ -63,6 +63,14 @@ interface StatementRendererContext {
63
63
  * list. Optional so ad-hoc/test constructions still work.
64
64
  */
65
65
  diagnostics?: Diagnostic[];
66
+ /**
67
+ * Optional compliance context for A3-9-1 (fixed-width integer default).
68
+ * When provided and A3-9-1 is enforced, defaultNumericType() returns a
69
+ * fixed-width type instead of the legacy 'int'.
70
+ */
71
+ compliance?: {
72
+ isBanned(ruleId: string): boolean;
73
+ };
66
74
  }
67
75
  /**
68
76
  * Renders StatementIR nodes to C++ code strings.
@@ -83,6 +91,8 @@ export declare class StatementRenderer {
83
91
  private readonly interfaceFieldTypes;
84
92
  /** Shared emit-time diagnostics sink (see StatementRendererContext.diagnostics). */
85
93
  private readonly _diagnostics;
94
+ /** Compliance context for A3-9-1 fixed-width integer default. */
95
+ private readonly _compliance?;
86
96
  constructor(context: StatementRendererContext);
87
97
  /**
88
98
  * Gets the underlying expression renderer for direct use.
@@ -116,6 +116,7 @@ export class StatementRenderer {
116
116
  this.namespaceNames = context.namespaceNames ?? new Set();
117
117
  this.interfaceFieldTypes = context.interfaceFieldTypes ?? new Map();
118
118
  this._diagnostics = context.diagnostics ?? [];
119
+ this._compliance = context.compliance;
119
120
  // Create expression renderer with shared context
120
121
  this.expressionRenderer = new ExpressionRenderer({
121
122
  strategy: context.strategy,
@@ -324,7 +325,7 @@ export class StatementRenderer {
324
325
  this.expressionRenderer.pushPrelude([
325
326
  `const char* ${idxVar}_keys[] = { ${keysArr} };`,
326
327
  ]);
327
- return `for (${this.strategy.defaultNumericType()} ${idxVar} = 0; ${idxVar} < ${statement.keys.length}; ${idxVar}++)`;
328
+ return `for (${this.strategy.defaultNumericType(this._compliance)} ${idxVar} = 0; ${idxVar} < ${statement.keys.length}; ${idxVar}++)`;
328
329
  }
329
330
  const varDecl = statement.variable;
330
331
  if (varDecl.kind === "var_decl") {
@@ -485,6 +486,28 @@ export class StatementRenderer {
485
486
  const rawStmt = statement.callee.slice('__RAW_STMT__'.length);
486
487
  return forHeader ? rawStmt : `${rawStmt.endsWith(';') ? rawStmt : rawStmt + ';'}`;
487
488
  }
489
+ // Handle expression statements whose IR must render at EMIT time (not the
490
+ // build-time renderExprAsText). The safe.read().ok().fail() chain uses this:
491
+ // its lambda args can only render via the emit-time renderLambda (which
492
+ // produces [&](){...}), so the structured chain IR is carried as args[0]
493
+ // and rendered here via the expression renderer.
494
+ if (statement.callee === '__EXPR_STMT__' && statement.args.length >= 1) {
495
+ const exprText = this.expressionRenderer.render(statement.args[0], undefined, knownVariableTypes);
496
+ return forHeader ? exprText : `${exprText};`;
497
+ }
498
+ // Awaited network markers (__WIFI_WAIT__/__HTTP_WAIT__) reaching the
499
+ // plain renderer means the await sits outside an async state machine
500
+ // (top-level await, or a position the state-machine splitter doesn't
501
+ // support). Fall back to the blocking form of the op they carry.
502
+ if ((statement.callee === "__WIFI_WAIT__" || statement.callee === "__HTTP_WAIT__" || statement.callee === "__BLE_WAIT__" || statement.callee === "__HAL_WAIT__")
503
+ && statement.args[0]?.kind === "hal-expr") {
504
+ const resolved = routeHALOp(statement.args[0].operation, this.strategy);
505
+ const code = resolved?.code ?? (resolved?.expression ? `${resolved.expression};` : undefined);
506
+ if (code) {
507
+ return forHeader ? code.replace(/;$/, "") : code;
508
+ }
509
+ return `/* unhandled awaited hal-op: ${statement.args[0].operation.operation} */`;
510
+ }
488
511
  // Handle emit() — compile-time C++ injection
489
512
  if (statement.callee === "__EMIT__") {
490
513
  const rawText = statement.args.map(arg => this.renderEmitArg(arg)).join("");
@@ -645,13 +668,13 @@ export class StatementRenderer {
645
668
  // that don't support std::vector → emit as a plain C-style array.
646
669
  // Mutable arrays (.push/.pop/.indexOf) are rewritten to StaticArray<int> in
647
670
  // the IR builder (via mutableArrayVars) and never reach this branch.
648
- const elementType = parsedElementString(rawType) ?? this.strategy.defaultNumericType();
671
+ const elementType = parsedElementString(rawType) ?? this.strategy.defaultNumericType(this._compliance);
649
672
  return forHeader
650
673
  ? `${elementType} ${safeArrName}[] = { ${elements} }`
651
674
  : `${elementType} ${safeArrName}[] = { ${elements} };`;
652
675
  }
653
676
  // Use "int" for "auto" element type since C arrays need explicit types
654
- const arrayType = statement.initializer.elementType === "auto" ? this.strategy.defaultNumericType() : statement.initializer.elementType;
677
+ const arrayType = statement.initializer.elementType === "auto" ? this.strategy.defaultNumericType(this._compliance) : statement.initializer.elementType;
655
678
  return forHeader
656
679
  ? `${arrayType} ${safeArrName}[] = { ${elements} }`
657
680
  : `${arrayType} ${safeArrName}[] = { ${elements} };`;
@@ -726,7 +749,7 @@ export class StatementRenderer {
726
749
  }
727
750
  // Handle spread array initializers
728
751
  if (statement.initializer.kind === "spread_array") {
729
- let arrayType = statement.initializer.elementType === "auto" ? this.strategy.defaultNumericType() : statement.initializer.elementType;
752
+ let arrayType = statement.initializer.elementType === "auto" ? this.strategy.defaultNumericType(this._compliance) : statement.initializer.elementType;
730
753
  const spreadName = this.expressionRenderer.render(statement.initializer.spreadExpr, calleeTransformer, knownVariableTypes);
731
754
  if (statement.initializer.elementType === "auto" && knownVariableTypes && statement.initializer.spreadExpr.kind === "identifier") {
732
755
  const srcInfo = knownVariableTypes.get(statement.initializer.spreadExpr.value);
@@ -879,11 +902,17 @@ export class StatementRenderer {
879
902
  if (this.stringEnumNames.has(typeName)) {
880
903
  return this.strategy.normalizeCppType("const char*");
881
904
  }
905
+ // A3-9-1: under autosar, substitute the platform's default numeric type
906
+ // for the legacy "int" spelling. The IR layer hardcodes "int" for number
907
+ // literals, so we catch it here at the renderer boundary.
908
+ if (typeName === "int" && this._compliance?.isBanned("A3-9-1")) {
909
+ return this.strategy.defaultNumericType(this._compliance);
910
+ }
882
911
  return this.strategy.normalizeCppType(typeName);
883
912
  }
884
913
  inferFieldType(value, parentName, fieldName) {
885
914
  return inferObjectFieldType(value, this.pointerVarTypes, this.knownFunctionReturnTypes, undefined, undefined, undefined, // largeEnumNames - would need to pass through
886
- parentName, fieldName, this.strategy.defaultNumericType(), (o, n) => this.strategy.resolvePinType?.(o, n));
915
+ parentName, fieldName, this.strategy.defaultNumericType(this._compliance), (o, n) => this.strategy.resolvePinType?.(o, n));
887
916
  }
888
917
  /**
889
918
  * Transform console.log/error/warn calls based on target platform.