c-next 0.2.2 → 0.2.4
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 +59 -57
- package/dist/index.js +641 -191
- package/dist/index.js.map +4 -4
- package/package.json +1 -1
- package/src/cli/Runner.ts +1 -1
- package/src/cli/__tests__/Runner.test.ts +8 -8
- package/src/cli/serve/ServeCommand.ts +29 -9
- package/src/transpiler/Transpiler.ts +105 -200
- package/src/transpiler/__tests__/DualCodePaths.test.ts +117 -68
- package/src/transpiler/__tests__/Transpiler.coverage.test.ts +87 -51
- package/src/transpiler/__tests__/Transpiler.test.ts +150 -48
- package/src/transpiler/__tests__/determineProjectRoot.test.ts +2 -2
- package/src/transpiler/data/IncludeResolver.ts +11 -3
- package/src/transpiler/data/__tests__/IncludeResolver.test.ts +2 -2
- package/src/transpiler/logic/analysis/ArrayIndexTypeAnalyzer.ts +346 -0
- package/src/transpiler/logic/analysis/FunctionCallAnalyzer.ts +170 -14
- package/src/transpiler/logic/analysis/__tests__/ArrayIndexTypeAnalyzer.test.ts +545 -0
- package/src/transpiler/logic/analysis/__tests__/FunctionCallAnalyzer.test.ts +327 -0
- package/src/transpiler/logic/analysis/runAnalyzers.ts +9 -2
- package/src/transpiler/logic/analysis/types/IArrayIndexTypeError.ts +15 -0
- package/src/transpiler/logic/symbols/TransitiveEnumCollector.ts +1 -1
- package/src/transpiler/logic/symbols/c/index.ts +50 -1
- package/src/transpiler/logic/symbols/c/utils/DeclaratorUtils.ts +99 -2
- package/src/transpiler/logic/symbols/c/utils/__tests__/DeclaratorUtils.test.ts +128 -0
- package/src/transpiler/output/codegen/CodeGenerator.ts +31 -5
- package/src/transpiler/output/codegen/TypeValidator.ts +10 -7
- package/src/transpiler/output/codegen/__tests__/CodeGenerator.test.ts +49 -36
- package/src/transpiler/output/codegen/__tests__/ExpressionWalker.test.ts +9 -3
- package/src/transpiler/output/codegen/__tests__/RequireInclude.test.ts +90 -25
- package/src/transpiler/output/codegen/__tests__/TrackVariableTypeHelpers.test.ts +3 -1
- package/src/transpiler/output/codegen/__tests__/TypeValidator.test.ts +43 -29
- package/src/transpiler/output/codegen/generators/IOrchestrator.ts +5 -2
- package/src/transpiler/output/codegen/generators/expressions/PostfixExpressionGenerator.ts +23 -14
- package/src/transpiler/output/codegen/generators/expressions/__tests__/PostfixExpressionGenerator.test.ts +10 -7
- package/src/transpiler/output/codegen/generators/statements/ControlFlowGenerator.ts +12 -3
- package/src/transpiler/output/codegen/generators/statements/SwitchGenerator.ts +10 -1
- package/src/transpiler/output/codegen/generators/statements/__tests__/ControlFlowGenerator.test.ts +4 -4
- package/src/transpiler/output/codegen/helpers/ArrayAccessHelper.ts +6 -3
- package/src/transpiler/output/codegen/helpers/FloatBitHelper.ts +36 -22
- package/src/transpiler/output/codegen/helpers/FunctionContextManager.ts +9 -1
- package/src/transpiler/output/codegen/helpers/__tests__/ArrayAccessHelper.test.ts +8 -6
- package/src/transpiler/output/codegen/helpers/__tests__/FloatBitHelper.test.ts +34 -18
- package/src/transpiler/output/headers/HeaderGeneratorUtils.ts +5 -2
- package/src/transpiler/state/CodeGenState.ts +6 -0
- package/src/transpiler/types/IPipelineFile.ts +2 -2
- package/src/transpiler/types/IPipelineInput.ts +1 -1
- package/src/transpiler/types/TTranspileInput.ts +18 -0
- package/src/utils/constants/TypeConstants.ts +22 -0
package/package.json
CHANGED
package/src/cli/Runner.ts
CHANGED
|
@@ -30,7 +30,7 @@ describe("Runner", () => {
|
|
|
30
30
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
31
31
|
let processExitSpy: any;
|
|
32
32
|
let mockConfig: ICliConfig;
|
|
33
|
-
let mockTranspilerInstance: {
|
|
33
|
+
let mockTranspilerInstance: { transpile: ReturnType<typeof vi.fn> };
|
|
34
34
|
|
|
35
35
|
beforeEach(() => {
|
|
36
36
|
vi.clearAllMocks();
|
|
@@ -66,7 +66,7 @@ describe("Runner", () => {
|
|
|
66
66
|
|
|
67
67
|
// Mock Transpiler
|
|
68
68
|
mockTranspilerInstance = {
|
|
69
|
-
|
|
69
|
+
transpile: vi.fn().mockResolvedValue({
|
|
70
70
|
success: true,
|
|
71
71
|
outputFiles: ["/project/src/main.c"],
|
|
72
72
|
errors: [],
|
|
@@ -94,7 +94,7 @@ describe("Runner", () => {
|
|
|
94
94
|
"src/main.cnx",
|
|
95
95
|
]);
|
|
96
96
|
expect(Transpiler).toHaveBeenCalled();
|
|
97
|
-
expect(mockTranspilerInstance.
|
|
97
|
+
expect(mockTranspilerInstance.transpile).toHaveBeenCalled();
|
|
98
98
|
expect(ResultPrinter.print).toHaveBeenCalled();
|
|
99
99
|
});
|
|
100
100
|
|
|
@@ -125,7 +125,7 @@ describe("Runner", () => {
|
|
|
125
125
|
});
|
|
126
126
|
|
|
127
127
|
it("passes include paths to Transpiler", async () => {
|
|
128
|
-
// Include discovery now happens inside Transpiler.
|
|
128
|
+
// Include discovery now happens inside Transpiler.discoverIncludes()
|
|
129
129
|
// Runner just passes config.includeDirs directly
|
|
130
130
|
mockConfig.includeDirs = ["/extra/include"];
|
|
131
131
|
|
|
@@ -168,7 +168,7 @@ describe("Runner", () => {
|
|
|
168
168
|
it("handles explicit output filename for single file", async () => {
|
|
169
169
|
mockConfig.outputPath = "output/result.c";
|
|
170
170
|
|
|
171
|
-
mockTranspilerInstance.
|
|
171
|
+
mockTranspilerInstance.transpile.mockResolvedValue({
|
|
172
172
|
success: true,
|
|
173
173
|
outputFiles: ["/project/output/main.c"],
|
|
174
174
|
errors: [],
|
|
@@ -236,7 +236,7 @@ describe("Runner", () => {
|
|
|
236
236
|
});
|
|
237
237
|
|
|
238
238
|
it("exits with 0 on success", async () => {
|
|
239
|
-
mockTranspilerInstance.
|
|
239
|
+
mockTranspilerInstance.transpile.mockResolvedValue({
|
|
240
240
|
success: true,
|
|
241
241
|
outputFiles: ["/project/src/main.c"],
|
|
242
242
|
errors: [],
|
|
@@ -248,7 +248,7 @@ describe("Runner", () => {
|
|
|
248
248
|
});
|
|
249
249
|
|
|
250
250
|
it("exits with 1 on failure", async () => {
|
|
251
|
-
mockTranspilerInstance.
|
|
251
|
+
mockTranspilerInstance.transpile.mockResolvedValue({
|
|
252
252
|
success: false,
|
|
253
253
|
outputFiles: [],
|
|
254
254
|
errors: ["Some error"],
|
|
@@ -285,7 +285,7 @@ describe("Runner", () => {
|
|
|
285
285
|
it("doesn't rename when generated file matches explicit path", async () => {
|
|
286
286
|
mockConfig.outputPath = "/project/output/main.c";
|
|
287
287
|
|
|
288
|
-
mockTranspilerInstance.
|
|
288
|
+
mockTranspilerInstance.transpile.mockResolvedValue({
|
|
289
289
|
success: true,
|
|
290
290
|
outputFiles: ["/project/output/main.c"],
|
|
291
291
|
errors: [],
|
|
@@ -19,7 +19,7 @@ import parseWithSymbols from "../../lib/parseWithSymbols";
|
|
|
19
19
|
import parseCHeader from "../../lib/parseCHeader";
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
|
-
* Method handler type (async to support Transpiler.
|
|
22
|
+
* Method handler type (async to support Transpiler.transpile)
|
|
23
23
|
*/
|
|
24
24
|
type MethodHandler = (
|
|
25
25
|
params?: Record<string, unknown>,
|
|
@@ -286,17 +286,35 @@ class ServeCommand {
|
|
|
286
286
|
? { workingDir: dirname(filePath), sourcePath: filePath }
|
|
287
287
|
: undefined;
|
|
288
288
|
|
|
289
|
-
const
|
|
289
|
+
const transpileResult = await ServeCommand.transpiler.transpile({
|
|
290
|
+
kind: "source",
|
|
290
291
|
source,
|
|
291
|
-
options,
|
|
292
|
-
);
|
|
292
|
+
...options,
|
|
293
|
+
});
|
|
294
|
+
const fileResult =
|
|
295
|
+
transpileResult.files.find(
|
|
296
|
+
(f) => f.sourcePath === (filePath ?? "<string>"),
|
|
297
|
+
) ?? transpileResult.files[0];
|
|
298
|
+
|
|
299
|
+
// When files[] is empty (e.g., parse errors), fall back to aggregate errors
|
|
300
|
+
if (!fileResult) {
|
|
301
|
+
return {
|
|
302
|
+
success: true,
|
|
303
|
+
result: {
|
|
304
|
+
success: false,
|
|
305
|
+
code: "",
|
|
306
|
+
errors: transpileResult.errors,
|
|
307
|
+
cppDetected: ServeCommand.transpiler.isCppDetected(),
|
|
308
|
+
},
|
|
309
|
+
};
|
|
310
|
+
}
|
|
293
311
|
|
|
294
312
|
return {
|
|
295
313
|
success: true,
|
|
296
314
|
result: {
|
|
297
|
-
success:
|
|
298
|
-
code:
|
|
299
|
-
errors:
|
|
315
|
+
success: fileResult.success,
|
|
316
|
+
code: fileResult.code,
|
|
317
|
+
errors: fileResult.errors,
|
|
300
318
|
cppDetected: ServeCommand.transpiler.isCppDetected(),
|
|
301
319
|
},
|
|
302
320
|
};
|
|
@@ -312,12 +330,14 @@ class ServeCommand {
|
|
|
312
330
|
): Promise<IMethodResult> {
|
|
313
331
|
const { source, filePath } = params;
|
|
314
332
|
|
|
315
|
-
// If transpiler is initialized, run
|
|
333
|
+
// If transpiler is initialized, run transpile to trigger header
|
|
316
334
|
// resolution and C++ detection (results are discarded, we just want
|
|
317
335
|
// the side effects on the symbol table)
|
|
318
336
|
if (ServeCommand.transpiler && filePath) {
|
|
319
337
|
try {
|
|
320
|
-
await ServeCommand.transpiler.
|
|
338
|
+
await ServeCommand.transpiler.transpile({
|
|
339
|
+
kind: "source",
|
|
340
|
+
source,
|
|
321
341
|
workingDir: dirname(filePath),
|
|
322
342
|
sourcePath: filePath,
|
|
323
343
|
});
|
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
* Key insight from ADR-053: "A single file transpilation is just a project
|
|
6
6
|
* with one .cnx file."
|
|
7
7
|
*
|
|
8
|
-
* Architecture:
|
|
9
|
-
*
|
|
10
|
-
* for all transpilation
|
|
8
|
+
* Architecture: transpile() is the single entry point. It discovers files
|
|
9
|
+
* via discoverIncludes(), then delegates to _executePipeline(). There is
|
|
10
|
+
* ONE pipeline for all transpilation.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import { join, basename, dirname, resolve } from "node:path";
|
|
@@ -52,6 +52,7 @@ import ITranspilerResult from "./types/ITranspilerResult";
|
|
|
52
52
|
import IFileResult from "./types/IFileResult";
|
|
53
53
|
import IPipelineFile from "./types/IPipelineFile";
|
|
54
54
|
import IPipelineInput from "./types/IPipelineInput";
|
|
55
|
+
import TTranspileInput from "./types/TTranspileInput";
|
|
55
56
|
import ITranspileError from "../lib/types/ITranspileError";
|
|
56
57
|
import TranspilerState from "./state/TranspilerState";
|
|
57
58
|
import runAnalyzers from "./logic/analysis/runAnalyzers";
|
|
@@ -134,42 +135,33 @@ class Transpiler {
|
|
|
134
135
|
}
|
|
135
136
|
|
|
136
137
|
// ===========================================================================
|
|
137
|
-
// Public API
|
|
138
|
+
// Public API
|
|
138
139
|
// ===========================================================================
|
|
139
140
|
|
|
140
141
|
/**
|
|
141
|
-
*
|
|
142
|
+
* Unified entry point for all transpilation.
|
|
142
143
|
*
|
|
143
|
-
*
|
|
144
|
+
* @param input - What to transpile:
|
|
145
|
+
* - { kind: 'files' } — discover from config.inputs, write to disk
|
|
146
|
+
* - { kind: 'source', source, ... } — transpile in-memory source
|
|
147
|
+
* @returns ITranspilerResult with per-file results in .files[]
|
|
144
148
|
*/
|
|
145
|
-
async
|
|
149
|
+
async transpile(input: TTranspileInput): Promise<ITranspilerResult> {
|
|
146
150
|
const result = this._initResult();
|
|
147
151
|
|
|
148
152
|
try {
|
|
149
153
|
await this._initializeRun();
|
|
150
154
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
if (cnextFiles.length === 0) {
|
|
155
|
+
const pipelineInput = await this.discoverIncludes(input);
|
|
156
|
+
if (pipelineInput.cnextFiles.length === 0) {
|
|
154
157
|
return this._finalizeResult(result, "No C-Next source files found");
|
|
155
158
|
}
|
|
156
159
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
const pipelineFiles: IPipelineFile[] = cnextFiles.map((f) => ({
|
|
161
|
-
path: f.path,
|
|
162
|
-
discoveredFile: f,
|
|
163
|
-
}));
|
|
164
|
-
|
|
165
|
-
const input: IPipelineInput = {
|
|
166
|
-
cnextFiles: pipelineFiles,
|
|
167
|
-
headerFiles,
|
|
168
|
-
writeOutputToDisk: true,
|
|
169
|
-
};
|
|
170
|
-
|
|
171
|
-
await this._executePipeline(input, result);
|
|
160
|
+
if (input.kind === "files") {
|
|
161
|
+
this._ensureOutputDirectories();
|
|
162
|
+
}
|
|
172
163
|
|
|
164
|
+
await this._executePipeline(pipelineInput, result);
|
|
173
165
|
return await this._finalizeResult(result);
|
|
174
166
|
} catch (err) {
|
|
175
167
|
return this._handleRunError(result, err);
|
|
@@ -177,66 +169,27 @@ class Transpiler {
|
|
|
177
169
|
}
|
|
178
170
|
|
|
179
171
|
/**
|
|
180
|
-
*
|
|
172
|
+
* Stage 1: Discover files and build pipeline input.
|
|
181
173
|
*
|
|
182
|
-
*
|
|
183
|
-
*
|
|
174
|
+
* Branches on input kind:
|
|
175
|
+
* - 'files': filesystem scan, dependency graph, topological sort
|
|
176
|
+
* - 'source': parse in-memory string, walk include tree
|
|
184
177
|
*
|
|
185
|
-
*
|
|
186
|
-
*
|
|
187
|
-
* @returns Promise<IFileResult> with generated code or errors
|
|
178
|
+
* Header directive storage happens via IncludeResolver.resolve() for both
|
|
179
|
+
* C headers and cnext includes (Issue #854).
|
|
188
180
|
*/
|
|
189
|
-
async
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
sourcePath?: string;
|
|
195
|
-
},
|
|
196
|
-
): Promise<IFileResult> {
|
|
197
|
-
const workingDir = options?.workingDir ?? process.cwd();
|
|
198
|
-
const additionalIncludeDirs = options?.includeDirs ?? [];
|
|
199
|
-
const sourcePath = options?.sourcePath ?? "<string>";
|
|
200
|
-
|
|
201
|
-
try {
|
|
202
|
-
await this._initializeRun();
|
|
203
|
-
|
|
204
|
-
const input = this._discoverFromSource(
|
|
205
|
-
source,
|
|
206
|
-
workingDir,
|
|
207
|
-
additionalIncludeDirs,
|
|
208
|
-
sourcePath,
|
|
209
|
-
);
|
|
210
|
-
|
|
211
|
-
const result = this._initResult();
|
|
212
|
-
await this._executePipeline(input, result);
|
|
213
|
-
|
|
214
|
-
// Find our main file's result
|
|
215
|
-
const fileResult = result.files.find((f) => f.sourcePath === sourcePath);
|
|
216
|
-
if (fileResult) {
|
|
217
|
-
return fileResult;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
// No file result found — pipeline exited early (e.g., parse errors in Stage 3)
|
|
221
|
-
// Return pipeline errors as a file result
|
|
222
|
-
if (result.errors.length > 0) {
|
|
223
|
-
return this.buildErrorResult(sourcePath, result.errors, 0);
|
|
224
|
-
}
|
|
225
|
-
return this.buildErrorResult(
|
|
226
|
-
sourcePath,
|
|
227
|
-
[
|
|
228
|
-
{
|
|
229
|
-
line: 1,
|
|
230
|
-
column: 0,
|
|
231
|
-
message: "Pipeline produced no result for source file",
|
|
232
|
-
severity: "error",
|
|
233
|
-
},
|
|
234
|
-
],
|
|
235
|
-
0,
|
|
236
|
-
);
|
|
237
|
-
} catch (err) {
|
|
238
|
-
return this.buildCatchResult(sourcePath, err);
|
|
181
|
+
private async discoverIncludes(
|
|
182
|
+
input: TTranspileInput,
|
|
183
|
+
): Promise<IPipelineInput> {
|
|
184
|
+
if (input.kind === "files") {
|
|
185
|
+
return this._discoverFromFiles();
|
|
239
186
|
}
|
|
187
|
+
return this._discoverFromSource(
|
|
188
|
+
input.source,
|
|
189
|
+
input.workingDir ?? process.cwd(),
|
|
190
|
+
input.includeDirs ?? [],
|
|
191
|
+
input.sourcePath ?? "<string>",
|
|
192
|
+
);
|
|
240
193
|
}
|
|
241
194
|
|
|
242
195
|
// ===========================================================================
|
|
@@ -246,7 +199,7 @@ class Transpiler {
|
|
|
246
199
|
/**
|
|
247
200
|
* The single unified pipeline for all transpilation.
|
|
248
201
|
*
|
|
249
|
-
*
|
|
202
|
+
* transpile() delegates here after file discovery via discoverIncludes().
|
|
250
203
|
*
|
|
251
204
|
* Stage 2: Collect symbols from C/C++ headers (includes building analyzer context)
|
|
252
205
|
* Stage 3: Collect symbols from C-Next files
|
|
@@ -291,7 +244,7 @@ class Transpiler {
|
|
|
291
244
|
);
|
|
292
245
|
}
|
|
293
246
|
|
|
294
|
-
// Stage 6: Generate headers (only write to disk in
|
|
247
|
+
// Stage 6: Generate headers (only write to disk in files mode)
|
|
295
248
|
if (result.success && input.writeOutputToDisk) {
|
|
296
249
|
this._generateAllHeadersFromPipeline(input.cnextFiles, result);
|
|
297
250
|
}
|
|
@@ -395,17 +348,7 @@ class Transpiler {
|
|
|
395
348
|
return this.buildParseOnlyResult(sourcePath, declarationCount);
|
|
396
349
|
}
|
|
397
350
|
|
|
398
|
-
//
|
|
399
|
-
const analyzerErrors = runAnalyzers(tree, tokenStream);
|
|
400
|
-
if (analyzerErrors.length > 0) {
|
|
401
|
-
return this.buildErrorResult(
|
|
402
|
-
sourcePath,
|
|
403
|
-
analyzerErrors,
|
|
404
|
-
declarationCount,
|
|
405
|
-
);
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
// Build symbolInfo for code generation
|
|
351
|
+
// Build symbolInfo for code generation (before analyzers so they can read it)
|
|
409
352
|
const tSymbols = CNextResolver.resolve(tree, sourcePath);
|
|
410
353
|
let symbolInfo = TSymbolInfoAdapter.convert(tSymbols);
|
|
411
354
|
|
|
@@ -421,6 +364,19 @@ class Transpiler {
|
|
|
421
364
|
);
|
|
422
365
|
}
|
|
423
366
|
|
|
367
|
+
// Make symbols available to analyzers (CodeGenerator.generate() sets this too)
|
|
368
|
+
CodeGenState.symbols = symbolInfo;
|
|
369
|
+
|
|
370
|
+
// Run analyzers (reads symbols, externalStructFields, and symbolTable from CodeGenState)
|
|
371
|
+
const analyzerErrors = runAnalyzers(tree, tokenStream);
|
|
372
|
+
if (analyzerErrors.length > 0) {
|
|
373
|
+
return this.buildErrorResult(
|
|
374
|
+
sourcePath,
|
|
375
|
+
analyzerErrors,
|
|
376
|
+
declarationCount,
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
|
|
424
380
|
// Inject cross-file modification data for const inference
|
|
425
381
|
this._setupCrossFileModifications();
|
|
426
382
|
|
|
@@ -450,19 +406,8 @@ class Transpiler {
|
|
|
450
406
|
this._accumulateFileModifications();
|
|
451
407
|
}
|
|
452
408
|
|
|
453
|
-
//
|
|
454
|
-
const
|
|
455
|
-
CodeGenState.symbolTable.getTSymbolsByFile(sourcePath);
|
|
456
|
-
|
|
457
|
-
// Generate header content
|
|
458
|
-
const headerCode = this.generateHeaderContent(
|
|
459
|
-
fileSymbols,
|
|
460
|
-
sourcePath,
|
|
461
|
-
this.cppDetected,
|
|
462
|
-
userIncludes,
|
|
463
|
-
passByValueCopy,
|
|
464
|
-
symbolInfo,
|
|
465
|
-
);
|
|
409
|
+
// Generate header content (reads from state populated above)
|
|
410
|
+
const headerCode = this.generateHeaderForFile(file) ?? undefined;
|
|
466
411
|
|
|
467
412
|
return this.buildSuccessResult(
|
|
468
413
|
sourcePath,
|
|
@@ -549,6 +494,15 @@ class Transpiler {
|
|
|
549
494
|
}
|
|
550
495
|
}
|
|
551
496
|
|
|
497
|
+
// Issue #854: Store header directives for cnext includes
|
|
498
|
+
for (const cnxInclude of resolved.cnextIncludes) {
|
|
499
|
+
const includePath = resolve(cnxInclude.path);
|
|
500
|
+
const directive = resolved.headerIncludeDirectives.get(includePath);
|
|
501
|
+
if (directive) {
|
|
502
|
+
this.state.setHeaderDirective(includePath, directive);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
552
506
|
// Walk C-Next includes transitively to build include file list
|
|
553
507
|
const cnextIncludeFiles: IPipelineFile[] = [];
|
|
554
508
|
IncludeTreeWalker.walk(
|
|
@@ -619,6 +573,9 @@ class Transpiler {
|
|
|
619
573
|
CodeGenState.symbolTable.clear();
|
|
620
574
|
// Reset SymbolRegistry for new run (new IFunctionSymbol type system)
|
|
621
575
|
SymbolRegistry.reset();
|
|
576
|
+
// Reset callback-compatible functions for new run
|
|
577
|
+
// (populated by FunctionCallAnalyzer, persists through CodeGenState.reset())
|
|
578
|
+
CodeGenState.callbackCompatibleFunctions = new Set();
|
|
622
579
|
}
|
|
623
580
|
|
|
624
581
|
/**
|
|
@@ -721,8 +678,12 @@ class Transpiler {
|
|
|
721
678
|
if (file.symbolOnly) {
|
|
722
679
|
continue;
|
|
723
680
|
}
|
|
724
|
-
const
|
|
725
|
-
if (
|
|
681
|
+
const headerContent = this.generateHeaderForFile(file);
|
|
682
|
+
if (headerContent) {
|
|
683
|
+
const headerPath = this.pathResolver.getHeaderOutputPath(
|
|
684
|
+
file.discoveredFile,
|
|
685
|
+
);
|
|
686
|
+
this.fs.writeFile(headerPath, headerContent);
|
|
726
687
|
result.outputFiles.push(headerPath);
|
|
727
688
|
}
|
|
728
689
|
}
|
|
@@ -766,7 +727,7 @@ class Transpiler {
|
|
|
766
727
|
}
|
|
767
728
|
|
|
768
729
|
// ===========================================================================
|
|
769
|
-
//
|
|
730
|
+
// File Discovery (Stage 1 for files mode)
|
|
770
731
|
// ===========================================================================
|
|
771
732
|
|
|
772
733
|
/**
|
|
@@ -841,7 +802,10 @@ class Transpiler {
|
|
|
841
802
|
* Issue #580: Track dependencies for topological sorting
|
|
842
803
|
*/
|
|
843
804
|
private _processCnextIncludes(
|
|
844
|
-
resolved: {
|
|
805
|
+
resolved: {
|
|
806
|
+
cnextIncludes: IDiscoveredFile[];
|
|
807
|
+
headerIncludeDirectives: Map<string, string>;
|
|
808
|
+
},
|
|
845
809
|
cnxPath: string,
|
|
846
810
|
depGraph: DependencyGraph,
|
|
847
811
|
cnextFiles: IDiscoveredFile[],
|
|
@@ -857,6 +821,12 @@ class Transpiler {
|
|
|
857
821
|
|
|
858
822
|
depGraph.addDependency(cnxPath, includePath);
|
|
859
823
|
|
|
824
|
+
// Issue #854: Store header directive for cnext include types
|
|
825
|
+
const directive = resolved.headerIncludeDirectives.get(includePath);
|
|
826
|
+
if (directive) {
|
|
827
|
+
this.state.setHeaderDirective(includePath, directive);
|
|
828
|
+
}
|
|
829
|
+
|
|
860
830
|
// Don't add if already in the list
|
|
861
831
|
const alreadyExists =
|
|
862
832
|
cnextBaseNames.has(includeBaseName) ||
|
|
@@ -944,10 +914,7 @@ class Transpiler {
|
|
|
944
914
|
* This ensures headers are found based on what the source actually
|
|
945
915
|
* includes, not by blindly scanning include directories.
|
|
946
916
|
*/
|
|
947
|
-
private async
|
|
948
|
-
cnextFiles: IDiscoveredFile[];
|
|
949
|
-
headerFiles: IDiscoveredFile[];
|
|
950
|
-
}> {
|
|
917
|
+
private async _discoverFromFiles(): Promise<IPipelineInput> {
|
|
951
918
|
// Step 1: Discover C-Next files from inputs (files or directories)
|
|
952
919
|
const cnextFiles: IDiscoveredFile[] = [];
|
|
953
920
|
const fileByPath = new Map<string, IDiscoveredFile>();
|
|
@@ -957,7 +924,7 @@ class Transpiler {
|
|
|
957
924
|
}
|
|
958
925
|
|
|
959
926
|
if (cnextFiles.length === 0) {
|
|
960
|
-
return { cnextFiles: [], headerFiles: [] };
|
|
927
|
+
return { cnextFiles: [], headerFiles: [], writeOutputToDisk: true };
|
|
961
928
|
}
|
|
962
929
|
|
|
963
930
|
// Step 2: For each C-Next file, resolve its #include directives
|
|
@@ -981,7 +948,7 @@ class Transpiler {
|
|
|
981
948
|
// Issue #580: Sort files topologically for correct cross-file const inference
|
|
982
949
|
const sortedCnextFiles = this._sortFilesByDependency(depGraph, fileByPath);
|
|
983
950
|
|
|
984
|
-
// Resolve headers transitively
|
|
951
|
+
// Resolve headers transitively
|
|
985
952
|
const { headers: allHeaders, warnings: headerWarnings } =
|
|
986
953
|
IncludeResolver.resolveHeadersTransitively(
|
|
987
954
|
[...headerSet.values()],
|
|
@@ -996,9 +963,16 @@ class Transpiler {
|
|
|
996
963
|
);
|
|
997
964
|
this.warnings.push(...headerWarnings);
|
|
998
965
|
|
|
966
|
+
// Convert IDiscoveredFile[] to IPipelineFile[] (disk-based, all get code gen)
|
|
967
|
+
const pipelineFiles: IPipelineFile[] = sortedCnextFiles.map((f) => ({
|
|
968
|
+
path: f.path,
|
|
969
|
+
discoveredFile: f,
|
|
970
|
+
}));
|
|
971
|
+
|
|
999
972
|
return {
|
|
1000
|
-
cnextFiles:
|
|
973
|
+
cnextFiles: pipelineFiles,
|
|
1001
974
|
headerFiles: allHeaders,
|
|
975
|
+
writeOutputToDisk: true,
|
|
1002
976
|
};
|
|
1003
977
|
}
|
|
1004
978
|
|
|
@@ -1242,48 +1216,41 @@ class Transpiler {
|
|
|
1242
1216
|
* Stage 6: Generate header file for a C-Next file
|
|
1243
1217
|
* ADR-055 Phase 7: Uses TSymbol directly, converts to IHeaderSymbol for generation.
|
|
1244
1218
|
*/
|
|
1245
|
-
|
|
1246
|
-
|
|
1219
|
+
/**
|
|
1220
|
+
* Generate header content for a single file's exported symbols.
|
|
1221
|
+
* Unified method replacing both generateHeader() and generateHeaderContent().
|
|
1222
|
+
* Reads all needed data from state (populated during Stage 5).
|
|
1223
|
+
*/
|
|
1224
|
+
private generateHeaderForFile(file: IPipelineFile): string | null {
|
|
1225
|
+
const sourcePath = file.path;
|
|
1226
|
+
const tSymbols = CodeGenState.symbolTable.getTSymbolsByFile(sourcePath);
|
|
1247
1227
|
const exportedSymbols = tSymbols.filter((s) => s.isExported);
|
|
1248
1228
|
|
|
1249
1229
|
if (exportedSymbols.length === 0) {
|
|
1250
1230
|
return null;
|
|
1251
1231
|
}
|
|
1252
1232
|
|
|
1253
|
-
const headerName = basename(
|
|
1254
|
-
const headerPath = this.pathResolver.getHeaderOutputPath(file);
|
|
1255
|
-
|
|
1256
|
-
// Issue #220: Get SymbolCollector for full type definitions
|
|
1257
|
-
const typeInput = this.state.getSymbolInfo(file.path);
|
|
1233
|
+
const headerName = basename(sourcePath).replace(/\.cnx$|\.cnext$/, ".h");
|
|
1258
1234
|
|
|
1259
|
-
|
|
1260
|
-
// This uses the snapshot taken during transpilation, not the current (stale) codeGenerator state.
|
|
1261
|
-
// Fallback to empty map if not found (defensive - should always exist after transpilation).
|
|
1235
|
+
const typeInput = this.state.getSymbolInfo(sourcePath);
|
|
1262
1236
|
const passByValueParams =
|
|
1263
|
-
this.state.getPassByValueParams(
|
|
1237
|
+
this.state.getPassByValueParams(sourcePath) ??
|
|
1264
1238
|
new Map<string, Set<string>>();
|
|
1239
|
+
const userIncludes = this.state.getUserIncludes(sourcePath);
|
|
1265
1240
|
|
|
1266
|
-
// Issue #424: Get user includes for header generation
|
|
1267
|
-
const userIncludes = this.state.getUserIncludes(file.path);
|
|
1268
|
-
|
|
1269
|
-
// Issue #478, #588: Collect all known enum names from all files for cross-file type handling
|
|
1270
1241
|
const allKnownEnums = TransitiveEnumCollector.aggregateKnownEnums(
|
|
1271
1242
|
this.state.getAllSymbolInfo(),
|
|
1272
1243
|
);
|
|
1273
1244
|
|
|
1274
|
-
// Issue #497: Build mapping from external types to their C header includes
|
|
1275
1245
|
const externalTypeHeaders = ExternalTypeHeaderBuilder.build(
|
|
1276
1246
|
this.state.getAllHeaderDirectives(),
|
|
1277
1247
|
CodeGenState.symbolTable,
|
|
1278
1248
|
);
|
|
1279
1249
|
|
|
1280
|
-
// Issue #502: Include symbolTable in typeInput for C++ namespace type detection
|
|
1281
1250
|
const typeInputWithSymbolTable = typeInput
|
|
1282
1251
|
? { ...typeInput, symbolTable: CodeGenState.symbolTable }
|
|
1283
1252
|
: undefined;
|
|
1284
1253
|
|
|
1285
|
-
// ADR-055 Phase 7: Convert TSymbol to IHeaderSymbol with auto-const info
|
|
1286
|
-
// Issue #817: Apply auto-const info same as generateHeaderContent() does
|
|
1287
1254
|
const unmodifiedParams = this.codeGenerator.getFunctionUnmodifiedParams();
|
|
1288
1255
|
const headerSymbols = this.convertToHeaderSymbols(
|
|
1289
1256
|
exportedSymbols,
|
|
@@ -1291,7 +1258,7 @@ class Transpiler {
|
|
|
1291
1258
|
allKnownEnums,
|
|
1292
1259
|
);
|
|
1293
1260
|
|
|
1294
|
-
|
|
1261
|
+
return this.headerGenerator.generate(
|
|
1295
1262
|
headerSymbols,
|
|
1296
1263
|
headerName,
|
|
1297
1264
|
{
|
|
@@ -1304,9 +1271,6 @@ class Transpiler {
|
|
|
1304
1271
|
passByValueParams,
|
|
1305
1272
|
allKnownEnums,
|
|
1306
1273
|
);
|
|
1307
|
-
|
|
1308
|
-
this.fs.writeFile(headerPath, headerContent);
|
|
1309
|
-
return headerPath;
|
|
1310
1274
|
}
|
|
1311
1275
|
|
|
1312
1276
|
/**
|
|
@@ -1351,65 +1315,6 @@ class Transpiler {
|
|
|
1351
1315
|
}
|
|
1352
1316
|
}
|
|
1353
1317
|
|
|
1354
|
-
/**
|
|
1355
|
-
* Generate header content for exported symbols.
|
|
1356
|
-
* Issue #591: Extracted from transpileSource() for reduced complexity.
|
|
1357
|
-
* ADR-055 Phase 7: Works with TSymbol[], converts to IHeaderSymbol for generation.
|
|
1358
|
-
*/
|
|
1359
|
-
private generateHeaderContent(
|
|
1360
|
-
tSymbols: TSymbol[],
|
|
1361
|
-
sourcePath: string,
|
|
1362
|
-
cppMode: boolean,
|
|
1363
|
-
userIncludes: string[],
|
|
1364
|
-
passByValueParams: Map<string, Set<string>>,
|
|
1365
|
-
symbolInfo: ICodeGenSymbols,
|
|
1366
|
-
): string | undefined {
|
|
1367
|
-
const exportedSymbols = tSymbols.filter((s) => s.isExported);
|
|
1368
|
-
|
|
1369
|
-
if (exportedSymbols.length === 0) {
|
|
1370
|
-
return undefined;
|
|
1371
|
-
}
|
|
1372
|
-
|
|
1373
|
-
// Convert to IHeaderSymbol with auto-const info
|
|
1374
|
-
const unmodifiedParams = this.codeGenerator.getFunctionUnmodifiedParams();
|
|
1375
|
-
const headerSymbols = this.convertToHeaderSymbols(
|
|
1376
|
-
exportedSymbols,
|
|
1377
|
-
unmodifiedParams,
|
|
1378
|
-
symbolInfo.knownEnums,
|
|
1379
|
-
);
|
|
1380
|
-
|
|
1381
|
-
const headerName = basename(sourcePath).replace(/\.cnx$|\.cnext$/, ".h");
|
|
1382
|
-
|
|
1383
|
-
// Get type input from CodeGenState (for struct/enum definitions)
|
|
1384
|
-
const typeInput = CodeGenState.symbols;
|
|
1385
|
-
|
|
1386
|
-
// Issue #497: Build mapping from external types to their C header includes
|
|
1387
|
-
const externalTypeHeaders = ExternalTypeHeaderBuilder.build(
|
|
1388
|
-
this.state.getAllHeaderDirectives(),
|
|
1389
|
-
CodeGenState.symbolTable,
|
|
1390
|
-
);
|
|
1391
|
-
|
|
1392
|
-
// Issue #502: Include symbolTable in typeInput for C++ namespace type detection
|
|
1393
|
-
const typeInputWithSymbolTable = typeInput
|
|
1394
|
-
? { ...typeInput, symbolTable: CodeGenState.symbolTable }
|
|
1395
|
-
: undefined;
|
|
1396
|
-
|
|
1397
|
-
// Issue #478: Pass all known enums for cross-file type handling
|
|
1398
|
-
return this.headerGenerator.generate(
|
|
1399
|
-
headerSymbols,
|
|
1400
|
-
headerName,
|
|
1401
|
-
{
|
|
1402
|
-
exportedOnly: true,
|
|
1403
|
-
userIncludes,
|
|
1404
|
-
externalTypeHeaders,
|
|
1405
|
-
cppMode,
|
|
1406
|
-
},
|
|
1407
|
-
typeInputWithSymbolTable,
|
|
1408
|
-
passByValueParams,
|
|
1409
|
-
symbolInfo.knownEnums,
|
|
1410
|
-
);
|
|
1411
|
-
}
|
|
1412
|
-
|
|
1413
1318
|
/**
|
|
1414
1319
|
* Convert TSymbols to IHeaderSymbols with auto-const information applied.
|
|
1415
1320
|
* ADR-055 Phase 7: Replaces mutation-based auto-const updating.
|