c-next 0.1.2 → 0.1.4
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 +62 -39
- package/grammar/CNext.g4 +2 -2
- 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 +959 -116
- package/src/codegen/HeaderGenerator.ts +39 -26
- package/src/codegen/TypeResolver.ts +36 -11
- 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/parser/grammar/CNext.interp +1 -1
- package/src/parser/grammar/CNextParser.ts +565 -538
- 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
|
@@ -661,42 +661,43 @@ Decisions are documented in `/docs/decisions/`:
|
|
|
661
661
|
|
|
662
662
|
### Implemented
|
|
663
663
|
|
|
664
|
-
| ADR
|
|
665
|
-
|
|
|
666
|
-
| [ADR-001](docs/decisions/adr-001-assignment-operator.md)
|
|
667
|
-
| [ADR-003](docs/decisions/adr-003-static-allocation.md)
|
|
668
|
-
| [ADR-004](docs/decisions/adr-004-register-bindings.md)
|
|
669
|
-
| [ADR-006](docs/decisions/adr-006-simplified-references.md)
|
|
670
|
-
| [ADR-007](docs/decisions/adr-007-type-aware-bit-indexing.md)
|
|
671
|
-
| [ADR-010](docs/decisions/adr-010-c-interoperability.md)
|
|
672
|
-
| [ADR-011](docs/decisions/adr-011-vscode-extension.md)
|
|
673
|
-
| [ADR-012](docs/decisions/adr-012-static-analysis.md)
|
|
674
|
-
| [ADR-013](docs/decisions/adr-013-const-qualifier.md)
|
|
675
|
-
| [ADR-014](docs/decisions/adr-014-structs.md)
|
|
676
|
-
| [ADR-015](docs/decisions/adr-015-null-state.md)
|
|
677
|
-
| [ADR-016](docs/decisions/adr-016-scope.md)
|
|
678
|
-
| [ADR-017](docs/decisions/adr-017-enums.md)
|
|
679
|
-
| [ADR-030](docs/decisions/adr-030-forward-declarations.md)
|
|
680
|
-
| [ADR-037](docs/decisions/adr-037-preprocessor.md)
|
|
681
|
-
| [ADR-043](docs/decisions/adr-043-comments.md)
|
|
682
|
-
| [ADR-044](docs/decisions/adr-044-primitive-types.md)
|
|
683
|
-
| [ADR-024](docs/decisions/adr-024-type-casting.md)
|
|
684
|
-
| [ADR-022](docs/decisions/adr-022-conditional-expressions.md)
|
|
685
|
-
| [ADR-025](docs/decisions/adr-025-switch-statements.md)
|
|
686
|
-
| [ADR-029](docs/decisions/adr-029-function-pointers.md)
|
|
687
|
-
| [ADR-045](docs/decisions/adr-045-string-type.md)
|
|
688
|
-
| [ADR-023](docs/decisions/adr-023-sizeof.md)
|
|
689
|
-
| [ADR-027](docs/decisions/adr-027-do-while.md)
|
|
690
|
-
| [ADR-032](docs/decisions/adr-032-nested-structs.md)
|
|
691
|
-
| [ADR-035](docs/decisions/adr-035-array-initializers.md)
|
|
692
|
-
| [ADR-036](docs/decisions/adr-036-multidimensional-arrays.md)
|
|
693
|
-
| [ADR-040](docs/decisions/adr-040-isr-declaration.md)
|
|
694
|
-
| [ADR-034](docs/decisions/adr-034-bit-fields.md)
|
|
695
|
-
| [ADR-048](docs/decisions/adr-048-cli-executable.md)
|
|
696
|
-
| [ADR-049](docs/decisions/adr-049-atomic-types.md)
|
|
697
|
-
| [ADR-050](docs/decisions/adr-050-critical-sections.md)
|
|
698
|
-
| [ADR-108](docs/decisions/adr-108-volatile-keyword.md)
|
|
699
|
-
| [ADR-047](docs/decisions/adr-047-nullable-types.md)
|
|
664
|
+
| ADR | Title | Description |
|
|
665
|
+
| -------------------------------------------------------------------- | ----------------------- | ------------------------------------------------------------ |
|
|
666
|
+
| [ADR-001](docs/decisions/adr-001-assignment-operator.md) | Assignment Operator | `<-` for assignment, `=` for comparison |
|
|
667
|
+
| [ADR-003](docs/decisions/adr-003-static-allocation.md) | Static Allocation | No dynamic memory after init |
|
|
668
|
+
| [ADR-004](docs/decisions/adr-004-register-bindings.md) | Register Bindings | Type-safe hardware access |
|
|
669
|
+
| [ADR-006](docs/decisions/adr-006-simplified-references.md) | Simplified References | Pass by reference, no pointer syntax |
|
|
670
|
+
| [ADR-007](docs/decisions/adr-007-type-aware-bit-indexing.md) | Type-Aware Bit Indexing | Integers as bit arrays, `.length` property |
|
|
671
|
+
| [ADR-010](docs/decisions/adr-010-c-interoperability.md) | C Interoperability | Unified ANTLR parser architecture |
|
|
672
|
+
| [ADR-011](docs/decisions/adr-011-vscode-extension.md) | VS Code Extension | Live C preview with syntax highlighting |
|
|
673
|
+
| [ADR-012](docs/decisions/adr-012-static-analysis.md) | Static Analysis | cppcheck integration for generated C |
|
|
674
|
+
| [ADR-013](docs/decisions/adr-013-const-qualifier.md) | Const Qualifier | Compile-time const enforcement |
|
|
675
|
+
| [ADR-014](docs/decisions/adr-014-structs.md) | Structs | Data containers without methods |
|
|
676
|
+
| [ADR-015](docs/decisions/adr-015-null-state.md) | Null State | Zero initialization for all variables |
|
|
677
|
+
| [ADR-016](docs/decisions/adr-016-scope.md) | Scope | `this.`/`global.` explicit qualification |
|
|
678
|
+
| [ADR-017](docs/decisions/adr-017-enums.md) | Enums | Type-safe enums with C-style casting |
|
|
679
|
+
| [ADR-030](docs/decisions/adr-030-forward-declarations.md) | Define-Before-Use | Functions must be defined before called |
|
|
680
|
+
| [ADR-037](docs/decisions/adr-037-preprocessor.md) | Preprocessor | Flag-only defines, const for values |
|
|
681
|
+
| [ADR-043](docs/decisions/adr-043-comments.md) | Comments | Comment preservation with MISRA compliance |
|
|
682
|
+
| [ADR-044](docs/decisions/adr-044-primitive-types.md) | Primitive Types | Fixed-width types with `clamp`/`wrap` overflow |
|
|
683
|
+
| [ADR-024](docs/decisions/adr-024-type-casting.md) | Type Casting | Widening implicit, narrowing uses bit indexing |
|
|
684
|
+
| [ADR-022](docs/decisions/adr-022-conditional-expressions.md) | Conditional Expressions | Ternary with required parens, boolean condition, no nesting |
|
|
685
|
+
| [ADR-025](docs/decisions/adr-025-switch-statements.md) | Switch Statements | Safe switch with braces, `\|\|` syntax, counted `default(n)` |
|
|
686
|
+
| [ADR-029](docs/decisions/adr-029-function-pointers.md) | Callbacks | Function-as-Type pattern with nominal typing |
|
|
687
|
+
| [ADR-045](docs/decisions/adr-045-string-type.md) | Bounded Strings | `string<N>` with compile-time safety |
|
|
688
|
+
| [ADR-023](docs/decisions/adr-023-sizeof.md) | Sizeof | Type/value size queries with safety checks |
|
|
689
|
+
| [ADR-027](docs/decisions/adr-027-do-while.md) | Do-While | `do { } while ()` with boolean condition (E0701) |
|
|
690
|
+
| [ADR-032](docs/decisions/adr-032-nested-structs.md) | Nested Structs | Named nested structs only (no anonymous) |
|
|
691
|
+
| [ADR-035](docs/decisions/adr-035-array-initializers.md) | Array Initializers | `[1, 2, 3]` syntax with `[0*]` fill-all |
|
|
692
|
+
| [ADR-036](docs/decisions/adr-036-multidimensional-arrays.md) | Multi-dim Arrays | `arr[i][j]` with compile-time bounds enforcement |
|
|
693
|
+
| [ADR-040](docs/decisions/adr-040-isr-declaration.md) | ISR Type | Built-in `ISR` type for `void(void)` function pointers |
|
|
694
|
+
| [ADR-034](docs/decisions/adr-034-bit-fields.md) | Bitmap Types | `bitmap8`/`bitmap16`/`bitmap32` for portable bit-packed data |
|
|
695
|
+
| [ADR-048](docs/decisions/adr-048-cli-executable.md) | CLI Executable | `cnext` command with smart defaults |
|
|
696
|
+
| [ADR-049](docs/decisions/adr-049-atomic-types.md) | Atomic Types | `atomic` keyword with LDREX/STREX or PRIMASK fallback |
|
|
697
|
+
| [ADR-050](docs/decisions/adr-050-critical-sections.md) | Critical Sections | `critical { }` blocks with PRIMASK save/restore |
|
|
698
|
+
| [ADR-108](docs/decisions/adr-108-volatile-keyword.md) | Volatile Variables | `volatile` keyword prevents compiler optimization |
|
|
699
|
+
| [ADR-047](docs/decisions/adr-047-nullable-types.md) | NULL for C Interop | `NULL` keyword for C stream function comparisons |
|
|
700
|
+
| [ADR-052](docs/decisions/adr-052-safe-numeric-literal-generation.md) | Safe Numeric Literals | `type_MIN`/`type_MAX` constants + safe hex conversion |
|
|
700
701
|
|
|
701
702
|
### Research (v1 Roadmap)
|
|
702
703
|
|
|
@@ -736,19 +737,41 @@ Decisions are documented in `/docs/decisions/`:
|
|
|
736
737
|
| [ADR-031](docs/decisions/adr-031-inline-functions.md) | Inline Functions | Trust compiler; `inline` is just a hint anyway |
|
|
737
738
|
| [ADR-033](docs/decisions/adr-033-packed-structs.md) | Packed Structs | Use ADR-004 register bindings or explicit serialization |
|
|
738
739
|
|
|
739
|
-
## Development
|
|
740
|
+
## Development
|
|
741
|
+
|
|
742
|
+
### Setup
|
|
743
|
+
|
|
744
|
+
```bash
|
|
745
|
+
# Clone and install (IMPORTANT: npm install sets up pre-commit hooks)
|
|
746
|
+
git clone https://github.com/jlaustill/c-next.git
|
|
747
|
+
cd c-next
|
|
748
|
+
npm install # Installs dependencies and Husky pre-commit hooks
|
|
749
|
+
```
|
|
750
|
+
|
|
751
|
+
**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.
|
|
752
|
+
|
|
753
|
+
### Commands
|
|
740
754
|
|
|
741
755
|
```bash
|
|
742
756
|
npm run antlr # Regenerate parser from grammar
|
|
743
757
|
npm run typecheck # Type-check TypeScript (no build required)
|
|
744
|
-
npm test
|
|
758
|
+
npm test # Run all tests
|
|
759
|
+
npm test -- --quiet # Minimal output (errors + summary only)
|
|
760
|
+
npm test -- tests/enum # Run specific directory
|
|
761
|
+
npm test -- tests/enum/my.test.cnx # Run single test file
|
|
762
|
+
|
|
763
|
+
# Code quality (auto-run by pre-commit hooks)
|
|
764
|
+
npm run prettier:fix # Format all code
|
|
765
|
+
npm run eslint:check # Check for lint errors
|
|
745
766
|
```
|
|
746
767
|
|
|
747
768
|
**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
769
|
|
|
749
770
|
## Contributing
|
|
750
771
|
|
|
751
|
-
|
|
772
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for the complete development workflow, testing requirements, and PR process.
|
|
773
|
+
|
|
774
|
+
**Quick start:** Ideas and feedback welcome via issues.
|
|
752
775
|
|
|
753
776
|
## License
|
|
754
777
|
|
package/grammar/CNext.g4
CHANGED
|
@@ -345,8 +345,8 @@ switchCase
|
|
|
345
345
|
caseLabel
|
|
346
346
|
: qualifiedType // Enum value: EState.IDLE
|
|
347
347
|
| IDENTIFIER // Const or enum member
|
|
348
|
-
| INTEGER_LITERAL
|
|
349
|
-
| HEX_LITERAL
|
|
348
|
+
| '-'? INTEGER_LITERAL // Allow negative integers
|
|
349
|
+
| '-'? HEX_LITERAL // Allow negative hex (e.g., -0x80)
|
|
350
350
|
| BINARY_LITERAL
|
|
351
351
|
| CHAR_LITERAL
|
|
352
352
|
;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "c-next",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
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;
|