c-next 0.1.6 → 0.1.7

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.7",
4
4
  "description": "A safer C for embedded systems development. Transpiles to clean, readable C.",
5
5
  "main": "src/index.ts",
6
6
  "bin": {
@@ -6749,9 +6749,15 @@ export default class CodeGenerator implements IOrchestrator {
6749
6749
  subscriptDepth >= dimensions.length
6750
6750
  ) {
6751
6751
  // 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 =
6752
+ // Try C-Next types first, then C types, then enum types
6753
+ let bitWidth =
6754
6754
  TYPE_WIDTH[memberType] || C_TYPE_WIDTH[memberType] || 0;
6755
+ // Issue #208: Check if it's a typed enum
6756
+ if (bitWidth === 0 && this.symbolTable) {
6757
+ const enumWidth =
6758
+ this.symbolTable.getEnumBitWidth(memberType);
6759
+ if (enumWidth) bitWidth = enumWidth;
6760
+ }
6755
6761
  if (bitWidth > 0) {
6756
6762
  result = String(bitWidth);
6757
6763
  } else {
@@ -6759,9 +6765,15 @@ export default class CodeGenerator implements IOrchestrator {
6759
6765
  }
6760
6766
  } else {
6761
6767
  // Non-array member -> return bit width
6762
- // Try C-Next types first, then C types
6763
- const bitWidth =
6768
+ // Try C-Next types first, then C types, then enum types
6769
+ let bitWidth =
6764
6770
  TYPE_WIDTH[memberType] || C_TYPE_WIDTH[memberType] || 0;
6771
+ // Issue #208: Check if it's a typed enum
6772
+ if (bitWidth === 0 && this.symbolTable) {
6773
+ const enumWidth =
6774
+ this.symbolTable.getEnumBitWidth(memberType);
6775
+ if (enumWidth) bitWidth = enumWidth;
6776
+ }
6765
6777
  if (bitWidth > 0) {
6766
6778
  result = String(bitWidth);
6767
6779
  } else {
@@ -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
@@ -312,10 +312,11 @@ class Pipeline {
312
312
  if (this.cacheManager?.isValid(file.path)) {
313
313
  const cached = this.cacheManager.getSymbols(file.path);
314
314
  if (cached) {
315
- // Restore symbols, struct fields, and needsStructKeyword from cache
315
+ // Restore symbols, struct fields, needsStructKeyword, and enumBitWidth from cache
316
316
  this.symbolTable.addSymbols(cached.symbols);
317
317
  this.symbolTable.restoreStructFields(cached.structFields);
318
318
  this.symbolTable.restoreNeedsStructKeyword(cached.needsStructKeyword);
319
+ this.symbolTable.restoreEnumBitWidths(cached.enumBitWidth);
319
320
  return; // Cache hit - skip parsing
320
321
  }
321
322
  }
@@ -356,23 +357,35 @@ class Pipeline {
356
357
  const needsStructKeyword = this.extractNeedsStructKeywordForFile(
357
358
  file.path,
358
359
  );
360
+ const enumBitWidth = this.extractEnumBitWidthsForFile(file.path);
359
361
  this.cacheManager.setSymbols(
360
362
  file.path,
361
363
  symbols,
362
364
  structFields,
363
365
  needsStructKeyword,
366
+ enumBitWidth,
364
367
  );
365
368
  }
366
369
  }
367
370
 
368
371
  /**
369
- * Parse a C header using dual-parse strategy
372
+ * Issue #208: Parse a C header using single-parser strategy
373
+ * Uses heuristic detection to choose the appropriate parser
370
374
  */
371
375
  private parseCHeader(content: string, filePath: string): void {
372
- let cSymbols: ISymbol[] = [];
373
- let cppSymbols: ISymbol[] = [];
376
+ if (detectCppSyntax(content)) {
377
+ // Use C++14 parser for headers with C++ syntax (typed enums, classes, etc.)
378
+ this.parseCppHeader(content, filePath);
379
+ } else {
380
+ // Use C parser for pure C headers
381
+ this.parsePureCHeader(content, filePath);
382
+ }
383
+ }
374
384
 
375
- // Try C parser first
385
+ /**
386
+ * Issue #208: Parse a pure C header (no C++ syntax detected)
387
+ */
388
+ private parsePureCHeader(content: string, filePath: string): void {
376
389
  try {
377
390
  const charStream = CharStream.fromString(content);
378
391
  const lexer = new CLexer(charStream);
@@ -382,36 +395,12 @@ class Pipeline {
382
395
 
383
396
  const tree = parser.compilationUnit();
384
397
  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);
398
+ const symbols = collector.collect(tree);
399
+ if (symbols.length > 0) {
400
+ this.symbolTable.addSymbols(symbols);
401
+ }
401
402
  } 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);
403
+ // Silently ignore parse errors in headers
415
404
  }
416
405
  }
417
406
 
@@ -961,6 +950,31 @@ class Pipeline {
961
950
  const allNeedsKeyword = this.symbolTable.getAllNeedsStructKeyword();
962
951
  return structNames.filter((name) => allNeedsKeyword.includes(name));
963
952
  }
953
+
954
+ /**
955
+ * Issue #208: Extract enum bit widths for a specific file
956
+ * Returns enum bit widths for enums defined in that file
957
+ */
958
+ private extractEnumBitWidthsForFile(filePath: string): Map<string, number> {
959
+ const result = new Map<string, number>();
960
+
961
+ // Get enum names defined in this file
962
+ const fileSymbols = this.symbolTable.getSymbolsByFile(filePath);
963
+ const enumNames = fileSymbols
964
+ .filter((s) => s.kind === "enum")
965
+ .map((s) => s.name);
966
+
967
+ // Get bit widths for each enum
968
+ const allBitWidths = this.symbolTable.getAllEnumBitWidths();
969
+ for (const enumName of enumNames) {
970
+ const width = allBitWidths.get(enumName);
971
+ if (width !== undefined) {
972
+ result.set(enumName, width);
973
+ }
974
+ }
975
+
976
+ return result;
977
+ }
964
978
  }
965
979
 
966
980
  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;
@@ -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
  /**