c-next 0.2.0 → 0.2.2

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 (37) hide show
  1. package/bin/cnext.js +17 -7
  2. package/dist/index.js +139747 -0
  3. package/dist/index.js.map +7 -0
  4. package/package.json +8 -4
  5. package/src/cli/Cli.ts +5 -2
  6. package/src/cli/PathNormalizer.ts +170 -0
  7. package/src/cli/PlatformIOCommand.ts +51 -3
  8. package/src/cli/__tests__/Cli.integration.test.ts +100 -0
  9. package/src/cli/__tests__/Cli.test.ts +17 -12
  10. package/src/cli/__tests__/PathNormalizer.test.ts +411 -0
  11. package/src/cli/__tests__/PlatformIOCommand.test.ts +156 -0
  12. package/src/cli/serve/__tests__/ServeCommand.test.ts +1 -1
  13. package/src/lib/__tests__/parseWithSymbols.test.ts +228 -0
  14. package/src/lib/parseCHeader.ts +5 -1
  15. package/src/lib/parseWithSymbols.ts +62 -5
  16. package/src/lib/types/ISymbolInfo.ts +4 -0
  17. package/src/lib/utils/SymbolPathUtils.ts +87 -0
  18. package/src/lib/utils/__tests__/SymbolPathUtils.test.ts +123 -0
  19. package/src/transpiler/NodeFileSystem.ts +5 -0
  20. package/src/transpiler/logic/symbols/SymbolTable.ts +17 -0
  21. package/src/transpiler/output/codegen/CodeGenerator.ts +27 -32
  22. package/src/transpiler/output/codegen/TypeResolver.ts +12 -24
  23. package/src/transpiler/output/codegen/__tests__/CodeGenerator.coverage.test.ts +130 -5
  24. package/src/transpiler/output/codegen/__tests__/CodeGenerator.test.ts +67 -57
  25. package/src/transpiler/output/codegen/analysis/MemberChainAnalyzer.ts +9 -13
  26. package/src/transpiler/output/codegen/analysis/__tests__/MemberChainAnalyzer.test.ts +20 -10
  27. package/src/transpiler/output/codegen/assignment/AssignmentClassifier.ts +5 -2
  28. package/src/transpiler/output/codegen/assignment/__tests__/AssignmentClassifier.test.ts +18 -0
  29. package/src/transpiler/output/codegen/assignment/handlers/StringHandlers.ts +25 -4
  30. package/src/transpiler/output/codegen/assignment/handlers/__tests__/handlerTestUtils.ts +18 -0
  31. package/src/transpiler/output/codegen/generators/expressions/CallExprGenerator.ts +51 -2
  32. package/src/transpiler/output/codegen/generators/expressions/LiteralGenerator.ts +76 -8
  33. package/src/transpiler/output/codegen/generators/expressions/__tests__/CallExprGenerator.test.ts +147 -0
  34. package/src/transpiler/output/codegen/generators/expressions/__tests__/LiteralGenerator.test.ts +116 -0
  35. package/src/transpiler/output/codegen/helpers/AssignmentExpectedTypeResolver.ts +6 -5
  36. package/src/transpiler/output/codegen/helpers/__tests__/AssignmentExpectedTypeResolver.test.ts +14 -5
  37. package/src/transpiler/types/IFileSystem.ts +8 -0
@@ -29,6 +29,8 @@ function setupGenerator(source: string): {
29
29
 
30
30
  const symbolTable = new SymbolTable();
31
31
  const tSymbols = CNextResolver.resolve(tree, "test.cnx");
32
+ // Issue #831: Register TSymbols in SymbolTable (single source of truth)
33
+ symbolTable.addTSymbols(tSymbols);
32
34
  const symbols = TSymbolInfoAdapter.convert(tSymbols);
33
35
 
34
36
  const generator = new CodeGenerator();
@@ -982,7 +984,7 @@ describe("CodeGenerator", () => {
982
984
  sourcePath: "test.cnx",
983
985
  });
984
986
 
985
- expect(code).toContain("x = 42;");
987
+ expect(code).toContain("x = 42U;");
986
988
  });
987
989
 
