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,236 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File Discovery
|
|
3
|
+
* Scans directories for source files
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { readdirSync, statSync, existsSync } from "fs";
|
|
7
|
+
import { join, extname, resolve } from "path";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* File types supported by the transpiler
|
|
11
|
+
*/
|
|
12
|
+
enum EFileType {
|
|
13
|
+
CNext = "cnext",
|
|
14
|
+
CHeader = "c_header",
|
|
15
|
+
CppHeader = "cpp_header",
|
|
16
|
+
CSource = "c_source",
|
|
17
|
+
CppSource = "cpp_source",
|
|
18
|
+
Unknown = "unknown",
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Discovered source file
|
|
23
|
+
*/
|
|
24
|
+
interface IDiscoveredFile {
|
|
25
|
+
/** Absolute path to the file */
|
|
26
|
+
path: string;
|
|
27
|
+
|
|
28
|
+
/** File type */
|
|
29
|
+
type: EFileType;
|
|
30
|
+
|
|
31
|
+
/** File extension */
|
|
32
|
+
extension: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Options for file discovery
|
|
37
|
+
*/
|
|
38
|
+
interface IDiscoveryOptions {
|
|
39
|
+
/** File extensions to include (default: all supported) */
|
|
40
|
+
extensions?: string[];
|
|
41
|
+
|
|
42
|
+
/** Whether to recurse into subdirectories */
|
|
43
|
+
recursive?: boolean;
|
|
44
|
+
|
|
45
|
+
/** Patterns to exclude */
|
|
46
|
+
excludePatterns?: RegExp[];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Default extensions for each file type
|
|
51
|
+
*/
|
|
52
|
+
const EXTENSION_MAP: Record<string, EFileType> = {
|
|
53
|
+
".cnx": EFileType.CNext,
|
|
54
|
+
".cnext": EFileType.CNext,
|
|
55
|
+
".h": EFileType.CHeader,
|
|
56
|
+
".hpp": EFileType.CppHeader,
|
|
57
|
+
".hxx": EFileType.CppHeader,
|
|
58
|
+
".hh": EFileType.CppHeader,
|
|
59
|
+
".c": EFileType.CSource,
|
|
60
|
+
".cpp": EFileType.CppSource,
|
|
61
|
+
".cxx": EFileType.CppSource,
|
|
62
|
+
".cc": EFileType.CppSource,
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Discovers source files in directories
|
|
67
|
+
*/
|
|
68
|
+
class FileDiscovery {
|
|
69
|
+
/**
|
|
70
|
+
* Discover files in the given directories
|
|
71
|
+
*/
|
|
72
|
+
static discover(
|
|
73
|
+
directories: string[],
|
|
74
|
+
options: IDiscoveryOptions = {},
|
|
75
|
+
): IDiscoveredFile[] {
|
|
76
|
+
const files: IDiscoveredFile[] = [];
|
|
77
|
+
const recursive = options.recursive ?? true;
|
|
78
|
+
const excludePatterns = options.excludePatterns ?? [
|
|
79
|
+
/node_modules/,
|
|
80
|
+
/\.git/,
|
|
81
|
+
/\.build/,
|
|
82
|
+
/\.pio/,
|
|
83
|
+
];
|
|
84
|
+
|
|
85
|
+
for (const dir of directories) {
|
|
86
|
+
const resolvedDir = resolve(dir);
|
|
87
|
+
|
|
88
|
+
if (!existsSync(resolvedDir)) {
|
|
89
|
+
console.warn(`Warning: Directory not found: ${dir}`);
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
this.scanDirectory(
|
|
94
|
+
resolvedDir,
|
|
95
|
+
files,
|
|
96
|
+
recursive,
|
|
97
|
+
options.extensions,
|
|
98
|
+
excludePatterns,
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return files;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Discover a single file
|
|
107
|
+
*/
|
|
108
|
+
static discoverFile(filePath: string): IDiscoveredFile | null {
|
|
109
|
+
const resolvedPath = resolve(filePath);
|
|
110
|
+
|
|
111
|
+
if (!existsSync(resolvedPath)) {
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const ext = extname(resolvedPath).toLowerCase();
|
|
116
|
+
const type = EXTENSION_MAP[ext] ?? EFileType.Unknown;
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
path: resolvedPath,
|
|
120
|
+
type,
|
|
121
|
+
extension: ext,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Discover multiple specific files
|
|
127
|
+
*/
|
|
128
|
+
static discoverFiles(filePaths: string[]): IDiscoveredFile[] {
|
|
129
|
+
const files: IDiscoveredFile[] = [];
|
|
130
|
+
|
|
131
|
+
for (const filePath of filePaths) {
|
|
132
|
+
const file = this.discoverFile(filePath);
|
|
133
|
+
if (file) {
|
|
134
|
+
files.push(file);
|
|
135
|
+
} else {
|
|
136
|
+
console.warn(`Warning: File not found: ${filePath}`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return files;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Filter discovered files by type
|
|
145
|
+
*/
|
|
146
|
+
static filterByType(
|
|
147
|
+
files: IDiscoveredFile[],
|
|
148
|
+
type: EFileType,
|
|
149
|
+
): IDiscoveredFile[] {
|
|
150
|
+
return files.filter((f) => f.type === type);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Get C-Next files from a list
|
|
155
|
+
*/
|
|
156
|
+
static getCNextFiles(files: IDiscoveredFile[]): IDiscoveredFile[] {
|
|
157
|
+
return this.filterByType(files, EFileType.CNext);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Get C/C++ header files from a list
|
|
162
|
+
*/
|
|
163
|
+
static getHeaderFiles(files: IDiscoveredFile[]): IDiscoveredFile[] {
|
|
164
|
+
return files.filter(
|
|
165
|
+
(f) => f.type === EFileType.CHeader || f.type === EFileType.CppHeader,
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Scan a directory for source files
|
|
171
|
+
*/
|
|
172
|
+
private static scanDirectory(
|
|
173
|
+
dir: string,
|
|
174
|
+
files: IDiscoveredFile[],
|
|
175
|
+
recursive: boolean,
|
|
176
|
+
extensions: string[] | undefined,
|
|
177
|
+
excludePatterns: RegExp[],
|
|
178
|
+
): void {
|
|
179
|
+
let entries: string[];
|
|
180
|
+
|
|
181
|
+
try {
|
|
182
|
+
entries = readdirSync(dir);
|
|
183
|
+
} catch {
|
|
184
|
+
console.warn(`Warning: Cannot read directory: ${dir}`);
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
for (const entry of entries) {
|
|
189
|
+
const fullPath = join(dir, entry);
|
|
190
|
+
|
|
191
|
+
// Check exclude patterns
|
|
192
|
+
if (excludePatterns.some((pattern) => pattern.test(fullPath))) {
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
let stats;
|
|
197
|
+
try {
|
|
198
|
+
stats = statSync(fullPath);
|
|
199
|
+
} catch {
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (stats.isDirectory()) {
|
|
204
|
+
if (recursive) {
|
|
205
|
+
this.scanDirectory(
|
|
206
|
+
fullPath,
|
|
207
|
+
files,
|
|
208
|
+
recursive,
|
|
209
|
+
extensions,
|
|
210
|
+
excludePatterns,
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
} else if (stats.isFile()) {
|
|
214
|
+
const ext = extname(fullPath).toLowerCase();
|
|
215
|
+
|
|
216
|
+
// Check extension filter
|
|
217
|
+
if (extensions && !extensions.includes(ext)) {
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const type = EXTENSION_MAP[ext];
|
|
222
|
+
if (type) {
|
|
223
|
+
files.push({
|
|
224
|
+
path: fullPath,
|
|
225
|
+
type,
|
|
226
|
+
extension: ext,
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export default FileDiscovery;
|
|
235
|
+
export { EFileType };
|
|
236
|
+
export type { IDiscoveredFile, IDiscoveryOptions };
|
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project
|
|
3
|
+
* Coordinates multi-file compilation with cross-language symbol resolution
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
|
|
7
|
+
import { CharStream, CommonTokenStream } from "antlr4ng";
|
|
8
|
+
import { join, basename, relative, dirname } from "path";
|
|
9
|
+
|
|
10
|
+
import { CNextLexer } from "../parser/grammar/CNextLexer";
|
|
11
|
+
import { CNextParser } from "../parser/grammar/CNextParser";
|
|
12
|
+
import { CLexer } from "../parser/c/grammar/CLexer";
|
|
13
|
+
import { CParser } from "../parser/c/grammar/CParser";
|
|
14
|
+
import { CPP14Lexer } from "../parser/cpp/grammar/CPP14Lexer";
|
|
15
|
+
import { CPP14Parser } from "../parser/cpp/grammar/CPP14Parser";
|
|
16
|
+
|
|
17
|
+
import CodeGenerator from "../codegen/CodeGenerator";
|
|
18
|
+
import HeaderGenerator from "../codegen/HeaderGenerator";
|
|
19
|
+
import SymbolTable from "../symbols/SymbolTable";
|
|
20
|
+
import CNextSymbolCollector from "../symbols/CNextSymbolCollector";
|
|
21
|
+
import CSymbolCollector from "../symbols/CSymbolCollector";
|
|
22
|
+
import CppSymbolCollector from "../symbols/CppSymbolCollector";
|
|
23
|
+
import Preprocessor from "../preprocessor/Preprocessor";
|
|
24
|
+
|
|
25
|
+
import IProjectConfig, { IProjectResult } from "./types/IProjectConfig";
|
|
26
|
+
import FileDiscovery, { EFileType, IDiscoveredFile } from "./FileDiscovery";
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Manages multi-file C-Next projects
|
|
30
|
+
*/
|
|
31
|
+
class Project {
|
|
32
|
+
private config: IProjectConfig;
|
|
33
|
+
|
|
34
|
+
private symbolTable: SymbolTable;
|
|
35
|
+
|
|
36
|
+
private preprocessor: Preprocessor;
|
|
37
|
+
|
|
38
|
+
private codeGenerator: CodeGenerator;
|
|
39
|
+
|
|
40
|
+
private headerGenerator: HeaderGenerator;
|
|
41
|
+
|
|
42
|
+
constructor(config: IProjectConfig) {
|
|
43
|
+
this.config = {
|
|
44
|
+
extensions: [".cnx", ".cnext"],
|
|
45
|
+
generateHeaders: true,
|
|
46
|
+
preprocess: true,
|
|
47
|
+
...config,
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
this.symbolTable = new SymbolTable();
|
|
51
|
+
this.preprocessor = new Preprocessor();
|
|
52
|
+
this.codeGenerator = new CodeGenerator();
|
|
53
|
+
this.headerGenerator = new HeaderGenerator();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Compile the project
|
|
58
|
+
*/
|
|
59
|
+
async compile(): Promise<IProjectResult> {
|
|
60
|
+
const result: IProjectResult = {
|
|
61
|
+
success: true,
|
|
62
|
+
filesProcessed: 0,
|
|
63
|
+
symbolsCollected: 0,
|
|
64
|
+
conflicts: [],
|
|
65
|
+
errors: [],
|
|
66
|
+
warnings: [],
|
|
67
|
+
outputFiles: [],
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
// Discover files
|
|
72
|
+
const files = this.discoverFiles();
|
|
73
|
+
|
|
74
|
+
if (files.length === 0) {
|
|
75
|
+
result.warnings.push("No source files found");
|
|
76
|
+
return result;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Ensure output directory exists
|
|
80
|
+
if (!existsSync(this.config.outDir)) {
|
|
81
|
+
mkdirSync(this.config.outDir, { recursive: true });
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Phase 1: Collect symbols from all C/C++ headers
|
|
85
|
+
const headerFiles = FileDiscovery.getHeaderFiles(files);
|
|
86
|
+
for (const file of headerFiles) {
|
|
87
|
+
try {
|
|
88
|
+
await this.collectHeaderSymbols(file, result);
|
|
89
|
+
result.filesProcessed++;
|
|
90
|
+
} catch (err) {
|
|
91
|
+
result.errors.push(`Failed to process ${file.path}: ${err}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Phase 2: Collect symbols from C-Next files
|
|
96
|
+
const cnextFiles = FileDiscovery.getCNextFiles(files);
|
|
97
|
+
for (const file of cnextFiles) {
|
|
98
|
+
try {
|
|
99
|
+
this.collectCNextSymbols(file, result);
|
|
100
|
+
result.filesProcessed++;
|
|
101
|
+
} catch (err) {
|
|
102
|
+
result.errors.push(`Failed to process ${file.path}: ${err}`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Phase 3: Check for conflicts
|
|
107
|
+
const conflicts = this.symbolTable.getConflicts();
|
|
108
|
+
for (const conflict of conflicts) {
|
|
109
|
+
result.conflicts.push(conflict.message);
|
|
110
|
+
if (conflict.severity === "error") {
|
|
111
|
+
result.success = false;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// If there are errors, stop here
|
|
116
|
+
if (!result.success) {
|
|
117
|
+
result.errors.push(
|
|
118
|
+
"Symbol conflicts detected - cannot proceed with code generation",
|
|
119
|
+
);
|
|
120
|
+
return result;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Phase 4: Generate C code for each C-Next file
|
|
124
|
+
for (const file of cnextFiles) {
|
|
125
|
+
try {
|
|
126
|
+
const outputFile = this.generateCode(file);
|
|
127
|
+
result.outputFiles.push(outputFile);
|
|
128
|
+
} catch (err) {
|
|
129
|
+
result.errors.push(
|
|
130
|
+
`Failed to generate code for ${file.path}: ${err}`,
|
|
131
|
+
);
|
|
132
|
+
result.success = false;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Phase 5: Generate headers if enabled
|
|
137
|
+
if (this.config.generateHeaders && result.success) {
|
|
138
|
+
for (const file of cnextFiles) {
|
|
139
|
+
try {
|
|
140
|
+
const headerFile = this.generateHeader(file);
|
|
141
|
+
if (headerFile) {
|
|
142
|
+
result.outputFiles.push(headerFile);
|
|
143
|
+
}
|
|
144
|
+
} catch (err) {
|
|
145
|
+
result.warnings.push(
|
|
146
|
+
`Failed to generate header for ${file.path}: ${err}`,
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
result.symbolsCollected = this.symbolTable.size;
|
|
153
|
+
} catch (err) {
|
|
154
|
+
result.errors.push(`Project compilation failed: ${err}`);
|
|
155
|
+
result.success = false;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return result;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Discover all project files
|
|
163
|
+
*/
|
|
164
|
+
private discoverFiles(): IDiscoveredFile[] {
|
|
165
|
+
// If specific files are provided, use those
|
|
166
|
+
if (this.config.files && this.config.files.length > 0) {
|
|
167
|
+
return FileDiscovery.discoverFiles(this.config.files);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Otherwise, scan directories
|
|
171
|
+
const allDirs = [...this.config.srcDirs, ...this.config.includeDirs];
|
|
172
|
+
return FileDiscovery.discover(allDirs, {
|
|
173
|
+
extensions: this.config.extensions,
|
|
174
|
+
recursive: true,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Collect symbols from a C/C++ header file
|
|
180
|
+
*/
|
|
181
|
+
private async collectHeaderSymbols(
|
|
182
|
+
file: IDiscoveredFile,
|
|
183
|
+
result: IProjectResult,
|
|
184
|
+
): Promise<void> {
|
|
185
|
+
let content: string;
|
|
186
|
+
|
|
187
|
+
// Preprocess if enabled
|
|
188
|
+
if (this.config.preprocess && this.preprocessor.isAvailable()) {
|
|
189
|
+
const preprocessResult = await this.preprocessor.preprocess(file.path, {
|
|
190
|
+
includePaths: this.config.includeDirs,
|
|
191
|
+
defines: this.config.defines,
|
|
192
|
+
keepLineDirectives: false,
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
if (!preprocessResult.success) {
|
|
196
|
+
result.warnings.push(
|
|
197
|
+
`Preprocessor warning for ${file.path}: ${preprocessResult.error}`,
|
|
198
|
+
);
|
|
199
|
+
// Fall back to raw content
|
|
200
|
+
content = readFileSync(file.path, "utf-8");
|
|
201
|
+
} else {
|
|
202
|
+
content = preprocessResult.content;
|
|
203
|
+
}
|
|
204
|
+
} else {
|
|
205
|
+
content = readFileSync(file.path, "utf-8");
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Parse based on file type
|
|
209
|
+
if (file.type === EFileType.CHeader) {
|
|
210
|
+
this.parseCHeader(content, file.path);
|
|
211
|
+
} else if (file.type === EFileType.CppHeader) {
|
|
212
|
+
this.parseCppHeader(content, file.path);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Parse a C header and collect symbols
|
|
218
|
+
*/
|
|
219
|
+
private parseCHeader(content: string, filePath: string): void {
|
|
220
|
+
const charStream = CharStream.fromString(content);
|
|
221
|
+
const lexer = new CLexer(charStream);
|
|
222
|
+
const tokenStream = new CommonTokenStream(lexer);
|
|
223
|
+
const parser = new CParser(tokenStream);
|
|
224
|
+
|
|
225
|
+
// Suppress error output for header parsing
|
|
226
|
+
parser.removeErrorListeners();
|
|
227
|
+
|
|
228
|
+
try {
|
|
229
|
+
const tree = parser.compilationUnit();
|
|
230
|
+
const collector = new CSymbolCollector(filePath, this.symbolTable);
|
|
231
|
+
const symbols = collector.collect(tree);
|
|
232
|
+
this.symbolTable.addSymbols(symbols);
|
|
233
|
+
} catch {
|
|
234
|
+
// Silently ignore parse errors in headers - they may have unsupported constructs
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Parse a C++ header and collect symbols
|
|
240
|
+
*/
|
|
241
|
+
private parseCppHeader(content: string, filePath: string): void {
|
|
242
|
+
const charStream = CharStream.fromString(content);
|
|
243
|
+
const lexer = new CPP14Lexer(charStream);
|
|
244
|
+
const tokenStream = new CommonTokenStream(lexer);
|
|
245
|
+
const parser = new CPP14Parser(tokenStream);
|
|
246
|
+
|
|
247
|
+
// Suppress error output for header parsing
|
|
248
|
+
parser.removeErrorListeners();
|
|
249
|
+
|
|
250
|
+
try {
|
|
251
|
+
const tree = parser.translationUnit();
|
|
252
|
+
const collector = new CppSymbolCollector(filePath);
|
|
253
|
+
const symbols = collector.collect(tree);
|
|
254
|
+
this.symbolTable.addSymbols(symbols);
|
|
255
|
+
} catch {
|
|
256
|
+
// Silently ignore parse errors in headers
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Collect symbols from a C-Next file
|
|
262
|
+
*/
|
|
263
|
+
private collectCNextSymbols(
|
|
264
|
+
file: IDiscoveredFile,
|
|
265
|
+
result: IProjectResult,
|
|
266
|
+
): void {
|
|
267
|
+
const content = readFileSync(file.path, "utf-8");
|
|
268
|
+
const charStream = CharStream.fromString(content);
|
|
269
|
+
const lexer = new CNextLexer(charStream);
|
|
270
|
+
const tokenStream = new CommonTokenStream(lexer);
|
|
271
|
+
const parser = new CNextParser(tokenStream);
|
|
272
|
+
|
|
273
|
+
const errors: string[] = [];
|
|
274
|
+
parser.removeErrorListeners();
|
|
275
|
+
parser.addErrorListener({
|
|
276
|
+
syntaxError(
|
|
277
|
+
_recognizer,
|
|
278
|
+
_offendingSymbol,
|
|
279
|
+
line,
|
|
280
|
+
charPositionInLine,
|
|
281
|
+
msg,
|
|
282
|
+
_e,
|
|
283
|
+
) {
|
|
284
|
+
errors.push(`${file.path}:${line}:${charPositionInLine} - ${msg}`);
|
|
285
|
+
},
|
|
286
|
+
reportAmbiguity() {},
|
|
287
|
+
reportAttemptingFullContext() {},
|
|
288
|
+
reportContextSensitivity() {},
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
const tree = parser.program();
|
|
292
|
+
|
|
293
|
+
if (errors.length > 0) {
|
|
294
|
+
for (const err of errors) {
|
|
295
|
+
result.errors.push(err);
|
|
296
|
+
}
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const collector = new CNextSymbolCollector(file.path);
|
|
301
|
+
const symbols = collector.collect(tree);
|
|
302
|
+
this.symbolTable.addSymbols(symbols);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Get output path for a file, preserving directory structure if from srcDirs
|
|
307
|
+
*
|
|
308
|
+
* @param file - The discovered file
|
|
309
|
+
* @returns Output path for the generated C file
|
|
310
|
+
*/
|
|
311
|
+
private getOutputPath(file: IDiscoveredFile): string {
|
|
312
|
+
const outputName = basename(file.path).replace(/\.cnx$|\.cnext$/, ".c");
|
|
313
|
+
|
|
314
|
+
// If file is in a subdirectory of srcDir, preserve structure
|
|
315
|
+
for (const srcDir of this.config.srcDirs) {
|
|
316
|
+
const relativePath = relative(srcDir, file.path);
|
|
317
|
+
if (!relativePath.startsWith("..")) {
|
|
318
|
+
// File is under srcDir - preserve directory structure
|
|
319
|
+
const outputRelative = relativePath.replace(/\.cnx$|\.cnext$/, ".c");
|
|
320
|
+
const outputPath = join(this.config.outDir, outputRelative);
|
|
321
|
+
|
|
322
|
+
// Ensure output directory exists
|
|
323
|
+
const outputDir = dirname(outputPath);
|
|
324
|
+
if (!existsSync(outputDir)) {
|
|
325
|
+
mkdirSync(outputDir, { recursive: true });
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
return outputPath;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Fallback: flat output (for files not in srcDirs)
|
|
333
|
+
return join(this.config.outDir, outputName);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Generate C code from a C-Next file
|
|
338
|
+
*/
|
|
339
|
+
private generateCode(file: IDiscoveredFile): string {
|
|
340
|
+
const content = readFileSync(file.path, "utf-8");
|
|
341
|
+
const charStream = CharStream.fromString(content);
|
|
342
|
+
const lexer = new CNextLexer(charStream);
|
|
343
|
+
const tokenStream = new CommonTokenStream(lexer);
|
|
344
|
+
const parser = new CNextParser(tokenStream);
|
|
345
|
+
|
|
346
|
+
const tree = parser.program();
|
|
347
|
+
const code = this.codeGenerator.generate(tree, this.symbolTable);
|
|
348
|
+
|
|
349
|
+
// Write output file with directory structure preserved
|
|
350
|
+
const outputPath = this.getOutputPath(file);
|
|
351
|
+
|
|
352
|
+
writeFileSync(outputPath, code, "utf-8");
|
|
353
|
+
|
|
354
|
+
return outputPath;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Get output path for a header file, preserving directory structure if from srcDirs
|
|
359
|
+
*
|
|
360
|
+
* @param file - The discovered file
|
|
361
|
+
* @returns Output path for the generated header file
|
|
362
|
+
*/
|
|
363
|
+
private getHeaderOutputPath(file: IDiscoveredFile): string {
|
|
364
|
+
const headerName = basename(file.path).replace(/\.cnx$|\.cnext$/, ".h");
|
|
365
|
+
|
|
366
|
+
// If file is in a subdirectory of srcDir, preserve structure
|
|
367
|
+
for (const srcDir of this.config.srcDirs) {
|
|
368
|
+
const relativePath = relative(srcDir, file.path);
|
|
369
|
+
if (!relativePath.startsWith("..")) {
|
|
370
|
+
// File is under srcDir - preserve directory structure
|
|
371
|
+
const outputRelative = relativePath.replace(/\.cnx$|\.cnext$/, ".h");
|
|
372
|
+
const outputPath = join(this.config.outDir, outputRelative);
|
|
373
|
+
|
|
374
|
+
// Ensure output directory exists
|
|
375
|
+
const outputDir = dirname(outputPath);
|
|
376
|
+
if (!existsSync(outputDir)) {
|
|
377
|
+
mkdirSync(outputDir, { recursive: true });
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
return outputPath;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// Fallback: flat output (for files not in srcDirs)
|
|
385
|
+
return join(this.config.outDir, headerName);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Generate header file for a C-Next file
|
|
390
|
+
*/
|
|
391
|
+
private generateHeader(file: IDiscoveredFile): string | null {
|
|
392
|
+
const symbols = this.symbolTable.getSymbolsByFile(file.path);
|
|
393
|
+
|
|
394
|
+
// Filter to exported symbols only
|
|
395
|
+
const exportedSymbols = symbols.filter((s) => s.isExported);
|
|
396
|
+
|
|
397
|
+
if (exportedSymbols.length === 0) {
|
|
398
|
+
return null; // No public symbols, no header needed
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
const headerName = basename(file.path).replace(/\.cnx$|\.cnext$/, ".h");
|
|
402
|
+
const headerPath = this.getHeaderOutputPath(file);
|
|
403
|
+
|
|
404
|
+
const headerContent = this.headerGenerator.generate(
|
|
405
|
+
exportedSymbols,
|
|
406
|
+
headerName,
|
|
407
|
+
{
|
|
408
|
+
exportedOnly: true,
|
|
409
|
+
},
|
|
410
|
+
);
|
|
411
|
+
|
|
412
|
+
writeFileSync(headerPath, headerContent, "utf-8");
|
|
413
|
+
|
|
414
|
+
return headerPath;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* Get the symbol table (for testing/inspection)
|
|
419
|
+
*/
|
|
420
|
+
getSymbolTable(): SymbolTable {
|
|
421
|
+
return this.symbolTable;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
export default Project;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project Configuration Types
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Configuration for a C-Next project
|
|
7
|
+
*/
|
|
8
|
+
interface IProjectConfig {
|
|
9
|
+
/** Project name */
|
|
10
|
+
name?: string;
|
|
11
|
+
|
|
12
|
+
/** Source directories to scan for .cnx files */
|
|
13
|
+
srcDirs: string[];
|
|
14
|
+
|
|
15
|
+
/** Include directories for C/C++ headers */
|
|
16
|
+
includeDirs: string[];
|
|
17
|
+
|
|
18
|
+
/** Output directory for generated files */
|
|
19
|
+
outDir: string;
|
|
20
|
+
|
|
21
|
+
/** Specific files to compile (overrides srcDirs) */
|
|
22
|
+
files?: string[];
|
|
23
|
+
|
|
24
|
+
/** File extensions to process (default: ['.cnx']) */
|
|
25
|
+
extensions?: string[];
|
|
26
|
+
|
|
27
|
+
/** Whether to generate header files */
|
|
28
|
+
generateHeaders?: boolean;
|
|
29
|
+
|
|
30
|
+
/** Whether to preprocess C/C++ files */
|
|
31
|
+
preprocess?: boolean;
|
|
32
|
+
|
|
33
|
+
/** Additional preprocessor defines */
|
|
34
|
+
defines?: Record<string, string | boolean>;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Result of compiling a project
|
|
39
|
+
*/
|
|
40
|
+
interface IProjectResult {
|
|
41
|
+
/** Whether compilation succeeded */
|
|
42
|
+
success: boolean;
|
|
43
|
+
|
|
44
|
+
/** Number of files processed */
|
|
45
|
+
filesProcessed: number;
|
|
46
|
+
|
|
47
|
+
/** Number of symbols collected */
|
|
48
|
+
symbolsCollected: number;
|
|
49
|
+
|
|
50
|
+
/** Symbol conflicts detected */
|
|
51
|
+
conflicts: string[];
|
|
52
|
+
|
|
53
|
+
/** Errors encountered */
|
|
54
|
+
errors: string[];
|
|
55
|
+
|
|
56
|
+
/** Warnings */
|
|
57
|
+
warnings: string[];
|
|
58
|
+
|
|
59
|
+
/** Generated output files */
|
|
60
|
+
outputFiles: string[];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export default IProjectConfig;
|
|
64
|
+
export type { IProjectResult };
|