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