988
990
  it("should translate compound assignment operators", () => {
@@ -1007,10 +1009,10 @@ describe("CodeGenerator", () => {
1007
1009
  sourcePath: "test.cnx",
1008
1010
  });
1009
1011
 
1010
- expect(code).toContain("x += 5;");
1011
- expect(code).toContain("x -= 3;");
1012
- expect(code).toContain("x *= 2;");
1013
- expect(code).toContain("x /= 2;");
1012
+ expect(code).toContain("x += 5U;");
1013
+ expect(code).toContain("x -= 3U;");
1014
+ expect(code).toContain("x *= 2U;");
1015
+ expect(code).toContain("x /= 2U;");
1014
1016
  });
1015
1017
  });
1016
1018
 
@@ -1159,7 +1161,7 @@ describe("CodeGenerator", () => {
1159
1161
  });
1160
1162
 
1161
1163
  expect(code).toContain("cnx_clamp_add_u8");
1162
- expect(code).toContain("cnx_clamp_add_u8(value, 100)");
1164
+ expect(code).toContain("cnx_clamp_add_u8(value, 100U)");
1163
1165
  });
1164
1166
  });
1165
1167
 
@@ -1302,7 +1304,7 @@ describe("CodeGenerator", () => {
1302
1304
  });
1303
1305
 
1304
1306
  expect(code).toContain("if (x == 10)");
1305
- expect(code).toContain("x = 5;");
1307
+ expect(code).toContain("x = 5U;");
1306
1308
  });
1307
1309
 
1308
1310
  it("should generate if-else statement", () => {
@@ -1482,7 +1484,7 @@ describe("CodeGenerator", () => {
1482
1484
  });
1483
1485
 
1484
1486
  expect(code).toContain("arr[3]");
1485
- expect(code).toContain("{1, 2, 3}");
1487
+ expect(code).toContain("{1U, 2U, 3U}");
1486
1488
  });
1487
1489
 
1488
1490
  it("should generate multi-dimensional array", () => {
@@ -1583,7 +1585,7 @@ describe("CodeGenerator", () => {
1583
1585
  sourcePath: "test.cnx",
1584
1586
  });
1585
1587
 
1586
- expect(code).toContain("a + b * 2 - 1");
1588
+ expect(code).toContain("a + b * 2U - 1U");
1587
1589
  });
1588
1590
 
1589
1591
  it("should generate comparison expressions", () => {
@@ -1868,7 +1870,7 @@ describe("CodeGenerator", () => {
1868
1870
  sourcePath: "test.cnx",
1869
1871
  });
1870
1872
 
1871
- expect(code).toContain("result = add(1, 2);");
1873
+ expect(code).toContain("result = add(1U, 2U);");
1872
1874
  });
1873
1875
 
1874
1876
  it("should generate scope function call", () => {
@@ -1893,7 +1895,7 @@ describe("CodeGenerator", () => {
1893
1895
  sourcePath: "test.cnx",
1894
1896
  });
1895
1897
 
1896
- expect(code).toContain("Math_square(5)");
1898
+ expect(code).toContain("Math_square(5U)");
1897
1899
  });
1898
1900
  });
1899
1901
 
@@ -2145,7 +2147,7 @@ describe("CodeGenerator", () => {
2145
2147
  sourcePath: "test.cnx",
2146
2148
  });
2147
2149
 
2148
- expect(code).toContain("(a > b) ? 1 : 0");
2150
+ expect(code).toContain("(a > b) ? 1U : 0U");
2149
2151
  });
2150
2152
  });
2151
2153
 
@@ -2940,7 +2942,7 @@ describe("CodeGenerator", () => {
2940
2942
  });
2941
2943
 
2942
2944
  expect(code).toContain("values[5]"); // Size inferred from initializer
2943
- expect(code).toContain("{1, 2, 3, 4, 5}");
2945
+ expect(code).toContain("{1U, 2U, 3U, 4U, 5U}");
2944
2946
  });
2945
2947
  });
2946
2948
 
@@ -3347,7 +3349,7 @@ describe("CodeGenerator", () => {
3347
3349
  });
3348
3350
 
3349
3351
  expect(code).toContain("const uint32_t LOOKUP[4]");
3350
- expect(code).toContain("{10, 20, 30, 40}");
3352
+ expect(code).toContain("{10U, 20U, 30U, 40U}");
3351
3353
  });
3352
3354
  });
3353
3355
 
@@ -3754,8 +3756,8 @@ describe("CodeGenerator", () => {
3754
3756
  sourcePath: "test.cnx",
3755
3757
  });
