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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "c-next",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "A safer C for embedded systems development. Transpiles to clean, readable C.",
5
5
  "main": "src/index.ts",
6
6
  "bin": {
@@ -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
- typeInfo.arrayDimensions &&
5598
- typeInfo.arrayDimensions.length > 0;
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
- const bitWidth =
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
- const bitWidth =
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
- outputExtension?: ".c" | ".cpp";
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(' { "outputExtension": ".cpp" }');
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
- outputExtension: ".c" | ".cpp",
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
- outputExtension,
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 cliOutputExtension: ".c" | ".cpp" | undefined;
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
- cliOutputExtension = ".cpp";
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 outputExtension = cliOutputExtension ?? config.outputExtension ?? ".c";
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
- outputExtension,
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 = 1;
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);
@@ -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
- outputExtension: config.outputExtension ?? ".c",
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 needsStructKeyword from cache
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
- return; // Cache hit - skip parsing
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 dual-parse strategy
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
- let cSymbols: ISymbol[] = [];
373
- let cppSymbols: ISymbol[] = [];
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
- // Try C parser first
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
- cSymbols = collector.collect(tree);
386
- } catch {
387
- // C parser failed
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
- // C++ parser failed
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
- const ext = this.config.outputExtension;
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
- /** Output file extension (default: ".c") */
27
- outputExtension?: ".c" | ".cpp";
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;
@@ -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
- outputExtension: this.config.outputExtension,
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
- /** Output file extension (default: ".c") */
33
- outputExtension?: ".c" | ".cpp";
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
  /**