c-next 0.1.0
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 +726 -0
- package/bin/cnext.js +5 -0
- package/grammar/C.g4 +1112 -0
- package/grammar/CNext.g4 +817 -0
- package/grammar/CPP14Lexer.g4 +282 -0
- package/grammar/CPP14Parser.g4 +1072 -0
- package/package.json +85 -0
- package/src/analysis/DivisionByZeroAnalyzer.ts +378 -0
- package/src/analysis/FunctionCallAnalyzer.ts +526 -0
- package/src/analysis/InitializationAnalyzer.ts +725 -0
- package/src/analysis/NullCheckAnalyzer.ts +427 -0
- package/src/analysis/types/IDivisionByZeroError.ts +25 -0
- package/src/analysis/types/IFunctionCallError.ts +17 -0
- package/src/analysis/types/IInitializationError.ts +55 -0
- package/src/analysis/types/INullCheckError.ts +25 -0
- package/src/codegen/CodeGenerator.ts +7945 -0
- package/src/codegen/CommentExtractor.ts +240 -0
- package/src/codegen/CommentFormatter.ts +155 -0
- package/src/codegen/HeaderGenerator.ts +265 -0
- package/src/codegen/TypeResolver.ts +365 -0
- package/src/codegen/types/ECommentType.ts +10 -0
- package/src/codegen/types/IComment.ts +21 -0
- package/src/codegen/types/ICommentError.ts +15 -0
- package/src/codegen/types/TOverflowBehavior.ts +6 -0
- package/src/codegen/types/TParameterInfo.ts +13 -0
- package/src/codegen/types/TTypeConstants.ts +94 -0
- package/src/codegen/types/TTypeInfo.ts +22 -0
- package/src/index.ts +518 -0
- package/src/lib/IncludeDiscovery.ts +131 -0
- package/src/lib/InputExpansion.ts +121 -0
- package/src/lib/PlatformIODetector.ts +162 -0
- package/src/lib/transpiler.ts +439 -0
- package/src/lib/types/ITranspileResult.ts +80 -0
- package/src/parser/c/grammar/C.interp +338 -0
- package/src/parser/c/grammar/C.tokens +229 -0
- package/src/parser/c/grammar/CLexer.interp +415 -0
- package/src/parser/c/grammar/CLexer.tokens +229 -0
- package/src/parser/c/grammar/CLexer.ts +750 -0
- package/src/parser/c/grammar/CListener.ts +976 -0
- package/src/parser/c/grammar/CParser.ts +9663 -0
- package/src/parser/c/grammar/CVisitor.ts +626 -0
- package/src/parser/cpp/grammar/CPP14Lexer.interp +478 -0
- package/src/parser/cpp/grammar/CPP14Lexer.tokens +264 -0
- package/src/parser/cpp/grammar/CPP14Lexer.ts +848 -0
- package/src/parser/cpp/grammar/CPP14Parser.interp +492 -0
- package/src/parser/cpp/grammar/CPP14Parser.tokens +264 -0
- package/src/parser/cpp/grammar/CPP14Parser.ts +19961 -0
- package/src/parser/cpp/grammar/CPP14ParserListener.ts +2120 -0
- package/src/parser/cpp/grammar/CPP14ParserVisitor.ts +1354 -0
- package/src/parser/grammar/CNext.interp +340 -0
- package/src/parser/grammar/CNext.tokens +214 -0
- package/src/parser/grammar/CNextLexer.interp +374 -0
- package/src/parser/grammar/CNextLexer.tokens +214 -0
- package/src/parser/grammar/CNextLexer.ts +668 -0
- package/src/parser/grammar/CNextListener.ts +1020 -0
- package/src/parser/grammar/CNextParser.ts +9239 -0
- package/src/parser/grammar/CNextVisitor.ts +654 -0
- package/src/preprocessor/Preprocessor.ts +301 -0
- package/src/preprocessor/ToolchainDetector.ts +225 -0
- package/src/preprocessor/types/IPreprocessResult.ts +39 -0
- package/src/preprocessor/types/IToolchain.ts +27 -0
- package/src/project/FileDiscovery.ts +236 -0
- package/src/project/Project.ts +425 -0
- package/src/project/types/IProjectConfig.ts +64 -0
- package/src/symbols/CNextSymbolCollector.ts +326 -0
- package/src/symbols/CSymbolCollector.ts +457 -0
- package/src/symbols/CppSymbolCollector.ts +362 -0
- package/src/symbols/SymbolTable.ts +312 -0
- package/src/symbols/types/IConflict.ts +20 -0
- package/src/types/ESourceLanguage.ts +10 -0
- package/src/types/ESymbolKind.ts +20 -0
- package/src/types/ISymbol.ts +45 -0
package/package.json
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "c-next",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A safer C for embedded systems development. Transpiles to clean, readable C.",
|
|
5
|
+
"main": "src/index.ts",
|
|
6
|
+
"bin": {
|
|
7
|
+
"cnext": "./bin/cnext.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"antlr": "antlr4ng -Dlanguage=TypeScript -visitor -listener -o src/parser grammar/CNext.g4",
|
|
11
|
+
"antlr:c": "antlr4ng -Dlanguage=TypeScript -visitor -listener -o src/parser/c grammar/C.g4",
|
|
12
|
+
"antlr:cpp:lexer": "antlr4ng -Dlanguage=TypeScript -o src/parser/cpp grammar/CPP14Lexer.g4",
|
|
13
|
+
"antlr:cpp:parser": "antlr4ng -Dlanguage=TypeScript -visitor -listener -o src/parser/cpp -lib src/parser/cpp/grammar grammar/CPP14Parser.g4",
|
|
14
|
+
"antlr:cpp": "npm run antlr:cpp:lexer && npm run antlr:cpp:parser",
|
|
15
|
+
"antlr:all": "npm run antlr && npm run antlr:c && npm run antlr:cpp",
|
|
16
|
+
"start": "tsx src/index.ts",
|
|
17
|
+
"dev": "tsx src/index.ts",
|
|
18
|
+
"test": "tsx scripts/test.js",
|
|
19
|
+
"test:cli": "node scripts/test-cli.js",
|
|
20
|
+
"test:update": "tsx scripts/test.js --update",
|
|
21
|
+
"analyze": "./scripts/static-analysis.sh",
|
|
22
|
+
"clean": "rm -rf src/parser",
|
|
23
|
+
"prettier:check": "prettier --check .",
|
|
24
|
+
"prettier:fix": "prettier --write .",
|
|
25
|
+
"eslint:check": "eslint src/",
|
|
26
|
+
"eslint:fix": "eslint src/ --fix",
|
|
27
|
+
"prepare": "husky",
|
|
28
|
+
"prepublishOnly": "npm run prettier:check && npm run eslint:check && npm test"
|
|
29
|
+
},
|
|
30
|
+
"keywords": [
|
|
31
|
+
"c",
|
|
32
|
+
"embedded",
|
|
33
|
+
"transpiler",
|
|
34
|
+
"safety",
|
|
35
|
+
"misra"
|
|
36
|
+
],
|
|
37
|
+
"author": "Joshua Austill <jlaustill@gmail.com>",
|
|
38
|
+
"license": "MIT",
|
|
39
|
+
"repository": {
|
|
40
|
+
"type": "git",
|
|
41
|
+
"url": "https://github.com/jlaustill/c-next.git"
|
|
42
|
+
},
|
|
43
|
+
"bugs": {
|
|
44
|
+
"url": "https://github.com/jlaustill/c-next/issues"
|
|
45
|
+
},
|
|
46
|
+
"homepage": "https://github.com/jlaustill/c-next#readme",
|
|
47
|
+
"engines": {
|
|
48
|
+
"node": ">=16.0.0"
|
|
49
|
+
},
|
|
50
|
+
"files": [
|
|
51
|
+
"bin/",
|
|
52
|
+
"src/",
|
|
53
|
+
"grammar/",
|
|
54
|
+
"README.md",
|
|
55
|
+
"LICENSE"
|
|
56
|
+
],
|
|
57
|
+
"devDependencies": {
|
|
58
|
+
"@types/node": "^25.0.3",
|
|
59
|
+
"@typescript-eslint/eslint-plugin": "^7.18.0",
|
|
60
|
+
"@typescript-eslint/parser": "^7.18.0",
|
|
61
|
+
"antlr4ng-cli": "^2.0.0",
|
|
62
|
+
"eslint": "^8.57.1",
|
|
63
|
+
"eslint-config-airbnb-typescript": "^18.0.0",
|
|
64
|
+
"eslint-config-prettier": "^10.1.8",
|
|
65
|
+
"eslint-plugin-import": "^2.32.0",
|
|
66
|
+
"husky": "^9.1.7",
|
|
67
|
+
"jest": "^30.2.0",
|
|
68
|
+
"lint-staged": "^16.2.7",
|
|
69
|
+
"prettier": "^3.7.4",
|
|
70
|
+
"ts-jest": "^29.4.6"
|
|
71
|
+
},
|
|
72
|
+
"dependencies": {
|
|
73
|
+
"antlr4ng": "^3.0.16",
|
|
74
|
+
"tsx": "^4.21.0",
|
|
75
|
+
"typescript": "^5.9.3"
|
|
76
|
+
},
|
|
77
|
+
"lint-staged": {
|
|
78
|
+
"*": [
|
|
79
|
+
"prettier --write --ignore-unknown"
|
|
80
|
+
],
|
|
81
|
+
"*.ts": [
|
|
82
|
+
"eslint --fix"
|
|
83
|
+
]
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Division By Zero Analyzer
|
|
3
|
+
* Detects division and modulo by zero at compile time (ADR-051)
|
|
4
|
+
*
|
|
5
|
+
* Implemented Phases:
|
|
6
|
+
* ✓ Phase 1: Literal zero detection (10 / 0, 10 % 0)
|
|
7
|
+
* ✓ Phase 3: Const zero detection (const u32 ZERO <- 0; x / ZERO)
|
|
8
|
+
*
|
|
9
|
+
* Future Enhancement (Phase 3+):
|
|
10
|
+
* - Const expression evaluation (const u32 VALUE <- 5 - 5; x / VALUE)
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { ParseTreeWalker } from "antlr4ng";
|
|
14
|
+
import { CNextListener } from "../parser/grammar/CNextListener";
|
|
15
|
+
import * as Parser from "../parser/grammar/CNextParser";
|
|
16
|
+
import { IDivisionByZeroError } from "./types/IDivisionByZeroError";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* First pass: Collect const declarations that are zero
|
|
20
|
+
*/
|
|
21
|
+
class ConstZeroCollector extends CNextListener {
|
|
22
|
+
private constZeros: Set<string> = new Set();
|
|
23
|
+
|
|
24
|
+
public getConstZeros(): Set<string> {
|
|
25
|
+
return this.constZeros;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Track const declarations
|
|
30
|
+
* variableDeclaration: atomicModifier? volatileModifier? constModifier? ...
|
|
31
|
+
*/
|
|
32
|
+
override enterVariableDeclaration = (
|
|
33
|
+
ctx: Parser.VariableDeclarationContext,
|
|
34
|
+
): void => {
|
|
35
|
+
// Only process const declarations
|
|
36
|
+
if (!ctx.constModifier()) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const identifier = ctx.IDENTIFIER();
|
|
41
|
+
if (!identifier) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const name = identifier.getText();
|
|
46
|
+
const expr = ctx.expression();
|
|
47
|
+
if (!expr) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Check if the expression is a literal zero
|
|
52
|
+
if (this.isExpressionZero(expr)) {
|
|
53
|
+
this.constZeros.add(name);
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Check if an expression evaluates to literal zero
|
|
59
|
+
* Navigate: expression -> ternaryExpression -> orExpression -> ... -> literal
|
|
60
|
+
*/
|
|
61
|
+
private isExpressionZero(ctx: Parser.ExpressionContext): boolean {
|
|
62
|
+
// expression -> ternaryExpression
|
|
63
|
+
const ternary = ctx.ternaryExpression();
|
|
64
|
+
if (!ternary) return false;
|
|
65
|
+
|
|
66
|
+
// ternaryExpression -> orExpression
|
|
67
|
+
const orExprs = ternary.orExpression();
|
|
68
|
+
if (!orExprs || orExprs.length === 0) return false;
|
|
69
|
+
|
|
70
|
+
const orExpr = orExprs[0];
|
|
71
|
+
|
|
72
|
+
// orExpression -> andExpression
|
|
73
|
+
const andExprs = orExpr.andExpression();
|
|
74
|
+
if (!andExprs || andExprs.length === 0) return false;
|
|
75
|
+
|
|
76
|
+
const andExpr = andExprs[0];
|
|
77
|
+
|
|
78
|
+
// andExpression -> equalityExpression
|
|
79
|
+
const eqExprs = andExpr.equalityExpression();
|
|
80
|
+
if (!eqExprs || eqExprs.length === 0) return false;
|
|
81
|
+
|
|
82
|
+
const eqExpr = eqExprs[0];
|
|
83
|
+
|
|
84
|
+
// equalityExpression -> relationalExpression
|
|
85
|
+
const relExprs = eqExpr.relationalExpression();
|
|
86
|
+
if (!relExprs || relExprs.length === 0) return false;
|
|
87
|
+
|
|
88
|
+
const relExpr = relExprs[0];
|
|
89
|
+
|
|
90
|
+
// relationalExpression -> bitwiseOrExpression
|
|
91
|
+
const bitorExprs = relExpr.bitwiseOrExpression();
|
|
92
|
+
if (!bitorExprs || bitorExprs.length === 0) return false;
|
|
93
|
+
|
|
94
|
+
const bitorExpr = bitorExprs[0];
|
|
95
|
+
|
|
96
|
+
// bitwiseOrExpression -> bitwiseXorExpression
|
|
97
|
+
const bitxorExprs = bitorExpr.bitwiseXorExpression();
|
|
98
|
+
if (!bitxorExprs || bitxorExprs.length === 0) return false;
|
|
99
|
+
|
|
100
|
+
const bitxorExpr = bitxorExprs[0];
|
|
101
|
+
|
|
102
|
+
// bitwiseXorExpression -> bitwiseAndExpression
|
|
103
|
+
const bitandExprs = bitxorExpr.bitwiseAndExpression();
|
|
104
|
+
if (!bitandExprs || bitandExprs.length === 0) return false;
|
|
105
|
+
|
|
106
|
+
const bitandExpr = bitandExprs[0];
|
|
107
|
+
|
|
108
|
+
// bitwiseAndExpression -> shiftExpression
|
|
109
|
+
const shiftExprs = bitandExpr.shiftExpression();
|
|
110
|
+
if (!shiftExprs || shiftExprs.length === 0) return false;
|
|
111
|
+
|
|
112
|
+
const shiftExpr = shiftExprs[0];
|
|
113
|
+
|
|
114
|
+
// shiftExpression -> additiveExpression
|
|
115
|
+
const addExprs = shiftExpr.additiveExpression();
|
|
116
|
+
if (!addExprs || addExprs.length === 0) return false;
|
|
117
|
+
|
|
118
|
+
const addExpr = addExprs[0];
|
|
119
|
+
|
|
120
|
+
// additiveExpression -> multiplicativeExpression
|
|
121
|
+
const multExprs = addExpr.multiplicativeExpression();
|
|
122
|
+
if (!multExprs || multExprs.length === 0) return false;
|
|
123
|
+
|
|
124
|
+
const multExpr = multExprs[0];
|
|
125
|
+
|
|
126
|
+
// multiplicativeExpression -> unaryExpression
|
|
127
|
+
const unaryExprs = multExpr.unaryExpression();
|
|
128
|
+
if (!unaryExprs || unaryExprs.length === 0) return false;
|
|
129
|
+
|
|
130
|
+
const unary = unaryExprs[0];
|
|
131
|
+
|
|
132
|
+
// unaryExpression -> postfixExpression
|
|
133
|
+
const postfix = unary.postfixExpression();
|
|
134
|
+
if (!postfix) return false;
|
|
135
|
+
|
|
136
|
+
// postfixExpression -> primaryExpression
|
|
137
|
+
const primary = postfix.primaryExpression();
|
|
138
|
+
if (!primary) return false;
|
|
139
|
+
|
|
140
|
+
// primaryExpression -> literal
|
|
141
|
+
const literal = primary.literal();
|
|
142
|
+
if (!literal) return false;
|
|
143
|
+
|
|
144
|
+
return this.isLiteralZero(literal);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Check if a literal is zero
|
|
149
|
+
*/
|
|
150
|
+
private isLiteralZero(ctx: Parser.LiteralContext): boolean {
|
|
151
|
+
const text = ctx.getText();
|
|
152
|
+
|
|
153
|
+
// Check integer literals
|
|
154
|
+
if (ctx.INTEGER_LITERAL()) {
|
|
155
|
+
return text === "0";
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Check hex literals
|
|
159
|
+
if (ctx.HEX_LITERAL()) {
|
|
160
|
+
return text === "0x0" || text === "0X0";
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Check binary literals
|
|
164
|
+
if (ctx.BINARY_LITERAL()) {
|
|
165
|
+
return text === "0b0" || text === "0B0";
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Check suffixed literals
|
|
169
|
+
if (ctx.SUFFIXED_DECIMAL()) {
|
|
170
|
+
return text.startsWith("0u") || text.startsWith("0i");
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (ctx.SUFFIXED_HEX()) {
|
|
174
|
+
return (
|
|
175
|
+
text.startsWith("0x0u") ||
|
|
176
|
+
text.startsWith("0x0i") ||
|
|
177
|
+
text.startsWith("0X0u") ||
|
|
178
|
+
text.startsWith("0X0i")
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (ctx.SUFFIXED_BINARY()) {
|
|
183
|
+
return (
|
|
184
|
+
text.startsWith("0b0u") ||
|
|
185
|
+
text.startsWith("0b0i") ||
|
|
186
|
+
text.startsWith("0B0u") ||
|
|
187
|
+
text.startsWith("0B0i")
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Second pass: Detect division by zero (including const identifiers)
|
|
197
|
+
*/
|
|
198
|
+
class DivisionByZeroListener extends CNextListener {
|
|
199
|
+
private analyzer: DivisionByZeroAnalyzer;
|
|
200
|
+
// eslint-disable-next-line @typescript-eslint/lines-between-class-members
|
|
201
|
+
private constZeros: Set<string>;
|
|
202
|
+
|
|
203
|
+
constructor(analyzer: DivisionByZeroAnalyzer, constZeros: Set<string>) {
|
|
204
|
+
super();
|
|
205
|
+
this.analyzer = analyzer;
|
|
206
|
+
this.constZeros = constZeros;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Check multiplicative expressions for division/modulo by zero
|
|
211
|
+
* multiplicativeExpression: unaryExpression (('*' | '/' | '%') unaryExpression)*
|
|
212
|
+
*/
|
|
213
|
+
override enterMultiplicativeExpression = (
|
|
214
|
+
ctx: Parser.MultiplicativeExpressionContext,
|
|
215
|
+
): void => {
|
|
216
|
+
// Get all unary expressions
|
|
217
|
+
const operands = ctx.unaryExpression();
|
|
218
|
+
if (operands.length < 2) {
|
|
219
|
+
return; // No operator, just a single operand
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Check each operator and its right operand
|
|
223
|
+
for (let i = 0; i < operands.length - 1; i++) {
|
|
224
|
+
const operatorToken = ctx.getChild(i * 2 + 1); // Operators are at odd indices
|
|
225
|
+
if (!operatorToken) continue;
|
|
226
|
+
|
|
227
|
+
const operator = operatorToken.getText();
|
|
228
|
+
if (operator !== "/" && operator !== "%") {
|
|
229
|
+
continue; // Only check division and modulo
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const rightOperand = operands[i + 1];
|
|
233
|
+
const line = rightOperand.start?.line ?? 0;
|
|
234
|
+
const column = rightOperand.start?.column ?? 0;
|
|
235
|
+
|
|
236
|
+
// Check if right operand is zero
|
|
237
|
+
if (this.isZero(rightOperand)) {
|
|
238
|
+
this.analyzer.addError(operator, line, column);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Check if a unary expression evaluates to zero
|
|
245
|
+
*/
|
|
246
|
+
private isZero(ctx: Parser.UnaryExpressionContext): boolean {
|
|
247
|
+
// Get the postfix expression
|
|
248
|
+
const postfixExpr = ctx.postfixExpression();
|
|
249
|
+
if (!postfixExpr) {
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Get the primary expression
|
|
254
|
+
const primaryExpr = postfixExpr.primaryExpression();
|
|
255
|
+
if (!primaryExpr) {
|
|
256
|
+
return false;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Check if it's a literal
|
|
260
|
+
const literal = primaryExpr.literal();
|
|
261
|
+
if (literal) {
|
|
262
|
+
return this.isLiteralZero(literal);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Check if it's a const identifier that evaluates to zero
|
|
266
|
+
const identifier = primaryExpr.IDENTIFIER();
|
|
267
|
+
if (identifier) {
|
|
268
|
+
const name = identifier.getText();
|
|
269
|
+
return this.constZeros.has(name);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return false;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Check if a literal is zero
|
|
277
|
+
*/
|
|
278
|
+
private isLiteralZero(ctx: Parser.LiteralContext): boolean {
|
|
279
|
+
const text = ctx.getText();
|
|
280
|
+
|
|
281
|
+
// Check integer literals
|
|
282
|
+
if (ctx.INTEGER_LITERAL()) {
|
|
283
|
+
return text === "0";
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Check hex literals
|
|
287
|
+
if (ctx.HEX_LITERAL()) {
|
|
288
|
+
return text === "0x0" || text === "0X0";
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Check binary literals
|
|
292
|
+
if (ctx.BINARY_LITERAL()) {
|
|
293
|
+
return text === "0b0" || text === "0B0";
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Check suffixed literals
|
|
297
|
+
if (ctx.SUFFIXED_DECIMAL()) {
|
|
298
|
+
return text.startsWith("0u") || text.startsWith("0i");
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (ctx.SUFFIXED_HEX()) {
|
|
302
|
+
return (
|
|
303
|
+
text.startsWith("0x0u") ||
|
|
304
|
+
text.startsWith("0x0i") ||
|
|
305
|
+
text.startsWith("0X0u") ||
|
|
306
|
+
text.startsWith("0X0i")
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (ctx.SUFFIXED_BINARY()) {
|
|
311
|
+
return (
|
|
312
|
+
text.startsWith("0b0u") ||
|
|
313
|
+
text.startsWith("0b0i") ||
|
|
314
|
+
text.startsWith("0B0u") ||
|
|
315
|
+
text.startsWith("0B0i")
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
return false;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Analyzer that detects division by zero
|
|
325
|
+
*/
|
|
326
|
+
export class DivisionByZeroAnalyzer {
|
|
327
|
+
private errors: IDivisionByZeroError[] = [];
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Analyze the parse tree for division by zero
|
|
331
|
+
* Two-pass analysis:
|
|
332
|
+
* 1. Collect const declarations that are zero
|
|
333
|
+
* 2. Detect division/modulo by literal zero or const zero
|
|
334
|
+
*/
|
|
335
|
+
public analyze(tree: Parser.ProgramContext): IDivisionByZeroError[] {
|
|
336
|
+
this.errors = [];
|
|
337
|
+
|
|
338
|
+
// First pass: collect const zeros
|
|
339
|
+
const collector = new ConstZeroCollector();
|
|
340
|
+
ParseTreeWalker.DEFAULT.walk(collector, tree);
|
|
341
|
+
const constZeros = collector.getConstZeros();
|
|
342
|
+
|
|
343
|
+
// Second pass: detect division by zero
|
|
344
|
+
const listener = new DivisionByZeroListener(this, constZeros);
|
|
345
|
+
ParseTreeWalker.DEFAULT.walk(listener, tree);
|
|
346
|
+
|
|
347
|
+
return this.errors;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Add a division by zero error
|
|
352
|
+
*/
|
|
353
|
+
public addError(operator: string, line: number, column: number): void {
|
|
354
|
+
const isDivision = operator === "/";
|
|
355
|
+
const code = isDivision ? "E0800" : "E0802";
|
|
356
|
+
const opName = isDivision ? "Division" : "Modulo";
|
|
357
|
+
|
|
358
|
+
this.errors.push({
|
|
359
|
+
code,
|
|
360
|
+
operator,
|
|
361
|
+
line,
|
|
362
|
+
column,
|
|
363
|
+
message: `${opName} by zero`,
|
|
364
|
+
helpText: isDivision
|
|
365
|
+
? "Consider using safe_div(output, numerator, divisor, defaultValue) for runtime safety"
|
|
366
|
+
: "Consider using safe_mod(output, numerator, divisor, defaultValue) for runtime safety",
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Get all detected errors
|
|
372
|
+
*/
|
|
373
|
+
public getErrors(): IDivisionByZeroError[] {
|
|
374
|
+
return this.errors;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
export default DivisionByZeroAnalyzer;
|