c-next 0.1.8 → 0.1.10

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.
Files changed (29) hide show
  1. package/README.md +36 -14
  2. package/package.json +6 -4
  3. package/src/analysis/ParameterNamingAnalyzer.ts +121 -0
  4. package/src/analysis/types/IParameterNamingError.ts +22 -0
  5. package/src/codegen/CodeGenerator.ts +89 -13
  6. package/src/codegen/HeaderGenerator.ts +64 -61
  7. package/src/codegen/SymbolCollector.ts +190 -0
  8. package/src/codegen/generators/GeneratorRegistry.ts +15 -6
  9. package/src/codegen/generators/ISymbolInfo.ts +17 -0
  10. package/src/codegen/generators/declarationGenerators/ScopeGenerator.ts +84 -3
  11. package/src/codegen/generators/expressions/BinaryExprGenerator.ts +118 -19
  12. package/src/codegen/generators/expressions/CallExprGenerator.ts +6 -3
  13. package/src/codegen/generators/support/HelperGenerator.ts +49 -17
  14. package/src/codegen/headerGenerators/IHeaderTypeInput.ts +32 -0
  15. package/src/codegen/headerGenerators/generateBitmapHeader.test.ts +212 -0
  16. package/src/codegen/headerGenerators/generateBitmapHeader.ts +58 -0
  17. package/src/codegen/headerGenerators/generateEnumHeader.test.ts +167 -0
  18. package/src/codegen/headerGenerators/generateEnumHeader.ts +53 -0
  19. package/src/codegen/headerGenerators/generateStructHeader.test.ts +177 -0
  20. package/src/codegen/headerGenerators/generateStructHeader.ts +60 -0
  21. package/src/codegen/headerGenerators/index.ts +18 -0
  22. package/src/codegen/headerGenerators/mapType.test.ts +94 -0
  23. package/src/codegen/headerGenerators/mapType.ts +63 -0
  24. package/src/codegen/types/ICodeGeneratorOptions.ts +5 -0
  25. package/src/pipeline/Pipeline.ts +17 -0
  26. package/src/pipeline/runAnalyzers.ts +25 -7
  27. package/src/symbols/CNextSymbolCollector.ts +57 -10
  28. package/src/symbols/SymbolTable.test.ts +201 -0
  29. package/src/symbols/SymbolTable.ts +38 -9
package/README.md CHANGED
@@ -303,36 +303,38 @@ GPIO7.DR_SET[LED_BIT] <- true; // Generates: GPIO7_DR_SET = (1 << LED_BIT);
303
303
 
304
304
  ### Slice Assignment for Memory Operations
305
305
 
