c-next 0.1.64 → 0.1.66

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 (33) hide show
  1. package/grammar/CNext.g4 +9 -2
  2. package/package.json +5 -1
  3. package/src/transpiler/logic/parser/grammar/CNext.interp +2 -1
  4. package/src/transpiler/logic/parser/grammar/CNextListener.ts +11 -0
  5. package/src/transpiler/logic/parser/grammar/CNextParser.ts +992 -870
  6. package/src/transpiler/logic/parser/grammar/CNextVisitor.ts +7 -0
  7. package/src/transpiler/logic/symbols/cnext/__tests__/FunctionCollector.test.ts +6 -6
  8. package/src/transpiler/logic/symbols/cnext/__tests__/TSymbolAdapter.test.ts +179 -0
  9. package/src/transpiler/logic/symbols/cnext/__tests__/VariableCollector.test.ts +55 -0
  10. package/src/transpiler/logic/symbols/cnext/adapters/TSymbolAdapter.ts +76 -8
  11. package/src/transpiler/logic/symbols/cnext/collectors/FunctionCollector.ts +9 -10
  12. package/src/transpiler/logic/symbols/cnext/collectors/StructCollector.ts +7 -1
  13. package/src/transpiler/logic/symbols/cnext/collectors/VariableCollector.ts +33 -14
  14. package/src/transpiler/output/codegen/CodeGenerator.ts +243 -166
  15. package/src/transpiler/output/codegen/__tests__/CodeGenerator.coverage.test.ts +1086 -0
  16. package/src/transpiler/output/codegen/__tests__/CodeGenerator.test.ts +254 -22
  17. package/src/transpiler/output/codegen/generators/declarationGenerators/ArrayDimensionUtils.ts +17 -9
  18. package/src/transpiler/output/codegen/generators/declarationGenerators/__tests__/ArrayDimensionUtils.test.ts +5 -3
  19. package/src/transpiler/output/codegen/generators/declarationGenerators/__tests__/ScopeGenerator.test.ts +12 -7
  20. package/src/transpiler/output/codegen/generators/expressions/PostfixExpressionGenerator.ts +624 -12
  21. package/src/transpiler/output/codegen/generators/expressions/__tests__/PostfixExpressionGenerator.test.ts +819 -0
  22. package/src/transpiler/output/codegen/helpers/ParameterInputAdapter.ts +337 -0
  23. package/src/transpiler/output/codegen/helpers/ParameterSignatureBuilder.ts +135 -0
  24. package/src/transpiler/output/codegen/helpers/TypeGenerationHelper.ts +4 -0
  25. package/src/transpiler/output/codegen/helpers/VariableDeclarationFormatter.ts +118 -0
  26. package/src/transpiler/output/codegen/helpers/__tests__/ParameterInputAdapter.test.ts +426 -0
  27. package/src/transpiler/output/codegen/helpers/__tests__/ParameterSignatureBuilder.test.ts +315 -0
  28. package/src/transpiler/output/codegen/helpers/__tests__/VariableDeclarationFormatter.test.ts +333 -0
  29. package/src/transpiler/output/codegen/types/IParameterInput.ts +58 -0
  30. package/src/transpiler/output/codegen/types/IVariableFormatInput.ts +51 -0
  31. package/src/transpiler/output/headers/BaseHeaderGenerator.ts +20 -35
  32. package/src/transpiler/output/headers/HeaderGeneratorUtils.ts +21 -48
  33. package/src/transpiler/output/headers/__tests__/HeaderGeneratorUtils.test.ts +0 -64
@@ -451,7 +451,78 @@ const resolveStringTypeInfo = (
451
451
  };
452
452
 