3756
3758
 
3757
- // Should generate {0} for fill-all syntax
3758
- expect(code).toContain("{0}");
3759
+ // Should generate all zeros with U suffix for unsigned fill-all
3760
+ expect(code).toContain("{0U, 0U");
3759
3761
  });
3760
3762
 
3761
3763
  it("should generate fill-all array with non-zero value", () => {
@@ -3773,7 +3775,7 @@ describe("CodeGenerator", () => {
3773
3775
  });
3774
3776
 
3775
3777
  // Non-zero fill-all expands to all elements
3776
- expect(code).toContain("= {255, 255, 255, 255}");
3778
+ expect(code).toContain("= {255U, 255U, 255U, 255U}");
3777
3779
  });
3778
3780
  });
3779
3781
 
@@ -3792,7 +3794,7 @@ describe("CodeGenerator", () => {
3792
3794
  sourcePath: "test.cnx",
3793
3795
  });
3794
3796
 
3795
- expect(code).toContain("{{1, 2, 3}, {4, 5, 6}}");
3797
+ expect(code).toContain("{{1U, 2U, 3U}, {4U, 5U, 6U}}");
3796
3798
  });
3797
3799
  });
3798
3800
 
@@ -5079,7 +5081,7 @@ describe("CodeGenerator", () => {
5079
5081
  sourcePath: "test.cnx",
5080
5082
  });
5081
5083
 
5082
- expect(code).toContain("add(10, 20)");
5084
+ expect(code).toContain("add(10U, 20U)");
5083
5085
  });
5084
5086
  });
5085
5087
 
@@ -5751,6 +5753,7 @@ describe("CodeGenerator", () => {
5751
5753
  sourcePath: "test.cnx",
5752
5754
  });
5753
5755
 
5756
+ // Array element assignments don't get U suffix yet
5754
5757
  expect(code).toContain("output[0]");
5755
5758
  expect(code).toContain("output[1]");
5756
5759
  });
@@ -6562,7 +6565,7 @@ describe("CodeGenerator", () => {
6562
6565
  sourcePath: "test.cnx",
6563
6566
  });
6564
6567
 
6565
- expect(code).toContain("{1, 2, 3, 4}");
6568
+ expect(code).toContain("{1U, 2U, 3U, 4U}");
6566
6569
  });
6567
6570
  });
6568
6571
 
@@ -7655,7 +7658,7 @@ describe("CodeGenerator", () => {
7655
7658
  });
7656
7659
 
7657
7660
  expect(code).toContain("while (i < 10)");
7658
- expect(code).toContain("cnx_clamp_add_u32(i, 1)");
7661
+ expect(code).toContain("cnx_clamp_add_u32(i, 1U)");
7659
7662
  });
7660
7663
 
7661
7664
  it("should handle for loop with block", () => {
@@ -7723,7 +7726,7 @@ describe("CodeGenerator", () => {
7723
7726
  });
7724
7727
 
7725
7728
  expect(code).toContain("__cnx_disable_irq()");
7726
- expect(code).toContain("cnx_clamp_add_u32(counter, 1)");
7729
+ expect(code).toContain("cnx_clamp_add_u32(counter, 1U)");
7727
7730
  expect(code).toContain("__cnx_set_PRIMASK");
7728
7731
  });
7729
7732
 
@@ -8349,7 +8352,7 @@ describe("CodeGenerator", () => {
8349
8352
  sourcePath: "test.cnx",
8350
8353
  });
8351
8354
 
8352
- expect(code).toContain("arr[5]");
8355
+ expect(code).toContain("arr[5U]");
8353
8356
  });
8354
8357
 
8355
8358
  it("should handle bit range access on integer", () => {
@@ -8389,7 +8392,7 @@ describe("CodeGenerator", () => {
8389
8392
  sourcePath: "test.cnx",
8390
8393
  });
8391
8394
 
8392
- expect(code).toContain("arr[0]");
8395
+ expect(code).toContain("arr[0U]");
8393
8396
  });
8394
8397
 
8395
8398
  it("should handle struct member array element access", () => {
@@ -8409,7 +8412,7 @@ describe("CodeGenerator", () => {
8409
8412
  sourcePath: "test.cnx",
8410
8413
  });
8411
8414
 
8412
- expect(code).toContain("d->values[0]");
8415
+ expect(code).toContain("d->values[0U]");
8413
8416
  });