306
- Multi-byte copying with bounds-checked `memcpy` generation:
306
+ Multi-byte copying with compile-time validated `memcpy` generation (Issue #234):
307
307
 
308
308
  ```cnx
309
309
  u8 buffer[256];
310
310
  u32 magic <- 0x12345678;
311
- u8 length <- 4;
312
- u32 offset <- 0;
313
311
 
314
- // Copy 4 bytes from value into buffer at offset
315
- buffer[offset, length] <- magic;
312
+ // Copy 4 bytes from value into buffer at offset 0
313
+ buffer[0, 4] <- magic;
314
+
315
+ // Named offsets using const variables
316
+ const u32 HEADER_OFFSET <- 0;
317
+ const u32 DATA_OFFSET <- 8;
318
+ buffer[HEADER_OFFSET, 4] <- magic;
319
+ buffer[DATA_OFFSET, 8] <- timestamp;
316
320
  ```
317
321
 
318
- Transpiles to runtime bounds-checked code:
322
+ Transpiles to direct memcpy (bounds validated at compile time):
319
323
 
320
324
  ```c
321
325
  uint8_t buffer[256] = {0};
322
326
  uint32_t magic = 0x12345678;
323
- uint8_t length = 4;
324
- uint32_t offset = 0;
325
327
 
326
- if (offset + length <= sizeof(buffer)) {
327
- memcpy(&buffer[offset], &magic, length);
328
- }
328
+ memcpy(&buffer[0], &magic, 4);
329
+ memcpy(&buffer[8], &timestamp, 8);
329
330
  ```
330
331
 
331
332
  **Key Features:**
332
333
 
333
- - Runtime bounds checking prevents buffer overflows
334
- - Enables binary serialization and protocol implementation
335
- - Works with struct fields: `buffer[offset, len] <- config.magic`
334
+ - **Compile-time bounds checking** prevents buffer overflows at compile time
335
+ - Offset and length must be compile-time constants (literals or `const` variables)
336
+ - Silent runtime failures are now compile-time errors
337
+ - Works with struct fields: `buffer[0, 4] <- config.magic`
336
338
  - Distinct from bit operations: array slices use `memcpy`, scalar bit ranges use bit manipulation
337
339
 
338
340
  ### Scopes (ADR-016)
@@ -773,6 +775,26 @@ npm run coverage:grammar:check # Grammar coverage with threshold check (CI)
773
775
 
774
776
  **Note:** C-Next runs directly via `tsx` without a build step. The `typecheck` command validates types only and does not generate any output files.
775
777
 
778
+ ### Formatting C-Next Files
779
+
780
+ The project includes a Prettier plugin for formatting `.cnx` files with consistent style (4-space indentation, same-line braces).
781
+
782
+ ```bash
783
+ # Format a single file
784
+ npx prettier --plugin ./prettier-plugin/dist/index.js --write myfile.cnx
785
+
786
+ # Format all .cnx files in tests/
787
+ npx prettier --plugin ./prettier-plugin/dist/index.js --write "tests/**/*.cnx"
788
+ ```
789
+
790
+ To build the plugin from source (after making changes):
791
+
792
+ ```bash
793
+ cd prettier-plugin
794
+ npm install
795
+ npm run build
796
+ ```
797
+
776
798
  ## Contributing
777
799
 
778
800
  See [CONTRIBUTING.md](CONTRIBUTING.md) for the complete development workflow, testing requirements, and PR process.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "c-next",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "description": "A safer C for embedded systems development. Transpiles to clean, readable C.",
5
5
  "main": "src/index.ts",
6
6
  "bin": {
@@ -34,7 +34,10 @@
34
34
  "coverage:ids": "tsx scripts/coverage-checker.ts ids",
35
35
  "coverage:grammar": "tsx scripts/grammar-coverage.ts report",
36
36
  "coverage:grammar:check": "tsx scripts/grammar-coverage.ts check",
37
- "coverage:grammar:console": "tsx scripts/grammar-coverage.ts console"
37
+ "coverage:grammar:console": "tsx scripts/grammar-coverage.ts console",
38
+ "unit": "vitest run",
39
+ "unit:watch": "vitest",
40
+ "test:all": "npm run unit && npm run test:q"
38
41
  },
39
42
  "keywords": [
40
43
  "c",
@@ -67,11 +70,10 @@
67
70
  "@types/node": "^25.0.3",
68
71
  "antlr4ng-cli": "^2.0.0",
69
72
  "husky": "^9.1.7",
70
- "jest": "^30.2.0",
71
73
  "lint-staged": "^16.2.7",
72
74
  "oxlint": "^1.39.0",
73
75
  "prettier": "^3.7.4",
74
- "ts-jest": "^29.4.6"
76
+ "vitest": "^3.0.0"
75
77
  },
76
78
  "dependencies": {
77
79
  "antlr4ng": "^3.0.16",
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Parameter Naming Analyzer
3
+ * Issue #227: Validates that function parameters don't use reserved naming patterns
4
+ *
5
+ * Parameters cannot start with their containing function's name followed by underscore.
6
+ * This naming pattern is reserved for scope-level variables and would bypass the
7
+ * conflict detection heuristic in SymbolTable.detectConflict().
8
+ */
9
+
10
+ import { ParseTreeWalker } from "antlr4ng";
11
+ import { CNextListener } from "../parser/grammar/CNextListener";
12
+ import * as Parser from "../parser/grammar/CNextParser";
13
+ import IParameterNamingError from "./types/IParameterNamingError";
14
+
15
+ /**
16
+ * Pure function to check if a parameter name uses a reserved pattern
17
+ *
18
+ * @param parameterName - The parameter name to check
19
+ * @param functionName - The name of the containing function (without scope prefix)
20
+ * @returns true if the parameter name is problematic
21
+ */
22
+ function isReservedParameterName(
23
+ parameterName: string,
24
+ functionName: string,
25
+ ): boolean {
26
+ return parameterName.startsWith(functionName + "_");
27
+ }
28
+
29
+ /**
30
+ * Pure function to create the error message
31
+ *
32
+ * @param parameterName - The parameter name
33
+ * @param functionName - The containing function name
34
+ * @returns The formatted error message
35
+ */
36
+ function formatParameterNamingError(
37
+ parameterName: string,
38
+ functionName: string,
39
+ ): string {
40
+ return (
41
+ `Parameter '${parameterName}' cannot start with function name prefix ` +
42
+ `'${functionName}_'. This naming pattern is reserved for scope-level variables.`
43
+ );
44
+ }
45
+
46
+ /**
47
+ * Listener that walks the parse tree to find parameter naming violations
48
+ */
49
+ class ParameterNamingListener extends CNextListener {
50
+ private analyzer: ParameterNamingAnalyzer;
51
+
52
+ constructor(analyzer: ParameterNamingAnalyzer) {
53
+ super();
54
+ this.analyzer = analyzer;
55
+ }
56
+
57
+ override enterFunctionDeclaration = (
58
+ ctx: Parser.FunctionDeclarationContext,
59
+ ): void => {
60
+ const functionName = ctx.IDENTIFIER().getText();
61
+ const paramList = ctx.parameterList();
62
+
63
+ if (paramList) {
64
+ for (const param of paramList.parameter()) {
65
+ const paramIdentifier = param.IDENTIFIER();
66
+ const paramName = paramIdentifier.getText();
67
+ // Use the identifier's position for more precise error location
68
+ const line = paramIdentifier.symbol.line;
69
+ const column = paramIdentifier.symbol.column;
70
+
71
+ if (isReservedParameterName(paramName, functionName)) {
72
+ this.analyzer.addError(paramName, functionName, line, column);
73
+ }
74
+ }
75
+ }
76
+ };
77
+ }
78
+
79
+ /**
80
+ * Analyzer for parameter naming violations
81
+ */
82
+ class ParameterNamingAnalyzer {
83
+ private errors: IParameterNamingError[] = [];
84
+
85
+ /**
86
+ * Analyze the parse tree for parameter naming violations
87
+ *
88
+ * @param tree - The parsed program AST
89
+ * @returns Array of errors (empty if all pass)
90
+ */
91
+ public analyze(tree: Parser.ProgramContext): IParameterNamingError[] {
92
+ this.errors = [];
93
+
94
+ const listener = new ParameterNamingListener(this);
95
+ ParseTreeWalker.DEFAULT.walk(listener, tree);
96
+
97
+ return this.errors;
98
+ }
99
+
100
+ /**
101
+ * Add a parameter naming error
102
+ */
103
+ public addError(
104
+ parameterName: string,
105
+ functionName: string,
106
+ line: number,
107
+ column: number,
108
+ ): void {
109
+ this.errors.push({
110
+ code: "E0227",
111
+ parameterName,
112
+ functionName,
113
+ line,
114
+ column,
115
+ message: formatParameterNamingError(parameterName, functionName),
116
+ helpText: `Consider renaming to a name that doesn't start with '${functionName}_'`,
117
+ });
118
+ }
119
+ }
120
+
121
+ export default ParameterNamingAnalyzer;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Error reported when a function parameter has a reserved naming pattern
3
+ * Issue #227: Parameters cannot start with their function name followed by underscore
4
+ */
5
+ interface IParameterNamingError {
6
+ /** Error code (E0227) - matches issue number */
7
+ code: string;
8
+ /** Name of the parameter */
9
+ parameterName: string;
10
+ /** Name of the containing function */
11
+ functionName: string;
12
+ /** Line number where the parameter is declared */
13
+ line: number;
14
+ /** Column number where the parameter is declared */
15
+ column: number;
16
+ /** Human-readable error message */
17
+ message: string;
18
+ /** Optional help text with suggested fix */
19
+ helpText?: string;
20
+ }
21
+
22
+ export default IParameterNamingError;
@@ -1131,6 +1131,20 @@ export default class CodeGenerator implements IOrchestrator {
1131
1131
  output.push(" */");
1132
1132
  output.push("");
1133
1133
 
1134
+ // Issue #230: Self-include for extern "C" linkage
1135
+ // When file has public symbols and headers are being generated,
1136
+ // include own header to ensure proper C linkage
1137
+ if (
1138
+ options?.generateHeaders &&
1139
+ this.symbols!.hasPublicSymbols() &&
1140
+ this.sourcePath
1141
+ ) {
1142
+ const basename = this.sourcePath.replace(/^.*[\\/]/, "");
1143
+ const headerName = basename.replace(/\.cnx$|\.cnext$/, ".h");
1144
+ output.push(`#include "${headerName}"`);
1145
+ output.push("");
1146
+ }
1147
+
1134
1148
  // Pass through #include directives from source
1135
1149
  // C-Next does NOT hardcode any libraries - all includes must be explicit
1136
1150
  // ADR-043: Comments before first include become file-level comments
@@ -5603,9 +5617,43 @@ export default class CodeGenerator implements IOrchestrator {
5603
5617
  if (isActualArray || isISRType) {
5604
5618
  // Check for slice assignment: array[offset, length] <- value
5605
5619
  if (exprs.length === 2) {
5606
- // Slice assignment - generate memcpy with bounds checking
5607
- const offset = this._generateExpression(exprs[0]);
5608
- const length = this._generateExpression(exprs[1]);
5620
+ // Issue #234: Slice assignment requires compile-time constant offset and length
5621
+ // to ensure bounds safety at compile time, not runtime
5622
+ const offsetValue = this._tryEvaluateConstant(exprs[0]);
5623
+ const lengthValue = this._tryEvaluateConstant(exprs[1]);
5624
+
5625
+ const line = exprs[0].start?.line ?? arrayAccessCtx.start?.line ?? 0;
5626
+
5627
+ // Issue #234: Reject slice assignment on multi-dimensional arrays
5628
+ // Slice assignment is only valid on 1D arrays (the innermost dimension)
5629
+ // For multi-dimensional arrays like board[4][8], use board[row][offset, length]
5630
+ // (Note: grammar currently doesn't support this - tracked as future work)
5631
+ if (
5632
+ typeInfo?.arrayDimensions &&
5633
+ typeInfo.arrayDimensions.length > 1
5634
+ ) {
5635
+ throw new Error(
5636
+ `${line}:0 Error: Slice assignment is only valid on one-dimensional arrays. ` +
5637
+ `'${name}' has ${typeInfo.arrayDimensions.length} dimensions. ` +
5638
+ `Access the innermost dimension first (e.g., ${name}[index][offset, length]).`,
5639
+ );
5640
+ }
5641
+
5642
+ // Validate offset is compile-time constant
5643
+ if (offsetValue === undefined) {
5644
+ throw new Error(
5645
+ `${line}:0 Error: Slice assignment offset must be a compile-time constant. ` +
5646
+ `Runtime offsets are not allowed to ensure bounds safety.`,
5647
+ );
5648
+ }
5649
+
5650
+ // Validate length is compile-time constant
5651
+ if (lengthValue === undefined) {
5652
+ throw new Error(
5653
+ `${line}:0 Error: Slice assignment length must be a compile-time constant. ` +
5654
+ `Runtime lengths are not allowed to ensure bounds safety.`,
5655
+ );
5656
+ }
5609
5657
 
5610
5658
  // Compound operators not supported for slice assignment
5611
5659
  if (cOp !== "=") {
@@ -5614,22 +5662,50 @@ export default class CodeGenerator implements IOrchestrator {
5614
5662
  );
5615
5663
  }
5616
5664
 
5617
- // Set flag to include string.h for memcpy
5618
- this.needsString = true;
5619
-
5620
- // Generate bounds-checked memcpy
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
5665
+ // Determine buffer capacity for compile-time bounds check
5666
+ let capacity: number;
5624
5667
  if (
5625
5668
  typeInfo?.isString &&
5626
5669
  typeInfo.stringCapacity &&
5627
5670
  !typeInfo.isArray
5628
5671
  ) {
5629
- const capacity = typeInfo.stringCapacity + 1;
5630
- return `if (${offset} + ${length} <= ${capacity}) { memcpy(&${name}[${offset}], &${value}, ${length}); }`;
5672
+ capacity = typeInfo.stringCapacity + 1;
5673
+ } else if (typeInfo?.arrayDimensions && typeInfo.arrayDimensions[0]) {
5674
+ capacity = typeInfo.arrayDimensions[0];
5675
+ } else {
5676
+ // Can't determine capacity at compile time - this shouldn't happen
5677
+ // for properly tracked arrays, but fall back to error
5678
+ throw new Error(
5679
+ `${line}:0 Error: Cannot determine buffer size for '${name}' at compile time.`,
5680
+ );
5631
5681
  }
5632
- return `if (${offset} + ${length} <= sizeof(${name})) { memcpy(&${name}[${offset}], &${value}, ${length}); }`;
5682
+
5683
+ // Issue #234: Compile-time bounds validation
5684
+ if (offsetValue + lengthValue > capacity) {
5685
+ throw new Error(
5686
+ `${line}:0 Error: Slice assignment out of bounds: ` +
5687
+ `offset(${offsetValue}) + length(${lengthValue}) = ${offsetValue + lengthValue} ` +
5688
+ `exceeds buffer capacity(${capacity}) for '${name}'.`,
5689
+ );
5690
+ }
5691
+
5692
+ if (offsetValue < 0) {
5693
+ throw new Error(
5694
+ `${line}:0 Error: Slice assignment offset cannot be negative: ${offsetValue}`,
5695
+ );
5696
+ }
5697
+
5698
+ if (lengthValue <= 0) {
5699
+ throw new Error(
5700
+ `${line}:0 Error: Slice assignment length must be positive: ${lengthValue}`,
5701
+ );
5702
+ }
5703
+
5704
+ // Set flag to include string.h for memcpy
5705
+ this.needsString = true;
5706
+
5707
+ // Generate memcpy without runtime bounds check (already validated at compile time)
5708
+ return `memcpy(&${name}[${offsetValue}], &${value}, ${lengthValue});`;
5633
5709
  }
5634
5710
 
5635
5711
  // Normal array element assignment (single index)
@@ -8,25 +8,13 @@ import ESymbolKind from "../types/ESymbolKind";
8
8
  import ESourceLanguage from "../types/ESourceLanguage";
9
9
  import SymbolTable from "../symbols/SymbolTable";
10
10
  import IHeaderOptions from "./types/IHeaderOptions";
11
+ import IHeaderTypeInput from "./headerGenerators/IHeaderTypeInput";
12
+ import typeUtils from "./headerGenerators/mapType";
13
+ import generateEnumHeader from "./headerGenerators/generateEnumHeader";
14
+ import generateStructHeader from "./headerGenerators/generateStructHeader";
15
+ import generateBitmapHeader from "./headerGenerators/generateBitmapHeader";
11
16
 
12
- /**
13
- * Maps C-Next types to C types
14
- */
15
- const TYPE_MAP: Record<string, string> = {
16
- u8: "uint8_t",
17
- u16: "uint16_t",
18
- u32: "uint32_t",
19
- u64: "uint64_t",
20
- i8: "int8_t",
21
- i16: "int16_t",
22
- i32: "int32_t",
23
- i64: "int64_t",
24
- f32: "float",
25
- f64: "double",
26
- bool: "bool",
27
- void: "void",
28
- ISR: "ISR", // ADR-040: Interrupt Service Routine function pointer
29
- };
17
+ const { TYPE_MAP, mapType } = typeUtils;
30
18
 
31
19
  /**
32
20
  * Generates C header files from symbol information
@@ -34,11 +22,17 @@ const TYPE_MAP: Record<string, string> = {
34
22
  class HeaderGenerator {
35
23
  /**
36
24
  * Generate a header file from symbols
25
+ *
26
+ * @param symbols - Array of symbols to include in header
27
+ * @param filename - Output filename (used for include guard)
28
+ * @param options - Header generation options
29
+ * @param typeInput - Optional type information for full definitions (enums, structs, bitmaps)
37
30
  */
38
31
  generate(
39
32
  symbols: ISymbol[],
40
33
  filename: string,
41
34
  options: IHeaderOptions = {},
35
+ typeInput?: IHeaderTypeInput,
42
36
  ): string {
43
37
  const lines: string[] = [];
44
38
  const guard = this.makeGuard(filename, options.guardPrefix);
@@ -86,17 +80,22 @@ class HeaderGenerator {
86
80
  );
87
81
  const enums = exportedSymbols.filter((s) => s.kind === ESymbolKind.Enum);
88
82
  const types = exportedSymbols.filter((s) => s.kind === ESymbolKind.Type);
83
+ const bitmaps = exportedSymbols.filter(
84
+ (s) => s.kind === ESymbolKind.Bitmap,
85
+ );
89
86
 
90
87
  // Issue #121: Collect external type dependencies from function signatures
91
88
  const localStructNames = new Set(structs.map((s) => s.name));
92
89
  const localEnumNames = new Set(enums.map((s) => s.name));
93
90
  const localTypeNames = new Set(types.map((s) => s.name));
91
+ const localBitmapNames = new Set(bitmaps.map((s) => s.name));
94
92
  const externalTypes = this.collectExternalTypes(
95
93
  functions,
96
94
  variables,
97
95
  localStructNames,
98
96
  localEnumNames,
99
97
  localTypeNames,
98
+ localBitmapNames,
100
99
  );
101
100
 
102
101
  // Emit forward declarations for external types
@@ -110,25 +109,52 @@ class HeaderGenerator {
110
109
  lines.push("");
111
110
  }
112
111
 
113
- // Forward declarations for structs
112
+ // Struct definitions or forward declarations
114
113
  if (structs.length > 0 || classes.length > 0) {
115
- lines.push("/* Forward declarations */");
116
- for (const sym of structs) {
117
- lines.push(`typedef struct ${sym.name} ${sym.name};`);
118
- }
119
- for (const sym of classes) {
120
- // Classes become typedefs to structs
121
- lines.push(`typedef struct ${sym.name} ${sym.name};`);
114
+ if (typeInput) {
115
+ lines.push("/* Struct definitions */");
116
+ for (const sym of structs) {
117
+ lines.push(generateStructHeader(sym.name, typeInput));
118
+ }
119
+ for (const sym of classes) {
120
+ lines.push(generateStructHeader(sym.name, typeInput));
121
+ }
122
+ } else {
123
+ lines.push("/* Forward declarations */");
124
+ for (const sym of structs) {
125
+ lines.push(`typedef struct ${sym.name} ${sym.name};`);
126
+ }
127
+ for (const sym of classes) {
128
+ lines.push(`typedef struct ${sym.name} ${sym.name};`);
129
+ }
122
130
  }
123
131
  lines.push("");
124
132
  }
125
133
 
126
- // Enum declarations
134
+ // Enum definitions or comments
127
135
  if (enums.length > 0) {
128
136
  lines.push("/* Enumerations */");
129
137
  for (const sym of enums) {
130
- // For now, just forward declare - full definition would require enum values
131
- lines.push(`/* Enum: ${sym.name} (see implementation for values) */`);
138
+ if (typeInput) {
139
+ lines.push(generateEnumHeader(sym.name, typeInput));
140
+ } else {
141
+ lines.push(`/* Enum: ${sym.name} (see implementation for values) */`);
142
+ }
143
+ }
144
+ lines.push("");
145
+ }
146
+
147
+ // Bitmap definitions (only when typeInput is available)
148
+ if (bitmaps.length > 0) {
149
+ lines.push("/* Bitmaps */");
150
+ for (const sym of bitmaps) {
151
+ if (typeInput) {
152
+ lines.push(generateBitmapHeader(sym.name, typeInput));
153
+ } else {
154
+ lines.push(
155
+ `/* Bitmap: ${sym.name} (see implementation for layout) */`,
156
+ );
157
+ }
132
158
  }
133
159
  lines.push("");
134
160
  }
@@ -138,7 +164,7 @@ class HeaderGenerator {
138
164
  lines.push("/* Type aliases */");
139
165
  for (const sym of types) {
140
166
  if (sym.type) {
141
- const cType = this.mapType(sym.type);
167
+ const cType = mapType(sym.type);
142
168
  lines.push(`typedef ${cType} ${sym.name};`);
143
169
  }
144
170
  }
@@ -149,7 +175,7 @@ class HeaderGenerator {
149
175
  if (variables.length > 0) {
150
176
  lines.push("/* External variables */");
151
177
  for (const sym of variables) {
152
- const cType = sym.type ? this.mapType(sym.type) : "int";
178
+ const cType = sym.type ? mapType(sym.type) : "int";
153
179
  lines.push(`extern ${cType} ${sym.name};`);
154
180
  }
155
181
  lines.push("");
@@ -224,47 +250,20 @@ class HeaderGenerator {
224
250
  return `${sanitized}_H`;
225
251
  }
226
252
 
227
- /**
228
- * Map a C-Next type to C type
229
- */
230
- private mapType(type: string): string {
231
- // Check direct mapping first
232
- if (TYPE_MAP[type]) {
233
- return TYPE_MAP[type];
234
- }
235
-
236
- // Handle pointer types
237
- if (type.endsWith("*")) {
238
- const baseType = type.slice(0, -1).trim();
239
- return `${this.mapType(baseType)}*`;
240
- }
241
-
242
- // Handle array types (simplified)
243
- const arrayMatch = type.match(/^(\w+)\[(\d*)\]$/);
244
- if (arrayMatch) {
245
- const baseType = this.mapType(arrayMatch[1]);
246
- const size = arrayMatch[2] || "";
247
- return `${baseType}[${size}]`;
248
- }
249
-
250
- // User-defined types pass through
251
- return type;
252
- }
253
-
254
253
  /**
255
254
  * Generate a function prototype from symbol info
256
255
  * Handles all parameter edge cases per ADR-006, ADR-029, and ADR-040
257
256
  */
258
257
  private generateFunctionPrototype(sym: ISymbol): string | null {
259
258
  // Map return type from C-Next to C
260
- const returnType = sym.type ? this.mapType(sym.type) : "void";
259
+ const returnType = sym.type ? mapType(sym.type) : "void";
261
260
 
262
261
  // Build parameter list with proper C semantics
263
262
  let params = "void"; // Default for no parameters
264
263
 
265
264
  if (sym.parameters && sym.parameters.length > 0) {
266
265
  const translatedParams = sym.parameters.map((p) => {
267
- const baseType = this.mapType(p.type);
266
+ const baseType = mapType(p.type);
268
267
  const constMod = p.isConst ? "const " : "";
269
268
 
270
269
  // Handle array parameters (pass naturally as pointers per C semantics)
@@ -301,7 +300,7 @@ class HeaderGenerator {
301
300
  * Issue #121: Collect external type dependencies from function signatures and variables
302
301
  * Returns types that are:
303
302
  * - Not primitive types (not in TYPE_MAP)
304
- * - Not locally defined structs, enums, or type aliases
303
+ * - Not locally defined structs, enums, bitmaps, or type aliases
305
304
  */
306
305
  private collectExternalTypes(
307
306
  functions: ISymbol[],
@@ -309,6 +308,7 @@ class HeaderGenerator {
309
308
  localStructs: Set<string>,
310
309
  localEnums: Set<string>,
311
310
  localTypes: Set<string>,
311
+ localBitmaps: Set<string>,
312
312
  ): Set<string> {
313
313
  const externalTypes = new Set<string>();
314
314
 
@@ -328,6 +328,9 @@ class HeaderGenerator {
328
328
  if (localTypes.has(typeName)) {
329
329
  return false;
330
330
  }
331
+ if (localBitmaps.has(typeName)) {
332
+ return false;
333
+ }
331
334
 
332
335
  // Skip pointer markers and array brackets
333
336
  if (typeName === "" || typeName === "*") {