453
453
  /**
454
- * Try handling property access (.length, .capacity, .size).
454
+ * Create context object for explicit length property generators.
455
+ */
456
+ const createExplicitLengthContext = (
457
+ tracking: ITrackingState,
458
+ rootIdentifier: string | undefined,
459
+ ): IExplicitLengthContext => ({
460
+ result: tracking.result,
461
+ rootIdentifier,
462
+ resolvedIdentifier: tracking.resolvedIdentifier,
463
+ previousStructType: tracking.previousStructType,
464
+ previousMemberName: tracking.previousMemberName,
465
+ subscriptDepth: tracking.subscriptDepth,
466
+ });
467
+
468
+ /**
469
+ * Apply property result to tracking state.
470
+ */
471
+ const applyPropertyResult = (
472
+ tracking: ITrackingState,
473
+ result: string,
474
+ ): void => {
475
+ tracking.result = result;
476
+ tracking.previousStructType = undefined;
477
+ tracking.previousMemberName = undefined;
478
+ };
479
+
480
+ /**
481
+ * Try handling explicit length property (ADR-058).
482
+ * Returns true if handled.
483
+ */
484
+ const tryExplicitLengthProperty = (
485
+ memberName: string,
486
+ tracking: ITrackingState,
487
+ rootIdentifier: string | undefined,
488
+ input: IGeneratorInput,
489
+ state: IGeneratorState,
490
+ orchestrator: IOrchestrator,
491
+ effects: TGeneratorEffect[],
492
+ ): boolean => {
493
+ const ctx = createExplicitLengthContext(tracking, rootIdentifier);
494
+
495
+ let result: string | null = null;
496
+ switch (memberName) {
497
+ case "bit_length":
498
+ result = generateBitLengthProperty(ctx, input, state, orchestrator);
499
+ break;
500
+ case "byte_length":
501
+ result = generateByteLengthProperty(ctx, input, state, orchestrator);
502
+ break;
503
+ case "element_count":
504
+ result = generateElementCountProperty(ctx, input, state, orchestrator);
505
+ break;
506
+ case "char_count":
507
+ result = generateCharCountProperty(
508
+ ctx,
509
+ input,
510
+ state,
511
+ orchestrator,
512
+ effects,
513
+ );
514
+ break;
515
+ }
516
+
517
+ if (result !== null) {
518
+ applyPropertyResult(tracking, result);
519
+ return true;
520
+ }
521
+ return false;
522
+ };
523
+
524
+ /**
525
+ * Try handling property access (.length, .capacity, .size, .bit_length, .byte_length, .element_count, .char_count).
455
526
  * Returns true if handled.
456
527
  */
