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.
Files changed (64) hide show
  1. package/README.md +62 -39
  2. package/grammar/CNext.g4 +2 -2
  3. package/package.json +8 -13
  4. package/src/analysis/DivisionByZeroAnalyzer.ts +2 -2
  5. package/src/analysis/FloatModuloAnalyzer.ts +196 -0
  6. package/src/analysis/FunctionCallAnalyzer.ts +2 -4
  7. package/src/analysis/InitializationAnalyzer.ts +81 -9
  8. package/src/analysis/NullCheckAnalyzer.ts +2 -4
  9. package/src/analysis/types/IDeclarationInfo.ts +13 -0
  10. package/src/analysis/types/IDivisionByZeroError.ts +1 -1
  11. package/src/analysis/types/IFloatModuloError.ts +20 -0
  12. package/src/analysis/types/IFunctionCallError.ts +1 -1
  13. package/src/analysis/types/IInitializationError.ts +2 -29
  14. package/src/analysis/types/INullCheckError.ts +1 -1
  15. package/src/analysis/types/formatInitializationError.ts +20 -0
  16. package/src/codegen/CodeGenerator.ts +959 -116
  17. package/src/codegen/HeaderGenerator.ts +39 -26
  18. package/src/codegen/TypeResolver.ts +36 -11
  19. package/src/codegen/types/BITMAP_BACKING_TYPE.ts +11 -0
  20. package/src/codegen/types/BITMAP_SIZE.ts +11 -0
  21. package/src/codegen/types/C_TYPE_WIDTH.ts +31 -0
  22. package/src/codegen/types/FLOAT_TYPES.ts +6 -0
  23. package/src/codegen/types/IAssignmentContext.ts +12 -0
  24. package/src/codegen/types/ICodeGeneratorOptions.ts +13 -0
  25. package/src/codegen/types/IHeaderOptions.ts +15 -0
  26. package/src/codegen/types/INTEGER_TYPES.ts +8 -0
  27. package/src/codegen/types/SIGNED_TYPES.ts +6 -0
  28. package/src/codegen/types/TCodeGenContext.ts +1 -10
  29. package/src/codegen/types/TYPE_RANGES.ts +16 -0
  30. package/src/codegen/types/TYPE_WIDTH.ts +18 -0
  31. package/src/codegen/types/UNSIGNED_TYPES.ts +6 -0
  32. package/src/index.ts +4 -29
  33. package/src/lib/IncludeDiscovery.ts +3 -1
  34. package/src/lib/InputExpansion.ts +6 -2
  35. package/src/lib/PlatformIODetector.ts +1 -1
  36. package/src/lib/parse.ts +16 -0
  37. package/src/lib/parseWithSymbols.ts +148 -0
  38. package/src/lib/transpiler.ts +33 -175
  39. package/src/lib/types/IParseWithSymbolsResult.ts +16 -0
  40. package/src/lib/types/ISymbolInfo.ts +28 -0
  41. package/src/lib/types/ITranspileError.ts +15 -0
  42. package/src/lib/types/ITranspileOptions.ts +13 -0
  43. package/src/lib/types/ITranspileResult.ts +2 -65
  44. package/src/lib/types/TSymbolKind.ts +15 -0
  45. package/src/parser/grammar/CNext.interp +1 -1
  46. package/src/parser/grammar/CNextParser.ts +565 -538
  47. package/src/preprocessor/Preprocessor.ts +4 -19
  48. package/src/preprocessor/types/IPreprocessOptions.ts +20 -0
  49. package/src/preprocessor/types/IPreprocessResult.ts +1 -14
  50. package/src/preprocessor/types/ISourceMapping.ts +15 -0
  51. package/src/project/FileDiscovery.ts +3 -42
  52. package/src/project/Project.ts +73 -19
  53. package/src/project/types/EFileType.ts +13 -0
  54. package/src/project/types/IDiscoveredFile.ts +17 -0
  55. package/src/project/types/IDiscoveryOptions.ts +15 -0
  56. package/src/project/types/IProjectConfig.ts +0 -31
  57. package/src/project/types/IProjectResult.ts +27 -0
  58. package/src/symbols/CNextSymbolCollector.ts +19 -0
  59. package/src/symbols/CppSymbolCollector.ts +110 -1
  60. package/src/symbols/SymbolTable.ts +1 -11
  61. package/src/symbols/types/IStructFieldInfo.ts +11 -0
  62. package/src/types/ISymbol.ts +9 -0
  63. package/src/codegen/types/TTypeConstants.ts +0 -94
  64. 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 | 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 |
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 Commands
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 # Run all tests
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
- Ideas and feedback welcome via issues.
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.2",
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.js",
19
+ "test": "tsx scripts/test.ts",
20
20
  "test:cli": "node scripts/test-cli.js",
21
- "test:update": "tsx scripts/test.js --update",
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
- "eslint:check": "eslint src/",
27
- "eslint:fix": "eslint src/ --fix",
26
+ "oxlint:check": "oxlint src/",
27
+ "oxlint:fix": "oxlint src/ --fix",
28
28
  "prepare": "husky",
29
- "prepublishOnly": "npm run prettier:check && npm run eslint:check && npm test"
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
- "eslint --fix"
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 { IDivisionByZeroError } from "./types/IDivisionByZeroError";
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
- export class DivisionByZeroAnalyzer {
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 { IFunctionCallError } from "./types/IFunctionCallError";
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
- export class FunctionCallAnalyzer {
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
- IInitializationError,
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 (only first-level field)
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
- override exitForStatement = (_ctx: Parser.ForStatementContext): void => {
350
- // Same as while - conservative approach
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.analyzer.restoreFromState(stateBeforeLoop);
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
- export class InitializationAnalyzer {
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 { INullCheckError } from "./types/INullCheckError";
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
- export class NullCheckAnalyzer {
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;
@@ -7,7 +7,7 @@
7
7
  * - E0802: Modulo by literal zero
8
8
  * - E0803: Modulo by const that evaluates to zero
9
9
  */
10
- export interface IDivisionByZeroError {
10
+ interface IDivisionByZeroError {
11
11
  /** Error code (E0800-E0803) */
12
12
  code: string;
13
13
  /** Operator used ('/' or '%') */
@@ -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;
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Error reported when a function is called before it's defined (ADR-030)
3
3
  */
4
- export interface IFunctionCallError {
4
+ interface IFunctionCallError {
5
5
  /** Error code (E0422) */
6
6
  code: string;
7
7
  /** Name of the function that was called */