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
package/src/index.ts ADDED
@@ -0,0 +1,518 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * C-Next Transpiler CLI
4
+ * A safer C for embedded systems development
5
+ */
6
+
7
+ import { transpile, ITranspileResult, ITranspileError } from "./lib/transpiler";
8
+ import { IncludeDiscovery } from "./lib/IncludeDiscovery";
9
+ import { InputExpansion } from "./lib/InputExpansion";
10
+ import Project from "./project/Project";
11
+ import {
12
+ readFileSync,
13
+ writeFileSync,
14
+ existsSync,
15
+ unlinkSync,
16
+ statSync,
17
+ } from "fs";
18
+ import { dirname, resolve } from "path";
19
+
20
+ // Read version from package.json to ensure consistency
21
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
22
+ const packageJson = require("../package.json");
23
+
24
+ /**
25
+ * C-Next configuration file options
26
+ */
27
+ interface ICNextConfig {
28
+ outputExtension?: ".c" | ".cpp";
29
+ generateHeaders?: boolean;
30
+ debugMode?: boolean;
31
+ target?: string; // ADR-049: Target platform (e.g., "teensy41", "cortex-m0")
32
+ }
33
+
34
+ /**
35
+ * Config file names in priority order (highest first)
36
+ */
37
+ const CONFIG_FILES = ["cnext.config.json", ".cnext.json", ".cnextrc"];
38
+
39
+ /**
40
+ * Load config from project directory, searching up the directory tree
41
+ */
42
+ function loadConfig(startDir: string): ICNextConfig {
43
+ let dir = resolve(startDir);
44
+
45
+ while (dir !== dirname(dir)) {
46
+ // Stop at filesystem root
47
+ for (const configFile of CONFIG_FILES) {
48
+ const configPath = resolve(dir, configFile);
49
+ if (existsSync(configPath)) {
50
+ try {
51
+ const content = readFileSync(configPath, "utf-8");
52
+ return JSON.parse(content) as ICNextConfig;
53
+ } catch (err) {
54
+ console.error(`Warning: Failed to parse ${configPath}`);
55
+ return {};
56
+ }
57
+ }
58
+ }
59
+ dir = dirname(dir);
60
+ }
61
+
62
+ return {}; // No config found
63
+ }
64
+
65
+ // Re-export library for backwards compatibility
66
+ export { transpile, ITranspileResult, ITranspileError };
67
+
68
+ // Read version dynamically from package.json
69
+ const VERSION = packageJson.version as string;
70
+
71
+ function showHelp(): void {
72
+ console.log(`C-Next Transpiler v${VERSION}`);
73
+ console.log("");
74
+ console.log("Usage:");
75
+ console.log(
76
+ " cnext <file.cnx> Single file (outputs file.c)",
77
+ );
78
+ console.log(
79
+ " cnext <file.cnx> -o <output.c> Single file with explicit output",
80
+ );
81
+ console.log(" cnext <files...> -o <dir> Multi-file mode");
82
+ console.log(" cnext --project <dir> [-o <outdir>] Project mode");
83
+ console.log("");
84
+ console.log("Options:");
85
+ console.log(
86
+ " -o <file|dir> Output file or directory (default: same dir as input)",
87
+ );
88
+ console.log(
89
+ " --cpp Output .cpp instead of .c (for C++ features like Serial)",
90
+ );
91
+ console.log(" --include <dir> Additional include directory (can repeat)");
92
+ console.log(" --verbose Show include path discovery");
93
+ console.log(" --parse Parse only, don't generate code");
94
+ console.log(
95
+ " --debug Generate panic-on-overflow helpers (ADR-044)",
96
+ );
97
+ console.log(
98
+ " --target <name> Target platform for atomic code gen (ADR-049)",
99
+ );
100
+ console.log(
101
+ " Options: teensy41, cortex-m7/m4/m3/m0+/m0, avr",
102
+ );
103
+ console.log(
104
+ " --exclude-headers Don't generate header files (default: generate)",
105
+ );
106
+ console.log(" --no-preprocess Don't run C preprocessor on headers");
107
+ console.log(" -D<name>[=value] Define preprocessor macro");
108
+ console.log(" --pio-install Setup PlatformIO integration");
109
+ console.log(" --pio-uninstall Remove PlatformIO integration");
110
+ console.log(" --version, -v Show version");
111
+ console.log(" --help, -h Show this help");
112
+ console.log("");
113
+ console.log("Examples:");
114
+ console.log(
115
+ " cnext main.cnx # Outputs main.c (same dir)",
116
+ );
117
+ console.log(
118
+ " cnext main.cnx -o build/main.c # Explicit output path",
119
+ );
120
+ console.log(
121
+ " cnext src/*.cnx -o build/ # Multiple files to directory",
122
+ );
123
+ console.log(
124
+ " cnext src/ # Compile all .cnx files in src/ (recursive)",
125
+ );
126
+ console.log("");
127
+ console.log("Config files (searched in order, JSON format):");
128
+ console.log(" cnext.config.json, .cnext.json, .cnextrc");
129
+ console.log("");
130
+ console.log("Config example:");
131
+ console.log(' { "outputExtension": ".cpp" }');
132
+ console.log("");
133
+ console.log("A safer C for embedded systems development.");
134
+ }
135
+
136
+ /**
137
+ * Print project compilation result
138
+ */
139
+ function printProjectResult(result: {
140
+ success: boolean;
141
+ filesProcessed: number;
142
+ symbolsCollected: number;
143
+ conflicts: string[];
144
+ errors: string[];
145
+ warnings: string[];
146
+ outputFiles: string[];
147
+ }): void {
148
+ // Print warnings
149
+ for (const warning of result.warnings) {
150
+ console.warn(`Warning: ${warning}`);
151
+ }
152
+
153
+ // Print conflicts
154
+ for (const conflict of result.conflicts) {
155
+ console.error(`Conflict: ${conflict}`);
156
+ }
157
+
158
+ // Print errors
159
+ for (const error of result.errors) {
160
+ console.error(`Error: ${error}`);
161
+ }
162
+
163
+ // Summary
164
+ if (result.success) {
165
+ console.log("");
166
+ console.log(`Compiled ${result.filesProcessed} files`);
167
+ console.log(`Collected ${result.symbolsCollected} symbols`);
168
+ console.log(`Generated ${result.outputFiles.length} output files:`);
169
+ for (const file of result.outputFiles) {
170
+ console.log(` ${file}`);
171
+ }
172
+ } else {
173
+ console.error("");
174
+ console.error("Compilation failed");
175
+ }
176
+ }
177
+
178
+ /**
179
+ * Unified mode - always use Project class with header discovery
180
+ */
181
+ async function runUnifiedMode(
182
+ inputs: string[],
183
+ outputPath: string,
184
+ includeDirs: string[],
185
+ defines: Record<string, string | boolean>,
186
+ generateHeaders: boolean,
187
+ preprocess: boolean,
188
+ verbose: boolean,
189
+ ): Promise<void> {
190
+ // Step 1: Expand directories to .cnx files
191
+ let files: string[];
192
+ try {
193
+ files = InputExpansion.expandInputs(inputs);
194
+ } catch (error) {
195
+ console.error(`Error: ${error}`);
196
+ process.exit(1);
197
+ }
198
+
199
+ if (files.length === 0) {
200
+ console.error("Error: No .cnx files found");
201
+ process.exit(1);
202
+ }
203
+
204
+ // Step 2: Auto-discover include paths from first file
205
+ const autoIncludePaths = IncludeDiscovery.discoverIncludePaths(files[0]);
206
+ const allIncludePaths = [...autoIncludePaths, ...includeDirs];
207
+
208
+ if (verbose) {
209
+ console.log("Include paths:");
210
+ for (const path of allIncludePaths) {
211
+ console.log(` ${path}`);
212
+ }
213
+ }
214
+
215
+ // Step 3: Determine output directory
216
+ let outDir: string;
217
+ if (outputPath) {
218
+ // User specified -o
219
+ const stats = existsSync(outputPath) ? statSync(outputPath) : null;
220
+ if (stats?.isDirectory() || outputPath.endsWith("/")) {
221
+ outDir = outputPath;
222
+ } else if (files.length === 1) {
223
+ // Single file + explicit file path -> use directory
224
+ outDir = dirname(outputPath);
225
+ } else {
226
+ outDir = outputPath;
227
+ }
228
+ } else {
229
+ // No -o flag: use same directory as first input file
230
+ outDir = dirname(files[0]);
231
+ }
232
+
233
+ // Step 4: Create Project
234
+ const project = new Project({
235
+ srcDirs: [], // No srcDirs, use explicit files
236
+ files,
237
+ includeDirs: allIncludePaths,
238
+ outDir,
239
+ generateHeaders,
240
+ preprocess,
241
+ defines,
242
+ });
243
+
244
+ // Step 5: Compile
245
+ const result = await project.compile();
246
+ printProjectResult(result);
247
+ process.exit(result.success ? 0 : 1);
248
+ }
249
+
250
+ /**
251
+ * Legacy compile function for backwards compatibility
252
+ * @deprecated Use transpile() from './lib/transpiler' instead
253
+ */
254
+ interface CompileResult {
255
+ errors: string[];
256
+ declarations: number;
257
+ code: string;
258
+ }
259
+
260
+ function compile(input: string, parseOnly: boolean = false): CompileResult {
261
+ const result = transpile(input, { parseOnly });
262
+ return {
263
+ errors: result.errors.map(
264
+ (e) => `Line ${e.line}:${e.column} - ${e.message}`,
265
+ ),
266
+ declarations: result.declarationCount,
267
+ code: result.code,
268
+ };
269
+ }
270
+
271
+ export { compile };
272
+
273
+ /**
274
+ * Setup PlatformIO integration
275
+ * Creates cnext_build.py and modifies platformio.ini
276
+ */
277
+ function setupPlatformIO(): void {
278
+ const pioIniPath = resolve(process.cwd(), "platformio.ini");
279
+ const scriptPath = resolve(process.cwd(), "cnext_build.py");
280
+
281
+ // Check if platformio.ini exists
282
+ if (!existsSync(pioIniPath)) {
283
+ console.error("Error: platformio.ini not found in current directory");
284
+ console.error("Run this command from your PlatformIO project root");
285
+ process.exit(1);
286
+ }
287
+
288
+ // Create cnext_build.py script
289
+ const buildScript = `Import("env")
290
+ import subprocess
291
+ from pathlib import Path
292
+
293
+ def transpile_cnext(source, target, env):
294
+ """Transpile all .cnx files before build"""
295
+ # Find all .cnx files in src directory
296
+ src_dir = Path("src")
297
+ if not src_dir.exists():
298
+ return
299
+
300
+ cnx_files = list(src_dir.rglob("*.cnx"))
301
+ if not cnx_files:
302
+ return
303
+
304
+ print(f"Transpiling {len(cnx_files)} c-next files...")
305
+
306
+ for cnx_file in cnx_files:
307
+ try:
308
+ result = subprocess.run(
309
+ ["cnext", str(cnx_file)],
310
+ check=True,
311
+ capture_output=True,
312
+ text=True
313
+ )
314
+ print(f" ✓ {cnx_file.name}")
315
+ except subprocess.CalledProcessError as e:
316
+ print(f" ✗ Error: {cnx_file.name}")
317
+ print(e.stderr)
318
+ env.Exit(1)
319
+
320
+ env.AddPreAction("buildprog", transpile_cnext)
321
+ `;
322
+
323
+ writeFileSync(scriptPath, buildScript, "utf-8");
324
+ console.log(`✓ Created: ${scriptPath}`);
325
+
326
+ // Read platformio.ini
327
+ let pioIni = readFileSync(pioIniPath, "utf-8");
328
+
329
+ // Check if extra_scripts is already present
330
+ if (pioIni.includes("cnext_build.py")) {
331
+ console.log("✓ PlatformIO already configured for c-next");
332
+ return;
333
+ }
334
+
335
+ // Add extra_scripts line to [env:*] section or create it
336
+ if (pioIni.includes("extra_scripts")) {
337
+ // Append to existing extra_scripts
338
+ pioIni = pioIni.replace(
339
+ /extra_scripts\s*=\s*(.+)/,
340
+ "extra_scripts = $1\n pre:cnext_build.py",
341
+ );
342
+ } else {
343
+ // Add new extra_scripts line after first [env:*] section
344
+ pioIni = pioIni.replace(
345
+ /(\[env:[^\]]+\])/,
346
+ "$1\nextra_scripts = pre:cnext_build.py",
347
+ );
348
+ }
349
+
350
+ writeFileSync(pioIniPath, pioIni, "utf-8");
351
+ console.log(`✓ Modified: ${pioIniPath}`);
352
+
353
+ console.log("");
354
+ console.log("✓ PlatformIO integration configured!");
355
+ console.log("");
356
+ console.log("Next steps:");
357
+ console.log(" 1. Create .cnx files in src/ (alongside your .c/.cpp files)");
358
+ console.log(" 2. Run: pio run");
359
+ console.log("");
360
+ console.log(
361
+ "The transpiler will automatically convert .cnx → .c before each build.",
362
+ );
363
+ console.log("Commit both .cnx and generated .c files to version control.");
364
+ }
365
+
366
+ /**
367
+ * Remove PlatformIO integration
368
+ * Deletes cnext_build.py and removes extra_scripts from platformio.ini
369
+ */
370
+ function uninstallPlatformIO(): void {
371
+ const pioIniPath = resolve(process.cwd(), "platformio.ini");
372
+ const scriptPath = resolve(process.cwd(), "cnext_build.py");
373
+
374
+ // Check if platformio.ini exists
375
+ if (!existsSync(pioIniPath)) {
376
+ console.error("Error: platformio.ini not found in current directory");
377
+ console.error("Run this command from your PlatformIO project root");
378
+ process.exit(1);
379
+ }
380
+
381
+ let hasChanges = false;
382
+
383
+ // Remove cnext_build.py if it exists
384
+ if (existsSync(scriptPath)) {
385
+ try {
386
+ unlinkSync(scriptPath);
387
+ console.log(`✓ Removed: ${scriptPath}`);
388
+ hasChanges = true;
389
+ } catch (err) {
390
+ console.error(`Error removing ${scriptPath}:`, err);
391
+ process.exit(1);
392
+ }
393
+ } else {
394
+ console.log("✓ cnext_build.py not found (already removed)");
395
+ }
396
+
397
+ // Read platformio.ini
398
+ let pioIni = readFileSync(pioIniPath, "utf-8");
399
+
400
+ // Check if extra_scripts includes cnext_build.py
401
+ if (pioIni.includes("cnext_build.py")) {
402
+ // Remove the cnext_build.py reference
403
+ // Handle both standalone and appended cases
404
+ pioIni = pioIni
405
+ // Remove standalone "extra_scripts = pre:cnext_build.py" line (with newline)
406
+ .replace(/^extra_scripts\s*=\s*pre:cnext_build\.py\s*\n/m, "")
407
+ // Remove from multi-line extra_scripts (e.g., " pre:cnext_build.py")
408
+ .replace(/\s+pre:cnext_build\.py/g, "")
409
+ // Clean up multiple consecutive blank lines
410
+ .replace(/\n\n\n+/g, "\n\n");
411
+
412
+ writeFileSync(pioIniPath, pioIni, "utf-8");
413
+ console.log(`✓ Modified: ${pioIniPath}`);
414
+ hasChanges = true;
415
+ } else {
416
+ console.log("✓ platformio.ini already clean (no c-next integration found)");
417
+ }
418
+
419
+ if (hasChanges) {
420
+ console.log("");
421
+ console.log("✓ PlatformIO integration removed!");
422
+ console.log("");
423
+ console.log("Your .cnx files remain untouched.");
424
+ console.log("To re-enable integration: cnext --pio-install");
425
+ } else {
426
+ console.log("");
427
+ console.log("No c-next integration found - nothing to remove.");
428
+ }
429
+ }
430
+
431
+ async function main(): Promise<void> {
432
+ const args = process.argv.slice(2);
433
+
434
+ if (args.length === 0 || args.includes("--help") || args.includes("-h")) {
435
+ showHelp();
436
+ process.exit(0);
437
+ }
438
+
439
+ if (args.includes("--version") || args.includes("-v")) {
440
+ console.log(`cnext v${VERSION}`);
441
+ process.exit(0);
442
+ }
443
+
444
+ if (args.includes("--pio-install")) {
445
+ setupPlatformIO();
446
+ process.exit(0);
447
+ }
448
+
449
+ if (args.includes("--pio-uninstall")) {
450
+ uninstallPlatformIO();
451
+ process.exit(0);
452
+ }
453
+
454
+ // Parse arguments (first pass to get input files for config lookup)
455
+ const inputFiles: string[] = [];
456
+ let outputPath = "";
457
+ const includeDirs: string[] = [];
458
+ const defines: Record<string, string | boolean> = {};
459
+ let cliGenerateHeaders: boolean | undefined;
460
+ let preprocess = true;
461
+ let verbose = false;
462
+
463
+ for (let i = 0; i < args.length; i++) {
464
+ const arg = args[i];
465
+
466
+ if (arg === "-o" && i + 1 < args.length) {
467
+ outputPath = args[++i];
468
+ } else if (arg === "--include" && i + 1 < args.length) {
469
+ includeDirs.push(args[++i]);
470
+ } else if (arg === "--verbose") {
471
+ verbose = true;
472
+ } else if (arg === "--exclude-headers") {
473
+ cliGenerateHeaders = false;
474
+ } else if (arg === "--no-preprocess") {
475
+ preprocess = false;
476
+ } else if (arg.startsWith("-D")) {
477
+ const define = arg.slice(2);
478
+ const eqIndex = define.indexOf("=");
479
+ if (eqIndex > 0) {
480
+ defines[define.slice(0, eqIndex)] = define.slice(eqIndex + 1);
481
+ } else {
482
+ defines[define] = true;
483
+ }
484
+ } else if (!arg.startsWith("-")) {
485
+ inputFiles.push(arg);
486
+ }
487
+ }
488
+
489
+ // Load config file (searches up from input file directory)
490
+ const configDir =
491
+ inputFiles.length > 0 ? dirname(resolve(inputFiles[0])) : process.cwd();
492
+ const config = loadConfig(configDir);
493
+
494
+ // Apply config defaults, CLI flags take precedence
495
+ const generateHeaders = cliGenerateHeaders ?? config.generateHeaders ?? true;
496
+
497
+ // Unified mode - always use Project class with header discovery
498
+ if (inputFiles.length === 0) {
499
+ console.error("Error: No input files specified");
500
+ showHelp();
501
+ process.exit(1);
502
+ }
503
+
504
+ await runUnifiedMode(
505
+ inputFiles,
506
+ outputPath,
507
+ includeDirs,
508
+ defines,
509
+ generateHeaders,
510
+ preprocess,
511
+ verbose,
512
+ );
513
+ }
514
+
515
+ main().catch((err) => {
516
+ console.error("Unexpected error:", err);
517
+ process.exit(1);
518
+ });
@@ -0,0 +1,131 @@
1
+ import { dirname, resolve, join, isAbsolute } from "path";
2
+ import { existsSync, statSync } from "fs";
3
+
4
+ /**
5
+ * Auto-discovery of include paths for C-Next compilation
6
+ *
7
+ * Implements 2-tier include path discovery:
8
+ * 1. File's own directory (for relative #include "header.h")
9
+ * 2. Project root (walk up to find platformio.ini, cnext.config.json, .git)
10
+ *
11
+ * Note: System paths (compiler defaults) not included to avoid dependencies.
12
+ * Users can add system paths via --include flag if needed.
13
+ */
14
+ export class IncludeDiscovery {
15
+ /**
16
+ * Discover include paths for a file
17
+ *
18
+ * @param inputFile - Path to .cnx file being compiled
19
+ * @returns Array of include directory paths
20
+ */
21
+ static discoverIncludePaths(inputFile: string): string[] {
22
+ const paths: string[] = [];
23
+
24
+ // Tier 1: File's own directory (highest priority)
25
+ const fileDir = dirname(resolve(inputFile));
26
+ paths.push(fileDir);
27
+
28
+ // Tier 2: Project root detection
29
+ const projectRoot = this.findProjectRoot(fileDir);
30
+ if (projectRoot) {
31
+ // Add common include directories if they exist
32
+ const commonDirs = ["include", "src", "lib"];
33
+ for (const dir of commonDirs) {
34
+ const includePath = join(projectRoot, dir);
35
+ if (existsSync(includePath) && statSync(includePath).isDirectory()) {
36
+ paths.push(includePath);
37
+ }
38
+ }
39
+ }
40
+
41
+ // Remove duplicates
42
+ return Array.from(new Set(paths));
43
+ }
44
+
45
+ /**
46
+ * Find project root by walking up directory tree looking for markers
47
+ *
48
+ * Project markers (in order of preference):
49
+ * - platformio.ini (PlatformIO project)
50
+ * - cnext.config.json or .cnext.json (C-Next config)
51
+ * - .git/ (Git repository root)
52
+ *
53
+ * @param startDir - Directory to start search from
54
+ * @returns Project root path or null if not found
55
+ */
56
+ static findProjectRoot(startDir: string): string | null {
57
+ let dir = resolve(startDir);
58
+
59
+ const markers = [
60
+ "platformio.ini",
61
+ "cnext.config.json",
62
+ ".cnext.json",
63
+ ".cnextrc",
64
+ ".git",
65
+ ];
66
+
67
+ // Walk up directory tree until marker found or filesystem root
68
+ while (dir !== dirname(dir)) {
69
+ for (const marker of markers) {
70
+ const markerPath = join(dir, marker);
71
+ if (existsSync(markerPath)) {
72
+ return dir;
73
+ }
74
+ }
75
+ dir = dirname(dir);
76
+ }
77
+
78
+ return null;
79
+ }
80
+
81
+ /**
82
+ * Extract #include directives from source code
83
+ *
84
+ * Matches both:
85
+ * - #include "header.h" (local includes)
86
+ * - #include <header.h> (system includes)
87
+ *
88
+ * @param content - Source file content
89
+ * @returns Array of include paths
90
+ */
91
+ static extractIncludes(content: string): string[] {
92
+ const includes: string[] = [];
93
+
94
+ // Match #include directives (both "..." and <...>)
95
+ const includeRegex = /^\s*#\s*include\s*[<"]([^>"]+)[>"]/gm;
96
+ let match;
97
+
98
+ while ((match = includeRegex.exec(content)) !== null) {
99
+ includes.push(match[1]);
100
+ }
101
+
102
+ return includes;
103
+ }
104
+
105
+ /**
106
+ * Resolve an include path using search directories
107
+ *
108
+ * @param includePath - The include path from #include directive
109
+ * @param searchPaths - Directories to search in
110
+ * @returns Resolved absolute path or null if not found
111
+ */
112
+ static resolveInclude(
113
+ includePath: string,
114
+ searchPaths: string[],
115
+ ): string | null {
116
+ // If already absolute, check if it exists
117
+ if (isAbsolute(includePath)) {
118
+ return existsSync(includePath) ? includePath : null;
119
+ }
120
+
121
+ // Search in each directory
122
+ for (const searchDir of searchPaths) {
123
+ const fullPath = join(searchDir, includePath);
124
+ if (existsSync(fullPath) && statSync(fullPath).isFile()) {
125
+ return fullPath;
126
+ }
127
+ }
128
+
129
+ return null;
130
+ }
131
+ }