457
528
  const tryPropertyAccess = (
@@ -464,29 +535,40 @@ const tryPropertyAccess = (
464
535
  effects: TGeneratorEffect[],
465
536
  ): boolean => {
466
537
  if (memberName === "length") {
538
+ const ctx = createExplicitLengthContext(tracking, rootIdentifier);
467
539
  const lengthResult = generateLengthProperty(
468
- {
469
- result: tracking.result,
470
- rootIdentifier,
471
- resolvedIdentifier: tracking.resolvedIdentifier,
472
- previousStructType: tracking.previousStructType,
473
- previousMemberName: tracking.previousMemberName,
474
- subscriptDepth: tracking.subscriptDepth,
475
- },
540
+ ctx,
476
541
  input,
477
542
  state,
478
543
  orchestrator,
479
544
  effects,
480
545
  );
481
546
  if (lengthResult !== null) {
482
- tracking.result = lengthResult;
483
- tracking.previousStructType = undefined;
484
- tracking.previousMemberName = undefined;
547
+ applyPropertyResult(tracking, lengthResult);
485
548
  return true;
486
549
  }
487
550
  return false;
488
551
  }
489
552
 
553
+ // ADR-058: Explicit length properties
554
+ const explicitProps = new Set([
555
+ "bit_length",
556
+ "byte_length",
557
+ "element_count",
558
+ "char_count",
559
+ ]);
560
+ if (explicitProps.has(memberName)) {
561
+ return tryExplicitLengthProperty(
562
+ memberName,
563
+ tracking,
564
+ rootIdentifier,
565
+ input,
566
+ state,
567
+ orchestrator,
568
+ effects,
569
+ );
570
+ }
571
+
490
572
  if (memberName === "capacity") {
491
573
  const typeInfo = resolveStringTypeInfo(
492
574
  tracking,
@@ -704,6 +786,12 @@ const generateStringLength = (
704
786
  return subscriptDepth === 0 ? String(dims[0]) : `strlen(${result})`;
705
787
  }
706
788
 
789
+ // String array with subscript - use result which contains arr[index]
790
+ // This handles string<32>[5] parameters where subscriptDepth=1 after arr[index]
791
+ if (subscriptDepth > 0) {
792
+ return `strlen(${result})`;
793
+ }
794
+
707
795
  // Simple string: check length cache first, then use strlen
708
796
  if (resolvedIdentifier && state.lengthCache?.has(resolvedIdentifier)) {
709
797
  return state.lengthCache.get(resolvedIdentifier)!;
@@ -768,6 +856,530 @@ const getTypeBitWidth = (typeName: string, input: IGeneratorInput): string => {
768
856
  }
769
857
  };
770
858
 
859
+ // ========================================================================
860
+ // ADR-058: Explicit Length Properties
861
+ // ========================================================================
862
+
863
+ /**
864
+ * Context for explicit length property generation.
865
+ */
866
+ interface IExplicitLengthContext {
867
+ result: string;
868
+ rootIdentifier: string | undefined;
869
+ resolvedIdentifier: string | undefined;
870
+ previousStructType: string | undefined;
871
+ previousMemberName: string | undefined;
872
+ subscriptDepth: number;
873
+ }
874
+
875
+ /**
876
+ * Get the numeric bit width for a type (internal helper for ADR-058).
877
+ * Returns 0 if type is unknown.
878
+ *
879
+ * Note: This differs from getTypeBitWidth() which returns a string and is
880
+ * used for the legacy .length property. This function returns a number for
881
+ * use in calculations (e.g., array total bits = elements * element width).
882
+ */
883
+ const getNumericBitWidth = (
884
+ typeName: string,
885
+ input: IGeneratorInput,
886
+ ): number => {
887
+ let bitWidth = TYPE_WIDTH[typeName] ?? C_TYPE_WIDTH[typeName] ?? 0;
888
+ if (bitWidth === 0 && input.symbolTable) {
889
+ const enumWidth = input.symbolTable.getEnumBitWidth(typeName);
890
+ if (enumWidth) bitWidth = enumWidth;
891
+ }
892
+ // Check if it's a known enum (default to 32 bits per ADR-017)
893
+ if (bitWidth === 0 && input.symbols?.knownEnums?.has(typeName)) {
894
+ bitWidth = 32;
895
+ }
896
+ // Check bitmap types
897
+ if (bitWidth === 0 && input.symbols?.bitmapBitWidth) {
898
+ const bitmapWidth = input.symbols.bitmapBitWidth.get(typeName);
899
+ if (bitmapWidth) bitWidth = bitmapWidth;
900
+ }
901
+ return bitWidth;
902
+ };
903
+
904
+ /**
905
+ * Generate .bit_length property access (ADR-058).
906
+ * Returns the bit width of any type.
907
+ */
908
+ const generateBitLengthProperty = (
909
+ ctx: IExplicitLengthContext,
910
+ input: IGeneratorInput,
911
+ state: IGeneratorState,
912
+ orchestrator: IOrchestrator,
913
+ ): string | null => {
914
+ // Special case: main function's args.bit_length -> not supported
915
+ if (state.mainArgsName && ctx.rootIdentifier === state.mainArgsName) {
916
+ throw new Error(
917
+ `Error: .bit_length is not supported on 'args' parameter. Use .element_count for argc.`,
918
+ );
919
+ }
920
+
921
+ // Check struct member access
922
+ if (ctx.previousStructType && ctx.previousMemberName) {
923
+ const fieldInfo = orchestrator.getStructFieldInfo(
924
+ ctx.previousStructType,
925
+ ctx.previousMemberName,
926
+ );
927
+ if (fieldInfo) {
928
+ return generateStructFieldBitLength(fieldInfo, ctx.subscriptDepth, input);
929
+ }
930
+ }
931
+
932
+ // Get type info for the resolved identifier
933
+ const typeInfo = ctx.resolvedIdentifier
934
+ ? input.typeRegistry.get(ctx.resolvedIdentifier)
935
+ : undefined;
936
+
937
+ if (!typeInfo) {
938
+ throw new Error(
939
+ `Error: Cannot determine .bit_length for '${ctx.result}' - type not found in registry.`,
940
+ );
941
+ }
942
+
943
+ return generateTypeInfoBitLength(typeInfo, ctx.subscriptDepth, input);
944
+ };
945
+
946
+ /**
947
+ * Calculate product of remaining array dimensions from subscript depth.
948
+ * Returns null if a dynamic dimension (C macro) is encountered.
949
+ */
950
+ const calculateRemainingDimensionsProduct = (
951
+ dimensions: (number | string)[],
952
+ subscriptDepth: number,
953
+ ): { product: number } | { dynamicDim: string } => {
954
+ let product = 1;
955
+ for (let i = subscriptDepth; i < dimensions.length; i++) {
956
+ const dim = dimensions[i];
957
+ if (typeof dim !== "number") {
958
+ return { dynamicDim: dim };
959
+ }
960
+ product *= dim;
961
+ }
962
+ return { product };
963
+ };
964
+
965
+ /**
966
+ * Generate bit length for string type from type name.
967
+ */
968
+ const generateStringBitLengthFromTypeName = (
969
+ typeName: string,
970
+ ): string | null => {
971
+ if (!typeName.startsWith("string<")) {
972
+ return null;
973
+ }
974
+ const capacityMatch = /^string<(\d+)>$/.exec(typeName);
975
+ if (capacityMatch) {
976
+ const capacity = Number(capacityMatch[1]);
977
+ return String((capacity + 1) * 8);
978
+ }
979
+ return null;
980
+ };
981
+
982
+ /**
983
+ * Generate .bit_length for a struct field.
984
+ */
985
+ const generateStructFieldBitLength = (
986
+ fieldInfo: { type: string; dimensions?: (number | string)[] },
987
+ subscriptDepth: number,
988
+ input: IGeneratorInput,
989
+ ): string => {
990
+ const memberType = fieldInfo.type;
991
+ const dimensions = fieldInfo.dimensions;
992
+
993
+ // String field: bit_length = (capacity + 1) * 8
994
+ const stringBitLength = generateStringBitLengthFromTypeName(memberType);
995
+ if (stringBitLength !== null) {
996
+ return stringBitLength;
997
+ }
998
+
999
+ // Array field: total bits = product of dimensions * element bit width
1000
+ if (dimensions && dimensions.length > subscriptDepth) {
1001
+ const elementBitWidth = getNumericBitWidth(memberType, input);
1002
+ if (elementBitWidth > 0) {
1003
+ const dimResult = calculateRemainingDimensionsProduct(
1004
+ dimensions,
1005
+ subscriptDepth,
1006
+ );
1007
+ if ("dynamicDim" in dimResult) {
1008
+ return `/* .bit_length: dynamic dimension ${dimResult.dynamicDim} */0`;
1009
+ }
1010
+ return String(dimResult.product * elementBitWidth);
1011
+ }
1012
+ }
1013
+
1014
+ // Scalar or fully subscripted: return element bit width
1015
+ const bitWidth = getNumericBitWidth(memberType, input);
1016
+ if (bitWidth > 0) {
1017
+ return String(bitWidth);
1018
+ }
1019
+
1020
+ throw new Error(
1021
+ `Error: Cannot determine .bit_length for unsupported type '${memberType}'.`,
1022
+ );
1023
+ };
1024
+
1025
+ /**
1026
+ * Generate bit length for a scalar (non-array) type.
1027
+ */
1028
+ const generateScalarBitLength = (
1029
+ typeInfo: {
1030
+ isEnum?: boolean;
1031
+ baseType: string;
1032
+ bitWidth?: number;
1033
+ },
1034
+ input: IGeneratorInput,
1035
+ ): string => {
1036
+ // Enum type: always 32 bits
1037
+ if (typeInfo.isEnum) {
1038
+ return "32";
1039
+ }
1040
+
1041
+ if (typeInfo.bitWidth) {
1042
+ return String(typeInfo.bitWidth);
1043
+ }
1044
+
1045
+ // Try lookup by base type
1046
+ const bitWidth = getNumericBitWidth(typeInfo.baseType, input);
1047
+ if (bitWidth > 0) {
1048
+ return String(bitWidth);
1049
+ }
1050
+ throw new Error(
1051
+ `Error: Cannot determine .bit_length for unsupported type '${typeInfo.baseType}'.`,
1052
+ );
1053
+ };
1054
+
1055
+ /**
1056
+ * Get element bit width for array type.
1057
+ */
1058
+ const getArrayElementBitWidth = (
1059
+ typeInfo: {
1060
+ isEnum?: boolean;
1061
+ baseType: string;
1062
+ bitWidth?: number;
1063
+ },
1064
+ input: IGeneratorInput,
1065
+ ): number => {
1066
+ let elementBitWidth = typeInfo.bitWidth || 0;
1067
+ if (elementBitWidth === 0) {
1068
+ elementBitWidth = getNumericBitWidth(typeInfo.baseType, input);
1069
+ }
1070
+ if (elementBitWidth === 0 && typeInfo.isEnum) {
1071
+ elementBitWidth = 32;
1072
+ }
1073
+ return elementBitWidth;
1074
+ };
1075
+
1076
+ /**
1077
+ * Generate bit length for an array type.
1078
+ */
1079
+ const generateArrayBitLength = (
1080
+ typeInfo: {
1081
+ isEnum?: boolean;
1082
+ arrayDimensions?: (number | string)[];
1083
+ baseType: string;
1084
+ bitWidth?: number;
1085
+ },
1086
+ subscriptDepth: number,
1087
+ input: IGeneratorInput,
1088
+ ): string => {
1089
+ const dims = typeInfo.arrayDimensions;
1090
+ if (!dims || dims.length === 0) {
1091
+ throw new Error(
1092
+ `Error: Cannot determine .bit_length for array with unknown dimensions.`,
1093
+ );
1094
+ }
1095
+
1096
+ const elementBitWidth = getArrayElementBitWidth(typeInfo, input);
1097
+ if (elementBitWidth === 0) {
1098
+ throw new Error(
1099
+ `Error: Cannot determine .bit_length for array with unsupported element type '${typeInfo.baseType}'.`,
1100
+ );
1101
+ }
1102
+
1103
+ const dimResult = calculateRemainingDimensionsProduct(dims, subscriptDepth);
1104
+ if ("dynamicDim" in dimResult) {
1105
+ return `/* .bit_length: dynamic dimension ${dimResult.dynamicDim} */0`;
1106
+ }
1107
+
1108
+ return String(dimResult.product * elementBitWidth);
1109
+ };
1110
+
1111
+ /**
1112
+ * Generate .bit_length from type info.
1113
+ */
1114
+ const generateTypeInfoBitLength = (
1115
+ typeInfo: {
1116
+ isString?: boolean;
1117
+ isArray?: boolean;
1118
+ isEnum?: boolean;
1119
+ arrayDimensions?: (number | string)[];
1120
+ baseType: string;
1121
+ bitWidth?: number;
1122
+ isBitmap?: boolean;
1123
+ bitmapTypeName?: string;
1124
+ stringCapacity?: number;
1125
+ },
1126
+ subscriptDepth: number,
1127
+ input: IGeneratorInput,
1128
+ ): string => {
1129
+ // String type: bit_length = (capacity + 1) * 8 (buffer size in bits)
1130
+ if (typeInfo.isString) {
1131
+ if (typeInfo.stringCapacity !== undefined) {
1132
+ return String((typeInfo.stringCapacity + 1) * 8);
1133
+ }
1134
+ throw new Error(
1135
+ `Error: Cannot determine .bit_length for string with unknown capacity.`,
1136
+ );
1137
+ }
1138
+
1139
+ // Non-array scalar: return bit width
1140
+ if (!typeInfo.isArray) {
1141
+ return generateScalarBitLength(typeInfo, input);
1142
+ }
1143
+
1144
+ // Array: calculate total bits
1145
+ return generateArrayBitLength(typeInfo, subscriptDepth, input);
1146
+ };
1147
+
1148
+ /**
1149
+ * Generate .byte_length property access (ADR-058).
1150
+ * Returns the byte size of any type (bit_length / 8).
1151
+ */
1152
+ const generateByteLengthProperty = (
1153
+ ctx: IExplicitLengthContext,
1154
+ input: IGeneratorInput,
1155
+ state: IGeneratorState,
1156
+ orchestrator: IOrchestrator,
1157
+ ): string | null => {
1158
+ // Special case: main function's args
1159
+ if (state.mainArgsName && ctx.rootIdentifier === state.mainArgsName) {
1160
+ throw new Error(
1161
+ `Error: .byte_length is not supported on 'args' parameter. Use .element_count for argc.`,
1162
+ );
1163
+ }
1164
+
1165
+ // Check struct member access
1166
+ if (ctx.previousStructType && ctx.previousMemberName) {
1167
+ const fieldInfo = orchestrator.getStructFieldInfo(
1168
+ ctx.previousStructType,
1169
+ ctx.previousMemberName,
1170
+ );
1171
+ if (fieldInfo) {
1172
+ const bitLength = generateStructFieldBitLength(
1173
+ fieldInfo,
1174
+ ctx.subscriptDepth,
1175
+ input,
1176
+ );
1177
+ // Parse and convert to bytes
1178
+ const bitValue = Number.parseInt(bitLength, 10);
1179
+ if (!Number.isNaN(bitValue)) {
1180
+ return String(bitValue / 8);
1181
+ }
1182
+ return bitLength.replace(".bit_length", ".byte_length");
1183
+ }
1184
+ }
1185
+
1186
+ // Get type info for the resolved identifier
1187
+ const typeInfo = ctx.resolvedIdentifier
1188
+ ? input.typeRegistry.get(ctx.resolvedIdentifier)
1189
+ : undefined;
1190
+
1191
+ if (!typeInfo) {
1192
+ throw new Error(
1193
+ `Error: Cannot determine .byte_length for '${ctx.result}' - type not found in registry.`,
1194
+ );
1195
+ }
1196
+
1197
+ const bitLength = generateTypeInfoBitLength(
1198
+ typeInfo,
1199
+ ctx.subscriptDepth,
1200
+ input,
1201
+ );
1202
+ const bitValue = Number.parseInt(bitLength, 10);
1203
+ if (!Number.isNaN(bitValue)) {
1204
+ return String(bitValue / 8);
1205
+ }
1206
+ return bitLength.replace(".bit_length", ".byte_length");
1207
+ };
1208
+
1209
+ /**
1210
+ * Get dimension value at subscript depth as string.
1211
+ */
1212
+ const getDimensionAtDepth = (
1213
+ dimensions: (number | string)[],
1214
+ subscriptDepth: number,
1215
+ ): string => {
1216
+ const dim = dimensions[subscriptDepth];
1217
+ return typeof dim === "number" ? String(dim) : dim;
1218
+ };
1219
+
1220
+ /**
1221
+ * Generate element_count for struct field.
1222
+ */
1223
+ const generateStructFieldElementCount = (
1224
+ ctx: IExplicitLengthContext,
1225
+ orchestrator: IOrchestrator,
1226
+ ): string | null => {
1227
+ if (!ctx.previousStructType || !ctx.previousMemberName) {
1228
+ return null;
1229
+ }
1230
+
1231
+ const fieldInfo = orchestrator.getStructFieldInfo(
1232
+ ctx.previousStructType,
1233
+ ctx.previousMemberName,
1234
+ );
1235
+
1236
+ if (
1237
+ fieldInfo?.dimensions &&
1238
+ fieldInfo.dimensions.length > ctx.subscriptDepth
1239
+ ) {
1240
+ return getDimensionAtDepth(fieldInfo.dimensions, ctx.subscriptDepth);
1241
+ }
1242
+
1243
+ // Non-array field - element_count not applicable
1244
+ throw new Error(
1245
+ `Error: .element_count is only available on arrays, not on '${fieldInfo?.type || ctx.previousMemberName}'.`,
1246
+ );
1247
+ };
1248
+
1249
+ /**
1250
+ * Generate element_count from type info.
1251
+ */
1252
+ const generateTypeInfoElementCount = (
1253
+ ctx: IExplicitLengthContext,
1254
+ input: IGeneratorInput,
1255
+ ): string => {
1256
+ const typeInfo = ctx.resolvedIdentifier
1257
+ ? input.typeRegistry.get(ctx.resolvedIdentifier)
1258
+ : undefined;
1259
+
1260
+ if (!typeInfo) {
1261
+ throw new Error(
1262
+ `Error: Cannot determine .element_count for '${ctx.result}' - type not found in registry.`,
1263
+ );
1264
+ }
1265
+
1266
+ if (!typeInfo.isArray) {
1267
+ throw new Error(
1268
+ `Error: .element_count is only available on arrays, not on '${typeInfo.baseType}'.`,
1269
+ );
1270
+ }
1271
+
1272
+ const dims = typeInfo.arrayDimensions;
1273
+ if (!dims || dims.length === 0) {
1274
+ throw new Error(
1275
+ `Error: Cannot determine .element_count for array with unknown dimensions.`,
1276
+ );
1277
+ }
1278
+
1279
+ if (ctx.subscriptDepth < dims.length) {
1280
+ return getDimensionAtDepth(dims, ctx.subscriptDepth);
1281
+ }
1282
+
1283
+ throw new Error(
1284
+ `Error: .element_count is not available on array elements. Array is fully subscripted.`,
1285
+ );
1286
+ };
1287
+
1288
+ /**
1289
+ * Generate .element_count property access (ADR-058).
1290
+ * Returns element count for arrays or argc for args.
1291
+ */
1292
+ const generateElementCountProperty = (
1293
+ ctx: IExplicitLengthContext,
1294
+ input: IGeneratorInput,
1295
+ state: IGeneratorState,
1296
+ orchestrator: IOrchestrator,
1297
+ ): string | null => {
1298
+ // Special case: main function's args.element_count -> argc
1299
+ if (state.mainArgsName && ctx.rootIdentifier === state.mainArgsName) {
1300
+ return "argc";
1301
+ }
1302
+
1303
+ // Check struct member access for array fields
1304
+ const structResult = generateStructFieldElementCount(ctx, orchestrator);
1305
+ if (structResult !== null) {
1306
+ return structResult;
1307
+ }
1308
+ if (ctx.previousStructType) {
1309
+ // generateStructFieldElementCount threw or returned null but struct context existed
1310
+ return null;
1311
+ }
1312
+
1313
+ // Get type info for variable
1314
+ return generateTypeInfoElementCount(ctx, input);
1315
+ };
1316
+
1317
+ /**
1318
+ * Generate .char_count property access (ADR-058).
1319
+ * Returns strlen() for strings.
1320
+ */
1321
+ const generateCharCountProperty = (
1322
+ ctx: IExplicitLengthContext,
1323
+ input: IGeneratorInput,
1324
+ state: IGeneratorState,
1325
+ orchestrator: IOrchestrator,
1326
+ effects: TGeneratorEffect[],
1327
+ ): string | null => {
1328
+ // Special case: main function's args
1329
+ if (state.mainArgsName && ctx.rootIdentifier === state.mainArgsName) {
1330
+ throw new Error(
1331
+ `Error: .char_count is only available on strings, not on 'args'. Use .element_count for argc.`,
1332
+ );
1333
+ }
1334
+
1335
+ // Check struct member access for string fields
1336
+ if (ctx.previousStructType && ctx.previousMemberName) {
1337
+ const fieldInfo = orchestrator.getStructFieldInfo(
1338
+ ctx.previousStructType,
1339
+ ctx.previousMemberName,
1340
+ );
1341
+ if (fieldInfo?.type.startsWith("string<")) {
1342
+ effects.push({ type: "include", header: "string" });
1343
+ return `strlen(${ctx.result})`;
1344
+ }
1345
+ // Non-string field
1346
+ throw new Error(
1347
+ `Error: .char_count is only available on strings, not on '${fieldInfo?.type || ctx.previousMemberName}'.`,
1348
+ );
1349
+ }
1350
+
1351
+ // Get type info
1352
+ const typeInfo = ctx.resolvedIdentifier
1353
+ ? input.typeRegistry.get(ctx.resolvedIdentifier)
1354
+ : undefined;
1355
+
1356
+ if (!typeInfo) {
1357
+ throw new Error(
1358
+ `Error: Cannot determine .char_count for '${ctx.result}' - type not found in registry.`,
1359
+ );
1360
+ }
1361
+
1362
+ // Must be a string type
1363
+ if (!typeInfo.isString) {
1364
+ throw new Error(
1365
+ `Error: .char_count is only available on strings, not on '${typeInfo.baseType}'.`,
1366
+ );
1367
+ }
1368
+
1369
+ effects.push({ type: "include", header: "string" });
1370
+
1371
+ // Check length cache first
1372
+ if (
1373
+ ctx.resolvedIdentifier &&
1374
+ state.lengthCache?.has(ctx.resolvedIdentifier)
1375
+ ) {
1376
+ return state.lengthCache.get(ctx.resolvedIdentifier)!;
1377
+ }
1378
+
1379
+ const target = ctx.resolvedIdentifier ?? ctx.result;
1380
+ return `strlen(${target})`;
1381
+ };
1382
+
771
1383
  // ========================================================================
772
1384
  // Member Access
773
1385
  // ========================================================================