c-next 0.1.61 → 0.1.63

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 (104) hide show
  1. package/README.md +86 -63
  2. package/grammar/CNext.g4 +3 -17
  3. package/package.json +1 -1
  4. package/src/cli/serve/ServeCommand.ts +57 -45
  5. package/src/lib/__tests__/parseCHeader.mocked.test.ts +145 -0
  6. package/src/transpiler/Transpiler.ts +603 -613
  7. package/src/transpiler/__tests__/DualCodePaths.test.ts +5 -1
  8. package/src/transpiler/__tests__/Transpiler.coverage.test.ts +2 -99
  9. package/src/transpiler/__tests__/Transpiler.test.ts +3 -26
  10. package/src/transpiler/data/IncludeTreeWalker.ts +1 -1
  11. package/src/transpiler/logic/analysis/InitializationAnalyzer.ts +23 -52
  12. package/src/transpiler/logic/parser/grammar/CNext.interp +1 -3
  13. package/src/transpiler/logic/parser/grammar/CNextListener.ts +0 -22
  14. package/src/transpiler/logic/parser/grammar/CNextParser.ts +665 -1084
  15. package/src/transpiler/logic/parser/grammar/CNextVisitor.ts +0 -14
  16. package/src/transpiler/logic/symbols/CppSymbolCollector.ts +67 -43
  17. package/src/transpiler/logic/symbols/cnext/collectors/StructCollector.ts +156 -70
  18. package/src/transpiler/logic/symbols/cnext/collectors/VariableCollector.ts +31 -6
  19. package/src/transpiler/logic/symbols/cnext/utils/TypeUtils.ts +43 -11
  20. package/src/transpiler/output/codegen/CodeGenState.ts +811 -0
  21. package/src/transpiler/output/codegen/CodeGenerator.ts +1410 -2587
  22. package/src/transpiler/output/codegen/TypeResolver.ts +193 -149
  23. package/src/transpiler/output/codegen/TypeValidator.ts +148 -370
  24. package/src/transpiler/output/codegen/__tests__/CodeGenState.test.ts +446 -0
  25. package/src/transpiler/output/codegen/__tests__/CodeGenerator.test.ts +2082 -52
  26. package/src/transpiler/output/codegen/__tests__/TrackVariableTypeHelpers.test.ts +1 -1
  27. package/src/transpiler/output/codegen/__tests__/TypeResolver.test.ts +435 -196
  28. package/src/transpiler/output/codegen/__tests__/TypeValidator.resolution.test.ts +51 -67
  29. package/src/transpiler/output/codegen/__tests__/TypeValidator.test.ts +495 -471
  30. package/src/transpiler/output/codegen/analysis/MemberChainAnalyzer.ts +227 -66
  31. package/src/transpiler/output/codegen/analysis/StringLengthCounter.ts +55 -58
  32. package/src/transpiler/output/codegen/analysis/__tests__/MemberChainAnalyzer.test.ts +288 -275
  33. package/src/transpiler/output/codegen/analysis/__tests__/StringLengthCounter.test.ts +101 -144
  34. package/src/transpiler/output/codegen/assignment/AssignmentClassifier.ts +195 -133
  35. package/src/transpiler/output/codegen/assignment/AssignmentContextBuilder.ts +24 -74
  36. package/src/transpiler/output/codegen/assignment/AssignmentKind.ts +3 -0
  37. package/src/transpiler/output/codegen/assignment/IAssignmentContext.ts +3 -0
  38. package/src/transpiler/output/codegen/assignment/__tests__/AssignmentClassifier.test.ts +290 -320
  39. package/src/transpiler/output/codegen/assignment/handlers/BitAccessHandlers.ts +42 -0
  40. package/src/transpiler/output/codegen/assignment/handlers/__tests__/BitAccessHandlers.test.ts +76 -2
  41. package/src/transpiler/output/codegen/generators/GeneratorRegistry.ts +12 -0
  42. package/src/transpiler/output/codegen/generators/IOrchestrator.ts +5 -1
  43. package/src/transpiler/output/codegen/generators/__tests__/GeneratorRegistry.test.ts +28 -1
  44. package/src/transpiler/output/codegen/generators/declarationGenerators/ArrayDimensionUtils.ts +67 -0
  45. package/src/transpiler/output/codegen/generators/declarationGenerators/RegisterGenerator.ts +11 -24
  46. package/src/transpiler/output/codegen/generators/declarationGenerators/RegisterMacroGenerator.ts +64 -0
  47. package/src/transpiler/output/codegen/generators/declarationGenerators/ScopeGenerator.ts +137 -61
  48. package/src/transpiler/output/codegen/generators/declarationGenerators/ScopedRegisterGenerator.ts +18 -27
  49. package/src/transpiler/output/codegen/generators/declarationGenerators/StructGenerator.ts +100 -23
  50. package/src/transpiler/output/codegen/generators/declarationGenerators/__tests__/ArrayDimensionUtils.test.ts +125 -0
  51. package/src/transpiler/output/codegen/generators/declarationGenerators/__tests__/ScopeGenerator.test.ts +157 -4
  52. package/src/transpiler/output/codegen/generators/expressions/PostfixExpressionGenerator.ts +5 -1
  53. package/src/transpiler/output/codegen/generators/statements/ControlFlowGenerator.ts +1 -17
  54. package/src/transpiler/output/codegen/generators/support/HelperGenerator.ts +23 -22
  55. package/src/transpiler/output/codegen/helpers/ArrayAccessHelper.ts +129 -0
  56. package/src/transpiler/output/codegen/helpers/ArrayInitHelper.ts +54 -61
  57. package/src/transpiler/output/codegen/helpers/AssignmentExpectedTypeResolver.ts +40 -44
  58. package/src/transpiler/output/codegen/helpers/AssignmentTargetExtractor.ts +17 -45
  59. package/src/transpiler/output/codegen/helpers/AssignmentValidator.ts +83 -78
  60. package/src/transpiler/output/codegen/helpers/CppModeHelper.ts +22 -30
  61. package/src/transpiler/output/codegen/helpers/EnumAssignmentValidator.ts +108 -50
  62. package/src/transpiler/output/codegen/helpers/FloatBitHelper.ts +16 -31
  63. package/src/transpiler/output/codegen/helpers/MemberSeparatorResolver.ts +10 -3
  64. package/src/transpiler/output/codegen/helpers/StringDeclHelper.ts +103 -96
  65. package/src/transpiler/output/codegen/helpers/SymbolLookupHelper.ts +44 -0
  66. package/src/transpiler/output/codegen/helpers/TypeGenerationHelper.ts +9 -0
  67. package/src/transpiler/output/codegen/helpers/__tests__/ArrayAccessHelper.test.ts +479 -0
  68. package/src/transpiler/output/codegen/helpers/__tests__/ArrayInitHelper.test.ts +58 -103
  69. package/src/transpiler/output/codegen/helpers/__tests__/AssignmentExpectedTypeResolver.test.ts +97 -40
  70. package/src/transpiler/output/codegen/helpers/__tests__/AssignmentValidator.test.ts +223 -128
  71. package/src/transpiler/output/codegen/helpers/__tests__/CppModeHelper.test.ts +68 -41
  72. package/src/transpiler/output/codegen/helpers/__tests__/EnumAssignmentValidator.test.ts +198 -47
  73. package/src/transpiler/output/codegen/helpers/__tests__/FloatBitHelper.test.ts +39 -37
  74. package/src/transpiler/output/codegen/helpers/__tests__/MemberSeparatorResolver.test.ts +1 -0
  75. package/src/transpiler/output/codegen/helpers/__tests__/StringDeclHelper.test.ts +191 -453
  76. package/src/transpiler/output/codegen/helpers/__tests__/SymbolLookupHelper.test.ts +201 -0
  77. package/src/transpiler/output/codegen/helpers/__tests__/TypeGenerationHelper.test.ts +50 -0
  78. package/src/transpiler/output/codegen/resolution/EnumTypeResolver.ts +229 -0
  79. package/src/transpiler/output/codegen/resolution/ScopeResolver.ts +60 -0
  80. package/src/transpiler/output/codegen/resolution/SizeofResolver.ts +177 -0
  81. package/src/transpiler/output/codegen/resolution/__tests__/EnumTypeResolver.test.ts +336 -0
  82. package/src/transpiler/output/codegen/resolution/__tests__/SizeofResolver.test.ts +201 -0
  83. package/src/transpiler/output/codegen/types/IArrayAccessDeps.ts +23 -0
  84. package/src/transpiler/output/codegen/types/IArrayAccessInfo.ts +26 -0
  85. package/src/transpiler/output/codegen/types/IMemberSeparatorDeps.ts +7 -0
  86. package/src/transpiler/output/codegen/utils/CodegenParserUtils.ts +98 -0
  87. package/src/transpiler/output/codegen/utils/ExpressionUnwrapper.ts +22 -22
  88. package/src/transpiler/output/codegen/utils/__tests__/CodegenParserUtils.test.ts +228 -0
  89. package/src/transpiler/types/IFileResult.ts +0 -4
  90. package/src/transpiler/types/IPipelineFile.ts +27 -0
  91. package/src/transpiler/types/IPipelineInput.ts +23 -0
  92. package/src/transpiler/types/TranspilerState.ts +1 -1
  93. package/src/utils/FormatUtils.ts +28 -2
  94. package/src/utils/MapUtils.ts +25 -0
  95. package/src/utils/PostfixAnalysisUtils.ts +48 -0
  96. package/src/utils/__tests__/FormatUtils.test.ts +42 -0
  97. package/src/utils/__tests__/MapUtils.test.ts +85 -0
  98. package/src/utils/constants/OperatorMappings.ts +19 -0
  99. package/src/transpiler/logic/StandaloneContextBuilder.ts +0 -150
  100. package/src/transpiler/logic/__tests__/StandaloneContextBuilder.test.ts +0 -647
  101. package/src/transpiler/output/codegen/types/ITypeResolverDeps.ts +0 -23
  102. package/src/transpiler/output/codegen/types/ITypeValidatorDeps.ts +0 -53
  103. package/src/transpiler/types/ITranspileContext.ts +0 -49
  104. package/src/transpiler/types/ITranspileContribution.ts +0 -32
