c-next 0.1.28 → 0.1.30
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 +1 -1
- package/src/codegen/CodeGenerator.ts +102 -9
- package/src/codegen/TypeValidator.ts +28 -10
- package/src/codegen/generators/IGeneratorState.ts +7 -0
- package/src/codegen/generators/declarationGenerators/ScopeGenerator.ts +4 -2
- package/src/codegen/memberAccessChain.test.ts +361 -0
- package/src/codegen/memberAccessChain.ts +280 -0
package/package.json
CHANGED
|
@@ -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,
|
|
@@ -317,6 +319,13 @@ export default class CodeGenerator implements IOrchestrator {
|
|
|
317
319
|
*/
|
|
318
320
|
private functionParamLists: Map<string, string[]> = new Map();
|
|
319
321
|
|
|
322
|
+
/**
|
|
323
|
+
* Issue #369: Tracks whether self-include was added.
|
|
324
|
+
* When true, skip struct/enum/bitmap definitions in .c file because
|
|
325
|
+
* they'll be defined in the included header.
|
|
326
|
+
*/
|
|
327
|
+
private selfIncludeAdded: boolean = false;
|
|
328
|
+
|
|
320
329
|
/**
|
|
321
330
|
* Initialize generator registry with extracted generators.
|
|
322
331
|
* Called once before code generation begins.
|
|
@@ -378,6 +387,7 @@ export default class CodeGenerator implements IOrchestrator {
|
|
|
378
387
|
localVariables: this.context.localVariables,
|
|
379
388
|
localArrays: this.context.localArrays,
|
|
380
389
|
expectedType: this.context.expectedType,
|
|
390
|
+
selfIncludeAdded: this.selfIncludeAdded, // Issue #369
|
|
381
391
|
};
|
|
382
392
|
}
|
|
383
393
|
|
|
@@ -1381,6 +1391,7 @@ export default class CodeGenerator implements IOrchestrator {
|
|
|
1381
1391
|
this.needsString = false; // ADR-045: Reset string header tracking
|
|
1382
1392
|
this.needsISR = false; // ADR-040: Reset ISR typedef tracking
|
|
1383
1393
|
this.needsCMSIS = false; // ADR-049/050: Reset CMSIS include tracking
|
|
1394
|
+
this.selfIncludeAdded = false; // Issue #369: Reset self-include tracking
|
|
1384
1395
|
|
|
1385
1396
|
// First pass: collect namespace and class members
|
|
1386
1397
|
// Issue #60: Create SymbolCollector (extracted from CodeGenerator)
|
|
@@ -1441,6 +1452,7 @@ export default class CodeGenerator implements IOrchestrator {
|
|
|
1441
1452
|
// When file has public symbols and headers are being generated,
|
|
1442
1453
|
// include own header to ensure proper C linkage
|
|
1443
1454
|
// Issue #339: Use relative path from source root when available
|
|
1455
|
+
// Issue #369: Track self-include to skip type definitions in .c file
|
|
1444
1456
|
if (
|
|
1445
1457
|
options?.generateHeaders &&
|
|
1446
1458
|
this.symbols!.hasPublicSymbols() &&
|
|
@@ -1453,6 +1465,8 @@ export default class CodeGenerator implements IOrchestrator {
|
|
|
1453
1465
|
const headerName = pathToUse.replace(/\.cnx$|\.cnext$/, ".h");
|
|
1454
1466
|
output.push(`#include "${headerName}"`);
|
|
1455
1467
|
output.push("");
|
|
1468
|
+
// Issue #369: Mark that self-include was added - types will be in header
|
|
1469
|
+
this.selfIncludeAdded = true;
|
|
1456
1470
|
}
|
|
1457
1471
|
|
|
1458
1472
|
// Pass through #include directives from source
|
|
@@ -2134,16 +2148,62 @@ export default class CodeGenerator implements IOrchestrator {
|
|
|
2134
2148
|
}
|
|
2135
2149
|
}
|
|
2136
2150
|
|
|
2137
|
-
//
|
|
2138
|
-
//
|
|
2139
|
-
//
|
|
2151
|
+
// Issue #365: Handle scope-qualified calls: Scope.method(...) or global.Scope.method(...)
|
|
2152
|
+
// Track member accesses to build the mangled callee name (e.g., Storage_load)
|
|
2153
|
+
// Then when we find the function call, record it to the call graph
|
|
2154
|
+
if (postfixOps.length > 0) {
|
|
2155
|
+
const memberNames: string[] = [];
|
|
2156
|
+
|
|
2157
|
+
// Start with primary identifier if it's a scope name (not 'global' or 'this')
|
|
2158
|
+
const primaryId = primary.IDENTIFIER()?.getText();
|
|
2159
|
+
if (primaryId && primaryId !== "global" && primaryId !== "this") {
|
|
2160
|
+
memberNames.push(primaryId);
|
|
2161
|
+
}
|
|
2162
|
+
|
|
2163
|
+
// Collect member access names until we hit a function call
|
|
2164
|
+
for (const op of postfixOps) {
|
|
2165
|
+
if (op.IDENTIFIER()) {
|
|
2166
|
+
// Member access: .IDENTIFIER
|
|
2167
|
+
memberNames.push(op.IDENTIFIER()!.getText());
|
|
2168
|
+
} else if (op.LPAREN()) {
|
|
2169
|
+
// Function call found - record to call graph if we have a callee name
|
|
2170
|
+
if (memberNames.length >= 1) {
|
|
2171
|
+
// Build mangled name: e.g., ["Storage", "load"] -> "Storage_load"
|
|
2172
|
+
// For scope methods, the last name is the method, everything before is scope
|
|
2173
|
+
const calleeName = memberNames.join("_");
|
|
2174
|
+
const argList = op.argumentList();
|
|
2175
|
+
|
|
2176
|
+
if (argList) {
|
|
2177
|
+
const args = argList.expression();
|
|
2178
|
+
for (let j = 0; j < args.length; j++) {
|
|
2179
|
+
const arg = args[j];
|
|
2180
|
+
const argName = this.getSimpleIdentifierFromExpr(arg);
|
|
2181
|
+
if (argName && paramSet.has(argName)) {
|
|
2182
|
+
this.functionCallGraph.get(funcName)!.push({
|
|
2183
|
+
callee: calleeName,
|
|
2184
|
+
paramIndex: j,
|
|
2185
|
+
argParamName: argName,
|
|
2186
|
+
});
|
|
2187
|
+
}
|
|
2188
|
+
}
|
|
2189
|
+
}
|
|
2190
|
+
}
|
|
2191
|
+
// Reset for potential chained calls like obj.foo().bar()
|
|
2192
|
+
memberNames.length = 0;
|
|
2193
|
+
} else if (op.expression().length > 0) {
|
|
2194
|
+
// Array subscript - doesn't contribute to method name
|
|
2195
|
+
// but reset member chain as array access breaks scope chain
|
|
2196
|
+
memberNames.length = 0;
|
|
2197
|
+
}
|
|
2198
|
+
}
|
|
2199
|
+
}
|
|
2140
2200
|
|
|
2141
2201
|
// Recurse into primary expression if it's a parenthesized expression
|
|
2142
2202
|
if (primary.expression()) {
|
|
2143
2203
|
this.walkExpressionForCalls(funcName, paramSet, primary.expression()!);
|
|
2144
2204
|
}
|
|
2145
2205
|
|
|
2146
|
-
// Walk arguments in any postfix function call ops
|
|
2206
|
+
// Walk arguments in any postfix function call ops (for nested calls)
|
|
2147
2207
|
for (const op of postfixOps) {
|
|
2148
2208
|
if (op.argumentList()) {
|
|
2149
2209
|
for (const argExpr of op.argumentList()!.expression()) {
|
|
@@ -4370,15 +4430,26 @@ export default class CodeGenerator implements IOrchestrator {
|
|
|
4370
4430
|
if (ctx.registerDeclaration()) {
|
|
4371
4431
|
return this.generateRegister(ctx.registerDeclaration()!);
|
|
4372
4432
|
}
|
|
4433
|
+
// Issue #369: Skip struct/enum/bitmap definitions when self-include is added
|
|
4434
|
+
// These types will be defined in the included header file
|
|
4373
4435
|
if (ctx.structDeclaration()) {
|
|
4436
|
+
if (this.selfIncludeAdded) {
|
|
4437
|
+
return ""; // Definition will come from header
|
|
4438
|
+
}
|
|
4374
4439
|
return this.generateStruct(ctx.structDeclaration()!);
|
|
4375
4440
|
}
|
|
4376
4441
|
// ADR-017: Handle enum declarations
|
|
4377
4442
|
if (ctx.enumDeclaration()) {
|
|
4443
|
+
if (this.selfIncludeAdded) {
|
|
4444
|
+
return ""; // Definition will come from header
|
|
4445
|
+
}
|
|
4378
4446
|
return this.generateEnum(ctx.enumDeclaration()!);
|
|
4379
4447
|
}
|
|
4380
4448
|
// ADR-034: Handle bitmap declarations
|
|
4381
4449
|
if (ctx.bitmapDeclaration()) {
|
|
4450
|
+
if (this.selfIncludeAdded) {
|
|
4451
|
+
return ""; // Definition will come from header
|
|
4452
|
+
}
|
|
4382
4453
|
return this.generateBitmap(ctx.bitmapDeclaration()!);
|
|
4383
4454
|
}
|
|
4384
4455
|
if (ctx.functionDeclaration()) {
|
|
@@ -6287,6 +6358,10 @@ export default class CodeGenerator implements IOrchestrator {
|
|
|
6287
6358
|
// Check if first identifier is a scope for special handling
|
|
6288
6359
|
const isCrossScope = this.isKnownScope(firstId);
|
|
6289
6360
|
|
|
6361
|
+
// ADR-006: Check if first identifier is a struct parameter (needs -> access)
|
|
6362
|
+
const paramInfo = this.context.currentParameters.get(firstId);
|
|
6363
|
+
const isStructParam = paramInfo?.isStruct ?? false;
|
|
6364
|
+
|
|
6290
6365
|
// Bug #8: Track struct types to detect bit access through chains
|
|
6291
6366
|
// e.g., items[0].byte[7] where byte is u8 - final [7] is bit access
|
|
6292
6367
|
let currentStructType: string | undefined;
|
|
@@ -6313,8 +6388,11 @@ export default class CodeGenerator implements IOrchestrator {
|
|
|
6313
6388
|
idIndex < identifiers.length
|
|
6314
6389
|
) {
|
|
6315
6390
|
const memberName = identifiers[idIndex].getText();
|
|
6316
|
-
// Use
|
|
6317
|
-
const separator =
|
|
6391
|
+
// ADR-006: Use determineSeparator helper for -> (struct param) / _ (scope) / .
|
|
6392
|
+
const separator = memberAccessChain.determineSeparator(
|
|
6393
|
+
{ isStructParam, isCrossScope },
|
|
6394
|
+
idIndex,
|
|
6395
|
+
);
|
|
6318
6396
|
result += `${separator}${memberName}`;
|
|
6319
6397
|
idIndex++;
|
|
6320
6398
|
|
|
@@ -6850,14 +6928,22 @@ export default class CodeGenerator implements IOrchestrator {
|
|
|
6850
6928
|
const exprs = arrayAccessCtx.expression();
|
|
6851
6929
|
const typeInfo = this.context.typeRegistry.get(name);
|
|
6852
6930
|
|
|
6931
|
+
// Issue #368: Check if this is an array parameter (e.g., void foo(u8 data[]))
|
|
6932
|
+
// Array parameters may not have arrayDimensions in typeRegistry (for unsized params),
|
|
6933
|
+
// but they ARE arrays and should use array indexing, not bit manipulation.
|
|
6934
|
+
const paramInfo = this.context.currentParameters.get(name);
|
|
6935
|
+
const isArrayParameter = paramInfo?.isArray ?? false;
|
|
6936
|
+
|
|
6853
6937
|
// ADR-040: ISR arrays use normal array indexing, not bit manipulation
|
|
6854
6938
|
// Also handle any array type that isn't an integer scalar
|
|
6855
6939
|
// Issue #213: String parameters (isString=true) should also use memcpy for slice assignment
|
|
6940
|
+
// Issue #368: Array parameters (even unsized like u8 data[]) should use array indexing
|
|
6856
6941
|
const isActualArray =
|
|
6857
6942
|
(typeInfo?.isArray &&
|
|
6858
6943
|
typeInfo.arrayDimensions &&
|
|
6859
6944
|
typeInfo.arrayDimensions.length > 0) ||
|
|
6860
|
-
typeInfo?.isString
|
|
6945
|
+
typeInfo?.isString ||
|
|
6946
|
+
isArrayParameter;
|
|
6861
6947
|
const isISRType = typeInfo?.baseType === "ISR";
|
|
6862
6948
|
|
|
6863
6949
|
if (isActualArray || isISRType) {
|
|
@@ -9320,6 +9406,10 @@ export default class CodeGenerator implements IOrchestrator {
|
|
|
9320
9406
|
// Check if first identifier is a scope for special handling
|
|
9321
9407
|
const isCrossScope = this.isKnownScope(firstPart);
|
|
9322
9408
|
|
|
9409
|
+
// ADR-006: Check if first identifier is a struct parameter (needs -> access)
|
|
9410
|
+
const paramInfo = this.context.currentParameters.get(firstPart);
|
|
9411
|
+
const isStructParam = paramInfo?.isStruct ?? false;
|
|
9412
|
+
|
|
9323
9413
|
// ADR-016: Inside a scope, accessing another scope requires global. prefix
|
|
9324
9414
|
if (isCrossScope && this.context.currentScope) {
|
|
9325
9415
|
// Self-referential access should use 'this.'
|
|
@@ -9356,8 +9446,11 @@ export default class CodeGenerator implements IOrchestrator {
|
|
|
9356
9446
|
i++;
|
|
9357
9447
|
if (i < ctx.children.length && idIndex < parts.length) {
|
|
9358
9448
|
const memberName = parts[idIndex];
|
|
9359
|
-
// Use
|
|
9360
|
-
const separator =
|
|
9449
|
+
// ADR-006: Use determineSeparator helper for -> (struct param) / _ (scope) / .
|
|
9450
|
+
const separator = memberAccessChain.determineSeparator(
|
|
9451
|
+
{ isStructParam, isCrossScope },
|
|
9452
|
+
idIndex,
|
|
9453
|
+
);
|
|
9361
9454
|
result += `${separator}${memberName}`;
|
|
9362
9455
|
idIndex++;
|
|
9363
9456
|
|
|
@@ -866,16 +866,8 @@ class TypeValidator {
|
|
|
866
866
|
for (const add of shift.additiveExpression()) {
|
|
867
867
|
for (const mult of add.multiplicativeExpression()) {
|
|
868
868
|
for (const unary of mult.unaryExpression()) {
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
for (const op of postfix.postfixOp()) {
|
|
872
|
-
if (
|
|
873
|
-
op.argumentList() ||
|
|
874
|
-
op.getText().startsWith("(")
|
|
875
|
-
) {
|
|
876
|
-
return true;
|
|
877
|
-
}
|
|
878
|
-
}
|
|
869
|
+
if (this.hasPostfixFunctionCallInUnary(unary)) {
|
|
870
|
+
return true;
|
|
879
871
|
}
|
|
880
872
|
}
|
|
881
873
|
}
|
|
@@ -890,6 +882,32 @@ class TypeValidator {
|
|
|
890
882
|
return false;
|
|
891
883
|
}
|
|
892
884
|
|
|
885
|
+
/**
|
|
886
|
+
* Issue #366: Recursively check unaryExpression for function calls.
|
|
887
|
+
* Handles unary operators (!, -, ~, &) that wrap function calls,
|
|
888
|
+
* including arbitrary nesting like !!isReady() or -~getValue().
|
|
889
|
+
*/
|
|
890
|
+
private hasPostfixFunctionCallInUnary(
|
|
891
|
+
unary: Parser.UnaryExpressionContext,
|
|
892
|
+
): boolean {
|
|
893
|
+
// Recurse through nested unary operators (!, -, ~, &) until we reach postfixExpression
|
|
894
|
+
const nestedUnary = unary.unaryExpression();
|
|
895
|
+
if (nestedUnary) {
|
|
896
|
+
return this.hasPostfixFunctionCallInUnary(nestedUnary);
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
// Base case: check postfixExpression
|
|
900
|
+
const postfix = unary.postfixExpression();
|
|
901
|
+
if (postfix) {
|
|
902
|
+
for (const op of postfix.postfixOp()) {
|
|
903
|
+
if (op.argumentList() || op.getText().startsWith("(")) {
|
|
904
|
+
return true;
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
return false;
|
|
909
|
+
}
|
|
910
|
+
|
|
893
911
|
// ========================================================================
|
|
894
912
|
// Shift Amount Validation (MISRA C:2012 Rule 12.2)
|
|
895
913
|
// ========================================================================
|
|
@@ -26,6 +26,13 @@ interface IGeneratorState {
|
|
|
26
26
|
|
|
27
27
|
/** Expected type for inferred struct initializers */
|
|
28
28
|
readonly expectedType: string | null;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Issue #369: Whether self-include was added.
|
|
32
|
+
* When true, type definitions (struct/enum/bitmap) should not be emitted
|
|
33
|
+
* in the .c file as they'll come from the included header.
|
|
34
|
+
*/
|
|
35
|
+
readonly selfIncludeAdded: boolean;
|
|
29
36
|
}
|
|
30
37
|
|
|
31
38
|
export default IGeneratorState;
|
|
@@ -142,14 +142,16 @@ const generateScope: TGeneratorFn<Parser.ScopeDeclarationContext> = (
|
|
|
142
142
|
|
|
143
143
|
// ADR-017: Handle enum declarations inside scopes
|
|
144
144
|
// The enum generator will use state.currentScope which we've set via orchestrator
|
|
145
|
-
if (
|
|
145
|
+
// Issue #369: Skip enum definition if self-include was added (it will be in the header)
|
|
146
|
+
if (member.enumDeclaration() && !state.selfIncludeAdded) {
|
|
146
147
|
const enumDecl = member.enumDeclaration()!;
|
|
147
148
|
lines.push("");
|
|
148
149
|
lines.push(generateScopedEnumInline(enumDecl, name, input, orchestrator));
|
|
149
150
|
}
|
|
150
151
|
|
|
151
152
|
// ADR-034: Handle bitmap declarations inside scopes
|
|
152
|
-
if (
|
|
153
|
+
// Issue #369: Skip bitmap definition if self-include was added (it will be in the header)
|
|
154
|
+
if (member.bitmapDeclaration() && !state.selfIncludeAdded) {
|
|
153
155
|
const bitmapDecl = member.bitmapDeclaration()!;
|
|
154
156
|
lines.push("");
|
|
155
157
|
lines.push(generateScopedBitmapInline(bitmapDecl, name, input));
|
|
@@ -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 };
|