c-next 0.1.65 → 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 (73) hide show
  1. package/package.json +5 -1
  2. package/src/transpiler/Transpiler.ts +49 -42
  3. package/src/transpiler/logic/symbols/cnext/__tests__/TSymbolAdapter.test.ts +129 -0
  4. package/src/transpiler/logic/symbols/cnext/adapters/TSymbolAdapter.ts +27 -3
  5. package/src/transpiler/output/codegen/CodeGenerator.ts +131 -186
  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 +1087 -0
  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/generators/expressions/PostfixExpressionGenerator.ts +618 -12
  39. package/src/transpiler/output/codegen/generators/expressions/__tests__/PostfixExpressionGenerator.test.ts +819 -0
  40. package/src/transpiler/output/codegen/helpers/ArrayInitHelper.ts +1 -1
  41. package/src/transpiler/output/codegen/helpers/AssignmentExpectedTypeResolver.ts +1 -1
  42. package/src/transpiler/output/codegen/helpers/AssignmentValidator.ts +1 -1
  43. package/src/transpiler/output/codegen/helpers/CppModeHelper.ts +1 -1
  44. package/src/transpiler/output/codegen/helpers/EnumAssignmentValidator.ts +1 -1
  45. package/src/transpiler/output/codegen/helpers/FloatBitHelper.ts +1 -1
  46. package/src/transpiler/output/codegen/helpers/ParameterInputAdapter.ts +337 -0
  47. package/src/transpiler/output/codegen/helpers/ParameterSignatureBuilder.ts +135 -0
  48. package/src/transpiler/output/codegen/helpers/StringDeclHelper.ts +1 -1
  49. package/src/transpiler/output/codegen/helpers/VariableDeclarationFormatter.ts +118 -0
  50. package/src/transpiler/output/codegen/helpers/__tests__/ArrayInitHelper.test.ts +1 -1
  51. package/src/transpiler/output/codegen/helpers/__tests__/AssignmentExpectedTypeResolver.test.ts +1 -1
  52. package/src/transpiler/output/codegen/helpers/__tests__/AssignmentValidator.test.ts +1 -1
  53. package/src/transpiler/output/codegen/helpers/__tests__/CppModeHelper.test.ts +1 -1
  54. package/src/transpiler/output/codegen/helpers/__tests__/EnumAssignmentValidator.test.ts +1 -1
  55. package/src/transpiler/output/codegen/helpers/__tests__/FloatBitHelper.test.ts +1 -1
  56. package/src/transpiler/output/codegen/helpers/__tests__/ParameterInputAdapter.test.ts +426 -0
  57. package/src/transpiler/output/codegen/helpers/__tests__/ParameterSignatureBuilder.test.ts +315 -0
  58. package/src/transpiler/output/codegen/helpers/__tests__/StringDeclHelper.test.ts +1 -1
  59. package/src/transpiler/output/codegen/helpers/__tests__/VariableDeclarationFormatter.test.ts +333 -0
  60. package/src/transpiler/output/codegen/resolution/EnumTypeResolver.ts +1 -1
  61. package/src/transpiler/output/codegen/resolution/ScopeResolver.ts +1 -1
  62. package/src/transpiler/output/codegen/resolution/SizeofResolver.ts +1 -1
  63. package/src/transpiler/output/codegen/resolution/__tests__/EnumTypeResolver.test.ts +1 -1
  64. package/src/transpiler/output/codegen/resolution/__tests__/SizeofResolver.test.ts +1 -1
  65. package/src/transpiler/output/codegen/types/ICodeGenApi.ts +57 -0
  66. package/src/transpiler/output/codegen/types/IParameterInput.ts +58 -0
  67. package/src/transpiler/output/codegen/types/IVariableFormatInput.ts +51 -0
  68. package/src/transpiler/output/headers/BaseHeaderGenerator.ts +20 -35
  69. package/src/transpiler/output/headers/HeaderGeneratorUtils.ts +21 -48
  70. package/src/transpiler/output/headers/__tests__/HeaderGeneratorUtils.test.ts +0 -64
  71. package/src/transpiler/{output/codegen → state}/CodeGenState.ts +46 -26
  72. package/src/transpiler/{output/codegen → state}/__tests__/CodeGenState.test.ts +12 -2
  73. 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.65",
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",
@@ -118,5 +118,9 @@
118
118
  "*.ts": [
119
119
  "oxlint --fix"
120
120
  ]
