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,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;