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.
- package/README.md +726 -0
- package/bin/cnext.js +5 -0
- package/grammar/C.g4 +1112 -0
- package/grammar/CNext.g4 +817 -0
- package/grammar/CPP14Lexer.g4 +282 -0
- package/grammar/CPP14Parser.g4 +1072 -0
- package/package.json +85 -0
- package/src/analysis/DivisionByZeroAnalyzer.ts +378 -0
- package/src/analysis/FunctionCallAnalyzer.ts +526 -0
- package/src/analysis/InitializationAnalyzer.ts +725 -0
- package/src/analysis/NullCheckAnalyzer.ts +427 -0
- package/src/analysis/types/IDivisionByZeroError.ts +25 -0
- package/src/analysis/types/IFunctionCallError.ts +17 -0
- package/src/analysis/types/IInitializationError.ts +55 -0
- package/src/analysis/types/INullCheckError.ts +25 -0
- package/src/codegen/CodeGenerator.ts +7945 -0
- package/src/codegen/CommentExtractor.ts +240 -0
- package/src/codegen/CommentFormatter.ts +155 -0
- package/src/codegen/HeaderGenerator.ts +265 -0
- package/src/codegen/TypeResolver.ts +365 -0
- package/src/codegen/types/ECommentType.ts +10 -0
- package/src/codegen/types/IComment.ts +21 -0
- package/src/codegen/types/ICommentError.ts +15 -0
- package/src/codegen/types/TOverflowBehavior.ts +6 -0
- package/src/codegen/types/TParameterInfo.ts +13 -0
- package/src/codegen/types/TTypeConstants.ts +94 -0
- package/src/codegen/types/TTypeInfo.ts +22 -0
- package/src/index.ts +518 -0
- package/src/lib/IncludeDiscovery.ts +131 -0
- package/src/lib/InputExpansion.ts +121 -0
- package/src/lib/PlatformIODetector.ts +162 -0
- package/src/lib/transpiler.ts +439 -0
- package/src/lib/types/ITranspileResult.ts +80 -0
- package/src/parser/c/grammar/C.interp +338 -0
- package/src/parser/c/grammar/C.tokens +229 -0
- package/src/parser/c/grammar/CLexer.interp +415 -0
- package/src/parser/c/grammar/CLexer.tokens +229 -0
- package/src/parser/c/grammar/CLexer.ts +750 -0
- package/src/parser/c/grammar/CListener.ts +976 -0
- package/src/parser/c/grammar/CParser.ts +9663 -0
- package/src/parser/c/grammar/CVisitor.ts +626 -0
- package/src/parser/cpp/grammar/CPP14Lexer.interp +478 -0
- package/src/parser/cpp/grammar/CPP14Lexer.tokens +264 -0
- package/src/parser/cpp/grammar/CPP14Lexer.ts +848 -0
- package/src/parser/cpp/grammar/CPP14Parser.interp +492 -0
- package/src/parser/cpp/grammar/CPP14Parser.tokens +264 -0
- package/src/parser/cpp/grammar/CPP14Parser.ts +19961 -0
- package/src/parser/cpp/grammar/CPP14ParserListener.ts +2120 -0
- package/src/parser/cpp/grammar/CPP14ParserVisitor.ts +1354 -0
- package/src/parser/grammar/CNext.interp +340 -0
- package/src/parser/grammar/CNext.tokens +214 -0
- package/src/parser/grammar/CNextLexer.interp +374 -0
- package/src/parser/grammar/CNextLexer.tokens +214 -0
- package/src/parser/grammar/CNextLexer.ts +668 -0
- package/src/parser/grammar/CNextListener.ts +1020 -0
- package/src/parser/grammar/CNextParser.ts +9239 -0
- package/src/parser/grammar/CNextVisitor.ts +654 -0
- package/src/preprocessor/Preprocessor.ts +301 -0
- package/src/preprocessor/ToolchainDetector.ts +225 -0
- package/src/preprocessor/types/IPreprocessResult.ts +39 -0
- package/src/preprocessor/types/IToolchain.ts +27 -0
- package/src/project/FileDiscovery.ts +236 -0
- package/src/project/Project.ts +425 -0
- package/src/project/types/IProjectConfig.ts +64 -0
- package/src/symbols/CNextSymbolCollector.ts +326 -0
- package/src/symbols/CSymbolCollector.ts +457 -0
- package/src/symbols/CppSymbolCollector.ts +362 -0
- package/src/symbols/SymbolTable.ts +312 -0
- package/src/symbols/types/IConflict.ts +20 -0
- package/src/types/ESourceLanguage.ts +10 -0
- package/src/types/ESymbolKind.ts +20 -0
- 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
|
+
}
|