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,121 @@
1
+ import { resolve, extname, basename } from "path";
2
+ import { existsSync, statSync, readdirSync } from "fs";
3
+
4
+ /**
5
+ * Input expansion for C-Next CLI
6
+ *
7
+ * Expands file paths and directories into a list of .cnx files to compile.
8
+ * Handles recursive directory scanning and file validation.
9
+ */
10
+ export class InputExpansion {
11
+ /**
12
+ * Expand inputs (files or directories) to list of .cnx files
13
+ *
14
+ * @param inputs - Array of file paths or directories
15
+ * @returns Array of .cnx file paths
16
+ */
17
+ static expandInputs(inputs: string[]): string[] {
18
+ const files: string[] = [];
19
+
20
+ for (const input of inputs) {
21
+ const resolvedPath = resolve(input);
22
+
23
+ if (!existsSync(resolvedPath)) {
24
+ throw new Error(`Input not found: ${input}`);
25
+ }
26
+
27
+ const stats = statSync(resolvedPath);
28
+
29
+ if (stats.isDirectory()) {
30
+ // Recursively find .cnx files
31
+ const cnextFiles = this.findCNextFiles(resolvedPath);
32
+ files.push(...cnextFiles);
33
+ } else if (stats.isFile()) {
34
+ // Validate and add file
35
+ this.validateFileExtension(resolvedPath);
36
+ files.push(resolvedPath);
37
+ }
38
+ }
39
+
40
+ // Remove duplicates
41
+ return Array.from(new Set(files));
42
+ }
43
+
44
+ /**
45
+ * Recursively find .cnx files in directory
46
+ *
47
+ * @param dir - Directory to scan
48
+ * @returns Array of .cnx file paths
49
+ */
50
+ static findCNextFiles(dir: string): string[] {
51
+ const files: string[] = [];
52
+
53
+ try {
54
+ const entries = readdirSync(dir, { withFileTypes: true });
55
+
56
+ for (const entry of entries) {
57
+ const fullPath = resolve(dir, entry.name);
58
+
59
+ // Skip hidden directories and common build/dependency directories
60
+ if (entry.isDirectory()) {
61
+ const dirName = entry.name;
62
+ if (
63
+ dirName.startsWith(".") ||
64
+ dirName === "node_modules" ||
65
+ dirName === "build" ||
66
+ dirName === ".pio" ||
67
+ dirName === "dist"
68
+ ) {
69
+ continue;
70
+ }
71
+
72
+ // Recursively scan subdirectory
73
+ const subFiles = this.findCNextFiles(fullPath);
74
+ files.push(...subFiles);
75
+ } else if (entry.isFile()) {
76
+ const ext = extname(entry.name);
77
+ if (ext === ".cnx" || ext === ".cnext") {
78
+ files.push(fullPath);
79
+ }
80
+ }
81
+ }
82
+ } catch (error) {
83
+ throw new Error(`Failed to scan directory ${dir}: ${error}`);
84
+ }
85
+
86
+ return files;
87
+ }
88
+
89
+ /**
90
+ * Validate file extension
91
+ *
92
+ * Accepts: .cnx, .cnext
93
+ * Rejects: .c, .cpp, .cc, .cxx, .c++ (implementation files)
94
+ *
95
+ * @param path - File path to validate
96
+ * @throws Error if extension is invalid
97
+ */
98
+ static validateFileExtension(path: string): void {
99
+ const ext = extname(path);
100
+ const fileName = basename(path);
101
+
102
+ // Reject implementation files
103
+ const rejectedExtensions = [".c", ".cpp", ".cc", ".cxx", ".c++"];
104
+ if (rejectedExtensions.includes(ext)) {
105
+ throw new Error(
106
+ `Cannot process implementation file '${fileName}'. ` +
107
+ `C-Next only compiles .cnx files. ` +
108
+ `If you need to include this file, create a header (.h) instead.`,
109
+ );
110
+ }
111
+
112
+ // Accept C-Next source files
113
+ const acceptedExtensions = [".cnx", ".cnext"];
114
+ if (!acceptedExtensions.includes(ext)) {
115
+ throw new Error(
116
+ `Invalid file extension '${ext}' for file '${fileName}'. ` +
117
+ `C-Next only accepts .cnx or .cnext files.`,
118
+ );
119
+ }
120
+ }
121
+ }
@@ -0,0 +1,162 @@
1
+ /**
2
+ * PlatformIO Configuration Detector
3
+ * ADR-049: Auto-detect target platform from platformio.ini
4
+ */
5
+
6
+ import { existsSync, readFileSync } from "fs";
7
+ import { dirname, resolve } from "path";
8
+
9
+ /**
10
+ * PlatformIO board to C-Next target mapping
11
+ * Maps common board names to their Cortex-M core types
12
+ */
13
+ const BOARD_TO_TARGET: Record<string, string> = {
14
+ // Teensy boards (NXP i.MX RT)
15
+ teensy41: "teensy41",
16
+ teensy40: "teensy40",
17
+ teensy36: "cortex-m4", // Kinetis K66
18
+ teensy35: "cortex-m4", // Kinetis K64
19
+ teensy32: "cortex-m4", // Kinetis MK20
20
+ teensy31: "cortex-m4", // Kinetis MK20
21
+ teensylc: "cortex-m0+", // Kinetis KL26
22
+
23
+ // STM32 F7 series (Cortex-M7)
24
+ nucleo_f767zi: "cortex-m7",
25
+ disco_f769ni: "cortex-m7",
26
+
27
+ // STM32 F4 series (Cortex-M4)
28
+ nucleo_f446re: "cortex-m4",
29
+ disco_f407vg: "cortex-m4",
30
+ black_f407ve: "cortex-m4",
31
+
32
+ // STM32 F3 series (Cortex-M4)
33
+ nucleo_f303re: "cortex-m4",
34
+
35
+ // STM32 F1 series (Cortex-M3)
36
+ bluepill_f103c8: "cortex-m3",
37
+ nucleo_f103rb: "cortex-m3",
38
+
39
+ // STM32 F0/L0 series (Cortex-M0)
40
+ nucleo_f030r8: "cortex-m0",
41
+ nucleo_l011k4: "cortex-m0+",
42
+
43
+ // Arduino boards (AVR)
44
+ uno: "avr",
45
+ mega: "avr",
46
+ nano: "avr",
47
+ leonardo: "avr",
48
+
49
+ // Arduino ARM boards
50
+ due: "cortex-m3", // SAM3X8E
51
+ zero: "cortex-m0+", // SAMD21
52
+ mkrzero: "cortex-m0+", // SAMD21
53
+
54
+ // ESP32 (Xtensa - no LDREX/STREX)
55
+ esp32dev: "esp32",
56
+ esp32: "esp32",
57
+
58
+ // RP2040 (Cortex-M0+)
59
+ pico: "cortex-m0+",
60
+ rpipico: "cortex-m0+",
61
+ };
62
+
63
+ /**
64
+ * Parse a platformio.ini file and extract configuration
65
+ */
66
+ function parsePlatformIOConfig(content: string): Record<string, string> {
67
+ const result: Record<string, string> = {};
68
+ let currentSection = "";
69
+
70
+ for (const line of content.split("\n")) {
71
+ const trimmed = line.trim();
72
+
73
+ // Skip comments and empty lines
74
+ if (trimmed.startsWith(";") || trimmed.startsWith("#") || !trimmed) {
75
+ continue;
76
+ }
77
+
78
+ // Section header
79
+ const sectionMatch = trimmed.match(/^\[([^\]]+)\]$/);
80
+ if (sectionMatch) {
81
+ currentSection = sectionMatch[1];
82
+ continue;
83
+ }
84
+
85
+ // Key = value pair
86
+ const kvMatch = trimmed.match(/^(\w+)\s*=\s*(.+)$/);
87
+ if (kvMatch && currentSection) {
88
+ const key = `${currentSection}.${kvMatch[1]}`;
89
+ result[key] = kvMatch[2].trim();
90
+ }
91
+ }
92
+
93
+ return result;
94
+ }
95
+
96
+ /**
97
+ * Search for platformio.ini starting from a directory and going up
98
+ */
99
+ function findPlatformIOConfig(startDir: string): string | null {
100
+ let dir = resolve(startDir);
101
+
102
+ while (dir !== dirname(dir)) {
103
+ const configPath = resolve(dir, "platformio.ini");
104
+ if (existsSync(configPath)) {
105
+ return configPath;
106
+ }
107
+ dir = dirname(dir);
108
+ }
109
+
110
+ return null;
111
+ }
112
+
113
+ /**
114
+ * Detect target platform from platformio.ini
115
+ *
116
+ * @param startDir - Directory to start searching from (usually input file's directory)
117
+ * @returns Target name if detected, undefined otherwise
118
+ *
119
+ * @example
120
+ * ```typescript
121
+ * const target = detectPlatformIOTarget('/home/user/project/src');
122
+ * // Returns "teensy41" if platformio.ini has board = teensy41
123
+ * ```
124
+ */
125
+ export function detectPlatformIOTarget(startDir: string): string | undefined {
126
+ const configPath = findPlatformIOConfig(startDir);
127
+ if (!configPath) {
128
+ return undefined;
129
+ }
130
+
131
+ try {
132
+ const content = readFileSync(configPath, "utf-8");
133
+ const config = parsePlatformIOConfig(content);
134
+
135
+ // Check for board in [env] or [env:*] sections
136
+ // Priority: [env:default] > [env] > first [env:*]
137
+ const envDefault = config["env:default.board"];
138
+ const env = config["env.board"];
139
+
140
+ // Find first env:* board if no default
141
+ let firstEnvBoard: string | undefined;
142
+ for (const key of Object.keys(config)) {
143
+ if (key.startsWith("env:") && key.endsWith(".board")) {
144
+ firstEnvBoard = config[key];
145
+ break;
146
+ }
147
+ }
148
+
149
+ const board = envDefault || env || firstEnvBoard;
150
+ if (!board) {
151
+ return undefined;
152
+ }
153
+
154
+ // Map board to target
155
+ const target = BOARD_TO_TARGET[board.toLowerCase()];
156
+ return target;
157
+ } catch {
158
+ return undefined;
159
+ }
160
+ }
161
+
162
+ export default detectPlatformIOTarget;
@@ -0,0 +1,439 @@
1
+ /**
2
+ * C-Next Transpiler Library
3
+ * Core transpilation API for use by CLI and VS Code extension
4
+ */
5
+
6
+ import { CharStream, CommonTokenStream } from "antlr4ng";
7
+ import { CNextLexer } from "../parser/grammar/CNextLexer";
8
+ import { CNextParser } from "../parser/grammar/CNextParser";
9
+ import CodeGenerator from "../codegen/CodeGenerator";
10
+ import CommentExtractor from "../codegen/CommentExtractor";
11
+ import CNextSymbolCollector from "../symbols/CNextSymbolCollector";
12
+ import ESymbolKind from "../types/ESymbolKind";
13
+ import InitializationAnalyzer from "../analysis/InitializationAnalyzer";
14
+ import FunctionCallAnalyzer from "../analysis/FunctionCallAnalyzer";
15
+ import NullCheckAnalyzer from "../analysis/NullCheckAnalyzer";
16
+ import DivisionByZeroAnalyzer from "../analysis/DivisionByZeroAnalyzer";
17
+ import {
18
+ ITranspileResult,
19
+ ITranspileError,
20
+ ISymbolInfo,
21
+ IParseWithSymbolsResult,
22
+ TSymbolKind,
23
+ } from "./types/ITranspileResult";
24
+
25
+ export {
26
+ ITranspileResult,
27
+ ITranspileError,
28
+ ISymbolInfo,
29
+ IParseWithSymbolsResult,
30
+ TSymbolKind,
31
+ };
32
+
33
+ /**
34
+ * Options for transpilation
35
+ */
36
+ export interface ITranspileOptions {
37
+ /** Parse only, don't generate code */
38
+ parseOnly?: boolean;
39
+ /** ADR-044: When true, generate panic-on-overflow helpers instead of clamp helpers */
40
+ debugMode?: boolean;
41
+ /** ADR-049: Target platform for atomic code generation (e.g., "teensy41", "cortex-m0") */
42
+ target?: string;
43
+ }
44
+
45
+ /**
46
+ * Transpile C-Next source code to C
47
+ *
48
+ * @param source - C-Next source code string
49
+ * @param options - Optional transpilation options
50
+ * @returns Transpilation result with code or errors
51
+ *
52
+ * @example
53
+ * ```typescript
54
+ * import { transpile } from './lib/transpiler';
55
+ *
56
+ * const result = transpile('u32 x <- 5;');
57
+ * if (result.success) {
58
+ * console.log(result.code);
59
+ * } else {
60
+ * result.errors.forEach(e => console.error(`${e.line}:${e.column} ${e.message}`));
61
+ * }
62
+ * ```
63
+ */
64
+ export function transpile(
65
+ source: string,
66
+ options: ITranspileOptions = {},
67
+ ): ITranspileResult {
68
+ const { parseOnly = false, debugMode = false, target } = options;
69
+ const errors: ITranspileError[] = [];
70
+
71
+ // Create the lexer and parser
72
+ const charStream = CharStream.fromString(source);
73
+ const lexer = new CNextLexer(charStream);
74
+ const tokenStream = new CommonTokenStream(lexer);
75
+ const parser = new CNextParser(tokenStream);
76
+
77
+ // Custom error listener to collect errors with line/column info
78
+ lexer.removeErrorListeners();
79
+ parser.removeErrorListeners();
80
+
81
+ const errorListener = {
82
+ syntaxError(
83
+ _recognizer: unknown,
84
+ _offendingSymbol: unknown,
85
+ line: number,
86
+ charPositionInLine: number,
87
+ msg: string,
88
+ _e: unknown,
89
+ ): void {
90
+ errors.push({
91
+ line,
92
+ column: charPositionInLine,
93
+ message: msg,
94
+ severity: "error",
95
+ });
96
+ },
97
+ reportAmbiguity(): void {},
98
+ reportAttemptingFullContext(): void {},
99
+ reportContextSensitivity(): void {},
100
+ };
101
+
102
+ lexer.addErrorListener(errorListener);
103
+ parser.addErrorListener(errorListener);
104
+
105
+ // Parse the input
106
+ let tree;
107
+ try {
108
+ tree = parser.program();
109
+ } catch (e) {
110
+ // Handle catastrophic parse failures
111
+ const errorMessage = e instanceof Error ? e.message : String(e);
112
+ errors.push({
113
+ line: 1,
114
+ column: 0,
115
+ message: `Parse failed: ${errorMessage}`,
116
+ severity: "error",
117
+ });
118
+ return {
119
+ success: false,
120
+ code: "",
121
+ errors,
122
+ declarationCount: 0,
123
+ };
124
+ }
125
+
126
+ const declarationCount = tree.declaration().length;
127
+
128
+ // If there are parse errors or parseOnly mode, return early
129
+ if (errors.length > 0) {
130
+ return {
131
+ success: false,
132
+ code: "",
133
+ errors,
134
+ declarationCount,
135
+ };
136
+ }
137
+
138
+ if (parseOnly) {
139
+ return {
140
+ success: true,
141
+ code: "",
142
+ errors: [],
143
+ declarationCount,
144
+ };
145
+ }
146
+
147
+ // Run initialization analysis (Rust-style use-before-init detection)
148
+ const initAnalyzer = new InitializationAnalyzer();
149
+ const initErrors = initAnalyzer.analyze(tree);
150
+
151
+ // Convert initialization errors to transpile errors
152
+ for (const initError of initErrors) {
153
+ errors.push({
154
+ line: initError.line,
155
+ column: initError.column,
156
+ message: `error[${initError.code}]: ${initError.message}`,
157
+ severity: "error",
158
+ });
159
+ }
160
+
161
+ // If there are initialization errors, fail compilation
162
+ if (errors.length > 0) {
163
+ return {
164
+ success: false,
165
+ code: "",
166
+ errors,
167
+ declarationCount,
168
+ };
169
+ }
170
+
171
+ // Run function call analysis (ADR-030: define-before-use)
172
+ const funcAnalyzer = new FunctionCallAnalyzer();
173
+ const funcErrors = funcAnalyzer.analyze(tree);
174
+
175
+ // Convert function call errors to transpile errors
176
+ for (const funcError of funcErrors) {
177
+ errors.push({
178
+ line: funcError.line,
179
+ column: funcError.column,
180
+ message: `error[${funcError.code}]: ${funcError.message}`,
181
+ severity: "error",
182
+ });
183
+ }
184
+
185
+ // If there are function call errors, fail compilation
186
+ if (errors.length > 0) {
187
+ return {
188
+ success: false,
189
+ code: "",
190
+ errors,
191
+ declarationCount,
192
+ };
193
+ }
194
+
195
+ // Run NULL check analysis (ADR-047: C library interop)
196
+ const nullAnalyzer = new NullCheckAnalyzer();
197
+ const nullErrors = nullAnalyzer.analyze(tree);
198
+
199
+ // Convert NULL check errors to transpile errors
200
+ for (const nullError of nullErrors) {
201
+ errors.push({
202
+ line: nullError.line,
203
+ column: nullError.column,
204
+ message: `error[${nullError.code}]: ${nullError.message}`,
205
+ severity: "error",
206
+ });
207
+ }
208
+
209
+ // If there are NULL check errors, fail compilation
210
+ if (errors.length > 0) {
211
+ return {
212
+ success: false,
213
+ code: "",
214
+ errors,
215
+ declarationCount,
216
+ };
217
+ }
218
+
219
+ // Run division by zero analysis (ADR-051: compile-time detection)
220
+ const divZeroAnalyzer = new DivisionByZeroAnalyzer();
221
+ const divZeroErrors = divZeroAnalyzer.analyze(tree);
222
+
223
+ // Convert division by zero errors to transpile errors
224
+ for (const divZeroError of divZeroErrors) {
225
+ errors.push({
226
+ line: divZeroError.line,
227
+ column: divZeroError.column,
228
+ message: `error[${divZeroError.code}]: ${divZeroError.message}`,
229
+ severity: "error",
230
+ });
231
+ }
232
+
233
+ // If there are division by zero errors, fail compilation
234
+ if (errors.length > 0) {
235
+ return {
236
+ success: false,
237
+ code: "",
238
+ errors,
239
+ declarationCount,
240
+ };
241
+ }
242
+
243
+ // Validate comments (MISRA C:2012 Rules 3.1, 3.2) - ADR-043
244
+ const commentExtractor = new CommentExtractor(tokenStream);
245
+ const commentErrors = commentExtractor.validate();
246
+
247
+ for (const commentError of commentErrors) {
248
+ errors.push({
249
+ line: commentError.line,
250
+ column: commentError.column,
251
+ message: `error[MISRA-${commentError.rule}]: ${commentError.message}`,
252
+ severity: "error",
253
+ });
254
+ }
255
+
256
+ // If there are comment validation errors, fail compilation
257
+ if (errors.length > 0) {
258
+ return {
259
+ success: false,
260
+ code: "",
261
+ errors,
262
+ declarationCount,
263
+ };
264
+ }
265
+
266
+ // Generate C code
267
+ try {
268
+ const generator = new CodeGenerator();
269
+ const code = generator.generate(tree, undefined, tokenStream, {
270
+ debugMode,
271
+ target,
272
+ });
273
+
274
+ return {
275
+ success: true,
276
+ code,
277
+ errors: [],
278
+ declarationCount,
279
+ };
280
+ } catch (e) {
281
+ // Handle code generation errors
282
+ const errorMessage = e instanceof Error ? e.message : String(e);
283
+ errors.push({
284
+ line: 1,
285
+ column: 0,
286
+ message: `Code generation failed: ${errorMessage}`,
287
+ severity: "error",
288
+ });
289
+ return {
290
+ success: false,
291
+ code: "",
292
+ errors,
293
+ declarationCount,
294
+ };
295
+ }
296
+ }
297
+
298
+ /**
299
+ * Parse C-Next source and return parse result without generating code
300
+ * Convenience wrapper around transpile with parseOnly: true
301
+ */
302
+ export function parse(source: string): ITranspileResult {
303
+ return transpile(source, { parseOnly: true });
304
+ }
305
+
306
+ /**
307
+ * Map ESymbolKind to TSymbolKind for extension use
308
+ */
309
+ function mapSymbolKind(kind: ESymbolKind): TSymbolKind {
310
+ switch (kind) {
311
+ case ESymbolKind.Namespace:
312
+ return "namespace";
313
+ case ESymbolKind.Class:
314
+ return "class";
315
+ case ESymbolKind.Struct:
316
+ return "struct";
317
+ case ESymbolKind.Register:
318
+ return "register";
319
+ case ESymbolKind.Function:
320
+ return "function";
321
+ case ESymbolKind.Variable:
322
+ return "variable";
323
+ case ESymbolKind.RegisterMember:
324
+ return "registerMember";
325
+ default:
326
+ return "variable";
327
+ }
328
+ }
329
+
330
+ /**
331
+ * Extract local name from full qualified name
332
+ * e.g., "LED_toggle" with parent "LED" -> "toggle"
333
+ */
334
+ function extractLocalName(fullName: string, parent?: string): string {
335
+ if (parent && fullName.startsWith(parent + "_")) {
336
+ return fullName.substring(parent.length + 1);
337
+ }
338
+ return fullName;
339
+ }
340
+
341
+ /**
342
+ * Parse C-Next source and extract symbols for IDE features
343
+ *
344
+ * Unlike transpile(), this function attempts to extract symbols even when
345
+ * there are parse errors, making it suitable for autocomplete during typing.
346
+ *
347
+ * @param source - C-Next source code string
348
+ * @returns Parse result with symbols
349
+ *
350
+ * @example
351
+ * ```typescript
352
+ * import { parseWithSymbols } from './lib/transpiler';
353
+ *
354
+ * const result = parseWithSymbols(source);
355
+ * // Find namespace members for autocomplete
356
+ * const ledMembers = result.symbols.filter(s => s.parent === 'LED');
357
+ * ```
358
+ */
359
+ export function parseWithSymbols(source: string): IParseWithSymbolsResult {
360
+ const errors: ITranspileError[] = [];
361
+
362
+ // Create the lexer and parser
363
+ const charStream = CharStream.fromString(source);
364
+ const lexer = new CNextLexer(charStream);
365
+ const tokenStream = new CommonTokenStream(lexer);
366
+ const parser = new CNextParser(tokenStream);
367
+
368
+ // Custom error listener to collect errors
369
+ lexer.removeErrorListeners();
370
+ parser.removeErrorListeners();
371
+
372
+ const errorListener = {
373
+ syntaxError(
374
+ _recognizer: unknown,
375
+ _offendingSymbol: unknown,
376
+ line: number,
377
+ charPositionInLine: number,
378
+ msg: string,
379
+ _e: unknown,
380
+ ): void {
381
+ errors.push({
382
+ line,
383
+ column: charPositionInLine,
384
+ message: msg,
385
+ severity: "error",
386
+ });
387
+ },
388
+ reportAmbiguity(): void {},
389
+ reportAttemptingFullContext(): void {},
390
+ reportContextSensitivity(): void {},
391
+ };
392
+
393
+ lexer.addErrorListener(errorListener);
394
+ parser.addErrorListener(errorListener);
395
+
396
+ // Parse the input - continue even with errors to get partial symbols
397
+ let tree;
398
+ try {
399
+ tree = parser.program();
400
+ } catch (e) {
401
+ const errorMessage = e instanceof Error ? e.message : String(e);
402
+ errors.push({
403
+ line: 1,
404
+ column: 0,
405
+ message: `Parse failed: ${errorMessage}`,
406
+ severity: "error",
407
+ });
408
+ return {
409
+ success: false,
410
+ errors,
411
+ symbols: [],
412
+ };
413
+ }
414
+
415
+ // Collect symbols from the parse tree
416
+ const collector = new CNextSymbolCollector("<source>");
417
+ const rawSymbols = collector.collect(tree);
418
+
419
+ // Transform ISymbol[] to ISymbolInfo[]
420
+ const symbols: ISymbolInfo[] = rawSymbols.map((sym) => ({
421
+ name: extractLocalName(sym.name, sym.parent),
422
+ fullName: sym.name,
423
+ kind: mapSymbolKind(sym.kind),
424
+ type: sym.type,
425
+ parent: sym.parent,
426
+ signature: sym.signature,
427
+ accessModifier: sym.accessModifier,
428
+ line: sym.sourceLine,
429
+ size: sym.size,
430
+ }));
431
+
432
+ return {
433
+ success: errors.length === 0,
434
+ errors,
435
+ symbols,
436
+ };
437
+ }
438
+
439
+ export default transpile;