c-next 0.1.5 → 0.1.6

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 (71) hide show
  1. package/README.md +43 -37
  2. package/grammar/CNext.g4 +4 -5
  3. package/package.json +6 -2
  4. package/src/analysis/GrammarCoverageListener.ts +150 -0
  5. package/src/analysis/InitializationAnalyzer.ts +114 -11
  6. package/src/analysis/types/IGrammarCoverageReport.ts +29 -0
  7. package/src/codegen/CodeGenerator.ts +5512 -6207
  8. package/src/codegen/SymbolCollector.ts +544 -0
  9. package/src/codegen/TypeResolver.ts +84 -27
  10. package/src/codegen/TypeValidator.ts +907 -0
  11. package/src/codegen/generators/GeneratorRegistry.ts +139 -0
  12. package/src/codegen/generators/IGeneratorInput.ts +48 -0
  13. package/src/codegen/generators/IGeneratorOutput.ts +15 -0
  14. package/src/codegen/generators/IGeneratorState.ts +31 -0
  15. package/src/codegen/generators/IOrchestrator.ts +272 -0
  16. package/src/codegen/generators/ISymbolInfo.ts +96 -0
  17. package/src/codegen/generators/TGeneratorEffect.ts +46 -0
  18. package/src/codegen/generators/TGeneratorFn.ts +25 -0
  19. package/src/codegen/generators/TIncludeHeader.ts +6 -0
  20. package/src/codegen/generators/declarationGenerators/BitmapGenerator.ts +85 -0
  21. package/src/codegen/generators/declarationGenerators/EnumGenerator.ts +69 -0
  22. package/src/codegen/generators/declarationGenerators/FunctionGenerator.ts +94 -0
  23. package/src/codegen/generators/declarationGenerators/RegisterGenerator.ts +70 -0
  24. package/src/codegen/generators/declarationGenerators/ScopeGenerator.ts +298 -0
  25. package/src/codegen/generators/declarationGenerators/ScopedRegisterGenerator.ts +67 -0
  26. package/src/codegen/generators/declarationGenerators/StructGenerator.ts +142 -0
  27. package/src/codegen/generators/expressions/AccessExprGenerator.ts +358 -0
  28. package/src/codegen/generators/expressions/BinaryExprGenerator.ts +407 -0
  29. package/src/codegen/generators/expressions/CallExprGenerator.ts +185 -0
  30. package/src/codegen/generators/expressions/ExpressionGenerator.ts +87 -0
  31. package/src/codegen/generators/expressions/LiteralGenerator.ts +65 -0
  32. package/src/codegen/generators/expressions/UnaryExprGenerator.ts +53 -0
  33. package/src/codegen/generators/expressions/index.ts +33 -0
  34. package/src/codegen/generators/statements/AtomicGenerator.ts +249 -0
  35. package/src/codegen/generators/statements/ControlFlowGenerator.ts +267 -0
  36. package/src/codegen/generators/statements/CriticalGenerator.ts +65 -0
  37. package/src/codegen/generators/statements/SwitchGenerator.ts +227 -0
  38. package/src/codegen/generators/statements/index.ts +29 -0
  39. package/src/codegen/generators/support/CommentUtils.ts +63 -0
  40. package/src/codegen/generators/support/HelperGenerator.ts +361 -0
  41. package/src/codegen/generators/support/IncludeGenerator.ts +134 -0
  42. package/src/codegen/generators/support/index.ts +37 -0
  43. package/src/codegen/types/ITypeResolverDeps.ts +23 -0
  44. package/src/codegen/types/ITypeValidatorDeps.ts +53 -0
  45. package/src/codegen/types/TYPE_LIMITS.ts +38 -0
  46. package/src/codegen/types/TYPE_MAP.ts +20 -0
  47. package/src/codegen/types/WIDER_TYPE_MAP.ts +16 -0
  48. package/src/index.ts +44 -3
  49. package/src/lib/transpiler.ts +36 -2
  50. package/src/lib/types/ITranspileOptions.ts +2 -0
  51. package/src/lib/types/ITranspileResult.ts +3 -0
  52. package/src/parser/grammar/CNext.interp +1 -5
  53. package/src/parser/grammar/CNext.tokens +155 -159
  54. package/src/parser/grammar/CNextLexer.interp +1 -7
  55. package/src/parser/grammar/CNextLexer.tokens +155 -159
  56. package/src/parser/grammar/CNextLexer.ts +532 -541
  57. package/src/parser/grammar/CNextParser.ts +891 -822
  58. package/src/pipeline/CacheManager.ts +370 -0
  59. package/src/pipeline/Pipeline.ts +966 -0
  60. package/src/pipeline/runAnalyzers.ts +151 -0
  61. package/src/pipeline/types/ICacheConfig.ts +13 -0
  62. package/src/pipeline/types/ICacheSymbols.ts +12 -0
  63. package/src/pipeline/types/ICachedFileEntry.ts +21 -0
  64. package/src/pipeline/types/IFileResult.ts +26 -0
  65. package/src/pipeline/types/IPipelineConfig.ts +45 -0
  66. package/src/pipeline/types/IPipelineResult.ts +37 -0
  67. package/src/pipeline/types/ISerializedSymbol.ts +40 -0
  68. package/src/project/Project.ts +41 -435
  69. package/src/project/types/IProjectConfig.ts +6 -0
  70. package/src/symbols/CSymbolCollector.ts +26 -2
  71. package/src/symbols/SymbolTable.ts +88 -0
