c-next 0.1.28 → 0.1.29

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "c-next",
3
- "version": "0.1.28",
3
+ "version": "0.1.29",
4
4
  "description": "A safer C for embedded systems development. Transpiles to clean, readable C.",
5
5
  "main": "src/index.ts",
6
6
  "bin": {
@@ -51,6 +51,8 @@ import includeGenerators from "./generators/support/IncludeGenerator";
51
51
  import commentUtils from "./generators/support/CommentUtils";
52
52
  // ADR-046: NullCheckAnalyzer for nullable C pointer type detection
53
53
  import NullCheckAnalyzer from "../analysis/NullCheckAnalyzer";
54
+ // ADR-006: Helper for building member access chains with proper separators
55
+ import memberAccessChain from "./memberAccessChain";
54
56
 
55
57
  const {
56
58
  generateOverflowHelpers: helperGenerateOverflowHelpers,
@@ -6287,6 +6289,10 @@ export default class CodeGenerator implements IOrchestrator {
6287
6289
  // Check if first identifier is a scope for special handling
6288
6290
  const isCrossScope = this.isKnownScope(firstId);
6289
6291
 
6292
+ // ADR-006: Check if first identifier is a struct parameter (needs -> access)
6293
+ const paramInfo = this.context.currentParameters.get(firstId);
6294
+ const isStructParam = paramInfo?.isStruct ?? false;
6295
+
6290
6296
  // Bug #8: Track struct types to detect bit access through chains
6291
6297
  // e.g., items[0].byte[7] where byte is u8 - final [7] is bit access
6292
6298
  let currentStructType: string | undefined;
@@ -6313,8 +6319,11 @@ export default class CodeGenerator implements IOrchestrator {
6313
6319
  idIndex < identifiers.length
6314
6320
  ) {
6315
6321
  const memberName = identifiers[idIndex].getText();
6316
- // Use underscore for first join if cross-scope, dot otherwise
6317
- const separator = isCrossScope && idIndex === 1 ? "_" : ".";
6322
+ // ADR-006: Use determineSeparator helper for -> (struct param) / _ (scope) / .
6323
+ const separator = memberAccessChain.determineSeparator(
6324
+ { isStructParam, isCrossScope },
6325
+ idIndex,
6326
+ );
6318
6327
  result += `${separator}${memberName}`;
6319
6328
  idIndex++;
6320
6329
 
@@ -9320,6 +9329,10 @@ export default class CodeGenerator implements IOrchestrator {
9320
9329
  // Check if first identifier is a scope for special handling
9321
9330
  const isCrossScope = this.isKnownScope(firstPart);
9322
9331
 
9332
+ // ADR-006: Check if first identifier is a struct parameter (needs -> access)
9333
+ const paramInfo = this.context.currentParameters.get(firstPart);
9334
+ const isStructParam = paramInfo?.isStruct ?? false;
9335
+
9323
9336
  // ADR-016: Inside a scope, accessing another scope requires global. prefix
9324
9337
  if (isCrossScope && this.context.currentScope) {
9325
9338
  // Self-referential access should use 'this.'
@@ -9356,8 +9369,11 @@ export default class CodeGenerator implements IOrchestrator {
9356
9369
  i++;
9357
9370
  if (i < ctx.children.length && idIndex < parts.length) {
9358
9371
  const memberName = parts[idIndex];
9359
- // Use underscore for first join if cross-scope, dot otherwise
9360
- const separator = isCrossScope && idIndex === 1 ? "_" : ".";
9372
+ // ADR-006: Use determineSeparator helper for -> (struct param) / _ (scope) / .
9373
+ const separator = memberAccessChain.determineSeparator(
9374
+ { isStructParam, isCrossScope },
9375
+ idIndex,
9376
+ );
9361
9377
  result += `${separator}${memberName}`;
9362
9378
  idIndex++;
9363
9379
 
@@ -0,0 +1,361 @@
1
+ /**
2
+ * Tests for memberAccessChain helper module.
3
+ *
4
+ * This module tests the shared logic for building member access chains
5
+ * with proper separators (-> for struct params, _ for cross-scope, . otherwise).
6
+ */
7
+
8
+ import { vi } from "vitest";
9
+ import { ParseTree } from "antlr4ng";
10
+ import memberAccessChain from "./memberAccessChain";
11
+
12
+ const { determineSeparator, buildMemberAccessChain } = memberAccessChain;
13
+
14
+ // Local type definition for separator options (mirrors internal type)
15
+ interface SeparatorOptions {
16
+ isStructParam: boolean;
17
+ isCrossScope: boolean;
18
+ }
19
+
20
+ describe("determineSeparator", () => {
21
+ describe("struct parameter access", () => {
22
+ it("should return -> for struct param at idIndex 1", () => {
23
+ const options: SeparatorOptions = {
24
+ isStructParam: true,
25
+ isCrossScope: false,
26
+ };
27
+ expect(determineSeparator(options, 1)).toBe("->");
28
+ });
29
+
30
+ it("should return . for struct param at idIndex > 1", () => {
31
+ const options: SeparatorOptions = {
32
+ isStructParam: true,
33
+ isCrossScope: false,
34
+ };
35
+ expect(determineSeparator(options, 2)).toBe(".");
36
+ expect(determineSeparator(options, 3)).toBe(".");
37
+ });
38
+ });
39
+
40
+ describe("cross-scope access", () => {
41
+ it("should return _ for cross-scope at idIndex 1", () => {
42
+ const options: SeparatorOptions = {
43
+ isStructParam: false,
44
+ isCrossScope: true,
45
+ };
46
+ expect(determineSeparator(options, 1)).toBe("_");
47
+ });
48
+
49
+ it("should return . for cross-scope at idIndex > 1", () => {
50
+ const options: SeparatorOptions = {
51
+ isStructParam: false,
52
+ isCrossScope: true,
53
+ };
54
+ expect(determineSeparator(options, 2)).toBe(".");
55
+ expect(determineSeparator(options, 3)).toBe(".");
56
+ });
57
+ });
58
+
59
+ describe("struct param takes precedence over cross-scope", () => {
60
+ it("should return -> when both struct param and cross-scope at idIndex 1", () => {
61
+ const options: SeparatorOptions = {
62
+ isStructParam: true,
63
+ isCrossScope: true,
64
+ };
65
+ expect(determineSeparator(options, 1)).toBe("->");
66
+ });
67
+ });
68
+
69
+ describe("regular access", () => {
70
+ it("should return . when neither struct param nor cross-scope", () => {
71
+ const options: SeparatorOptions = {
72
+ isStructParam: false,
73
+ isCrossScope: false,
74
+ };
75
+ expect(determineSeparator(options, 1)).toBe(".");
76
+ expect(determineSeparator(options, 2)).toBe(".");
77
+ });
78
+ });
79
+ });
80
+
81
+ // Mock ParseTree for testing - only getText() is used by buildMemberAccessChain
82
+ function createMockChildren(tokens: string[]): ParseTree[] {
83
+ return tokens.map(
84
+ (text) => ({ getText: () => text }) as unknown as ParseTree,
85
+ );
86
+ }
87
+
88
+ describe("buildMemberAccessChain", () => {
89
+ // Simple expression generator that just returns the expression as-is
90
+ const simpleExprGen = (expr: string) => expr;
91
+
92
+ describe("simple member access (no subscripts)", () => {
93
+ it("should build chain with regular separator", () => {
94
+ // cfg.tempInputs.assignedSpn
95
+ const options = {
96
+ firstId: "cfg",
97
+ identifiers: ["cfg", "tempInputs", "assignedSpn"],
98
+ expressions: [],
99
+ children: createMockChildren([
100
+ "cfg",
101
+ ".",
102
+ "tempInputs",
103
+ ".",
104
+ "assignedSpn",
105
+ ]),
106
+ separatorOptions: { isStructParam: false, isCrossScope: false },
107
+ generateExpression: simpleExprGen,
108
+ };
109
+
110
+ const result = buildMemberAccessChain(options);
111
+ expect(result.code).toBe("cfg.tempInputs.assignedSpn");
112
+ expect(result.identifiersConsumed).toBe(3);
113
+ expect(result.expressionsConsumed).toBe(0);
114
+ });
115
+
116
+ it("should build chain with -> for struct parameter", () => {
117
+ // conf->tempInputs.assignedSpn (conf is a struct param)
118
+ const options = {
119
+ firstId: "conf",
120
+ identifiers: ["conf", "tempInputs", "assignedSpn"],
121
+ expressions: [],
122
+ children: createMockChildren([
123
+ "conf",
124
+ ".",
125
+ "tempInputs",
126
+ ".",
127
+ "assignedSpn",
128
+ ]),
129
+ separatorOptions: { isStructParam: true, isCrossScope: false },
130
+ generateExpression: simpleExprGen,
131
+ };
132
+
133
+ const result = buildMemberAccessChain(options);
134
+ expect(result.code).toBe("conf->tempInputs.assignedSpn");
135
+ expect(result.identifiersConsumed).toBe(3);
136
+ });
137
+
138
+ it("should build chain with _ for cross-scope", () => {
139
+ // Timing_tickCount (cross-scope access)
140
+ const options = {
141
+ firstId: "Timing",
142
+ identifiers: ["Timing", "tickCount"],
143
+ expressions: [],
144
+ children: createMockChildren(["Timing", ".", "tickCount"]),
145
+ separatorOptions: { isStructParam: false, isCrossScope: true },
146
+ generateExpression: simpleExprGen,
147
+ };
148
+
149
+ const result = buildMemberAccessChain(options);
150
+ expect(result.code).toBe("Timing_tickCount");
151
+ expect(result.identifiersConsumed).toBe(2);
152
+ });
153
+ });
154
+
155
+ describe("member access with subscripts", () => {
156
+ it("should build chain with subscript at end", () => {
157
+ // cfg.tempInputs[idx]
158
+ const options = {
159
+ firstId: "cfg",
160
+ identifiers: ["cfg", "tempInputs"],
161
+ expressions: ["idx"],
162
+ children: createMockChildren([
163
+ "cfg",
164
+ ".",
165
+ "tempInputs",
166
+ "[",
167
+ "idx",
168
+ "]",
169
+ ]),
170
+ separatorOptions: { isStructParam: false, isCrossScope: false },
171
+ generateExpression: simpleExprGen,
172
+ };
173
+
174
+ const result = buildMemberAccessChain(options);
175
+ expect(result.code).toBe("cfg.tempInputs[idx]");
176
+ expect(result.identifiersConsumed).toBe(2);
177
+ expect(result.expressionsConsumed).toBe(1);
178
+ });
179
+
180
+ it("should build chain with subscript then member (the issue #69 pattern)", () => {
181
+ // conf->tempInputs[idx].assignedSpn (struct param with subscript then member)
182
+ const options = {
183
+ firstId: "conf",
184
+ identifiers: ["conf", "tempInputs", "assignedSpn"],
185
+ expressions: ["idx"],
186
+ children: createMockChildren([
187
+ "conf",
188
+ ".",
189
+ "tempInputs",
190
+ "[",
191
+ "idx",
192
+ "]",
193
+ ".",
194
+ "assignedSpn",
195
+ ]),
196
+ separatorOptions: { isStructParam: true, isCrossScope: false },
197
+ generateExpression: simpleExprGen,
198
+ };
199
+
200
+ const result = buildMemberAccessChain(options);
201
+ expect(result.code).toBe("conf->tempInputs[idx].assignedSpn");
202
+ expect(result.identifiersConsumed).toBe(3);
203
+ expect(result.expressionsConsumed).toBe(1);
204
+ });
205
+
206
+ it("should handle multiple subscripts", () => {
207
+ // matrix[i][j]
208
+ const options = {
209
+ firstId: "matrix",
210
+ identifiers: ["matrix"],
211
+ expressions: ["i", "j"],
212
+ children: createMockChildren(["matrix", "[", "i", "]", "[", "j", "]"]),
213
+ separatorOptions: { isStructParam: false, isCrossScope: false },
214
+ generateExpression: simpleExprGen,
215
+ };
216
+
217
+ const result = buildMemberAccessChain(options);
218
+ expect(result.code).toBe("matrix[i][j]");
219
+ expect(result.expressionsConsumed).toBe(2);
220
+ });
221
+
222
+ it("should handle mixed subscripts and members", () => {
223
+ // data->items[0].value[1].flag
224
+ const options = {
225
+ firstId: "data",
226
+ identifiers: ["data", "items", "value", "flag"],
227
+ expressions: ["0", "1"],
228
+ children: createMockChildren([
229
+ "data",
230
+ ".",
231
+ "items",
232
+ "[",
233
+ "0",
234
+ "]",
235
+ ".",
236
+ "value",
237
+ "[",
238
+ "1",
239
+ "]",
240
+ ".",
241
+ "flag",
242
+ ]),
243
+ separatorOptions: { isStructParam: true, isCrossScope: false },
244
+ generateExpression: simpleExprGen,
245
+ };
246
+
247
+ const result = buildMemberAccessChain(options);
248
+ expect(result.code).toBe("data->items[0].value[1].flag");
249
+ expect(result.identifiersConsumed).toBe(4);
250
+ expect(result.expressionsConsumed).toBe(2);
251
+ });
252
+ });
253
+
254
+ describe("type tracking and bit access", () => {
255
+ it("should call onBitAccess when accessing primitive integer field with subscript", () => {
256
+ // items[0].byte[7] -> bit access on u8 field
257
+ const mockTypeTracking = {
258
+ getStructFields: vi.fn().mockReturnValue(new Map([["byte", "u8"]])),
259
+ getStructArrayFields: vi.fn().mockReturnValue(new Set()),
260
+ isKnownStruct: vi.fn().mockImplementation((type) => type === "Item"),
261
+ };
262
+
263
+ const onBitAccess = vi.fn().mockReturnValue("((items[0].byte >> 7) & 1)");
264
+
265
+ const options = {
266
+ firstId: "items",
267
+ identifiers: ["items", "byte"],
268
+ expressions: ["0", "7"],
269
+ children: createMockChildren([
270
+ "items",
271
+ "[",
272
+ "0",
273
+ "]",
274
+ ".",
275
+ "byte",
276
+ "[",
277
+ "7",
278
+ "]",
279
+ ]),
280
+ separatorOptions: { isStructParam: false, isCrossScope: false },
281
+ generateExpression: simpleExprGen,
282
+ initialTypeInfo: { isArray: true, baseType: "Item" },
283
+ typeTracking: mockTypeTracking,
284
+ onBitAccess,
285
+ };
286
+
287
+ const result = buildMemberAccessChain(options);
288
+ expect(onBitAccess).toHaveBeenCalledWith("items[0].byte", "7", "u8");
289
+ expect(result.code).toBe("((items[0].byte >> 7) & 1)");
290
+ });
291
+
292
+ it("should not trigger bit access for array fields", () => {
293
+ // items[0].indices[12] -> normal array access, not bit access
294
+ const mockTypeTracking = {
295
+ getStructFields: vi.fn().mockReturnValue(new Map([["indices", "u8"]])),
296
+ getStructArrayFields: vi.fn().mockReturnValue(new Set(["indices"])), // indices IS an array
297
+ isKnownStruct: vi.fn().mockImplementation((type) => type === "Item"),
298
+ };
299
+
300
+ const onBitAccess = vi.fn();
301
+
302
+ const options = {
303
+ firstId: "items",
304
+ identifiers: ["items", "indices"],
305
+ expressions: ["0", "12"],
306
+ children: createMockChildren([
307
+ "items",
308
+ "[",
309
+ "0",
310
+ "]",
311
+ ".",
312
+ "indices",
313
+ "[",
314
+ "12",
315
+ "]",
316
+ ]),
317
+ separatorOptions: { isStructParam: false, isCrossScope: false },
318
+ generateExpression: simpleExprGen,
319
+ initialTypeInfo: { isArray: true, baseType: "Item" },
320
+ typeTracking: mockTypeTracking,
321
+ onBitAccess,
322
+ };
323
+
324
+ const result = buildMemberAccessChain(options);
325
+ expect(onBitAccess).not.toHaveBeenCalled();
326
+ expect(result.code).toBe("items[0].indices[12]");
327
+ });
328
+ });
329
+
330
+ describe("edge cases", () => {
331
+ it("should handle single identifier with subscript", () => {
332
+ // arr[0]
333
+ const options = {
334
+ firstId: "arr",
335
+ identifiers: ["arr"],
336
+ expressions: ["0"],
337
+ children: createMockChildren(["arr", "[", "0", "]"]),
338
+ separatorOptions: { isStructParam: false, isCrossScope: false },
339
+ generateExpression: simpleExprGen,
340
+ };
341
+
342
+ const result = buildMemberAccessChain(options);
343
+ expect(result.code).toBe("arr[0]");
344
+ });
345
+
346
+ it("should handle complex expression in subscript", () => {
347
+ // cfg.items[i + 1]
348
+ const options = {
349
+ firstId: "cfg",
350
+ identifiers: ["cfg", "items"],
351
+ expressions: ["expr_placeholder"],
352
+ children: createMockChildren(["cfg", ".", "items", "[", "i + 1", "]"]),
353
+ separatorOptions: { isStructParam: false, isCrossScope: false },
354
+ generateExpression: () => "i + 1", // Mock that generates "i + 1"
355
+ };
356
+
357
+ const result = buildMemberAccessChain(options);
358
+ expect(result.code).toBe("cfg.items[i + 1]");
359
+ });
360
+ });
361
+ });
@@ -0,0 +1,280 @@
1
+ /**
2
+ * Helper module for building member access chains with proper separators.
3
+ *
4
+ * This module extracts the shared logic for building member access chains
5
+ * like `conf->tempInputs[idx].assignedSpn` from both read and write contexts.
6
+ *
7
+ * ADR-006: Struct parameters need -> access (passed by pointer in C)
8
+ * ADR-016: Cross-scope access uses _ separator
9
+ */
10
+
11
+ import { ParseTree } from "antlr4ng";
12
+
13
+ /**
14
+ * Options for determining the separator between the first identifier and
15
+ * the first member in a member access chain.
16
+ */
17
+ interface SeparatorOptions {
18
+ /** Whether the first identifier is a struct parameter (needs -> in C) */
19
+ isStructParam: boolean;
20
+ /** Whether the first identifier is a cross-scope access (needs _ in C) */
21
+ isCrossScope: boolean;
22
+ }
23
+
24
+ /**
25
+ * Determines the separator to use between the first identifier and the first member.
26
+ *
27
+ * @param options - The separator options
28
+ * @param idIndex - The current identifier index (1 = first member after base)
29
+ * @returns The separator string: "->" for struct params, "_" for cross-scope, "." otherwise
30
+ */
31
+ function determineSeparator(
32
+ options: SeparatorOptions,
33
+ idIndex: number,
34
+ ): string {
35
+ // Only the first separator (idIndex === 1) can be special
36
+ if (idIndex !== 1) {
37
+ return ".";
38
+ }
39
+
40
+ // ADR-006: Struct parameters are passed as pointers, need -> access
41
+ if (options.isStructParam) {
42
+ return "->";
43
+ }
44
+
45
+ // ADR-016: Cross-scope access uses underscore (scope_member)
46
+ if (options.isCrossScope) {
47
+ return "_";
48
+ }
49
+
50
+ return ".";
51
+ }
52
+
53
+ /**
54
+ * Result of building a member access chain.
55
+ */
56
+ interface MemberAccessChainResult {
57
+ /** The generated C code for the member access chain */
58
+ code: string;
59
+ /** Number of identifiers consumed */
60
+ identifiersConsumed: number;
61
+ /** Number of expressions (subscripts) consumed */
62
+ expressionsConsumed: number;
63
+ }
64
+
65
+ /**
66
+ * Callback type for generating expression code from subscript expressions.
67
+ */
68
+ type ExpressionGenerator<TExpr> = (expr: TExpr) => string;
69
+
70
+ /**
71
+ * Callback type for checking if a type is a known struct.
72
+ */
73
+ type StructChecker = (typeName: string) => boolean;
74
+
75
+ /**
76
+ * Type tracking state while walking a member access chain.
77
+ */
78
+ interface TypeTrackingState {
79
+ /** Current struct type being accessed (undefined if not in a struct) */
80
+ currentStructType: string | undefined;
81
+ /** Type of the last accessed member */
82
+ lastMemberType: string | undefined;
83
+ /** Whether the last accessed member is an array field */
84
+ lastMemberIsArray: boolean;
85
+ }
86
+
87
+ /**
88
+ * Callbacks for type tracking while building the chain.
89
+ */
90
+ interface TypeTrackingCallbacks {
91
+ /** Get the fields map for a struct type */
92
+ getStructFields: (structType: string) => Map<string, string> | undefined;
93
+ /** Get the array fields set for a struct type */
94
+ getStructArrayFields: (structType: string) => Set<string> | undefined;
95
+ /** Check if a type name is a known struct */
96
+ isKnownStruct: StructChecker;
97
+ }
98
+
99
+ /**
100
+ * Options for building a member access chain.
101
+ */
102
+ interface BuildChainOptions<TExpr> {
103
+ /** The first identifier (base of the chain) */
104
+ firstId: string;
105
+ /** All identifier names in the chain */
106
+ identifiers: string[];
107
+ /** Subscript expressions */
108
+ expressions: TExpr[];
109
+ /** Parse tree children for walking */
110
+ children: ParseTree[];
111
+ /** Separator options (struct param, cross-scope) */
112
+ separatorOptions: SeparatorOptions;
113
+ /** Function to generate code from an expression */
114
+ generateExpression: ExpressionGenerator<TExpr>;
115
+ /** Optional: Initial type info for type tracking */
116
+ initialTypeInfo?: {
117
+ isArray: boolean;
118
+ baseType: string;
119
+ };
120
+ /** Optional: Type tracking callbacks for bit access detection */
121
+ typeTracking?: TypeTrackingCallbacks;
122
+ /** Optional: Callback when bit access is detected on last subscript */
123
+ onBitAccess?: (
124
+ result: string,
125
+ bitIndex: string,
126
+ memberType: string,
127
+ ) => string | null;
128
+ }
129
+
130
+ /**
131
+ * Builds a member access chain with proper separators and subscripts.
132
+ *
133
+ * This function walks through the parse tree children in order, building
134
+ * the C code string incrementally. It handles:
135
+ * - Struct parameter access (-> separator)
136
+ * - Cross-scope access (_ separator)
137
+ * - Array subscripts
138
+ * - Optional type tracking for bit access detection
139
+ *
140
+ * @param options - The build options
141
+ * @returns The result containing the generated code and consumption counts
142
+ */
143
+ function buildMemberAccessChain<TExpr>(
144
+ options: BuildChainOptions<TExpr>,
145
+ ): MemberAccessChainResult {
146
+ const {
147
+ firstId,
148
+ identifiers,
149
+ expressions,
150
+ children,
151
+ separatorOptions,
152
+ generateExpression,
153
+ initialTypeInfo,
154
+ typeTracking,
155
+ onBitAccess,
156
+ } = options;
157
+
158
+ let result = firstId;
159
+ let idIndex = 1; // Start at 1 since we already have firstId
160
+ let exprIndex = 0;
161
+
162
+ // Initialize type tracking state
163
+ let typeState: TypeTrackingState | undefined;
164
+ if (typeTracking && initialTypeInfo) {
165
+ typeState = {
166
+ currentStructType: typeTracking.isKnownStruct(initialTypeInfo.baseType)
167
+ ? initialTypeInfo.baseType
168
+ : undefined,
169
+ lastMemberType: undefined,
170
+ lastMemberIsArray: false,
171
+ };
172
+ }
173
+
174
+ let i = 1;
175
+ while (i < children.length) {
176
+ const childText = children[i].getText();
177
+
178
+ if (childText === ".") {
179
+ // Dot found - consume it, then get the next identifier
180
+ i++;
181
+ if (i < children.length && idIndex < identifiers.length) {
182
+ const memberName = identifiers[idIndex];
183
+ const separator = determineSeparator(separatorOptions, idIndex);
184
+ result += `${separator}${memberName}`;
185
+ idIndex++;
186
+
187
+ // Update type tracking for the member we just accessed
188
+ if (typeState && typeTracking && typeState.currentStructType) {
189
+ const fields = typeTracking.getStructFields(
190
+ typeState.currentStructType,
191
+ );
192
+ typeState.lastMemberType = fields?.get(memberName);
193
+
194
+ const arrayFields = typeTracking.getStructArrayFields(
195
+ typeState.currentStructType,
196
+ );
197
+ typeState.lastMemberIsArray = arrayFields?.has(memberName) ?? false;
198
+
199
+ // Check if this member is itself a struct
200
+ if (
201
+ typeState.lastMemberType &&
202
+ typeTracking.isKnownStruct(typeState.lastMemberType)
203
+ ) {
204
+ typeState.currentStructType = typeState.lastMemberType;
205
+ } else {
206
+ typeState.currentStructType = undefined;
207
+ }
208
+ }
209
+ }
210
+ } else if (childText === "[") {
211
+ // Opening bracket - handle subscript
212
+
213
+ // Check for bit access on primitive integer (if type tracking enabled)
214
+ if (typeState && onBitAccess) {
215
+ const isPrimitiveInt =
216
+ typeState.lastMemberType &&
217
+ !typeState.lastMemberIsArray &&
218
+ ["u8", "u16", "u32", "u64", "i8", "i16", "i32", "i64"].includes(
219
+ typeState.lastMemberType,
220
+ );
221
+ const isLastExpr = exprIndex === expressions.length - 1;
222
+
223
+ if (isPrimitiveInt && isLastExpr && exprIndex < expressions.length) {
224
+ const bitIndex = generateExpression(expressions[exprIndex]);
225
+ const bitResult = onBitAccess(
226
+ result,
227
+ bitIndex,
228
+ typeState.lastMemberType!,
229
+ );
230
+ if (bitResult !== null) {
231
+ return {
232
+ code: bitResult,
233
+ identifiersConsumed: idIndex,
234
+ expressionsConsumed: exprIndex + 1,
235
+ };
236
+ }
237
+ }
238
+ }
239
+
240
+ // Normal array subscript
241
+ if (exprIndex < expressions.length) {
242
+ const expr = generateExpression(expressions[exprIndex]);
243
+ result += `[${expr}]`;
244
+ exprIndex++;
245
+
246
+ // After subscripting an array, update type tracking
247
+ if (
248
+ typeState &&
249
+ typeTracking &&
250
+ initialTypeInfo?.isArray &&
251
+ exprIndex === 1
252
+ ) {
253
+ // First subscript on array - element type might be a struct
254
+ if (typeTracking.isKnownStruct(initialTypeInfo.baseType)) {
255
+ typeState.currentStructType = initialTypeInfo.baseType;
256
+ }
257
+ }
258
+ }
259
+
260
+ // Skip forward to find and pass the closing bracket
261
+ while (i < children.length && children[i].getText() !== "]") {
262
+ i++;
263
+ }
264
+
265
+ // Reset lastMemberType after subscript (no longer on a member)
266
+ if (typeState) {
267
+ typeState.lastMemberType = undefined;
268
+ }
269
+ }
270
+ i++;
271
+ }
272
+
273
+ return {
274
+ code: result,
275
+ identifiersConsumed: idIndex,
276
+ expressionsConsumed: exprIndex,
277
+ };
278
+ }
279
+
280
+ export default { determineSeparator, buildMemberAccessChain };