c-next 0.2.12 → 0.2.13

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 (66) hide show
  1. package/README.md +80 -649
  2. package/dist/index.js +7387 -6211
  3. package/dist/index.js.map +4 -4
  4. package/grammar/C.g4 +9 -3
  5. package/package.json +1 -2
  6. package/src/__tests__/index.test.ts +1 -1
  7. package/src/cli/CleanCommand.ts +8 -12
  8. package/src/cli/Cli.ts +29 -6
  9. package/src/cli/Runner.ts +42 -62
  10. package/src/cli/__tests__/CleanCommand.test.ts +10 -10
  11. package/src/cli/__tests__/Cli.test.ts +59 -7
  12. package/src/cli/__tests__/ConfigPrinter.test.ts +12 -12
  13. package/src/cli/__tests__/PathNormalizer.test.ts +5 -5
  14. package/src/cli/__tests__/Runner.test.ts +108 -82
  15. package/src/cli/serve/ServeCommand.ts +1 -1
  16. package/src/cli/types/ICliConfig.ts +2 -2
  17. package/src/lib/parseWithSymbols.ts +21 -21
  18. package/src/transpiler/Transpiler.ts +88 -43
  19. package/src/transpiler/__tests__/DualCodePaths.test.ts +29 -29
  20. package/src/transpiler/__tests__/Transpiler.coverage.test.ts +244 -72
  21. package/src/transpiler/__tests__/Transpiler.test.ts +32 -72
  22. package/src/transpiler/__tests__/determineProjectRoot.test.ts +30 -28
  23. package/src/transpiler/__tests__/needsConditionalPreprocessing.test.ts +1 -1
  24. package/src/transpiler/data/CNextMarkerDetector.ts +34 -0
  25. package/src/transpiler/data/CppEntryPointScanner.ts +174 -0
  26. package/src/transpiler/data/FileDiscovery.ts +2 -105
  27. package/src/transpiler/data/InputExpansion.ts +37 -81
  28. package/src/transpiler/data/__tests__/CNextMarkerDetector.test.ts +62 -0
  29. package/src/transpiler/data/__tests__/CppEntryPointScanner.test.ts +239 -0
  30. package/src/transpiler/data/__tests__/FileDiscovery.test.ts +45 -191
  31. package/src/transpiler/data/__tests__/InputExpansion.test.ts +36 -204
  32. package/src/transpiler/logic/analysis/InitializationAnalyzer.ts +2 -2
  33. package/src/transpiler/logic/analysis/PassByValueAnalyzer.ts +4 -5
  34. package/src/transpiler/logic/parser/c/grammar/C.interp +19 -1
  35. package/src/transpiler/logic/parser/c/grammar/C.tokens +231 -213
  36. package/src/transpiler/logic/parser/c/grammar/CLexer.interp +28 -1
  37. package/src/transpiler/logic/parser/c/grammar/CLexer.tokens +231 -213
  38. package/src/transpiler/logic/parser/c/grammar/CLexer.ts +654 -600
  39. package/src/transpiler/logic/parser/c/grammar/CParser.ts +1175 -1099
  40. package/src/transpiler/logic/symbols/SymbolTable.ts +19 -7
  41. package/src/transpiler/logic/symbols/__tests__/SymbolTable.test.ts +78 -0
  42. package/src/transpiler/logic/symbols/cnext/__tests__/TSymbolInfoAdapter.test.ts +6 -6
  43. package/src/transpiler/logic/symbols/cnext/adapters/TSymbolInfoAdapter.ts +28 -27
  44. package/src/transpiler/logic/symbols/cnext/index.ts +4 -4
  45. package/src/transpiler/logic/symbols/cnext/utils/SymbolNameUtils.ts +5 -5
  46. package/src/transpiler/output/codegen/CodeGenerator.ts +7 -1
  47. package/src/transpiler/output/codegen/__tests__/CodeGenerator.test.ts +15 -0
  48. package/src/transpiler/output/codegen/__tests__/ExpressionWalker.test.ts +3 -3
  49. package/src/transpiler/output/codegen/__tests__/RequireInclude.test.ts +14 -14
  50. package/src/transpiler/output/codegen/__tests__/TrackVariableTypeHelpers.test.ts +2 -2
  51. package/src/transpiler/output/codegen/utils/QualifiedNameGenerator.ts +7 -7
  52. package/src/transpiler/output/codegen/utils/__tests__/QualifiedNameGenerator.test.ts +3 -3
  53. package/src/transpiler/output/headers/BaseHeaderGenerator.ts +10 -1
  54. package/src/transpiler/output/headers/HeaderGenerator.ts +3 -0
  55. package/src/transpiler/output/headers/HeaderGeneratorUtils.ts +6 -2
  56. package/src/transpiler/output/headers/__tests__/HeaderGeneratorUtils.test.ts +16 -0
  57. package/src/transpiler/output/headers/adapters/HeaderSymbolAdapter.ts +19 -19
  58. package/src/transpiler/output/headers/adapters/__tests__/HeaderSymbolAdapter.test.ts +5 -5
  59. package/src/transpiler/state/SymbolRegistry.ts +10 -12
  60. package/src/transpiler/state/__tests__/SymbolRegistry.test.ts +11 -13
  61. package/src/transpiler/types/IPipelineFile.ts +3 -0
  62. package/src/transpiler/types/ITranspilerConfig.ts +2 -2
  63. package/src/transpiler/types/symbols/IScopeSymbol.ts +1 -1
  64. package/src/utils/FunctionUtils.ts +3 -3
  65. package/src/utils/__tests__/FunctionUtils.test.ts +6 -4
  66. package/src/transpiler/data/types/IDiscoveryOptions.ts +0 -15