package/README.md CHANGED
@@ -661,43 +661,44 @@ 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 |
700
- | [ADR-052](docs/decisions/adr-052-safe-numeric-literal-generation.md) | Safe Numeric Literals | `type_MIN`/`type_MAX` constants + safe hex conversion |
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 |
701
+ | [ADR-053](docs/decisions/adr-053-transpiler-pipeline-architecture.md) | Transpiler Pipeline | Unified multi-pass pipeline with header symbol extraction |
701
702
 
702
703
  ### Research (v1 Roadmap)
703
704
 
@@ -763,6 +764,11 @@ npm test -- tests/enum/my.test.cnx # Run single test file
763
764
  # Code quality (auto-run by pre-commit hooks)
764
765
  npm run prettier:fix # Format all code
765
766
  npm run eslint:check # Check for lint errors
767
+
768
+ # Coverage tracking
769
+ npm run coverage:check # Feature coverage report
770
+ npm run coverage:grammar # Grammar rule coverage (generates GRAMMAR-COVERAGE.md)
771
+ npm run coverage:grammar:check # Grammar coverage with threshold check (CI)
766
772
  ```
767
773
 
768
774
  **Note:** C-Next runs directly via `tsx` without a build step. The `typecheck` command validates types only and does not generate any output files.
package/grammar/CNext.g4 CHANGED
@@ -281,7 +281,9 @@ globalMemberAccess
281
281
 
282
282
  globalArrayAccess
283
283
  : 'global' '.' IDENTIFIER '[' expression ']' // global.arr[i]
284
+ | 'global' '.' IDENTIFIER '[' expression ',' expression ']' // global.arr[start, width]
284
285
  | 'global' '.' IDENTIFIER ('.' IDENTIFIER)+ '[' expression ']' // global.GPIO7.DR_SET[i]
286
+ | 'global' '.' IDENTIFIER ('.' IDENTIFIER)+ '[' expression ',' expression ']' // global.GPIO7.REG[start, width]
285
287
  ;
286
288
 
287
289
  expressionStatement
@@ -558,8 +560,7 @@ literal
558
560
  | CHAR_LITERAL
559
561
  | 'true'
560
562
  | 'false'
561
- | 'null'
562
- | 'NULL' // ADR-047: C library NULL for interop
563
+ | 'NULL' // ADR-047: C library NULL for interop (no lowercase 'null' - ADR-039)
563
564
  ;
564
565
 
565
566
  // ============================================================================
@@ -639,8 +640,7 @@ DEFAULT : 'default';
639
640
  RETURN : 'return';
640
641
  TRUE : 'true';
641
642
  FALSE : 'false';
642
- NULL : 'null';
643
- C_NULL : 'NULL'; // ADR-047: C library NULL for interop
643
+ C_NULL : 'NULL'; // ADR-047: C library NULL for interop (no lowercase 'null' - ADR-039)
644
644
  STRING : 'string'; // ADR-045: Bounded string type
645
645
  SIZEOF : 'sizeof'; // ADR-023: Sizeof operator
646
646
 
@@ -751,7 +751,6 @@ RBRACKET : ']';
751
751
  SEMI : ';';
752
752
  COMMA : ',';
753
753
  DOT : '.';
754
- DOTDOT : '..';
755
754
  AT : '@';
756
755
  COLON : ':';
757
756
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "c-next",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "A safer C for embedded systems development. Transpiles to clean, readable C.",
5
5
  "main": "src/index.ts",
6
6
  "bin": {
@@ -18,6 +18,7 @@
18
18
  "typecheck": "tsc --noEmit",
19
19
  "test": "tsx scripts/test.ts",
20
20
  "test:cli": "node scripts/test-cli.js",
21
+ "test:q": "tsx scripts/test.ts -q",
21
22
  "test:update": "tsx scripts/test.ts --update",
22
23
  "analyze": "./scripts/static-analysis.sh",
23
24
  "clean": "rm -rf src/parser",
@@ -30,7 +31,10 @@
30
31
  "coverage:check": "tsx scripts/coverage-checker.ts check",
31
32
  "coverage:report": "tsx scripts/coverage-checker.ts report",
32
33
  "coverage:gaps": "tsx scripts/coverage-checker.ts gaps",
33
- "coverage:ids": "tsx scripts/coverage-checker.ts ids"
34
+ "coverage:ids": "tsx scripts/coverage-checker.ts ids",
35
+ "coverage:grammar": "tsx scripts/grammar-coverage.ts report",
36
+ "coverage:grammar:check": "tsx scripts/grammar-coverage.ts check",
37
+ "coverage:grammar:console": "tsx scripts/grammar-coverage.ts console"
34
38
  },
35
39
  "keywords": [
36
40
  "c",
@@ -0,0 +1,150 @@
1
+ /**
2
+ * Grammar Coverage Listener
3
+ * Tracks which ANTLR grammar rules are executed during parsing
4
+ *
5
+ * This listener attaches to the parser and records:
6
+ * - Parser rules visited (e.g., program, expression, statement)
7
+ * - Lexer rules matched (e.g., IDENTIFIER, INTEGER_LITERAL)
8
+ *
9
+ * Used to identify dead grammar code and untested language constructs.
10
+ */
11
+
12
+ import {
13
+ ErrorNode,
14
+ ParserRuleContext,
15
+ ParseTreeListener,
16
+ TerminalNode,
17
+ } from "antlr4ng";
18
+ import IGrammarCoverageReport from "./types/IGrammarCoverageReport";
19
+
20
+ class GrammarCoverageListener implements ParseTreeListener {
21
+ private parserRuleVisits: Map<string, number> = new Map();
22
+ private lexerRuleVisits: Map<string, number> = new Map();
23
+ private readonly parserRuleNames: string[];
24
+ private readonly lexerRuleNames: string[];
25
+
26
+ constructor(parserRuleNames: string[], lexerRuleNames: string[]) {
27
+ this.parserRuleNames = parserRuleNames;
28
+ this.lexerRuleNames = lexerRuleNames;
29
+ }
30
+
31
+ /**
32
+ * Called when entering every parser rule
33
+ */
34
+ enterEveryRule(ctx: ParserRuleContext): void {
35
+ const ruleName = this.parserRuleNames[ctx.ruleIndex];
36
+ if (ruleName) {
37
+ const count = this.parserRuleVisits.get(ruleName) || 0;
38
+ this.parserRuleVisits.set(ruleName, count + 1);
39
+ }
40
+ }
41
+
42
+ /**
43
+ * Called when exiting every parser rule
44
+ */
45
+ exitEveryRule(_ctx: ParserRuleContext): void {
46
+ // Not needed for coverage tracking
47
+ }
48
+
49
+ /**
50
+ * Called when visiting a terminal node (token)
51
+ */
52
+ visitTerminal(node: TerminalNode): void {
53
+ const tokenType = node.symbol.type;
54
+ // Token type -1 is EOF, skip it
55
+ if (tokenType < 0) return;
56
+
57
+ // Token types are 1-indexed in ANTLR, but the array is 0-indexed
58
+ // The first element (index 0) corresponds to token type 1
59
+ const ruleName = this.lexerRuleNames[tokenType - 1];
60
+ if (ruleName) {
61
+ const count = this.lexerRuleVisits.get(ruleName) || 0;
62
+ this.lexerRuleVisits.set(ruleName, count + 1);
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Called when visiting an error node
68
+ */
69
+ visitErrorNode(_node: ErrorNode): void {
70
+ // Track error nodes if needed in the future
71
+ }
72
+
73
+ /**
74
+ * Merge coverage from another listener (for aggregating across files)
75
+ */
76
+ merge(other: GrammarCoverageListener): void {
77
+ for (const [rule, count] of other.parserRuleVisits) {
78
+ const current = this.parserRuleVisits.get(rule) || 0;
79
+ this.parserRuleVisits.set(rule, current + count);
80
+ }
81
+ for (const [rule, count] of other.lexerRuleVisits) {
82
+ const current = this.lexerRuleVisits.get(rule) || 0;
83
+ this.lexerRuleVisits.set(rule, current + count);
84
+ }
85
+ }
86
+
87
+ /**
88
+ * Reset all coverage counters
89
+ */
90
+ reset(): void {
91
+ this.parserRuleVisits.clear();
92
+ this.lexerRuleVisits.clear();
93
+ }
94
+
95
+ /**
96
+ * Get the current parser rule visit counts
97
+ */
98
+ getParserRuleVisits(): Map<string, number> {
99
+ return new Map(this.parserRuleVisits);
100
+ }
101
+
102
+ /**
103
+ * Get the current lexer rule visit counts
104
+ */
105
+ getLexerRuleVisits(): Map<string, number> {
106
+ return new Map(this.lexerRuleVisits);
107
+ }
108
+
109
+ /**
110
+ * Generate a coverage report
111
+ */
112
+ getReport(): IGrammarCoverageReport {
113
+ const totalParserRules = this.parserRuleNames.length;
114
+ const totalLexerRules = this.lexerRuleNames.length;
115
+ const visitedParserRules = this.parserRuleVisits.size;
116
+ const visitedLexerRules = this.lexerRuleVisits.size;
117
+
118
+ const neverVisitedParserRules = this.parserRuleNames.filter(
119
+ (name) => !this.parserRuleVisits.has(name),
120
+ );
121
+ const neverVisitedLexerRules = this.lexerRuleNames.filter(
122
+ (name) => !this.lexerRuleVisits.has(name),
123
+ );
124
+
125
+ const parserCoveragePercentage =
126
+ totalParserRules > 0 ? (visitedParserRules / totalParserRules) * 100 : 0;
127
+ const lexerCoveragePercentage =
128
+ totalLexerRules > 0 ? (visitedLexerRules / totalLexerRules) * 100 : 0;
129
+ const combinedTotal = totalParserRules + totalLexerRules;
130
+ const combinedVisited = visitedParserRules + visitedLexerRules;
131
+ const combinedCoveragePercentage =
132
+ combinedTotal > 0 ? (combinedVisited / combinedTotal) * 100 : 0;
133
+
134
+ return {
135
+ totalParserRules,
136
+ totalLexerRules,
137
+ visitedParserRules,
138
+ visitedLexerRules,
139
+ neverVisitedParserRules,
140
+ neverVisitedLexerRules,
141
+ parserRuleVisits: new Map(this.parserRuleVisits),
142
+ lexerRuleVisits: new Map(this.lexerRuleVisits),
143
+ parserCoveragePercentage,
144
+ lexerCoveragePercentage,
145
+ combinedCoveragePercentage,
146
+ };
147
+ }
148
+ }
149
+
150
+ export default GrammarCoverageListener;
@@ -31,6 +31,8 @@ interface IVariableState {
31
31
  initializedFields: Set<string>;
32
32
  /** Is this a struct type? */
33
33
  isStruct: boolean;
34
+ /** Is this a string type? (string<N> or string) */
35
+ isStringType: boolean;
34
36
  }
35
37
 
36
38
  /**
@@ -132,7 +134,17 @@ class InitializationListener extends CNextListener {
132
134
  typeName = typeCtx.userType()!.IDENTIFIER().getText();
133
135
  }
134
136
 
135
- this.analyzer.declareVariable(name, line, column, hasInitializer, typeName);
137
+ // Check if this is a string type (string<N> or string)
138
+ const isStringType = typeCtx.stringType() !== null;
139
+
140
+ this.analyzer.declareVariable(
141
+ name,
142
+ line,
143
+ column,
144
+ hasInitializer,
145
+ typeName,
146
+ isStringType,
147
+ );
136
148
  };
137
149
 
138
150
  // ========================================================================
@@ -266,6 +278,47 @@ class InitializationListener extends CNextListener {
266
278
  const line = ctx.start?.line ?? 0;
267
279
  const column = ctx.start?.column ?? 0;
268
280
 
281
+ // Check if this is part of a postfixExpression with member access
282
+ const parent = ctx.parent as Parser.PostfixExpressionContext | undefined;
283
+ if (parent?.postfixOp && parent.postfixOp().length > 0) {
284
+ const ops = parent.postfixOp();
285
+ const firstOp = ops[0];
286
+ const opText = firstOp.getText();
287
+
288
+ // Issue #196 Bug 2: Skip init check for .length on non-string types
289
+ // .length is a compile-time type property that doesn't read runtime values
290
+ // BUT: struct.field.length where field is a string DOES need init check
291
+ const varState = this.analyzer.lookupVariableState(name);
292
+ const isStringType = varState?.isStringType ?? false;
293
+
294
+ // Check if chain ends with .length on non-string type
295
+ if (!isStringType) {
296
+ const lastOp = ops[ops.length - 1].getText();
297
+ if (lastOp === ".length") {
298
+ const firstOpText = ops[0].getText();
299
+ // Only skip if:
300
+ // 1. Direct .length access on non-string variable (ops = [".length"])
301
+ // 2. Array element .length access (ops = ["[...]", ".length"])
302
+ // Do NOT skip for struct member access (ops = [".field", ".length"])
303
+ // because the field might be a string type that needs init check
304
+ if (ops.length === 1 || firstOpText.startsWith("[")) {
305
+ // .length on non-string base or array element - compile-time, skip
306
+ return;
307
+ }
308
+ // Fall through for struct member access - the member might be a string
309
+ }
310
+ }
311
+
312
+ // If the first postfixOp is a member access (has '.'), check the field
313
+ if (opText.startsWith(".")) {
314
+ // Extract field name (remove the leading '.')
315
+ const fieldName = opText.slice(1);
316
+ // Check field-level initialization instead of whole variable
317
+ this.analyzer.checkRead(name, line, column, fieldName);
318
+ return;
319
+ }
320
+ }
321
+
269
322
  this.analyzer.checkRead(name, line, column);
270
323
  }
271
324
  };
@@ -415,6 +468,20 @@ class InitializationAnalyzer {
415
468
  /** Track if we're processing a write target (left side of assignment) */
416
469
  private inWriteContext: boolean = false;
417
470
 
471
+ /**
472
+ * Register external struct fields from C/C++ headers
473
+ * This allows the analyzer to recognize types defined in headers
474
+ *
475
+ * @param externalFields Map of struct name -> Set of field names
476
+ */
477
+ public registerExternalStructFields(
478
+ externalFields: Map<string, Set<string>>,
479
+ ): void {
480
+ for (const [structName, fields] of externalFields) {
481
+ this.structFields.set(structName, fields);
482
+ }
483
+ }
484
+
418
485
  /**
419
486
  * Analyze a parsed program for initialization errors
420
487
  * @param tree The parsed program AST
@@ -423,7 +490,7 @@ class InitializationAnalyzer {
423
490
  public analyze(tree: Parser.ProgramContext): IInitializationError[] {
424
491
  this.errors = [];
425
492
  this.currentScope = null;
426
- this.structFields.clear();
493
+ // Don't clear structFields - external fields may have been registered
427
494
 
428
495
  // First pass: collect struct definitions
429
496
  this.collectStructDefinitions(tree);
@@ -548,6 +615,7 @@ class InitializationAnalyzer {
548
615
  column: number,
549
616
  hasInitializer: boolean,
550
617
  typeName: string | null,
618
+ isStringType: boolean = false,
551
619
  ): void {
552
620
  if (!this.currentScope) {
553
621
  // Global scope - create implicit scope
@@ -564,6 +632,7 @@ class InitializationAnalyzer {
564
632
  initialized: hasInitializer,
565
633
  typeName,
566
634
  isStruct,
635
+ isStringType,
567
636
  // If initialized with full struct initializer, all fields are initialized
568
637
  initializedFields: hasInitializer ? new Set(fields) : new Set(),
569
638
  };
@@ -624,16 +693,41 @@ class InitializationAnalyzer {
624
693
  }
625
694
 
626
695
  if (field) {
627
- // Reading a specific field
628
- if (!state.initializedFields.has(field)) {
629
- this.addError(
630
- `${name}.${field}`,
631
- line,
632
- column,
633
- state.declaration,
634
- false, // Definitely uninitialized
635
- );
696
+ // Reading a specific field/property
697
+ if (state.isStruct && state.typeName) {
698
+ // Struct type: check if this is a real field
699
+ const structFields = this.structFields.get(state.typeName);
700
+ if (structFields && structFields.has(field)) {
701
+ // This is a real struct field - check initialization
702
+ if (!state.initializedFields.has(field)) {
703
+ this.addError(
704
+ `${name}.${field}`,
705
+ line,
706
+ column,
707
+ state.declaration,
708
+ false, // Definitely uninitialized
709
+ );
710
+ }
711
+ }
712
+ // else: field doesn't exist in struct, could be a type property like .length
713
+ // Skip check - let code generator handle unknown field errors
714
+ } else if (state.isStringType) {
715
+ // String type: .length, .capacity, and .size are runtime properties that require initialization
716
+ if (
717
+ (field === "length" || field === "capacity" || field === "size") &&
718
+ !state.initialized
719
+ ) {
720
+ this.addError(
721
+ name,
722
+ line,
723
+ column,
724
+ state.declaration,
725
+ false, // Definitely uninitialized
726
+ );
727
+ }
636
728
  }
729
+ // Other types (primitives): .field access is a type property (like .length for bit width)
730
+ // which is always available at compile time, no init check needed
637
731
  } else if (!state.initialized) {
638
732
  // Reading the whole variable
639
733
  this.addError(
@@ -660,6 +754,14 @@ class InitializationAnalyzer {
660
754
  return null;
661
755
  }
662
756
 
757
+ /**
758
+ * Public method to look up variable state
759
+ * Issue #196 Bug 2: Used by visitor to check if variable is string type
760
+ */
761
+ public lookupVariableState(name: string): IVariableState | null {
762
+ return this.lookupVariable(name);
763
+ }
764
+
663
765
  // ========================================================================
664
766
  // Control Flow
665
767
  // ========================================================================
@@ -787,6 +889,7 @@ class InitializationAnalyzer {
787
889
  initialized: true, // Parameters are always initialized by caller
788
890
  typeName,
789
891
  isStruct,
892
+ isStringType: false, // Not tracked for parameters since they're always initialized
790
893
  initializedFields: new Set(fields), // All fields initialized
791
894
  };
792
895
 
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Grammar rule coverage report aggregating statistics across parsed files
3
+ */
4
+ interface IGrammarCoverageReport {
5
+ /** Total number of parser rules in the grammar */
6
+ totalParserRules: number;
7
+ /** Total number of lexer rules in the grammar */
8
+ totalLexerRules: number;
9
+ /** Number of parser rules that were visited at least once */
10
+ visitedParserRules: number;
11
+ /** Number of lexer rules (token types) that were matched at least once */
12
+ visitedLexerRules: number;
13
+ /** Parser rules that were never visited */
14
+ neverVisitedParserRules: string[];
15
+ /** Lexer rules that were never matched */
16
+ neverVisitedLexerRules: string[];
17
+ /** Visit counts for each parser rule */
18
+ parserRuleVisits: Map<string, number>;
19
+ /** Match counts for each lexer rule (token type) */
20
+ lexerRuleVisits: Map<string, number>;
21
+ /** Coverage percentage for parser rules (0-100) */
22
+ parserCoveragePercentage: number;
23
+ /** Coverage percentage for lexer rules (0-100) */
24
+ lexerCoveragePercentage: number;
25
+ /** Combined coverage percentage (0-100) */
26
+ combinedCoveragePercentage: number;
27
+ }
28
+
29
+ export default IGrammarCoverageReport;