c-next 0.1.21 → 0.1.23
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 +6 -0
- package/package.json +1 -1
- package/src/commands/CleanCommand.ts +148 -0
- package/src/index.ts +20 -0
- package/src/pipeline/Pipeline.ts +27 -4
- package/src/pipeline/types/IPipelineConfig.ts +3 -0
- package/src/project/FileDiscovery.ts +17 -0
- package/src/project/Project.ts +1 -0
- package/src/project/types/IProjectConfig.ts +3 -0
- package/src/symbols/CNextSymbolCollector.ts +45 -1
package/README.md
CHANGED
|
@@ -77,6 +77,12 @@ cnext examples/blink.cnx --cpp
|
|
|
77
77
|
# Target platform for atomic code generation (ADR-049)
|
|
78
78
|
cnext examples/blink.cnx --target teensy41
|
|
79
79
|
|
|
80
|
+
# Separate output directories for code and headers
|
|
81
|
+
cnext src/ -o build/src --header-out build/include
|
|
82
|
+
|
|
83
|
+
# Clean generated files
|
|
84
|
+
cnext src/ -o build/src --header-out build/include --clean
|
|
85
|
+
|
|
80
86
|
# Show all options
|
|
81
87
|
cnext --help
|
|
82
88
|
```
|
package/package.json
CHANGED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CleanCommand
|
|
3
|
+
* Deletes generated files (.c, .cpp, .h, .hpp) that have matching .cnx sources
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { basename, join, relative, resolve } from "path";
|
|
7
|
+
import { existsSync, statSync, unlinkSync } from "fs";
|
|
8
|
+
import InputExpansion from "../lib/InputExpansion";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Command to clean generated output files
|
|
12
|
+
*/
|
|
13
|
+
class CleanCommand {
|
|
14
|
+
/**
|
|
15
|
+
* Execute the clean command
|
|
16
|
+
*
|
|
17
|
+
* @param inputs - Input files or directories (source locations)
|
|
18
|
+
* @param outDir - Output directory for code files
|
|
19
|
+
* @param headerOutDir - Optional separate output directory for headers
|
|
20
|
+
*/
|
|
21
|
+
static execute(
|
|
22
|
+
inputs: string[],
|
|
23
|
+
outDir: string,
|
|
24
|
+
headerOutDir?: string,
|
|
25
|
+
): void {
|
|
26
|
+
// If no outDir specified, we can't determine where to clean
|
|
27
|
+
if (!outDir) {
|
|
28
|
+
console.log("No output directory specified. Nothing to clean.");
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Discover all .cnx files
|
|
33
|
+
let cnxFiles: string[];
|
|
34
|
+
try {
|
|
35
|
+
cnxFiles = InputExpansion.expandInputs(inputs);
|
|
36
|
+
} catch (error) {
|
|
37
|
+
console.error(`Error: ${error}`);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (cnxFiles.length === 0) {
|
|
42
|
+
console.log("No .cnx files found. Nothing to clean.");
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const resolvedOutDir = resolve(outDir);
|
|
47
|
+
const resolvedHeaderDir = headerOutDir
|
|
48
|
+
? resolve(headerOutDir)
|
|
49
|
+
: resolvedOutDir;
|
|
50
|
+
|
|
51
|
+
let deletedCount = 0;
|
|
52
|
+
|
|
53
|
+
// For each .cnx file, calculate and delete generated files
|
|
54
|
+
for (const cnxFile of cnxFiles) {
|
|
55
|
+
const baseName = basename(cnxFile).replace(/\.cnx$|\.cnext$/, "");
|
|
56
|
+
|
|
57
|
+
// Calculate relative path from input directories
|
|
58
|
+
const relativePath = this.getRelativePath(cnxFile, inputs);
|
|
59
|
+
|
|
60
|
+
// Code files (.c and .cpp) go to outDir
|
|
61
|
+
const codeExtensions = [".c", ".cpp"];
|
|
62
|
+
for (const ext of codeExtensions) {
|
|
63
|
+
const outputPath = relativePath
|
|
64
|
+
? join(resolvedOutDir, relativePath.replace(/\.cnx$|\.cnext$/, ext))
|
|
65
|
+
: join(resolvedOutDir, baseName + ext);
|
|
66
|
+
|
|
67
|
+
if (this.deleteIfExists(outputPath)) {
|
|
68
|
+
deletedCount++;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Header files (.h and .hpp) go to headerOutDir
|
|
73
|
+
const headerExtensions = [".h", ".hpp"];
|
|
74
|
+
for (const ext of headerExtensions) {
|
|
75
|
+
const headerPath = relativePath
|
|
76
|
+
? join(
|
|
77
|
+
resolvedHeaderDir,
|
|
78
|
+
relativePath.replace(/\.cnx$|\.cnext$/, ext),
|
|
79
|
+
)
|
|
80
|
+
: join(resolvedHeaderDir, baseName + ext);
|
|
81
|
+
|
|
82
|
+
if (this.deleteIfExists(headerPath)) {
|
|
83
|
+
deletedCount++;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (deletedCount === 0) {
|
|
89
|
+
console.log("No generated files found to delete.");
|
|
90
|
+
} else {
|
|
91
|
+
console.log(`Deleted ${deletedCount} generated file(s).`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Get relative path of a file from input directories.
|
|
97
|
+
* Returns undefined if file is not under any input directory.
|
|
98
|
+
*
|
|
99
|
+
* When undefined is returned (e.g., for single file inputs), the caller
|
|
100
|
+
* falls back to using just the basename, which is correct behavior since
|
|
101
|
+
* there's no directory structure to preserve.
|
|
102
|
+
*/
|
|
103
|
+
private static getRelativePath(
|
|
104
|
+
filePath: string,
|
|
105
|
+
inputs: string[],
|
|
106
|
+
): string | undefined {
|
|
107
|
+
for (const input of inputs) {
|
|
108
|
+
const resolvedInput = resolve(input);
|
|
109
|
+
|
|
110
|
+
// Skip file inputs - only directories can establish relative structure.
|
|
111
|
+
// For single file inputs like "cnext myfile.cnx -o build", we return
|
|
112
|
+
// undefined and the caller uses baseName, which is the correct behavior.
|
|
113
|
+
if (existsSync(resolvedInput) && statSync(resolvedInput).isFile()) {
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const rel = relative(resolvedInput, filePath);
|
|
118
|
+
|
|
119
|
+
// Check if file is under this input directory
|
|
120
|
+
if (rel && !rel.startsWith("..")) {
|
|
121
|
+
return rel;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return undefined;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Delete a file if it exists
|
|
130
|
+
* @returns true if file was deleted, false otherwise
|
|
131
|
+
*/
|
|
132
|
+
private static deleteIfExists(filePath: string): boolean {
|
|
133
|
+
try {
|
|
134
|
+
unlinkSync(filePath);
|
|
135
|
+
console.log(` Deleted: ${filePath}`);
|
|
136
|
+
return true;
|
|
137
|
+
} catch (err: unknown) {
|
|
138
|
+
// ENOENT means file doesn't exist - not an error for our purposes
|
|
139
|
+
if ((err as NodeJS.ErrnoException).code === "ENOENT") {
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
console.error(` Failed to delete ${filePath}: ${err}`);
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export default CleanCommand;
|
package/src/index.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* A safer C for embedded systems development
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import CleanCommand from "./commands/CleanCommand";
|
|
7
8
|
import IncludeDiscovery from "./lib/IncludeDiscovery";
|
|
8
9
|
import InputExpansion from "./lib/InputExpansion";
|
|
9
10
|
import Project from "./project/Project";
|
|
@@ -107,6 +108,10 @@ function showHelp(): void {
|
|
|
107
108
|
);
|
|
108
109
|
console.log(" --no-preprocess Don't run C preprocessor on headers");
|
|
109
110
|
console.log(" --no-cache Disable symbol cache (.cnx/ directory)");
|
|
111
|
+
console.log(" --header-out <dir> Output directory for header files");
|
|
112
|
+
console.log(
|
|
113
|
+
" --clean Delete generated files for all .cnx sources",
|
|
114
|
+
);
|
|
110
115
|
console.log(" -D<name>[=value] Define preprocessor macro");
|
|
111
116
|
console.log(" --pio-install Setup PlatformIO integration");
|
|
112
117
|
console.log(" --pio-uninstall Remove PlatformIO integration");
|
|
@@ -191,6 +196,7 @@ async function runUnifiedMode(
|
|
|
191
196
|
verbose: boolean,
|
|
192
197
|
cppRequired: boolean,
|
|
193
198
|
noCache: boolean,
|
|
199
|
+
headerOutDir?: string,
|
|
194
200
|
): Promise<void> {
|
|
195
201
|
// Step 1: Expand directories to .cnx files
|
|
196
202
|
let files: string[];
|
|
@@ -255,6 +261,7 @@ async function runUnifiedMode(
|
|
|
255
261
|
files,
|
|
256
262
|
includeDirs: allIncludePaths,
|
|
257
263
|
outDir,
|
|
264
|
+
headerOutDir,
|
|
258
265
|
generateHeaders,
|
|
259
266
|
preprocess,
|
|
260
267
|
defines,
|
|
@@ -471,6 +478,8 @@ async function main(): Promise<void> {
|
|
|
471
478
|
let preprocess = true;
|
|
472
479
|
let verbose = false;
|
|
473
480
|
let noCache = false;
|
|
481
|
+
let headerOutDir: string | undefined;
|
|
482
|
+
let cleanMode = false;
|
|
474
483
|
|
|
475
484
|
for (let i = 0; i < args.length; i++) {
|
|
476
485
|
const arg = args[i];
|
|
@@ -489,6 +498,10 @@ async function main(): Promise<void> {
|
|
|
489
498
|
preprocess = false;
|
|
490
499
|
} else if (arg === "--no-cache") {
|
|
491
500
|
noCache = true;
|
|
501
|
+
} else if (arg === "--header-out" && i + 1 < args.length) {
|
|
502
|
+
headerOutDir = args[++i];
|
|
503
|
+
} else if (arg === "--clean") {
|
|
504
|
+
cleanMode = true;
|
|
492
505
|
} else if (arg.startsWith("-D")) {
|
|
493
506
|
const define = arg.slice(2);
|
|
494
507
|
const eqIndex = define.indexOf("=");
|
|
@@ -518,6 +531,12 @@ async function main(): Promise<void> {
|
|
|
518
531
|
process.exit(1);
|
|
519
532
|
}
|
|
520
533
|
|
|
534
|
+
// Clean mode: delete generated files and exit
|
|
535
|
+
if (cleanMode) {
|
|
536
|
+
CleanCommand.execute(inputFiles, outputPath, headerOutDir);
|
|
537
|
+
process.exit(0);
|
|
538
|
+
}
|
|
539
|
+
|
|
521
540
|
await runUnifiedMode(
|
|
522
541
|
inputFiles,
|
|
523
542
|
outputPath,
|
|
@@ -528,6 +547,7 @@ async function main(): Promise<void> {
|
|
|
528
547
|
verbose,
|
|
529
548
|
cppRequired,
|
|
530
549
|
noCache,
|
|
550
|
+
headerOutDir,
|
|
531
551
|
);
|
|
532
552
|
}
|
|
533
553
|
|
package/src/pipeline/Pipeline.ts
CHANGED
|
@@ -76,6 +76,7 @@ class Pipeline {
|
|
|
76
76
|
inputs: config.inputs,
|
|
77
77
|
includeDirs: config.includeDirs ?? [],
|
|
78
78
|
outDir: config.outDir ?? "",
|
|
79
|
+
headerOutDir: config.headerOutDir ?? "",
|
|
79
80
|
defines: config.defines ?? {},
|
|
80
81
|
preprocess: config.preprocess ?? true,
|
|
81
82
|
generateHeaders: config.generateHeaders ?? true,
|
|
@@ -137,6 +138,11 @@ class Pipeline {
|
|
|
137
138
|
mkdirSync(this.config.outDir, { recursive: true });
|
|
138
139
|
}
|
|
139
140
|
|
|
141
|
+
// Ensure header output directory exists if specified separately
|
|
142
|
+
if (this.config.headerOutDir && !existsSync(this.config.headerOutDir)) {
|
|
143
|
+
mkdirSync(this.config.headerOutDir, { recursive: true });
|
|
144
|
+
}
|
|
145
|
+
|
|
140
146
|
// Stage 2: Collect symbols from C/C++ headers
|
|
141
147
|
for (const file of headerFiles) {
|
|
142
148
|
try {
|
|
@@ -363,6 +369,17 @@ class Pipeline {
|
|
|
363
369
|
// The preprocessor expands/removes #include directives, so we need the original
|
|
364
370
|
const originalContent = readFileSync(file.path, "utf-8");
|
|
365
371
|
|
|
372
|
+
// Issue #328: Skip headers generated by C-Next Transpiler
|
|
373
|
+
// During incremental migration, generated .h files may be discovered via #include
|
|
374
|
+
// recursion from C++ files. These headers contain the same symbols as their .cnx
|
|
375
|
+
// source files, so including them would cause false symbol conflicts.
|
|
376
|
+
if (originalContent.includes("Generated by C-Next Transpiler")) {
|
|
377
|
+
if (this.config.debugMode) {
|
|
378
|
+
console.log(`[DEBUG] Skipping C-Next generated header: ${file.path}`);
|
|
379
|
+
}
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
|
|
366
383
|
// Issue #321: Recursively process #include directives in headers
|
|
367
384
|
// This ensures symbols from nested headers (like Arduino's extern HardwareSerial Serial)
|
|
368
385
|
// are properly collected even when included transitively
|
|
@@ -537,7 +554,9 @@ class Pipeline {
|
|
|
537
554
|
throw new Error(errors.join("\n"));
|
|
538
555
|
}
|
|
539
556
|
|
|
540
|
-
|
|
557
|
+
// Issue #332: Pass symbolTable to collector so struct fields are registered
|
|
558
|
+
// This enables TypeResolver.isStructType() to identify C-Next structs from included files
|
|
559
|
+
const collector = new CNextSymbolCollector(file.path, this.symbolTable);
|
|
541
560
|
const symbols = collector.collect(tree);
|
|
542
561
|
this.symbolTable.addSymbols(symbols);
|
|
543
562
|
}
|
|
@@ -794,10 +813,14 @@ class Pipeline {
|
|
|
794
813
|
|
|
795
814
|
/**
|
|
796
815
|
* Get output path for a header file
|
|
816
|
+
* Uses headerOutDir if specified, otherwise falls back to outDir
|
|
797
817
|
*/
|
|
798
818
|
private getHeaderOutputPath(file: IDiscoveredFile): string {
|
|
799
819
|
const headerName = basename(file.path).replace(/\.cnx$|\.cnext$/, ".h");
|
|
800
820
|
|
|
821
|
+
// Use headerOutDir if specified, otherwise fall back to outDir
|
|
822
|
+
const headerDir = this.config.headerOutDir || this.config.outDir;
|
|
823
|
+
|
|
801
824
|
// Check if file is in any input directory (for preserving structure)
|
|
802
825
|
for (const input of this.config.inputs) {
|
|
803
826
|
const resolvedInput = resolve(input);
|
|
@@ -812,7 +835,7 @@ class Pipeline {
|
|
|
812
835
|
// Check if file is under this input directory
|
|
813
836
|
if (relativePath && !relativePath.startsWith("..")) {
|
|
814
837
|
const outputRelative = relativePath.replace(/\.cnx$|\.cnext$/, ".h");
|
|
815
|
-
const outputPath = join(
|
|
838
|
+
const outputPath = join(headerDir, outputRelative);
|
|
816
839
|
|
|
817
840
|
const outputDir = dirname(outputPath);
|
|
818
841
|
if (!existsSync(outputDir)) {
|
|
@@ -823,8 +846,8 @@ class Pipeline {
|
|
|
823
846
|
}
|
|
824
847
|
}
|
|
825
848
|
|
|
826
|
-
// Fallback: flat output in
|
|
827
|
-
return join(
|
|
849
|
+
// Fallback: flat output in headerDir
|
|
850
|
+
return join(headerDir, headerName);
|
|
828
851
|
}
|
|
829
852
|
|
|
830
853
|
/**
|
|
@@ -14,6 +14,9 @@ interface IPipelineConfig {
|
|
|
14
14
|
/** Output directory for generated files (defaults to same as input) */
|
|
15
15
|
outDir?: string;
|
|
16
16
|
|
|
17
|
+
/** Separate output directory for header files (defaults to outDir) */
|
|
18
|
+
headerOutDir?: string;
|
|
19
|
+
|
|
17
20
|
/** Preprocessor defines for C/C++ headers */
|
|
18
21
|
defines?: Record<string, string | boolean>;
|
|
19
22
|
|
|
@@ -31,6 +31,9 @@ const EXTENSION_MAP: Record<string, EFileType> = {
|
|
|
31
31
|
class FileDiscovery {
|
|
32
32
|
/**
|
|
33
33
|
* Discover files in the given directories
|
|
34
|
+
*
|
|
35
|
+
* Issue #331: Uses a Set to track discovered file paths and avoid duplicates
|
|
36
|
+
* when overlapping directories are provided (e.g., both src/Display and src).
|
|
34
37
|
*/
|
|
35
38
|
static discover(
|
|
36
39
|
directories: string[],
|
|
@@ -44,6 +47,8 @@ class FileDiscovery {
|
|
|
44
47
|
/\.build/,
|
|
45
48
|
/\.pio/,
|
|
46
49
|
];
|
|
50
|
+
// Issue #331: Track discovered paths to avoid duplicates from overlapping dirs
|
|
51
|
+
const discoveredPaths = new Set<string>();
|
|
47
52
|
|
|
48
53
|
for (const dir of directories) {
|
|
49
54
|
const resolvedDir = resolve(dir);
|
|
@@ -59,6 +64,7 @@ class FileDiscovery {
|
|
|
59
64
|
recursive,
|
|
60
65
|
options.extensions,
|
|
61
66
|
excludePatterns,
|
|
67
|
+
discoveredPaths,
|
|
62
68
|
);
|
|
63
69
|
}
|
|
64
70
|
|
|
@@ -131,6 +137,9 @@ class FileDiscovery {
|
|
|
131
137
|
|
|
132
138
|
/**
|
|
133
139
|
* Scan a directory for source files
|
|
140
|
+
*
|
|
141
|
+
* Issue #331: discoveredPaths parameter tracks already-discovered files
|
|
142
|
+
* to avoid duplicates when scanning overlapping directories.
|
|
134
143
|
*/
|
|
135
144
|
private static scanDirectory(
|
|
136
145
|
dir: string,
|
|
@@ -138,6 +147,7 @@ class FileDiscovery {
|
|
|
138
147
|
recursive: boolean,
|
|
139
148
|
extensions: string[] | undefined,
|
|
140
149
|
excludePatterns: RegExp[],
|
|
150
|
+
discoveredPaths: Set<string>,
|
|
141
151
|
): void {
|
|
142
152
|
let entries: string[];
|
|
143
153
|
|
|
@@ -171,9 +181,15 @@ class FileDiscovery {
|
|
|
171
181
|
recursive,
|
|
172
182
|
extensions,
|
|
173
183
|
excludePatterns,
|
|
184
|
+
discoveredPaths,
|
|
174
185
|
);
|
|
175
186
|
}
|
|
176
187
|
} else if (stats.isFile()) {
|
|
188
|
+
// Issue #331: Skip already-discovered files (from overlapping directories)
|
|
189
|
+
if (discoveredPaths.has(fullPath)) {
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
|
|
177
193
|
const ext = extname(fullPath).toLowerCase();
|
|
178
194
|
|
|
179
195
|
// Check extension filter
|
|
@@ -183,6 +199,7 @@ class FileDiscovery {
|
|
|
183
199
|
|
|
184
200
|
const type = EXTENSION_MAP[ext];
|
|
185
201
|
if (type) {
|
|
202
|
+
discoveredPaths.add(fullPath);
|
|
186
203
|
files.push({
|
|
187
204
|
path: fullPath,
|
|
188
205
|
type,
|
package/src/project/Project.ts
CHANGED
|
@@ -46,6 +46,7 @@ class Project {
|
|
|
46
46
|
inputs,
|
|
47
47
|
includeDirs: this.config.includeDirs,
|
|
48
48
|
outDir: this.config.outDir,
|
|
49
|
+
headerOutDir: this.config.headerOutDir,
|
|
49
50
|
defines: this.config.defines,
|
|
50
51
|
preprocess: this.config.preprocess,
|
|
51
52
|
generateHeaders: this.config.generateHeaders,
|
|
@@ -14,6 +14,9 @@ interface IProjectConfig {
|
|
|
14
14
|
/** Output directory for generated files */
|
|
15
15
|
outDir: string;
|
|
16
16
|
|
|
17
|
+
/** Separate output directory for header files (defaults to outDir) */
|
|
18
|
+
headerOutDir?: string;
|
|
19
|
+
|
|
17
20
|
/** Specific files to compile (overrides srcDirs) */
|
|
18
21
|
files?: string[];
|
|
19
22
|
|
|
@@ -7,6 +7,7 @@ import * as Parser from "../parser/grammar/CNextParser";
|
|
|
7
7
|
import ISymbol from "../types/ISymbol";
|
|
8
8
|
import ESymbolKind from "../types/ESymbolKind";
|
|
9
9
|
import ESourceLanguage from "../types/ESourceLanguage";
|
|
10
|
+
import SymbolTable from "./SymbolTable";
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* Collects symbols from a C-Next parse tree
|
|
@@ -16,8 +17,12 @@ class CNextSymbolCollector {
|
|
|
16
17
|
|
|
17
18
|
private symbols: ISymbol[] = [];
|
|
18
19
|
|
|
19
|
-
|
|
20
|
+
// Issue #332: Optional SymbolTable to register struct fields for isStructType() lookup
|
|
21
|
+
private symbolTable: SymbolTable | null;
|
|
22
|
+
|
|
23
|
+
constructor(sourceFile: string, symbolTable?: SymbolTable) {
|
|
20
24
|
this.sourceFile = sourceFile;
|
|
25
|
+
this.symbolTable = symbolTable ?? null;
|
|
21
26
|
}
|
|
22
27
|
|
|
23
28
|
/**
|
|
@@ -166,6 +171,45 @@ class CNextSymbolCollector {
|
|
|
166
171
|
sourceLanguage: ESourceLanguage.CNext,
|
|
167
172
|
isExported: true,
|
|
168
173
|
});
|
|
174
|
+
|
|
175
|
+
// Issue #332: Register struct fields in SymbolTable for isStructType() lookup
|
|
176
|
+
// This enables TypeResolver to identify C-Next structs from included files
|
|
177
|
+
// when determining if & should be added for pointer parameters
|
|
178
|
+
if (this.symbolTable) {
|
|
179
|
+
for (const member of struct.structMember()) {
|
|
180
|
+
const fieldName = member.IDENTIFIER().getText();
|
|
181
|
+
const fieldType = this.getTypeText(member.type());
|
|
182
|
+
|
|
183
|
+
// Check for array dimensions
|
|
184
|
+
const arrayDims = member.arrayDimension();
|
|
185
|
+
const dimensions: number[] = [];
|
|
186
|
+
for (const dim of arrayDims) {
|
|
187
|
+
const sizeExpr = dim.expression();
|
|
188
|
+
if (sizeExpr) {
|
|
189
|
+
const size = parseInt(sizeExpr.getText(), 10);
|
|
190
|
+
if (!isNaN(size)) {
|
|
191
|
+
dimensions.push(size);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
this.symbolTable!.addStructField(
|
|
197
|
+
name,
|
|
198
|
+
fieldName,
|
|
199
|
+
fieldType,
|
|
200
|
+
dimensions.length > 0 ? dimensions : undefined,
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Issue #332: Extract type text from a type context
|
|
208
|
+
* Simple extraction for struct field registration
|
|
209
|
+
*/
|
|
210
|
+
private getTypeText(ctx: Parser.TypeContext | null): string {
|
|
211
|
+
if (!ctx) return "void";
|
|
212
|
+
return ctx.getText();
|
|
169
213
|
}
|
|
170
214
|
|
|
171
215
|
private collectRegister(reg: Parser.RegisterDeclarationContext): void {
|