c-next 0.1.6 → 0.1.8
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/package.json +1 -1
- package/src/codegen/CodeGenerator.ts +58 -7
- package/src/index.ts +9 -8
- package/src/pipeline/CacheManager.ts +21 -1
- package/src/pipeline/Pipeline.ts +75 -38
- package/src/pipeline/detectCppSyntax.ts +42 -0
- package/src/pipeline/types/ICachedFileEntry.ts +2 -0
- package/src/pipeline/types/IPipelineConfig.ts +2 -2
- package/src/project/Project.ts +1 -1
- package/src/project/types/IProjectConfig.ts +2 -2
- package/src/symbols/CppSymbolCollector.ts +60 -0
- package/src/symbols/SymbolTable.ts +44 -0
package/package.json
CHANGED
|
@@ -5592,10 +5592,12 @@ export default class CodeGenerator implements IOrchestrator {
|
|
|
5592
5592
|
|
|
5593
5593
|
// ADR-040: ISR arrays use normal array indexing, not bit manipulation
|
|
5594
5594
|
// Also handle any array type that isn't an integer scalar
|
|
5595
|
+
// Issue #213: String parameters (isString=true) should also use memcpy for slice assignment
|
|
5595
5596
|
const isActualArray =
|
|
5596
|
-
typeInfo?.isArray &&
|
|
5597
|
-
|
|
5598
|
-
|
|
5597
|
+
(typeInfo?.isArray &&
|
|
5598
|
+
typeInfo.arrayDimensions &&
|
|
5599
|
+
typeInfo.arrayDimensions.length > 0) ||
|
|
5600
|
+
typeInfo?.isString;
|
|
5599
5601
|
const isISRType = typeInfo?.baseType === "ISR";
|
|
5600
5602
|
|
|
5601
5603
|
if (isActualArray || isISRType) {
|
|
@@ -5617,6 +5619,16 @@ export default class CodeGenerator implements IOrchestrator {
|
|
|
5617
5619
|
|
|
5618
5620
|
// Generate bounds-checked memcpy
|
|
5619
5621
|
// if (offset + length <= sizeof(buffer)) { memcpy(&buffer[offset], &value, length); }
|
|
5622
|
+
// Issue #213: For string parameters, use capacity for bounds checking
|
|
5623
|
+
// since sizeof(char*) gives pointer size, not buffer size
|
|
5624
|
+
if (
|
|
5625
|
+
typeInfo?.isString &&
|
|
5626
|
+
typeInfo.stringCapacity &&
|
|
5627
|
+
!typeInfo.isArray
|
|
5628
|
+
) {
|
|
5629
|
+
const capacity = typeInfo.stringCapacity + 1;
|
|
5630
|
+
return `if (${offset} + ${length} <= ${capacity}) { memcpy(&${name}[${offset}], &${value}, ${length}); }`;
|
|
5631
|
+
}
|
|
5620
5632
|
return `if (${offset} + ${length} <= sizeof(${name})) { memcpy(&${name}[${offset}], &${value}, ${length}); }`;
|
|
5621
5633
|
}
|
|
5622
5634
|
|
|
@@ -6693,6 +6705,33 @@ export default class CodeGenerator implements IOrchestrator {
|
|
|
6693
6705
|
continue; // Skip further processing, this just sets the base identifier
|
|
6694
6706
|
}
|
|
6695
6707
|
|
|
6708
|
+
// Issue #212: Check if 'length' is a scope variable before treating it as a property accessor
|
|
6709
|
+
// When accessing this.length, we need to check if 'length' is a scope variable name
|
|
6710
|
+
// If so, transform it to the scope variable (e.g., Scope_length) instead of treating
|
|
6711
|
+
// it as the .length property accessor
|
|
6712
|
+
if (result === "__THIS_SCOPE__" && memberName === "length") {
|
|
6713
|
+
if (!this.context.currentScope) {
|
|
6714
|
+
throw new Error("Error: 'this' can only be used inside a scope");
|
|
6715
|
+
}
|
|
6716
|
+
const members = this.context.scopeMembers.get(
|
|
6717
|
+
this.context.currentScope,
|
|
6718
|
+
);
|
|
6719
|
+
if (members && members.has("length")) {
|
|
6720
|
+
// This is a scope variable named 'length', not a property accessor
|
|
6721
|
+
result = `${this.context.currentScope}_${memberName}`;
|
|
6722
|
+
currentIdentifier = result;
|
|
6723
|
+
// Set struct type for chained access if applicable
|
|
6724
|
+
const resolvedTypeInfo = this.context.typeRegistry.get(result);
|
|
6725
|
+
if (
|
|
6726
|
+
resolvedTypeInfo &&
|
|
6727
|
+
this.isKnownStruct(resolvedTypeInfo.baseType)
|
|
6728
|
+
) {
|
|
6729
|
+
currentStructType = resolvedTypeInfo.baseType;
|
|
6730
|
+
}
|
|
6731
|
+
continue; // Skip the .length property handler
|
|
6732
|
+
}
|
|
6733
|
+
}
|
|
6734
|
+
|
|
6696
6735
|
// Handle .length property for arrays, strings, and integers
|
|
6697
6736
|
if (memberName === "length") {
|
|
6698
6737
|
// Special case: main function's args.length -> argc
|
|
@@ -6749,9 +6788,15 @@ export default class CodeGenerator implements IOrchestrator {
|
|
|
6749
6788
|
subscriptDepth >= dimensions.length
|
|
6750
6789
|
) {
|
|
6751
6790
|
// Array member fully subscripted (e.g., ts.arr[0][1].length) -> return element bit width
|
|
6752
|
-
// Try C-Next types first, then C types
|
|
6753
|
-
|
|
6791
|
+
// Try C-Next types first, then C types, then enum types
|
|
6792
|
+
let bitWidth =
|
|
6754
6793
|
TYPE_WIDTH[memberType] || C_TYPE_WIDTH[memberType] || 0;
|
|
6794
|
+
// Issue #208: Check if it's a typed enum
|
|
6795
|
+
if (bitWidth === 0 && this.symbolTable) {
|
|
6796
|
+
const enumWidth =
|
|
6797
|
+
this.symbolTable.getEnumBitWidth(memberType);
|
|
6798
|
+
if (enumWidth) bitWidth = enumWidth;
|
|
6799
|
+
}
|
|
6755
6800
|
if (bitWidth > 0) {
|
|
6756
6801
|
result = String(bitWidth);
|
|
6757
6802
|
} else {
|
|
@@ -6759,9 +6804,15 @@ export default class CodeGenerator implements IOrchestrator {
|
|
|
6759
6804
|
}
|
|
6760
6805
|
} else {
|
|
6761
6806
|
// Non-array member -> return bit width
|
|
6762
|
-
// Try C-Next types first, then C types
|
|
6763
|
-
|
|
6807
|
+
// Try C-Next types first, then C types, then enum types
|
|
6808
|
+
let bitWidth =
|
|
6764
6809
|
TYPE_WIDTH[memberType] || C_TYPE_WIDTH[memberType] || 0;
|
|
6810
|
+
// Issue #208: Check if it's a typed enum
|
|
6811
|
+
if (bitWidth === 0 && this.symbolTable) {
|
|
6812
|
+
const enumWidth =
|
|
6813
|
+
this.symbolTable.getEnumBitWidth(memberType);
|
|
6814
|
+
if (enumWidth) bitWidth = enumWidth;
|
|
6815
|
+
}
|
|
6765
6816
|
if (bitWidth > 0) {
|
|
6766
6817
|
result = String(bitWidth);
|
|
6767
6818
|
} else {
|
package/src/index.ts
CHANGED
|
@@ -25,7 +25,8 @@ const packageJson = require("../package.json");
|
|
|
25
25
|
* C-Next configuration file options
|
|
26
26
|
*/
|
|
27
27
|
interface ICNextConfig {
|
|
28
|
-
|
|
28
|
+
/** Issue #211: Force C++ output. Auto-detection may also enable this. */
|
|
29
|
+
cppRequired?: boolean;
|
|
29
30
|
generateHeaders?: boolean;
|
|
30
31
|
debugMode?: boolean;
|
|
31
32
|
target?: string; // ADR-049: Target platform (e.g., "teensy41", "cortex-m0")
|
|
@@ -130,7 +131,7 @@ function showHelp(): void {
|
|
|
130
131
|
console.log(" cnext.config.json, .cnext.json, .cnextrc");
|
|
131
132
|
console.log("");
|
|
132
133
|
console.log("Config example:");
|
|
133
|
-
console.log(' { "
|
|
134
|
+
console.log(' { "cppRequired": true }');
|
|
134
135
|
console.log("");
|
|
135
136
|
console.log("A safer C for embedded systems development.");
|
|
136
137
|
}
|
|
@@ -188,7 +189,7 @@ async function runUnifiedMode(
|
|
|
188
189
|
generateHeaders: boolean,
|
|
189
190
|
preprocess: boolean,
|
|
190
191
|
verbose: boolean,
|
|
191
|
-
|
|
192
|
+
cppRequired: boolean,
|
|
192
193
|
noCache: boolean,
|
|
193
194
|
): Promise<void> {
|
|
194
195
|
// Step 1: Expand directories to .cnx files
|
|
@@ -257,7 +258,7 @@ async function runUnifiedMode(
|
|
|
257
258
|
generateHeaders,
|
|
258
259
|
preprocess,
|
|
259
260
|
defines,
|
|
260
|
-
|
|
261
|
+
cppRequired,
|
|
261
262
|
noCache,
|
|
262
263
|
});
|
|
263
264
|
|
|
@@ -466,7 +467,7 @@ async function main(): Promise<void> {
|
|
|
466
467
|
const includeDirs: string[] = [];
|
|
467
468
|
const defines: Record<string, string | boolean> = {};
|
|
468
469
|
let cliGenerateHeaders: boolean | undefined;
|
|
469
|
-
let
|
|
470
|
+
let cliCppRequired: boolean | undefined;
|
|
470
471
|
let preprocess = true;
|
|
471
472
|
let verbose = false;
|
|
472
473
|
let noCache = false;
|
|
@@ -483,7 +484,7 @@ async function main(): Promise<void> {
|
|
|
483
484
|
} else if (arg === "--exclude-headers") {
|
|
484
485
|
cliGenerateHeaders = false;
|
|
485
486
|
} else if (arg === "--cpp") {
|
|
486
|
-
|
|
487
|
+
cliCppRequired = true;
|
|
487
488
|
} else if (arg === "--no-preprocess") {
|
|
488
489
|
preprocess = false;
|
|
489
490
|
} else if (arg === "--no-cache") {
|
|
@@ -508,7 +509,7 @@ async function main(): Promise<void> {
|
|
|
508
509
|
|
|
509
510
|
// Apply config defaults, CLI flags take precedence
|
|
510
511
|
const generateHeaders = cliGenerateHeaders ?? config.generateHeaders ?? true;
|
|
511
|
-
const
|
|
512
|
+
const cppRequired = cliCppRequired ?? config.cppRequired ?? false;
|
|
512
513
|
|
|
513
514
|
// Unified mode - always use Project class with header discovery
|
|
514
515
|
if (inputFiles.length === 0) {
|
|
@@ -525,7 +526,7 @@ async function main(): Promise<void> {
|
|
|
525
526
|
generateHeaders,
|
|
526
527
|
preprocess,
|
|
527
528
|
verbose,
|
|
528
|
-
|
|
529
|
+
cppRequired,
|
|
529
530
|
noCache,
|
|
530
531
|
);
|
|
531
532
|
}
|
|
@@ -27,7 +27,7 @@ import ICachedFileEntry from "./types/ICachedFileEntry";
|
|
|
27
27
|
import ISerializedSymbol from "./types/ISerializedSymbol";
|
|
28
28
|
|
|
29
29
|
/** Current cache format version - increment when serialization format changes */
|
|
30
|
-
const CACHE_VERSION =
|
|
30
|
+
const CACHE_VERSION = 2; // Issue #208: Added enumBitWidth
|
|
31
31
|
|
|
32
32
|
// Read version from package.json
|
|
33
33
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
@@ -109,6 +109,7 @@ class CacheManager {
|
|
|
109
109
|
symbols: ISymbol[];
|
|
110
110
|
structFields: Map<string, Map<string, IStructFieldInfo>>;
|
|
111
111
|
needsStructKeyword: string[];
|
|
112
|
+
enumBitWidth: Map<string, number>;
|
|
112
113
|
} | null {
|
|
113
114
|
const entry = this.entries.get(filePath);
|
|
114
115
|
if (!entry) {
|
|
@@ -128,10 +129,19 @@ class CacheManager {
|
|
|
128
129
|
structFields.set(structName, fieldMap);
|
|
129
130
|
}
|
|
130
131
|
|
|
132
|
+
// Issue #208: Convert enum bit widths from plain object to Map
|
|
133
|
+
const enumBitWidth = new Map<string, number>();
|
|
134
|
+
if (entry.enumBitWidth) {
|
|
135
|
+
for (const [enumName, width] of Object.entries(entry.enumBitWidth)) {
|
|
136
|
+
enumBitWidth.set(enumName, width);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
131
140
|
return {
|
|
132
141
|
symbols,
|
|
133
142
|
structFields,
|
|
134
143
|
needsStructKeyword: entry.needsStructKeyword ?? [],
|
|
144
|
+
enumBitWidth,
|
|
135
145
|
};
|
|
136
146
|
}
|
|
137
147
|
|
|
@@ -143,6 +153,7 @@ class CacheManager {
|
|
|
143
153
|
symbols: ISymbol[],
|
|
144
154
|
structFields: Map<string, Map<string, IStructFieldInfo>>,
|
|
145
155
|
needsStructKeyword?: string[],
|
|
156
|
+
enumBitWidth?: Map<string, number>,
|
|
146
157
|
): void {
|
|
147
158
|
// Get current mtime
|
|
148
159
|
let mtime: number;
|
|
@@ -169,6 +180,14 @@ class CacheManager {
|
|
|
169
180
|
}
|
|
170
181
|
}
|
|
171
182
|
|
|
183
|
+
// Issue #208: Convert enum bit widths from Map to plain object
|
|
184
|
+
const serializedEnumBitWidth: Record<string, number> = {};
|
|
185
|
+
if (enumBitWidth) {
|
|
186
|
+
for (const [enumName, width] of enumBitWidth) {
|
|
187
|
+
serializedEnumBitWidth[enumName] = width;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
172
191
|
// Create entry
|
|
173
192
|
const entry: ICachedFileEntry = {
|
|
174
193
|
filePath,
|
|
@@ -176,6 +195,7 @@ class CacheManager {
|
|
|
176
195
|
symbols: serializedSymbols,
|
|
177
196
|
structFields: serializedFields,
|
|
178
197
|
needsStructKeyword,
|
|
198
|
+
enumBitWidth: serializedEnumBitWidth,
|
|
179
199
|
};
|
|
180
200
|
|
|
181
201
|
this.entries.set(filePath, entry);
|
package/src/pipeline/Pipeline.ts
CHANGED
|
@@ -26,7 +26,6 @@ import { CPP14Parser } from "../parser/cpp/grammar/CPP14Parser";
|
|
|
26
26
|
import CodeGenerator from "../codegen/CodeGenerator";
|
|
27
27
|
import HeaderGenerator from "../codegen/HeaderGenerator";
|
|
28
28
|
import SymbolTable from "../symbols/SymbolTable";
|
|
29
|
-
import ISymbol from "../types/ISymbol";
|
|
30
29
|
import CNextSymbolCollector from "../symbols/CNextSymbolCollector";
|
|
31
30
|
import CSymbolCollector from "../symbols/CSymbolCollector";
|
|
32
31
|
import CppSymbolCollector from "../symbols/CppSymbolCollector";
|
|
@@ -44,6 +43,7 @@ import IFileResult from "./types/IFileResult";
|
|
|
44
43
|
import runAnalyzers from "./runAnalyzers";
|
|
45
44
|
import CacheManager from "./CacheManager";
|
|
46
45
|
import IStructFieldInfo from "../symbols/types/IStructFieldInfo";
|
|
46
|
+
import detectCppSyntax from "./detectCppSyntax";
|
|
47
47
|
|
|
48
48
|
/**
|
|
49
49
|
* Unified transpilation pipeline
|
|
@@ -56,6 +56,8 @@ class Pipeline {
|
|
|
56
56
|
private headerGenerator: HeaderGenerator;
|
|
57
57
|
private warnings: string[];
|
|
58
58
|
private cacheManager: CacheManager | null;
|
|
59
|
+
/** Issue #211: Tracks if C++ output is needed (one-way flag, false → true only) */
|
|
60
|
+
private cppDetected: boolean;
|
|
59
61
|
|
|
60
62
|
constructor(config: IPipelineConfig) {
|
|
61
63
|
// Apply defaults
|
|
@@ -66,7 +68,7 @@ class Pipeline {
|
|
|
66
68
|
defines: config.defines ?? {},
|
|
67
69
|
preprocess: config.preprocess ?? true,
|
|
68
70
|
generateHeaders: config.generateHeaders ?? true,
|
|
69
|
-
|
|
71
|
+
cppRequired: config.cppRequired ?? false,
|
|
70
72
|
parseOnly: config.parseOnly ?? false,
|
|
71
73
|
debugMode: config.debugMode ?? false,
|
|
72
74
|
target: config.target ?? "",
|
|
@@ -74,6 +76,9 @@ class Pipeline {
|
|
|
74
76
|
noCache: config.noCache ?? false,
|
|
75
77
|
};
|
|
76
78
|
|
|
79
|
+
// Issue #211: Initialize cppDetected from config (--cpp flag sets this)
|
|
80
|
+
this.cppDetected = this.config.cppRequired;
|
|
81
|
+
|
|
77
82
|
this.symbolTable = new SymbolTable();
|
|
78
83
|
this.preprocessor = new Preprocessor();
|
|
79
84
|
this.codeGenerator = new CodeGenerator();
|
|
@@ -312,11 +317,25 @@ class Pipeline {
|
|
|
312
317
|
if (this.cacheManager?.isValid(file.path)) {
|
|
313
318
|
const cached = this.cacheManager.getSymbols(file.path);
|
|
314
319
|
if (cached) {
|
|
315
|
-
// Restore symbols, struct fields, and
|
|
320
|
+
// Restore symbols, struct fields, needsStructKeyword, and enumBitWidth from cache
|
|
316
321
|
this.symbolTable.addSymbols(cached.symbols);
|
|
317
322
|
this.symbolTable.restoreStructFields(cached.structFields);
|
|
318
323
|
this.symbolTable.restoreNeedsStructKeyword(cached.needsStructKeyword);
|
|
319
|
-
|
|
324
|
+
this.symbolTable.restoreEnumBitWidths(cached.enumBitWidth);
|
|
325
|
+
|
|
326
|
+
// Issue #211: Still check for C++ syntax even on cache hit
|
|
327
|
+
// The detection is cheap (regex only) and ensures cppDetected is set correctly
|
|
328
|
+
if (file.type === EFileType.CHeader) {
|
|
329
|
+
const content = readFileSync(file.path, "utf-8");
|
|
330
|
+
if (detectCppSyntax(content)) {
|
|
331
|
+
this.cppDetected = true;
|
|
332
|
+
}
|
|
333
|
+
} else if (file.type === EFileType.CppHeader) {
|
|
334
|
+
// .hpp files are always C++
|
|
335
|
+
this.cppDetected = true;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
return; // Cache hit - skip full parsing
|
|
320
339
|
}
|
|
321
340
|
}
|
|
322
341
|
|
|
@@ -346,6 +365,8 @@ class Pipeline {
|
|
|
346
365
|
if (file.type === EFileType.CHeader) {
|
|
347
366
|
this.parseCHeader(content, file.path);
|
|
348
367
|
} else if (file.type === EFileType.CppHeader) {
|
|
368
|
+
// Issue #211: .hpp files are always C++
|
|
369
|
+
this.cppDetected = true;
|
|
349
370
|
this.parseCppHeader(content, file.path);
|
|
350
371
|
}
|
|
351
372
|
|
|
@@ -356,23 +377,37 @@ class Pipeline {
|
|
|
356
377
|
const needsStructKeyword = this.extractNeedsStructKeywordForFile(
|
|
357
378
|
file.path,
|
|
358
379
|
);
|
|
380
|
+
const enumBitWidth = this.extractEnumBitWidthsForFile(file.path);
|
|
359
381
|
this.cacheManager.setSymbols(
|
|
360
382
|
file.path,
|
|
361
383
|
symbols,
|
|
362
384
|
structFields,
|
|
363
385
|
needsStructKeyword,
|
|
386
|
+
enumBitWidth,
|
|
364
387
|
);
|
|
365
388
|
}
|
|
366
389
|
}
|
|
367
390
|
|
|
368
391
|
/**
|
|
369
|
-
* Parse a C header using
|
|
392
|
+
* Issue #208: Parse a C header using single-parser strategy
|
|
393
|
+
* Uses heuristic detection to choose the appropriate parser
|
|
370
394
|
*/
|
|
371
395
|
private parseCHeader(content: string, filePath: string): void {
|
|
372
|
-
|
|
373
|
-
|
|
396
|
+
if (detectCppSyntax(content)) {
|
|
397
|
+
// Issue #211: C++ detected, set flag for .cpp output
|
|
398
|
+
this.cppDetected = true;
|
|
399
|
+
// Use C++14 parser for headers with C++ syntax (typed enums, classes, etc.)
|
|
400
|
+
this.parseCppHeader(content, filePath);
|
|
401
|
+
} else {
|
|
402
|
+
// Use C parser for pure C headers
|
|
403
|
+
this.parsePureCHeader(content, filePath);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
374
406
|
|
|
375
|
-
|
|
407
|
+
/**
|
|
408
|
+
* Issue #208: Parse a pure C header (no C++ syntax detected)
|
|
409
|
+
*/
|
|
410
|
+
private parsePureCHeader(content: string, filePath: string): void {
|
|
376
411
|
try {
|
|
377
412
|
const charStream = CharStream.fromString(content);
|
|
378
413
|
const lexer = new CLexer(charStream);
|
|
@@ -382,36 +417,12 @@ class Pipeline {
|
|
|
382
417
|
|
|
383
418
|
const tree = parser.compilationUnit();
|
|
384
419
|
const collector = new CSymbolCollector(filePath, this.symbolTable);
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
// Also try C++ parser for better C++11 support
|
|
391
|
-
try {
|
|
392
|
-
const cppCharStream = CharStream.fromString(content);
|
|
393
|
-
const cppLexer = new CPP14Lexer(cppCharStream);
|
|
394
|
-
const cppTokenStream = new CommonTokenStream(cppLexer);
|
|
395
|
-
const cppParser = new CPP14Parser(cppTokenStream);
|
|
396
|
-
cppParser.removeErrorListeners();
|
|
397
|
-
|
|
398
|
-
const cppTree = cppParser.translationUnit();
|
|
399
|
-
const cppCollector = new CppSymbolCollector(filePath, this.symbolTable);
|
|
400
|
-
cppSymbols = cppCollector.collect(cppTree);
|
|
420
|
+
const symbols = collector.collect(tree);
|
|
421
|
+
if (symbols.length > 0) {
|
|
422
|
+
this.symbolTable.addSymbols(symbols);
|
|
423
|
+
}
|
|
401
424
|
} catch {
|
|
402
|
-
//
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
// Merge symbols: prefer C++ but supplement with C-only symbols
|
|
406
|
-
const cppSymbolNames = new Set(cppSymbols.map((s) => s.name));
|
|
407
|
-
const additionalSymbols = cSymbols.filter(
|
|
408
|
-
(s) => !cppSymbolNames.has(s.name),
|
|
409
|
-
);
|
|
410
|
-
|
|
411
|
-
const allSymbols = [...cppSymbols, ...additionalSymbols];
|
|
412
|
-
|
|
413
|
-
if (allSymbols.length > 0) {
|
|
414
|
-
this.symbolTable.addSymbols(allSymbols);
|
|
425
|
+
// Silently ignore parse errors in headers
|
|
415
426
|
}
|
|
416
427
|
}
|
|
417
428
|
|
|
@@ -591,7 +602,8 @@ class Pipeline {
|
|
|
591
602
|
* Get output path for a file
|
|
592
603
|
*/
|
|
593
604
|
private getOutputPath(file: IDiscoveredFile): string {
|
|
594
|
-
|
|
605
|
+
// Issue #211: Derive extension from cppDetected flag
|
|
606
|
+
const ext = this.cppDetected ? ".cpp" : ".c";
|
|
595
607
|
const outputName = basename(file.path).replace(/\.cnx$|\.cnext$/, ext);
|
|
596
608
|
|
|
597
609
|
// Check if file is in any input directory (for preserving structure)
|
|
@@ -961,6 +973,31 @@ class Pipeline {
|
|
|
961
973
|
const allNeedsKeyword = this.symbolTable.getAllNeedsStructKeyword();
|
|
962
974
|
return structNames.filter((name) => allNeedsKeyword.includes(name));
|
|
963
975
|
}
|
|
976
|
+
|
|
977
|
+
/**
|
|
978
|
+
* Issue #208: Extract enum bit widths for a specific file
|
|
979
|
+
* Returns enum bit widths for enums defined in that file
|
|
980
|
+
*/
|
|
981
|
+
private extractEnumBitWidthsForFile(filePath: string): Map<string, number> {
|
|
982
|
+
const result = new Map<string, number>();
|
|
983
|
+
|
|
984
|
+
// Get enum names defined in this file
|
|
985
|
+
const fileSymbols = this.symbolTable.getSymbolsByFile(filePath);
|
|
986
|
+
const enumNames = fileSymbols
|
|
987
|
+
.filter((s) => s.kind === "enum")
|
|
988
|
+
.map((s) => s.name);
|
|
989
|
+
|
|
990
|
+
// Get bit widths for each enum
|
|
991
|
+
const allBitWidths = this.symbolTable.getAllEnumBitWidths();
|
|
992
|
+
for (const enumName of enumNames) {
|
|
993
|
+
const width = allBitWidths.get(enumName);
|
|
994
|
+
if (width !== undefined) {
|
|
995
|
+
result.set(enumName, width);
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
return result;
|
|
1000
|
+
}
|
|
964
1001
|
}
|
|
965
1002
|
|
|
966
1003
|
export default Pipeline;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Issue #208: Detect if header content contains C++ syntax requiring C++14 parser
|
|
3
|
+
*
|
|
4
|
+
* This heuristic determines whether to parse a .h file with the C or C++ parser.
|
|
5
|
+
* Previously, the pipeline would parse ALL .h files with BOTH parsers and merge,
|
|
6
|
+
* which was wasteful and led to issues with C++-specific features like typed enums.
|
|
7
|
+
*
|
|
8
|
+
* C++ indicators:
|
|
9
|
+
* - Typed enums: enum Name : type { ... }
|
|
10
|
+
* - Class/struct inheritance: class Foo : public Bar
|
|
11
|
+
* - Namespaces: namespace Foo { ... }
|
|
12
|
+
* - Templates: template<...>
|
|
13
|
+
* - Access specifiers: public:, private:, protected:
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Detect if header content contains C++ syntax requiring C++14 parser
|
|
18
|
+
* @param content Raw header file content
|
|
19
|
+
* @returns true if C++ parser should be used, false for C parser
|
|
20
|
+
*/
|
|
21
|
+
function detectCppSyntax(content: string): boolean {
|
|
22
|
+
// Typed enums: enum Name : type { (C++14 feature, key for Issue #208)
|
|
23
|
+
if (/enum\s+\w+\s*:\s*\w+\s*\{/.test(content)) return true;
|
|
24
|
+
|
|
25
|
+
// Class/struct with inheritance: class Foo : public/private/protected Bar
|
|
26
|
+
if (/\b(class|struct)\s+\w+\s*:\s*(public|private|protected)/.test(content))
|
|
27
|
+
return true;
|
|
28
|
+
|
|
29
|
+
// namespace keyword: namespace Foo {
|
|
30
|
+
if (/\bnamespace\s+\w+/.test(content)) return true;
|
|
31
|
+
|
|
32
|
+
// template declarations: template<...>
|
|
33
|
+
if (/\btemplate\s*</.test(content)) return true;
|
|
34
|
+
|
|
35
|
+
// Access specifiers at line start (class members)
|
|
36
|
+
if (/^\s*(public|private|protected)\s*:/m.test(content)) return true;
|
|
37
|
+
|
|
38
|
+
// Default to C parser for pure C headers
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export default detectCppSyntax;
|
|
@@ -16,6 +16,8 @@ interface ICachedFileEntry {
|
|
|
16
16
|
structFields: Record<string, Record<string, IStructFieldInfo>>;
|
|
17
17
|
/** Issue #196 Bug 3: Struct names requiring 'struct' keyword in C */
|
|
18
18
|
needsStructKeyword?: string[];
|
|
19
|
+
/** Issue #208: Enum bit widths from typed enums (enum name -> bit width) */
|
|
20
|
+
enumBitWidth?: Record<string, number>;
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
export default ICachedFileEntry;
|
|
@@ -23,8 +23,8 @@ interface IPipelineConfig {
|
|
|
23
23
|
/** Whether to generate .h files for exported symbols (default: true) */
|
|
24
24
|
generateHeaders?: boolean;
|
|
25
25
|
|
|
26
|
-
/**
|
|
27
|
-
|
|
26
|
+
/** Issue #211: Force C++ output (--cpp flag). Auto-detection may also enable this. */
|
|
27
|
+
cppRequired?: boolean;
|
|
28
28
|
|
|
29
29
|
/** Parse only mode - no code generation */
|
|
30
30
|
parseOnly?: boolean;
|
package/src/project/Project.ts
CHANGED
|
@@ -49,7 +49,7 @@ class Project {
|
|
|
49
49
|
defines: this.config.defines,
|
|
50
50
|
preprocess: this.config.preprocess,
|
|
51
51
|
generateHeaders: this.config.generateHeaders,
|
|
52
|
-
|
|
52
|
+
cppRequired: this.config.cppRequired,
|
|
53
53
|
noCache: this.config.noCache,
|
|
54
54
|
});
|
|
55
55
|
}
|
|
@@ -29,8 +29,8 @@ interface IProjectConfig {
|
|
|
29
29
|
/** Additional preprocessor defines */
|
|
30
30
|
defines?: Record<string, string | boolean>;
|
|
31
31
|
|
|
32
|
-
/**
|
|
33
|
-
|
|
32
|
+
/** Issue #211: Force C++ output. Auto-detection may also enable this. */
|
|
33
|
+
cppRequired?: boolean;
|
|
34
34
|
|
|
35
35
|
/** Issue #183: Disable symbol caching (default: false = cache enabled) */
|
|
36
36
|
noCache?: boolean;
|
|
@@ -384,6 +384,66 @@ class CppSymbolCollector {
|
|
|
384
384
|
isExported: true,
|
|
385
385
|
parent: this.currentNamespace,
|
|
386
386
|
});
|
|
387
|
+
|
|
388
|
+
// Issue #208: Extract enum backing type for typed enums (e.g., enum EPressureType : uint8_t)
|
|
389
|
+
if (this.symbolTable) {
|
|
390
|
+
const enumbase = enumHead.enumbase?.();
|
|
391
|
+
if (enumbase) {
|
|
392
|
+
const typeSpecSeq = enumbase.typeSpecifierSeq?.();
|
|
393
|
+
if (typeSpecSeq) {
|
|
394
|
+
const typeName = typeSpecSeq.getText();
|
|
395
|
+
const bitWidth = this.getTypeWidth(typeName);
|
|
396
|
+
if (bitWidth > 0) {
|
|
397
|
+
this.symbolTable.addEnumBitWidth(fullName, bitWidth);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Issue #208: Map C/C++ type names to their bit widths
|
|
406
|
+
* Supports standard integer types used as enum backing types
|
|
407
|
+
*/
|
|
408
|
+
private getTypeWidth(typeName: string): number {
|
|
409
|
+
const typeWidths: Record<string, number> = {
|
|
410
|
+
// stdint.h types
|
|
411
|
+
uint8_t: 8,
|
|
412
|
+
int8_t: 8,
|
|
413
|
+
uint16_t: 16,
|
|
414
|
+
int16_t: 16,
|
|
415
|
+
uint32_t: 32,
|
|
416
|
+
int32_t: 32,
|
|
417
|
+
uint64_t: 64,
|
|
418
|
+
int64_t: 64,
|
|
419
|
+
// Standard C types (common sizes)
|
|
420
|
+
char: 8,
|
|
421
|
+
"signed char": 8,
|
|
422
|
+
"unsigned char": 8,
|
|
423
|
+
short: 16,
|
|
424
|
+
"short int": 16,
|
|
425
|
+
"signed short": 16,
|
|
426
|
+
"signed short int": 16,
|
|
427
|
+
"unsigned short": 16,
|
|
428
|
+
"unsigned short int": 16,
|
|
429
|
+
int: 32,
|
|
430
|
+
"signed int": 32,
|
|
431
|
+
unsigned: 32,
|
|
432
|
+
"unsigned int": 32,
|
|
433
|
+
long: 32,
|
|
434
|
+
"long int": 32,
|
|
435
|
+
"signed long": 32,
|
|
436
|
+
"signed long int": 32,
|
|
437
|
+
"unsigned long": 32,
|
|
438
|
+
"unsigned long int": 32,
|
|
439
|
+
"long long": 64,
|
|
440
|
+
"long long int": 64,
|
|
441
|
+
"signed long long": 64,
|
|
442
|
+
"signed long long int": 64,
|
|
443
|
+
"unsigned long long": 64,
|
|
444
|
+
"unsigned long long int": 64,
|
|
445
|
+
};
|
|
446
|
+
return typeWidths[typeName] ?? 0;
|
|
387
447
|
}
|
|
388
448
|
|
|
389
449
|
// Helper methods
|
|
@@ -33,6 +33,12 @@ class SymbolTable {
|
|
|
33
33
|
*/
|
|
34
34
|
private needsStructKeyword: Set<string> = new Set();
|
|
35
35
|
|
|
36
|
+
/**
|
|
37
|
+
* Issue #208: Track enum backing type bit widths
|
|
38
|
+
* C++14 typed enums: enum Name : uint8_t { ... } have explicit bit widths
|
|
39
|
+
*/
|
|
40
|
+
private enumBitWidth: Map<string, number> = new Map();
|
|
41
|
+
|
|
36
42
|
/**
|
|
37
43
|
* Add a symbol to the table
|
|
38
44
|
*/
|
|
@@ -237,6 +243,42 @@ class SymbolTable {
|
|
|
237
243
|
}
|
|
238
244
|
}
|
|
239
245
|
|
|
246
|
+
/**
|
|
247
|
+
* Issue #208: Add enum bit width for a typed enum
|
|
248
|
+
* @param enumName Name of the enum (e.g., "EPressureType")
|
|
249
|
+
* @param bitWidth Bit width from backing type (e.g., 8 for uint8_t)
|
|
250
|
+
*/
|
|
251
|
+
addEnumBitWidth(enumName: string, bitWidth: number): void {
|
|
252
|
+
this.enumBitWidth.set(enumName, bitWidth);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Issue #208: Get enum bit width for a typed enum
|
|
257
|
+
* @param enumName Name of the enum
|
|
258
|
+
* @returns Bit width or undefined if not a typed enum
|
|
259
|
+
*/
|
|
260
|
+
getEnumBitWidth(enumName: string): number | undefined {
|
|
261
|
+
return this.enumBitWidth.get(enumName);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Issue #208: Get all enum bit widths for cache serialization
|
|
266
|
+
* @returns Map of enum name -> bit width
|
|
267
|
+
*/
|
|
268
|
+
getAllEnumBitWidths(): Map<string, number> {
|
|
269
|
+
return this.enumBitWidth;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Issue #208: Restore enum bit widths from cache
|
|
274
|
+
* @param bitWidths Map of enum name -> bit width
|
|
275
|
+
*/
|
|
276
|
+
restoreEnumBitWidths(bitWidths: Map<string, number>): void {
|
|
277
|
+
for (const [enumName, width] of bitWidths) {
|
|
278
|
+
this.enumBitWidth.set(enumName, width);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
240
282
|
/**
|
|
241
283
|
* Get all fields for a struct
|
|
242
284
|
* @param structName Name of the struct
|
|
@@ -301,6 +343,8 @@ class SymbolTable {
|
|
|
301
343
|
this.symbols.clear();
|
|
302
344
|
this.byFile.clear();
|
|
303
345
|
this.structFields.clear();
|
|
346
|
+
this.needsStructKeyword.clear();
|
|
347
|
+
this.enumBitWidth.clear();
|
|
304
348
|
}
|
|
305
349
|
|
|
306
350
|
/**
|