8414
8417
 
8415
8418
  it("should handle C++ mode array access", () => {
@@ -8430,7 +8433,7 @@ describe("CodeGenerator", () => {
8430
8433
  cppMode: true,
8431
8434
  });
8432
8435
 
8433
- expect(code).toContain("arr[5]");
8436
+ expect(code).toContain("arr[5U]");
8434
8437
  });
8435
8438
 
8436
8439
  it("should handle multi-dimensional array access", () => {
@@ -8450,7 +8453,7 @@ describe("CodeGenerator", () => {
8450
8453
  sourcePath: "test.cnx",
8451
8454
  });
8452
8455
 
8453
- expect(code).toContain("matrix[1][2]");
8456
+ expect(code).toContain("matrix[1U][2U]");
8454
8457
  });
8455
8458
  });
8456
8459
 
@@ -8594,6 +8597,10 @@ describe("CodeGenerator", () => {
8594
8597
  const { tree, tokenStream } = CNextSourceParser.parse(source);
8595
8598
  const generator = new CodeGenerator();
8596
8599
  const tSymbols = CNextResolver.resolve(tree, "test.cnx");
8600
+ // Issue #831: Register TSymbols in SymbolTable (single source of truth)
8601
+ const symbolTable = new SymbolTable();
8602
+ symbolTable.addTSymbols(tSymbols);
8603
+ CodeGenState.symbolTable = symbolTable;
8597
8604
  const symbols = TSymbolInfoAdapter.convert(tSymbols);
8598
8605
 
8599
8606
  const code = generator.generate(tree, tokenStream, {
@@ -8602,7 +8609,7 @@ describe("CodeGenerator", () => {
8602
8609
  cppMode: true,
8603
8610
  });
8604
8611
 
8605
- expect(code).toContain("d.values[0]");
8612
+ expect(code).toContain("d.values[0U]");
8606
8613
  });
8607
8614
 
8608
8615
  it("should handle struct array element member access", () => {
@@ -8623,7 +8630,7 @@ describe("CodeGenerator", () => {
8623
8630
  sourcePath: "test.cnx",
8624
8631
  });
8625
8632
 
8626
- expect(code).toContain("points[0].x");
8633
+ expect(code).toContain("points[0U].x");
8627
8634
  });
8628
8635
 
8629
8636
  it("should handle function return member access pattern", () => {
@@ -9105,8 +9112,8 @@ describe("CodeGenerator", () => {
9105
9112
  sourcePath: "test.cnx",
9106
9113
  });
9107
9114
 
9108
- // Bitmap indexing generates bit extraction
9109
- expect(code).toContain("((flags >> 0) & 1)");
9115
+ // Bitmap indexing generates bit extraction with U suffix
9116
+ expect(code).toContain("((flags >> 0U) & 1)");
9110
9117
  });
9111
9118
  });
9112
9119
 
@@ -9368,7 +9375,7 @@ describe("CodeGenerator", () => {
9368
9375
  sourcePath: "test.cnx",
9369
9376
  });
9370
9377
 
9371
- expect(code).toContain("(x > 3) ? 10 : 20");
9378
+ expect(code).toContain("(x > 3U) ? 10U : 20U");
9372
9379
  });
9373
9380
  });
9374
9381
 
@@ -9632,7 +9639,7 @@ describe("CodeGenerator", () => {
9632
9639
  sourcePath: "test.cnx",
9633
9640
  });
9634
9641
 
9635
- expect(code).toContain("{1, 2, 3, 4, 5}");
9642
+ expect(code).toContain("{1U, 2U, 3U, 4U, 5U}");
9636
9643
  });
9637
9644
  });
9638
9645
 
@@ -9890,7 +9897,7 @@ describe("CodeGenerator", () => {
9890
9897
  sourcePath: "test.cnx",
9891
9898
  });
9892
9899
 
9893
- expect(code).toContain("sizeof(x + 1)");
9900
+ expect(code).toContain("sizeof(x + 1U)");
9894
9901
  });
9895
9902
  });
9896
9903
 
@@ -9936,6 +9943,7 @@ describe("CodeGenerator", () => {
9936
9943
  cppMode: true,
9937
9944
  });
9938
9945
 
9946
+ // Array element assignments don't get U suffix yet
9939
9947
  expect(code).toContain("c.points[0].x = 5");
9940
9948
  });