@@ -4,6 +4,10 @@
4
4
  *
5
5
  * Key insight from ADR-053: "A single file transpilation is just a project
6
6
  * with one .cnx file."
7
+ *
8
+ * Architecture: Both run() and transpileSource() are thin wrappers that
9
+ * discover files and delegate to _executePipeline(). There is ONE pipeline
10
+ * for all transpilation — no branching on context/standalone mode.
7
11
  */
8
12
 
9
13
  import { join, basename, dirname, resolve } from "node:path";
@@ -15,6 +19,7 @@ import CNextSourceParser from "./logic/parser/CNextSourceParser";
15
19
  import HeaderParser from "./logic/parser/HeaderParser";
16
20
 
17
21
  import CodeGenerator from "./output/codegen/CodeGenerator";
22
+ import CodeGenState from "./output/codegen/CodeGenState";
18
23
  import HeaderGenerator from "./output/headers/HeaderGenerator";
19
24
  import ExternalTypeHeaderBuilder from "./output/headers/ExternalTypeHeaderBuilder";
20
25
  import ICodeGenSymbols from "./types/ICodeGenSymbols";
@@ -34,6 +39,7 @@ import EFileType from "./data/types/EFileType";
34
39
  import IDiscoveredFile from "./data/types/IDiscoveredFile";
35
40
  import IncludeDiscovery from "./data/IncludeDiscovery";
36
41
  import IncludeResolver from "./data/IncludeResolver";
42
+ import IncludeTreeWalker from "./data/IncludeTreeWalker";
37
43
  import DependencyGraph from "./data/DependencyGraph";
38
44
  import PathResolver from "./data/PathResolver";
39
45
 
@@ -41,17 +47,18 @@ import ParserUtils from "../utils/ParserUtils";
41
47
  import ITranspilerConfig from "./types/ITranspilerConfig";
42
48
  import ITranspilerResult from "./types/ITranspilerResult";
43
49
  import IFileResult from "./types/IFileResult";
44
- import ITranspileContext from "./types/ITranspileContext";
45
- import ITranspileContribution from "./types/ITranspileContribution";
50
+ import IPipelineFile from "./types/IPipelineFile";
51
+ import IPipelineInput from "./types/IPipelineInput";
52
+ import ITranspileError from "../lib/types/ITranspileError";
46
53
  import TranspilerState from "./types/TranspilerState";
47
54
  import runAnalyzers from "./logic/analysis/runAnalyzers";
48
55
  import ModificationAnalyzer from "./logic/analysis/ModificationAnalyzer";
49
56
  import AnalyzerContextBuilder from "./logic/analysis/AnalyzerContextBuilder";
50
57
  import CacheManager from "../utils/cache/CacheManager";
58
+ import MapUtils from "../utils/MapUtils";
51
59
  import detectCppSyntax from "./logic/detectCppSyntax";
52
60
  import AutoConstUpdater from "./logic/symbols/AutoConstUpdater";
53
61
  import TransitiveEnumCollector from "./logic/symbols/TransitiveEnumCollector";
54
- import StandaloneContextBuilder from "./logic/StandaloneContextBuilder";
55
62
 
56
63
  /**
57
64
  * Unified transpiler
@@ -127,8 +134,14 @@ class Transpiler {
127
134
  : null;
128
135
  }
129
136
 
137
+ // ===========================================================================
138
+ // Public API: run() and transpileSource()
139
+ // ===========================================================================
140
+
130
141
  /**
131
- * Execute the unified pipeline
142
+ * Execute the unified pipeline from CLI inputs.
143
+ *
144
+ * Stage 1 (file discovery) happens here, then delegates to _executePipeline().
132
145
  */
