c-next 0.1.67 → 0.1.69
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/transpiler/Transpiler.ts +5 -13
- package/src/transpiler/logic/analysis/InitializationAnalyzer.ts +41 -36
- package/src/transpiler/logic/analysis/__tests__/InitializationAnalyzer.test.ts +29 -28
- package/src/transpiler/logic/analysis/__tests__/runAnalyzers.test.ts +32 -19
- package/src/transpiler/logic/analysis/runAnalyzers.ts +8 -14
- package/src/transpiler/output/codegen/CodeGenerator.ts +125 -135
- package/src/transpiler/output/codegen/TypeValidator.ts +2 -2
- package/src/transpiler/output/codegen/__tests__/CodeGenerator.coverage.test.ts +2 -2
- package/src/transpiler/output/codegen/__tests__/CodeGenerator.test.ts +29 -1
- package/src/transpiler/output/codegen/__tests__/TypeValidator.resolution.test.ts +1 -3
- package/src/transpiler/output/codegen/__tests__/TypeValidator.test.ts +3 -1
- package/src/transpiler/output/codegen/assignment/AssignmentContextBuilder.ts +49 -0
- package/src/transpiler/output/codegen/assignment/IAssignmentContext.ts +15 -0
- package/src/transpiler/output/codegen/assignment/__tests__/AssignmentClassifier.test.ts +7 -0
- package/src/transpiler/output/codegen/assignment/handlers/ArrayHandlers.ts +24 -17
- package/src/transpiler/output/codegen/assignment/handlers/BitAccessHandlers.ts +16 -5
- package/src/transpiler/output/codegen/assignment/handlers/__tests__/AccessPatternHandlers.test.ts +9 -1
- package/src/transpiler/output/codegen/assignment/handlers/__tests__/ArrayHandlers.test.ts +18 -1
- package/src/transpiler/output/codegen/assignment/handlers/__tests__/BitAccessHandlers.test.ts +9 -1
- package/src/transpiler/output/codegen/assignment/handlers/__tests__/BitmapHandlers.test.ts +9 -1
- package/src/transpiler/output/codegen/assignment/handlers/__tests__/RegisterHandlers.test.ts +10 -1
- package/src/transpiler/output/codegen/assignment/handlers/__tests__/SpecialHandlers.test.ts +9 -1
- package/src/transpiler/output/codegen/assignment/handlers/__tests__/StringHandlers.test.ts +9 -1
- package/src/transpiler/output/codegen/generators/expressions/PostfixExpressionGenerator.ts +31 -10
- package/src/transpiler/output/codegen/generators/expressions/__tests__/PostfixExpressionGenerator.test.ts +66 -4
- package/src/transpiler/output/codegen/helpers/MemberAccessValidator.ts +47 -6
- package/src/transpiler/output/codegen/helpers/MemberSeparatorResolver.ts +6 -1
- package/src/transpiler/output/codegen/helpers/__tests__/MemberAccessValidator.test.ts +109 -3
- package/src/transpiler/state/CodeGenState.ts +75 -1
- package/src/transpiler/state/__tests__/CodeGenState.test.ts +124 -5
- package/src/transpiler/logic/analysis/AnalyzerContextBuilder.ts +0 -58
- package/src/transpiler/logic/analysis/__tests__/AnalyzerContextBuilder.test.ts +0 -137
|
@@ -20,26 +20,30 @@ function gen(): ICodeGenApi {
|
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
22
|
* Handle simple array element: arr[i] <- value
|
|
23
|
+
*
|
|
24
|
+
* Uses resolvedTarget which includes scope prefix and subscript,
|
|
25
|
+
* e.g., "data[0]" inside scope ArrayBug -> "ArrayBug_data[0]"
|
|
23
26
|
*/
|
|
24
27
|
function handleArrayElement(ctx: IAssignmentContext): string {
|
|
25
|
-
|
|
26
|
-
const index = gen().generateExpression(ctx.subscripts[0]);
|
|
27
|
-
|
|
28
|
-
return `${name}[${index}] ${ctx.cOp} ${ctx.generatedValue};`;
|
|
28
|
+
return `${ctx.resolvedTarget} ${ctx.cOp} ${ctx.generatedValue};`;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
32
|
* Handle multi-dimensional array element: matrix[i][j] <- value
|
|
33
|
+
*
|
|
34
|
+
* Uses resolvedTarget which includes scope prefix and all subscripts.
|
|
35
|
+
* Uses resolvedBaseIdentifier for type lookups to support scoped arrays.
|
|
33
36
|
*/
|
|
34
37
|
function handleMultiDimArrayElement(ctx: IAssignmentContext): string {
|
|
35
|
-
|
|
36
|
-
|
|
38
|
+
// Use resolvedBaseIdentifier for type lookup (includes scope prefix)
|
|
39
|
+
// e.g., "ArrayBug_data" instead of "data"
|
|
40
|
+
const typeInfo = CodeGenState.typeRegistry.get(ctx.resolvedBaseIdentifier);
|
|
37
41
|
|
|
38
42
|
// ADR-036: Compile-time bounds checking for constant indices
|
|
39
43
|
if (typeInfo?.arrayDimensions) {
|
|
40
44
|
const line = ctx.subscripts[0]?.start?.line ?? 0;
|
|
41
45
|
TypeValidator.checkArrayBounds(
|
|
42
|
-
|
|
46
|
+
ctx.resolvedBaseIdentifier,
|
|
43
47
|
[...typeInfo.arrayDimensions],
|
|
44
48
|
[...ctx.subscripts],
|
|
45
49
|
line,
|
|
@@ -47,11 +51,7 @@ function handleMultiDimArrayElement(ctx: IAssignmentContext): string {
|
|
|
47
51
|
);
|
|
48
52
|
}
|
|
49
53
|
|
|
50
|
-
|
|
51
|
-
.map((e) => gen().generateExpression(e))
|
|
52
|
-
.join("][");
|
|
53
|
-
|
|
54
|
-
return `${name}[${indices}] ${ctx.cOp} ${ctx.generatedValue};`;
|
|
54
|
+
return `${ctx.resolvedTarget} ${ctx.cOp} ${ctx.generatedValue};`;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
/**
|
|
@@ -69,7 +69,8 @@ function handleArraySlice(ctx: IAssignmentContext): string {
|
|
|
69
69
|
);
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
|
|
72
|
+
// Use resolvedBaseIdentifier for type lookup (includes scope prefix)
|
|
73
|
+
const name = ctx.resolvedBaseIdentifier;
|
|
73
74
|
const typeInfo = CodeGenState.typeRegistry.get(name);
|
|
74
75
|
|
|
75
76
|
// Get line number for error messages
|
|
@@ -77,10 +78,12 @@ function handleArraySlice(ctx: IAssignmentContext): string {
|
|
|
77
78
|
|
|
78
79
|
// Validate 1D array only
|
|
79
80
|
if (typeInfo?.arrayDimensions && typeInfo.arrayDimensions.length > 1) {
|
|
81
|
+
// Use raw identifier in error message for clarity
|
|
82
|
+
const rawName = ctx.identifiers[0];
|
|
80
83
|
throw new Error(
|
|
81
84
|
`${line}:0 Error: Slice assignment is only valid on one-dimensional arrays. ` +
|
|
82
|
-
`'${
|
|
83
|
-
`Access the innermost dimension first (e.g., ${
|
|
85
|
+
`'${rawName}' has ${typeInfo.arrayDimensions.length} dimensions. ` +
|
|
86
|
+
`Access the innermost dimension first (e.g., ${rawName}[index][offset, length]).`,
|
|
84
87
|
);
|
|
85
88
|
}
|
|
86
89
|
|
|
@@ -109,17 +112,21 @@ function handleArraySlice(ctx: IAssignmentContext): string {
|
|
|
109
112
|
} else if (typeInfo?.arrayDimensions?.[0]) {
|
|
110
113
|
capacity = typeInfo.arrayDimensions[0];
|
|
111
114
|
} else {
|
|
115
|
+
// Use raw identifier in error message for clarity
|
|
116
|
+
const rawName = ctx.identifiers[0];
|
|
112
117
|
throw new Error(
|
|
113
|
-
`${line}:0 Error: Cannot determine buffer size for '${
|
|
118
|
+
`${line}:0 Error: Cannot determine buffer size for '${rawName}' at compile time.`,
|
|
114
119
|
);
|
|
115
120
|
}
|
|
116
121
|
|
|
117
122
|
// Bounds validation
|
|
118
123
|
if (offsetValue + lengthValue > capacity) {
|
|
124
|
+
// Use raw identifier in error message for clarity
|
|
125
|
+
const rawName = ctx.identifiers[0];
|
|
119
126
|
throw new Error(
|
|
120
127
|
`${line}:0 Error: Slice assignment out of bounds: ` +
|
|
121
128
|
`offset(${offsetValue}) + length(${lengthValue}) = ${offsetValue + lengthValue} ` +
|
|
122
|
-
`exceeds buffer capacity(${capacity}) for '${
|
|
129
|
+
`exceeds buffer capacity(${capacity}) for '${rawName}'.`,
|
|
123
130
|
);
|
|
124
131
|
}
|
|
125
132
|
|
|
@@ -33,11 +33,14 @@ function validateNotCompound(ctx: IAssignmentContext): void {
|
|
|
33
33
|
/**
|
|
34
34
|
* Handle single bit on integer variable: flags[3] <- true
|
|
35
35
|
* Also handles float bit indexing: f32Var[3] <- true
|
|
36
|
+
* Uses resolvedBaseIdentifier for proper scope prefix support.
|
|
36
37
|
*/
|
|
37
38
|
function handleIntegerBit(ctx: IAssignmentContext): string {
|
|
38
39
|
validateNotCompound(ctx);
|
|
39
40
|
|
|
40
|
-
|
|
41
|
+
// Use resolvedBaseIdentifier for type lookup and code generation
|
|
42
|
+
// e.g., "ArrayBug_flags" instead of "flags"
|
|
43
|
+
const name = ctx.resolvedBaseIdentifier;
|
|
41
44
|
const bitIndex = gen().generateExpression(ctx.subscripts[0]);
|
|
42
45
|
const typeInfo = CodeGenState.typeRegistry.get(name);
|
|
43
46
|
|
|
@@ -67,11 +70,13 @@ function handleIntegerBit(ctx: IAssignmentContext): string {
|
|
|
67
70
|
/**
|
|
68
71
|
* Handle bit range on integer variable: flags[0, 3] <- 5
|
|
69
72
|
* Also handles float bit range: f32Var[0, 8] <- 0xFF
|
|
73
|
+
* Uses resolvedBaseIdentifier for proper scope prefix support.
|
|
70
74
|
*/
|
|
71
75
|
function handleIntegerBitRange(ctx: IAssignmentContext): string {
|
|
72
76
|
validateNotCompound(ctx);
|
|
73
77
|
|
|
74
|
-
|
|
78
|
+
// Use resolvedBaseIdentifier for type lookup and code generation
|
|
79
|
+
const name = ctx.resolvedBaseIdentifier;
|
|
75
80
|
const start = gen().generateExpression(ctx.subscripts[0]);
|
|
76
81
|
const width = gen().generateExpression(ctx.subscripts[1]);
|
|
77
82
|
const typeInfo = CodeGenState.typeRegistry.get(name);
|
|
@@ -126,15 +131,19 @@ function handleStructMemberBit(ctx: IAssignmentContext): string {
|
|
|
126
131
|
|
|
127
132
|
/**
|
|
128
133
|
* Handle bit on multi-dimensional array element: matrix[i][j][FIELD_BIT] <- false
|
|
134
|
+
* Uses resolvedBaseIdentifier for proper scope prefix support.
|
|
129
135
|
*/
|
|
130
136
|
function handleArrayElementBit(ctx: IAssignmentContext): string {
|
|
131
137
|
validateNotCompound(ctx);
|
|
132
138
|
|
|
133
|
-
|
|
139
|
+
// Use resolvedBaseIdentifier for type lookup and code generation
|
|
140
|
+
const arrayName = ctx.resolvedBaseIdentifier;
|
|
134
141
|
const typeInfo = CodeGenState.typeRegistry.get(arrayName);
|
|
135
142
|
|
|
136
143
|
if (!typeInfo?.arrayDimensions) {
|
|
137
|
-
|
|
144
|
+
// Use raw identifier in error message for clarity
|
|
145
|
+
const rawName = ctx.identifiers[0];
|
|
146
|
+
throw new Error(`Error: ${rawName} is not an array`);
|
|
138
147
|
}
|
|
139
148
|
|
|
140
149
|
const numDims = typeInfo.arrayDimensions.length;
|
|
@@ -160,12 +169,14 @@ function handleArrayElementBit(ctx: IAssignmentContext): string {
|
|
|
160
169
|
*
|
|
161
170
|
* The target is a chain like array[idx].member or struct.field with a
|
|
162
171
|
* bit range subscript [start, width] at the end.
|
|
172
|
+
* Uses resolvedBaseIdentifier for proper scope prefix support.
|
|
163
173
|
*/
|
|
164
174
|
function handleStructChainBitRange(ctx: IAssignmentContext): string {
|
|
165
175
|
validateNotCompound(ctx);
|
|
166
176
|
|
|
167
177
|
// Build the base target from postfixOps, excluding the last one (the bit range)
|
|
168
|
-
|
|
178
|
+
// Use resolvedBaseIdentifier for the base to include scope prefix
|
|
179
|
+
const baseId = ctx.resolvedBaseIdentifier;
|
|
169
180
|
const opsBeforeLast = ctx.postfixOps.slice(0, -1);
|
|
170
181
|
|
|
171
182
|
let baseTarget = baseId;
|
package/src/transpiler/output/codegen/assignment/handlers/__tests__/AccessPatternHandlers.test.ts
CHANGED
|
@@ -16,8 +16,14 @@ import HandlerTestUtils from "./handlerTestUtils";
|
|
|
16
16
|
function createMockContext(
|
|
17
17
|
overrides: Partial<IAssignmentContext> = {},
|
|
18
18
|
): IAssignmentContext {
|
|
19
|
+
// Default resolved values based on first identifier
|
|
20
|
+
const identifiers = overrides.identifiers ?? ["Counter", "value"];
|
|
21
|
+
const resolvedTarget = overrides.resolvedTarget ?? `${identifiers.join("_")}`;
|
|
22
|
+
const resolvedBaseIdentifier =
|
|
23
|
+
overrides.resolvedBaseIdentifier ?? identifiers[0];
|
|
24
|
+
|
|
19
25
|
return {
|
|
20
|
-
identifiers
|
|
26
|
+
identifiers,
|
|
21
27
|
subscripts: [],
|
|
22
28
|
isCompound: false,
|
|
23
29
|
cnextOp: "<-",
|
|
@@ -34,6 +40,8 @@ function createMockContext(
|
|
|
34
40
|
isSimpleIdentifier: false,
|
|
35
41
|
isSimpleThisAccess: false,
|
|
36
42
|
isSimpleGlobalAccess: false,
|
|
43
|
+
resolvedTarget,
|
|
44
|
+
resolvedBaseIdentifier,
|
|
37
45
|
...overrides,
|
|
38
46
|
} as IAssignmentContext;
|
|
39
47
|
}
|
|
@@ -28,8 +28,14 @@ import HandlerTestUtils from "./handlerTestUtils";
|
|
|
28
28
|
function createMockContext(
|
|
29
29
|
overrides: Partial<IAssignmentContext> = {},
|
|
30
30
|
): IAssignmentContext {
|
|
31
|
+
// Default resolved values based on first identifier
|
|
32
|
+
const identifiers = overrides.identifiers ?? ["arr"];
|
|
33
|
+
const resolvedTarget = overrides.resolvedTarget ?? `${identifiers[0]}[i]`;
|
|
34
|
+
const resolvedBaseIdentifier =
|
|
35
|
+
overrides.resolvedBaseIdentifier ?? identifiers[0];
|
|
36
|
+
|
|
31
37
|
return {
|
|
32
|
-
identifiers
|
|
38
|
+
identifiers,
|
|
33
39
|
subscripts: [{ mockValue: "i", start: { line: 1 } } as never],
|
|
34
40
|
isCompound: false,
|
|
35
41
|
cnextOp: "<-",
|
|
@@ -46,6 +52,8 @@ function createMockContext(
|
|
|
46
52
|
isSimpleIdentifier: false,
|
|
47
53
|
isSimpleThisAccess: false,
|
|
48
54
|
isSimpleGlobalAccess: false,
|
|
55
|
+
resolvedTarget,
|
|
56
|
+
resolvedBaseIdentifier,
|
|
49
57
|
...overrides,
|
|
50
58
|
} as IAssignmentContext;
|
|
51
59
|
}
|
|
@@ -96,6 +104,7 @@ describe("ArrayHandlers", () => {
|
|
|
96
104
|
isCompound: true,
|
|
97
105
|
cnextOp: "+<-",
|
|
98
106
|
cOp: "+=",
|
|
107
|
+
resolvedTarget: "arr[0]",
|
|
99
108
|
});
|
|
100
109
|
|
|
101
110
|
const result = getHandler()!(ctx);
|
|
@@ -109,6 +118,8 @@ describe("ArrayHandlers", () => {
|
|
|
109
118
|
});
|
|
110
119
|
const ctx = createMockContext({
|
|
111
120
|
identifiers: ["buffer"],
|
|
121
|
+
resolvedTarget: "buffer[idx]",
|
|
122
|
+
resolvedBaseIdentifier: "buffer",
|
|
112
123
|
});
|
|
113
124
|
|
|
114
125
|
const result = getHandler()!(ctx);
|
|
@@ -137,6 +148,8 @@ describe("ArrayHandlers", () => {
|
|
|
137
148
|
{ mockValue: "j", start: { line: 1 } } as never,
|
|
138
149
|
],
|
|
139
150
|
subscriptDepth: 2,
|
|
151
|
+
resolvedTarget: "matrix[i][j]",
|
|
152
|
+
resolvedBaseIdentifier: "matrix",
|
|
140
153
|
});
|
|
141
154
|
|
|
142
155
|
const result = getHandler()!(ctx);
|
|
@@ -160,6 +173,8 @@ describe("ArrayHandlers", () => {
|
|
|
160
173
|
{ mockValue: "z", start: { line: 1 } } as never,
|
|
161
174
|
],
|
|
162
175
|
subscriptDepth: 3,
|
|
176
|
+
resolvedTarget: "cube[x][y][z]",
|
|
177
|
+
resolvedBaseIdentifier: "cube",
|
|
163
178
|
});
|
|
164
179
|
|
|
165
180
|
const result = getHandler()!(ctx);
|
|
@@ -212,6 +227,8 @@ describe("ArrayHandlers", () => {
|
|
|
212
227
|
],
|
|
213
228
|
isCompound: true,
|
|
214
229
|
cOp: "-=",
|
|
230
|
+
resolvedTarget: "grid[0][1]",
|
|
231
|
+
resolvedBaseIdentifier: "grid",
|
|
215
232
|
});
|
|
216
233
|
|
|
217
234
|
const result = getHandler()!(ctx);
|
package/src/transpiler/output/codegen/assignment/handlers/__tests__/BitAccessHandlers.test.ts
CHANGED
|
@@ -16,8 +16,14 @@ import HandlerTestUtils from "./handlerTestUtils";
|
|
|
16
16
|
function createMockContext(
|
|
17
17
|
overrides: Partial<IAssignmentContext> = {},
|
|
18
18
|
): IAssignmentContext {
|
|
19
|
+
// Default resolved values based on first identifier
|
|
20
|
+
const identifiers = overrides.identifiers ?? ["flags"];
|
|
21
|
+
const resolvedTarget = overrides.resolvedTarget ?? `${identifiers[0]}[3]`;
|
|
22
|
+
const resolvedBaseIdentifier =
|
|
23
|
+
overrides.resolvedBaseIdentifier ?? identifiers[0];
|
|
24
|
+
|
|
19
25
|
return {
|
|
20
|
-
identifiers
|
|
26
|
+
identifiers,
|
|
21
27
|
subscripts: [{ mockValue: "3" } as never],
|
|
22
28
|
isCompound: false,
|
|
23
29
|
cnextOp: "<-",
|
|
@@ -34,6 +40,8 @@ function createMockContext(
|
|
|
34
40
|
isSimpleIdentifier: false,
|
|
35
41
|
isSimpleThisAccess: false,
|
|
36
42
|
isSimpleGlobalAccess: false,
|
|
43
|
+
resolvedTarget,
|
|
44
|
+
resolvedBaseIdentifier,
|
|
37
45
|
...overrides,
|
|
38
46
|
} as IAssignmentContext;
|
|
39
47
|
}
|
|
@@ -25,8 +25,14 @@ import HandlerTestUtils from "./handlerTestUtils";
|
|
|
25
25
|
function createMockContext(
|
|
26
26
|
overrides: Partial<IAssignmentContext> = {},
|
|
27
27
|
): IAssignmentContext {
|
|
28
|
+
// Default resolved values based on first identifier
|
|
29
|
+
const identifiers = overrides.identifiers ?? ["flags", "Running"];
|
|
30
|
+
const resolvedTarget = overrides.resolvedTarget ?? identifiers[0];
|
|
31
|
+
const resolvedBaseIdentifier =
|
|
32
|
+
overrides.resolvedBaseIdentifier ?? identifiers[0];
|
|
33
|
+
|
|
28
34
|
return {
|
|
29
|
-
identifiers
|
|
35
|
+
identifiers,
|
|
30
36
|
subscripts: [],
|
|
31
37
|
isCompound: false,
|
|
32
38
|
cnextOp: "<-",
|
|
@@ -44,6 +50,8 @@ function createMockContext(
|
|
|
44
50
|
isSimpleIdentifier: false,
|
|
45
51
|
isSimpleThisAccess: false,
|
|
46
52
|
isSimpleGlobalAccess: false,
|
|
53
|
+
resolvedTarget,
|
|
54
|
+
resolvedBaseIdentifier,
|
|
47
55
|
...overrides,
|
|
48
56
|
} as IAssignmentContext;
|
|
49
57
|
}
|
package/src/transpiler/output/codegen/assignment/handlers/__tests__/RegisterHandlers.test.ts
CHANGED
|
@@ -16,8 +16,15 @@ import HandlerTestUtils from "./handlerTestUtils";
|
|
|
16
16
|
function createMockContext(
|
|
17
17
|
overrides: Partial<IAssignmentContext> = {},
|
|
18
18
|
): IAssignmentContext {
|
|
19
|
+
// Default resolved values based on first identifier
|
|
20
|
+
const identifiers = overrides.identifiers ?? ["GPIO7", "DR_SET"];
|
|
21
|
+
const resolvedTarget =
|
|
22
|
+
overrides.resolvedTarget ?? `${identifiers.join("_")}[LED_BIT]`;
|
|
23
|
+
const resolvedBaseIdentifier =
|
|
24
|
+
overrides.resolvedBaseIdentifier ?? identifiers[0];
|
|
25
|
+
|
|
19
26
|
return {
|
|
20
|
-
identifiers
|
|
27
|
+
identifiers,
|
|
21
28
|
subscripts: [{ mockValue: "LED_BIT" } as never],
|
|
22
29
|
isCompound: false,
|
|
23
30
|
cnextOp: "<-",
|
|
@@ -34,6 +41,8 @@ function createMockContext(
|
|
|
34
41
|
isSimpleIdentifier: false,
|
|
35
42
|
isSimpleThisAccess: false,
|
|
36
43
|
isSimpleGlobalAccess: false,
|
|
44
|
+
resolvedTarget,
|
|
45
|
+
resolvedBaseIdentifier,
|
|
37
46
|
...overrides,
|
|
38
47
|
} as IAssignmentContext;
|
|
39
48
|
}
|
|
@@ -16,8 +16,14 @@ import HandlerTestUtils from "./handlerTestUtils";
|
|
|
16
16
|
function createMockContext(
|
|
17
17
|
overrides: Partial<IAssignmentContext> = {},
|
|
18
18
|
): IAssignmentContext {
|
|
19
|
+
// Default resolved values based on first identifier
|
|
20
|
+
const identifiers = overrides.identifiers ?? ["counter"];
|
|
21
|
+
const resolvedTarget = overrides.resolvedTarget ?? identifiers[0];
|
|
22
|
+
const resolvedBaseIdentifier =
|
|
23
|
+
overrides.resolvedBaseIdentifier ?? identifiers[0];
|
|
24
|
+
|
|
19
25
|
return {
|
|
20
|
-
identifiers
|
|
26
|
+
identifiers,
|
|
21
27
|
subscripts: [],
|
|
22
28
|
isCompound: true,
|
|
23
29
|
cnextOp: "+<-",
|
|
@@ -34,6 +40,8 @@ function createMockContext(
|
|
|
34
40
|
isSimpleIdentifier: true,
|
|
35
41
|
isSimpleThisAccess: false,
|
|
36
42
|
isSimpleGlobalAccess: false,
|
|
43
|
+
resolvedTarget,
|
|
44
|
+
resolvedBaseIdentifier,
|
|
37
45
|
...overrides,
|
|
38
46
|
} as IAssignmentContext;
|
|
39
47
|
}
|
|
@@ -16,14 +16,22 @@ import HandlerTestUtils from "./handlerTestUtils";
|
|
|
16
16
|
function createMockContext(
|
|
17
17
|
overrides: Partial<IAssignmentContext> = {},
|
|
18
18
|
): IAssignmentContext {
|
|
19
|
+
// Default resolved values based on first identifier
|
|
20
|
+
const identifiers = overrides.identifiers ?? ["testVar"];
|
|
21
|
+
const resolvedTarget = overrides.resolvedTarget ?? identifiers[0];
|
|
22
|
+
const resolvedBaseIdentifier =
|
|
23
|
+
overrides.resolvedBaseIdentifier ?? identifiers[0];
|
|
24
|
+
|
|
19
25
|
return {
|
|
20
|
-
identifiers
|
|
26
|
+
identifiers,
|
|
21
27
|
subscripts: [],
|
|
22
28
|
isCompound: false,
|
|
23
29
|
cnextOp: "<-",
|
|
24
30
|
cOp: "=",
|
|
25
31
|
generatedValue: '"hello"',
|
|
26
32
|
targetCtx: {} as never,
|
|
33
|
+
resolvedTarget,
|
|
34
|
+
resolvedBaseIdentifier,
|
|
27
35
|
...overrides,
|
|
28
36
|
} as IAssignmentContext;
|
|
29
37
|
}
|
|
@@ -1441,16 +1441,33 @@ const generateMemberAccess = (
|
|
|
1441
1441
|
state: IGeneratorState,
|
|
1442
1442
|
orchestrator: IOrchestrator,
|
|
1443
1443
|
effects: TGeneratorEffect[],
|
|
1444
|
-
): MemberAccessResult =>
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1444
|
+
): MemberAccessResult => {
|
|
1445
|
+
// Check for enum shadowing before dispatch - catches case where identifier
|
|
1446
|
+
// was resolved to a scope member that shadows a global enum
|
|
1447
|
+
MemberAccessValidator.validateGlobalEntityAccess(
|
|
1448
|
+
ctx.result,
|
|
1449
|
+
ctx.memberName,
|
|
1450
|
+
"enum",
|
|
1451
|
+
state.currentScope,
|
|
1452
|
+
ctx.isGlobalAccess,
|
|
1453
|
+
{
|
|
1454
|
+
rootIdentifier: ctx.rootIdentifier,
|
|
1455
|
+
knownEnums: input.symbols!.knownEnums,
|
|
1456
|
+
},
|
|
1457
|
+
);
|
|
1458
|
+
|
|
1459
|
+
return (
|
|
1460
|
+
tryBitmapFieldAccess(ctx, input, effects) ??
|
|
1461
|
+
tryScopeMemberAccess(ctx, input, state, orchestrator) ??
|
|
1462
|
+
tryKnownScopeAccess(ctx, input, state, orchestrator) ??
|
|
1463
|
+
tryEnumMemberAccess(ctx, input, state, orchestrator) ??
|
|
1464
|
+
tryRegisterMemberAccess(ctx, input, state) ??
|
|
1465
|
+
tryStructParamAccess(ctx, orchestrator) ??
|
|
1466
|
+
tryRegisterBitmapAccess(ctx, input, effects) ??
|
|
1467
|
+
tryStructBitmapAccess(ctx, input, effects) ??
|
|
1468
|
+
generateDefaultAccess(ctx, orchestrator)
|
|
1469
|
+
);
|
|
1470
|
+
};
|
|
1454
1471
|
|
|
1455
1472
|
// ========================================================================
|
|
1456
1473
|
// Member Access Handlers
|
|
@@ -1575,12 +1592,15 @@ const tryEnumMemberAccess = (
|
|
|
1575
1592
|
return null;
|
|
1576
1593
|
}
|
|
1577
1594
|
|
|
1595
|
+
// Shadowing check already done in generateMemberAccess; this catches
|
|
1596
|
+
// direct conflicts where ctx.result is the enum name (no resolution happened)
|
|
1578
1597
|
MemberAccessValidator.validateGlobalEntityAccess(
|
|
1579
1598
|
ctx.result,
|
|
1580
1599
|
ctx.memberName,
|
|
1581
1600
|
"enum",
|
|
1582
1601
|
state.currentScope,
|
|
1583
1602
|
ctx.isGlobalAccess,
|
|
1603
|
+
{ scopeMembers: state.scopeMembers },
|
|
1584
1604
|
);
|
|
1585
1605
|
|
|
1586
1606
|
const output = initializeMemberOutput(ctx);
|
|
@@ -1606,6 +1626,7 @@ const tryRegisterMemberAccess = (
|
|
|
1606
1626
|
"register",
|
|
1607
1627
|
state.currentScope,
|
|
1608
1628
|
ctx.isGlobalAccess,
|
|
1629
|
+
{ scopeMembers: state.scopeMembers },
|
|
1609
1630
|
);
|
|
1610
1631
|
|
|
1611
1632
|
MemberAccessValidator.validateRegisterReadAccess(
|
|
@@ -1318,7 +1318,7 @@ describe("PostfixExpressionGenerator", () => {
|
|
|
1318
1318
|
expect(result.code).toBe("Color_Red");
|
|
1319
1319
|
});
|
|
1320
1320
|
|
|
1321
|
-
it("throws when accessing enum
|
|
1321
|
+
it("throws when accessing enum with naming conflict inside scope", () => {
|
|
1322
1322
|
const symbols = createMockSymbols({
|
|
1323
1323
|
knownEnums: new Set(["Color"]),
|
|
1324
1324
|
});
|
|
@@ -1326,7 +1326,8 @@ describe("PostfixExpressionGenerator", () => {
|
|
|
1326
1326
|
createMockPostfixOp({ identifier: "Red" }),
|
|
1327
1327
|
]);
|
|
1328
1328
|
const input = createMockInput({ symbols });
|
|
1329
|
-
const
|
|
1329
|
+
const scopeMembers = new Map([["Motor", new Set(["Color"])]]);
|
|
1330
|
+
const state = createMockState({ currentScope: "Motor", scopeMembers });
|
|
1330
1331
|
const orchestrator = createMockOrchestrator({
|
|
1331
1332
|
generatePrimaryExpr: () => "Color",
|
|
1332
1333
|
getScopeSeparator: () => "_",
|
|
@@ -1336,6 +1337,48 @@ describe("PostfixExpressionGenerator", () => {
|
|
|
1336
1337
|
generatePostfixExpression(ctx, input, state, orchestrator),
|
|
1337
1338
|
).toThrow("Use 'global.Color.Red' to access enum 'Color'");
|
|
1338
1339
|
});
|
|
1340
|
+
|
|
1341
|
+
it("allows enum access without global prefix when no naming conflict", () => {
|
|
1342
|
+
const symbols = createMockSymbols({
|
|
1343
|
+
knownEnums: new Set(["Color"]),
|
|
1344
|
+
});
|
|
1345
|
+
const ctx = createMockPostfixExpressionContext("Color", [
|
|
1346
|
+
createMockPostfixOp({ identifier: "Red" }),
|
|
1347
|
+
]);
|
|
1348
|
+
const input = createMockInput({ symbols });
|
|
1349
|
+
const scopeMembers = new Map([["Motor", new Set(["speed"])]]);
|
|
1350
|
+
const state = createMockState({ currentScope: "Motor", scopeMembers });
|
|
1351
|
+
const orchestrator = createMockOrchestrator({
|
|
1352
|
+
generatePrimaryExpr: () => "Color",
|
|
1353
|
+
getScopeSeparator: () => "_",
|
|
1354
|
+
});
|
|
1355
|
+
|
|
1356
|
+
const result = generatePostfixExpression(ctx, input, state, orchestrator);
|
|
1357
|
+
expect(result.code).toBe("Color_Red");
|
|
1358
|
+
});
|
|
1359
|
+
|
|
1360
|
+
it("throws when scope member shadows global enum (resolved identifier differs)", () => {
|
|
1361
|
+
const symbols = createMockSymbols({
|
|
1362
|
+
knownEnums: new Set(["Color"]),
|
|
1363
|
+
});
|
|
1364
|
+
const ctx = createMockPostfixExpressionContext("Color", [
|
|
1365
|
+
createMockPostfixOp({ identifier: "Red" }),
|
|
1366
|
+
]);
|
|
1367
|
+
const input = createMockInput({ symbols });
|
|
1368
|
+
const scopeMembers = new Map([["Motor", new Set(["Color"])]]);
|
|
1369
|
+
const state = createMockState({ currentScope: "Motor", scopeMembers });
|
|
1370
|
+
const orchestrator = createMockOrchestrator({
|
|
1371
|
+
// Simulates identifier resolution: Color -> Motor_Color (scope member)
|
|
1372
|
+
generatePrimaryExpr: () => "Motor_Color",
|
|
1373
|
+
getScopeSeparator: () => "_",
|
|
1374
|
+
});
|
|
1375
|
+
|
|
1376
|
+
expect(() =>
|
|
1377
|
+
generatePostfixExpression(ctx, input, state, orchestrator),
|
|
1378
|
+
).toThrow(
|
|
1379
|
+
"Use 'global.Color.Red' to access enum 'Color' from inside scope 'Motor' (scope member 'Color' shadows the global enum)",
|
|
1380
|
+
);
|
|
1381
|
+
});
|
|
1339
1382
|
});
|
|
1340
1383
|
|
|
1341
1384
|
describe("register member access", () => {
|
|
@@ -1375,7 +1418,7 @@ describe("PostfixExpressionGenerator", () => {
|
|
|
1375
1418
|
).toThrow("cannot read from write-only register member 'DATA'");
|
|
1376
1419
|
});
|
|
1377
1420
|
|
|
1378
|
-
it("throws when accessing register
|
|
1421
|
+
it("throws when accessing register with naming conflict inside scope", () => {
|
|
1379
1422
|
const symbols = createMockSymbols({
|
|
1380
1423
|
knownRegisters: new Set(["GPIO"]),
|
|
1381
1424
|
});
|
|
@@ -1383,7 +1426,8 @@ describe("PostfixExpressionGenerator", () => {
|
|
|
1383
1426
|
createMockPostfixOp({ identifier: "PIN0" }),
|
|
1384
1427
|
]);
|
|
1385
1428
|
const input = createMockInput({ symbols });
|
|
1386
|
-
const
|
|
1429
|
+
const scopeMembers = new Map([["Motor", new Set(["GPIO"])]]);
|
|
1430
|
+
const state = createMockState({ currentScope: "Motor", scopeMembers });
|
|
1387
1431
|
const orchestrator = createMockOrchestrator({
|
|
1388
1432
|
generatePrimaryExpr: () => "GPIO",
|
|
1389
1433
|
});
|
|
@@ -1392,6 +1436,24 @@ describe("PostfixExpressionGenerator", () => {
|
|
|
1392
1436
|
generatePostfixExpression(ctx, input, state, orchestrator),
|
|
1393
1437
|
).toThrow("Use 'global.GPIO.PIN0' to access register 'GPIO'");
|
|
1394
1438
|
});
|
|
1439
|
+
|
|
1440
|
+
it("allows register access without global prefix when no naming conflict", () => {
|
|
1441
|
+
const symbols = createMockSymbols({
|
|
1442
|
+
knownRegisters: new Set(["GPIO"]),
|
|
1443
|
+
});
|
|
1444
|
+
const ctx = createMockPostfixExpressionContext("GPIO", [
|
|
1445
|
+
createMockPostfixOp({ identifier: "PIN0" }),
|
|
1446
|
+
]);
|
|
1447
|
+
const input = createMockInput({ symbols });
|
|
1448
|
+
const scopeMembers = new Map([["Motor", new Set(["speed"])]]);
|
|
1449
|
+
const state = createMockState({ currentScope: "Motor", scopeMembers });
|
|
1450
|
+
const orchestrator = createMockOrchestrator({
|
|
1451
|
+
generatePrimaryExpr: () => "GPIO",
|
|
1452
|
+
});
|
|
1453
|
+
|
|
1454
|
+
const result = generatePostfixExpression(ctx, input, state, orchestrator);
|
|
1455
|
+
expect(result.code).toBe("GPIO_PIN0");
|
|
1456
|
+
});
|
|
1395
1457
|
});
|
|
1396
1458
|
|
|
1397
1459
|
describe("struct parameter access", () => {
|
|
@@ -55,13 +55,22 @@ class MemberAccessValidator {
|
|
|
55
55
|
|
|
56
56
|
/**
|
|
57
57
|
* ADR-016/057: Validate that a global entity (enum or register) is accessed
|
|
58
|
-
* with 'global.' prefix when inside a scope that
|
|
58
|
+
* with 'global.' prefix when inside a scope that has a naming conflict.
|
|
59
59
|
*
|
|
60
|
-
*
|
|
60
|
+
* Detects two types of conflicts:
|
|
61
|
+
* 1. Scope member with same name as entity (via scopeMembers lookup)
|
|
62
|
+
* 2. Identifier was resolved to scope member, shadowing a global enum
|
|
63
|
+
* (via rootIdentifier != resolvedName comparison)
|
|
64
|
+
*
|
|
65
|
+
* @param entityName - The resolved entity name (e.g., "Color" or "Motor_Color")
|
|
61
66
|
* @param memberName - The member after the entity (e.g., "Red", "PIN0")
|
|
62
67
|
* @param entityType - "enum" or "register" (for error message)
|
|
63
68
|
* @param currentScope - Active scope context (null = not in a scope)
|
|
64
69
|
* @param isGlobalAccess - Whether the access used 'global.' prefix
|
|
70
|
+
* @param options - Optional conflict detection parameters
|
|
71
|
+
* @param options.scopeMembers - Map of scope names to their member names
|
|
72
|
+
* @param options.rootIdentifier - Original identifier before resolution (for shadowing detection)
|
|
73
|
+
* @param options.knownEnums - Set of known enum names (for shadowing detection)
|
|
65
74
|
*/
|
|
66
75
|
static validateGlobalEntityAccess(
|
|
67
76
|
entityName: string,
|
|
@@ -69,18 +78,50 @@ class MemberAccessValidator {
|
|
|
69
78
|
entityType: string,
|
|
70
79
|
currentScope: string | null,
|
|
71
80
|
isGlobalAccess: boolean,
|
|
81
|
+
options?: {
|
|
82
|
+
scopeMembers?: ReadonlyMap<string, ReadonlySet<string>>;
|
|
83
|
+
rootIdentifier?: string;
|
|
84
|
+
knownEnums?: ReadonlySet<string>;
|
|
85
|
+
},
|
|
72
86
|
): void {
|
|
73
87
|
if (isGlobalAccess) {
|
|
74
88
|
return;
|
|
75
89
|
}
|
|
76
|
-
if (currentScope) {
|
|
77
|
-
|
|
78
|
-
|
|
90
|
+
if (!currentScope) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const { scopeMembers, rootIdentifier, knownEnums } = options ?? {};
|
|
95
|
+
|
|
96
|
+
// Check 1: Shadowing detection - identifier was resolved to a scope member
|
|
97
|
+
// that shadows a global enum. This produces invalid C (e.g., Motor_Color.RED).
|
|
98
|
+
// This check must run BEFORE the belongsToCurrentScope check because
|
|
99
|
+
// the resolved name (e.g., Motor_Color) will start with the scope prefix.
|
|
100
|
+
if (rootIdentifier && knownEnums) {
|
|
101
|
+
const wasResolved = entityName !== rootIdentifier;
|
|
102
|
+
const rootIsEnum = knownEnums.has(rootIdentifier);
|
|
103
|
+
const resolvedIsNotEnum = !knownEnums.has(entityName);
|
|
104
|
+
if (wasResolved && rootIsEnum && resolvedIsNotEnum) {
|
|
79
105
|
throw new Error(
|
|
80
|
-
`Error: Use 'global.${
|
|
106
|
+
`Error: Use 'global.${rootIdentifier}.${memberName}' to access enum '${rootIdentifier}' from inside scope '${currentScope}' (scope member '${rootIdentifier}' shadows the global enum)`,
|
|
81
107
|
);
|
|
82
108
|
}
|
|
83
109
|
}
|
|
110
|
+
|
|
111
|
+
// Skip check if entity belongs to current scope (e.g., Motor_State in scope Motor)
|
|
112
|
+
const belongsToCurrentScope = entityName.startsWith(currentScope + "_");
|
|
113
|
+
if (belongsToCurrentScope) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Check 2: Direct conflict - scope has a member with the same name as the entity
|
|
118
|
+
const scopeMemberNames = scopeMembers?.get(currentScope);
|
|
119
|
+
const hasConflict = scopeMemberNames?.has(entityName) ?? false;
|
|
120
|
+
if (hasConflict) {
|
|
121
|
+
throw new Error(
|
|
122
|
+
`Error: Use 'global.${entityName}.${memberName}' to access ${entityType} '${entityName}' from inside scope '${currentScope}'`,
|
|
123
|
+
);
|
|
124
|
+
}
|
|
84
125
|
}
|
|
85
126
|
}
|
|
86
127
|
|
|
@@ -89,7 +89,12 @@ class MemberSeparatorResolver {
|
|
|
89
89
|
// Scope member access: Sensor.buffer -> Sensor_buffer
|
|
90
90
|
// Works with or without global. prefix (both are valid syntax)
|
|
91
91
|
if (deps.isKnownScope(identifierChain[0])) {
|
|
92
|
-
|
|
92
|
+
// Issue #779: Skip cross-scope validation for scoped register access
|
|
93
|
+
// Board.GPIO where Board_GPIO is a known register is valid
|
|
94
|
+
const scopedRegisterName = `${identifierChain[0]}_${memberName}`;
|
|
95
|
+
if (!deps.isKnownRegister(scopedRegisterName)) {
|
|
96
|
+
deps.validateCrossScopeVisibility(identifierChain[0], memberName);
|
|
97
|
+
}
|
|
93
98
|
return "_";
|
|
94
99
|
}
|
|
95
100
|
|