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
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* C/C++ Preprocessor
|
|
3
|
+
* Runs the system preprocessor on C/C++ files before parsing
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { exec } from "child_process";
|
|
7
|
+
import { promisify } from "util";
|
|
8
|
+
import { writeFile, mkdtemp, rm } from "fs/promises";
|
|
9
|
+
import { tmpdir } from "os";
|
|
10
|
+
import { join, basename, dirname } from "path";
|
|
11
|
+
import IToolchain from "./types/IToolchain";
|
|
12
|
+
import IPreprocessResult, { ISourceMapping } from "./types/IPreprocessResult";
|
|
13
|
+
import ToolchainDetector from "./ToolchainDetector";
|
|
14
|
+
|
|
15
|
+
const execAsync = promisify(exec);
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Preprocessor options
|
|
19
|
+
*/
|
|
20
|
+
interface IPreprocessOptions {
|
|
21
|
+
/** Additional include paths */
|
|
22
|
+
includePaths?: string[];
|
|
23
|
+
|
|
24
|
+
/** Preprocessor defines (-D flags) */
|
|
25
|
+
defines?: Record<string, string | boolean>;
|
|
26
|
+
|
|
27
|
+
/** Specific toolchain to use (auto-detect if not specified) */
|
|
28
|
+
toolchain?: IToolchain;
|
|
29
|
+
|
|
30
|
+
/** Keep #line directives for source mapping (default: true) */
|
|
31
|
+
keepLineDirectives?: boolean;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Handles preprocessing of C/C++ files
|
|
36
|
+
*/
|
|
37
|
+
class Preprocessor {
|
|
38
|
+
private toolchain: IToolchain | null;
|
|
39
|
+
|
|
40
|
+
private defaultIncludePaths: string[] = [];
|
|
41
|
+
|
|
42
|
+
constructor(toolchain?: IToolchain) {
|
|
43
|
+
this.toolchain = toolchain ?? ToolchainDetector.detect();
|
|
44
|
+
|
|
45
|
+
if (this.toolchain) {
|
|
46
|
+
this.defaultIncludePaths = ToolchainDetector.getDefaultIncludePaths(
|
|
47
|
+
this.toolchain,
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Check if a toolchain is available
|
|
54
|
+
*/
|
|
55
|
+
isAvailable(): boolean {
|
|
56
|
+
return this.toolchain !== null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Get the current toolchain
|
|
61
|
+
*/
|
|
62
|
+
getToolchain(): IToolchain | null {
|
|
63
|
+
return this.toolchain;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Preprocess a C/C++ file
|
|
68
|
+
*/
|
|
69
|
+
async preprocess(
|
|
70
|
+
filePath: string,
|
|
71
|
+
options: IPreprocessOptions = {},
|
|
72
|
+
): Promise<IPreprocessResult> {
|
|
73
|
+
if (!this.toolchain) {
|
|
74
|
+
return {
|
|
75
|
+
content: "",
|
|
76
|
+
sourceMappings: [],
|
|
77
|
+
success: false,
|
|
78
|
+
error:
|
|
79
|
+
"No C/C++ toolchain available. Install gcc, clang, or arm-none-eabi-gcc.",
|
|
80
|
+
originalFile: filePath,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
const content = await this.runPreprocessor(filePath, options);
|
|
86
|
+
const sourceMappings =
|
|
87
|
+
options.keepLineDirectives !== false
|
|
88
|
+
? this.parseLineDirectives(content)
|
|
89
|
+
: [];
|
|
90
|
+
|
|
91
|
+
// Optionally strip #line directives for cleaner output
|
|
92
|
+
const cleanContent =
|
|
93
|
+
options.keepLineDirectives === false
|
|
94
|
+
? this.stripLineDirectives(content)
|
|
95
|
+
: content;
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
content: cleanContent,
|
|
99
|
+
sourceMappings,
|
|
100
|
+
success: true,
|
|
101
|
+
originalFile: filePath,
|
|
102
|
+
toolchain: this.toolchain.name,
|
|
103
|
+
};
|
|
104
|
+
} catch (error) {
|
|
105
|
+
return {
|
|
106
|
+
content: "",
|
|
107
|
+
sourceMappings: [],
|
|
108
|
+
success: false,
|
|
109
|
+
error: error instanceof Error ? error.message : String(error),
|
|
110
|
+
originalFile: filePath,
|
|
111
|
+
toolchain: this.toolchain.name,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Preprocess content from a string (creates temp file)
|
|
118
|
+
*/
|
|
119
|
+
async preprocessString(
|
|
120
|
+
content: string,
|
|
121
|
+
filename: string,
|
|
122
|
+
options: IPreprocessOptions = {},
|
|
123
|
+
): Promise<IPreprocessResult> {
|
|
124
|
+
let tempDir: string | null = null;
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
// Create temp directory
|
|
128
|
+
tempDir = await mkdtemp(join(tmpdir(), "cnext-"));
|
|
129
|
+
const tempFile = join(tempDir, basename(filename));
|
|
130
|
+
|
|
131
|
+
// Write content to temp file
|
|
132
|
+
await writeFile(tempFile, content, "utf-8");
|
|
133
|
+
|
|
134
|
+
// Preprocess
|
|
135
|
+
const result = await this.preprocess(tempFile, options);
|
|
136
|
+
|
|
137
|
+
// Update the original file reference
|
|
138
|
+
result.originalFile = filename;
|
|
139
|
+
|
|
140
|
+
return result;
|
|
141
|
+
} finally {
|
|
142
|
+
// Clean up temp directory
|
|
143
|
+
if (tempDir) {
|
|
144
|
+
try {
|
|
145
|
+
await rm(tempDir, { recursive: true });
|
|
146
|
+
} catch {
|
|
147
|
+
// Ignore cleanup errors
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Run the preprocessor command
|
|
155
|
+
*/
|
|
156
|
+
private async runPreprocessor(
|
|
157
|
+
filePath: string,
|
|
158
|
+
options: IPreprocessOptions,
|
|
159
|
+
): Promise<string> {
|
|
160
|
+
const toolchain = options.toolchain ?? this.toolchain!;
|
|
161
|
+
const args: string[] = [
|
|
162
|
+
"-E", // Preprocess only
|
|
163
|
+
"-P", // Don't generate linemarkers (we'll add them back if needed)
|
|
164
|
+
];
|
|
165
|
+
|
|
166
|
+
// If we want line directives, don't use -P
|
|
167
|
+
if (options.keepLineDirectives !== false) {
|
|
168
|
+
args.pop(); // Remove -P
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Add include paths
|
|
172
|
+
const includePaths = [
|
|
173
|
+
...this.defaultIncludePaths,
|
|
174
|
+
...(options.includePaths ?? []),
|
|
175
|
+
dirname(filePath), // Include the file's directory
|
|
176
|
+
];
|
|
177
|
+
|
|
178
|
+
for (const path of includePaths) {
|
|
179
|
+
args.push(`-I${path}`);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Add defines
|
|
183
|
+
if (options.defines) {
|
|
184
|
+
for (const [key, value] of Object.entries(options.defines)) {
|
|
185
|
+
if (value === true) {
|
|
186
|
+
args.push(`-D${key}`);
|
|
187
|
+
} else if (value !== false) {
|
|
188
|
+
args.push(`-D${key}=${value}`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Add the input file
|
|
194
|
+
args.push(filePath);
|
|
195
|
+
|
|
196
|
+
// Build command
|
|
197
|
+
const command = `${toolchain.cpp} ${args.join(" ")}`;
|
|
198
|
+
|
|
199
|
+
try {
|
|
200
|
+
const { stdout, stderr } = await execAsync(command, {
|
|
201
|
+
maxBuffer: 50 * 1024 * 1024, // 50MB buffer for large headers
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
// Log warnings to console but don't fail
|
|
205
|
+
if (stderr && stderr.trim()) {
|
|
206
|
+
console.warn(`Preprocessor warnings for ${filePath}:\n${stderr}`);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return stdout;
|
|
210
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
211
|
+
} catch (error: any) {
|
|
212
|
+
// Include stderr in error message for better debugging
|
|
213
|
+
const stderr = error.stderr ?? "";
|
|
214
|
+
throw new Error(
|
|
215
|
+
`Preprocessor failed for ${filePath}:\n${error.message}\n${stderr}`,
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Parse #line directives to build source mappings
|
|
222
|
+
* Format: # linenum "filename" [flags]
|
|
223
|
+
*/
|
|
224
|
+
private parseLineDirectives(content: string): ISourceMapping[] {
|
|
225
|
+
const mappings: ISourceMapping[] = [];
|
|
226
|
+
const lines = content.split("\n");
|
|
227
|
+
|
|
228
|
+
let currentFile = "";
|
|
229
|
+
let currentOriginalLine = 1;
|
|
230
|
+
|
|
231
|
+
for (let i = 0; i < lines.length; i++) {
|
|
232
|
+
const line = lines[i];
|
|
233
|
+
|
|
234
|
+
// Match # linenum "filename" or #line linenum "filename"
|
|
235
|
+
const match = line.match(
|
|
236
|
+
/^#\s*(?:line\s+)?(\d+)\s+"([^"]+)"(?:\s+\d+)*\s*$/,
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
if (match) {
|
|
240
|
+
currentOriginalLine = parseInt(match[1], 10);
|
|
241
|
+
currentFile = match[2];
|
|
242
|
+
} else if (currentFile) {
|
|
243
|
+
mappings.push({
|
|
244
|
+
preprocessedLine: i + 1,
|
|
245
|
+
originalFile: currentFile,
|
|
246
|
+
originalLine: currentOriginalLine,
|
|
247
|
+
});
|
|
248
|
+
currentOriginalLine++;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return mappings;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Strip #line directives from preprocessed output
|
|
257
|
+
*/
|
|
258
|
+
private stripLineDirectives(content: string): string {
|
|
259
|
+
return content
|
|
260
|
+
.split("\n")
|
|
261
|
+
.filter((line) => !line.match(/^#\s*(?:line\s+)?\d+\s+"/))
|
|
262
|
+
.join("\n");
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Map a line in preprocessed output back to original source
|
|
267
|
+
*/
|
|
268
|
+
static mapToOriginal(
|
|
269
|
+
mappings: ISourceMapping[],
|
|
270
|
+
preprocessedLine: number,
|
|
271
|
+
): { file: string; line: number } | null {
|
|
272
|
+
// Find the mapping for this line or the closest previous one
|
|
273
|
+
let bestMapping: ISourceMapping | null = null;
|
|
274
|
+
|
|
275
|
+
for (const mapping of mappings) {
|
|
276
|
+
if (mapping.preprocessedLine <= preprocessedLine) {
|
|
277
|
+
if (
|
|
278
|
+
!bestMapping ||
|
|
279
|
+
mapping.preprocessedLine > bestMapping.preprocessedLine
|
|
280
|
+
) {
|
|
281
|
+
bestMapping = mapping;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if (!bestMapping) {
|
|
287
|
+
return null;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Calculate the offset from the mapping
|
|
291
|
+
const offset = preprocessedLine - bestMapping.preprocessedLine;
|
|
292
|
+
|
|
293
|
+
return {
|
|
294
|
+
file: bestMapping.originalFile,
|
|
295
|
+
line: bestMapping.originalLine + offset,
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
export default Preprocessor;
|
|
301
|
+
export type { IPreprocessOptions };
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Toolchain Detector
|
|
3
|
+
* Finds available C/C++ compilers on the system
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { execSync } from "child_process";
|
|
7
|
+
import { existsSync } from "fs";
|
|
8
|
+
import { join } from "path";
|
|
9
|
+
import IToolchain from "./types/IToolchain";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Detects available C/C++ toolchains
|
|
13
|
+
*/
|
|
14
|
+
class ToolchainDetector {
|
|
15
|
+
/**
|
|
16
|
+
* Detect the best available toolchain
|
|
17
|
+
* Priority: ARM cross-compiler > clang > gcc
|
|
18
|
+
*/
|
|
19
|
+
static detect(): IToolchain | null {
|
|
20
|
+
// Try ARM cross-compiler first (for embedded)
|
|
21
|
+
const arm = this.detectArmToolchain();
|
|
22
|
+
if (arm) return arm;
|
|
23
|
+
|
|
24
|
+
// Try clang
|
|
25
|
+
const clang = this.detectClang();
|
|
26
|
+
if (clang) return clang;
|
|
27
|
+
|
|
28
|
+
// Try gcc
|
|
29
|
+
const gcc = this.detectGcc();
|
|
30
|
+
if (gcc) return gcc;
|
|
31
|
+
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Detect all available toolchains
|
|
37
|
+
*/
|
|
38
|
+
static detectAll(): IToolchain[] {
|
|
39
|
+
const toolchains: IToolchain[] = [];
|
|
40
|
+
|
|
41
|
+
const arm = this.detectArmToolchain();
|
|
42
|
+
if (arm) toolchains.push(arm);
|
|
43
|
+
|
|
44
|
+
const clang = this.detectClang();
|
|
45
|
+
if (clang) toolchains.push(clang);
|
|
46
|
+
|
|
47
|
+
const gcc = this.detectGcc();
|
|
48
|
+
if (gcc) toolchains.push(gcc);
|
|
49
|
+
|
|
50
|
+
return toolchains;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Detect ARM cross-compiler (arm-none-eabi-gcc)
|
|
55
|
+
*/
|
|
56
|
+
private static detectArmToolchain(): IToolchain | null {
|
|
57
|
+
const cc = this.findExecutable("arm-none-eabi-gcc");
|
|
58
|
+
if (!cc) return null;
|
|
59
|
+
|
|
60
|
+
const cxx = this.findExecutable("arm-none-eabi-g++") ?? cc;
|
|
61
|
+
const version = this.getVersion(cc);
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
name: "arm-none-eabi-gcc",
|
|
65
|
+
cc,
|
|
66
|
+
cxx,
|
|
67
|
+
cpp: cc, // Use cc with -E flag
|
|
68
|
+
version,
|
|
69
|
+
isCrossCompiler: true,
|
|
70
|
+
target: "arm-none-eabi",
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Detect clang
|
|
76
|
+
*/
|
|
77
|
+
private static detectClang(): IToolchain | null {
|
|
78
|
+
const cc = this.findExecutable("clang");
|
|
79
|
+
if (!cc) return null;
|
|
80
|
+
|
|
81
|
+
const cxx = this.findExecutable("clang++") ?? cc;
|
|
82
|
+
const version = this.getVersion(cc);
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
name: "clang",
|
|
86
|
+
cc,
|
|
87
|
+
cxx,
|
|
88
|
+
cpp: cc,
|
|
89
|
+
version,
|
|
90
|
+
isCrossCompiler: false,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Detect GCC
|
|
96
|
+
*/
|
|
97
|
+
private static detectGcc(): IToolchain | null {
|
|
98
|
+
const cc = this.findExecutable("gcc");
|
|
99
|
+
if (!cc) return null;
|
|
100
|
+
|
|
101
|
+
const cxx = this.findExecutable("g++") ?? cc;
|
|
102
|
+
const version = this.getVersion(cc);
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
name: "gcc",
|
|
106
|
+
cc,
|
|
107
|
+
cxx,
|
|
108
|
+
cpp: cc,
|
|
109
|
+
version,
|
|
110
|
+
isCrossCompiler: false,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Find an executable in PATH
|
|
116
|
+
*/
|
|
117
|
+
private static findExecutable(name: string): string | null {
|
|
118
|
+
try {
|
|
119
|
+
const result = execSync(`which ${name}`, {
|
|
120
|
+
encoding: "utf-8",
|
|
121
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
122
|
+
}).trim();
|
|
123
|
+
|
|
124
|
+
if (result && existsSync(result)) {
|
|
125
|
+
return result;
|
|
126
|
+
}
|
|
127
|
+
} catch {
|
|
128
|
+
// Not found
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Get compiler version string
|
|
136
|
+
*/
|
|
137
|
+
private static getVersion(compiler: string): string | undefined {
|
|
138
|
+
try {
|
|
139
|
+
const result = execSync(`${compiler} --version`, {
|
|
140
|
+
encoding: "utf-8",
|
|
141
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// Extract first line which usually has version info
|
|
145
|
+
const firstLine = result.split("\n")[0];
|
|
146
|
+
return firstLine?.trim();
|
|
147
|
+
} catch {
|
|
148
|
+
return undefined;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Get default include paths for a toolchain
|
|
154
|
+
*/
|
|
155
|
+
static getDefaultIncludePaths(toolchain: IToolchain): string[] {
|
|
156
|
+
try {
|
|
157
|
+
// Ask the compiler for its default include paths
|
|
158
|
+
const result = execSync(`echo | ${toolchain.cc} -E -Wp,-v - 2>&1`, {
|
|
159
|
+
encoding: "utf-8",
|
|
160
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
const paths: string[] = [];
|
|
164
|
+
let inIncludeSection = false;
|
|
165
|
+
|
|
166
|
+
for (const line of result.split("\n")) {
|
|
167
|
+
if (line.includes("#include <...> search starts here:")) {
|
|
168
|
+
inIncludeSection = true;
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
if (line.includes("End of search list.")) {
|
|
172
|
+
break;
|
|
173
|
+
}
|
|
174
|
+
if (inIncludeSection && line.trim()) {
|
|
175
|
+
paths.push(line.trim());
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return paths;
|
|
180
|
+
} catch {
|
|
181
|
+
return [];
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Parse PlatformIO environment for include paths
|
|
187
|
+
* Looks for platformio.ini in project root
|
|
188
|
+
*/
|
|
189
|
+
static getPlatformIOIncludePaths(projectRoot: string): string[] {
|
|
190
|
+
const paths: string[] = [];
|
|
191
|
+
const pioIniPath = join(projectRoot, "platformio.ini");
|
|
192
|
+
|
|
193
|
+
if (!existsSync(pioIniPath)) {
|
|
194
|
+
return paths;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
try {
|
|
198
|
+
// Use pio to get the include paths
|
|
199
|
+
const result = execSync("pio project config --json-output", {
|
|
200
|
+
cwd: projectRoot,
|
|
201
|
+
encoding: "utf-8",
|
|
202
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
const config = JSON.parse(result);
|
|
206
|
+
|
|
207
|
+
// Extract include directories from build flags
|
|
208
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
209
|
+
for (const env of Object.values(config) as any[]) {
|
|
210
|
+
const buildFlags = env.build_flags ?? [];
|
|
211
|
+
for (const flag of buildFlags) {
|
|
212
|
+
if (flag.startsWith("-I")) {
|
|
213
|
+
paths.push(flag.slice(2));
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
} catch {
|
|
218
|
+
// PlatformIO not available or project not configured
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return paths;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export default ToolchainDetector;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Source location mapping from preprocessed output back to original file
|
|
3
|
+
*/
|
|
4
|
+
interface ISourceMapping {
|
|
5
|
+
/** Line number in preprocessed output */
|
|
6
|
+
preprocessedLine: number;
|
|
7
|
+
|
|
8
|
+
/** Original file path */
|
|
9
|
+
originalFile: string;
|
|
10
|
+
|
|
11
|
+
/** Line number in original file */
|
|
12
|
+
originalLine: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Result of preprocessing a C/C++ file
|
|
17
|
+
*/
|
|
18
|
+
interface IPreprocessResult {
|
|
19
|
+
/** Preprocessed content */
|
|
20
|
+
content: string;
|
|
21
|
+
|
|
22
|
+
/** Source mappings from #line directives */
|
|
23
|
+
sourceMappings: ISourceMapping[];
|
|
24
|
+
|
|
25
|
+
/** Whether preprocessing succeeded */
|
|
26
|
+
success: boolean;
|
|
27
|
+
|
|
28
|
+
/** Error message if failed */
|
|
29
|
+
error?: string;
|
|
30
|
+
|
|
31
|
+
/** Original file that was preprocessed */
|
|
32
|
+
originalFile: string;
|
|
33
|
+
|
|
34
|
+
/** Toolchain used for preprocessing */
|
|
35
|
+
toolchain?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export default IPreprocessResult;
|
|
39
|
+
export type { ISourceMapping };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Represents a detected C/C++ toolchain
|
|
3
|
+
*/
|
|
4
|
+
interface IToolchain {
|
|
5
|
+
/** Name of the toolchain (e.g., "gcc", "clang", "arm-none-eabi-gcc") */
|
|
6
|
+
name: string;
|
|
7
|
+
|
|
8
|
+
/** Path to the C compiler */
|
|
9
|
+
cc: string;
|
|
10
|
+
|
|
11
|
+
/** Path to the C++ compiler */
|
|
12
|
+
cxx: string;
|
|
13
|
+
|
|
14
|
+
/** Path to the preprocessor (usually same as cc with -E flag) */
|
|
15
|
+
cpp: string;
|
|
16
|
+
|
|
17
|
+
/** Toolchain version string */
|
|
18
|
+
version?: string;
|
|
19
|
+
|
|
20
|
+
/** Whether this is a cross-compiler (e.g., for ARM) */
|
|
21
|
+
isCrossCompiler: boolean;
|
|
22
|
+
|
|
23
|
+
/** Target triple if cross-compiling (e.g., "arm-none-eabi") */
|
|
24
|
+
target?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export default IToolchain;
|