9941
9949
  });
@@ -10002,8 +10010,8 @@ describe("CodeGenerator", () => {
10002
10010
  sourcePath: "test.cnx",
10003
10011
  });
10004
10012
 
10005
- // Constant expressions may be folded
10006
- expect(code).toContain("compute(8)");
10013
+ // Literals get U suffix per MISRA Rule 7.2
10014
+ expect(code).toContain("compute(5U + 3U)");
10007
10015
  });
10008
10016
  });
10009
10017
 
@@ -10210,9 +10218,9 @@ describe("CodeGenerator", () => {
10210
10218
  sourcePath: "test.cnx",
10211
10219
  });
10212
10220
 
10213
- expect(code).toContain(">> 8");
10214
- // Mask is generated as ((1U << 4) - 1) for bit width 4
10215
- expect(code).toContain("((1U << 4) - 1)");
10221
+ expect(code).toContain(">> 8U");
10222
+ // Mask is generated as ((1U << 4U) - 1) for bit width 4
10223
+ expect(code).toContain("((1U << 4U) - 1)");
10216
10224
  });
10217
10225
  });
10218
10226
 
@@ -10694,6 +10702,7 @@ describe("CodeGenerator", () => {
10694
10702
  });
10695
10703
 
10696
10704
  // Array params don't need dereference
10705
+ // Array element access doesn't get U suffix yet
10697
10706
  expect(code).toContain("arr[5]");
10698
10707
  expect(code).not.toContain("(*arr)");
10699
10708
  });
@@ -11952,7 +11961,7 @@ describe("CodeGenerator", () => {
11952
11961
  const source = `
11953
11962
  struct Point { i32 x; i32 y; }
11954
11963
  void processPoints(Point[4] points) {
11955
- points[0].x <- 10;
11964
+ points[0U].x <- 10;
11956
11965
  }
11957
11966
  `;
11958
11967
  const { tree, tokenStream } = CNextSourceParser.parse(source);
@@ -12361,7 +12370,7 @@ describe("CodeGenerator", () => {
12361
12370
  sourcePath: "test.cnx",
12362
12371
  });
12363
12372
 
12364
- expect(code).toContain("arr[5]");
12373
+ expect(code).toContain("arr[5U]");
12365
12374
  });
12366
12375
 
12367
12376
  it("should resolve array element with variable index", () => {
@@ -12550,8 +12559,8 @@ describe("CodeGenerator", () => {
12550
12559
  sourcePath: "test.cnx",
12551
12560
  });
12552
12561
 
12553
- // Constant expressions get folded
12554
- expect(code).toContain("15");
12562
+ // Literals get U suffix per MISRA Rule 7.2
12563
+ expect(code).toContain("10U + 5U");
12555
12564
  });
12556
12565
  });
12557
12566
 
@@ -12949,11 +12958,11 @@ describe("CodeGenerator", () => {
12949
12958
  sourcePath: "test.cnx",
12950
12959
  });
12951
12960
 
12952
- // When start=0, no shift needed - just mask
12961
+ // Bit range access generates shift and mask
12953
12962
  expect(code).toContain("flags");
12954
12963
  expect(code).toContain("& 0xFF");
12955
- // Should NOT have >> 0 shift
12956
- expect(code).not.toContain(">> 0");
12964
+ // MISRA Rule 7.2: shift amount gets U suffix
12965
+ expect(code).toContain(">> 0U");
12957
12966
  });
12958
12967
 
12959
12968
  it("should generate integer bit range with shift for non-zero start", () => {
@@ -13532,7 +13541,7 @@ describe("CodeGenerator", () => {
13532
13541
  sourcePath: "test.cnx",
13533
13542
  });
13534
13543
 
13535
- expect(code).toContain("buffer[5]");
13544
+ expect(code).toContain("buffer[5U]");
13536
13545
  });
13537
13546
 
13538
13547
  it("should handle array access with variable index", () => {
@@ -13648,8 +13657,8 @@ describe("CodeGenerator", () => {
13648
13657
  });
13649
13658
 
13650
13659
  expect(code).toContain("__bits_val");
13651
- // start=0 should NOT have >> 0 shift
13652
- expect(code).not.toContain(">> 0");
13660
+ // MISRA Rule 7.2: shift amount gets U suffix
13661
+ expect(code).toContain(">> 0U");
13653
13662
  });
