c-next 0.1.8 → 0.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -773,6 +773,26 @@ npm run coverage:grammar:check # Grammar coverage with threshold check (CI)
773
773
 
774
774
  **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
775
 
776
+ ### Formatting C-Next Files
777
+
778
+ The project includes a Prettier plugin for formatting `.cnx` files with consistent style (4-space indentation, same-line braces).
779
+
780
+ ```bash
781
+ # Format a single file
782
+ npx prettier --plugin ./prettier-plugin/dist/index.js --write myfile.cnx
783
+
784
+ # Format all .cnx files in tests/
785
+ npx prettier --plugin ./prettier-plugin/dist/index.js --write "tests/**/*.cnx"
786
+ ```
787
+
788
+ To build the plugin from source (after making changes):
789
+
790
+ ```bash
791
+ cd prettier-plugin
792
+ npm install
793
+ npm run build
794
+ ```
795
+
776
796
  ## Contributing
777
797
 
778
798
  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.9",
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 test"
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;
@@ -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 === "*") {
@@ -62,8 +62,11 @@ export default class GeneratorRegistry {
62
62
  * @param kind - Declaration kind (e.g., 'struct', 'enum', 'function')
63
63
  * @param fn - Generator function
64
64
  */
65
- registerDeclaration(kind: string, fn: TGeneratorFn<ParserRuleContext>): void {
66
- this.declarations.set(kind, fn);
65
+ registerDeclaration<T extends ParserRuleContext>(
66
+ kind: string,
67
+ fn: TGeneratorFn<T>,
68
+ ): void {
69
+ this.declarations.set(kind, fn as TGeneratorFn<ParserRuleContext>);
67
70
  }
68
71
 
69
72
  /**
@@ -71,8 +74,11 @@ export default class GeneratorRegistry {
71
74
  * @param kind - Statement kind (e.g., 'if', 'while', 'assignment')
72
75
  * @param fn - Generator function
73
76
  */
74
- registerStatement(kind: string, fn: TGeneratorFn<ParserRuleContext>): void {
75
- this.statements.set(kind, fn);
77
+ registerStatement<T extends ParserRuleContext>(
78
+ kind: string,
79
+ fn: TGeneratorFn<T>,
80
+ ): void {
81
+ this.statements.set(kind, fn as TGeneratorFn<ParserRuleContext>);
76
82
  }
77
83
 
78
84
  /**
@@ -80,8 +86,11 @@ export default class GeneratorRegistry {
80
86
  * @param kind - Expression kind (e.g., 'ternary', 'binary', 'call')
81
87
  * @param fn - Generator function
82
88
  */
83
- registerExpression(kind: string, fn: TGeneratorFn<ParserRuleContext>): void {
84
- this.expressions.set(kind, fn);
89
+ registerExpression<T extends ParserRuleContext>(
90
+ kind: string,
91
+ fn: TGeneratorFn<T>,
92
+ ): void {
93
+ this.expressions.set(kind, fn as TGeneratorFn<ParserRuleContext>);
85
94
  }
86
95
 
87
96
  // =========================================================================
@@ -268,7 +268,7 @@ function generateScopedBitmapInline(
268
268
  lines.push(" */");
269
269
  } else {
270
270
  // Fall back to AST parsing
271
- const fields = node.bitmapField();
271
+ const fields = node.bitmapMember();
272
272
  if (fields.length > 0) {
273
273
  lines.push("/* Fields:");
274
274
  let bitOffset = 0;
@@ -7,7 +7,10 @@
7
7
  * - C function calls with pass-by-value semantics
8
8
  * - Const-to-non-const validation (ADR-013)
9
9
  */
10
- import { ArgumentListContext } from "../../../parser/grammar/CNextParser";
10
+ import {
11
+ ArgumentListContext,
12
+ ExpressionContext,
13
+ } from "../../../parser/grammar/CNextParser";
11
14
  import IGeneratorOutput from "../IGeneratorOutput";
12
15
  import TGeneratorEffect from "../TGeneratorEffect";
13
16
  import IGeneratorInput from "../IGeneratorInput";
@@ -94,7 +97,7 @@ const generateFunctionCall = (
94
97
  */
95
98
  const generateSafeDivMod = (
96
99
  funcName: string,
97
- argExprs: ReturnType<ArgumentListContext["expression"]>,
100
+ argExprs: ExpressionContext[],
98
101
  input: IGeneratorInput,
99
102
  orchestrator: IOrchestrator,
100
103
  effects: TGeneratorEffect[],
@@ -157,7 +160,7 @@ const generateSafeDivMod = (
157
160
  */
158
161
  const validateConstToNonConst = (
159
162
  funcName: string,
160
- argExprs: ReturnType<ArgumentListContext["expression"]>,
163
+ argExprs: ExpressionContext[],
161
164
  input: IGeneratorInput,
162
165
  orchestrator: IOrchestrator,
163
166
  ): void => {
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Input interface for header type generators.
3
+ * Provides read-only access to symbol data needed for generating
4
+ * type definitions in C headers.
5
+ *
6
+ * This interface is a subset of ISymbolInfo, containing only
7
+ * the data needed for generating enums, structs, and bitmaps.
8
+ */
9
+ interface IHeaderTypeInput {
10
+ /** Enum members and values: enumName -> (memberName -> value) */
11
+ readonly enumMembers: ReadonlyMap<string, ReadonlyMap<string, number>>;
12
+
13
+ /** Struct field types: structName -> (fieldName -> typeName) */
14
+ readonly structFields: ReadonlyMap<string, ReadonlyMap<string, string>>;
15
+
16
+ /** Array dimensions for struct fields: structName -> (fieldName -> dimensions) */
17
+ readonly structFieldDimensions: ReadonlyMap<
18
+ string,
19
+ ReadonlyMap<string, readonly number[]>
20
+ >;
21
+
22
+ /** Backing type for each bitmap: bitmapName -> typeName (e.g., "uint8_t") */
23
+ readonly bitmapBackingType: ReadonlyMap<string, string>;
24
+
25
+ /** Bitmap field info: bitmapName -> (fieldName -> {offset, width}) */
26
+ readonly bitmapFields: ReadonlyMap<
27
+ string,
28
+ ReadonlyMap<string, { readonly offset: number; readonly width: number }>
29
+ >;
30
+ }
31
+
32
+ export default IHeaderTypeInput;