@typecad/framework-arduino 0.1.0-alpha.1

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 (63) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +192 -0
  3. package/dist/arduino/i2c-arduino.d.ts +47 -0
  4. package/dist/arduino/i2c-arduino.js +6 -0
  5. package/dist/arduino/index.d.ts +6 -0
  6. package/dist/arduino/index.js +8 -0
  7. package/dist/arduino/spi-arduino.d.ts +42 -0
  8. package/dist/arduino/spi-arduino.js +6 -0
  9. package/dist/arduino/stubs-uno.d.ts +9 -0
  10. package/dist/arduino/stubs-uno.js +65 -0
  11. package/dist/arduino/uart-arduino.d.ts +41 -0
  12. package/dist/arduino/uart-arduino.js +6 -0
  13. package/dist/arduino-class-map.d.ts +5 -0
  14. package/dist/arduino-class-map.js +21 -0
  15. package/dist/arduino-compile.d.ts +24 -0
  16. package/dist/arduino-compile.js +316 -0
  17. package/dist/arduino-libs.d.ts +6 -0
  18. package/dist/arduino-libs.js +11 -0
  19. package/dist/cli-metadata.d.ts +12 -0
  20. package/dist/cli-metadata.js +196 -0
  21. package/dist/cpp-parser.d.ts +47 -0
  22. package/dist/cpp-parser.js +292 -0
  23. package/dist/debug-codegen.d.ts +20 -0
  24. package/dist/debug-codegen.js +115 -0
  25. package/dist/displays/ili9341-spi.d.ts +6 -0
  26. package/dist/displays/ili9341-spi.js +28 -0
  27. package/dist/displays/ssd1309-i2c.d.ts +2 -0
  28. package/dist/displays/ssd1309-i2c.js +16 -0
  29. package/dist/displays/st7796-spi.d.ts +2 -0
  30. package/dist/displays/st7796-spi.js +20 -0
  31. package/dist/graphics/ili9341.d.ts +24 -0
  32. package/dist/graphics/ili9341.js +59 -0
  33. package/dist/index.d.ts +13 -0
  34. package/dist/index.js +27 -0
  35. package/dist/lib-declaration.d.ts +35 -0
  36. package/dist/lib-declaration.js +399 -0
  37. package/dist/lib-discovery.d.ts +38 -0
  38. package/dist/lib-discovery.js +189 -0
  39. package/dist/profile.d.ts +8 -0
  40. package/dist/profile.js +584 -0
  41. package/dist/strategy.d.ts +174 -0
  42. package/dist/strategy.js +2032 -0
  43. package/package.json +73 -0
  44. package/src/arduino/i2c-arduino.ts +72 -0
  45. package/src/arduino/index.ts +18 -0
  46. package/src/arduino/spi-arduino.ts +63 -0
  47. package/src/arduino/stubs-uno.ts +80 -0
  48. package/src/arduino/uart-arduino.ts +59 -0
  49. package/src/arduino-class-map.ts +32 -0
  50. package/src/arduino-compile.ts +359 -0
  51. package/src/arduino-libs.ts +33 -0
  52. package/src/cli-metadata.ts +250 -0
  53. package/src/cpp-parser.ts +361 -0
  54. package/src/debug-codegen.ts +155 -0
  55. package/src/displays/ili9341-spi.ts +33 -0
  56. package/src/displays/ssd1309-i2c.ts +19 -0
  57. package/src/displays/st7796-spi.ts +23 -0
  58. package/src/graphics/ili9341.ts +80 -0
  59. package/src/index.ts +37 -0
  60. package/src/lib-declaration.ts +493 -0
  61. package/src/lib-discovery.ts +267 -0
  62. package/src/profile.ts +676 -0
  63. package/src/strategy.ts +2126 -0
