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.
- package/README.md +43 -37
- package/grammar/CNext.g4 +4 -5
- package/package.json +6 -2
- package/src/analysis/GrammarCoverageListener.ts +150 -0
- package/src/analysis/InitializationAnalyzer.ts +114 -11
- package/src/analysis/types/IGrammarCoverageReport.ts +29 -0
- package/src/codegen/CodeGenerator.ts +5512 -6207
- package/src/codegen/SymbolCollector.ts +544 -0
- package/src/codegen/TypeResolver.ts +84 -27
- package/src/codegen/TypeValidator.ts +907 -0
- package/src/codegen/generators/GeneratorRegistry.ts +139 -0
- package/src/codegen/generators/IGeneratorInput.ts +48 -0
- package/src/codegen/generators/IGeneratorOutput.ts +15 -0
- package/src/codegen/generators/IGeneratorState.ts +31 -0
- package/src/codegen/generators/IOrchestrator.ts +272 -0
- package/src/codegen/generators/ISymbolInfo.ts +96 -0
- package/src/codegen/generators/TGeneratorEffect.ts +46 -0
- package/src/codegen/generators/TGeneratorFn.ts +25 -0
- package/src/codegen/generators/TIncludeHeader.ts +6 -0
- package/src/codegen/generators/declarationGenerators/BitmapGenerator.ts +85 -0
- package/src/codegen/generators/declarationGenerators/EnumGenerator.ts +69 -0
- package/src/codegen/generators/declarationGenerators/FunctionGenerator.ts +94 -0
- package/src/codegen/generators/declarationGenerators/RegisterGenerator.ts +70 -0
- package/src/codegen/generators/declarationGenerators/ScopeGenerator.ts +298 -0
- package/src/codegen/generators/declarationGenerators/ScopedRegisterGenerator.ts +67 -0
- package/src/codegen/generators/declarationGenerators/StructGenerator.ts +142 -0
- package/src/codegen/generators/expressions/AccessExprGenerator.ts +358 -0
- package/src/codegen/generators/expressions/BinaryExprGenerator.ts +407 -0
- package/src/codegen/generators/expressions/CallExprGenerator.ts +185 -0
- package/src/codegen/generators/expressions/ExpressionGenerator.ts +87 -0
- package/src/codegen/generators/expressions/LiteralGenerator.ts +65 -0
- package/src/codegen/generators/expressions/UnaryExprGenerator.ts +53 -0
- package/src/codegen/generators/expressions/index.ts +33 -0
- package/src/codegen/generators/statements/AtomicGenerator.ts +249 -0
- package/src/codegen/generators/statements/ControlFlowGenerator.ts +267 -0
- package/src/codegen/generators/statements/CriticalGenerator.ts +65 -0
- package/src/codegen/generators/statements/SwitchGenerator.ts +227 -0
- package/src/codegen/generators/statements/index.ts +29 -0
- package/src/codegen/generators/support/CommentUtils.ts +63 -0
- package/src/codegen/generators/support/HelperGenerator.ts +361 -0
- package/src/codegen/generators/support/IncludeGenerator.ts +134 -0
- package/src/codegen/generators/support/index.ts +37 -0
- package/src/codegen/types/ITypeResolverDeps.ts +23 -0
- package/src/codegen/types/ITypeValidatorDeps.ts +53 -0
- package/src/codegen/types/TYPE_LIMITS.ts +38 -0
- package/src/codegen/types/TYPE_MAP.ts +20 -0
- package/src/codegen/types/WIDER_TYPE_MAP.ts +16 -0
- package/src/index.ts +44 -3
- package/src/lib/transpiler.ts +36 -2
- package/src/lib/types/ITranspileOptions.ts +2 -0
- package/src/lib/types/ITranspileResult.ts +3 -0
- package/src/parser/grammar/CNext.interp +1 -5
- package/src/parser/grammar/CNext.tokens +155 -159
- package/src/parser/grammar/CNextLexer.interp +1 -7
- package/src/parser/grammar/CNextLexer.tokens +155 -159
- package/src/parser/grammar/CNextLexer.ts +532 -541
- package/src/parser/grammar/CNextParser.ts +891 -822
- package/src/pipeline/CacheManager.ts +370 -0
- package/src/pipeline/Pipeline.ts +966 -0
- package/src/pipeline/runAnalyzers.ts +151 -0
- package/src/pipeline/types/ICacheConfig.ts +13 -0
- package/src/pipeline/types/ICacheSymbols.ts +12 -0
- package/src/pipeline/types/ICachedFileEntry.ts +21 -0
- package/src/pipeline/types/IFileResult.ts +26 -0
- package/src/pipeline/types/IPipelineConfig.ts +45 -0
- package/src/pipeline/types/IPipelineResult.ts +37 -0
- package/src/pipeline/types/ISerializedSymbol.ts +40 -0
- package/src/project/Project.ts +41 -435
- package/src/project/types/IProjectConfig.ts +6 -0
- package/src/symbols/CSymbolCollector.ts +26 -2
- 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
|
|
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)
|
|
700
|
-
| [ADR-052](docs/decisions/adr-052-safe-numeric-literal-generation.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 |
|
|
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
|
|
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.
|
|
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
|
|
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
|
-
|
|
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 (
|
|
629
|
-
this
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
state.
|
|
634
|
-
|
|
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;
|