c-next 0.1.4 → 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 +45 -39
- package/grammar/CNext.g4 +4 -15
- package/package.json +10 -2
- package/src/analysis/FunctionCallAnalyzer.ts +78 -1
- 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 +5613 -5882
- package/src/codegen/HeaderGenerator.ts +112 -0
- 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/C_TYPE_WIDTH.ts +2 -2
- package/src/codegen/types/ITypeResolverDeps.ts +23 -0
- package/src/codegen/types/ITypeValidatorDeps.ts +53 -0
- package/src/codegen/types/TParameterInfo.ts +1 -0
- package/src/codegen/types/TYPE_LIMITS.ts +38 -0
- package/src/codegen/types/TYPE_MAP.ts +20 -0
- package/src/codegen/types/TYPE_WIDTH.ts +1 -1
- package/src/codegen/types/WIDER_TYPE_MAP.ts +16 -0
- package/src/index.ts +47 -4
- 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 -7
- 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/CNextListener.ts +0 -22
- package/src/parser/grammar/CNextParser.ts +1259 -1384
- package/src/parser/grammar/CNextVisitor.ts +0 -14
- 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
|
@@ -187,8 +187,8 @@ Your `.cnx` files and generated `.c` files remain untouched.
|
|
|
187
187
|
If you prefer manual control, you can also run the transpiler explicitly:
|
|
188
188
|
|
|
189
189
|
```bash
|
|
190
|
-
# Transpile all .cnx files in
|
|
191
|
-
cnext
|
|
190
|
+
# Transpile all .cnx files in a directory (recursive)
|
|
191
|
+
cnext src/
|
|
192
192
|
|
|
193
193
|
# Or transpile specific files
|
|
194
194
|
cnext src/ConfigStorage.cnx
|
|
@@ -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
|
|
@@ -505,7 +507,6 @@ type
|
|
|
505
507
|
| qualifiedType // ADR-016: Scope.Type from outside scope
|
|
506
508
|
| userType
|
|
507
509
|
| arrayType
|
|
508
|
-
| genericType
|
|
509
510
|
| 'void'
|
|
510
511
|
;
|
|
511
512
|
|
|
@@ -543,15 +544,6 @@ arrayType
|
|
|
543
544
|
| userType '[' expression ']'
|
|
544
545
|
;
|
|
545
546
|
|
|
546
|
-
genericType
|
|
547
|
-
: IDENTIFIER '<' typeArgument (',' typeArgument)* '>'
|
|
548
|
-
;
|
|
549
|
-
|
|
550
|
-
typeArgument
|
|
551
|
-
: type
|
|
552
|
-
| expression // For numeric type parameters like buffer sizes
|
|
553
|
-
;
|
|
554
|
-
|
|
555
547
|
// ----------------------------------------------------------------------------
|
|
556
548
|
// Literals (ADR-024: Type suffixes OPTIONAL, validated against target type)
|
|
557
549
|
// ----------------------------------------------------------------------------
|
|
@@ -568,8 +560,7 @@ literal
|
|
|
568
560
|
| CHAR_LITERAL
|
|
569
561
|
| 'true'
|
|
570
562
|
| 'false'
|
|
571
|
-
| 'null'
|
|
572
|
-
| 'NULL' // ADR-047: C library NULL for interop
|
|
563
|
+
| 'NULL' // ADR-047: C library NULL for interop (no lowercase 'null' - ADR-039)
|
|
573
564
|
;
|
|
574
565
|
|
|
575
566
|
// ============================================================================
|
|
@@ -649,8 +640,7 @@ DEFAULT : 'default';
|
|
|
649
640
|
RETURN : 'return';
|
|
650
641
|
TRUE : 'true';
|
|
651
642
|
FALSE : 'false';
|
|
652
|
-
NULL
|
|
653
|
-
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)
|
|
654
644
|
STRING : 'string'; // ADR-045: Bounded string type
|
|
655
645
|
SIZEOF : 'sizeof'; // ADR-023: Sizeof operator
|
|
656
646
|
|
|
@@ -761,7 +751,6 @@ RBRACKET : ']';
|
|
|
761
751
|
SEMI : ';';
|
|
762
752
|
COMMA : ',';
|
|
763
753
|
DOT : '.';
|
|
764
|
-
DOTDOT : '..';
|
|
765
754
|
AT : '@';
|
|
766
755
|
COLON : ':';
|
|
767
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",
|
|
@@ -26,7 +27,14 @@
|
|
|
26
27
|
"oxlint:check": "oxlint src/",
|
|
27
28
|
"oxlint:fix": "oxlint src/ --fix",
|
|
28
29
|
"prepare": "husky",
|
|
29
|
-
"prepublishOnly": "npm run prettier:check && npm run oxlint:check && npm test"
|
|
30
|
+
"prepublishOnly": "npm run prettier:check && npm run oxlint:check && npm test",
|
|
31
|
+
"coverage:check": "tsx scripts/coverage-checker.ts check",
|
|
32
|
+
"coverage:report": "tsx scripts/coverage-checker.ts report",
|
|
33
|
+
"coverage:gaps": "tsx scripts/coverage-checker.ts gaps",
|
|
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"
|
|
30
38
|
},
|
|
31
39
|
"keywords": [
|
|
32
40
|
"c",
|
|
@@ -229,6 +229,42 @@ class FunctionCallListener extends CNextListener {
|
|
|
229
229
|
this.analyzer = analyzer;
|
|
230
230
|
}
|
|
231
231
|
|
|
232
|
+
// ========================================================================
|
|
233
|
+
// ISR/Callback Variable Tracking (ADR-040)
|
|
234
|
+
// ========================================================================
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Track ISR-typed variables from variable declarations
|
|
238
|
+
* e.g., `ISR handler <- myFunction;`
|
|
239
|
+
*/
|
|
240
|
+
override enterVariableDeclaration = (
|
|
241
|
+
ctx: Parser.VariableDeclarationContext,
|
|
242
|
+
): void => {
|
|
243
|
+
const typeCtx = ctx.type();
|
|
244
|
+
const typeName = typeCtx.getText();
|
|
245
|
+
|
|
246
|
+
// Check if this is an ISR type or a callback type (function-as-type)
|
|
247
|
+
if (typeName === "ISR" || this.analyzer.isCallbackType(typeName)) {
|
|
248
|
+
const varName = ctx.IDENTIFIER().getText();
|
|
249
|
+
this.analyzer.defineCallableVariable(varName);
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Track ISR-typed parameters in function declarations
|
|
255
|
+
* e.g., `void execute(ISR handler) { handler(); }`
|
|
256
|
+
*/
|
|
257
|
+
override enterParameter = (ctx: Parser.ParameterContext): void => {
|
|
258
|
+
const typeCtx = ctx.type();
|
|
259
|
+
const typeName = typeCtx.getText();
|
|
260
|
+
|
|
261
|
+
// Check if this is an ISR type or a callback type (function-as-type)
|
|
262
|
+
if (typeName === "ISR" || this.analyzer.isCallbackType(typeName)) {
|
|
263
|
+
const paramName = ctx.IDENTIFIER().getText();
|
|
264
|
+
this.analyzer.defineCallableVariable(paramName);
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
|
|
232
268
|
// ========================================================================
|
|
233
269
|
// Function Definitions
|
|
234
270
|
// ========================================================================
|
|
@@ -357,6 +393,12 @@ class FunctionCallAnalyzer {
|
|
|
357
393
|
/** Current function being defined (for self-recursion detection) */
|
|
358
394
|
private currentFunctionName: string | null = null;
|
|
359
395
|
|
|
396
|
+
/** ADR-040: Variables of type ISR or callback types that can be invoked */
|
|
397
|
+
private callableVariables: Set<string> = new Set();
|
|
398
|
+
|
|
399
|
+
/** ADR-029: Callback types (function-as-type pattern) */
|
|
400
|
+
private callbackTypes: Set<string> = new Set();
|
|
401
|
+
|
|
360
402
|
/**
|
|
361
403
|
* Analyze a parsed program for function call errors
|
|
362
404
|
* @param tree The parsed program AST
|
|
@@ -373,10 +415,13 @@ class FunctionCallAnalyzer {
|
|
|
373
415
|
this.includedHeaders = new Set();
|
|
374
416
|
this.symbolTable = symbolTable ?? null;
|
|
375
417
|
this.currentFunctionName = null;
|
|
418
|
+
this.callableVariables = new Set();
|
|
419
|
+
this.callbackTypes = new Set();
|
|
376
420
|
|
|
377
|
-
// First pass: collect scope names and
|
|
421
|
+
// First pass: collect scope names, includes, and callback types
|
|
378
422
|
this.collectScopes(tree);
|
|
379
423
|
this.collectIncludes(tree);
|
|
424
|
+
this.collectCallbackTypes(tree);
|
|
380
425
|
|
|
381
426
|
// Second pass: walk tree in order, tracking definitions and checking calls
|
|
382
427
|
const listener = new FunctionCallListener(this);
|
|
@@ -424,6 +469,33 @@ class FunctionCallAnalyzer {
|
|
|
424
469
|
return false;
|
|
425
470
|
}
|
|
426
471
|
|
|
472
|
+
/**
|
|
473
|
+
* ADR-029: Collect callback types (function-as-type pattern)
|
|
474
|
+
* Any function definition creates a type that can be used for callback fields/parameters
|
|
475
|
+
*/
|
|
476
|
+
private collectCallbackTypes(tree: Parser.ProgramContext): void {
|
|
477
|
+
for (const decl of tree.declaration()) {
|
|
478
|
+
if (decl.functionDeclaration()) {
|
|
479
|
+
const name = decl.functionDeclaration()!.IDENTIFIER().getText();
|
|
480
|
+
this.callbackTypes.add(name);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* ADR-029: Check if a type name is a callback type (function-as-type)
|
|
487
|
+
*/
|
|
488
|
+
public isCallbackType(name: string): boolean {
|
|
489
|
+
return this.callbackTypes.has(name);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* ADR-040: Register a variable that holds a callable (ISR or callback)
|
|
494
|
+
*/
|
|
495
|
+
public defineCallableVariable(name: string): void {
|
|
496
|
+
this.callableVariables.add(name);
|
|
497
|
+
}
|
|
498
|
+
|
|
427
499
|
/**
|
|
428
500
|
* Register a function as defined
|
|
429
501
|
*/
|
|
@@ -488,6 +560,11 @@ class FunctionCallAnalyzer {
|
|
|
488
560
|
return; // OK - external C/C++ function
|
|
489
561
|
}
|
|
490
562
|
|
|
563
|
+
// ADR-040: Check if this is an ISR or callback variable being invoked
|
|
564
|
+
if (this.callableVariables.has(name)) {
|
|
565
|
+
return; // OK - invoking a function pointer variable
|
|
566
|
+
}
|
|
567
|
+
|
|
491
568
|
// Not defined - report error
|
|
492
569
|
this.errors.push({
|
|
493
570
|
code: "E0422",
|
|
@@ -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
|
|