13654
13663
 
13655
13664
  it("should handle multiple reads from same float reusing shadow", () => {
@@ -14102,6 +14111,7 @@ describe("CodeGenerator", () => {
14102
14111
  });
14103
14112
 
14104
14113
  expect(code).toContain("Point points[3]");
14114
+ // Array element assignments don't get U suffix yet
14105
14115
  expect(code).toContain("points[0].x = 10");
14106
14116
  });
14107
14117
 
@@ -14229,7 +14239,7 @@ describe("CodeGenerator", () => {
14229
14239
  });
14230
14240
 
14231
14241
  // Array element member access
14232
- expect(code).toContain("items[0].id");
14242
+ expect(code).toContain("items[0U].id");
14233
14243
  });
14234
14244
 
14235
14245
  it("should use arrow for struct params in C mode", () => {
@@ -14629,8 +14639,8 @@ describe("CodeGenerator", () => {
14629
14639
  sourcePath: "test.cnx",
14630
14640
  });
14631
14641
 
14632
- // Position 0 should NOT generate >> 0
14633
- expect(code).not.toContain(">> 0");
14642
+ // MISRA Rule 7.2: shift amount gets U suffix
14643
+ expect(code).toContain(">> 0U");
14634
14644
  });
14635
14645
 
14636
14646
  it("should generate 64-bit mask for u64 values", () => {
@@ -15190,7 +15200,7 @@ describe("CodeGenerator", () => {
15190
15200
  sourcePath: "test.cnx",
15191
15201
  });
15192
15202
 
15193
- expect(code).toContain("multiply(5, 10)");
15203
+ expect(code).toContain("multiply(5U, 10U)");
15194
15204
  });
15195
15205
 
15196
15206
  it("should generate scoped function call", () => {
@@ -15214,7 +15224,7 @@ describe("CodeGenerator", () => {
15214
15224
  sourcePath: "test.cnx",
15215
15225
  });
15216
15226
 
15217
- expect(code).toContain("Math_square(5)");
15227
+ expect(code).toContain("Math_square(5U)");
15218
15228
  });
15219
15229
  });
15220
15230
 
@@ -15352,7 +15362,7 @@ describe("CodeGenerator", () => {
15352
15362
  sourcePath: "test.cnx",
15353
15363
  });
15354
15364
 
15355
- expect(code).toContain("arr[5]");
15365
+ expect(code).toContain("arr[5U]");
15356
15366
  });
15357
15367
 
15358
15368
  it("should generate array access with variable index", () => {
@@ -15487,8 +15497,8 @@ describe("CodeGenerator", () => {
15487
15497
  sourcePath: "test.cnx",
15488
15498
  });
15489
15499
 
15490
- // Expression should be constant-folded: 10 + 5 = 15
15491
- expect(code).toContain("uint8_t result = 15;");
15500
+ // Const values are inlined, 5 gets U suffix per MISRA Rule 7.2
15501
+ expect(code).toContain("uint8_t result = 10 + 5U;");
15492
15502
  expect(code).not.toContain("Bar_OFFSET");
15493
15503
  });
15494
15504
  });
@@ -171,25 +171,21 @@ class MemberChainAnalyzer {
171
171
  return false;
172
172
  }
173
173
 
