c-next 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/README.md +726 -0
  2. package/bin/cnext.js +5 -0
  3. package/grammar/C.g4 +1112 -0
  4. package/grammar/CNext.g4 +817 -0
  5. package/grammar/CPP14Lexer.g4 +282 -0
  6. package/grammar/CPP14Parser.g4 +1072 -0
  7. package/package.json +85 -0
  8. package/src/analysis/DivisionByZeroAnalyzer.ts +378 -0
  9. package/src/analysis/FunctionCallAnalyzer.ts +526 -0
  10. package/src/analysis/InitializationAnalyzer.ts +725 -0
  11. package/src/analysis/NullCheckAnalyzer.ts +427 -0
  12. package/src/analysis/types/IDivisionByZeroError.ts +25 -0
  13. package/src/analysis/types/IFunctionCallError.ts +17 -0
  14. package/src/analysis/types/IInitializationError.ts +55 -0
  15. package/src/analysis/types/INullCheckError.ts +25 -0
  16. package/src/codegen/CodeGenerator.ts +7945 -0
  17. package/src/codegen/CommentExtractor.ts +240 -0
  18. package/src/codegen/CommentFormatter.ts +155 -0
  19. package/src/codegen/HeaderGenerator.ts +265 -0
  20. package/src/codegen/TypeResolver.ts +365 -0
  21. package/src/codegen/types/ECommentType.ts +10 -0
  22. package/src/codegen/types/IComment.ts +21 -0
  23. package/src/codegen/types/ICommentError.ts +15 -0
  24. package/src/codegen/types/TOverflowBehavior.ts +6 -0
  25. package/src/codegen/types/TParameterInfo.ts +13 -0
  26. package/src/codegen/types/TTypeConstants.ts +94 -0
  27. package/src/codegen/types/TTypeInfo.ts +22 -0
  28. package/src/index.ts +518 -0
  29. package/src/lib/IncludeDiscovery.ts +131 -0
  30. package/src/lib/InputExpansion.ts +121 -0
  31. package/src/lib/PlatformIODetector.ts +162 -0
  32. package/src/lib/transpiler.ts +439 -0
  33. package/src/lib/types/ITranspileResult.ts +80 -0
  34. package/src/parser/c/grammar/C.interp +338 -0
  35. package/src/parser/c/grammar/C.tokens +229 -0
  36. package/src/parser/c/grammar/CLexer.interp +415 -0
  37. package/src/parser/c/grammar/CLexer.tokens +229 -0
  38. package/src/parser/c/grammar/CLexer.ts +750 -0
  39. package/src/parser/c/grammar/CListener.ts +976 -0
  40. package/src/parser/c/grammar/CParser.ts +9663 -0
  41. package/src/parser/c/grammar/CVisitor.ts +626 -0
  42. package/src/parser/cpp/grammar/CPP14Lexer.interp +478 -0
  43. package/src/parser/cpp/grammar/CPP14Lexer.tokens +264 -0
  44. package/src/parser/cpp/grammar/CPP14Lexer.ts +848 -0
  45. package/src/parser/cpp/grammar/CPP14Parser.interp +492 -0
  46. package/src/parser/cpp/grammar/CPP14Parser.tokens +264 -0
  47. package/src/parser/cpp/grammar/CPP14Parser.ts +19961 -0
  48. package/src/parser/cpp/grammar/CPP14ParserListener.ts +2120 -0
  49. package/src/parser/cpp/grammar/CPP14ParserVisitor.ts +1354 -0
  50. package/src/parser/grammar/CNext.interp +340 -0
  51. package/src/parser/grammar/CNext.tokens +214 -0
  52. package/src/parser/grammar/CNextLexer.interp +374 -0
  53. package/src/parser/grammar/CNextLexer.tokens +214 -0
  54. package/src/parser/grammar/CNextLexer.ts +668 -0
  55. package/src/parser/grammar/CNextListener.ts +1020 -0
  56. package/src/parser/grammar/CNextParser.ts +9239 -0
  57. package/src/parser/grammar/CNextVisitor.ts +654 -0
  58. package/src/preprocessor/Preprocessor.ts +301 -0
  59. package/src/preprocessor/ToolchainDetector.ts +225 -0
  60. package/src/preprocessor/types/IPreprocessResult.ts +39 -0
  61. package/src/preprocessor/types/IToolchain.ts +27 -0
  62. package/src/project/FileDiscovery.ts +236 -0
  63. package/src/project/Project.ts +425 -0
  64. package/src/project/types/IProjectConfig.ts +64 -0
  65. package/src/symbols/CNextSymbolCollector.ts +326 -0
  66. package/src/symbols/CSymbolCollector.ts +457 -0
  67. package/src/symbols/CppSymbolCollector.ts +362 -0
  68. package/src/symbols/SymbolTable.ts +312 -0
  69. package/src/symbols/types/IConflict.ts +20 -0
  70. package/src/types/ESourceLanguage.ts +10 -0
  71. package/src/types/ESymbolKind.ts +20 -0
  72. package/src/types/ISymbol.ts +45 -0