@@ -45,6 +45,8 @@ import IncludeResolver from "./data/IncludeResolver";
45
45
  import IncludeTreeWalker from "./data/IncludeTreeWalker";
46
46
  import DependencyGraph from "./data/DependencyGraph";
47
47
  import PathResolver from "./data/PathResolver";
48
+ import InputExpansion from "./data/InputExpansion";
49
+ import CppEntryPointScanner from "./data/CppEntryPointScanner";
48
50
 
49
51
  import ParserUtils from "../utils/ParserUtils";
50
52
  import ITranspilerConfig from "./types/ITranspilerConfig";
@@ -92,7 +94,7 @@ class Transpiler {
92
94
  this.fs = fs ?? new NodeFileSystem();
93
95
  // Apply defaults
94
96
  this.config = {
95
- inputs: config.inputs,
97
+ input: config.input,
96
98
  includeDirs: config.includeDirs ?? [],
97
99
  outDir: config.outDir ?? "",
98
100
  headerOutDir: config.headerOutDir ?? "",
@@ -118,7 +120,7 @@ class Transpiler {
118
120
  // Issue #586: Initialize path resolver
119
121
  this.pathResolver = new PathResolver(
120
122
  {
121
- inputs: this.config.inputs,
123
+ inputs: [dirname(resolve(this.config.input))],
122
124
  outDir: this.config.outDir,
123
125
  headerOutDir: this.config.headerOutDir,
124
126
  basePath: this.config.basePath,
@@ -395,12 +397,17 @@ class Transpiler {
395
397
  this._setupCrossFileModifications();
396
398
 
397
399
  // Generate code
400
+ // Use file's sourceRelativePath (source mode) or compute from PathResolver (files mode)
401
+ const sourceRelativePath =
402
+ file.sourceRelativePath ??
403
+ this.pathResolver.getSourceRelativePath(sourcePath);
398
404
  const code = this.codeGenerator.generate(tree, tokenStream, {
399
405
  debugMode: this.config.debugMode,
400
406
  target: this.config.target,
401
407
  sourcePath,
402
408
  cppMode: this.cppDetected,
403
409
  symbolInfo,
410
+ sourceRelativePath,
404
411
  });
405
412
 
406
413
  // Collect user includes
@@ -535,6 +542,7 @@ class Transpiler {
535
542
  );
536
543
 
537
544
  // Build the main file (with in-memory source and cnextIncludes for enum resolution)
545
+ // Source mode uses basename for self-include to match files mode behavior
538
546
  const mainFile: IPipelineFile = {
539
547
  path: sourcePath,
540
548
  source,
@@ -544,6 +552,7 @@ class Transpiler {
544
552
  extension: ".cnx",
545
553
  },
546
554
  cnextIncludes: resolved.cnextIncludes,
555
+ sourceRelativePath: basename(sourcePath),
547
556
  };
548
557
 
549
558
  // Includes first (symbols must be collected before main file code gen),
@@ -753,41 +762,6 @@ class Transpiler {
753
762
  /**
754
763
  * Discover C-Next files from a single input (file or directory).
755
764
  */
756
- private _discoverCNextFromInput(
757
- input: string,
758
- cnextFiles: IDiscoveredFile[],
759
- fileByPath: Map<string, IDiscoveredFile>,
760
- ): void {
761
- const resolvedInput = resolve(input);
762
-
763
- if (!this.fs.exists(resolvedInput)) {
764
- throw new Error(`Input not found: ${input}`);
765
- }
766
-
767
- const file = FileDiscovery.discoverFile(resolvedInput, this.fs);
768
- if (file?.type === EFileType.CNext) {
769
- cnextFiles.push(file);
770
- fileByPath.set(resolve(file.path), file);
771
- return;
772
- }
773
-
774
- if (file?.type !== EFileType.Unknown && file !== null) {
775
- // Other supported file type (direct header input) - skip for now
776
- return;
777
- }
778
-
779
- // It's a directory - scan for C-Next files
780
- const discovered = FileDiscovery.discover(
781
- [resolvedInput],
782
- { recursive: true },
783
- this.fs,
784
- );
785
- for (const f of FileDiscovery.getCNextFiles(discovered)) {
786
- cnextFiles.push(f);
787
- fileByPath.set(resolve(f.path), f);
788
- }
789
- }
790
-
791
765
  /**
792
766
  * Collect headers from resolved includes, filtering out generated ones.
793
767
  */
@@ -935,19 +909,89 @@ class Transpiler {
935
909
  * includes, not by blindly scanning include directories.
936
910
  */
937
911
  private async _discoverFromFiles(): Promise<IPipelineInput> {
938
- // Step 1: Discover C-Next files from inputs (files or directories)
912
+ const entryPath = resolve(this.config.input);
913
+
914
+ // Check if this is a C/C++ entry point
915
+ if (InputExpansion.isCppEntryPoint(entryPath)) {
916
+ return this._discoverFromCppEntryPoint(entryPath);
917
+ }
918
+
919
+ // Step 1: Discover entry point file (original .cnx entry point logic)
939
920
  const cnextFiles: IDiscoveredFile[] = [];
940
921
  const fileByPath = new Map<string, IDiscoveredFile>();
941
922
 
942
- for (const input of this.config.inputs) {
943
- this._discoverCNextFromInput(input, cnextFiles, fileByPath);
923
+ const entryFile = FileDiscovery.discoverFile(entryPath, this.fs);
924
+ if (entryFile?.type !== EFileType.CNext) {
925
+ return { cnextFiles: [], headerFiles: [], writeOutputToDisk: true };
944
926
  }
927
+ cnextFiles.push(entryFile);
928
+ fileByPath.set(resolve(entryFile.path), entryFile);
929
+
930
+ // Step 2: Build dependency graph, resolve headers, and return pipeline input
931
+ return this._buildPipelineInput(cnextFiles, fileByPath);
932
+ }
945
933
 
946
- if (cnextFiles.length === 0) {
934
+ /**
935
+ * Discover C-Next files from a C/C++ entry point.
936
+ *
937
+ * Scans the include tree for headers with C-Next generation markers,
938
+ * extracts the source .cnx paths, and returns them for transpilation.
939
+ */
940
+ private _discoverFromCppEntryPoint(entryPath: string): IPipelineInput {
941
+ const entryDir = dirname(entryPath);
942
+ const searchPaths = IncludeResolver.buildSearchPaths(
943
+ entryDir,
944
+ this.config.includeDirs,
945
+ [],
946
+ undefined,
947
+ this.fs,
948
+ );
949
+
950
+ const scanner = new CppEntryPointScanner(searchPaths, this.fs);
951
+ const scanResult = scanner.scan(entryPath);
952
+
953
+ // Report errors and warnings
954
+ // Prefix errors to distinguish from informational warnings
955
+ for (const error of scanResult.errors) {
956
+ this.warnings.push(`Error: ${error}`);
957
+ }
958
+ this.warnings.push(...scanResult.warnings);
959
+
960
+ if (scanResult.noCNextFound) {
947
961
  return { cnextFiles: [], headerFiles: [], writeOutputToDisk: true };
948
962
  }
949
963
 
950
- // Step 2: For each C-Next file, resolve its #include directives
964
+ // Convert discovered .cnx paths to IDiscoveredFile array
965
+ const cnextFiles: IDiscoveredFile[] = scanResult.cnextSources.map(
966
+ (path) => ({
967
+ path,
968
+ type: EFileType.CNext,
969
+ extension: ".cnx",
970
+ }),
971
+ );
972
+
973
+ // Build fileByPath map for dependency resolution
974
+ const fileByPath = new Map<string, IDiscoveredFile>();
975
+ for (const cnxFile of cnextFiles) {
976
+ fileByPath.set(resolve(cnxFile.path), cnxFile);
977
+ }
978
+
979
+ // Scanner discovers .cnx files via header markers in the C/C++ include tree.
980
+ // _buildPipelineInput then resolves direct .cnx-to-.cnx includes (e.g.,
981
+ // #include "utils.cnx") which the scanner visits but doesn't add to sources.
982
+ return this._buildPipelineInput(cnextFiles, fileByPath);
983
+ }
984
+
985
+ /**
986
+ * Shared helper: Build pipeline input from discovered C-Next files.
987
+ *
988
+ * Processes includes, builds dependency graph, resolves headers transitively,
989
+ * and converts to pipeline files. Used by both .cnx and C/C++ entry point paths.
990
+ */
991
+ private _buildPipelineInput(
992
+ cnextFiles: IDiscoveredFile[],
993
+ fileByPath: Map<string, IDiscoveredFile>,
994
+ ): IPipelineInput {
951
995
  const headerSet = new Map<string, IDiscoveredFile>();
952
996
  const depGraph = new DependencyGraph();
953
997
  const cnextBaseNames = new Set(
@@ -1381,6 +1425,7 @@ class Transpiler {
1381
1425
  typeInputWithSymbolTable,
1382
1426
  passByValueParams,
1383
1427
  allKnownEnums,
1428
+ basename(sourcePath),
1384
1429
  );
1385
1430
  }
1386
1431
 
@@ -1595,7 +1640,7 @@ class Transpiler {
1595
1640
  */
1596
1641
  private determineProjectRoot(): string | undefined {
1597
1642
  // Start from first input
1598
- const firstInput = this.config.inputs[0];
1643
+ const firstInput = this.config.input;
1599
1644
  if (!firstInput) {
1600
1645
  return undefined;
1601
1646
  }
@@ -26,9 +26,9 @@ describe("Dual Code Paths (Issue #634)", () => {
26
26
  rmSync(tempDir, { recursive: true, force: true });
27
27
  });
28
28
 
29
- function createTranspiler(inputs: string[]): Transpiler {
29
+ function createTranspiler(input: string): Transpiler {
30
30
  const config: ITranspilerConfig = {
31
- inputs,
31
+ input,
32
32
  includeDirs: [tempDir],
33
33
  outDir: tempDir,
34
34
  headerOutDir: tempDir,
@@ -53,11 +53,11 @@ void main() {
53
53
  writeFileSync(filePath, source);
54
54
 
55
55
  // Path 1: Via run()
56
- const transpiler1 = createTranspiler([filePath]);
56
+ const transpiler1 = createTranspiler(filePath);
57
57
  const result1 = await transpiler1.transpile({ kind: "files" });
58
58
 
59
59
  // Path 2: Via transpileSource()
60
- const transpiler2 = createTranspiler([]);
60
+ const transpiler2 = createTranspiler("");
61
61
  const result2 = (
62
62
  await transpiler2.transpile({
63
63
  kind: "source",
@@ -89,10 +89,10 @@ void main() {
89
89
  const filePath = join(tempDir, "structs.cnx");
90
90
  writeFileSync(filePath, source);
91
91
 
92
- const transpiler1 = createTranspiler([filePath]);
92
+ const transpiler1 = createTranspiler(filePath);
93
93
  const result1 = await transpiler1.transpile({ kind: "files" });
94
94
 
95
- const transpiler2 = createTranspiler([]);
95
+ const transpiler2 = createTranspiler("");
96
96
  const result2 = (
97
97
  await transpiler2.transpile({
98
98
  kind: "source",
@@ -119,10 +119,10 @@ void main() {
119
119
  const filePath = join(tempDir, "const-array.cnx");
120
120
  writeFileSync(filePath, source);
121
121
 
122
- const transpiler1 = createTranspiler([filePath]);
122
+ const transpiler1 = createTranspiler(filePath);
123
123
  const result1 = await transpiler1.transpile({ kind: "files" });
124
124
 
125
- const transpiler2 = createTranspiler([]);
125
+ const transpiler2 = createTranspiler("");
126
126
  const result2 = (
127
127
  await transpiler2.transpile({
128
128
  kind: "source",
@@ -155,10 +155,10 @@ void main() {
155
155
  const filePath = join(tempDir, "enums.cnx");
156
156
  writeFileSync(filePath, source);
157
157
 
158
- const transpiler1 = createTranspiler([filePath]);
158
+ const transpiler1 = createTranspiler(filePath);
159
159
  const result1 = await transpiler1.transpile({ kind: "files" });
160
160
 
161
- const transpiler2 = createTranspiler([]);
161
+ const transpiler2 = createTranspiler("");
162
162
  const result2 = (
163
163
  await transpiler2.transpile({
164
164
  kind: "source",
@@ -195,10 +195,10 @@ void main() {
195
195
  const filePath = join(tempDir, "scopes.cnx");
196
196
  writeFileSync(filePath, source);
197
197
 
198
- const transpiler1 = createTranspiler([filePath]);
198
+ const transpiler1 = createTranspiler(filePath);
199
199
  const result1 = await transpiler1.transpile({ kind: "files" });
200
200
 
201
- const transpiler2 = createTranspiler([]);
201
+ const transpiler2 = createTranspiler("");
202
202
  const result2 = (
203
203
  await transpiler2.transpile({
204
204
  kind: "source",
@@ -246,7 +246,7 @@ void main() {
246
246
  writeFileSync(mainPath, mainSource);
247
247
 
248
248
  // run() should handle cross-file references
249
- const transpiler = createTranspiler([mainPath]);
249
+ const transpiler = createTranspiler(mainPath);
250
250
  const result = await transpiler.transpile({ kind: "files" });
251
251
 
252
252
  expect(result.success).toBe(true);
@@ -283,7 +283,7 @@ void main() {
283
283
  const mainPath = join(tempDir, "main.cnx");
284
284
  writeFileSync(mainPath, mainSource);
285
285
 
286
- const transpiler = createTranspiler([mainPath]);
286
+ const transpiler = createTranspiler(mainPath);
287
287
  const result = await transpiler.transpile({ kind: "files" });
288
288
 
289
289
  expect(result.success).toBe(true);
@@ -310,7 +310,7 @@ void main() {
310
310
  const mainPath = join(tempDir, "main.cnx");
311
311
  writeFileSync(mainPath, mainSource);
312
312
 
313
- const transpiler = createTranspiler([mainPath]);
313
+ const transpiler = createTranspiler(mainPath);
314
314
  const result = await transpiler.transpile({ kind: "files" });
315
315
 
316
316
  expect(result.success).toBe(true);
@@ -347,7 +347,7 @@ void main() {
347
347
  writeFileSync(consumerPath, consumerSource);
348
348
 
349
349
  // Via run()
350
- const transpiler1 = createTranspiler([consumerPath]);
350
+ const transpiler1 = createTranspiler(consumerPath);
351
351
  const result1 = await transpiler1.transpile({ kind: "files" });
352
352
 
353
353
  expect(result1.success).toBe(true);
@@ -362,7 +362,7 @@ void main() {
362
362
  expect(consumerFile!.code).toContain("Consumer_fetch()");
363
363
 
364
364
  // Via transpileSource() with context from run()
365
- const transpiler2 = createTranspiler([]);
365
+ const transpiler2 = createTranspiler("");
366
366
  const result2 = (
367
367
  await transpiler2.transpile({
368
368
  kind: "source",
@@ -406,7 +406,7 @@ void main() {
406
406
  writeFileSync(writerPath, writerSource);
407
407
 
408
408
  // Via run()
409
- const transpiler1 = createTranspiler([writerPath]);
409
+ const transpiler1 = createTranspiler(writerPath);
410
410
  const result1 = await transpiler1.transpile({ kind: "files" });
411
411
 
412
412
  expect(result1.success).toBe(true);
@@ -419,7 +419,7 @@ void main() {
419
419
  expect(writerFile!.code).toContain("Storage_value = data");
420
420
 
421
421
  // Via transpileSource()
422
- const transpiler2 = createTranspiler([]);
422
+ const transpiler2 = createTranspiler("");
423
423
  const result2 = (
424
424
  await transpiler2.transpile({
425
425
  kind: "source",
@@ -447,7 +447,7 @@ void main() {}
447
447
  `;
448
448
 
449
449
  // Two separate standalone transpileSource calls
450
- const transpiler = createTranspiler([]);
450
+ const transpiler = createTranspiler("");
451
451
 
452
452
  const result1 = (
453
453
  await transpiler.transpile({
@@ -482,7 +482,7 @@ void main() {}
482
482
  const filePath = join(tempDir, "test.cnx");
483
483
  writeFileSync(filePath, source);
484
484
 
485
- const transpiler = createTranspiler([filePath]);
485
+ const transpiler = createTranspiler(filePath);
486
486
 
487
487
  // First run
488
488
  const result1 = await transpiler.transpile({ kind: "files" });
@@ -520,10 +520,10 @@ void main() {
520
520
  const filePath = join(tempDir, "same-file-struct.cnx");
521
521
  writeFileSync(filePath, source);
522
522
 
523
- const transpiler1 = createTranspiler([filePath]);
523
+ const transpiler1 = createTranspiler(filePath);
524
524
  const result1 = await transpiler1.transpile({ kind: "files" });
525
525
 
526
- const transpiler2 = createTranspiler([]);
526
+ const transpiler2 = createTranspiler("");
527
527
  const result2 = (
528
528
  await transpiler2.transpile({
529
529
  kind: "source",
@@ -564,10 +564,10 @@ void main() {
564
564
  const filePath = join(tempDir, "same-file-enum.cnx");
565
565
  writeFileSync(filePath, source);
566
566
 
567
- const transpiler1 = createTranspiler([filePath]);
567
+ const transpiler1 = createTranspiler(filePath);
568
568
  const result1 = await transpiler1.transpile({ kind: "files" });
569
569
 
570
- const transpiler2 = createTranspiler([]);
570
+ const transpiler2 = createTranspiler("");
571
571
  const result2 = (
572
572
  await transpiler2.transpile({
573
573
  kind: "source",
@@ -607,10 +607,10 @@ void main() {
607
607
  const filePath = join(tempDir, "nested-struct.cnx");
608
608
  writeFileSync(filePath, source);
609
609
 
610
- const transpiler1 = createTranspiler([filePath]);
610
+ const transpiler1 = createTranspiler(filePath);
611
611
  const result1 = await transpiler1.transpile({ kind: "files" });
612
612
 
613
- const transpiler2 = createTranspiler([]);
613
+ const transpiler2 = createTranspiler("");
614
614
  const result2 = (
615
615
  await transpiler2.transpile({
616
616
  kind: "source",
@@ -634,10 +634,10 @@ u8 x <- ; // Parse error: missing expression
634
634
  const filePath = join(tempDir, "invalid.cnx");
635
635
  writeFileSync(filePath, invalidSource);
636
636
 
637
- const transpiler1 = createTranspiler([filePath]);
637
+ const transpiler1 = createTranspiler(filePath);
638
638
  const result1 = await transpiler1.transpile({ kind: "files" });
639
639
 
640
- const transpiler2 = createTranspiler([]);
640
+ const transpiler2 = createTranspiler("");
641
641
  const result2 = await transpiler2.transpile({
642
642
  kind: "source",
643
643
  source: invalidSource,