package/src/profile.ts ADDED
@@ -0,0 +1,676 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Arduino profile resolution
3
+ //
4
+ // Resolves Arduino-specific profile settings based on FQBN and program IR.
5
+ // ---------------------------------------------------------------------------
6
+
7
+ import type { ExpressionIR, ProgramIR, StatementIR, Diagnostic, PlatformContext } from "@typecad/cuttlefish/api/shared";
8
+ import type { ArduinoPlatformContext } from "./strategy.js";
9
+
10
+ function arduinoCtx(ctx?: PlatformContext): ArduinoPlatformContext | undefined {
11
+ const data = ctx?.frameworkData as { buildTarget?: string } | undefined;
12
+ return data ?? undefined;
13
+ }
14
+ import type { ArduinoCliMetadata } from "./cli-metadata.js";
15
+ import { loadArduinoCliMetadata } from "./cli-metadata.js";
16
+
17
+ /**
18
+ * Extract architecture from FQBN string.
19
+ * FQBN format: vendor:arch:board[:menu=options]
20
+ * e.g., "arduino:avr:uno" -> "avr"
21
+ */
22
+ function toArchitectureFromFqbn(buildTarget?: string): string | undefined {
23
+ if (!buildTarget) return undefined;
24
+ const parts = buildTarget.split(":");
25
+ return parts.length >= 2 ? parts[1] : undefined;
26
+ }
27
+
28
+ interface ArduinoProfileVariant {
29
+ architecture: string;
30
+ forcedIncludes: string[];
31
+ symbolAliases?: Record<string, string>;
32
+ }
33
+
34
+ interface ArduinoCapabilities {
35
+ architecture: string;
36
+ builtinFunctions: Set<string>;
37
+ builtinGlobals: Set<string>;
38
+ fallbackPins: {
39
+ A0?: number;
40
+ };
41
+ }
42
+
43
+ interface FqbnPinOverride {
44
+ fqbnIncludes: string;
45
+ pins: {
46
+ A0?: number;
47
+ };
48
+ }
49
+
50
+ const PROFILE_VARIANTS: ArduinoProfileVariant[] = [
51
+ { architecture: "avr", forcedIncludes: ["<Arduino.h>", "<avr/wdt.h>"] },
52
+ { architecture: "esp32", forcedIncludes: ["<Arduino.h>"] },
53
+ { architecture: "esp32s3", forcedIncludes: ["<Arduino.h>"] },
54
+ { architecture: "esp32c3", forcedIncludes: ["<Arduino.h>"] },
55
+ { architecture: "esp32c6", forcedIncludes: ["<Arduino.h>"] },
56
+ { architecture: "samd", forcedIncludes: ["<Arduino.h>"] },
57
+ { architecture: "rp2040", forcedIncludes: ["<Arduino.h>"] },
58
+ ];
59
+
60
+ const DEFAULT_PROFILE: ArduinoProfileVariant = {
61
+ architecture: "default",
62
+ forcedIncludes: ["<Arduino.h>"],
63
+ };
64
+
65
+ const CAPABILITY_TABLE: ArduinoCapabilities[] = [
66
+ {
67
+ architecture: "avr",
68
+ builtinFunctions: new Set(["pinMode", "digitalWrite", "analogRead", "analogReference", "delay", "millis", "micros", "setInterval", "setTimeout", "clearInterval", "clearTimeout"]),
69
+ builtinGlobals: new Set(["A0", "HIGH", "LOW", "INPUT", "OUTPUT", "INPUT_PULLUP", "Serial"]),
70
+ fallbackPins: { A0: 14 },
71
+ },
72
+ {
73
+ architecture: "esp32",
74
+ builtinFunctions: new Set(["pinMode", "digitalWrite", "analogRead", "analogReference", "delay", "millis", "micros", "setInterval", "setTimeout", "clearInterval", "clearTimeout"]),
75
+ builtinGlobals: new Set(["A0", "HIGH", "LOW", "INPUT", "OUTPUT", "INPUT_PULLUP", "Serial"]),
76
+ fallbackPins: { A0: 36 },
77
+ },
78
+ {
79
+ architecture: "esp32s3",
80
+ builtinFunctions: new Set(["pinMode", "digitalWrite", "analogRead", "analogReference", "delay", "millis", "micros", "setInterval", "setTimeout", "clearInterval", "clearTimeout"]),
81
+ builtinGlobals: new Set(["A0", "HIGH", "LOW", "INPUT", "OUTPUT", "INPUT_PULLUP", "Serial"]),
82
+ fallbackPins: { A0: 1 },
83
+ },
84
+ {
85
+ architecture: "esp32c3",
86
+ builtinFunctions: new Set(["pinMode", "digitalWrite", "analogRead", "analogReference", "delay", "millis", "micros", "setInterval", "setTimeout", "clearInterval", "clearTimeout"]),
87
+ builtinGlobals: new Set(["A0", "HIGH", "LOW", "INPUT", "OUTPUT", "INPUT_PULLUP", "Serial"]),
88
+ fallbackPins: { A0: 0 },
89
+ },
90
+ {
91
+ architecture: "esp32c6",
92
+ builtinFunctions: new Set(["pinMode", "digitalWrite", "analogRead", "analogReference", "delay", "millis", "micros", "setInterval", "setTimeout", "clearInterval", "clearTimeout"]),
93
+ builtinGlobals: new Set(["A0", "HIGH", "LOW", "INPUT", "OUTPUT", "INPUT_PULLUP", "Serial"]),
94
+ fallbackPins: { A0: 0 },
95
+ },
96
+ {
97
+ architecture: "samd",
98
+ builtinFunctions: new Set(["pinMode", "digitalWrite", "analogRead", "analogReference", "delay", "millis", "micros", "setInterval", "setTimeout", "clearInterval", "clearTimeout"]),
99
+ builtinGlobals: new Set(["A0", "HIGH", "LOW", "INPUT", "OUTPUT", "INPUT_PULLUP", "Serial"]),
100
+ fallbackPins: { A0: 14 },
101
+ },
102
+ {
103
+ architecture: "rp2040",
104
+ builtinFunctions: new Set(["pinMode", "digitalWrite", "analogRead", "analogReference", "delay", "millis", "micros", "setInterval", "setTimeout", "clearInterval", "clearTimeout"]),
105
+ builtinGlobals: new Set(["A0", "HIGH", "LOW", "INPUT", "OUTPUT", "INPUT_PULLUP", "Serial"]),
106
+ fallbackPins: { A0: 26 },
107
+ },
108
+ ];
109
+
110
+ const DEFAULT_CAPABILITIES: ArduinoCapabilities = {
111
+ architecture: "default",
112
+ builtinFunctions: new Set(["pinMode", "digitalWrite", "analogRead", "analogReference", "delay", "millis", "micros", "setInterval", "setTimeout", "clearInterval", "clearTimeout"]),
113
+ builtinGlobals: new Set(["A0", "INPUT", "OUTPUT", "INPUT_PULLUP", "Serial"]),
114
+ fallbackPins: { A0: 0 },
115
+ };
116
+
117
+ const FQBN_PIN_OVERRIDES: FqbnPinOverride[] = [
118
+ { fqbnIncludes: "arduino:avr:uno", pins: { A0: 14 } },
119
+ { fqbnIncludes: "arduino:avr:nano", pins: { A0: 14 } },
120
+ { fqbnIncludes: "arduino:avr:mega", pins: { A0: 54 } },
121
+ { fqbnIncludes: "arduino:samd:mkrzero", pins: { A0: 15 } },
122
+ { fqbnIncludes: "esp32:esp32:", pins: { A0: 36 } },
123
+ { fqbnIncludes: "esp32:esp32s3:", pins: { A0: 1 } },
124
+ { fqbnIncludes: "esp32:esp32c3:", pins: { A0: 0 } },
125
+ { fqbnIncludes: "esp32:esp32c6:", pins: { A0: 0 } },
126
+ { fqbnIncludes: "rp2040:rp2040:", pins: { A0: 26 } },
127
+ ];
128
+
129
+ export interface ResolvedArduinoProfile {
130
+ forcedIncludes: string[];
131
+ symbolAliases: Record<string, string>;
132
+ shimLines: string[];
133
+ diagnostics: Diagnostic[];
134
+ }
135
+
136
+ function walkStatements(statements: StatementIR[], onStatement: (statement: StatementIR) => void): void {
137
+ for (const statement of statements) {
138
+ onStatement(statement);
139
+ if (statement.kind === "while") {
140
+ walkStatements(statement.body, onStatement);
141
+ } else if (statement.kind === "do_while") {
142
+ walkStatements(statement.body, onStatement);
143
+ } else if (statement.kind === "if") {
144
+ walkStatements(statement.thenBranch, onStatement);
145
+ if (statement.elseBranch) walkStatements(statement.elseBranch, onStatement);
146
+ } else if (statement.kind === "for") {
147
+ if (statement.body) walkStatements(statement.body, onStatement);
148
+ } else if (statement.kind === "for_of" || statement.kind === "for_in") {
149
+ if (statement.body) walkStatements(statement.body, onStatement);
150
+ } else if (statement.kind === "try") {
151
+ walkStatements(statement.tryBlock, onStatement);
152
+ if (statement.catchBlock) walkStatements(statement.catchBlock, onStatement);
153
+ if (statement.finallyBlock) walkStatements(statement.finallyBlock, onStatement);
154
+ } else if (statement.kind === "block") {
155
+ walkStatements(statement.body ?? [], onStatement);
156
+ }
157
+ }
158
+ }
159
+
160
+ function collectUsedIdentifiers(program: ProgramIR): Set<string> {
161
+ const used = new Set<string>();
162
+
163
+ function collectExpression(expr?: ExpressionIR): void {
164
+ if (!expr) {
165
+ return;
166
+ }
167
+
168
+ if (expr.kind === "identifier") {
169
+ used.add(expr.value);
170
+ return;
171
+ }
172
+
173
+ if (expr.kind === "raw") {
174
+ for (const token of expr.value.match(/[A-Za-z_][A-Za-z0-9_]*/g) ?? []) {
175
+ used.add(token);
176
+ }
177
+ }
178
+ }
179
+
180
+ const collectFromStatement = (statement: StatementIR): void => {
181
+ if (statement.kind === "call") {
182
+ if (statement.callee === "__EMIT__") {
183
+ for (const arg of statement.args) {
184
+ if (arg.kind === "string") {
185
+ for (const token of arg.value.match(/[A-Za-z_][A-Za-z0-9_]*/g) ?? []) {
186
+ used.add(token);
187
+ }
188
+ }
189
+ }
190
+ } else {
191
+ const root = statement.callee.split(".")[0];
192
+ if (root) {
193
+ used.add(root);
194
+ }
195
+ for (const arg of statement.args) {
196
+ collectExpression(arg);
197
+ }
198
+ }
199
+ return;
200
+ }
201
+
202
+ if (statement.kind === "assign") {
203
+ used.add(statement.target);
204
+ collectExpression(statement.value);
205
+ return;
206
+ }
207
+
208
+ if (statement.kind === "update") {
209
+ used.add(statement.target);
210
+ return;
211
+ }
212
+
213
+ if (statement.kind === "var_decl") {
214
+ collectExpression(statement.initializer);
215
+ return;
216
+ }
217
+
218
+ if (statement.kind === "return") {
219
+ collectExpression(statement.value);
220
+ return;
221
+ }
222
+
223
+ if (statement.kind === "hal-op") {
224
+ // HAL ops carry raw C++ code (e.g. the Preferences namespace lowers to
225
+ // `Preferences.begin(...)` inside a hal-op). Scan it for identifiers so
226
+ // namespace-detection shims (Preferences/EEPROM/etc.) fire. Without this,
227
+ // `used` never sees "Preferences" and the AVR Preferences shim is dropped
228
+ // (avr-g++: "'Preferences' was not declared in this scope"). Demo #33.
229
+ const op = statement.operation;
230
+ if (op && op.operation === "raw" && typeof op.code === "string") {
231
+ for (const token of op.code.match(/[A-Za-z_][A-Za-z0-9_]*/g) ?? []) {
232
+ used.add(token);
233
+ }
234
+ }
235
+ return;
236
+ }
237
+
238
+ if (statement.kind === "while") {
239
+ collectExpression(statement.condition);
240
+ }
241
+ };
242
+
243
+ walkStatements(program.topLevelStatements, collectFromStatement);
244
+ for (const fn of program.functions) {
245
+ walkStatements(fn.statements, collectFromStatement);
246
+ }
247
+
248
+ return used;
249
+ }
250
+
251
+ function collectCalledFunctions(program: ProgramIR): Set<string> {
252
+ const called = new Set<string>();
253
+
254
+ const collectFromStatement = (statement: StatementIR): void => {
255
+ if (statement.kind === "call") {
256
+ // Exclude method calls (both . and -> syntax) from built-in function checking
257
+ if (!statement.callee.includes(".") && !statement.callee.includes("->")) {
258
+ called.add(statement.callee);
259
+ }
260
+ return;
261
+ }
262
+
263
+ if (statement.kind === "while") {
264
+ return;
265
+ }
266
+ };
267
+
268
+ walkStatements(program.topLevelStatements, collectFromStatement);
269
+ for (const fn of program.functions) {
270
+ walkStatements(fn.statements, collectFromStatement);
271
+ }
272
+
273
+ return called;
274
+ }
275
+
276
+ function collectTopLevelDeclarations(program: ProgramIR): Set<string> {
277
+ const declared = new Set<string>();
278
+ for (const statement of program.topLevelStatements) {
279
+ if (statement.kind === "var_decl") {
280
+ declared.add(statement.name);
281
+ }
282
+ }
283
+
284
+ for (const fn of program.functions) {
285
+ declared.add(fn.originalName);
286
+ }
287
+
288
+ return declared;
289
+ }
290
+
291
+ function resolveVariant(context?: ArduinoPlatformContext): ArduinoProfileVariant {
292
+ const architecture = toArchitectureFromFqbn(context?.buildTarget);
293
+ if (!architecture) {
294
+ return DEFAULT_PROFILE;
295
+ }
296
+
297
+ return PROFILE_VARIANTS.find((item) => item.architecture === architecture) ?? DEFAULT_PROFILE;
298
+ }
299
+
300
+ function resolveCapabilities(context?: ArduinoPlatformContext): ArduinoCapabilities {
301
+ const architecture = toArchitectureFromFqbn(context?.buildTarget);
302
+ if (!architecture) {
303
+ return DEFAULT_CAPABILITIES;
304
+ }
305
+
306
+ return CAPABILITY_TABLE.find((item) => item.architecture === architecture) ?? DEFAULT_CAPABILITIES;
307
+ }
308
+
309
+ function mergeCapabilities(base: ArduinoCapabilities, metadata?: ArduinoCliMetadata): ArduinoCapabilities {
310
+ if (!metadata) {
311
+ return base;
312
+ }
313
+
314
+ return {
315
+ architecture: metadata.architecture ?? base.architecture,
316
+ builtinFunctions: new Set<string>([...base.builtinFunctions, ...metadata.builtinFunctions]),
317
+ builtinGlobals: new Set<string>([...base.builtinGlobals, ...metadata.builtinGlobals]),
318
+ fallbackPins: {
319
+ A0: metadata.pins.A0 ?? base.fallbackPins.A0,
320
+ },
321
+ };
322
+ }
323
+
324
+ function resolveA0Fallback(
325
+ context: ArduinoPlatformContext | undefined,
326
+ capabilities: ArduinoCapabilities,
327
+ metadata?: ArduinoCliMetadata,
328
+ ): number {
329
+ if (metadata?.pins.A0 !== undefined) {
330
+ return metadata.pins.A0;
331
+ }
332
+
333
+ const buildTarget = context?.buildTarget?.toLowerCase() ?? "";
334
+ if (buildTarget) {
335
+ const override = FQBN_PIN_OVERRIDES.find((item) => buildTarget.includes(item.fqbnIncludes.toLowerCase()));
336
+ if (override?.pins.A0 !== undefined) {
337
+ return override.pins.A0;
338
+ }
339
+ }
340
+
341
+ return capabilities.fallbackPins.A0 ?? 0;
342
+ }
343
+
344
+ // ---------------------------------------------------------------------------
345
+ // AVR EEPROM-backed Preferences shim
346
+ //
347
+ // Injected as raw C++ when the program uses the Preferences namespace on a
348
+ // non-ESP32 target. Slot layout (16 bytes each):
349
+ // Byte 0: magic (0xA5 = occupied)
350
+ // Byte 1: type tag (_T_I32/_T_U32/_T_BOOL/_T_FLT/_T_STR)
351
+ // Bytes 2-5: 32-bit DJB hash of key (little-endian)
352
+ // Bytes 6-15: value (10 bytes — int32, uint32, bool, float, strings ≤9 chars)
353
+ //
354
+ // 32 slots × 16 bytes = 512 bytes of EEPROM.
355
+ // ---------------------------------------------------------------------------
356
+
357
+ const AVR_PREFERENCES_SHIM: string[] = [
358
+ 'class __tc_Preferences {',
359
+ ' static const int _SLOT_COUNT = 32;',
360
+ ' static const int _SLOT_SIZE = 16;',
361
+ ' static const int _BASE_ADDR = 0;',
362
+ ' static const uint8_t _MAGIC = 0xA5;',
363
+ ' static const uint8_t _T_I32 = 0x01;',
364
+ ' static const uint8_t _T_U32 = 0x02;',
365
+ ' static const uint8_t _T_BOOL = 0x03;',
366
+ ' static const uint8_t _T_FLT = 0x04;',
367
+ ' static const uint8_t _T_STR = 0x05;',
368
+ ' bool _started;',
369
+ ' static uint32_t _djbHash(const char* s) {',
370
+ ' uint32_t h = 5381;',
371
+ ' while (*s) { h = ((h << 5) + h) + (uint8_t)(*s); s++; }',
372
+ ' return h;',
373
+ ' }',
374
+ ' int _findSlot(const char* key, uint8_t typeTag) {',
375
+ ' uint32_t h = _djbHash(key);',
376
+ ' int start = (int)(h % (uint32_t)_SLOT_COUNT);',
377
+ ' for (int i = 0; i < _SLOT_COUNT; i++) {',
378
+ ' int idx = (start + i) % _SLOT_COUNT;',
379
+ ' int addr = _BASE_ADDR + idx * _SLOT_SIZE;',
380
+ ' if (EEPROM.read(addr) != _MAGIC) return -1;',
381
+ ' if (EEPROM.read(addr + 1) != typeTag) continue;',
382
+ ' uint32_t stored = (uint32_t)EEPROM.read(addr+2)',
383
+ ' | ((uint32_t)EEPROM.read(addr+3) << 8)',
384
+ ' | ((uint32_t)EEPROM.read(addr+4) << 16)',
385
+ ' | ((uint32_t)EEPROM.read(addr+5) << 24);',
386
+ ' if (stored == h) return idx;',
387
+ ' }',
388
+ ' return -1;',
389
+ ' }',
390
+ ' int _findSlotForWrite(const char* key, uint8_t typeTag) {',
391
+ ' uint32_t h = _djbHash(key);',
392
+ ' int start = (int)(h % (uint32_t)_SLOT_COUNT);',
393
+ ' int firstEmpty = -1;',
394
+ ' for (int i = 0; i < _SLOT_COUNT; i++) {',
395
+ ' int idx = (start + i) % _SLOT_COUNT;',
396
+ ' int addr = _BASE_ADDR + idx * _SLOT_SIZE;',
397
+ ' if (EEPROM.read(addr) != _MAGIC) {',
398
+ ' if (firstEmpty < 0) firstEmpty = idx;',
399
+ ' continue;',
400
+ ' }',
401
+ ' if (EEPROM.read(addr + 1) != typeTag) continue;',
402
+ ' uint32_t stored = (uint32_t)EEPROM.read(addr+2)',
403
+ ' | ((uint32_t)EEPROM.read(addr+3) << 8)',
404
+ ' | ((uint32_t)EEPROM.read(addr+4) << 16)',
405
+ ' | ((uint32_t)EEPROM.read(addr+5) << 24);',
406
+ ' if (stored == h) return idx;',
407
+ ' }',
408
+ ' return firstEmpty;',
409
+ ' }',
410
+ ' void _writeHeader(int addr, uint32_t hash, uint8_t typeTag) {',
411
+ ' EEPROM.update(addr, _MAGIC);',
412
+ ' EEPROM.update(addr + 1, typeTag);',
413
+ ' EEPROM.update(addr + 2, (uint8_t)(hash & 0xFF));',
414
+ ' EEPROM.update(addr + 3, (uint8_t)((hash >> 8) & 0xFF));',
415
+ ' EEPROM.update(addr + 4, (uint8_t)((hash >> 16) & 0xFF));',
416
+ ' EEPROM.update(addr + 5, (uint8_t)((hash >> 24) & 0xFF));',
417
+ ' }',
418
+ 'public:',
419
+ ' __tc_Preferences() : _started(false) {}',
420
+ ' void begin(const char* ns, bool readOnly = false) { _started = true; }',
421
+ ' void end() { _started = false; }',
422
+ ' size_t putInt(const char* key, int32_t value) {',
423
+ ' if (!_started) return 0;',
424
+ ' uint32_t h = _djbHash(key);',
425
+ ' int idx = _findSlotForWrite(key, _T_I32);',
426
+ ' if (idx < 0) return 0;',
427
+ ' int addr = _BASE_ADDR + idx * _SLOT_SIZE + 6;',
428
+ ' _writeHeader(addr - 6, h, _T_I32);',
429
+ ' EEPROM.update(addr, (uint8_t)(value & 0xFF));',
430
+ ' EEPROM.update(addr + 1, (uint8_t)((value >> 8) & 0xFF));',
431
+ ' EEPROM.update(addr + 2, (uint8_t)((value >> 16) & 0xFF));',
432
+ ' EEPROM.update(addr + 3, (uint8_t)((value >> 24) & 0xFF));',
433
+ ' return 4;',
434
+ ' }',
435
+ ' int32_t getInt(const char* key, int32_t defaultValue) {',
436
+ ' if (!_started) return defaultValue;',
437
+ ' int idx = _findSlot(key, _T_I32);',
438
+ ' if (idx < 0) return defaultValue;',
439
+ ' int addr = _BASE_ADDR + idx * _SLOT_SIZE + 6;',
440
+ ' return (int32_t)((uint32_t)EEPROM.read(addr)',
441
+ ' | ((uint32_t)EEPROM.read(addr+1) << 8)',
442
+ ' | ((uint32_t)EEPROM.read(addr+2) << 16)',
443
+ ' | ((uint32_t)EEPROM.read(addr+3) << 24));',
444
+ ' }',
445
+ ' size_t putUInt(const char* key, uint32_t value) {',
446
+ ' if (!_started) return 0;',
447
+ ' uint32_t h = _djbHash(key);',
448
+ ' int idx = _findSlotForWrite(key, _T_U32);',
449
+ ' if (idx < 0) return 0;',
450
+ ' int addr = _BASE_ADDR + idx * _SLOT_SIZE + 6;',
451
+ ' _writeHeader(addr - 6, h, _T_U32);',
452
+ ' EEPROM.update(addr, (uint8_t)(value & 0xFF));',
453
+ ' EEPROM.update(addr + 1, (uint8_t)((value >> 8) & 0xFF));',
454
+ ' EEPROM.update(addr + 2, (uint8_t)((value >> 16) & 0xFF));',
455
+ ' EEPROM.update(addr + 3, (uint8_t)((value >> 24) & 0xFF));',
456
+ ' return 4;',
457
+ ' }',
458
+ ' uint32_t getUInt(const char* key, uint32_t defaultValue) {',
459
+ ' if (!_started) return defaultValue;',
460
+ ' int idx = _findSlot(key, _T_U32);',
461
+ ' if (idx < 0) return defaultValue;',
462
+ ' int addr = _BASE_ADDR + idx * _SLOT_SIZE + 6;',
463
+ ' return (uint32_t)EEPROM.read(addr)',
464
+ ' | ((uint32_t)EEPROM.read(addr+1) << 8)',
465
+ ' | ((uint32_t)EEPROM.read(addr+2) << 16)',
466
+ ' | ((uint32_t)EEPROM.read(addr+3) << 24);',
467
+ ' }',
468
+ ' size_t putBool(const char* key, bool value) {',
469
+ ' if (!_started) return 0;',
470
+ ' uint32_t h = _djbHash(key);',
471
+ ' int idx = _findSlotForWrite(key, _T_BOOL);',
472
+ ' if (idx < 0) return 0;',
473
+ ' int addr = _BASE_ADDR + idx * _SLOT_SIZE + 6;',
474
+ ' _writeHeader(addr - 6, h, _T_BOOL);',
475
+ ' EEPROM.update(addr, value ? 1 : 0);',
476
+ ' return 1;',
477
+ ' }',
478
+ ' bool getBool(const char* key, bool defaultValue) {',
479
+ ' if (!_started) return defaultValue;',
480
+ ' int idx = _findSlot(key, _T_BOOL);',
481
+ ' if (idx < 0) return defaultValue;',
482
+ ' return EEPROM.read(_BASE_ADDR + idx * _SLOT_SIZE + 6) != 0;',
483
+ ' }',
484
+ ' size_t putFloat(const char* key, float value) {',
485
+ ' if (!_started) return 0;',
486
+ ' uint32_t h = _djbHash(key);',
487
+ ' int idx = _findSlotForWrite(key, _T_FLT);',
488
+ ' if (idx < 0) return 0;',
489
+ ' int addr = _BASE_ADDR + idx * _SLOT_SIZE + 6;',
490
+ ' _writeHeader(addr - 6, h, _T_FLT);',
491
+ ' uint8_t* pb = (uint8_t*)&value;',
492
+ ' for (int i = 0; i < 4; i++) EEPROM.update(addr + i, pb[i]);',
493
+ ' return 4;',
494
+ ' }',
495
+ ' float getFloat(const char* key, float defaultValue) {',
496
+ ' if (!_started) return defaultValue;',
497
+ ' int idx = _findSlot(key, _T_FLT);',
498
+ ' if (idx < 0) return defaultValue;',
499
+ ' int addr = _BASE_ADDR + idx * _SLOT_SIZE + 6;',
500
+ ' float v; uint8_t* pb = (uint8_t*)&v;',
501
+ ' for (int i = 0; i < 4; i++) pb[i] = EEPROM.read(addr + i);',
502
+ ' return v;',
503
+ ' }',
504
+ ' size_t putString(const char* key, const char* value) {',
505
+ ' if (!_started) return 0;',
506
+ ' uint32_t h = _djbHash(key);',
507
+ ' int idx = _findSlotForWrite(key, _T_STR);',
508
+ ' if (idx < 0) return 0;',
509
+ ' int addr = _BASE_ADDR + idx * _SLOT_SIZE + 6;',
510
+ ' _writeHeader(addr - 6, h, _T_STR);',
511
+ ' int vLen = (int)strlen(value);',
512
+ ' if (vLen > 9) vLen = 9;',
513
+ ' for (int i = 0; i < vLen; i++) EEPROM.update(addr + i, (uint8_t)value[i]);',
514
+ ' EEPROM.update(addr + vLen, 0);',
515
+ ' return (size_t)vLen;',
516
+ ' }',
517
+ ' const char* getString(const char* key, const char* defaultValue) {',
518
+ ' if (!_started) return defaultValue;',
519
+ ' int idx = _findSlot(key, _T_STR);',
520
+ ' if (idx < 0) return defaultValue;',
521
+ ' int addr = _BASE_ADDR + idx * _SLOT_SIZE + 6;',
522
+ ' static char result[11];',
523
+ ' for (int i = 0; i < 10; i++) {',
524
+ ' char c = (char)EEPROM.read(addr + i);',
525
+ ' result[i] = c;',
526
+ ' if (c == 0) break;',
527
+ ' }',
528
+ ' result[10] = 0;',
529
+ ' return result;',
530
+ ' }',
531
+ ' bool clear() {',
532
+ ' for (int i = 0; i < _SLOT_COUNT; i++)',
533
+ ' EEPROM.update(_BASE_ADDR + i * _SLOT_SIZE, 0xFF);',
534
+ ' return true;',
535
+ ' }',
536
+ ' bool remove(const char* key) {',
537
+ ' if (!_started) return false;',
538
+ ' uint8_t tags[] = {_T_I32, _T_U32, _T_BOOL, _T_FLT, _T_STR};',
539
+ ' for (int t = 0; t < 5; t++) {',
540
+ ' int idx = _findSlot(key, tags[t]);',
541
+ ' if (idx >= 0) { EEPROM.update(_BASE_ADDR + idx * _SLOT_SIZE, 0xFF); return true; }',
542
+ ' }',
543
+ ' return false;',
544
+ ' }',
545
+ '};',
546
+ ];
547
+
548
+ export function resolveArduinoProfile(program: ProgramIR, platformContext?: PlatformContext): ResolvedArduinoProfile {
549
+ const context = arduinoCtx(platformContext);
550
+ const metadataResult = loadArduinoCliMetadata(context);
551
+ const variant = resolveVariant(context);
552
+ const capabilities = mergeCapabilities(resolveCapabilities(context), metadataResult.metadata);
553
+
554
+ const diagnostics: Diagnostic[] = [...metadataResult.diagnostics];
555
+
556
+ const used = collectUsedIdentifiers(program);
557
+ const calledFunctions = collectCalledFunctions(program);
558
+ const declared = collectTopLevelDeclarations(program);
559
+ const shimLines: string[] = [];
560
+
561
+ for (const functionName of calledFunctions) {
562
+ if (declared.has(functionName)) {
563
+ continue;
564
+ }
565
+
566
+ // Skip internal transpiler functions and runtime helpers
567
+ if (functionName === "__EMIT__" || functionName.startsWith("__RAW_STMT__") || functionName.startsWith("__tc_")) {
568
+ continue;
569
+ }
570
+
571
+ if (!capabilities.builtinFunctions.has(functionName)) {
572
+ diagnostics.push({
573
+ severity: "warning",
574
+ code: "TypeCAD_ARDUINO_FUNC_UNKNOWN",
575
+ message: `Function '${functionName}' is not in the known Arduino built-in function set for architecture '${capabilities.architecture}'.`,
576
+ });
577
+ }
578
+ }
579
+
580
+ for (const identifier of used) {
581
+ if (declared.has(identifier)) {
582
+ continue;
583
+ }
584
+
585
+ if (
586
+ identifier === "HIGH" ||
587
+ identifier === "LOW" ||
588
+ identifier === "A0" ||
589
+ identifier === "INPUT" ||
590
+ identifier === "OUTPUT" ||
591
+ identifier === "INPUT_PULLUP" ||
592
+ identifier === "Serial"
593
+ ) {
594
+ if (!capabilities.builtinGlobals.has(identifier)) {
595
+ diagnostics.push({
596
+ severity: "warning",
597
+ code: "TypeCAD_ARDUINO_GLOBAL_UNKNOWN",
598
+ message: `Global '${identifier}' is not in the known Arduino built-in set for architecture '${capabilities.architecture}'.`,
599
+ });
600
+ }
601
+ }
602
+ }
603
+
604
+ const needsHigh = used.has("HIGH") && !declared.has("HIGH") && !capabilities.builtinGlobals.has("HIGH");
605
+ const needsLow = used.has("LOW") && !declared.has("LOW") && !capabilities.builtinGlobals.has("LOW");
606
+ const needsA0 = used.has("A0") && !declared.has("A0") && !capabilities.builtinGlobals.has("A0");
607
+
608
+ if (needsHigh) {
609
+ shimLines.push("#ifndef HIGH", "#define HIGH 0x1", "#endif");
610
+ diagnostics.push({
611
+ severity: "warning",
612
+ code: "TypeCAD_ARDUINO_SHIM_HIGH",
613
+ message: "Injected fallback HIGH shim. Verify platform-specific value if your core overrides it.",
614
+ });
615
+ }
616
+
617
+ if (needsLow) {
618
+ shimLines.push("#ifndef LOW", "#define LOW 0x0", "#endif");
619
+ diagnostics.push({
620
+ severity: "warning",
621
+ code: "TypeCAD_ARDUINO_SHIM_LOW",
622
+ message: "Injected fallback LOW shim. Verify platform-specific value if your core overrides it.",
623
+ });
624
+ }
625
+
626
+ if (needsA0) {
627
+ const a0Fallback = resolveA0Fallback(context, capabilities, metadataResult.metadata);
628
+ shimLines.push("#ifndef A0", `#define A0 ${a0Fallback}`, "#endif");
629
+ diagnostics.push({
630
+ severity: "warning",
631
+ code: "TypeCAD_ARDUINO_SHIM_A0",
632
+ message: `Injected fallback A0 shim as '${a0Fallback}'. Board-specific analog pin mapping may differ; set --fqbn for accurate pin mapping.`,
633
+ });
634
+ }
635
+
636
+ const extraIncludes: string[] = [];
637
+
638
+ if (used.has('DAC1') || used.has('DAC2')) {
639
+ const arch = toArchitectureFromFqbn(context?.buildTarget);
640
+ if (arch === 'esp32') {
641
+ extraIncludes.push('<driver/dac.h>');
642
+ }
643
+ }
644
+
645
+ const needsPreferences = used.has("Preferences") && !declared.has("Preferences");
646
+ if (needsPreferences) {
647
+ const arch = toArchitectureFromFqbn(context?.buildTarget);
648
+ if (arch === 'avr' || arch === 'megaavr' || arch === 'default') {
649
+ shimLines.push(...AVR_PREFERENCES_SHIM);
650
+ shimLines.push("static __tc_Preferences Preferences;");
651
+ extraIncludes.push('<EEPROM.h>');
652
+ } else if (arch === 'esp32') {
653
+ shimLines.push(
654
+ "#include <Preferences.h>",
655
+ "struct __tc_Esp32Prefs : public Preferences {",
656
+ " const char* getString(const char* key, const char* defaultValue = \"\") {",
657
+ " static char _buf[64];",
658
+ " if (Preferences::getString(key, _buf, sizeof(_buf))) return _buf;",
659
+ " return defaultValue;",
660
+ " }",
661
+ "};",
662
+ "static __tc_Esp32Prefs __tc_prefs;",
663
+ "#define Preferences __tc_prefs"
664
+ );
665
+ }
666
+ }
667
+
668
+ return {
669
+ forcedIncludes: [...variant.forcedIncludes, ...extraIncludes],
670
+ symbolAliases: {
671
+ ...(variant.symbolAliases ?? {}),
672
+ },
673
+ shimLines,
674
+ diagnostics,
675
+ };
676
+ }