121
+ },
122
+ "volta": {
123
+ "node": "24.13.1",
124
+ "npm": "11.8.0"
121
125
  }
122
126
  }
@@ -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
  /**
@@ -463,4 +463,133 @@ describe("TSymbolAdapter", () => {
463
463
  expect(symbolTable.getStructFieldType("Point", "x")).toBe("i32");
464
464
  });
465
465
  });
466
+
467
+ describe("array dimension conversion", () => {
468
+ it("converts qualified enum access (dots to underscores) in variable dimensions", () => {
469
+ const variable: IVariableSymbol = {
470
+ kind: ESymbolKind.Variable,
471
+ name: "DATA",
472
+ sourceFile: "test.cnx",
473
+ sourceLine: 10,
474
+ sourceLanguage: ESourceLanguage.CNext,
475
+ isExported: true,
476
+ type: "u8",
477
+ isConst: true,
478
+ isAtomic: false,
479
+ isArray: true,
480
+ arrayDimensions: ["EColor.COUNT"],
481
+ };
482
+
483
+ const result = TSymbolAdapter.toISymbols([variable], symbolTable);
484
+
485
+ const varSym = result.find(
486
+ (s) => s.kind === ESymbolKind.Variable && s.name === "DATA",
487
+ );
488
+ expect(varSym).toBeDefined();
489
+ expect(varSym!.arrayDimensions).toEqual(["EColor_COUNT"]);
490
+ });
491
+
492
+ it("converts qualified enum access in function parameter dimensions", () => {
493
+ const funcSym: IFunctionSymbol = {
494
+ kind: ESymbolKind.Function,
495
+ name: "process",
496
+ sourceFile: "test.cnx",
497
+ sourceLine: 5,
498
+ sourceLanguage: ESourceLanguage.CNext,
499
+ isExported: true,
500
+ returnType: "void",
501
+ visibility: "public",
502
+ parameters: [
503
+ {
504
+ name: "buffer",
505
+ type: "u8",
506
+ isConst: false,
507
+ isArray: true,
508
+ arrayDimensions: ["Size.MEDIUM"],
509
+ },
510
+ ],
511
+ };
512
+
513
+ const result = TSymbolAdapter.toISymbols([funcSym], symbolTable);
514
+
515
+ const funcResult = result.find(
516
+ (s) => s.kind === ESymbolKind.Function && s.name === "process",
517
+ );
518
+ expect(funcResult).toBeDefined();
519
+ expect(funcResult!.parameters).toBeDefined();
520
+ expect(funcResult!.parameters![0].arrayDimensions).toEqual([
521
+ "Size_MEDIUM",
522
+ ]);
523
+ });
524
+
525
+ it("passes through unqualified string dimensions as-is", () => {
526
+ const variable: IVariableSymbol = {
527
+ kind: ESymbolKind.Variable,
528
+ name: "DATA",
529
+ sourceFile: "test.cnx",
530
+ sourceLine: 10,
531
+ sourceLanguage: ESourceLanguage.CNext,
532
+ isExported: true,
533
+ type: "u8",
534
+ isConst: true,
535
+ isAtomic: false,
536
+ isArray: true,
537
+ arrayDimensions: ["DEVICE_COUNT"], // C macro passthrough
538
+ };
539
+
540
+ const result = TSymbolAdapter.toISymbols([variable], symbolTable);
541
+
542
+ const varSym = result.find(
543
+ (s) => s.kind === ESymbolKind.Variable && s.name === "DATA",
544
+ );
545
+ expect(varSym).toBeDefined();
546
+ expect(varSym!.arrayDimensions).toEqual(["DEVICE_COUNT"]);
547
+ });
548
+
549
+ it("preserves numeric array dimensions", () => {
550
+ const variable: IVariableSymbol = {
551
+ kind: ESymbolKind.Variable,
552
+ name: "DATA",
553
+ sourceFile: "test.cnx",
554
+ sourceLine: 10,
555
+ sourceLanguage: ESourceLanguage.CNext,
556
+ isExported: true,
557
+ type: "u8",
558
+ isConst: true,
559
+ isAtomic: false,
560
+ isArray: true,
561
+ arrayDimensions: [256],
562
+ };
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)", () => {
573
+ const variable: IVariableSymbol = {
574
+ kind: ESymbolKind.Variable,
575
+ name: "DATA",
576
+ sourceFile: "test.cnx",
577
+ sourceLine: 10,
578
+ sourceLanguage: ESourceLanguage.CNext,
579
+ isExported: true,
580
+ type: "u8",
581
+ isConst: true,
582
+ isAtomic: false,
583
+ isArray: true,
584
+ arrayDimensions: ["Motor.State.COUNT"],
585
+ };
586
+
587
+ const result = TSymbolAdapter.toISymbols([variable], symbolTable);
588
+
589
+ const varSym = result.find(
590
+ (s) => s.kind === ESymbolKind.Variable && s.name === "DATA",
591
+ );
592
+ expect(varSym!.arrayDimensions).toEqual(["Motor_State_COUNT"]);
593
+ });
594
+ });
466
595
  });
@@ -71,6 +71,24 @@ class TSymbolAdapter {
71
71
  return result;
72
72
  }
73
73
 
74
+ /**
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").
77
+ */
78
+ private static resolveArrayDimension(dim: number | string): string {
79
+ if (typeof dim === "number") {
80
+ return String(dim);
81
+ }
82
+
83
+ // Qualified enum access (e.g., "EColor.COUNT") - convert dots to underscores
84
+ if (dim.includes(".")) {
85
+ return dim.replaceAll(".", "_");
86
+ }
87
+
88
+ // Pass through as-is (C macros, other identifiers)
89
+ return dim;
90
+ }
91
+
74
92
  /**
75
93
  * Convert IBitmapSymbol to ISymbol + BitmapField symbols.
76
94
  */