133
146
  async run(): Promise<ITranspilerResult> {
134
147
  const result = this._initResult();
@@ -144,37 +157,452 @@ class Transpiler {
144
157
 
145
158
  this._ensureOutputDirectories();
146
159
 
147
- // Stage 2: Collect symbols from C/C++ headers
148
- this._collectAllHeaderSymbols(headerFiles, result);
160
+ // Convert IDiscoveredFile[] to IPipelineFile[] (disk-based, all get code gen)
161
+ const pipelineFiles: IPipelineFile[] = cnextFiles.map((f) => ({
162
+ path: f.path,
163
+ discoveredFile: f,
164
+ }));
165
+
166
+ const input: IPipelineInput = {
167
+ cnextFiles: pipelineFiles,
168
+ headerFiles,
169
+ writeOutputToDisk: true,
170
+ };
171
+
172
+ await this._executePipeline(input, result);
173
+
174
+ return await this._finalizeResult(result);
175
+ } catch (err) {
176
+ return this._handleRunError(result, err);
177
+ }
178
+ }
179
+
180
+ /**
181
+ * Transpile source code provided as a string.
182
+ *
183
+ * Discovers includes from the source, builds an IPipelineInput, and
184
+ * delegates to the same _executePipeline() as run().
185
+ *
186
+ * @param source - The C-Next source code as a string
187
+ * @param options - Options for transpilation
188
+ * @returns Promise<IFileResult> with generated code or errors
189
+ */
190
+ async transpileSource(
191
+ source: string,
192
+ options?: {
193
+ workingDir?: string;
194
+ includeDirs?: string[];
195
+ sourcePath?: string;
196
+ },
197
+ ): Promise<IFileResult> {
198
+ const workingDir = options?.workingDir ?? process.cwd();
199
+ const additionalIncludeDirs = options?.includeDirs ?? [];
200
+ const sourcePath = options?.sourcePath ?? "<string>";
201
+
202
+ try {
203
+ await this._initializeRun();
204
+
205
+ const input = this._discoverFromSource(
206
+ source,
207
+ workingDir,
208
+ additionalIncludeDirs,
209
+ sourcePath,
210
+ );
149
211
 
150
- // Stage 3: Collect symbols from C-Next files
151
- if (!this._collectAllCNextSymbols(cnextFiles, result)) {
152
- return this._finalizeResult(result);
212
+ const result = this._initResult();
213
+ await this._executePipeline(input, result);
214
+
215
+ // Find our main file's result
216
+ const fileResult = result.files.find((f) => f.sourcePath === sourcePath);
217
+ if (fileResult) {
218
+ return fileResult;
219
+ }
220
+
221
+ // No file result found — pipeline exited early (e.g., parse errors in Stage 3)
222
+ // Return pipeline errors as a file result
223
+ if (result.errors.length > 0) {
224
+ return this.buildErrorResult(sourcePath, result.errors, 0);
153
225
  }
226
+ return this.buildErrorResult(
227
+ sourcePath,
228
+ [
229
+ {
230
+ line: 1,
231
+ column: 0,
232
+ message: "Pipeline produced no result for source file",
233
+ severity: "error",
234
+ },
235
+ ],
236
+ 0,
237
+ );
238
+ } catch (err) {
239
+ return this.buildCatchResult(sourcePath, err);
240
+ }
241
+ }
242
+
243
+ // ===========================================================================
244
+ // Unified Pipeline
245
+ // ===========================================================================
246
+
247
+ /**
248
+ * The single unified pipeline for all transpilation.
249
+ *
250
+ * Both run() and transpileSource() delegate here after file discovery.
251
+ *
252
+ * Stage 2: Collect symbols from C/C++ headers
253
+ * Stage 3: Collect symbols from C-Next files
254
+ * Stage 3b: Resolve external const array dimensions
255
+ * Stage 4: Check for symbol conflicts
256
+ * Stage 5: Generate code (per-file)
257
+ * Stage 6: Generate headers (per-file)
258
+ */
259
+ private async _executePipeline(
260
+ input: IPipelineInput,
261
+ result: ITranspilerResult,
262
+ ): Promise<void> {
263
+ // Stage 2: Collect symbols from C/C++ headers
264
+ this._collectAllHeaderSymbols(input.headerFiles, result);
265
+
266
+ // Stage 3: Collect symbols from C-Next files
267
+ if (!this._collectAllCNextSymbolsFromPipeline(input.cnextFiles, result)) {
268
+ return;
269
+ }
270
+
271
+ // Stage 3b: Resolve external const array dimensions
272
+ this.symbolTable.resolveExternalArrayDimensions();
154
273
 
155
- // Stage 3b: Resolve external const array dimensions
156
- this.symbolTable.resolveExternalArrayDimensions();
274
+ // Stage 4: Check for symbol conflicts (skipped in standalone mode)
275
+ if (!input.skipConflictCheck && !this._checkSymbolConflicts(result)) {
276
+ return;
277
+ }
157
278
 
158
- // Stage 4: Check for symbol conflicts
159
- if (!this._checkSymbolConflicts(result)) {
160
- return this._finalizeResult(result);
279
+ // Stage 5: Analyze and transpile each C-Next file
280
+ for (const file of input.cnextFiles) {
281
+ if (file.symbolOnly) {
282
+ continue;
161
283
  }
162
284
 
163
- // Stage 5: Analyze and transpile each C-Next file
164
- const context = this._buildTranspileContext();
165
- await this._transpileAllFiles(cnextFiles, context, result);
285
+ const fileResult = this._transpileFile(file);
286
+ this._recordFileResult(
287
+ file.discoveredFile,
288
+ fileResult,
289
+ result,
290
+ input.writeOutputToDisk,
291
+ );
292
+ }
293
+
294
+ // Stage 6: Generate headers (only write to disk in run() mode)
295
+ if (result.success && input.writeOutputToDisk) {
296
+ this._generateAllHeadersFromPipeline(input.cnextFiles, result);
297
+ }
298
+ }
166
299
 
167
- // Stage 6: Generate headers
168
- if (result.success) {
169
- this._generateAllHeaders(cnextFiles, result);
300
+ /**
301
+ * Stage 3 for pipeline files: Collect symbols from all C-Next files.
302
+ *
303
+ * Reads source from file.source or disk, then collects symbols.
304
+ * @returns true if successful, false if errors occurred
305
+ */
306
+ private _collectAllCNextSymbolsFromPipeline(
307
+ cnextFiles: IPipelineFile[],
308
+ result: ITranspilerResult,
309
+ ): boolean {
310
+ for (const file of cnextFiles) {
311
+ const errors = this._doCollectCNextSymbolsFromPipeline(file);
312
+ if (errors) {
313
+ result.errors.push(...errors);
314
+ result.success = false;
170
315
  }
316
+ }
317
+ return result.success;
318
+ }
171
319
 
172
- return await this._finalizeResult(result);
320
+ /**
321
+ * Collect symbols from a single C-Next pipeline file.
322
+ * Uses file.source when available (in-memory), otherwise reads from disk.
323
+ *
324
+ * @returns null on success, or an array of ITranspileError on failure
325
+ */
326
+ private _doCollectCNextSymbolsFromPipeline(
327
+ file: IPipelineFile,
328
+ ): ITranspileError[] | null {
329
+ const content = file.source ?? this.fs.readFile(file.path);
330
+ const { tree, errors } = CNextSourceParser.parse(content);
331
+
332
+ // Parse errors — return them with original line/column and sourcePath
333
+ if (errors.length > 0) {
334
+ return errors.map((e) => ({ ...e, sourcePath: file.path }));
335
+ }
336
+
337
+ try {
338
+ // ADR-055: Use composable collectors via CNextResolver + TSymbolAdapter
339
+ const tSymbols = CNextResolver.resolve(tree, file.path);
340
+ const iSymbols = TSymbolAdapter.toISymbols(tSymbols, this.symbolTable);
341
+ this.symbolTable.addSymbols(iSymbols);
342
+
343
+ // Issue #465: Store ICodeGenSymbols for external enum resolution in stage 5
344
+ const symbolInfo = TSymbolInfoAdapter.convert(tSymbols);
345
+ this.state.setFileSymbolInfo(file.path, symbolInfo);
346
+
347
+ // Issue #593: Collect modification analysis in C++ mode
348
+ if (this.cppDetected) {
349
+ const results = this.codeGenerator.analyzeModificationsOnly(
350
+ tree,
351
+ this.modificationAnalyzer.getModifications(),
352
+ this.modificationAnalyzer.getParamLists(),
353
+ );
354
+ this.modificationAnalyzer.accumulateResults(results);
355
+ }
173
356
  } catch (err) {
174
- return this._handleRunError(result, err);
357
+ // Symbol collection errors (e.g., BitmapCollector) — format as "Code generation failed"
358
+ const rawMessage = err instanceof Error ? err.message : String(err);
359
+ const parsed = ParserUtils.parseErrorLocation(rawMessage);
360
+ return [
361
+ {
362
+ line: parsed.line,
363
+ column: parsed.column,
364
+ message: `Code generation failed: ${parsed.message}`,
365
+ severity: "error",
366
+ },
367
+ ];
368
+ }
369
+
370
+ return null;
371
+ }
372
+
373
+ /**
374
+ * Stage 5: Transpile a single C-Next file.
375
+ *
376
+ * Assumes the symbol table is already populated (stages 2-3 complete).
377
+ * Directly updates this.state and this.modificationAnalyzer.
378
+ */
379
+ private _transpileFile(file: IPipelineFile): IFileResult {
380
+ const sourcePath = file.path;
381
+ const source = file.source ?? this.fs.readFile(file.path);
382
+
383
+ try {
384
+ // Parse source
385
+ const { tree, tokenStream, errors, declarationCount } =
386
+ CNextSourceParser.parse(source);
387
+
388
+ if (errors.length > 0) {
389
+ return this.buildErrorResult(sourcePath, errors, declarationCount);
390
+ }
391
+
392
+ // Parse only mode
393
+ if (this.config.parseOnly) {
394
+ return this.buildParseOnlyResult(sourcePath, declarationCount);
395
+ }
396
+
397
+ // Run analyzers
398
+ const externalStructFields =
399
+ AnalyzerContextBuilder.buildExternalStructFields(this.symbolTable);
400
+
401
+ const analyzerErrors = runAnalyzers(tree, tokenStream, {
402
+ externalStructFields,
403
+ symbolTable: this.symbolTable,
404
+ });
405
+ if (analyzerErrors.length > 0) {
406
+ return this.buildErrorResult(
407
+ sourcePath,
408
+ analyzerErrors,
409
+ declarationCount,
410
+ );
411
+ }
412
+
413
+ // Build symbolInfo for code generation
414
+ const tSymbols = CNextResolver.resolve(tree, sourcePath);
415
+ let symbolInfo = TSymbolInfoAdapter.convert(tSymbols);
416
+
417
+ // Merge enum info from included .cnx files
418
+ const externalEnumSources = this._collectExternalEnumSources(
419
+ sourcePath,
420
+ file.cnextIncludes,
421
+ );
422
+ if (externalEnumSources.length > 0) {
423
+ symbolInfo = TSymbolInfoAdapter.mergeExternalEnums(
424
+ symbolInfo,
425
+ externalEnumSources,
426
+ );
427
+ }
428
+
429
+ // Inject cross-file modification data for const inference
430
+ this._setupCrossFileModifications();
431
+
432
+ // 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
+ );
445
+
446
+ // Collect user includes
447
+ const userIncludes = IncludeExtractor.collectUserIncludes(tree);
448
+
449
+ // Get pass-by-value params (snapshot before next file clears it)
450
+ const passByValue = this.codeGenerator.getPassByValueParams();
451
+ const passByValueCopy = MapUtils.deepCopyStringSetMap(passByValue);
452
+
453
+ // Directly update state (no contribution round-trip)
454
+ this.state.setSymbolInfo(sourcePath, symbolInfo);
455
+ this.state.setPassByValueParams(sourcePath, passByValueCopy);
456
+ this.state.setUserIncludes(sourcePath, [...userIncludes]);
457
+
458
+ // Accumulate C++ modifications directly
459
+ if (this.cppDetected) {
460
+ this._accumulateFileModifications();
461
+ }
462
+
463
+ // Update symbol parameters with auto-const info
464
+ const symbols = this.symbolTable.getSymbolsByFile(sourcePath);
465
+ const unmodifiedParams = this.codeGenerator.getFunctionUnmodifiedParams();
466
+ const knownEnums =
467
+ this.state.getSymbolInfo(sourcePath)?.knownEnums ?? new Set<string>();
468
+ AutoConstUpdater.update(symbols, unmodifiedParams, knownEnums);
469
+
470
+ // Generate header content
471
+ const headerCode = this.generateHeaderContent(
472
+ symbols,
473
+ sourcePath,
474
+ this.symbolTable,
475
+ this.cppDetected,
476
+ userIncludes,
477
+ passByValueCopy,
478
+ symbolInfo,
479
+ );
480
+
481
+ return this.buildSuccessResult(
482
+ sourcePath,
483
+ code,
484
+ headerCode,
485
+ declarationCount,
486
+ );
487
+ } catch (err) {
488
+ return this.buildCatchResult(sourcePath, err);
489
+ }
490
+ }
491
+
492
+ /**
493
+ * Accumulate C++ modification data from the code generator into the
494
+ * centralized modification analyzer.
495
+ */
496
+ private _accumulateFileModifications(): void {
497
+ const fileModifications = this.codeGenerator.getModifiedParameters();
498
+ const modifiedParameters = new Map<string, Set<string>>();
499
+ for (const [funcName, params] of fileModifications) {
500
+ modifiedParameters.set(funcName, new Set(params));
501
+ }
502
+
503
+ const fileParamLists = this.codeGenerator.getFunctionParamLists();
504
+ const functionParamLists = new Map<string, readonly string[]>();
505
+ for (const [funcName, params] of fileParamLists) {
506
+ functionParamLists.set(funcName, [...params]);
507
+ }
508
+
509
+ this.modificationAnalyzer.accumulateModifications(modifiedParameters);
510
+ this.modificationAnalyzer.accumulateParamLists(functionParamLists);
511
+ }
512
+
513
+ // ===========================================================================
514
+ // File Discovery
515
+ // ===========================================================================
516
+
517
+ /**
518
+ * Build IPipelineInput from a source string (standalone mode).
519
+ *
520
+ * Absorbs what StandaloneContextBuilder used to do, but returns data
521
+ * instead of performing side effects.
522
+ */
523
+ private _discoverFromSource(
524
+ source: string,
525
+ workingDir: string,
526
+ additionalIncludeDirs: string[],
527
+ sourcePath: string,
528
+ ): IPipelineInput {
529
+ // Build search paths
530
+ const searchPaths = IncludeResolver.buildSearchPaths(
531
+ workingDir,
532
+ this.config.includeDirs,
533
+ additionalIncludeDirs,
534
+ undefined,
535
+ this.fs,
536
+ );
537
+
538
+ // Resolve includes from source content
539
+ const resolver = new IncludeResolver(searchPaths, this.fs);
540
+ const resolved = resolver.resolve(source, sourcePath);
541
+ this.warnings.push(...resolved.warnings);
542
+
543
+ // Resolve C/C++ headers transitively
544
+ const { headers: allHeaders, warnings: headerWarnings } =
545
+ IncludeResolver.resolveHeadersTransitively(
546
+ resolved.headers,
547
+ [...this.config.includeDirs],
548
+ {
549
+ onDebug: this.config.debugMode
550
+ ? (msg) => console.log(`[DEBUG] ${msg}`)
551
+ : undefined,
552
+ processedPaths: this.state.getProcessedHeadersSet(),
553
+ fs: this.fs,
554
+ },
555
+ );
556
+ this.warnings.push(...headerWarnings);
557
+
558
+ // Store header include directives
559
+ for (const header of allHeaders) {
560
+ const directive = resolved.headerIncludeDirectives.get(header.path);
561
+ if (directive) {
562
+ this.state.setHeaderDirective(header.path, directive);
563
+ }
175
564
  }
565
+
566
+ // Walk C-Next includes transitively to build include file list
567
+ const cnextIncludeFiles: IPipelineFile[] = [];
568
+ IncludeTreeWalker.walk(
569
+ resolved.cnextIncludes,
570
+ this.config.includeDirs,
571
+ (file) => {
572
+ cnextIncludeFiles.push({
573
+ path: file.path,
574
+ discoveredFile: file,
575
+ symbolOnly: true,
576
+ });
577
+ },
578
+ );
579
+
580
+ // Build the main file (with in-memory source and cnextIncludes for enum resolution)
581
+ const mainFile: IPipelineFile = {
582
+ path: sourcePath,
583
+ source,
584
+ discoveredFile: {
585
+ path: sourcePath,
586
+ type: EFileType.CNext,
587
+ extension: ".cnx",
588
+ },
589
+ cnextIncludes: resolved.cnextIncludes,
590
+ };
591
+
592
+ // Includes first (symbols must be collected before main file code gen),
593
+ // then main file
594
+ return {
595
+ cnextFiles: [...cnextIncludeFiles, mainFile],
596
+ headerFiles: allHeaders,
597
+ writeOutputToDisk: false,
598
+ skipConflictCheck: true,
599
+ };
176
600
  }
177
601
 
602
+ // ===========================================================================
603
+ // Pipeline Helper Methods
604
+ // ===========================================================================
605
+
178
606
  /**
179
607
  * Initialize a fresh result object
180
608
  */
@@ -225,21 +653,7 @@ class Transpiler {
225
653
  headerFiles: IDiscoveredFile[],
226
654
  result: ITranspilerResult,
227
655
  ): void {
228
- const { headers: allHeaders, warnings: headerWarnings } =
229
- IncludeResolver.resolveHeadersTransitively(
230
- headerFiles,
231
- this.config.includeDirs,
232
- {
233
- onDebug: this.config.debugMode
234
- ? (msg) => console.log(`[DEBUG] ${msg}`)
235
- : undefined,
236
- processedPaths: this.state.getProcessedHeadersSet(),
237
- fs: this.fs,
238
- },
239
- );
240
- this.warnings.push(...headerWarnings);
241
-
242
- for (const file of allHeaders) {
656
+ for (const file of headerFiles) {
243
657
  try {
244
658
  this.doCollectHeaderSymbols(file);
245
659
  result.filesProcessed++;
@@ -250,32 +664,8 @@ class Transpiler {
250
664
  }
251
665
 
252
666
  /**
253
- * Stage 3: Collect symbols from all C-Next files
254
- * @returns true if successful, false if parse errors occurred
255
- */
256
- private _collectAllCNextSymbols(
257
- cnextFiles: IDiscoveredFile[],
258
- result: ITranspilerResult,
259
- ): boolean {
260
- for (const file of cnextFiles) {
261
- try {
262
- this.doCollectCNextSymbols(file);
263
- } catch (err) {
264
- result.errors.push({
265
- line: 1,
266
- column: 0,
267
- message: `Failed to collect symbols from ${file.path}: ${err}`,
268
- severity: "error",
269
- });
270
- result.success = false;
271
- }
272
- }
273
- return result.success;
274
- }
275
-
276
- /**
277
- * Stage 4: Check for symbol conflicts
278
- * @returns true if no blocking conflicts, false otherwise
667
+ * Stage 4: Check for symbol conflicts
668
+ * @returns true if no blocking conflicts, false otherwise
279
669
  */
280
670
  private _checkSymbolConflicts(result: ITranspilerResult): boolean {
281
671
  const conflicts = this.symbolTable.getConflicts();
@@ -298,94 +688,21 @@ class Transpiler {
298
688
  }
299
689
 
300
690
  /**
301
- * Build shared context for transpileSource delegation
302
- */
303
- private _buildTranspileContext(): ITranspileContext {
304
- return {
305
- symbolTable: this.symbolTable,
306
- symbolInfoByFile: this.state.getSymbolInfoByFileMap(),
307
- accumulatedModifications: this.modificationAnalyzer.getModifications(),
308
- accumulatedParamLists: this.modificationAnalyzer.getParamLists(),
309
- headerIncludeDirectives: this.state.getAllHeaderDirectives(),
310
- cppMode: this.cppDetected,
311
- includeDirs: this.config.includeDirs,
312
- target: this.config.target,
313
- debugMode: this.config.debugMode,
314
- };
315
- }
316
-
317
- /**
318
- * Stage 5: Transpile all C-Next files
319
- */
320
- private async _transpileAllFiles(
321
- cnextFiles: IDiscoveredFile[],
322
- context: ITranspileContext,
323
- result: ITranspilerResult,
324
- ): Promise<void> {
325
- for (const file of cnextFiles) {
326
- const source = this.fs.readFile(file.path);
327
- const fileResult = await this.transpileSource(source, {
328
- workingDir: dirname(file.path),
329
- sourcePath: file.path,
330
- context,
331
- });
332
-
333
- this._processFileContribution(file.path, fileResult);
334
- this._recordFileResult(file, fileResult, result);
335
- }
336
- }
337
-
338
- /**
339
- * Process contributions from a transpiled file
340
- */
341
- private _processFileContribution(
342
- filePath: string,
343
- fileResult: IFileResult,
344
- ): void {
345
- if (!fileResult.contribution) {
346
- return;
347
- }
348
-
349
- // Store symbol info for header generation
350
- this.state.setSymbolInfo(filePath, fileResult.contribution.symbolInfo);
351
- this.state.setPassByValueParams(
352
- filePath,
353
- fileResult.contribution.passByValueParams,
354
- );
355
- this.state.setUserIncludes(filePath, [
356
- ...fileResult.contribution.userIncludes,
357
- ]);
358
-
359
- // Issue #593: Merge C++ mode modifications via centralized analyzer
360
- if (fileResult.contribution.modifiedParameters) {
361
- this.modificationAnalyzer.accumulateModifications(
362
- fileResult.contribution.modifiedParameters,
363
- );
364
- }
365
- if (fileResult.contribution.functionParamLists) {
366
- this.modificationAnalyzer.accumulateParamLists(
367
- fileResult.contribution.functionParamLists,
368
- );
369
- }
370
-
371
- // Issue #588: Update symbol parameters with auto-const info
372
- const symbols = this.symbolTable.getSymbolsByFile(filePath);
373
- const unmodifiedParams = this.codeGenerator.getFunctionUnmodifiedParams();
374
- const knownEnums =
375
- this.state.getSymbolInfo(filePath)?.knownEnums ?? new Set<string>();
376
- AutoConstUpdater.update(symbols, unmodifiedParams, knownEnums);
377
- }
378
-
379
- /**
380
- * Record file result and write output
691
+ * Record file result and optionally write output to disk
381
692
  */
382
693
  private _recordFileResult(
383
694
  file: IDiscoveredFile,
384
695
  fileResult: IFileResult,
385
696
  result: ITranspilerResult,
697
+ writeOutputToDisk: boolean,
386
698
  ): void {
387
699
  let outputPath: string | undefined;
388
- if (this.config.outDir && fileResult.success && fileResult.code) {
700
+ if (
701
+ writeOutputToDisk &&
702
+ this.config.outDir &&
703
+ fileResult.success &&
704
+ fileResult.code
705
+ ) {
389
706
  outputPath = this.pathResolver.getOutputPath(file, this.cppDetected);
390
707
  this.fs.writeFile(outputPath, fileResult.code);
391
708
  }
@@ -407,14 +724,17 @@ class Transpiler {
407
724
  }
408
725
 
409
726
  /**
410
- * Stage 6: Generate headers for all C-Next files
727
+ * Stage 6: Generate headers for pipeline files
411
728
  */
412
- private _generateAllHeaders(
413
- cnextFiles: IDiscoveredFile[],
729
+ private _generateAllHeadersFromPipeline(
730
+ cnextFiles: IPipelineFile[],
414
731
  result: ITranspilerResult,
415
732
  ): void {
416
733
  for (const file of cnextFiles) {
417
- const headerPath = this.generateHeader(file);
734
+ if (file.symbolOnly) {
735
+ continue;
736
+ }
737
+ const headerPath = this.generateHeader(file.discoveredFile);
418
738
  if (headerPath) {
419
739
  result.outputFiles.push(headerPath);
420
740
  }
@@ -458,6 +778,10 @@ class Transpiler {
458
778
  return result;
459
779
  }
460
780
 
781
+ // ===========================================================================
782
+ // Source Discovery (Stage 1 for run())
783
+ // ===========================================================================
784
+
461
785
  /**
462
786
  * Discover C-Next files from a single input (file or directory).
463
787
  */
@@ -670,19 +994,38 @@ class Transpiler {
670
994
  // Issue #580: Sort files topologically for correct cross-file const inference
671
995
  const sortedCnextFiles = this._sortFilesByDependency(depGraph, fileByPath);
672
996
 
997
+ // Resolve headers transitively for the run() path
998
+ const { headers: allHeaders, warnings: headerWarnings } =
999
+ IncludeResolver.resolveHeadersTransitively(
1000
+ [...headerSet.values()],
1001
+ this.config.includeDirs,
1002
+ {
1003
+ onDebug: this.config.debugMode
1004
+ ? (msg) => console.log(`[DEBUG] ${msg}`)
1005
+ : undefined,
1006
+ processedPaths: this.state.getProcessedHeadersSet(),
1007
+ fs: this.fs,
1008
+ },
1009
+ );
1010
+ this.warnings.push(...headerWarnings);
1011
+
673
1012
  return {
674
1013
  cnextFiles: sortedCnextFiles,
675
- headerFiles: [...headerSet.values()],
1014
+ headerFiles: allHeaders,
676
1015
  };
677
1016
  }
678
1017
 
1018
+ // ===========================================================================
1019
+ // Header Symbol Collection
1020
+ // ===========================================================================
1021
+
679
1022
  /**
680
1023
  * Stage 2: Collect symbols from a single C/C++ header
681
1024
  * Issue #592: Recursive include processing moved to IncludeResolver.resolveHeadersTransitively()
682
1025
  * SonarCloud S3776: Refactored to use helper methods for reduced complexity.
683
1026
  */
684
1027
  private doCollectHeaderSymbols(file: IDiscoveredFile): void {
685
- // Track as processed (for cycle detection in transpileSource path)
1028
+ // Track as processed (for cycle detection)
686
1029
  const absolutePath = resolve(file.path);
687
1030
  this.state.markHeaderProcessed(absolutePath);
688
1031
 
@@ -817,47 +1160,9 @@ class Transpiler {
817
1160
  }
818
1161
  }
819
1162
 
820
- /**
821
- * Stage 3: Collect symbols from a C-Next file
822
- * Issue #561: Also collects modification analysis in C++ mode for unified cross-file const inference
823
- */
824
- private doCollectCNextSymbols(file: IDiscoveredFile): void {
825
- const content = this.fs.readFile(file.path);
826
- const { tree, errors } = CNextSourceParser.parse(content);
827
-
828
- if (errors.length > 0) {
829
- // Format errors with file path for better diagnostics
830
- const formattedErrors = errors.map(
831
- (e) => `${file.path}:${e.line}:${e.column} - ${e.message}`,
832
- );
833
- throw new Error(formattedErrors.join("\n"));
834
- }
835
-
836
- // ADR-055: Use composable collectors via CNextResolver + TSymbolAdapter
837
- // TSymbolAdapter.toISymbols registers struct fields in symbolTable for TypeResolver.isStructType()
838
- const tSymbols = CNextResolver.resolve(tree, file.path);
839
- const iSymbols = TSymbolAdapter.toISymbols(tSymbols, this.symbolTable);
840
- this.symbolTable.addSymbols(iSymbols);
841
-
842
- // Issue #465: Store ICodeGenSymbols for external enum resolution in stage 5
843
- // This ensures enum member info is available for all files before code generation
844
- const symbolInfo = TSymbolInfoAdapter.convert(tSymbols);
845
- this.state.setFileSymbolInfo(file.path, symbolInfo);
846
-
847
- // Issue #593: Collect modification analysis in C++ mode for cross-file const inference
848
- // This unifies behavior between run() and transpileSource() code paths
849
- // Issue #565: Pass accumulated cross-file data for transitive propagation
850
- if (this.cppDetected) {
851
- const results = this.codeGenerator.analyzeModificationsOnly(
852
- tree,
853
- this.modificationAnalyzer.getModifications(),
854
- this.modificationAnalyzer.getParamLists(),
855
- );
856
-
857
- // Issue #593: Accumulate via centralized analyzer
858
- this.modificationAnalyzer.accumulateResults(results);
859
- }
860
- }
1163
+ // ===========================================================================
1164
+ // Code Generation Helpers
1165
+ // ===========================================================================
861
1166
 
862
1167
  /**
863
1168
  * Stage 6: Generate header file for a C-Next file
@@ -921,288 +1226,125 @@ class Transpiler {
921
1226
  }
922
1227
 
923
1228
  /**
924
- * Transpile source code provided as a string
925
- *
926
- * This method enables string-based transpilation while still parsing headers.
927
- * Useful for test frameworks that need header type information without reading
928
- * the source file from disk.
929
- *
930
- * ## Issue #634: Consolidated Code Paths
931
- *
932
- * Both run() and transpileSource() now use the same symbol collection timing:
933
- * symbols are collected BEFORE code generation. This eliminates the previous
934
- * architectural discrepancy where run() collected symbols first but standalone
935
- * mode collected them after.
936
- *
937
- * When called with a context (from run()), this method:
938
- * - Uses the shared symbol table (already populated in Stage 3)
939
- * - Skips header/include parsing (Steps 4a, 4b)
940
- * - Uses pre-collected symbolInfoByFile for enum resolution
941
- * - Returns ITranspileContribution in the result
942
- *
943
- * When called without context (standalone mode):
944
- * - Parses headers and includes via StandaloneContextBuilder
945
- * - Collects main file symbols BEFORE code generation (same as run())
946
- * - Maintains isolation between calls (symbolTable cleared each time)
947
- *
948
- * @param source - The C-Next source code as a string
949
- * @param options - Options for transpilation
950
- * @returns Promise<IFileResult> with generated code or errors
1229
+ * Collect external enum sources from included C-Next files.
951
1230
  */
952
- async transpileSource(
953
- source: string,
954
- options?: {
955
- workingDir?: string; // For resolving relative #includes
956
- includeDirs?: string[]; // Additional include paths
957
- sourcePath?: string; // Optional source path for error messages
958
- context?: ITranspileContext; // Cross-file context from run()
959
- },
960
- ): Promise<IFileResult> {
961
- const workingDir = options?.workingDir ?? process.cwd();
962
- const additionalIncludeDirs = options?.includeDirs ?? [];
963
- const sourcePath = options?.sourcePath ?? "<string>";
964
- const context = options?.context;
965
-
966
- // Determine which symbol table to use
967
- const symbolTable = context?.symbolTable ?? this.symbolTable;
968
- // Note: cppMode may be updated after header parsing in standalone mode
969
- let cppMode = context?.cppMode ?? this.cppDetected;
970
-
971
- // Used for include resolution in standalone mode
972
- let resolved: ReturnType<
973
- InstanceType<typeof IncludeResolver>["resolve"]
974
- > | null = null;
975
-
976
- try {
977
- // Initialize cache if enabled (skip if context provided - already done in run())
978
- if (!context && this.cacheManager) {
979
- await this.cacheManager.initialize();
980
- }
981
-
982
- // Issue #593: Clear cross-file modification tracking for fresh analysis
983
- // Skip if context provided - run() manages the shared accumulated state
984
- if (!context) {
985
- this.modificationAnalyzer.clear();
986
- // Issue #634: Reset symbol table and state for standalone mode
987
- // This ensures repeated standalone calls don't accumulate symbols
988
- this.symbolTable.clear();
989
- this.state.reset();
990
- }
991
-
992
- // Step 1: Build search paths using unified IncludeResolver
993
- const searchPaths = IncludeResolver.buildSearchPaths(
994
- workingDir,
995
- context?.includeDirs
996
- ? [...context.includeDirs]
997
- : this.config.includeDirs,
998
- additionalIncludeDirs,
999
- undefined,
1000
- this.fs,
1001
- );
1002
-
1003
- // Step 2: Resolve includes from source content
1004
- const resolver = new IncludeResolver(searchPaths, this.fs);
1005
- resolved = resolver.resolve(source, sourcePath);
1006
-
1007
- // Step 3: Collect warnings
1008
- this.warnings.push(...resolved.warnings);
1009
-
1010
- // Steps 4a, 4b: Parse headers and C-Next includes
1011
- // Skip when context is provided - run() has already done this in Stages 2-3
1012
- // Issue #591: Extracted to StandaloneContextBuilder for reduced complexity
1013
- if (!context) {
1014
- StandaloneContextBuilder.build(this, resolved);
1015
-
1016
- // Re-capture cppMode after header parsing (may have been set to true)
1017
- cppMode = this.cppDetected;
1018
- }
1019
-
1020
- // Step 5: Parse C-Next source from string
1021
- const { tree, tokenStream, errors, declarationCount } =
1022
- CNextSourceParser.parse(source);
1023
-
1024
- // Check for parse errors
1025
- if (errors.length > 0) {
1026
- return this.buildErrorResult(sourcePath, errors, declarationCount);
1027
- }
1028
-
1029
- // Parse only mode
1030
- if (this.config.parseOnly) {
1031
- return this.buildParseOnlyResult(sourcePath, declarationCount);
1032
- }
1033
-
1034
- // Step 6: Run analyzers with struct field info from C/C++ headers
1035
- // Issue #591: Struct field conversion extracted to AnalyzerContextBuilder
1036
- const externalStructFields =
1037
- AnalyzerContextBuilder.buildExternalStructFields(symbolTable);
1038
-
1039
- const analyzerErrors = runAnalyzers(tree, tokenStream, {
1040
- externalStructFields,
1041
- symbolTable,
1042
- });
1043
- if (analyzerErrors.length > 0) {
1044
- return this.buildErrorResult(
1045
- sourcePath,
1046
- analyzerErrors,
1047
- declarationCount,
1048
- );
1049
- }
1050
-
1051
- // Step 7: Generate code with symbol table
1052
- // ADR-055 Phase 5: Create ICodeGenSymbols from TSymbol[] for CodeGenerator
1053
- const tSymbols = CNextResolver.resolve(tree, sourcePath);
1054
- let symbolInfo = TSymbolInfoAdapter.convert(tSymbols);
1055
-
1056
- // Issue #634: Consolidate symbol collection timing with run() path
1057
- // In standalone mode, collect main file symbols BEFORE code generation (like run() does)
1058
- // This eliminates the duplicate CNextResolver.resolve() call that was after generate()
1059
- if (!context) {
1060
- const collectedSymbols = TSymbolAdapter.toISymbols(
1061
- tSymbols,
1062
- symbolTable,
1063
- );
1064
- symbolTable.addSymbols(collectedSymbols);
1065
- }
1066
-
1067
- // Issue #465: Merge enum info from included .cnx files (including transitive)
1068
- // SonarCloud S3776: Extracted to collectExternalEnumSources helper
1069
- const externalEnumSources = this.collectExternalEnumSources(
1070
- context,
1071
- sourcePath,
1072
- resolved,
1073
- );
1074
-
1075
- // Merge external enum info if any includes were found
1076
- if (externalEnumSources.length > 0) {
1077
- symbolInfo = TSymbolInfoAdapter.mergeExternalEnums(
1078
- symbolInfo,
1079
- externalEnumSources,
1080
- );
1081
- }
1082
-
1083
- // Issue #593: Inject cross-file modification data for const inference
1084
- // SonarCloud S3776: Extracted to setupCrossFileModifications helper
1085
- this.setupCrossFileModifications(context, cppMode);
1086
-
1087
- const code = this.codeGenerator.generate(tree, symbolTable, tokenStream, {
1088
- debugMode: context?.debugMode ?? this.config.debugMode,
1089
- target: context?.target ?? this.config.target,
1090
- sourcePath, // Issue #230: For self-include header generation
1091
- cppMode, // Issue #250: C++ compatible code generation
1092
- symbolInfo, // ADR-055: Pre-collected symbol info
1093
- });
1094
-
1095
- // Issue #461: Always generate header content
1096
- // Collect user includes for both header generation and contribution
1097
- const userIncludes = IncludeExtractor.collectUserIncludes(tree);
1098
-
1099
- // Get pass-by-value params (snapshot before next file clears it)
1100
- const passByValue = this.codeGenerator.getPassByValueParams();
1101
- const passByValueCopy = new Map<string, Set<string>>();
1102
- for (const [funcName, params] of passByValue) {
1103
- passByValueCopy.set(funcName, new Set(params));
1104
- }
1105
-
1106
- // Issue #634: Symbol collection moved to before code generation (line ~905)
1107
- // This consolidates the timing with run() path - both now collect symbols before generate()
1108
- // Issue #461: Resolve external const array dimensions before header generation
1109
- if (!context) {
1110
- this.symbolTable.resolveExternalArrayDimensions();
1111
- }
1112
-
1113
- // Issue #591: Header generation extracted to helper method
1114
- const symbols = symbolTable.getSymbolsByFile(sourcePath);
1115
- const headerCode = this.generateHeaderContent(
1116
- symbols,
1117
- sourcePath,
1118
- symbolTable,
1119
- cppMode,
1120
- userIncludes,
1121
- passByValueCopy,
1122
- symbolInfo,
1123
- );
1124
-
1125
- // Flush cache to disk (skip if context provided - run() handles this)
1126
- if (!context && this.cacheManager) {
1127
- await this.cacheManager.flush();
1128
- }
1129
-
1130
- // Build contribution for run() to accumulate
1131
- const contribution = context
1132
- ? this.buildContribution(
1133
- cppMode,
1134
- symbolInfo,
1135
- passByValueCopy,
1136
- userIncludes,
1137
- )
1138
- : undefined;
1231
+ private _collectExternalEnumSources(
1232
+ sourcePath: string,
1233
+ cnextIncludes?: ReadonlyArray<{ path: string }>,
1234
+ ): ICodeGenSymbols[] {
1235
+ const symbolInfoByFile = this.state.getSymbolInfoByFileMap();
1139
1236
 
1140
- return this.buildSuccessResult(
1141
- sourcePath,
1142
- code,
1143
- headerCode,
1144
- declarationCount,
1145
- contribution,
1237
+ if (cnextIncludes) {
1238
+ // Standalone mode: use unified collectForStandalone method
1239
+ return TransitiveEnumCollector.collectForStandalone(
1240
+ cnextIncludes,
1241
+ symbolInfoByFile,
1242
+ this.config.includeDirs,
1146
1243
  );
1147
- } catch (err) {
1148
- return this.buildCatchResult(sourcePath, err);
1149
1244
  }
1245
+
1246
+ // run() mode: use TransitiveEnumCollector with pre-populated symbolInfoByFile
1247
+ return TransitiveEnumCollector.collect(
1248
+ sourcePath,
1249
+ symbolInfoByFile,
1250
+ this.config.includeDirs,
1251
+ );
1150
1252
  }
1151
1253
 
1152
1254
  /**
1153
- * Get the symbol table (for testing/inspection)
1255
+ * Setup cross-file modification tracking for const inference.
1154
1256
  */
1155
- getSymbolTable(): SymbolTable {
1156
- return this.symbolTable;
1257
+ private _setupCrossFileModifications(): void {
1258
+ const accumulatedModifications =
1259
+ this.modificationAnalyzer.getModifications();
1260
+ const accumulatedParamLists = this.modificationAnalyzer.getParamLists();
1261
+
1262
+ if (this.cppDetected && accumulatedModifications.size > 0) {
1263
+ this.codeGenerator.setCrossFileModifications(
1264
+ accumulatedModifications,
1265
+ accumulatedParamLists,
1266
+ );
1267
+ }
1157
1268
  }
1158
1269
 
1159
1270
  /**
1160
- * Check if C++ output was detected during transpilation.
1161
- * This is set when C++ syntax is found in included headers (e.g., Arduino.h).
1271
+ * Generate header content for exported symbols.
1272
+ * Issue #591: Extracted from transpileSource() for reduced complexity.
1162
1273
  */
1163
- isCppDetected(): boolean {
1164
- return this.cppDetected;
1165
- }
1274
+ private generateHeaderContent(
1275
+ symbols: ISymbol[],
1276
+ sourcePath: string,
1277
+ symbolTable: SymbolTable,
1278
+ cppMode: boolean,
1279
+ userIncludes: string[],
1280
+ passByValueParams: Map<string, Set<string>>,
1281
+ symbolInfo: ICodeGenSymbols,
1282
+ ): string | undefined {
1283
+ const exportedSymbols = symbols.filter(
1284
+ (s: { isExported?: boolean }) => s.isExported,
1285
+ );
1166
1286
 
1167
- // === IStandaloneTranspiler implementation (Issue #591) ===
1168
- // These methods implement the interface used by StandaloneContextBuilder
1287
+ if (exportedSymbols.length === 0) {
1288
+ return undefined;
1289
+ }
1169
1290
 
1170
- /** @implements IStandaloneTranspiler.collectHeaderSymbols (Issue #592: now sync) */
1171
- collectHeaderSymbols(header: IDiscoveredFile): void {
1172
- this.doCollectHeaderSymbols(header);
1173
- }
1291
+ const headerName = basename(sourcePath).replace(/\.cnx$|\.cnext$/, ".h");
1174
1292
 
1175
- /** @implements IStandaloneTranspiler.collectCNextSymbols */
1176
- collectCNextSymbols(cnxInclude: IDiscoveredFile): void {
1177
- this.doCollectCNextSymbols(cnxInclude);
1178
- }
1293
+ // Get type input from CodeGenState (for struct/enum definitions)
1294
+ const typeInput = CodeGenState.symbols;
1179
1295
 
1180
- /** @implements IStandaloneTranspiler.getIncludeDirs */
1181
- getIncludeDirs(): readonly string[] {
1182
- return this.config.includeDirs;
1183
- }
1296
+ // Update auto-const info on symbol parameters
1297
+ const unmodifiedParams = this.codeGenerator.getFunctionUnmodifiedParams();
1298
+ for (const symbol of symbols) {
1299
+ if (symbol.kind !== ESymbolKind.Function || !symbol.parameters) {
1300
+ continue;
1301
+ }
1302
+ const unmodified = unmodifiedParams.get(symbol.name);
1303
+ if (!unmodified) continue;
1184
1304
 
1185
- /** @implements IStandaloneTranspiler.setHeaderIncludeDirective */
1186
- setHeaderIncludeDirective(headerPath: string, directive: string): void {
1187
- this.state.setHeaderDirective(headerPath, directive);
1188
- }
1305
+ for (const param of symbol.parameters) {
1306
+ const isPointerParam =
1307
+ !param.isConst &&
1308
+ !param.isArray &&
1309
+ param.type !== "f32" &&
1310
+ param.type !== "f64" &&
1311
+ param.type !== "ISR";
1312
+ if (isPointerParam && unmodified.has(param.name)) {
1313
+ param.isAutoConst = true;
1314
+ }
1315
+ }
1316
+ }
1189
1317
 
1190
- /** @implements IStandaloneTranspiler.addWarning */
1191
- addWarning(message: string): void {
1192
- this.warnings.push(message);
1193
- }
1318
+ // Issue #497: Build mapping from external types to their C header includes
1319
+ const externalTypeHeaders = ExternalTypeHeaderBuilder.build(
1320
+ this.state.getAllHeaderDirectives(),
1321
+ symbolTable,
1322
+ );
1194
1323
 
1195
- /** @implements IStandaloneTranspiler.getProcessedHeaders (Issue #592) */
1196
- getProcessedHeaders(): Set<string> {
1197
- return this.state.getProcessedHeadersSet();
1198
- }
1324
+ // Issue #502: Include symbolTable in typeInput for C++ namespace type detection
1325
+ const typeInputWithSymbolTable = typeInput
1326
+ ? { ...typeInput, symbolTable }
1327
+ : undefined;
1199
1328
 
1200
- /** @implements IStandaloneTranspiler.isDebugMode (Issue #592) */
1201
- isDebugMode(): boolean {
1202
- return this.config.debugMode;
1329
+ // Issue #478: Pass all known enums for cross-file type handling
1330
+ return this.headerGenerator.generate(
1331
+ exportedSymbols,
1332
+ headerName,
1333
+ {
1334
+ exportedOnly: true,
1335
+ userIncludes,
1336
+ externalTypeHeaders,
1337
+ cppMode,
1338
+ },
1339
+ typeInputWithSymbolTable,
1340
+ passByValueParams,
1341
+ symbolInfo.knownEnums,
1342
+ );
1203
1343
  }
1204
1344
 
1205
- // === Result builder helper methods (Issue #591) ===
1345
+ // ===========================================================================
1346
+ // Result Builder Helpers
1347
+ // ===========================================================================
1206
1348
 
1207
1349
  /**
1208
1350
  * Build an error result for parse/analyzer failures.
@@ -1245,7 +1387,6 @@ class Transpiler {
1245
1387
  code: string,
1246
1388
  headerCode: string | undefined,
1247
1389
  declarationCount: number,
1248
- contribution?: ITranspileContribution,
1249
1390
  ): IFileResult {
1250
1391
  return {
1251
1392
  sourcePath,
@@ -1254,7 +1395,6 @@ class Transpiler {
1254
1395
  success: true,
1255
1396
  errors: [],
1256
1397
  declarationCount,
1257
- contribution,
1258
1398
  };
1259
1399
  }
1260
1400
 
@@ -1281,173 +1421,23 @@ class Transpiler {
1281
1421
  };
1282
1422
  }
1283
1423
 
1284
- /**
1285
- * Build the contribution object for run() to accumulate.
1286
- */
1287
- private buildContribution(
1288
- cppMode: boolean,
1289
- symbolInfo: ICodeGenSymbols,
1290
- passByValueParams: Map<string, Set<string>>,
1291
- userIncludes: string[],
1292
- ): ITranspileContribution {
1293
- let modifiedParameters: Map<string, Set<string>> | undefined;
1294
- let functionParamLists: Map<string, readonly string[]> | undefined;
1295
-
1296
- if (cppMode) {
1297
- const fileModifications = this.codeGenerator.getModifiedParameters();
1298
- modifiedParameters = new Map();
1299
- for (const [funcName, params] of fileModifications) {
1300
- modifiedParameters.set(funcName, new Set(params));
1301
- }
1302
-
1303
- const fileParamLists = this.codeGenerator.getFunctionParamLists();
1304
- functionParamLists = new Map();
1305
- for (const [funcName, params] of fileParamLists) {
1306
- functionParamLists.set(funcName, [...params]);
1307
- }
1308
- }
1309
-
1310
- return {
1311
- symbolInfo,
1312
- passByValueParams,
1313
- userIncludes,
1314
- modifiedParameters,
1315
- functionParamLists,
1316
- };
1317
- }
1318
-
1319
- /**
1320
- * Generate header content for exported symbols.
1321
- * Issue #591: Extracted from transpileSource() for reduced complexity.
1322
- */
1323
- private generateHeaderContent(
1324
- symbols: ISymbol[],
1325
- sourcePath: string,
1326
- symbolTable: SymbolTable,
1327
- cppMode: boolean,
1328
- userIncludes: string[],
1329
- passByValueParams: Map<string, Set<string>>,
1330
- symbolInfo: ICodeGenSymbols,
1331
- ): string | undefined {
1332
- const exportedSymbols = symbols.filter(
1333
- (s: { isExported?: boolean }) => s.isExported,
1334
- );
1335
-
1336
- if (exportedSymbols.length === 0) {
1337
- return undefined;
1338
- }
1339
-
1340
- const headerName = basename(sourcePath).replace(/\.cnx$|\.cnext$/, ".h");
1341
-
1342
- // Get type input from code generator (for struct/enum definitions)
1343
- const typeInput = this.codeGenerator.symbols;
1344
-
1345
- // Update auto-const info on symbol parameters
1346
- const unmodifiedParams = this.codeGenerator.getFunctionUnmodifiedParams();
1347
- for (const symbol of symbols) {
1348
- if (symbol.kind !== ESymbolKind.Function || !symbol.parameters) {
1349
- continue;
1350
- }
1351
- const unmodified = unmodifiedParams.get(symbol.name);
1352
- if (!unmodified) continue;
1353
-
1354
- for (const param of symbol.parameters) {
1355
- const isPointerParam =
1356
- !param.isConst &&
1357
- !param.isArray &&
1358
- param.type !== "f32" &&
1359
- param.type !== "f64" &&
1360
- param.type !== "ISR";
1361
- if (isPointerParam && unmodified.has(param.name)) {
1362
- param.isAutoConst = true;
1363
- }
1364
- }
1365
- }
1366
-
1367
- // Issue #497: Build mapping from external types to their C header includes
1368
- const externalTypeHeaders = ExternalTypeHeaderBuilder.build(
1369
- this.state.getAllHeaderDirectives(),
1370
- symbolTable,
1371
- );
1372
-
1373
- // Issue #502: Include symbolTable in typeInput for C++ namespace type detection
1374
- const typeInputWithSymbolTable = typeInput
1375
- ? { ...typeInput, symbolTable }
1376
- : undefined;
1377
-
1378
- // Issue #478: Pass all known enums for cross-file type handling
1379
- // This includes enums from this file and all transitively included .cnx files
1380
- return this.headerGenerator.generate(
1381
- exportedSymbols,
1382
- headerName,
1383
- {
1384
- exportedOnly: true,
1385
- userIncludes,
1386
- externalTypeHeaders,
1387
- cppMode,
1388
- },
1389
- typeInputWithSymbolTable,
1390
- passByValueParams,
1391
- symbolInfo.knownEnums,
1392
- );
1393
- }
1424
+ // ===========================================================================
1425
+ // Public Accessors
1426
+ // ===========================================================================
1394
1427
 
1395
1428
  /**
1396
- * Collect external enum sources from included C-Next files.
1397
- * SonarCloud S3776: Extracted from transpileSource() for reduced complexity.
1429
+ * Get the symbol table (for testing/inspection)
1398
1430
  */
1399
- private collectExternalEnumSources(
1400
- context: ITranspileContext | undefined,
1401
- sourcePath: string,
1402
- resolved: ReturnType<
1403
- InstanceType<typeof IncludeResolver>["resolve"]
1404
- > | null,
1405
- ): ICodeGenSymbols[] {
1406
- const symbolInfoByFile =
1407
- context?.symbolInfoByFile ?? this.state.getSymbolInfoByFileMap();
1408
-
1409
- if (context) {
1410
- // Context mode: use TransitiveEnumCollector with pre-populated symbolInfoByFile
1411
- return TransitiveEnumCollector.collect(
1412
- sourcePath,
1413
- this.state.getSymbolInfoByFileMap(),
1414
- this.config.includeDirs,
1415
- );
1416
- }
1417
-
1418
- if (resolved) {
1419
- // Standalone mode: use unified collectForStandalone method
1420
- return TransitiveEnumCollector.collectForStandalone(
1421
- resolved.cnextIncludes,
1422
- symbolInfoByFile,
1423
- this.config.includeDirs,
1424
- );
1425
- }
1426
-
1427
- return [];
1431
+ getSymbolTable(): SymbolTable {
1432
+ return this.symbolTable;
1428
1433
  }
1429
1434
 
1430
1435
  /**
1431
- * Setup cross-file modification tracking for const inference.
1432
- * SonarCloud S3776: Extracted from transpileSource() for reduced complexity.
1436
+ * Check if C++ output was detected during transpilation.
1437
+ * This is set when C++ syntax is found in included headers (e.g., Arduino.h).
1433
1438
  */
1434
- private setupCrossFileModifications(
1435
- context: ITranspileContext | undefined,
1436
- cppMode: boolean,
1437
- ): void {
1438
- const accumulatedModifications =
1439
- context?.accumulatedModifications ??
1440
- this.modificationAnalyzer.getModifications();
1441
- const accumulatedParamLists =
1442
- context?.accumulatedParamLists ??
1443
- this.modificationAnalyzer.getParamLists();
1444
-
1445
- if (cppMode && accumulatedModifications.size > 0) {
1446
- this.codeGenerator.setCrossFileModifications(
1447
- accumulatedModifications,
1448
- accumulatedParamLists,
1449
- );
1450
- }
1439
+ isCppDetected(): boolean {
1440
+ return this.cppDetected;
1451
1441
  }
1452
1442
 
1453
1443
  /**