c-next 0.1.30 → 0.1.32

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/grammar/CNext.g4 CHANGED
@@ -84,6 +84,7 @@ scopeMember
84
84
  | visibilityModifier? enumDeclaration
85
85
  | visibilityModifier? bitmapDeclaration
86
86
  | visibilityModifier? registerDeclaration
87
+ | visibilityModifier? structDeclaration
87
88
  ;
88
89
 
89
90
  visibilityModifier
@@ -193,6 +194,12 @@ arrayDimension
193
194
  // ----------------------------------------------------------------------------
194
195
  variableDeclaration
195
196
  : atomicModifier? volatileModifier? constModifier? overflowModifier? type IDENTIFIER arrayDimension* ('<-' expression)? ';'
197
+ | type IDENTIFIER '(' constructorArgumentList ')' ';' // Issue #375: C++ constructor
198
+ ;
199
+
200
+ // Issue #375: Constructor argument list - identifiers only (must be const variables)
201
+ constructorArgumentList
202
+ : IDENTIFIER (',' IDENTIFIER)*
196
203
  ;
197
204
 
198
205
  // ----------------------------------------------------------------------------
@@ -264,7 +271,8 @@ thisMemberAccess
264
271
 
265
272
  // ADR-016: this.member[idx] or this.member.member[idx] for scope-local array/bit access
266
273
  thisArrayAccess
267
- : 'this' '.' IDENTIFIER '[' expression ']' // this.arr[i]
274
+ : 'this' '.' IDENTIFIER '[' expression ']' ('.' IDENTIFIER)+ // this.arr[i].field.field2...
275
+ | 'this' '.' IDENTIFIER '[' expression ']' // this.arr[i]
268
276
  | 'this' '.' IDENTIFIER '[' expression ',' expression ']' // this.reg[offset, width]
269
277
  | 'this' '.' IDENTIFIER ('.' IDENTIFIER)+ '[' expression ']' // this.GPIO7.DR_SET[i]
270
278
  | 'this' '.' IDENTIFIER ('.' IDENTIFIER)+ '[' expression ',' expression ']' // this.GPIO7.ICR1[6, 2]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "c-next",
3
- "version": "0.1.30",
3
+ "version": "0.1.32",
4
4
  "description": "A safer C for embedded systems development. Transpiles to clean, readable C.",
5
5
  "main": "src/index.ts",
6
6
  "bin": {
@@ -37,6 +37,8 @@
37
37
  "coverage:grammar:console": "tsx scripts/grammar-coverage.ts console",
38
38
  "unit": "vitest run",
39
39
  "unit:watch": "vitest",
40
+ "unit:coverage": "vitest run --coverage",
41
+ "unit:coverage:html": "vitest run --coverage && echo 'Coverage report: coverage/index.html'",
40
42
  "test:all": "npm run unit && npm run test:q"
41
43
  },
42
44
  "keywords": [
@@ -68,6 +70,7 @@
68
70
  ],