@@ -180,6 +198,7 @@ class TSymbolAdapter {
180
198
 
181
199
  /**
182
200
  * Convert IFunctionSymbol to ISymbol + parameter symbols for hover support.
201
+ * Converts qualified enum names in parameter array dimensions.
183
202
  */
184
203
  private static convertFunction(func: IFunctionSymbol): ISymbol[] {
185
204
  const result: ISymbol[] = [];
@@ -194,7 +213,9 @@ class TSymbolAdapter {
194
213
  type: p.type,
195
214
  isConst: p.isConst,
196
215
  isArray: p.isArray,
197
- arrayDimensions: p.arrayDimensions,
216
+ arrayDimensions: p.arrayDimensions?.map((dim) =>
217
+ TSymbolAdapter.resolveArrayDimension(dim),
218
+ ),
198
219
  isAutoConst: p.isAutoConst,
199
220
  }));
200
221
 
@@ -233,10 +254,13 @@ class TSymbolAdapter {
233
254
 
234
255
  /**
235
256
  * Convert IVariableSymbol to ISymbol.
257
+ * Converts qualified enum names in array dimensions.
236
258
  */
237
259
  private static convertVariable(variable: IVariableSymbol): ISymbol {
238
- // Convert dimensions to string dimensions for ISymbol
239
- const arrayDimensions = variable.arrayDimensions?.map(String);
260
+ // Convert dimensions to string dimensions
261
+ const arrayDimensions = variable.arrayDimensions?.map((dim) =>
262
+ TSymbolAdapter.resolveArrayDimension(dim),
263
+ );
240
264
 
241
265
  // Get first dimension for legacy size field (only if numeric)
242
266
  const firstDim = variable.arrayDimensions?.[0];