c-next 0.1.2 → 0.1.3
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 +25 -3
- package/package.json +8 -13
- package/src/analysis/DivisionByZeroAnalyzer.ts +2 -2
- package/src/analysis/FloatModuloAnalyzer.ts +196 -0
- package/src/analysis/FunctionCallAnalyzer.ts +2 -4
- package/src/analysis/InitializationAnalyzer.ts +81 -9
- package/src/analysis/NullCheckAnalyzer.ts +2 -4
- package/src/analysis/types/IDeclarationInfo.ts +13 -0
- package/src/analysis/types/IDivisionByZeroError.ts +1 -1
- package/src/analysis/types/IFloatModuloError.ts +20 -0
- package/src/analysis/types/IFunctionCallError.ts +1 -1
- package/src/analysis/types/IInitializationError.ts +2 -29
- package/src/analysis/types/INullCheckError.ts +1 -1
- package/src/analysis/types/formatInitializationError.ts +20 -0
- package/src/codegen/CodeGenerator.ts +795 -74
- package/src/codegen/HeaderGenerator.ts +39 -26
- package/src/codegen/TypeResolver.ts +6 -8
- package/src/codegen/types/BITMAP_BACKING_TYPE.ts +11 -0
- package/src/codegen/types/BITMAP_SIZE.ts +11 -0
- package/src/codegen/types/C_TYPE_WIDTH.ts +31 -0
- package/src/codegen/types/FLOAT_TYPES.ts +6 -0
- package/src/codegen/types/IAssignmentContext.ts +12 -0
- package/src/codegen/types/ICodeGeneratorOptions.ts +13 -0
- package/src/codegen/types/IHeaderOptions.ts +15 -0
- package/src/codegen/types/INTEGER_TYPES.ts +8 -0
- package/src/codegen/types/SIGNED_TYPES.ts +6 -0
- package/src/codegen/types/TCodeGenContext.ts +1 -10
- package/src/codegen/types/TYPE_RANGES.ts +16 -0
- package/src/codegen/types/TYPE_WIDTH.ts +18 -0
- package/src/codegen/types/UNSIGNED_TYPES.ts +6 -0
- package/src/index.ts +4 -29
- package/src/lib/IncludeDiscovery.ts +3 -1
- package/src/lib/InputExpansion.ts +6 -2
- package/src/lib/PlatformIODetector.ts +1 -1
- package/src/lib/parse.ts +16 -0
- package/src/lib/parseWithSymbols.ts +148 -0
- package/src/lib/transpiler.ts +33 -175
- package/src/lib/types/IParseWithSymbolsResult.ts +16 -0
- package/src/lib/types/ISymbolInfo.ts +28 -0
- package/src/lib/types/ITranspileError.ts +15 -0
- package/src/lib/types/ITranspileOptions.ts +13 -0
- package/src/lib/types/ITranspileResult.ts +2 -65
- package/src/lib/types/TSymbolKind.ts +15 -0
- package/src/preprocessor/Preprocessor.ts +4 -19
- package/src/preprocessor/types/IPreprocessOptions.ts +20 -0
- package/src/preprocessor/types/IPreprocessResult.ts +1 -14
- package/src/preprocessor/types/ISourceMapping.ts +15 -0
- package/src/project/FileDiscovery.ts +3 -42
- package/src/project/Project.ts +73 -19
- package/src/project/types/EFileType.ts +13 -0
- package/src/project/types/IDiscoveredFile.ts +17 -0
- package/src/project/types/IDiscoveryOptions.ts +15 -0
- package/src/project/types/IProjectConfig.ts +0 -31
- package/src/project/types/IProjectResult.ts +27 -0
- package/src/symbols/CNextSymbolCollector.ts +19 -0
- package/src/symbols/CppSymbolCollector.ts +110 -1
- package/src/symbols/SymbolTable.ts +1 -11
- package/src/symbols/types/IStructFieldInfo.ts +11 -0
- package/src/types/ISymbol.ts +9 -0
- package/src/codegen/types/TTypeConstants.ts +0 -94
- package/src/codegen/types/index.ts +0 -17
package/README.md
CHANGED
|
@@ -736,19 +736,41 @@ Decisions are documented in `/docs/decisions/`:
|
|
|
736
736
|
| [ADR-031](docs/decisions/adr-031-inline-functions.md) | Inline Functions | Trust compiler; `inline` is just a hint anyway |
|
|
737
737
|
| [ADR-033](docs/decisions/adr-033-packed-structs.md) | Packed Structs | Use ADR-004 register bindings or explicit serialization |
|
|
738
738
|
|
|
739
|
-
## Development
|
|
739
|
+
## Development
|
|
740
|
+
|
|
741
|
+
### Setup
|
|
742
|
+
|
|
743
|
+
```bash
|
|
744
|
+
# Clone and install (IMPORTANT: npm install sets up pre-commit hooks)
|
|
745
|
+
git clone https://github.com/jlaustill/c-next.git
|
|
746
|
+
cd c-next
|
|
747
|
+
npm install # Installs dependencies and Husky pre-commit hooks
|
|
748
|
+
```
|
|
749
|
+
|
|
750
|
+
**Pre-commit hooks:** The project uses [Husky](https://typicode.github.io/husky/) to automatically format code (Prettier) and fix linting (ESLint) before every commit. This prevents formatting errors in PRs.
|
|
751
|
+
|
|
752
|
+
### Commands
|
|
740
753
|
|
|
741
754
|
```bash
|
|
742
755
|
npm run antlr # Regenerate parser from grammar
|
|
743
756
|
npm run typecheck # Type-check TypeScript (no build required)
|
|
744
|
-
npm test
|
|
757
|
+
npm test # Run all tests
|
|
758
|
+
npm test -- --quiet # Minimal output (errors + summary only)
|
|
759
|
+
npm test -- tests/enum # Run specific directory
|
|
760
|
+
npm test -- tests/enum/my.test.cnx # Run single test file
|
|
761
|
+
|
|
762
|
+
# Code quality (auto-run by pre-commit hooks)
|
|
763
|
+
npm run prettier:fix # Format all code
|
|
764
|
+
npm run eslint:check # Check for lint errors
|
|
745
765
|
```
|
|
746
766
|
|
|
747
767
|
**Note:** C-Next runs directly via `tsx` without a build step. The `typecheck` command validates types only and does not generate any output files.
|
|
748
768
|
|
|
749
769
|
## Contributing
|
|
750
770
|
|
|
751
|
-
|
|
771
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for the complete development workflow, testing requirements, and PR process.
|
|
772
|
+
|
|
773
|
+
**Quick start:** Ideas and feedback welcome via issues.
|
|
752
774
|
|
|
753
775
|
## License
|
|
754
776
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "c-next",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "A safer C for embedded systems development. Transpiles to clean, readable C.",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"bin": {
|
|
@@ -16,17 +16,17 @@
|
|
|
16
16
|
"start": "tsx src/index.ts",
|
|
17
17
|
"dev": "tsx src/index.ts",
|
|
18
18
|
"typecheck": "tsc --noEmit",
|
|
19
|
-
"test": "tsx scripts/test.
|
|
19
|
+
"test": "tsx scripts/test.ts",
|
|
20
20
|
"test:cli": "node scripts/test-cli.js",
|
|
21
|
-
"test:update": "tsx scripts/test.
|
|
21
|
+
"test:update": "tsx scripts/test.ts --update",
|
|
22
22
|
"analyze": "./scripts/static-analysis.sh",
|
|
23
23
|
"clean": "rm -rf src/parser",
|
|
24
24
|
"prettier:check": "prettier --check .",
|
|
25
25
|
"prettier:fix": "prettier --write .",
|
|
26
|
-
"
|
|
27
|
-
"
|
|
26
|
+
"oxlint:check": "oxlint src/",
|
|
27
|
+
"oxlint:fix": "oxlint src/ --fix",
|
|
28
28
|
"prepare": "husky",
|
|
29
|
-
"prepublishOnly": "npm run prettier:check && npm run
|
|
29
|
+
"prepublishOnly": "npm run prettier:check && npm run oxlint:check && npm test"
|
|
30
30
|
},
|
|
31
31
|
"keywords": [
|
|
32
32
|
"c",
|
|
@@ -57,16 +57,11 @@
|
|
|
57
57
|
],
|
|
58
58
|
"devDependencies": {
|
|
59
59
|
"@types/node": "^25.0.3",
|
|
60
|
-
"@typescript-eslint/eslint-plugin": "^7.18.0",
|
|
61
|
-
"@typescript-eslint/parser": "^7.18.0",
|
|
62
60
|
"antlr4ng-cli": "^2.0.0",
|
|
63
|
-
"eslint": "^8.57.1",
|
|
64
|
-
"eslint-config-airbnb-typescript": "^18.0.0",
|
|
65
|
-
"eslint-config-prettier": "^10.1.8",
|
|
66
|
-
"eslint-plugin-import": "^2.32.0",
|
|
67
61
|
"husky": "^9.1.7",
|
|
68
62
|
"jest": "^30.2.0",
|
|
69
63
|
"lint-staged": "^16.2.7",
|
|
64
|
+
"oxlint": "^1.39.0",
|
|
70
65
|
"prettier": "^3.7.4",
|
|
71
66
|
"ts-jest": "^29.4.6"
|
|
72
67
|
},
|
|
@@ -80,7 +75,7 @@
|
|
|
80
75
|
"prettier --write --ignore-unknown"
|
|
81
76
|
],
|
|
82
77
|
"*.ts": [
|
|
83
|
-
"
|
|
78
|
+
"oxlint --fix"
|
|
84
79
|
]
|
|
85
80
|
}
|
|
86
81
|
}
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
import { ParseTreeWalker } from "antlr4ng";
|
|
14
14
|
import { CNextListener } from "../parser/grammar/CNextListener";
|
|
15
15
|
import * as Parser from "../parser/grammar/CNextParser";
|
|
16
|
-
import
|
|
16
|
+
import IDivisionByZeroError from "./types/IDivisionByZeroError";
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
19
|
* First pass: Collect const declarations that are zero
|
|
@@ -323,7 +323,7 @@ class DivisionByZeroListener extends CNextListener {
|
|
|
323
323
|
/**
|
|
324
324
|
* Analyzer that detects division by zero
|
|
325
325
|
*/
|
|
326
|
-
|
|
326
|
+
class DivisionByZeroAnalyzer {
|
|
327
327
|
private errors: IDivisionByZeroError[] = [];
|
|
328
328
|
|
|
329
329
|
/**
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Float Modulo Analyzer
|
|
3
|
+
* Detects modulo operator usage with floating-point types at compile time
|
|
4
|
+
*
|
|
5
|
+
* The modulo operator (%) is only valid for integer types in C.
|
|
6
|
+
* C-Next catches this early with a clear error message.
|
|
7
|
+
*
|
|
8
|
+
* Two-pass analysis:
|
|
9
|
+
* 1. Collect variable declarations with f32/f64 types
|
|
10
|
+
* 2. Detect modulo operations using float variables or literals
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { ParseTreeWalker } from "antlr4ng";
|
|
14
|
+
import { CNextListener } from "../parser/grammar/CNextListener";
|
|
15
|
+
import * as Parser from "../parser/grammar/CNextParser";
|
|
16
|
+
import IFloatModuloError from "./types/IFloatModuloError";
|
|
17
|
+
|
|
18
|
+
const FLOAT_TYPES = ["f32", "f64", "float", "double"];
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* First pass: Collect variable declarations with float types
|
|
22
|
+
*/
|
|
23
|
+
class FloatVariableCollector extends CNextListener {
|
|
24
|
+
private floatVars: Set<string> = new Set();
|
|
25
|
+
|
|
26
|
+
public getFloatVars(): Set<string> {
|
|
27
|
+
return this.floatVars;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Track variable declarations with f32/f64 types
|
|
32
|
+
*/
|
|
33
|
+
override enterVariableDeclaration = (
|
|
34
|
+
ctx: Parser.VariableDeclarationContext,
|
|
35
|
+
): void => {
|
|
36
|
+
const typeCtx = ctx.type();
|
|
37
|
+
if (!typeCtx) return;
|
|
38
|
+
|
|
39
|
+
const typeName = typeCtx.getText();
|
|
40
|
+
if (!FLOAT_TYPES.includes(typeName)) return;
|
|
41
|
+
|
|
42
|
+
const identifier = ctx.IDENTIFIER();
|
|
43
|
+
if (!identifier) return;
|
|
44
|
+
|
|
45
|
+
this.floatVars.add(identifier.getText());
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Track function parameters with f32/f64 types
|
|
50
|
+
*/
|
|
51
|
+
override enterParameter = (ctx: Parser.ParameterContext): void => {
|
|
52
|
+
const typeCtx = ctx.type();
|
|
53
|
+
if (!typeCtx) return;
|
|
54
|
+
|
|
55
|
+
const typeName = typeCtx.getText();
|
|
56
|
+
if (!FLOAT_TYPES.includes(typeName)) return;
|
|
57
|
+
|
|
58
|
+
const identifier = ctx.IDENTIFIER();
|
|
59
|
+
if (!identifier) return;
|
|
60
|
+
|
|
61
|
+
this.floatVars.add(identifier.getText());
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Second pass: Detect modulo operations with float operands
|
|
67
|
+
*/
|
|
68
|
+
class FloatModuloListener extends CNextListener {
|
|
69
|
+
private analyzer: FloatModuloAnalyzer;
|
|
70
|
+
|
|
71
|
+
// eslint-disable-next-line @typescript-eslint/lines-between-class-members
|
|
72
|
+
private floatVars: Set<string>;
|
|
73
|
+
|
|
74
|
+
constructor(analyzer: FloatModuloAnalyzer, floatVars: Set<string>) {
|
|
75
|
+
super();
|
|
76
|
+
this.analyzer = analyzer;
|
|
77
|
+
this.floatVars = floatVars;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Check multiplicative expressions for modulo with float operands
|
|
82
|
+
* multiplicativeExpression: unaryExpression (('*' | '/' | '%') unaryExpression)*
|
|
83
|
+
*/
|
|
84
|
+
override enterMultiplicativeExpression = (
|
|
85
|
+
ctx: Parser.MultiplicativeExpressionContext,
|
|
86
|
+
): void => {
|
|
87
|
+
const operands = ctx.unaryExpression();
|
|
88
|
+
if (operands.length < 2) return;
|
|
89
|
+
|
|
90
|
+
// Check each operator
|
|
91
|
+
for (let i = 0; i < operands.length - 1; i++) {
|
|
92
|
+
const operatorToken = ctx.getChild(i * 2 + 1);
|
|
93
|
+
if (!operatorToken) continue;
|
|
94
|
+
|
|
95
|
+
const operator = operatorToken.getText();
|
|
96
|
+
if (operator !== "%") continue;
|
|
97
|
+
|
|
98
|
+
const leftOperand = operands[i];
|
|
99
|
+
const rightOperand = operands[i + 1];
|
|
100
|
+
|
|
101
|
+
const leftIsFloat = this.isFloatOperand(leftOperand);
|
|
102
|
+
const rightIsFloat = this.isFloatOperand(rightOperand);
|
|
103
|
+
|
|
104
|
+
if (leftIsFloat || rightIsFloat) {
|
|
105
|
+
const line = leftOperand.start?.line ?? 0;
|
|
106
|
+
const column = leftOperand.start?.column ?? 0;
|
|
107
|
+
this.analyzer.addError(line, column);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Check if a unary expression is a float type
|
|
114
|
+
*/
|
|
115
|
+
private isFloatOperand(ctx: Parser.UnaryExpressionContext): boolean {
|
|
116
|
+
const postfixExpr = ctx.postfixExpression();
|
|
117
|
+
if (!postfixExpr) return false;
|
|
118
|
+
|
|
119
|
+
const primaryExpr = postfixExpr.primaryExpression();
|
|
120
|
+
if (!primaryExpr) return false;
|
|
121
|
+
|
|
122
|
+
// Check for float literal
|
|
123
|
+
const literal = primaryExpr.literal();
|
|
124
|
+
if (literal) {
|
|
125
|
+
return this.isFloatLiteral(literal);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Check for identifier that's a float variable
|
|
129
|
+
const identifier = primaryExpr.IDENTIFIER();
|
|
130
|
+
if (identifier) {
|
|
131
|
+
return this.floatVars.has(identifier.getText());
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Check if a literal is a floating-point number
|
|
139
|
+
*/
|
|
140
|
+
private isFloatLiteral(ctx: Parser.LiteralContext): boolean {
|
|
141
|
+
// Check for FLOAT_LITERAL token
|
|
142
|
+
if (ctx.FLOAT_LITERAL()) return true;
|
|
143
|
+
|
|
144
|
+
// Check text for decimal point (fallback)
|
|
145
|
+
const text = ctx.getText();
|
|
146
|
+
return text.includes(".") && !text.startsWith('"');
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Analyzer that detects modulo operations with floating-point types
|
|
152
|
+
*/
|
|
153
|
+
class FloatModuloAnalyzer {
|
|
154
|
+
private errors: IFloatModuloError[] = [];
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Analyze the parse tree for float modulo operations
|
|
158
|
+
*/
|
|
159
|
+
public analyze(tree: Parser.ProgramContext): IFloatModuloError[] {
|
|
160
|
+
this.errors = [];
|
|
161
|
+
|
|
162
|
+
// First pass: collect float variables
|
|
163
|
+
const collector = new FloatVariableCollector();
|
|
164
|
+
ParseTreeWalker.DEFAULT.walk(collector, tree);
|
|
165
|
+
const floatVars = collector.getFloatVars();
|
|
166
|
+
|
|
167
|
+
// Second pass: detect modulo with floats
|
|
168
|
+
const listener = new FloatModuloListener(this, floatVars);
|
|
169
|
+
ParseTreeWalker.DEFAULT.walk(listener, tree);
|
|
170
|
+
|
|
171
|
+
return this.errors;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Add a float modulo error
|
|
176
|
+
*/
|
|
177
|
+
public addError(line: number, column: number): void {
|
|
178
|
+
this.errors.push({
|
|
179
|
+
code: "E0804",
|
|
180
|
+
line,
|
|
181
|
+
column,
|
|
182
|
+
message: "Modulo operator not supported for floating-point types",
|
|
183
|
+
helpText:
|
|
184
|
+
"The % operator only works with integer types. Use fmod() from <math.h> for floating-point remainder.",
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Get all detected errors
|
|
190
|
+
*/
|
|
191
|
+
public getErrors(): IFloatModuloError[] {
|
|
192
|
+
return this.errors;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export default FloatModuloAnalyzer;
|
|
@@ -13,9 +13,7 @@ import * as Parser from "../parser/grammar/CNextParser";
|
|
|
13
13
|
import SymbolTable from "../symbols/SymbolTable";
|
|
14
14
|
import ESourceLanguage from "../types/ESourceLanguage";
|
|
15
15
|
import ESymbolKind from "../types/ESymbolKind";
|
|
16
|
-
import
|
|
17
|
-
|
|
18
|
-
export { IFunctionCallError };
|
|
16
|
+
import IFunctionCallError from "./types/IFunctionCallError";
|
|
19
17
|
|
|
20
18
|
/**
|
|
21
19
|
* C-Next built-in functions
|
|
@@ -341,7 +339,7 @@ class FunctionCallListener extends CNextListener {
|
|
|
341
339
|
/**
|
|
342
340
|
* Analyzes C-Next AST for function calls before definition
|
|
343
341
|
*/
|
|
344
|
-
|
|
342
|
+
class FunctionCallAnalyzer {
|
|
345
343
|
private errors: IFunctionCallError[] = [];
|
|
346
344
|
|
|
347
345
|
/** Functions that have been defined (in order of appearance) */
|
|
@@ -14,10 +14,8 @@
|
|
|
14
14
|
import { ParseTreeWalker } from "antlr4ng";
|
|
15
15
|
import { CNextListener } from "../parser/grammar/CNextListener";
|
|
16
16
|
import * as Parser from "../parser/grammar/CNextParser";
|
|
17
|
-
import
|
|
18
|
-
|
|
19
|
-
IDeclarationInfo,
|
|
20
|
-
} from "./types/IInitializationError";
|
|
17
|
+
import IInitializationError from "./types/IInitializationError";
|
|
18
|
+
import IDeclarationInfo from "./types/IDeclarationInfo";
|
|
21
19
|
|
|
22
20
|
/**
|
|
23
21
|
* Tracks the initialization state of a variable
|
|
@@ -156,14 +154,19 @@ class InitializationListener extends CNextListener {
|
|
|
156
154
|
this.analyzer.recordAssignment(name);
|
|
157
155
|
}
|
|
158
156
|
|
|
159
|
-
// Member access: p.x <- value (
|
|
157
|
+
// Member access: p.x <- value (struct field) or arr[i][j] <- value (multi-dim array)
|
|
160
158
|
if (targetCtx.memberAccess()) {
|
|
161
159
|
const memberCtx = targetCtx.memberAccess()!;
|
|
162
160
|
const identifiers = memberCtx.IDENTIFIER();
|
|
163
161
|
if (identifiers.length >= 2) {
|
|
162
|
+
// Struct field access: p.x <- value
|
|
164
163
|
const varName = identifiers[0].getText();
|
|
165
164
|
const fieldName = identifiers[1].getText();
|
|
166
165
|
this.analyzer.recordAssignment(varName, fieldName);
|
|
166
|
+
} else if (identifiers.length === 1) {
|
|
167
|
+
// Multi-dimensional array access: arr[i][j] <- value
|
|
168
|
+
const varName = identifiers[0].getText();
|
|
169
|
+
this.analyzer.recordAssignment(varName);
|
|
167
170
|
}
|
|
168
171
|
}
|
|
169
172
|
|
|
@@ -346,11 +349,54 @@ class InitializationListener extends CNextListener {
|
|
|
346
349
|
this.savedStates.push(this.analyzer.cloneScopeState());
|
|
347
350
|
};
|
|
348
351
|
|
|
349
|
-
|
|
350
|
-
|
|
352
|
+
/**
|
|
353
|
+
* Check if a for-loop is deterministic (will definitely run at least once)
|
|
354
|
+
* A loop is deterministic if it has the form: for (var <- 0; var < CONSTANT; ...)
|
|
355
|
+
* where CONSTANT > 0
|
|
356
|
+
*/
|
|
357
|
+
private isDeterministicForLoop(ctx: Parser.ForStatementContext): boolean {
|
|
358
|
+
const init = ctx.forInit();
|
|
359
|
+
const cond = ctx.expression();
|
|
360
|
+
|
|
361
|
+
if (!init || !cond) return false;
|
|
362
|
+
|
|
363
|
+
// Check if init is a variable declaration starting at 0
|
|
364
|
+
const forVarDecl = init.forVarDecl();
|
|
365
|
+
if (forVarDecl) {
|
|
366
|
+
const initExpr = forVarDecl.expression();
|
|
367
|
+
if (!initExpr) return false;
|
|
368
|
+
const initText = initExpr.getText();
|
|
369
|
+
if (initText !== "0") return false;
|
|
370
|
+
} else {
|
|
371
|
+
// forAssignment case: check if assigning 0
|
|
372
|
+
const forAssign = init.forAssignment();
|
|
373
|
+
if (!forAssign) return false;
|
|
374
|
+
const assignExpr = forAssign.expression();
|
|
375
|
+
if (!assignExpr) return false;
|
|
376
|
+
const assignText = assignExpr.getText();
|
|
377
|
+
if (assignText !== "0") return false;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// Check if condition is var < POSITIVE_CONSTANT
|
|
381
|
+
const condText = cond.getText();
|
|
382
|
+
// Match patterns like "i<4" or "ti<3" (no spaces in AST getText())
|
|
383
|
+
const match = condText.match(/^\w+<(\d+)$/);
|
|
384
|
+
if (!match) return false;
|
|
385
|
+
const bound = parseInt(match[1], 10);
|
|
386
|
+
return bound > 0;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
override exitForStatement = (ctx: Parser.ForStatementContext): void => {
|
|
351
390
|
const stateBeforeLoop = this.savedStates.pop();
|
|
352
391
|
if (stateBeforeLoop) {
|
|
353
|
-
this.
|
|
392
|
+
const isDeterministic = this.isDeterministicForLoop(ctx);
|
|
393
|
+
if (isDeterministic) {
|
|
394
|
+
// Deterministic loop - preserve initialization (loop WILL run)
|
|
395
|
+
this.analyzer.mergeInitializationState(stateBeforeLoop);
|
|
396
|
+
} else {
|
|
397
|
+
// Non-deterministic loop - conservative restore (loop might not run)
|
|
398
|
+
this.analyzer.restoreFromState(stateBeforeLoop);
|
|
399
|
+
}
|
|
354
400
|
}
|
|
355
401
|
};
|
|
356
402
|
}
|
|
@@ -358,7 +404,7 @@ class InitializationListener extends CNextListener {
|
|
|
358
404
|
/**
|
|
359
405
|
* Analyzes C-Next AST for use-before-initialization errors
|
|
360
406
|
*/
|
|
361
|
-
|
|
407
|
+
class InitializationAnalyzer {
|
|
362
408
|
private errors: IInitializationError[] = [];
|
|
363
409
|
|
|
364
410
|
private currentScope: IScope | null = null;
|
|
@@ -653,6 +699,32 @@ export class InitializationAnalyzer {
|
|
|
653
699
|
}
|
|
654
700
|
}
|
|
655
701
|
|
|
702
|
+
/**
|
|
703
|
+
* Merge initialization state from a saved snapshot
|
|
704
|
+
* Used for deterministic loops where initialization inside the loop
|
|
705
|
+
* should be preserved (the loop WILL run at least once)
|
|
706
|
+
*/
|
|
707
|
+
public mergeInitializationState(
|
|
708
|
+
beforeState: Map<string, IVariableState>,
|
|
709
|
+
): void {
|
|
710
|
+
let scope = this.currentScope;
|
|
711
|
+
while (scope) {
|
|
712
|
+
for (const [name, currentState] of scope.variables) {
|
|
713
|
+
const beforeVar = beforeState.get(name);
|
|
714
|
+
if (beforeVar) {
|
|
715
|
+
// Preserve initialization if it happened inside the loop
|
|
716
|
+
// (currentState.initialized stays true if set inside loop)
|
|
717
|
+
// Merge initializedFields from before state to preserve any
|
|
718
|
+
// fields that were initialized before the loop
|
|
719
|
+
for (const field of beforeVar.initializedFields) {
|
|
720
|
+
currentState.initializedFields.add(field);
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
scope = scope.parent;
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
|
|
656
728
|
// ========================================================================
|
|
657
729
|
// Error Reporting
|
|
658
730
|
// ========================================================================
|
|
@@ -16,9 +16,7 @@
|
|
|
16
16
|
import { ParseTreeWalker } from "antlr4ng";
|
|
17
17
|
import { CNextListener } from "../parser/grammar/CNextListener";
|
|
18
18
|
import * as Parser from "../parser/grammar/CNextParser";
|
|
19
|
-
import
|
|
20
|
-
|
|
21
|
-
export { INullCheckError };
|
|
19
|
+
import INullCheckError from "./types/INullCheckError";
|
|
22
20
|
|
|
23
21
|
/**
|
|
24
22
|
* Metadata for C library functions that can return NULL
|
|
@@ -284,7 +282,7 @@ class NullCheckListener extends CNextListener {
|
|
|
284
282
|
/**
|
|
285
283
|
* Analyzes C-Next AST for NULL safety violations
|
|
286
284
|
*/
|
|
287
|
-
|
|
285
|
+
class NullCheckAnalyzer {
|
|
288
286
|
private errors: INullCheckError[] = [];
|
|
289
287
|
|
|
290
288
|
/** Included headers (for context) */
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Information about where a variable was declared
|
|
3
|
+
*/
|
|
4
|
+
interface IDeclarationInfo {
|
|
5
|
+
/** Variable name */
|
|
6
|
+
name: string;
|
|
7
|
+
/** Line where variable was declared */
|
|
8
|
+
line: number;
|
|
9
|
+
/** Column where variable was declared */
|
|
10
|
+
column: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export default IDeclarationInfo;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error reported when modulo operator is used with floating-point types
|
|
3
|
+
*
|
|
4
|
+
* Error codes:
|
|
5
|
+
* - E0804: Modulo operator used with floating-point operand
|
|
6
|
+
*/
|
|
7
|
+
interface IFloatModuloError {
|
|
8
|
+
/** Error code (E0804) */
|
|
9
|
+
code: string;
|
|
10
|
+
/** Line number where the error occurred */
|
|
11
|
+
line: number;
|
|
12
|
+
/** Column number where the error occurred */
|
|
13
|
+
column: number;
|
|
14
|
+
/** Human-readable error message */
|
|
15
|
+
message: string;
|
|
16
|
+
/** Optional help text with suggested fix */
|
|
17
|
+
helpText?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export default IFloatModuloError;
|
|
@@ -3,22 +3,12 @@
|
|
|
3
3
|
* Used for Rust-style "use before initialization" detection
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
* Information about where a variable was declared
|
|
8
|
-
*/
|
|
9
|
-
export interface IDeclarationInfo {
|
|
10
|
-
/** Variable name */
|
|
11
|
-
name: string;
|
|
12
|
-
/** Line where variable was declared */
|
|
13
|
-
line: number;
|
|
14
|
-
/** Column where variable was declared */
|
|
15
|
-
column: number;
|
|
16
|
-
}
|
|
6
|
+
import IDeclarationInfo from "./IDeclarationInfo";
|
|
17
7
|
|
|
18
8
|
/**
|
|
19
9
|
* Error for using a variable before initialization
|
|
20
10
|
*/
|
|
21
|
-
|
|
11
|
+
interface IInitializationError {
|
|
22
12
|
/** Error code (E0381 matches Rust's error code) */
|
|
23
13
|
code: "E0381";
|
|
24
14
|
/** The variable or field that was used before initialization */
|
|
@@ -35,21 +25,4 @@ export interface IInitializationError {
|
|
|
35
25
|
message: string;
|
|
36
26
|
}
|
|
37
27
|
|
|
38
|
-
/**
|
|
39
|
-
* Format an initialization error into a Rust-style error message
|
|
40
|
-
*/
|
|
41
|
-
export function formatInitializationError(error: IInitializationError): string {
|
|
42
|
-
const certainty = error.mayBeUninitialized ? "possibly " : "";
|
|
43
|
-
return `error[${error.code}]: use of ${certainty}uninitialized variable '${error.variable}'
|
|
44
|
-
--> line ${error.line}:${error.column}
|
|
45
|
-
|
|
|
46
|
-
${error.declaration.line} | ${error.declaration.name}
|
|
47
|
-
| - variable declared here
|
|
48
|
-
...
|
|
49
|
-
${error.line} | ${error.variable}
|
|
50
|
-
| ^ use of ${certainty}uninitialized '${error.variable}'
|
|
51
|
-
|
|
|
52
|
-
= help: consider initializing '${error.variable}'`;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
28
|
export default IInitializationError;
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* - E0903: NULL can only be used in comparison context
|
|
8
8
|
* - E0904: Cannot store C function pointer return in variable
|
|
9
9
|
*/
|
|
10
|
-
|
|
10
|
+
interface INullCheckError {
|
|
11
11
|
/** Error code (E0901-E0904) */
|
|
12
12
|
code: string;
|
|
13
13
|
/** Name of the function or literal involved */
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import IInitializationError from "./IInitializationError";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Format an initialization error into a Rust-style error message
|
|
5
|
+
*/
|
|
6
|
+
function formatInitializationError(error: IInitializationError): string {
|
|
7
|
+
const certainty = error.mayBeUninitialized ? "possibly " : "";
|
|
8
|
+
return `error[${error.code}]: use of ${certainty}uninitialized variable '${error.variable}'
|
|
9
|
+
--> line ${error.line}:${error.column}
|
|
10
|
+
|
|
|
11
|
+
${error.declaration.line} | ${error.declaration.name}
|
|
12
|
+
| - variable declared here
|
|
13
|
+
...
|
|
14
|
+
${error.line} | ${error.variable}
|
|
15
|
+
| ^ use of ${certainty}uninitialized '${error.variable}'
|
|
16
|
+
|
|
|
17
|
+
= help: consider initializing '${error.variable}'`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export default formatInitializationError;
|