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 +9 -1
- package/package.json +4 -1
- package/src/analysis/DivisionByZeroAnalyzer.test.ts +331 -0
- package/src/analysis/FloatModuloAnalyzer.test.ts +352 -0
- package/src/analysis/FunctionCallAnalyzer.test.ts +416 -0
- package/src/analysis/InitializationAnalyzer.ts +36 -83
- package/src/analysis/ParameterNamingAnalyzer.test.ts +274 -0
- package/src/analysis/ScopeStack.test.ts +442 -0
- package/src/analysis/ScopeStack.ts +206 -0
- package/src/codegen/CodeGenerator.ts +331 -6
- package/src/codegen/CommentExtractor.test.ts +623 -0
- package/src/codegen/CommentFormatter.test.ts +230 -0
- package/src/codegen/HeaderGenerator.ts +6 -1
- package/src/codegen/SymbolCollector.ts +27 -6
- package/src/codegen/TypeResolver.test.ts +941 -0
- package/src/codegen/TypeValidator.test.ts +2480 -0
- package/src/codegen/generators/declarationGenerators/ScopeGenerator.ts +90 -0
- package/src/codegen/generators/support/CommentUtils.test.ts +222 -0
- package/src/codegen/memberAccessChain.test.ts +84 -0
- package/src/codegen/types/TTypeInfo.ts +1 -0
- package/src/parser/grammar/CNext.interp +2 -1
- package/src/parser/grammar/CNextListener.ts +11 -0
- package/src/parser/grammar/CNextParser.ts +1409 -1224
- package/src/parser/grammar/CNextVisitor.ts +7 -0
- package/src/pipeline/detectCppSyntax.test.ts +151 -0
- package/src/symbols/CNextSymbolCollector.ts +19 -5
- package/src/symbols/SymbolTable.test.ts +781 -1
- package/src/symbols/SymbolUtils.test.ts +137 -0
- package/src/types/ISymbol.ts +6 -0
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 ']'
|
|
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.
|
|
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
|
+
});
|