c-next 0.1.66 → 0.1.67

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 (60) hide show
  1. package/package.json +1 -1
  2. package/src/transpiler/Transpiler.ts +49 -42
  3. package/src/transpiler/logic/symbols/cnext/__tests__/TSymbolAdapter.test.ts +32 -82
  4. package/src/transpiler/logic/symbols/cnext/adapters/TSymbolAdapter.ts +17 -61
  5. package/src/transpiler/output/codegen/CodeGenerator.ts +22 -59
  6. package/src/transpiler/output/codegen/TypeResolver.ts +2 -2
  7. package/src/transpiler/output/codegen/TypeValidator.ts +1 -1
  8. package/src/transpiler/output/codegen/__tests__/CodeGenerator.coverage.test.ts +5 -4
  9. package/src/transpiler/output/codegen/__tests__/CodeGenerator.test.ts +665 -1315
  10. package/src/transpiler/output/codegen/__tests__/TypeResolver.test.ts +1 -1
  11. package/src/transpiler/output/codegen/__tests__/TypeValidator.resolution.test.ts +1 -1
  12. package/src/transpiler/output/codegen/__tests__/TypeValidator.test.ts +1 -1
  13. package/src/transpiler/output/codegen/analysis/MemberChainAnalyzer.ts +1 -1
  14. package/src/transpiler/output/codegen/analysis/StringLengthCounter.ts +1 -1
  15. package/src/transpiler/output/codegen/analysis/__tests__/MemberChainAnalyzer.test.ts +1 -1
  16. package/src/transpiler/output/codegen/analysis/__tests__/StringLengthCounter.test.ts +1 -1
  17. package/src/transpiler/output/codegen/assignment/AssignmentClassifier.ts +1 -1
  18. package/src/transpiler/output/codegen/assignment/__tests__/AssignmentClassifier.test.ts +1 -1
  19. package/src/transpiler/output/codegen/assignment/handlers/AccessPatternHandlers.ts +24 -27
  20. package/src/transpiler/output/codegen/assignment/handlers/ArrayHandlers.ts +25 -18
  21. package/src/transpiler/output/codegen/assignment/handlers/BitAccessHandlers.ts +27 -33
  22. package/src/transpiler/output/codegen/assignment/handlers/BitmapHandlers.ts +39 -42
  23. package/src/transpiler/output/codegen/assignment/handlers/RegisterHandlers.ts +39 -97
  24. package/src/transpiler/output/codegen/assignment/handlers/RegisterUtils.ts +75 -0
  25. package/src/transpiler/output/codegen/assignment/handlers/SimpleHandler.ts +9 -6
  26. package/src/transpiler/output/codegen/assignment/handlers/SpecialHandlers.ts +30 -22
  27. package/src/transpiler/output/codegen/assignment/handlers/StringHandlers.ts +42 -50
  28. package/src/transpiler/output/codegen/assignment/handlers/TAssignmentHandler.ts +6 -5
  29. package/src/transpiler/output/codegen/assignment/handlers/__tests__/AccessPatternHandlers.test.ts +81 -134
  30. package/src/transpiler/output/codegen/assignment/handlers/__tests__/ArrayHandlers.test.ts +85 -124
  31. package/src/transpiler/output/codegen/assignment/handlers/__tests__/BitAccessHandlers.test.ts +82 -124
  32. package/src/transpiler/output/codegen/assignment/handlers/__tests__/BitmapHandlers.test.ts +135 -297
  33. package/src/transpiler/output/codegen/assignment/handlers/__tests__/RegisterHandlers.test.ts +105 -227
  34. package/src/transpiler/output/codegen/assignment/handlers/__tests__/RegisterUtils.test.ts +214 -1
  35. package/src/transpiler/output/codegen/assignment/handlers/__tests__/SpecialHandlers.test.ts +66 -127
  36. package/src/transpiler/output/codegen/assignment/handlers/__tests__/StringHandlers.test.ts +37 -83
  37. package/src/transpiler/output/codegen/assignment/handlers/__tests__/handlerTestUtils.ts +162 -0
  38. package/src/transpiler/output/codegen/helpers/ArrayInitHelper.ts +1 -1
  39. package/src/transpiler/output/codegen/helpers/AssignmentExpectedTypeResolver.ts +1 -1
  40. package/src/transpiler/output/codegen/helpers/AssignmentValidator.ts +1 -1
  41. package/src/transpiler/output/codegen/helpers/CppModeHelper.ts +1 -1
  42. package/src/transpiler/output/codegen/helpers/EnumAssignmentValidator.ts +1 -1
  43. package/src/transpiler/output/codegen/helpers/FloatBitHelper.ts +1 -1
  44. package/src/transpiler/output/codegen/helpers/StringDeclHelper.ts +1 -1
  45. package/src/transpiler/output/codegen/helpers/__tests__/ArrayInitHelper.test.ts +1 -1
  46. package/src/transpiler/output/codegen/helpers/__tests__/AssignmentExpectedTypeResolver.test.ts +1 -1
  47. package/src/transpiler/output/codegen/helpers/__tests__/AssignmentValidator.test.ts +1 -1
  48. package/src/transpiler/output/codegen/helpers/__tests__/CppModeHelper.test.ts +1 -1
  49. package/src/transpiler/output/codegen/helpers/__tests__/EnumAssignmentValidator.test.ts +1 -1
  50. package/src/transpiler/output/codegen/helpers/__tests__/FloatBitHelper.test.ts +1 -1
  51. package/src/transpiler/output/codegen/helpers/__tests__/StringDeclHelper.test.ts +1 -1
  52. package/src/transpiler/output/codegen/resolution/EnumTypeResolver.ts +1 -1
  53. package/src/transpiler/output/codegen/resolution/ScopeResolver.ts +1 -1
  54. package/src/transpiler/output/codegen/resolution/SizeofResolver.ts +1 -1
  55. package/src/transpiler/output/codegen/resolution/__tests__/EnumTypeResolver.test.ts +1 -1
  56. package/src/transpiler/output/codegen/resolution/__tests__/SizeofResolver.test.ts +1 -1
  57. package/src/transpiler/output/codegen/types/ICodeGenApi.ts +57 -0
  58. package/src/transpiler/{output/codegen → state}/CodeGenState.ts +46 -26
  59. package/src/transpiler/{output/codegen → state}/__tests__/CodeGenState.test.ts +12 -2
  60. package/src/transpiler/output/codegen/assignment/handlers/IHandlerDeps.ts +0 -161
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "c-next",
3
- "version": "0.1.66",
3
+ "version": "0.1.67",
4
4
  "description": "A safer C for embedded systems development. Transpiles to clean, readable C.",