@@ -0,0 +1,236 @@
1
+ /**
2
+ * File Discovery
3
+ * Scans directories for source files
4
+ */
5
+
6
+ import { readdirSync, statSync, existsSync } from "fs";
7
+ import { join, extname, resolve } from "path";
8
+
9
+ /**
10
+ * File types supported by the transpiler
11
+ */
12
+ enum EFileType {
13
+ CNext = "cnext",
14
+ CHeader = "c_header",
15
+ CppHeader = "cpp_header",
16
+ CSource = "c_source",
17
+ CppSource = "cpp_source",
18
+ Unknown = "unknown",
19
+ }
20
+
21
+ /**
22
+ * Discovered source file
23
+ */
24
+ interface IDiscoveredFile {
25
+ /** Absolute path to the file */
26
+ path: string;
27
+
28
+ /** File type */
29
+ type: EFileType;
30
+
31
+ /** File extension */
32
+ extension: string;
33
+ }
34
+
35
+ /**
36
+ * Options for file discovery
37
+ */
38
+ interface IDiscoveryOptions {
39
+ /** File extensions to include (default: all supported) */
40
+ extensions?: string[];
41
+
42
+ /** Whether to recurse into subdirectories */
43
+ recursive?: boolean;
44
+
45
+ /** Patterns to exclude */
46
+ excludePatterns?: RegExp[];
47
+ }
48
+
49
+ /**
50
+ * Default extensions for each file type
51
+ */
52
+ const EXTENSION_MAP: Record<string, EFileType> = {
53
+ ".cnx": EFileType.CNext,
54
+ ".cnext": EFileType.CNext,
55
+ ".h": EFileType.CHeader,
56
+ ".hpp": EFileType.CppHeader,
57
+ ".hxx": EFileType.CppHeader,
58
+ ".hh": EFileType.CppHeader,
59
+ ".c": EFileType.CSource,
60
+ ".cpp": EFileType.CppSource,
61
+ ".cxx": EFileType.CppSource,
62
+ ".cc": EFileType.CppSource,
63
+ };
64
+
65
+ /**
66
+ * Discovers source files in directories
67
+ */
68
+ class FileDiscovery {
69
+ /**
70
+ * Discover files in the given directories
71
+ */
72
+ static discover(
73
+ directories: string[],
74
+ options: IDiscoveryOptions = {},
75
+ ): IDiscoveredFile[] {
76
+ const files: IDiscoveredFile[] = [];
77
+ const recursive = options.recursive ?? true;
78
+ const excludePatterns = options.excludePatterns ?? [
79
+ /node_modules/,
80
+ /\.git/,
81
+ /\.build/,
82
+ /\.pio/,
83
+ ];
84
+
85
+ for (const dir of directories) {
86
+ const resolvedDir = resolve(dir);
87
+
88
+ if (!existsSync(resolvedDir)) {
89
+ console.warn(`Warning: Directory not found: ${dir}`);
90
+ continue;
91
+ }
92
+
93
+ this.scanDirectory(
94
+ resolvedDir,
95
+ files,
96
+ recursive,
97
+ options.extensions,
98
+ excludePatterns,
99
+ );
100
+ }
101
+
102
+ return files;
103
+ }
104
+
105
+ /**
106
+ * Discover a single file
107
+ */
108
+ static discoverFile(filePath: string): IDiscoveredFile | null {
109
+ const resolvedPath = resolve(filePath);
110
+
111
+ if (!existsSync(resolvedPath)) {
112
+ return null;
113
+ }
114
+
115
+ const ext = extname(resolvedPath).toLowerCase();
116
+ const type = EXTENSION_MAP[ext] ?? EFileType.Unknown;
117
+
118
+ return {
119
+ path: resolvedPath,
120
+ type,
121
+ extension: ext,
122
+ };
123
+ }
124
+
125
+ /**
126
+ * Discover multiple specific files
127
+ */
128
+ static discoverFiles(filePaths: string[]): IDiscoveredFile[] {
129
+ const files: IDiscoveredFile[] = [];
130
+
131
+ for (const filePath of filePaths) {
132
+ const file = this.discoverFile(filePath);
133
+ if (file) {
134
+ files.push(file);
135
+ } else {
136
+ console.warn(`Warning: File not found: ${filePath}`);
137
+ }
138
+ }
139
+
140
+ return files;
141
+ }
142
+
143
+ /**
144
+ * Filter discovered files by type
145
+ */
146
+ static filterByType(
147
+ files: IDiscoveredFile[],
148
+ type: EFileType,
149
+ ): IDiscoveredFile[] {
150
+ return files.filter((f) => f.type === type);
151
+ }
152
+
153
+ /**
154
+ * Get C-Next files from a list
155
+ */
156
+ static getCNextFiles(files: IDiscoveredFile[]): IDiscoveredFile[] {
157
+ return this.filterByType(files, EFileType.CNext);
158
+ }
159
+
160
+ /**
161
+ * Get C/C++ header files from a list
162
+ */
163
+ static getHeaderFiles(files: IDiscoveredFile[]): IDiscoveredFile[] {
164
+ return files.filter(
165
+ (f) => f.type === EFileType.CHeader || f.type === EFileType.CppHeader,
166
+ );
167
+ }
168
+
169
+ /**
170
+ * Scan a directory for source files
171
+ */
172
+ private static scanDirectory(
173
+ dir: string,
174
+ files: IDiscoveredFile[],
175
+ recursive: boolean,
176
+ extensions: string[] | undefined,
177
+ excludePatterns: RegExp[],
178
+ ): void {
179
+ let entries: string[];
180
+
181
+ try {
182
+ entries = readdirSync(dir);
183
+ } catch {
184
+ console.warn(`Warning: Cannot read directory: ${dir}`);
185
+ return;
186
+ }
187
+
188
+ for (const entry of entries) {
189
+ const fullPath = join(dir, entry);
190
+
191
+ // Check exclude patterns
192
+ if (excludePatterns.some((pattern) => pattern.test(fullPath))) {
193
+ continue;
194
+ }
195
+
196
+ let stats;
197
+ try {
198
+ stats = statSync(fullPath);
199
+ } catch {
200
+ continue;
201
+ }
202
+
203
+ if (stats.isDirectory()) {
204
+ if (recursive) {
205
+ this.scanDirectory(
206
+ fullPath,
207
+ files,
208
+ recursive,
209
+ extensions,
210
+ excludePatterns,
211
+ );
212
+ }
213
+ } else if (stats.isFile()) {
214
+ const ext = extname(fullPath).toLowerCase();
215
+
216
+ // Check extension filter
217
+ if (extensions && !extensions.includes(ext)) {
218
+ continue;
219
+ }
220
+
221
+ const type = EXTENSION_MAP[ext];
222
+ if (type) {
223
+ files.push({
224
+ path: fullPath,
225
+ type,
226
+ extension: ext,
227
+ });
228
+ }
229
+ }
230
+ }
231
+ }
232
+ }
233
+
234
+ export default FileDiscovery;
235
+ export { EFileType };
236
+ export type { IDiscoveredFile, IDiscoveryOptions };
@@ -0,0 +1,425 @@
1
+ /**
2
+ * Project
3
+ * Coordinates multi-file compilation with cross-language symbol resolution
4
+ */
5
+
6
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
7
+ import { CharStream, CommonTokenStream } from "antlr4ng";
8
+ import { join, basename, relative, dirname } from "path";
9
+
10
+ import { CNextLexer } from "../parser/grammar/CNextLexer";
11
+ import { CNextParser } from "../parser/grammar/CNextParser";
12
+ import { CLexer } from "../parser/c/grammar/CLexer";
13
+ import { CParser } from "../parser/c/grammar/CParser";
14
+ import { CPP14Lexer } from "../parser/cpp/grammar/CPP14Lexer";
15
+ import { CPP14Parser } from "../parser/cpp/grammar/CPP14Parser";
16
+
17
+ import CodeGenerator from "../codegen/CodeGenerator";
18
+ import HeaderGenerator from "../codegen/HeaderGenerator";
19
+ import SymbolTable from "../symbols/SymbolTable";
20
+ import CNextSymbolCollector from "../symbols/CNextSymbolCollector";
21
+ import CSymbolCollector from "../symbols/CSymbolCollector";
22
+ import CppSymbolCollector from "../symbols/CppSymbolCollector";
23
+ import Preprocessor from "../preprocessor/Preprocessor";
24
+
25
+ import IProjectConfig, { IProjectResult } from "./types/IProjectConfig";
26
+ import FileDiscovery, { EFileType, IDiscoveredFile } from "./FileDiscovery";
27
+
28
+ /**
29
+ * Manages multi-file C-Next projects
30
+ */
31
+ class Project {
32
+ private config: IProjectConfig;
33
+
34
+ private symbolTable: SymbolTable;
35
+
36
+ private preprocessor: Preprocessor;
37
+
38
+ private codeGenerator: CodeGenerator;
39
+
40
+ private headerGenerator: HeaderGenerator;
41
+
42
+ constructor(config: IProjectConfig) {
43
+ this.config = {
44
+ extensions: [".cnx", ".cnext"],
45
+ generateHeaders: true,
46
+ preprocess: true,
47
+ ...config,
48
+ };
49
+
50
+ this.symbolTable = new SymbolTable();
51
+ this.preprocessor = new Preprocessor();
52
+ this.codeGenerator = new CodeGenerator();
53
+ this.headerGenerator = new HeaderGenerator();
54
+ }
55
+
56
+ /**
57
+ * Compile the project
58
+ */
59
+ async compile(): Promise<IProjectResult> {
60
+ const result: IProjectResult = {
61
+ success: true,
62
+ filesProcessed: 0,
63
+ symbolsCollected: 0,
64
+ conflicts: [],
65
+ errors: [],
66
+ warnings: [],
67
+ outputFiles: [],
68
+ };
69
+
70
+ try {
71
+ // Discover files
72
+ const files = this.discoverFiles();
73
+
74
+ if (files.length === 0) {
75
+ result.warnings.push("No source files found");
76
+ return result;
77
+ }
78
+
79
+ // Ensure output directory exists
80
+ if (!existsSync(this.config.outDir)) {
81
+ mkdirSync(this.config.outDir, { recursive: true });
82
+ }
83
+
84
+ // Phase 1: Collect symbols from all C/C++ headers
85
+ const headerFiles = FileDiscovery.getHeaderFiles(files);
86
+ for (const file of headerFiles) {
87
+ try {
88
+ await this.collectHeaderSymbols(file, result);
89
+ result.filesProcessed++;
90
+ } catch (err) {
91
+ result.errors.push(`Failed to process ${file.path}: ${err}`);
92
+ }
93
+ }
94
+
95
+ // Phase 2: Collect symbols from C-Next files
96
+ const cnextFiles = FileDiscovery.getCNextFiles(files);
97
+ for (const file of cnextFiles) {
98
+ try {
99
+ this.collectCNextSymbols(file, result);
100
+ result.filesProcessed++;
101
+ } catch (err) {
102
+ result.errors.push(`Failed to process ${file.path}: ${err}`);
103
+ }
104
+ }
105
+
106
+ // Phase 3: Check for conflicts
107
+ const conflicts = this.symbolTable.getConflicts();
108
+ for (const conflict of conflicts) {
109
+ result.conflicts.push(conflict.message);
110
+ if (conflict.severity === "error") {
111
+ result.success = false;
112
+ }
113
+ }
114
+
115
+ // If there are errors, stop here
116
+ if (!result.success) {
117
+ result.errors.push(
118
+ "Symbol conflicts detected - cannot proceed with code generation",
119
+ );
120
+ return result;
121
+ }
122
+
123
+ // Phase 4: Generate C code for each C-Next file
124
+ for (const file of cnextFiles) {
125
+ try {
126
+ const outputFile = this.generateCode(file);
127
+ result.outputFiles.push(outputFile);
128
+ } catch (err) {
129
+ result.errors.push(
130
+ `Failed to generate code for ${file.path}: ${err}`,
131
+ );
132
+ result.success = false;
133
+ }
134
+ }
135
+
136
+ // Phase 5: Generate headers if enabled
137
+ if (this.config.generateHeaders && result.success) {
138
+ for (const file of cnextFiles) {
139
+ try {
140
+ const headerFile = this.generateHeader(file);
141
+ if (headerFile) {
142
+ result.outputFiles.push(headerFile);
143
+ }
144
+ } catch (err) {
145
+ result.warnings.push(
146
+ `Failed to generate header for ${file.path}: ${err}`,
147
+ );
148
+ }
149
+ }
150
+ }
151
+
152
+ result.symbolsCollected = this.symbolTable.size;
153
+ } catch (err) {
154
+ result.errors.push(`Project compilation failed: ${err}`);
155
+ result.success = false;
156
+ }
157
+
158
+ return result;
159
+ }
160
+
161
+ /**
162
+ * Discover all project files
163
+ */
164
+ private discoverFiles(): IDiscoveredFile[] {
165
+ // If specific files are provided, use those
166
+ if (this.config.files && this.config.files.length > 0) {
167
+ return FileDiscovery.discoverFiles(this.config.files);
168
+ }
169
+
170
+ // Otherwise, scan directories
171
+ const allDirs = [...this.config.srcDirs, ...this.config.includeDirs];
172
+ return FileDiscovery.discover(allDirs, {
173
+ extensions: this.config.extensions,
174
+ recursive: true,
175
+ });
176
+ }
177
+
178
+ /**
179
+ * Collect symbols from a C/C++ header file
180
+ */
181
+ private async collectHeaderSymbols(
182
+ file: IDiscoveredFile,
183
+ result: IProjectResult,
184
+ ): Promise<void> {
185
+ let content: string;
186
+
187
+ // Preprocess if enabled
188
+ if (this.config.preprocess && this.preprocessor.isAvailable()) {
189
+ const preprocessResult = await this.preprocessor.preprocess(file.path, {
190
+ includePaths: this.config.includeDirs,
191
+ defines: this.config.defines,
192
+ keepLineDirectives: false,
193
+ });
194
+
195
+ if (!preprocessResult.success) {
196
+ result.warnings.push(
197
+ `Preprocessor warning for ${file.path}: ${preprocessResult.error}`,
198
+ );
199
+ // Fall back to raw content
200
+ content = readFileSync(file.path, "utf-8");
201
+ } else {
202
+ content = preprocessResult.content;
203
+ }
204
+ } else {
205
+ content = readFileSync(file.path, "utf-8");
206
+ }
207
+
208
+ // Parse based on file type
209
+ if (file.type === EFileType.CHeader) {
210
+ this.parseCHeader(content, file.path);
211
+ } else if (file.type === EFileType.CppHeader) {
212
+ this.parseCppHeader(content, file.path);
213
+ }
214
+ }
215
+
216
+ /**
217
+ * Parse a C header and collect symbols
218
+ */
219
+ private parseCHeader(content: string, filePath: string): void {
220
+ const charStream = CharStream.fromString(content);
221
+ const lexer = new CLexer(charStream);
222
+ const tokenStream = new CommonTokenStream(lexer);
223
+ const parser = new CParser(tokenStream);
224
+
225
+ // Suppress error output for header parsing
226
+ parser.removeErrorListeners();
227
+
228
+ try {
229
+ const tree = parser.compilationUnit();
230
+ const collector = new CSymbolCollector(filePath, this.symbolTable);
231
+ const symbols = collector.collect(tree);
232
+ this.symbolTable.addSymbols(symbols);
233
+ } catch {
234
+ // Silently ignore parse errors in headers - they may have unsupported constructs
235
+ }
236
+ }
237
+
238
+ /**
239
+ * Parse a C++ header and collect symbols
240
+ */
241
+ private parseCppHeader(content: string, filePath: string): void {
242
+ const charStream = CharStream.fromString(content);
243
+ const lexer = new CPP14Lexer(charStream);
244
+ const tokenStream = new CommonTokenStream(lexer);
245
+ const parser = new CPP14Parser(tokenStream);
246
+
247
+ // Suppress error output for header parsing
248
+ parser.removeErrorListeners();
249
+
250
+ try {
251
+ const tree = parser.translationUnit();
252
+ const collector = new CppSymbolCollector(filePath);
253
+ const symbols = collector.collect(tree);
254
+ this.symbolTable.addSymbols(symbols);
255
+ } catch {
256
+ // Silently ignore parse errors in headers
257
+ }
258
+ }
259
+
260
+ /**
261
+ * Collect symbols from a C-Next file
262
+ */
263
+ private collectCNextSymbols(
264
+ file: IDiscoveredFile,
265
+ result: IProjectResult,
266
+ ): void {
267
+ const content = readFileSync(file.path, "utf-8");
268
+ const charStream = CharStream.fromString(content);
269
+ const lexer = new CNextLexer(charStream);
270
+ const tokenStream = new CommonTokenStream(lexer);
271
+ const parser = new CNextParser(tokenStream);
272
+
273
+ const errors: string[] = [];
274
+ parser.removeErrorListeners();
275
+ parser.addErrorListener({
276
+ syntaxError(
277
+ _recognizer,
278
+ _offendingSymbol,
279
+ line,
280
+ charPositionInLine,
281
+ msg,
282
+ _e,
283
+ ) {
284
+ errors.push(`${file.path}:${line}:${charPositionInLine} - ${msg}`);
285
+ },
286
+ reportAmbiguity() {},
287
+ reportAttemptingFullContext() {},
288
+ reportContextSensitivity() {},
289
+ });
290
+
291
+ const tree = parser.program();
292
+
293
+ if (errors.length > 0) {
294
+ for (const err of errors) {
295
+ result.errors.push(err);
296
+ }
297
+ return;
298
+ }
299
+
300
+ const collector = new CNextSymbolCollector(file.path);
301
+ const symbols = collector.collect(tree);
302
+ this.symbolTable.addSymbols(symbols);
303
+ }
304
+
305
+ /**
306
+ * Get output path for a file, preserving directory structure if from srcDirs
307
+ *
308
+ * @param file - The discovered file
309
+ * @returns Output path for the generated C file
310
+ */
311
+ private getOutputPath(file: IDiscoveredFile): string {
312
+ const outputName = basename(file.path).replace(/\.cnx$|\.cnext$/, ".c");
313
+
314
+ // If file is in a subdirectory of srcDir, preserve structure
315
+ for (const srcDir of this.config.srcDirs) {
316
+ const relativePath = relative(srcDir, file.path);
317
+ if (!relativePath.startsWith("..")) {
318
+ // File is under srcDir - preserve directory structure
319
+ const outputRelative = relativePath.replace(/\.cnx$|\.cnext$/, ".c");
320
+ const outputPath = join(this.config.outDir, outputRelative);
321
+
322
+ // Ensure output directory exists
323
+ const outputDir = dirname(outputPath);
324
+ if (!existsSync(outputDir)) {
325
+ mkdirSync(outputDir, { recursive: true });
326
+ }
327
+
328
+ return outputPath;
329
+ }
330
+ }
331
+
332
+ // Fallback: flat output (for files not in srcDirs)
333
+ return join(this.config.outDir, outputName);
334
+ }
335
+
336
+ /**
337
+ * Generate C code from a C-Next file
338
+ */
339
+ private generateCode(file: IDiscoveredFile): string {
340
+ const content = readFileSync(file.path, "utf-8");
341
+ const charStream = CharStream.fromString(content);
342
+ const lexer = new CNextLexer(charStream);
343
+ const tokenStream = new CommonTokenStream(lexer);
344
+ const parser = new CNextParser(tokenStream);
345
+
346
+ const tree = parser.program();
347
+ const code = this.codeGenerator.generate(tree, this.symbolTable);
348
+
349
+ // Write output file with directory structure preserved
350
+ const outputPath = this.getOutputPath(file);
351
+
352
+ writeFileSync(outputPath, code, "utf-8");
353
+
354
+ return outputPath;
355
+ }
356
+
357
+ /**
358
+ * Get output path for a header file, preserving directory structure if from srcDirs
359
+ *
360
+ * @param file - The discovered file
361
+ * @returns Output path for the generated header file
362
+ */
363
+ private getHeaderOutputPath(file: IDiscoveredFile): string {
364
+ const headerName = basename(file.path).replace(/\.cnx$|\.cnext$/, ".h");
365
+
366
+ // If file is in a subdirectory of srcDir, preserve structure
367
+ for (const srcDir of this.config.srcDirs) {
368
+ const relativePath = relative(srcDir, file.path);
369
+ if (!relativePath.startsWith("..")) {
370
+ // File is under srcDir - preserve directory structure
371
+ const outputRelative = relativePath.replace(/\.cnx$|\.cnext$/, ".h");
372
+ const outputPath = join(this.config.outDir, outputRelative);
373
+
374
+ // Ensure output directory exists
375
+ const outputDir = dirname(outputPath);
376
+ if (!existsSync(outputDir)) {
377
+ mkdirSync(outputDir, { recursive: true });
378
+ }
379
+
380
+ return outputPath;
381
+ }
382
+ }
383
+
384
+ // Fallback: flat output (for files not in srcDirs)
385
+ return join(this.config.outDir, headerName);
386
+ }
387
+
388
+ /**
389
+ * Generate header file for a C-Next file
390
+ */
391
+ private generateHeader(file: IDiscoveredFile): string | null {
392
+ const symbols = this.symbolTable.getSymbolsByFile(file.path);
393
+
394
+ // Filter to exported symbols only
395
+ const exportedSymbols = symbols.filter((s) => s.isExported);
396
+
397
+ if (exportedSymbols.length === 0) {
398
+ return null; // No public symbols, no header needed
399
+ }
400
+
401
+ const headerName = basename(file.path).replace(/\.cnx$|\.cnext$/, ".h");
402
+ const headerPath = this.getHeaderOutputPath(file);
403
+
404
+ const headerContent = this.headerGenerator.generate(
405
+ exportedSymbols,
406
+ headerName,
407
+ {
408
+ exportedOnly: true,
409
+ },
410
+ );
411
+
412
+ writeFileSync(headerPath, headerContent, "utf-8");
413
+
414
+ return headerPath;
415
+ }
416
+
417
+ /**
418
+ * Get the symbol table (for testing/inspection)
419
+ */
420
+ getSymbolTable(): SymbolTable {
421
+ return this.symbolTable;
422
+ }
423
+ }
424
+
425
+ export default Project;
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Project Configuration Types
3
+ */
4
+
5
+ /**
6
+ * Configuration for a C-Next project
7
+ */
8
+ interface IProjectConfig {
9
+ /** Project name */
10
+ name?: string;
11
+
12
+ /** Source directories to scan for .cnx files */
13
+ srcDirs: string[];
14
+
15
+ /** Include directories for C/C++ headers */
16
+ includeDirs: string[];
17
+
18
+ /** Output directory for generated files */
19
+ outDir: string;
20
+
21
+ /** Specific files to compile (overrides srcDirs) */
22
+ files?: string[];
23
+
24
+ /** File extensions to process (default: ['.cnx']) */
25
+ extensions?: string[];
26
+
27
+ /** Whether to generate header files */
28
+ generateHeaders?: boolean;
29
+
30
+ /** Whether to preprocess C/C++ files */
31
+ preprocess?: boolean;
32
+
33
+ /** Additional preprocessor defines */
34
+ defines?: Record<string, string | boolean>;
35
+ }
36
+
37
+ /**
38
+ * Result of compiling a project
39
+ */
40
+ interface IProjectResult {
41
+ /** Whether compilation succeeded */
42
+ success: boolean;
43
+
44
+ /** Number of files processed */
45
+ filesProcessed: number;
46
+
47
+ /** Number of symbols collected */
48
+ symbolsCollected: number;
49
+
50
+ /** Symbol conflicts detected */
51
+ conflicts: string[];
52
+
53
+ /** Errors encountered */
54
+ errors: string[];
55
+
56
+ /** Warnings */
57
+ warnings: string[];
58
+
59
+ /** Generated output files */
60
+ outputFiles: string[];
61
+ }
62
+
63
+ export default IProjectConfig;
64
+ export type { IProjectResult };