69
71
  "devDependencies": {
70
72
  "@types/node": "^25.0.3",
73
+ "@vitest/coverage-v8": "^3.2.4",
71
74
  "antlr4ng-cli": "^2.0.0",
72
75
  "husky": "^9.1.7",
73
76
  "lint-staged": "^16.2.7",
@@ -0,0 +1,331 @@
1
+ /**
2
+ * Unit tests for DivisionByZeroAnalyzer
3
+ * Tests detection of division and modulo by zero at compile time (ADR-051)
4
+ */
5
+ import { describe, it, expect } from "vitest";
6
+ import { CharStream, CommonTokenStream } from "antlr4ng";
7
+ import { CNextLexer } from "../parser/grammar/CNextLexer";
8
+ import { CNextParser } from "../parser/grammar/CNextParser";
9
+ import DivisionByZeroAnalyzer from "./DivisionByZeroAnalyzer";
10
+
11
+ /**
12
+ * Helper to parse C-Next code and return the AST
13
+ */
14
+ function parse(source: string) {
15
+ const charStream = CharStream.fromString(source);
16
+ const lexer = new CNextLexer(charStream);
17
+ const tokenStream = new CommonTokenStream(lexer);
18
+ const parser = new CNextParser(tokenStream);
19
+ return parser.program();
20
+ }
21
+
22
+ describe("DivisionByZeroAnalyzer", () => {
23
+ // ========================================================================
24
+ // Phase 1: Literal Zero Detection
25
+ // ========================================================================
26
+
27
+ describe("literal zero detection", () => {
28
+ it("should detect division by literal zero", () => {
29
+ const code = `
30
+ void main() {
31
+ u32 x <- 10 / 0;
32
+ }
33
+ `;
34
+ const tree = parse(code);
35
+ const analyzer = new DivisionByZeroAnalyzer();
36
+ const errors = analyzer.analyze(tree);
37
+
38
+ expect(errors).toHaveLength(1);
39
+ expect(errors[0].code).toBe("E0800");
40
+ expect(errors[0].operator).toBe("/");
41
+ expect(errors[0].message).toBe("Division by zero");
42
+ });
43
+
44
+ it("should detect modulo by literal zero", () => {
45
+ const code = `
46
+ void main() {
47
+ u32 x <- 10 % 0;
48
+ }
49
+ `;
50
+ const tree = parse(code);
51
+ const analyzer = new DivisionByZeroAnalyzer();
52
+ const errors = analyzer.analyze(tree);
53
+
54
+ expect(errors).toHaveLength(1);
55
+ expect(errors[0].code).toBe("E0802");
56
+ expect(errors[0].operator).toBe("%");
57
+ expect(errors[0].message).toBe("Modulo by zero");
58
+ });
59
+
60
+ it("should detect hex zero (0x0)", () => {
61
+ const code = `
62
+ void main() {
63
+ u32 x <- 10 / 0x0;
64
+ }
65
+ `;
66
+ const tree = parse(code);
67
+ const analyzer = new DivisionByZeroAnalyzer();
68
+ const errors = analyzer.analyze(tree);
69
+
70
+ expect(errors).toHaveLength(1);
71
+ expect(errors[0].code).toBe("E0800");
72
+ });
73
+
74
+ it("should detect binary zero (0b0)", () => {
75
+ const code = `
76
+ void main() {
77
+ u32 x <- 10 / 0b0;
78
+ }
79
+ `;
80
+ const tree = parse(code);
81
+ const analyzer = new DivisionByZeroAnalyzer();
82
+ const errors = analyzer.analyze(tree);
83
+
84
+ expect(errors).toHaveLength(1);
85
+ expect(errors[0].code).toBe("E0800");
86
+ });
87
+
88
+ it("should not flag division by non-zero literal", () => {
89
+ const code = `
90
+ void main() {
91
+ u32 x <- 10 / 2;
92
+ u32 y <- 20 % 3;
93
+ }
94
+ `;
95
+ const tree = parse(code);
96
+ const analyzer = new DivisionByZeroAnalyzer();
97
+ const errors = analyzer.analyze(tree);
98
+
99
+ expect(errors).toHaveLength(0);
100
+ });
101
+
102
+ it("should not flag multiplication by zero", () => {
103
+ const code = `
104
+ void main() {
105
+ u32 x <- 10 * 0;
106
+ }
107
+ `;
108
+ const tree = parse(code);
109
+ const analyzer = new DivisionByZeroAnalyzer();
110
+ const errors = analyzer.analyze(tree);
111
+
112
+ expect(errors).toHaveLength(0);
113
+ });
114
+ });
115
+
116
+ // ========================================================================
117
+ // Phase 3: Const Zero Detection
118
+ // ========================================================================
119
+
120
+ describe("const zero detection", () => {
121
+ it("should detect division by const zero variable", () => {
122
+ const code = `
123
+ const u32 ZERO <- 0;
124
+ void main() {
125
+ u32 x <- 10 / ZERO;
126
+ }
127
+ `;
128
+ const tree = parse(code);
129
+ const analyzer = new DivisionByZeroAnalyzer();
130
+ const errors = analyzer.analyze(tree);
131
+
132
+ expect(errors).toHaveLength(1);
133
+ expect(errors[0].code).toBe("E0800");
134
+ });
135
+
136
+ it("should detect modulo by const zero variable", () => {
137
+ const code = `
138
+ const u32 ZERO <- 0;
139
+ void main() {
140
+ u32 x <- 10 % ZERO;
141
+ }
142
+ `;
143
+ const tree = parse(code);
144
+ const analyzer = new DivisionByZeroAnalyzer();
145
+ const errors = analyzer.analyze(tree);
146
+
147
+ expect(errors).toHaveLength(1);
148
+ expect(errors[0].code).toBe("E0802");
149
+ });
150
+
151
+ it("should not flag division by non-zero const", () => {
152
+ const code = `
153
+ const u32 DIVISOR <- 5;
154
+ void main() {
155
+ u32 x <- 10 / DIVISOR;
156
+ }
157
+ `;
158
+ const tree = parse(code);
159
+ const analyzer = new DivisionByZeroAnalyzer();
160
+ const errors = analyzer.analyze(tree);
161
+
162
+ expect(errors).toHaveLength(0);
163
+ });
164
+
165
+ it("should not flag division by non-const variable", () => {
166
+ const code = `
167
+ u32 divisor <- 0;
168
+ void main() {
169
+ u32 x <- 10 / divisor;
170
+ }
171
+ `;
172
+ const tree = parse(code);
173
+ const analyzer = new DivisionByZeroAnalyzer();
174
+ const errors = analyzer.analyze(tree);
175
+
176
+ // Non-const variables are not tracked (runtime value)
177
+ expect(errors).toHaveLength(0);
178
+ });
179
+ });
180
+
181
+ // ========================================================================
182
+ // Multiple Errors
183
+ // ========================================================================
184
+
185
+ describe("multiple errors", () => {
186
+ it("should detect multiple division by zero errors", () => {
187
+ const code = `
188
+ void main() {
189
+ u32 a <- 10 / 0;
190
+ u32 b <- 20 % 0;
191
+ u32 c <- 30 / 0;
192
+ }
193
+ `;
194
+ const tree = parse(code);
195
+ const analyzer = new DivisionByZeroAnalyzer();
196
+ const errors = analyzer.analyze(tree);
197
+
198
+ expect(errors).toHaveLength(3);
199
+ expect(errors[0].code).toBe("E0800"); // division
200
+ expect(errors[1].code).toBe("E0802"); // modulo
201
+ expect(errors[2].code).toBe("E0800"); // division
202
+ });
203
+ });
204
+
205
+ // ========================================================================
206
+ // Chained Expressions
207
+ // ========================================================================
208
+
209
+ describe("chained expressions", () => {
210
+ it("should detect zero in chained division", () => {
211
+ const code = `
212
+ void main() {
213
+ u32 x <- 10 / 2 / 0;
214
+ }
215
+ `;
216
+ const tree = parse(code);
217
+ const analyzer = new DivisionByZeroAnalyzer();
218
+ const errors = analyzer.analyze(tree);
219
+
220
+ expect(errors).toHaveLength(1);
221
+ expect(errors[0].code).toBe("E0800");
222
+ });
223
+ });
224
+
225
+ // ========================================================================
226
+ // Error Details
227
+ // ========================================================================
228
+
229
+ describe("error details", () => {
230
+ it("should provide helpful text for division by zero", () => {
231
+ const code = `
232
+ void main() {
233
+ u32 x <- 10 / 0;
234
+ }
235
+ `;
236
+ const tree = parse(code);
237
+ const analyzer = new DivisionByZeroAnalyzer();
238
+ const errors = analyzer.analyze(tree);
239
+
240
+ expect(errors[0].helpText).toContain("safe_div");
241
+ });
242
+
243
+ it("should provide helpful text for modulo by zero", () => {
244
+ const code = `
245
+ void main() {
246
+ u32 x <- 10 % 0;
247
+ }
248
+ `;
249
+ const tree = parse(code);
250
+ const analyzer = new DivisionByZeroAnalyzer();
251
+ const errors = analyzer.analyze(tree);
252
+
253
+ expect(errors[0].helpText).toContain("safe_mod");
254
+ });
255
+
256
+ it("should report correct line and column", () => {
257
+ const code = `void main() {
258
+ u32 x <- 10 / 0;
259
+ }`;
260
+ const tree = parse(code);
261
+ const analyzer = new DivisionByZeroAnalyzer();
262
+ const errors = analyzer.analyze(tree);
263
+
264
+ expect(errors[0].line).toBe(2);
265
+ // Column should point to the zero operand
266
+ expect(errors[0].column).toBeGreaterThan(0);
267
+ });
268
+ });
269
+
270
+ // ========================================================================
271
+ // Suffixed Literals
272
+ // ========================================================================
273
+
274
+ describe("suffixed literals", () => {
275
+ it("should detect suffixed decimal zero (0u32)", () => {
276
+ const code = `
277
+ void main() {
278
+ u32 x <- 10 / 0u32;
279
+ }
280
+ `;
281
+ const tree = parse(code);
282
+ const analyzer = new DivisionByZeroAnalyzer();
283
+ const errors = analyzer.analyze(tree);
284
+
285
+ expect(errors).toHaveLength(1);
286
+ expect(errors[0].code).toBe("E0800");
287
+ });
288
+
289
+ it("should not flag suffixed non-zero (5u32)", () => {
290
+ const code = `
291
+ void main() {
292
+ u32 x <- 10 / 5u32;
293
+ }
294
+ `;
295
+ const tree = parse(code);
296
+ const analyzer = new DivisionByZeroAnalyzer();
297
+ const errors = analyzer.analyze(tree);
298
+
299
+ expect(errors).toHaveLength(0);
300
+ });
301
+ });
302
+
303
+ // ========================================================================
304
+ // Edge Cases
305
+ // ========================================================================
306
+
307
+ describe("edge cases", () => {
308
+ it("should handle empty program", () => {
309
+ const code = ``;
310
+ const tree = parse(code);
311
+ const analyzer = new DivisionByZeroAnalyzer();
312
+ const errors = analyzer.analyze(tree);
313
+
314
+ expect(errors).toHaveLength(0);
315
+ });
316
+
317
+ it("should handle program with no division operations", () => {
318
+ const code = `
319
+ void main() {
320
+ u32 x <- 5 + 3;
321
+ u32 y <- 10 - 2;
322
+ }
323
+ `;
324
+ const tree = parse(code);
325
+ const analyzer = new DivisionByZeroAnalyzer();
326
+ const errors = analyzer.analyze(tree);
327
+
328
+ expect(errors).toHaveLength(0);
329
+ });
330
+ });
331
+ });