174
- const structFields = CodeGenState.symbols?.structFields.get(
174
+ // Issue #831: Use SymbolTable as single source of truth for struct fields
175
+ const fieldInfo = CodeGenState.symbolTable?.getStructFieldInfo(
175
176
  state.currentStructType,
177
+ fieldName,
176
178
  );
177
- if (!structFields) {
179
+ if (!fieldInfo) {
178
180
  return false;
179
181
  }
180
182
 
181
- const fieldType = structFields.get(fieldName);
182
- if (!fieldType) {
183
- return false;
184
- }
185
-
186
- state.currentType = fieldType;
183
+ state.currentType = fieldInfo.type;
187
184
 
188
- // Check if this field is an array
189
- const arrayFields = CodeGenState.symbols?.structFieldArrays.get(
190
- state.currentStructType,
191
- );
192
- state.isCurrentArray = arrayFields?.has(fieldName) ?? false;
185
+ // Check if this field is an array (has array dimensions)
186
+ state.isCurrentArray =
187
+ fieldInfo.arrayDimensions !== undefined &&
188
+ fieldInfo.arrayDimensions.length > 0;
193
189
 
194
190
  // If the field type is a struct, update currentStructType
195
191
  state.currentStructType = CodeGenState.isKnownStruct(state.currentType)
@@ -9,6 +9,7 @@
9
9
  import { describe, it, expect, beforeEach } from "vitest";
10
10
  import MemberChainAnalyzer from "../MemberChainAnalyzer.js";
11
11
  import CodeGenState from "../../../../state/CodeGenState.js";
12
+ import SymbolTable from "../../../../logic/symbols/SymbolTable.js";
12
13
  import type * as Parser from "../../../../logic/parser/grammar/CNextParser.js";
13
14
 
14
15
  /** Mock type for PostfixTargetOpContext */
@@ -70,14 +71,31 @@ describe("MemberChainAnalyzer", () => {
70
71
  });
71
72
 
72
73
  /**
73
- * Helper to set up struct fields in CodeGenState.symbols
74
+ * Helper to set up struct fields in CodeGenState.symbolTable
75
+ * Issue #831: SymbolTable is now the single source of truth for struct fields
74
76
  */
75
77
  function setupStructFields(
76
78
  structName: string,
77
79
  fields: Map<string, string>,
78
80
  arrayFields: Set<string> = new Set(),
79
81
  ): void {
80
- // Initialize symbols if not set
82
+ // Initialize symbolTable if not set
83
+ if (!CodeGenState.symbolTable) {
84
+ CodeGenState.symbolTable = new SymbolTable();
85
+ }
86
+
87
+ // Register struct fields in SymbolTable
88
+ for (const [fieldName, fieldType] of fields) {
89
+ const isArray = arrayFields.has(fieldName);
90
+ CodeGenState.symbolTable.addStructField(
91
+ structName,
92
+ fieldName,
93
+ fieldType,
94
+ isArray ? [10] : undefined, // Use realistic dimension for arrays
95
+ );
96
+ }
97
+
98
+ // Also mark struct as known (for isKnownStruct checks)
81
99
  if (!CodeGenState.symbols) {
82
100
  CodeGenState.symbols = {
83
101
  knownStructs: new Set(),
@@ -108,14 +126,6 @@ describe("MemberChainAnalyzer", () => {
108
126
  };
109
127
  }
110
128
  (CodeGenState.symbols.knownStructs as Set<string>).add(structName);
111
- (CodeGenState.symbols.structFields as Map<string, Map<string, string>>).set(
112
- structName,
113
- fields,
114
- );
115
- (CodeGenState.symbols.structFieldArrays as Map<string, Set<string>>).set(
116
- structName,
117
- arrayFields,
118
- );
119
129
  }
120
130
 
121
131
  describe("analyze", () => {
@@ -669,8 +669,11 @@ class AssignmentClassifier {
669
669
  if (!structType) {
670
670
  return null;
671
671
  }
672
- const structFields = CodeGenState.symbols!.structFields.get(structType);
673
- const fieldType = structFields?.get(structFieldNames.fieldName);
672
+ // Issue #831: Use SymbolTable as single source of truth for struct fields
673
+ const fieldType = CodeGenState.symbolTable?.getStructFieldType(
674
+ structType,
675
+ structFieldNames.fieldName,
676
+ );
674
677
  return { structType, fieldType };
675
678
  }
676
679
 
@@ -3,6 +3,7 @@ import AssignmentClassifier from "../AssignmentClassifier";
3
3
  import AssignmentKind from "../AssignmentKind";
4
4
  import IAssignmentContext from "../IAssignmentContext";
5
5
  import CodeGenState from "../../../../state/CodeGenState";
6
+ import SymbolTable from "../../../../logic/symbols/SymbolTable";
6
7
  import TTypeInfo from "../../types/TTypeInfo";
7
8
 
8
9
  // ========================================================================
@@ -64,6 +65,7 @@ function createTypeInfo(overrides: Partial<TTypeInfo> = {}): TTypeInfo {
64
65
 
65
66
  /**
66
67
  * Helper to set up CodeGenState.symbols with minimal fields.
68
+ * Issue #831: Also registers struct fields in SymbolTable (single source of truth).
67
69
  */
68
70
  function setupSymbols(
69
71
  overrides: {
@@ -77,6 +79,22 @@ function setupSymbols(
77
79
  structFieldDimensions?: Map<string, Map<string, readonly number[]>>;
78
80
  } = {},
79
81
  ): void {
82
+ // Initialize symbolTable for struct field lookups
83
+ CodeGenState.symbolTable = new SymbolTable();
84
+
85
+ // Register struct fields in SymbolTable
86
+ if (overrides.structFields) {
87
+ for (const [structName, fields] of overrides.structFields) {
88
+ for (const [fieldName, fieldType] of fields) {
89
+ CodeGenState.symbolTable.addStructField(
90
+ structName,
91
+ fieldName,
92
+ fieldType,
93
+ );
94
+ }
95
+ }
96
+ }
97
+
80
98
  CodeGenState.symbols = {
81
99
  knownScopes: overrides.knownScopes ?? new Set(),
82
100
  knownStructs: overrides.knownStructs ?? new Set(),
@@ -57,10 +57,26 @@ function handleSimpleStringAssignment(ctx: IAssignmentContext): string {
57
57
  * Shared helper for struct field string handlers.
58
58
  */
59
59
  function getStructFieldType(structName: string, fieldName: string): string {
60
+ // Issue #831: Use SymbolTable as single source of truth for struct fields
60
61
  const structTypeInfo = CodeGenState.getVariableTypeInfo(structName);
61
- const structType = structTypeInfo!.baseType;
62
- const structFields = CodeGenState.symbols!.structFields.get(structType);
63
- return structFields!.get(fieldName)!;
62
+ if (!structTypeInfo) {
63
+ throw new Error(
64
+ `Error: Unknown struct variable '${structName}' in string assignment`,
65
+ );
66
+ }
67
+
68
+ const structType = structTypeInfo.baseType;
69
+ const fieldType = CodeGenState.symbolTable?.getStructFieldType(
70
+ structType,
71
+ fieldName,
72
+ );
73
+ if (!fieldType) {
74
+ throw new Error(
75
+ `Error: Unknown field '${fieldName}' on struct '${structType}' in string assignment`,
76
+ );
77
+ }
78
+
79
+ return fieldType;
64
80
  }
65
81
 
66
82
  /**
@@ -70,7 +86,12 @@ function getStructFieldType(structName: string, fieldName: string): string {
70
86
  */
71
87
  function getStructType(structName: string): string {
72
88
  const structTypeInfo = CodeGenState.getVariableTypeInfo(structName);
73
- return structTypeInfo!.baseType;
89
+ if (!structTypeInfo) {
90
+ throw new Error(
91
+ `Error: Unknown struct variable '${structName}' in string assignment`,
92
+ );
93
+ }
94
+ return structTypeInfo.baseType;
74
95
  }
75
96
 
76
97
  /**
@@ -5,6 +5,7 @@
5
5
 
6
6
  import { vi } from "vitest";
7
7
  import CodeGenState from "../../../../../state/CodeGenState";
8
+ import SymbolTable from "../../../../../logic/symbols/SymbolTable";
8
9
  import type ICodeGenApi from "../../../types/ICodeGenApi";
9
10
  import type ICodeGenSymbols from "../../../../../types/ICodeGenSymbols";
10
11
  import type TTypeInfo from "../../../types/TTypeInfo";
@@ -63,12 +64,29 @@ function createDefaultMockSymbols(): ICodeGenSymbols {
63
64
  /**
64
65
  * Set up mock symbols on CodeGenState.
65
66
  * Provides comprehensive defaults that can be overridden.
67
+ * Issue #831: Also registers struct fields in SymbolTable for single source of truth.
66
68
  */
67
69
  function setupMockSymbols(overrides: Partial<ICodeGenSymbols> = {}): void {
68
70
  CodeGenState.symbols = {
69
71
  ...createDefaultMockSymbols(),
70
72
  ...overrides,
71
73
  };
74
+
75
+ // Also register struct fields in SymbolTable (single source of truth)
76
+ if (overrides.structFields) {
77
+ if (!CodeGenState.symbolTable) {
78
+ CodeGenState.symbolTable = new SymbolTable();
79
+ }
80
+ for (const [structName, fields] of overrides.structFields) {
81
+ for (const [fieldName, fieldType] of fields) {
82
+ CodeGenState.symbolTable.addStructField(
83
+ structName,
84
+ fieldName,
85
+ fieldType,
86
+ );
87
+ }
88
+ }
89
+ }
72
90
  }
73
91
 
74
92
  /**