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.
Files changed (78) hide show
  1. package/README.md +45 -39
  2. package/grammar/CNext.g4 +4 -15
  3. package/package.json +10 -2
  4. package/src/analysis/FunctionCallAnalyzer.ts +78 -1
  5. package/src/analysis/GrammarCoverageListener.ts +150 -0
  6. package/src/analysis/InitializationAnalyzer.ts +114 -11
  7. package/src/analysis/types/IGrammarCoverageReport.ts +29 -0
  8. package/src/codegen/CodeGenerator.ts +5613 -5882
  9. package/src/codegen/HeaderGenerator.ts +112 -0
  10. package/src/codegen/SymbolCollector.ts +544 -0
  11. package/src/codegen/TypeResolver.ts +84 -27
  12. package/src/codegen/TypeValidator.ts +907 -0
  13. package/src/codegen/generators/GeneratorRegistry.ts +139 -0
  14. package/src/codegen/generators/IGeneratorInput.ts +48 -0
  15. package/src/codegen/generators/IGeneratorOutput.ts +15 -0
  16. package/src/codegen/generators/IGeneratorState.ts +31 -0
  17. package/src/codegen/generators/IOrchestrator.ts +272 -0
  18. package/src/codegen/generators/ISymbolInfo.ts +96 -0
  19. package/src/codegen/generators/TGeneratorEffect.ts +46 -0
  20. package/src/codegen/generators/TGeneratorFn.ts +25 -0
  21. package/src/codegen/generators/TIncludeHeader.ts +6 -0
  22. package/src/codegen/generators/declarationGenerators/BitmapGenerator.ts +85 -0
  23. package/src/codegen/generators/declarationGenerators/EnumGenerator.ts +69 -0
  24. package/src/codegen/generators/declarationGenerators/FunctionGenerator.ts +94 -0
  25. package/src/codegen/generators/declarationGenerators/RegisterGenerator.ts +70 -0
  26. package/src/codegen/generators/declarationGenerators/ScopeGenerator.ts +298 -0
  27. package/src/codegen/generators/declarationGenerators/ScopedRegisterGenerator.ts +67 -0
  28. package/src/codegen/generators/declarationGenerators/StructGenerator.ts +142 -0
  29. package/src/codegen/generators/expressions/AccessExprGenerator.ts +358 -0
  30. package/src/codegen/generators/expressions/BinaryExprGenerator.ts +407 -0
  31. package/src/codegen/generators/expressions/CallExprGenerator.ts +185 -0
  32. package/src/codegen/generators/expressions/ExpressionGenerator.ts +87 -0
  33. package/src/codegen/generators/expressions/LiteralGenerator.ts +65 -0
  34. package/src/codegen/generators/expressions/UnaryExprGenerator.ts +53 -0
  35. package/src/codegen/generators/expressions/index.ts +33 -0
  36. package/src/codegen/generators/statements/AtomicGenerator.ts +249 -0
  37. package/src/codegen/generators/statements/ControlFlowGenerator.ts +267 -0
  38. package/src/codegen/generators/statements/CriticalGenerator.ts +65 -0
  39. package/src/codegen/generators/statements/SwitchGenerator.ts +227 -0
  40. package/src/codegen/generators/statements/index.ts +29 -0
  41. package/src/codegen/generators/support/CommentUtils.ts +63 -0
  42. package/src/codegen/generators/support/HelperGenerator.ts +361 -0
  43. package/src/codegen/generators/support/IncludeGenerator.ts +134 -0
  44. package/src/codegen/generators/support/index.ts +37 -0
  45. package/src/codegen/types/C_TYPE_WIDTH.ts +2 -2
  46. package/src/codegen/types/ITypeResolverDeps.ts +23 -0
  47. package/src/codegen/types/ITypeValidatorDeps.ts +53 -0
  48. package/src/codegen/types/TParameterInfo.ts +1 -0
  49. package/src/codegen/types/TYPE_LIMITS.ts +38 -0
  50. package/src/codegen/types/TYPE_MAP.ts +20 -0
  51. package/src/codegen/types/TYPE_WIDTH.ts +1 -1
  52. package/src/codegen/types/WIDER_TYPE_MAP.ts +16 -0
  53. package/src/index.ts +47 -4
  54. package/src/lib/transpiler.ts +36 -2
  55. package/src/lib/types/ITranspileOptions.ts +2 -0
  56. package/src/lib/types/ITranspileResult.ts +3 -0
  57. package/src/parser/grammar/CNext.interp +1 -7
  58. package/src/parser/grammar/CNext.tokens +155 -159
  59. package/src/parser/grammar/CNextLexer.interp +1 -7
  60. package/src/parser/grammar/CNextLexer.tokens +155 -159
  61. package/src/parser/grammar/CNextLexer.ts +532 -541
  62. package/src/parser/grammar/CNextListener.ts +0 -22
  63. package/src/parser/grammar/CNextParser.ts +1259 -1384
  64. package/src/parser/grammar/CNextVisitor.ts +0 -14
  65. package/src/pipeline/CacheManager.ts +370 -0
  66. package/src/pipeline/Pipeline.ts +966 -0
  67. package/src/pipeline/runAnalyzers.ts +151 -0
  68. package/src/pipeline/types/ICacheConfig.ts +13 -0
  69. package/src/pipeline/types/ICacheSymbols.ts +12 -0
  70. package/src/pipeline/types/ICachedFileEntry.ts +21 -0
  71. package/src/pipeline/types/IFileResult.ts +26 -0
  72. package/src/pipeline/types/IPipelineConfig.ts +45 -0
  73. package/src/pipeline/types/IPipelineResult.ts +37 -0
  74. package/src/pipeline/types/ISerializedSymbol.ts +40 -0
  75. package/src/project/Project.ts +41 -435
  76. package/src/project/types/IProjectConfig.ts +6 -0
  77. package/src/symbols/CSymbolCollector.ts +26 -2
  78. 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 src/
191
- cnext --project src
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 | 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
@@ -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 : '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.4",
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 included headers
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.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