5
5
  "packageManager": "npm@11.9.0",
6
6
  "type": "module",
@@ -19,7 +19,7 @@ import CNextSourceParser from "./logic/parser/CNextSourceParser";
19
19
  import HeaderParser from "./logic/parser/HeaderParser";
20
20
 
21
21
  import CodeGenerator from "./output/codegen/CodeGenerator";
22
- import CodeGenState from "./output/codegen/CodeGenState";
22
+ import CodeGenState from "./state/CodeGenState";
23
23
  import HeaderGenerator from "./output/headers/HeaderGenerator";
24
24
  import ExternalTypeHeaderBuilder from "./output/headers/ExternalTypeHeaderBuilder";
25
25
  import ICodeGenSymbols from "./types/ICodeGenSymbols";
@@ -65,7 +65,6 @@ import TransitiveEnumCollector from "./logic/symbols/TransitiveEnumCollector";
65
65
  */
66
66
  class Transpiler {
67
67
  private readonly config: Required<ITranspilerConfig>;
68
- private readonly symbolTable: SymbolTable;
69
68
  private readonly preprocessor: Preprocessor;
70
69
  private readonly codeGenerator: CodeGenerator;
71
70
  private readonly headerGenerator: HeaderGenerator;
@@ -108,7 +107,6 @@ class Transpiler {
108
107
  // Issue #211: Initialize cppDetected from config (--cpp flag sets this)
109
108
  this.cppDetected = this.config.cppRequired;
110
109
 
111
- this.symbolTable = new SymbolTable();
112
110
  this.preprocessor = new Preprocessor();
113
111
  this.codeGenerator = new CodeGenerator();
114
112
  this.headerGenerator = new HeaderGenerator();
@@ -269,7 +267,7 @@ class Transpiler {
269
267
  }
270
268
 
271
269
  // Stage 3b: Resolve external const array dimensions
272
- this.symbolTable.resolveExternalArrayDimensions();
270
+ CodeGenState.symbolTable.resolveExternalArrayDimensions();
273
271
 
274
272
  // Stage 4: Check for symbol conflicts (skipped in standalone mode)
275
273
  if (!input.skipConflictCheck && !this._checkSymbolConflicts(result)) {
@@ -337,8 +335,11 @@ class Transpiler {
337
335
  try {
338
336
  // ADR-055: Use composable collectors via CNextResolver + TSymbolAdapter
339
337
  const tSymbols = CNextResolver.resolve(tree, file.path);
340
- const iSymbols = TSymbolAdapter.toISymbols(tSymbols, this.symbolTable);
341
- this.symbolTable.addSymbols(iSymbols);
338
+ const iSymbols = TSymbolAdapter.toISymbols(
339
+ tSymbols,
340
+ CodeGenState.symbolTable,
341
+ );
342
+ CodeGenState.symbolTable.addSymbols(iSymbols);
342
343
 
343
344
  // Issue #465: Store ICodeGenSymbols for external enum resolution in stage 5
344
345
  const symbolInfo = TSymbolInfoAdapter.convert(tSymbols);
@@ -396,11 +397,13 @@ class Transpiler {
396
397
 
397
398
  // Run analyzers
398
399
  const externalStructFields =
399
- AnalyzerContextBuilder.buildExternalStructFields(this.symbolTable);
400
+ AnalyzerContextBuilder.buildExternalStructFields(
401
+ CodeGenState.symbolTable,
402
+ );
400
403
 
401
404
  const analyzerErrors = runAnalyzers(tree, tokenStream, {
402
405
  externalStructFields,
403
- symbolTable: this.symbolTable,
406
+ symbolTable: CodeGenState.symbolTable,
404
407
  });
405
408
  if (analyzerErrors.length > 0) {
406
409
  return this.buildErrorResult(
@@ -430,18 +433,13 @@ class Transpiler {
430
433
  this._setupCrossFileModifications();
431
434
 
432
435
  // Generate code
433
- const code = this.codeGenerator.generate(
434
- tree,
435
- this.symbolTable,
436
- tokenStream,
437
- {
438
- debugMode: this.config.debugMode,
439
- target: this.config.target,
440
- sourcePath,
441
- cppMode: this.cppDetected,
442
- symbolInfo,
443
- },
444
- );
436
+ const code = this.codeGenerator.generate(tree, tokenStream, {
437
+ debugMode: this.config.debugMode,
438
+ target: this.config.target,
439
+ sourcePath,
440
+ cppMode: this.cppDetected,
441
+ symbolInfo,
442
+ });
445
443
 
446
444
  // Collect user includes
447
445
  const userIncludes = IncludeExtractor.collectUserIncludes(tree);
@@ -461,7 +459,7 @@ class Transpiler {
461
459
  }
462
460
 
463
461
  // Update symbol parameters with auto-const info
464
- const symbols = this.symbolTable.getSymbolsByFile(sourcePath);
462
+ const symbols = CodeGenState.symbolTable.getSymbolsByFile(sourcePath);
465
463
  const unmodifiedParams = this.codeGenerator.getFunctionUnmodifiedParams();
466
464
  const knownEnums =
467
465
  this.state.getSymbolInfo(sourcePath)?.knownEnums ?? new Set<string>();
@@ -471,7 +469,6 @@ class Transpiler {
471
469
  const headerCode = this.generateHeaderContent(
472
470
  symbols,
473
471
  sourcePath,
474
- this.symbolTable,
475
472
  this.cppDetected,
476
473
  userIncludes,
477
474
  passByValueCopy,
@@ -631,7 +628,7 @@ class Transpiler {
631
628
  // Issue #587: Reset accumulated state for new run
632
629
  this.state.reset();
633
630
  // Issue #634: Reset symbol table for new run
634
- this.symbolTable.clear();
631
+ CodeGenState.symbolTable.clear();
635
632
  }
636
633
 
637
634
  /**
@@ -668,7 +665,7 @@ class Transpiler {
668
665
  * @returns true if no blocking conflicts, false otherwise
669
666
  */
670
667
  private _checkSymbolConflicts(result: ITranspilerResult): boolean {
671
- const conflicts = this.symbolTable.getConflicts();
668
+ const conflicts = CodeGenState.symbolTable.getConflicts();
672
669
  for (const conflict of conflicts) {
673
670
  result.conflicts.push(conflict.message);
674
671
  if (conflict.severity === "error") {
@@ -751,7 +748,7 @@ class Transpiler {
751
748
  if (warning) {
752
749
  result.warnings.push(warning);
753
750
  }
754
- result.symbolsCollected = this.symbolTable.size;
751
+ result.symbolsCollected = CodeGenState.symbolTable.size;
755
752
  result.warnings = [...result.warnings, ...this.warnings];
756
753
 
757
754
  if (this.cacheManager) {
@@ -1040,13 +1037,16 @@ class Transpiler {
1040
1037
 
1041
1038
  // Debug: Show symbols found
1042
1039
  if (this.config.debugMode) {
1043
- const symbols = this.symbolTable.getSymbolsByFile(file.path);
1040
+ const symbols = CodeGenState.symbolTable.getSymbolsByFile(file.path);
1044
1041
  console.log(`[DEBUG] Found ${symbols.length} symbols in ${file.path}`);
1045
1042
  }
1046
1043
 
1047
1044
  // Issue #590: Cache the results using simplified API
1048
1045
  if (this.cacheManager) {
1049
- this.cacheManager.setSymbolsFromTable(file.path, this.symbolTable);
1046
+ this.cacheManager.setSymbolsFromTable(
1047
+ file.path,
1048
+ CodeGenState.symbolTable,
1049
+ );
1050
1050
  }
1051
1051
  }
1052
1052
 
@@ -1065,10 +1065,12 @@ class Transpiler {
1065
1065
  }
1066
1066
 
1067
1067
  // Restore symbols, struct fields, needsStructKeyword, and enumBitWidth from cache
1068
- this.symbolTable.addSymbols(cached.symbols);
1069
- this.symbolTable.restoreStructFields(cached.structFields);
1070
- this.symbolTable.restoreNeedsStructKeyword(cached.needsStructKeyword);
1071
- this.symbolTable.restoreEnumBitWidths(cached.enumBitWidth);
1068
+ CodeGenState.symbolTable.addSymbols(cached.symbols);
1069
+ CodeGenState.symbolTable.restoreStructFields(cached.structFields);
1070
+ CodeGenState.symbolTable.restoreNeedsStructKeyword(
1071
+ cached.needsStructKeyword,
1072
+ );
1073
+ CodeGenState.symbolTable.restoreEnumBitWidths(cached.enumBitWidth);
1072
1074
 
1073
1075
  // Issue #211: Still check for C++ syntax even on cache hit
1074
1076
  this.detectCppFromFileType(file);
@@ -1140,10 +1142,13 @@ class Transpiler {
1140
1142
  private parsePureCHeader(content: string, filePath: string): void {
1141
1143
  const { tree } = HeaderParser.parseC(content);
1142
1144
  if (tree) {
1143
- const collector = new CSymbolCollector(filePath, this.symbolTable);
1145
+ const collector = new CSymbolCollector(
1146
+ filePath,
1147
+ CodeGenState.symbolTable,
1148
+ );
1144
1149
  const symbols = collector.collect(tree);
1145
1150
  if (symbols.length > 0) {
1146
- this.symbolTable.addSymbols(symbols);
1151
+ CodeGenState.symbolTable.addSymbols(symbols);
1147
1152
  }
1148
1153
  }
1149
1154
  }
@@ -1154,9 +1159,12 @@ class Transpiler {
1154
1159
  private parseCppHeader(content: string, filePath: string): void {
1155
1160
  const { tree } = HeaderParser.parseCpp(content);
1156
1161
  if (tree) {
1157
- const collector = new CppSymbolCollector(filePath, this.symbolTable);
1162
+ const collector = new CppSymbolCollector(
1163
+ filePath,
1164
+ CodeGenState.symbolTable,
1165
+ );
1158
1166
  const symbols = collector.collect(tree);
1159
- this.symbolTable.addSymbols(symbols);
1167
+ CodeGenState.symbolTable.addSymbols(symbols);
1160
1168
  }
1161
1169
  }
1162
1170
 
@@ -1168,7 +1176,7 @@ class Transpiler {
1168
1176
  * Stage 6: Generate header file for a C-Next file
1169
1177
  */
1170
1178
  private generateHeader(file: IDiscoveredFile): string | null {
1171
- const symbols = this.symbolTable.getSymbolsByFile(file.path);
1179
+ const symbols = CodeGenState.symbolTable.getSymbolsByFile(file.path);
1172
1180
  const exportedSymbols = symbols.filter((s) => s.isExported);
1173
1181
 
1174
1182
  if (exportedSymbols.length === 0) {
@@ -1199,12 +1207,12 @@ class Transpiler {
1199
1207
  // Issue #497: Build mapping from external types to their C header includes
1200
1208
  const externalTypeHeaders = ExternalTypeHeaderBuilder.build(
1201
1209
  this.state.getAllHeaderDirectives(),
1202
- this.symbolTable,
1210
+ CodeGenState.symbolTable,
1203
1211
  );
1204
1212
 
1205
1213
  // Issue #502: Include symbolTable in typeInput for C++ namespace type detection
1206
1214
  const typeInputWithSymbolTable = typeInput
1207
- ? { ...typeInput, symbolTable: this.symbolTable }
1215
+ ? { ...typeInput, symbolTable: CodeGenState.symbolTable }
1208
1216
  : undefined;
1209
1217
 
1210
1218
  const headerContent = this.headerGenerator.generate(
@@ -1274,7 +1282,6 @@ class Transpiler {
1274
1282
  private generateHeaderContent(
1275
1283
  symbols: ISymbol[],
1276
1284
  sourcePath: string,
1277
- symbolTable: SymbolTable,
1278
1285
  cppMode: boolean,
1279
1286
  userIncludes: string[],
1280
1287
  passByValueParams: Map<string, Set<string>>,
@@ -1318,12 +1325,12 @@ class Transpiler {
1318
1325
  // Issue #497: Build mapping from external types to their C header includes
1319
1326
  const externalTypeHeaders = ExternalTypeHeaderBuilder.build(
1320
1327
  this.state.getAllHeaderDirectives(),
1321
- symbolTable,
1328
+ CodeGenState.symbolTable,
1322
1329
  );
1323
1330
 
1324
1331
  // Issue #502: Include symbolTable in typeInput for C++ namespace type detection
1325
1332
  const typeInputWithSymbolTable = typeInput
1326
- ? { ...typeInput, symbolTable }
1333
+ ? { ...typeInput, symbolTable: CodeGenState.symbolTable }
1327
1334
  : undefined;
1328
1335
 
1329
1336
  // Issue #478: Pass all known enums for cross-file type handling
@@ -1429,7 +1436,7 @@ class Transpiler {
1429
1436
  * Get the symbol table (for testing/inspection)
1430
1437
  */
1431
1438
  getSymbolTable(): SymbolTable {
1432
- return this.symbolTable;
1439
+ return CodeGenState.symbolTable;
1433
1440
  }
1434
1441
 
1435
1442
  /**
@@ -464,23 +464,8 @@ describe("TSymbolAdapter", () => {
464
464
  });
465
465
  });
466
466
 
467
- describe("enum member resolution in array dimensions", () => {
468
- it("resolves unqualified enum member in variable array dimension", () => {
469
- const enumSym: IEnumSymbol = {
470
- kind: ESymbolKind.Enum,
471
- name: "EColor",
472
- sourceFile: "test.cnx",
473
- sourceLine: 1,
474
- sourceLanguage: ESourceLanguage.CNext,
475
- isExported: true,
476
- members: new Map([
477
- ["RED", 0],
478
- ["GREEN", 1],
479
- ["BLUE", 2],
480
- ["COUNT", 3],
481
- ]),
482
- };
483
-
467
+ describe("array dimension conversion", () => {
468
+ it("converts qualified enum access (dots to underscores) in variable dimensions", () => {
484
469
  const variable: IVariableSymbol = {
485
470
  kind: ESymbolKind.Variable,
486
471
  name: "DATA",
@@ -492,15 +477,11 @@ describe("TSymbolAdapter", () => {
492
477
  isConst: true,
493
478
  isAtomic: false,
494
479
  isArray: true,
495
- arrayDimensions: ["COUNT"], // Unqualified enum member
480
+ arrayDimensions: ["EColor.COUNT"],
496
481
  };
497
482
 
498
- const result = TSymbolAdapter.toISymbols(
499
- [enumSym, variable],
500
- symbolTable,
501
- );
483
+ const result = TSymbolAdapter.toISymbols([variable], symbolTable);
502
484
 
503
- // Find the variable symbol
504
485
  const varSym = result.find(
505
486
  (s) => s.kind === ESymbolKind.Variable && s.name === "DATA",
506
487
  );
@@ -508,21 +489,7 @@ describe("TSymbolAdapter", () => {
508
489
  expect(varSym!.arrayDimensions).toEqual(["EColor_COUNT"]);
509
490
  });
510
491
 
511
- it("resolves enum member in function parameter array dimension", () => {
512
- const enumSym: IEnumSymbol = {
513
- kind: ESymbolKind.Enum,
514
- name: "Size",
515
- sourceFile: "test.cnx",
516
- sourceLine: 1,
517
- sourceLanguage: ESourceLanguage.CNext,
518
- isExported: true,
519
- members: new Map([
520
- ["SMALL", 10],
521
- ["MEDIUM", 20],
522
- ["LARGE", 30],
523
- ]),
524
- };
525
-
492
+ it("converts qualified enum access in function parameter dimensions", () => {
526
493
  const funcSym: IFunctionSymbol = {
527
494
  kind: ESymbolKind.Function,
528
495
  name: "process",
@@ -538,14 +505,13 @@ describe("TSymbolAdapter", () => {
538
505
  type: "u8",
539
506
  isConst: false,
540
507
  isArray: true,
541
- arrayDimensions: ["MEDIUM"], // Unqualified enum member
508
+ arrayDimensions: ["Size.MEDIUM"],
542
509
  },
543
510
  ],
544
511
  };
545
512
 
546
- const result = TSymbolAdapter.toISymbols([enumSym, funcSym], symbolTable);
513
+ const result = TSymbolAdapter.toISymbols([funcSym], symbolTable);
547
514
 
548
- // Find the function symbol
549
515
  const funcResult = result.find(
550
516
  (s) => s.kind === ESymbolKind.Function && s.name === "process",
551
517
  );
@@ -556,29 +522,7 @@ describe("TSymbolAdapter", () => {
556
522
  ]);
557
523
  });
558
524
 
559
- it("does not resolve ambiguous enum member (exists in multiple enums)", () => {
560
- const enum1: IEnumSymbol = {
561
- kind: ESymbolKind.Enum,
562
- name: "EColor",
563
- sourceFile: "test.cnx",
564
- sourceLine: 1,
565
- sourceLanguage: ESourceLanguage.CNext,
566
- isExported: true,
567
- members: new Map([["COUNT", 3]]),
568
- };
569
-
570
- const enum2: IEnumSymbol = {
571
- kind: ESymbolKind.Enum,
572
- name: "ESize",
573
- sourceFile: "test.cnx",
574
- sourceLine: 5,
575
- sourceLanguage: ESourceLanguage.CNext,
576
- isExported: true,
577
- members: new Map([
578
- ["COUNT", 5], // Same member name in different enum
579
- ]),
580
- };
581
-
525
+ it("passes through unqualified string dimensions as-is", () => {
582
526
  const variable: IVariableSymbol = {
583
527
  kind: ESymbolKind.Variable,
584
528
  name: "DATA",
@@ -590,33 +534,42 @@ describe("TSymbolAdapter", () => {
590
534
  isConst: true,
591
535
  isAtomic: false,
592
536
  isArray: true,
593
- arrayDimensions: ["COUNT"], // Ambiguous - exists in both enums
537
+ arrayDimensions: ["DEVICE_COUNT"], // C macro passthrough
594
538
  };
595
539
 
596
- const result = TSymbolAdapter.toISymbols(
597
- [enum1, enum2, variable],
598
- symbolTable,
599
- );
540
+ const result = TSymbolAdapter.toISymbols([variable], symbolTable);
600
541
 
601
- // Find the variable symbol - dimension should NOT be resolved
602
542
  const varSym = result.find(
603
543
  (s) => s.kind === ESymbolKind.Variable && s.name === "DATA",
604
544
  );
605
545
  expect(varSym).toBeDefined();
606
- expect(varSym!.arrayDimensions).toEqual(["COUNT"]); // Left as-is
546
+ expect(varSym!.arrayDimensions).toEqual(["DEVICE_COUNT"]);
607
547
  });
608
548
 
609
549
  it("preserves numeric array dimensions", () => {
610
- const enumSym: IEnumSymbol = {
611
- kind: ESymbolKind.Enum,
612
- name: "EColor",
550
+ const variable: IVariableSymbol = {
551
+ kind: ESymbolKind.Variable,
552
+ name: "DATA",
613
553
  sourceFile: "test.cnx",
614
- sourceLine: 1,
554
+ sourceLine: 10,
615
555
  sourceLanguage: ESourceLanguage.CNext,
616
556
  isExported: true,
617
- members: new Map([["COUNT", 3]]),
557
+ type: "u8",
558
+ isConst: true,
559
+ isAtomic: false,
560
+ isArray: true,
561
+ arrayDimensions: [256],
618
562
  };
619
563
 
564
+ const result = TSymbolAdapter.toISymbols([variable], symbolTable);
565
+
566
+ const varSym = result.find(
567
+ (s) => s.kind === ESymbolKind.Variable && s.name === "DATA",
568
+ );
569
+ expect(varSym!.arrayDimensions).toEqual(["256"]);
570
+ });
571
+
572
+ it("handles multi-dot qualified access (Motor.State.IDLE)", () => {
620
573
  const variable: IVariableSymbol = {
621
574
  kind: ESymbolKind.Variable,
622
575
  name: "DATA",
@@ -628,18 +581,15 @@ describe("TSymbolAdapter", () => {
628
581
  isConst: true,
629
582
  isAtomic: false,
630
583
  isArray: true,
631
- arrayDimensions: [256], // Numeric dimension
584
+ arrayDimensions: ["Motor.State.COUNT"],
632
585
  };
633
586
 
634
- const result = TSymbolAdapter.toISymbols(
635
- [enumSym, variable],
636
- symbolTable,
637
- );
587
+ const result = TSymbolAdapter.toISymbols([variable], symbolTable);
638
588
 
639
589
  const varSym = result.find(
640
590
  (s) => s.kind === ESymbolKind.Variable && s.name === "DATA",
641
591
  );
642
- expect(varSym!.arrayDimensions).toEqual(["256"]);
592
+ expect(varSym!.arrayDimensions).toEqual(["Motor_State_COUNT"]);
643
593
  });
644
594
  });
645
595
  });
@@ -40,9 +40,6 @@ class TSymbolAdapter {
40
40
  * @returns Array of flat ISymbol objects for Pipeline consumption
41
41
  */
42
42
  static toISymbols(symbols: TSymbol[], symbolTable: SymbolTable): ISymbol[] {
43
- // First pass: Build enum member lookup for array dimension resolution
44
- const enumMemberLookup = TSymbolAdapter.buildEnumMemberLookup(symbols);
45
-
46
43
  const result: ISymbol[] = [];
47
44
 
48
45
  for (const symbol of symbols) {
@@ -57,12 +54,10 @@ class TSymbolAdapter {
57
54
  result.push(TSymbolAdapter.convertStruct(symbol, symbolTable));
58
55
  break;
59
56
  case ESymbolKind.Function:
60
- result.push(
61
- ...TSymbolAdapter.convertFunction(symbol, enumMemberLookup),
62
- );
57
+ result.push(...TSymbolAdapter.convertFunction(symbol));
63
58
  break;
64
59
  case ESymbolKind.Variable:
65
- result.push(TSymbolAdapter.convertVariable(symbol, enumMemberLookup));
60
+ result.push(TSymbolAdapter.convertVariable(symbol));
66
61
  break;
67
62
  case ESymbolKind.Register:
68
63
  result.push(...TSymbolAdapter.convertRegister(symbol));
@@ -77,53 +72,20 @@ class TSymbolAdapter {
77
72
  }
78
73
 
79
74
  /**
80
- * Build a lookup map from enum member names to their fully-qualified enum names.
81
- * Used to resolve unqualified enum members in array dimensions.
82
- *
83
- * If a member name exists in multiple enums, it's marked as ambiguous (null value)
84
- * and will not be resolved (the header will fail to compile, surfacing the issue).
85
- */
86
- private static buildEnumMemberLookup(
87
- symbols: TSymbol[],
88
- ): Map<string, string | null> {
89
- const lookup = new Map<string, string | null>();
90
-
91
- for (const symbol of symbols) {
92
- if (symbol.kind !== ESymbolKind.Enum) continue;
93
-
94
- for (const memberName of symbol.members.keys()) {
95
- if (lookup.has(memberName)) {
96
- // Ambiguous: member exists in multiple enums
97
- lookup.set(memberName, null);
98
- } else {
99
- lookup.set(memberName, symbol.name);
100
- }
101
- }
102
- }
103
-
104
- return lookup;
105
- }
106
-
107
- /**
108
- * Resolve an array dimension string, adding enum prefix if it's an unqualified enum member.
75
+ * Convert an array dimension to a string for header generation.
76
+ * Converts qualified enum access (e.g., "EColor.COUNT") to C-style ("EColor_COUNT").
109
77
  */
110
- private static resolveArrayDimension(
111
- dim: number | string,
112
- enumMemberLookup: Map<string, string | null>,
113
- ): string {
114
- // Numeric dimensions don't need resolution
78
+ private static resolveArrayDimension(dim: number | string): string {
115
79
  if (typeof dim === "number") {
116
80
  return String(dim);
117
81
  }
118
82
 
119
- // Check if this is an unqualified enum member
120
- const enumName = enumMemberLookup.get(dim);
121
- if (enumName) {
122
- // Found in exactly one enum - add the prefix
123
- return `${enumName}_${dim}`;
83
+ // Qualified enum access (e.g., "EColor.COUNT") - convert dots to underscores
84
+ if (dim.includes(".")) {
85
+ return dim.replaceAll(".", "_");
124
86
  }
125
87
 
126
- // Not an enum member, or ambiguous - return as-is
88
+ // Pass through as-is (C macros, other identifiers)
127
89
  return dim;
128
90
  }
129
91
 
@@ -236,26 +198,23 @@ class TSymbolAdapter {
236
198
 
237
199
  /**
238
200
  * Convert IFunctionSymbol to ISymbol + parameter symbols for hover support.
239
- * Resolves unqualified enum members in parameter array dimensions.
201
+ * Converts qualified enum names in parameter array dimensions.
240
202
  */
241
- private static convertFunction(
242
- func: IFunctionSymbol,
243
- enumMemberLookup: Map<string, string | null>,
244
- ): ISymbol[] {
203
+ private static convertFunction(func: IFunctionSymbol): ISymbol[] {
245
204
  const result: ISymbol[] = [];
246
205
 
247
206
  // Build parameter types for signature
248
207
  const paramTypes = func.parameters.map((p) => p.type);
249
208
  const signature = `${func.returnType} ${func.name}(${paramTypes.join(", ")})`;
250
209
 
251
- // Build parameter info for header generation, resolving enum members in dimensions
210
+ // Build parameter info for header generation
252
211
  const parameters = func.parameters.map((p) => ({
253
212
  name: p.name,
254
213
  type: p.type,
255
214
  isConst: p.isConst,
256
215
  isArray: p.isArray,
257
216
  arrayDimensions: p.arrayDimensions?.map((dim) =>
258
- TSymbolAdapter.resolveArrayDimension(dim, enumMemberLookup),
217
+ TSymbolAdapter.resolveArrayDimension(dim),
259
218
  ),
260
219
  isAutoConst: p.isAutoConst,
261
220
  }));
@@ -295,15 +254,12 @@ class TSymbolAdapter {
295
254
 
296
255
  /**
297
256
  * Convert IVariableSymbol to ISymbol.
298
- * Resolves unqualified enum members in array dimensions.
257
+ * Converts qualified enum names in array dimensions.
299
258
  */
300
- private static convertVariable(
301
- variable: IVariableSymbol,
302
- enumMemberLookup: Map<string, string | null>,
303
- ): ISymbol {
304
- // Convert dimensions to string dimensions, resolving enum member references
259
+ private static convertVariable(variable: IVariableSymbol): ISymbol {
260
+ // Convert dimensions to string dimensions
305
261
  const arrayDimensions = variable.arrayDimensions?.map((dim) =>
306
- TSymbolAdapter.resolveArrayDimension(dim, enumMemberLookup),
262
+ TSymbolAdapter.resolveArrayDimension(dim),
307
263
  );
308
264
 
309
265
  // Get first dimension for legacy size field (only if numeric)