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.
- package/README.md +86 -63
- package/grammar/CNext.g4 +3 -17
- package/package.json +1 -1
- package/src/cli/serve/ServeCommand.ts +57 -45
- package/src/lib/__tests__/parseCHeader.mocked.test.ts +145 -0
- package/src/transpiler/Transpiler.ts +603 -613
- package/src/transpiler/__tests__/DualCodePaths.test.ts +5 -1
- package/src/transpiler/__tests__/Transpiler.coverage.test.ts +2 -99
- package/src/transpiler/__tests__/Transpiler.test.ts +3 -26
- package/src/transpiler/data/IncludeTreeWalker.ts +1 -1
- package/src/transpiler/logic/analysis/InitializationAnalyzer.ts +23 -52
- package/src/transpiler/logic/parser/grammar/CNext.interp +1 -3
- package/src/transpiler/logic/parser/grammar/CNextListener.ts +0 -22
- package/src/transpiler/logic/parser/grammar/CNextParser.ts +665 -1084
- package/src/transpiler/logic/parser/grammar/CNextVisitor.ts +0 -14
- package/src/transpiler/logic/symbols/CppSymbolCollector.ts +67 -43
- package/src/transpiler/logic/symbols/cnext/collectors/StructCollector.ts +156 -70
- package/src/transpiler/logic/symbols/cnext/collectors/VariableCollector.ts +31 -6
- package/src/transpiler/logic/symbols/cnext/utils/TypeUtils.ts +43 -11
- package/src/transpiler/output/codegen/CodeGenState.ts +811 -0
- package/src/transpiler/output/codegen/CodeGenerator.ts +1410 -2587
- package/src/transpiler/output/codegen/TypeResolver.ts +193 -149
- package/src/transpiler/output/codegen/TypeValidator.ts +148 -370
- package/src/transpiler/output/codegen/__tests__/CodeGenState.test.ts +446 -0
- package/src/transpiler/output/codegen/__tests__/CodeGenerator.test.ts +2082 -52
- package/src/transpiler/output/codegen/__tests__/TrackVariableTypeHelpers.test.ts +1 -1
- package/src/transpiler/output/codegen/__tests__/TypeResolver.test.ts +435 -196
- package/src/transpiler/output/codegen/__tests__/TypeValidator.resolution.test.ts +51 -67
- package/src/transpiler/output/codegen/__tests__/TypeValidator.test.ts +495 -471
- package/src/transpiler/output/codegen/analysis/MemberChainAnalyzer.ts +227 -66
- package/src/transpiler/output/codegen/analysis/StringLengthCounter.ts +55 -58
- package/src/transpiler/output/codegen/analysis/__tests__/MemberChainAnalyzer.test.ts +288 -275
- package/src/transpiler/output/codegen/analysis/__tests__/StringLengthCounter.test.ts +101 -144
- package/src/transpiler/output/codegen/assignment/AssignmentClassifier.ts +195 -133
- package/src/transpiler/output/codegen/assignment/AssignmentContextBuilder.ts +24 -74
- package/src/transpiler/output/codegen/assignment/AssignmentKind.ts +3 -0
- package/src/transpiler/output/codegen/assignment/IAssignmentContext.ts +3 -0
- package/src/transpiler/output/codegen/assignment/__tests__/AssignmentClassifier.test.ts +290 -320
- package/src/transpiler/output/codegen/assignment/handlers/BitAccessHandlers.ts +42 -0
- package/src/transpiler/output/codegen/assignment/handlers/__tests__/BitAccessHandlers.test.ts +76 -2
- package/src/transpiler/output/codegen/generators/GeneratorRegistry.ts +12 -0
- package/src/transpiler/output/codegen/generators/IOrchestrator.ts +5 -1
- package/src/transpiler/output/codegen/generators/__tests__/GeneratorRegistry.test.ts +28 -1
- package/src/transpiler/output/codegen/generators/declarationGenerators/ArrayDimensionUtils.ts +67 -0
- package/src/transpiler/output/codegen/generators/declarationGenerators/RegisterGenerator.ts +11 -24
- package/src/transpiler/output/codegen/generators/declarationGenerators/RegisterMacroGenerator.ts +64 -0
- package/src/transpiler/output/codegen/generators/declarationGenerators/ScopeGenerator.ts +137 -61
- package/src/transpiler/output/codegen/generators/declarationGenerators/ScopedRegisterGenerator.ts +18 -27
- package/src/transpiler/output/codegen/generators/declarationGenerators/StructGenerator.ts +100 -23
- package/src/transpiler/output/codegen/generators/declarationGenerators/__tests__/ArrayDimensionUtils.test.ts +125 -0
- package/src/transpiler/output/codegen/generators/declarationGenerators/__tests__/ScopeGenerator.test.ts +157 -4
- package/src/transpiler/output/codegen/generators/expressions/PostfixExpressionGenerator.ts +5 -1
- package/src/transpiler/output/codegen/generators/statements/ControlFlowGenerator.ts +1 -17
- package/src/transpiler/output/codegen/generators/support/HelperGenerator.ts +23 -22
- package/src/transpiler/output/codegen/helpers/ArrayAccessHelper.ts +129 -0
- package/src/transpiler/output/codegen/helpers/ArrayInitHelper.ts +54 -61
- package/src/transpiler/output/codegen/helpers/AssignmentExpectedTypeResolver.ts +40 -44
- package/src/transpiler/output/codegen/helpers/AssignmentTargetExtractor.ts +17 -45
- package/src/transpiler/output/codegen/helpers/AssignmentValidator.ts +83 -78
- package/src/transpiler/output/codegen/helpers/CppModeHelper.ts +22 -30
- package/src/transpiler/output/codegen/helpers/EnumAssignmentValidator.ts +108 -50
- package/src/transpiler/output/codegen/helpers/FloatBitHelper.ts +16 -31
- package/src/transpiler/output/codegen/helpers/MemberSeparatorResolver.ts +10 -3
- package/src/transpiler/output/codegen/helpers/StringDeclHelper.ts +103 -96
- package/src/transpiler/output/codegen/helpers/SymbolLookupHelper.ts +44 -0
- package/src/transpiler/output/codegen/helpers/TypeGenerationHelper.ts +9 -0
- package/src/transpiler/output/codegen/helpers/__tests__/ArrayAccessHelper.test.ts +479 -0
- package/src/transpiler/output/codegen/helpers/__tests__/ArrayInitHelper.test.ts +58 -103
- package/src/transpiler/output/codegen/helpers/__tests__/AssignmentExpectedTypeResolver.test.ts +97 -40
- package/src/transpiler/output/codegen/helpers/__tests__/AssignmentValidator.test.ts +223 -128
- package/src/transpiler/output/codegen/helpers/__tests__/CppModeHelper.test.ts +68 -41
- package/src/transpiler/output/codegen/helpers/__tests__/EnumAssignmentValidator.test.ts +198 -47
- package/src/transpiler/output/codegen/helpers/__tests__/FloatBitHelper.test.ts +39 -37
- package/src/transpiler/output/codegen/helpers/__tests__/MemberSeparatorResolver.test.ts +1 -0
- package/src/transpiler/output/codegen/helpers/__tests__/StringDeclHelper.test.ts +191 -453
- package/src/transpiler/output/codegen/helpers/__tests__/SymbolLookupHelper.test.ts +201 -0
- package/src/transpiler/output/codegen/helpers/__tests__/TypeGenerationHelper.test.ts +50 -0
- package/src/transpiler/output/codegen/resolution/EnumTypeResolver.ts +229 -0
- package/src/transpiler/output/codegen/resolution/ScopeResolver.ts +60 -0
- package/src/transpiler/output/codegen/resolution/SizeofResolver.ts +177 -0
- package/src/transpiler/output/codegen/resolution/__tests__/EnumTypeResolver.test.ts +336 -0
- package/src/transpiler/output/codegen/resolution/__tests__/SizeofResolver.test.ts +201 -0
- package/src/transpiler/output/codegen/types/IArrayAccessDeps.ts +23 -0
- package/src/transpiler/output/codegen/types/IArrayAccessInfo.ts +26 -0
- package/src/transpiler/output/codegen/types/IMemberSeparatorDeps.ts +7 -0
- package/src/transpiler/output/codegen/utils/CodegenParserUtils.ts +98 -0
- package/src/transpiler/output/codegen/utils/ExpressionUnwrapper.ts +22 -22
- package/src/transpiler/output/codegen/utils/__tests__/CodegenParserUtils.test.ts +228 -0
- package/src/transpiler/types/IFileResult.ts +0 -4
- package/src/transpiler/types/IPipelineFile.ts +27 -0
- package/src/transpiler/types/IPipelineInput.ts +23 -0
- package/src/transpiler/types/TranspilerState.ts +1 -1
- package/src/utils/FormatUtils.ts +28 -2
- package/src/utils/MapUtils.ts +25 -0
- package/src/utils/PostfixAnalysisUtils.ts +48 -0
- package/src/utils/__tests__/FormatUtils.test.ts +42 -0
- package/src/utils/__tests__/MapUtils.test.ts +85 -0
- package/src/utils/constants/OperatorMappings.ts +19 -0
- package/src/transpiler/logic/StandaloneContextBuilder.ts +0 -150
- package/src/transpiler/logic/__tests__/StandaloneContextBuilder.test.ts +0 -647
- package/src/transpiler/output/codegen/types/ITypeResolverDeps.ts +0 -23
- package/src/transpiler/output/codegen/types/ITypeValidatorDeps.ts +0 -53
- package/src/transpiler/types/ITranspileContext.ts +0 -49
- 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
|
|
45
|
-
import
|
|
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
|
-
//
|
|
148
|
-
|
|
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
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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
|
-
|
|
156
|
-
|
|
274
|
+
// Stage 4: Check for symbol conflicts (skipped in standalone mode)
|
|
275
|
+
if (!input.skipConflictCheck && !this._checkSymbolConflicts(result)) {
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
157
278
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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
|
-
|
|
164
|
-
|
|
165
|
-
|
|
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
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
254
|
-
* @returns true if
|
|
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
|
-
*
|
|
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 (
|
|
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
|
|
727
|
+
* Stage 6: Generate headers for pipeline files
|
|
411
728
|
*/
|
|
412
|
-
private
|
|
413
|
-
cnextFiles:
|
|
729
|
+
private _generateAllHeadersFromPipeline(
|
|
730
|
+
cnextFiles: IPipelineFile[],
|
|
414
731
|
result: ITranspilerResult,
|
|
415
732
|
): void {
|
|
416
733
|
for (const file of cnextFiles) {
|
|
417
|
-
|
|
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:
|
|
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
|
|
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
|
-
|
|
822
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
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
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
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
|
-
*
|
|
1255
|
+
* Setup cross-file modification tracking for const inference.
|
|
1154
1256
|
*/
|
|
1155
|
-
|
|
1156
|
-
|
|
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
|
-
*
|
|
1161
|
-
*
|
|
1271
|
+
* Generate header content for exported symbols.
|
|
1272
|
+
* Issue #591: Extracted from transpileSource() for reduced complexity.
|
|
1162
1273
|
*/
|
|
1163
|
-
|
|
1164
|
-
|
|
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
|
-
|
|
1168
|
-
|
|
1287
|
+
if (exportedSymbols.length === 0) {
|
|
1288
|
+
return undefined;
|
|
1289
|
+
}
|
|
1169
1290
|
|
|
1170
|
-
|
|
1171
|
-
collectHeaderSymbols(header: IDiscoveredFile): void {
|
|
1172
|
-
this.doCollectHeaderSymbols(header);
|
|
1173
|
-
}
|
|
1291
|
+
const headerName = basename(sourcePath).replace(/\.cnx$|\.cnext$/, ".h");
|
|
1174
1292
|
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
this.doCollectCNextSymbols(cnxInclude);
|
|
1178
|
-
}
|
|
1293
|
+
// Get type input from CodeGenState (for struct/enum definitions)
|
|
1294
|
+
const typeInput = CodeGenState.symbols;
|
|
1179
1295
|
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
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
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
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
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
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
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
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
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
-
*
|
|
1397
|
-
* SonarCloud S3776: Extracted from transpileSource() for reduced complexity.
|
|
1429
|
+
* Get the symbol table (for testing/inspection)
|
|
1398
1430
|
*/
|
|
1399
|
-
|
|
1400
|
-
|
|
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
|
-
*
|
|
1432
|
-
*
|
|
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
|
-
|
|
1435
|
-
|
|
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
|
/**
|