c-next 0.1.38 → 0.1.39

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "c-next",
3
- "version": "0.1.38",
3
+ "version": "0.1.39",
4
4
  "description": "A safer C for embedded systems development. Transpiles to clean, readable C.",
5
5
  "main": "src/index.ts",
6
6
  "bin": {
@@ -75,6 +75,8 @@ class Pipeline {
75
75
  > = new Map();
76
76
  /** Issue #424: Store user includes per file for header generation */
77
77
  private readonly userIncludesCollectors: Map<string, string[]> = new Map();
78
+ /** Issue #465: Store ISymbolInfo per file during stage 3 for external enum resolution */
79
+ private readonly symbolInfoByFile: Map<string, ISymbolInfo> = new Map();
78
80
 
79
81
  constructor(config: IPipelineConfig) {
80
82
  // Apply defaults
@@ -593,6 +595,54 @@ class Pipeline {
593
595
  const tSymbols = CNextResolver.resolve(tree, file.path);
594
596
  const iSymbols = TSymbolAdapter.toISymbols(tSymbols, this.symbolTable);
595
597
  this.symbolTable.addSymbols(iSymbols);
598
+
599
+ // Issue #465: Store ISymbolInfo for external enum resolution in stage 5
600
+ // This ensures enum member info is available for all files before code generation
601
+ const symbolInfo = TSymbolInfoAdapter.convert(tSymbols);
602
+ this.symbolInfoByFile.set(file.path, symbolInfo);
603
+ }
604
+
605
+ /**
606
+ * Issue #465: Collect ISymbolInfo from all transitively included .cnx files.
607
+ *
608
+ * Recursively resolves includes to gather enum info from the entire include tree.
609
+ * This ensures that deeply nested enums (A includes B, B includes C with enum)
610
+ * are available for prefixing in the top-level file.
611
+ *
612
+ * @param filePath The file to collect transitive includes for
613
+ * @returns Array of ISymbolInfo from all transitively included .cnx files
614
+ */
615
+ private collectTransitiveEnumInfo(filePath: string): ISymbolInfo[] {
616
+ const result: ISymbolInfo[] = [];
617
+ const visited = new Set<string>();
618
+
619
+ const collectRecursively = (currentPath: string): void => {
620
+ if (visited.has(currentPath)) return;
621
+ visited.add(currentPath);
622
+
623
+ // Read and parse includes from current file
624
+ const content = readFileSync(currentPath, "utf-8");
625
+ const searchPaths = IncludeResolver.buildSearchPaths(
626
+ dirname(currentPath),
627
+ this.config.includeDirs,
628
+ [],
629
+ );
630
+ const resolver = new IncludeResolver(searchPaths);
631
+ const resolved = resolver.resolve(content, currentPath);
632
+
633
+ // Process each included .cnx file
634
+ for (const cnxInclude of resolved.cnextIncludes) {
635
+ const externalInfo = this.symbolInfoByFile.get(cnxInclude.path);
636
+ if (externalInfo) {
637
+ result.push(externalInfo);
638
+ }
639
+ // Recursively collect from this include's includes
640
+ collectRecursively(cnxInclude.path);
641
+ }
642
+ };
643
+
644
+ collectRecursively(filePath);
645
+ return result;
596
646
  }
597
647
 
598
648
  /**
@@ -735,7 +785,19 @@ class Pipeline {
735
785
  try {
736
786
  // ADR-055 Phase 5: Create ISymbolInfo from TSymbol[] for CodeGenerator
737
787
  const tSymbols = CNextResolver.resolve(tree, file.path);
738
- const symbolInfo = TSymbolInfoAdapter.convert(tSymbols);
788
+ let symbolInfo = TSymbolInfoAdapter.convert(tSymbols);
789
+
790
+ // Issue #465: Merge enum info from included .cnx files (including transitive)
791
+ // This enables external enum member references to get the correct type prefix
792
+ const externalEnumSources = this.collectTransitiveEnumInfo(file.path);
793
+
794
+ // Merge external enum info if any includes were found
795
+ if (externalEnumSources.length > 0) {
796
+ symbolInfo = TSymbolInfoAdapter.mergeExternalEnums(
797
+ symbolInfo,
798
+ externalEnumSources,
799
+ );
800
+ }
739
801
 
740
802
  const code = this.codeGenerator.generate(
741
803
  tree,
@@ -1044,15 +1106,39 @@ class Pipeline {
1044
1106
 
1045
1107
  // Step 4b: Issue #294 - Parse C-Next includes to populate symbol table
1046
1108
  // This enables cross-file scope references (e.g., decoder.getSpn() -> decoder_getSpn())
1047
- for (const cnxInclude of resolved.cnextIncludes) {
1048
- try {
1049
- this.collectCNextSymbols(cnxInclude);
1050
- } catch (err) {
1051
- this.warnings.push(
1052
- `Failed to process C-Next include ${cnxInclude.path}: ${err}`,
1053
- );
1109
+ // Issue #465: Recursively process transitive includes for enum info
1110
+ const processedCnxIncludes = new Set<string>();
1111
+ const processCnxIncludesRecursively = (
1112
+ includes: IDiscoveredFile[],
1113
+ ): void => {
1114
+ for (const cnxInclude of includes) {
1115
+ if (processedCnxIncludes.has(cnxInclude.path)) continue;
1116
+ processedCnxIncludes.add(cnxInclude.path);
1117
+
1118
+ try {
1119
+ this.collectCNextSymbols(cnxInclude);
1120
+
1121
+ // Recursively process this include's includes
1122
+ const nestedContent = readFileSync(cnxInclude.path, "utf-8");
1123
+ const nestedSearchPaths = IncludeResolver.buildSearchPaths(
1124
+ dirname(cnxInclude.path),
1125
+ this.config.includeDirs,
1126
+ [],
1127
+ );
1128
+ const nestedResolver = new IncludeResolver(nestedSearchPaths);
1129
+ const nestedResolved = nestedResolver.resolve(
1130
+ nestedContent,
1131
+ cnxInclude.path,
1132
+ );
1133
+ processCnxIncludesRecursively(nestedResolved.cnextIncludes);
1134
+ } catch (err) {
1135
+ this.warnings.push(
1136
+ `Failed to process C-Next include ${cnxInclude.path}: ${err}`,
1137
+ );
1138
+ }
1054
1139
  }
1055
- }
1140
+ };
1141
+ processCnxIncludesRecursively(resolved.cnextIncludes);
1056
1142
 
1057
1143
  // Step 5: Parse C-Next source from string
1058
1144
  const charStream = CharStream.fromString(source);
@@ -1147,7 +1233,49 @@ class Pipeline {
1147
1233
  // Step 7: Generate code with symbol table
1148
1234
  // ADR-055 Phase 5: Create ISymbolInfo from TSymbol[] for CodeGenerator
1149
1235
  const tSymbols = CNextResolver.resolve(tree, sourcePath);
1150
- const symbolInfo = TSymbolInfoAdapter.convert(tSymbols);
1236
+ let symbolInfo = TSymbolInfoAdapter.convert(tSymbols);
1237
+
1238
+ // Issue #465: Merge enum info from included .cnx files (including transitive)
1239
+ // This enables external enum member references to get the correct type prefix
1240
+ const externalEnumSources: ISymbolInfo[] = [];
1241
+ const visitedIncludes = new Set<string>();
1242
+
1243
+ // Recursively collect enum info from all transitive includes
1244
+ const collectFromIncludes = (includes: IDiscoveredFile[]): void => {
1245
+ for (const cnxInclude of includes) {
1246
+ if (visitedIncludes.has(cnxInclude.path)) continue;
1247
+ visitedIncludes.add(cnxInclude.path);
1248
+
1249
+ const externalInfo = this.symbolInfoByFile.get(cnxInclude.path);
1250
+ if (externalInfo) {
1251
+ externalEnumSources.push(externalInfo);
1252
+ }
1253
+
1254
+ // Recursively process this include's includes
1255
+ const nestedContent = readFileSync(cnxInclude.path, "utf-8");
1256
+ const nestedSearchPaths = IncludeResolver.buildSearchPaths(
1257
+ dirname(cnxInclude.path),
1258
+ this.config.includeDirs,
1259
+ [],
1260
+ );
1261
+ const nestedResolver = new IncludeResolver(nestedSearchPaths);
1262
+ const nestedResolved = nestedResolver.resolve(
1263
+ nestedContent,
1264
+ cnxInclude.path,
1265
+ );
1266
+ collectFromIncludes(nestedResolved.cnextIncludes);
1267
+ }
1268
+ };
1269
+
1270
+ collectFromIncludes(resolved.cnextIncludes);
1271
+
1272
+ // Merge external enum info if any includes were found
1273
+ if (externalEnumSources.length > 0) {
1274
+ symbolInfo = TSymbolInfoAdapter.mergeExternalEnums(
1275
+ symbolInfo,
1276
+ externalEnumSources,
1277
+ );
1278
+ }
1151
1279
 
1152
1280
  const code = this.codeGenerator.generate(
1153
1281
  tree,
@@ -365,6 +365,94 @@ class TSymbolInfoAdapter {
365
365
  // Extract the single element from the Set (we know it exists since size === 1)
366
366
  return [...usedIn][0];
367
367
  }
368
+
369
+ /**
370
+ * Issue #465: Merge external enum info into an existing ISymbolInfo.
371
+ *
372
+ * When a file includes other .cnx files, the enum types from those external
373
+ * files need to be available for code generation (enum member prefixing).
374
+ * This method creates a new ISymbolInfo that includes both the base symbols
375
+ * and merged enum info from external sources.
376
+ *
377
+ * @param base The ISymbolInfo from the current file
378
+ * @param externalEnumSources Array of ISymbolInfo from included .cnx files
379
+ * @returns New ISymbolInfo with merged enum data
380
+ */
381
+ static mergeExternalEnums(
382
+ base: ISymbolInfo,
383
+ externalEnumSources: ISymbolInfo[],
384
+ ): ISymbolInfo {
385
+ // If no external sources, return base unchanged
386
+ if (externalEnumSources.length === 0) {
387
+ return base;
388
+ }
389
+
390
+ // Create mutable copies of enum-related data
391
+ const mergedKnownEnums = new Set(base.knownEnums);
392
+ const mergedEnumMembers = new Map<string, Map<string, number>>();
393
+
394
+ // Copy base enum members
395
+ for (const [enumName, members] of base.enumMembers) {
396
+ mergedEnumMembers.set(enumName, new Map(members));
397
+ }
398
+
399
+ // Merge in external enum info
400
+ for (const external of externalEnumSources) {
401
+ for (const enumName of external.knownEnums) {
402
+ mergedKnownEnums.add(enumName);
403
+ }
404
+ for (const [enumName, members] of external.enumMembers) {
405
+ // Don't overwrite if already exists (local takes precedence)
406
+ if (!mergedEnumMembers.has(enumName)) {
407
+ mergedEnumMembers.set(enumName, new Map(members));
408
+ }
409
+ }
410
+ }
411
+
412
+ // Return new ISymbolInfo with merged enum data
413
+ // All other fields remain unchanged from base
414
+ return {
415
+ // Type sets - only knownEnums is merged
416
+ knownScopes: base.knownScopes,
417
+ knownStructs: base.knownStructs,
418
+ knownEnums: mergedKnownEnums,
419
+ knownBitmaps: base.knownBitmaps,
420
+ knownRegisters: base.knownRegisters,
421
+
422
+ // Scope info
423
+ scopeMembers: base.scopeMembers,
424
+ scopeMemberVisibility: base.scopeMemberVisibility,
425
+ scopeVariableUsage: base.scopeVariableUsage,
426
+
427
+ // Struct info
428
+ structFields: base.structFields,
429
+ structFieldArrays: base.structFieldArrays,
430
+ structFieldDimensions: base.structFieldDimensions,
431
+
432
+ // Enum info - merged
433
+ enumMembers: mergedEnumMembers,
434
+
435
+ // Bitmap info
436
+ bitmapFields: base.bitmapFields,
437
+ bitmapBackingType: base.bitmapBackingType,
438
+ bitmapBitWidth: base.bitmapBitWidth,
439
+
440
+ // Register info
441
+ scopedRegisters: base.scopedRegisters,
442
+ registerMemberAccess: base.registerMemberAccess,
443
+ registerMemberTypes: base.registerMemberTypes,
444
+ registerBaseAddresses: base.registerBaseAddresses,
445
+ registerMemberOffsets: base.registerMemberOffsets,
446
+ registerMemberCTypes: base.registerMemberCTypes,
447
+
448
+ // Private const values
449
+ scopePrivateConstValues: base.scopePrivateConstValues,
450
+
451
+ // Methods - delegate to base's implementation
452
+ getSingleFunctionForVariable: base.getSingleFunctionForVariable,
453
+ hasPublicSymbols: base.hasPublicSymbols,
454
+ };
455
+ }
368
456
  }
369
457
 
370
458
  export default TSymbolInfoAdapter;