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
|
@@ -118,7 +118,8 @@ describe("MemberAccessValidator", () => {
|
|
|
118
118
|
});
|
|
119
119
|
|
|
120
120
|
describe("validateGlobalEntityAccess", () => {
|
|
121
|
-
it("throws when in scope and entity
|
|
121
|
+
it("throws when in scope and entity name conflicts with scope member", () => {
|
|
122
|
+
const scopeMembers = new Map([["Motor", new Set(["Color", "speed"])]]);
|
|
122
123
|
expect(() =>
|
|
123
124
|
MemberAccessValidator.validateGlobalEntityAccess(
|
|
124
125
|
"Color",
|
|
@@ -126,13 +127,29 @@ describe("MemberAccessValidator", () => {
|
|
|
126
127
|
"enum",
|
|
127
128
|
"Motor",
|
|
128
129
|
false,
|
|
130
|
+
{ scopeMembers },
|
|
129
131
|
),
|
|
130
132
|
).toThrow(
|
|
131
133
|
"Use 'global.Color.Red' to access enum 'Color' from inside scope 'Motor'",
|
|
132
134
|
);
|
|
133
135
|
});
|
|
134
136
|
|
|
135
|
-
it("does NOT throw when
|
|
137
|
+
it("does NOT throw when in scope but no naming conflict", () => {
|
|
138
|
+
const scopeMembers = new Map([["Motor", new Set(["speed"])]]);
|
|
139
|
+
expect(() =>
|
|
140
|
+
MemberAccessValidator.validateGlobalEntityAccess(
|
|
141
|
+
"Color",
|
|
142
|
+
"Red",
|
|
143
|
+
"enum",
|
|
144
|
+
"Motor",
|
|
145
|
+
false,
|
|
146
|
+
{ scopeMembers },
|
|
147
|
+
),
|
|
148
|
+
).not.toThrow();
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it("does NOT throw when isGlobalAccess is true even with conflict", () => {
|
|
152
|
+
const scopeMembers = new Map([["Motor", new Set(["Color"])]]);
|
|
136
153
|
expect(() =>
|
|
137
154
|
MemberAccessValidator.validateGlobalEntityAccess(
|
|
138
155
|
"Color",
|
|
@@ -140,6 +157,7 @@ describe("MemberAccessValidator", () => {
|
|
|
140
157
|
"enum",
|
|
141
158
|
"Motor",
|
|
142
159
|
true,
|
|
160
|
+
{ scopeMembers },
|
|
143
161
|
),
|
|
144
162
|
).not.toThrow();
|
|
145
163
|
});
|
|
@@ -168,7 +186,8 @@ describe("MemberAccessValidator", () => {
|
|
|
168
186
|
).not.toThrow();
|
|
169
187
|
});
|
|
170
188
|
|
|
171
|
-
it("
|
|
189
|
+
it("throws for register with naming conflict", () => {
|
|
190
|
+
const scopeMembers = new Map([["Motor", new Set(["GPIO"])]]);
|
|
172
191
|
expect(() =>
|
|
173
192
|
MemberAccessValidator.validateGlobalEntityAccess(
|
|
174
193
|
"GPIO",
|
|
@@ -176,10 +195,97 @@ describe("MemberAccessValidator", () => {
|
|
|
176
195
|
"register",
|
|
177
196
|
"Motor",
|
|
178
197
|
false,
|
|
198
|
+
{ scopeMembers },
|
|
179
199
|
),
|
|
180
200
|
).toThrow(
|
|
181
201
|
"Use 'global.GPIO.PIN0' to access register 'GPIO' from inside scope 'Motor'",
|
|
182
202
|
);
|
|
183
203
|
});
|
|
204
|
+
|
|
205
|
+
it("does NOT throw for register without naming conflict", () => {
|
|
206
|
+
const scopeMembers = new Map([["Motor", new Set(["speed"])]]);
|
|
207
|
+
expect(() =>
|
|
208
|
+
MemberAccessValidator.validateGlobalEntityAccess(
|
|
209
|
+
"GPIO",
|
|
210
|
+
"PIN0",
|
|
211
|
+
"register",
|
|
212
|
+
"Motor",
|
|
213
|
+
false,
|
|
214
|
+
{ scopeMembers },
|
|
215
|
+
),
|
|
216
|
+
).not.toThrow();
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it("does NOT throw when options is undefined", () => {
|
|
220
|
+
expect(() =>
|
|
221
|
+
MemberAccessValidator.validateGlobalEntityAccess(
|
|
222
|
+
"Color",
|
|
223
|
+
"Red",
|
|
224
|
+
"enum",
|
|
225
|
+
"Motor",
|
|
226
|
+
false,
|
|
227
|
+
undefined,
|
|
228
|
+
),
|
|
229
|
+
).not.toThrow();
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
// Shadowing detection tests (using rootIdentifier and knownEnums)
|
|
233
|
+
it("throws when scope member shadows global enum (resolved identifier differs)", () => {
|
|
234
|
+
const knownEnums = new Set(["Color"]);
|
|
235
|
+
expect(() =>
|
|
236
|
+
MemberAccessValidator.validateGlobalEntityAccess(
|
|
237
|
+
"Motor_Color", // resolved to scope member
|
|
238
|
+
"Red",
|
|
239
|
+
"enum",
|
|
240
|
+
"Motor",
|
|
241
|
+
false,
|
|
242
|
+
{ rootIdentifier: "Color", knownEnums },
|
|
243
|
+
),
|
|
244
|
+
).toThrow(
|
|
245
|
+
"Use 'global.Color.Red' to access enum 'Color' from inside scope 'Motor' (scope member 'Color' shadows the global enum)",
|
|
246
|
+
);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it("does NOT throw for shadowing when isGlobalAccess is true", () => {
|
|
250
|
+
const knownEnums = new Set(["Color"]);
|
|
251
|
+
expect(() =>
|
|
252
|
+
MemberAccessValidator.validateGlobalEntityAccess(
|
|
253
|
+
"Motor_Color",
|
|
254
|
+
"Red",
|
|
255
|
+
"enum",
|
|
256
|
+
"Motor",
|
|
257
|
+
true,
|
|
258
|
+
{ rootIdentifier: "Color", knownEnums },
|
|
259
|
+
),
|
|
260
|
+
).not.toThrow();
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it("does NOT throw when root is not a known enum", () => {
|
|
264
|
+
const knownEnums = new Set(["OtherEnum"]);
|
|
265
|
+
expect(() =>
|
|
266
|
+
MemberAccessValidator.validateGlobalEntityAccess(
|
|
267
|
+
"Motor_Color",
|
|
268
|
+
"Red",
|
|
269
|
+
"enum",
|
|
270
|
+
"Motor",
|
|
271
|
+
false,
|
|
272
|
+
{ rootIdentifier: "Color", knownEnums },
|
|
273
|
+
),
|
|
274
|
+
).not.toThrow();
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
it("does NOT throw when resolved name IS a known enum", () => {
|
|
278
|
+
const knownEnums = new Set(["Color", "Motor_Color"]);
|
|
279
|
+
expect(() =>
|
|
280
|
+
MemberAccessValidator.validateGlobalEntityAccess(
|
|
281
|
+
"Motor_Color",
|
|
282
|
+
"Red",
|
|
283
|
+
"enum",
|
|
284
|
+
"Motor",
|
|
285
|
+
false,
|
|
286
|
+
{ rootIdentifier: "Color", knownEnums },
|
|
287
|
+
),
|
|
288
|
+
).not.toThrow();
|
|
289
|
+
});
|
|
184
290
|
});
|
|
185
291
|
});
|
|
@@ -90,6 +90,13 @@ export default class CodeGenState {
|
|
|
90
90
|
*/
|
|
91
91
|
static symbolTable: SymbolTable = new SymbolTable();
|
|
92
92
|
|
|
93
|
+
/**
|
|
94
|
+
* External struct fields from C/C++ headers for initialization analysis.
|
|
95
|
+
* Maps struct name -> Set of non-array field names.
|
|
96
|
+
* Persists across per-file reset() calls, cleared at start of run.
|
|
97
|
+
*/
|
|
98
|
+
static externalStructFields: Map<string, Set<string>> = new Map();
|
|
99
|
+
|
|
93
100
|
// ===========================================================================
|
|
94
101
|
// TYPE TRACKING
|
|
95
102
|
// ===========================================================================
|
|
@@ -180,7 +187,7 @@ export default class CodeGenState {
|
|
|
180
187
|
static localArrays: Set<string> = new Set();
|
|
181
188
|
|
|
182
189
|
/** Scope member names: scope -> Set of member names */
|
|
183
|
-
static scopeMembers: Map<string, Set<string>> = new Map();
|
|
190
|
+
private static scopeMembers: Map<string, Set<string>> = new Map();
|
|
184
191
|
|
|
185
192
|
/** Float bit indexing: declared shadow variables */
|
|
186
193
|
static floatBitShadows: Set<string> = new Set();
|
|
@@ -456,6 +463,26 @@ export default class CodeGenState {
|
|
|
456
463
|
return this.modifiedParameters.get(funcName)?.has(paramName) ?? false;
|
|
457
464
|
}
|
|
458
465
|
|
|
466
|
+
/**
|
|
467
|
+
* Compute unmodified parameters for all functions on-demand.
|
|
468
|
+
* Returns a map of function name -> Set of parameter names NOT modified.
|
|
469
|
+
* Computed from functionSignatures and modifiedParameters (no cached state).
|
|
470
|
+
*/
|
|
471
|
+
static getUnmodifiedParameters(): Map<string, Set<string>> {
|
|
472
|
+
const result = new Map<string, Set<string>>();
|
|
473
|
+
for (const [funcName, signature] of this.functionSignatures) {
|
|
474
|
+
const modifiedSet = this.modifiedParameters.get(funcName);
|
|
475
|
+
const unmodified = new Set<string>();
|
|
476
|
+
for (const param of signature.parameters) {
|
|
477
|
+
if (!modifiedSet?.has(param.name)) {
|
|
478
|
+
unmodified.add(param.name);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
result.set(funcName, unmodified);
|
|
482
|
+
}
|
|
483
|
+
return result;
|
|
484
|
+
}
|
|
485
|
+
|
|
459
486
|
/**
|
|
460
487
|
* Check if a parameter should pass by value.
|
|
461
488
|
*/
|
|
@@ -535,6 +562,20 @@ export default class CodeGenState {
|
|
|
535
562
|
return this.scopeMembers.get(scopeName);
|
|
536
563
|
}
|
|
537
564
|
|
|
565
|
+
/**
|
|
566
|
+
* Set members of a scope.
|
|
567
|
+
*/
|
|
568
|
+
static setScopeMembers(scopeName: string, members: Set<string>): void {
|
|
569
|
+
this.scopeMembers.set(scopeName, members);
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
/**
|
|
573
|
+
* Get all scope members (for IGeneratorState).
|
|
574
|
+
*/
|
|
575
|
+
static getAllScopeMembers(): ReadonlyMap<string, ReadonlySet<string>> {
|
|
576
|
+
return this.scopeMembers;
|
|
577
|
+
}
|
|
578
|
+
|
|
538
579
|
/**
|
|
539
580
|
* Check if an identifier is a member of the current scope.
|
|
540
581
|
*/
|
|
@@ -650,6 +691,39 @@ export default class CodeGenState {
|
|
|
650
691
|
return this.symbols?.enumMembers.get(enumName);
|
|
651
692
|
}
|
|
652
693
|
|
|
694
|
+
/**
|
|
695
|
+
* Build external struct fields from the symbol table.
|
|
696
|
+
* Called once per run after all headers are processed.
|
|
697
|
+
* Issue #355: Excludes array fields from init checking.
|
|
698
|
+
*/
|
|
699
|
+
static buildExternalStructFields(): void {
|
|
700
|
+
this.externalStructFields.clear();
|
|
701
|
+
const allStructFields = this.symbolTable.getAllStructFields();
|
|
702
|
+
|
|
703
|
+
for (const [structName, fieldMap] of allStructFields) {
|
|
704
|
+
const nonArrayFields = new Set<string>();
|
|
705
|
+
for (const [fieldName, fieldInfo] of fieldMap) {
|
|
706
|
+
// Only include non-array fields in init checking
|
|
707
|
+
if (
|
|
708
|
+
!fieldInfo.arrayDimensions ||
|
|
709
|
+
fieldInfo.arrayDimensions.length === 0
|
|
710
|
+
) {
|
|
711
|
+
nonArrayFields.add(fieldName);
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
if (nonArrayFields.size > 0) {
|
|
715
|
+
this.externalStructFields.set(structName, nonArrayFields);
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
/**
|
|
721
|
+
* Get external struct fields for initialization analysis.
|
|
722
|
+
*/
|
|
723
|
+
static getExternalStructFields(): Map<string, Set<string>> {
|
|
724
|
+
return this.externalStructFields;
|
|
725
|
+
}
|
|
726
|
+
|
|
653
727
|
/**
|
|
654
728
|
* Get function return type.
|
|
655
729
|
*/
|
|
@@ -110,7 +110,7 @@ describe("CodeGenState", () => {
|
|
|
110
110
|
|
|
111
111
|
it("getScopeMembers returns members for known scope", () => {
|
|
112
112
|
const members = new Set(["member1", "member2"]);
|
|
113
|
-
CodeGenState.
|
|
113
|
+
CodeGenState.setScopeMembers("TestScope", members);
|
|
114
114
|
|
|
115
115
|
expect(CodeGenState.getScopeMembers("TestScope")).toBe(members);
|
|
116
116
|
});
|
|
@@ -122,14 +122,14 @@ describe("CodeGenState", () => {
|
|
|
122
122
|
|
|
123
123
|
it("isCurrentScopeMember returns false for non-member", () => {
|
|
124
124
|
CodeGenState.currentScope = "TestScope";
|
|
125
|
-
CodeGenState.
|
|
125
|
+
CodeGenState.setScopeMembers("TestScope", new Set(["member1"]));
|
|
126
126
|
|
|
127
127
|
expect(CodeGenState.isCurrentScopeMember("nonMember")).toBe(false);
|
|
128
128
|
});
|
|
129
129
|
|
|
130
130
|
it("isCurrentScopeMember returns true for member", () => {
|
|
131
131
|
CodeGenState.currentScope = "TestScope";
|
|
132
|
-
CodeGenState.
|
|
132
|
+
CodeGenState.setScopeMembers("TestScope", new Set(["member1"]));
|
|
133
133
|
|
|
134
134
|
expect(CodeGenState.isCurrentScopeMember("member1")).toBe(true);
|
|
135
135
|
});
|
|
@@ -143,14 +143,14 @@ describe("CodeGenState", () => {
|
|
|
143
143
|
|
|
144
144
|
it("returns identifier unchanged when not a scope member", () => {
|
|
145
145
|
CodeGenState.currentScope = "TestScope";
|
|
146
|
-
CodeGenState.
|
|
146
|
+
CodeGenState.setScopeMembers("TestScope", new Set(["member1"]));
|
|
147
147
|
|
|
148
148
|
expect(CodeGenState.resolveIdentifier("varName")).toBe("varName");
|
|
149
149
|
});
|
|
150
150
|
|
|
151
151
|
it("returns scoped name for scope member", () => {
|
|
152
152
|
CodeGenState.currentScope = "TestScope";
|
|
153
|
-
CodeGenState.
|
|
153
|
+
CodeGenState.setScopeMembers("TestScope", new Set(["member1"]));
|
|
154
154
|
|
|
155
155
|
expect(CodeGenState.resolveIdentifier("member1")).toBe(
|
|
156
156
|
"TestScope_member1",
|
|
@@ -453,4 +453,123 @@ describe("CodeGenState", () => {
|
|
|
453
453
|
expect(CodeGenState.isLocalArray("myArr")).toBe(true);
|
|
454
454
|
});
|
|
455
455
|
});
|
|
456
|
+
|
|
457
|
+
describe("buildExternalStructFields", () => {
|
|
458
|
+
it("returns empty map when no struct fields exist", () => {
|
|
459
|
+
CodeGenState.buildExternalStructFields();
|
|
460
|
+
const result = CodeGenState.getExternalStructFields();
|
|
461
|
+
expect(result.size).toBe(0);
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
it("includes non-array fields in result", () => {
|
|
465
|
+
// Manually add struct fields to the symbol table
|
|
466
|
+
const structFields = new Map<
|
|
467
|
+
string,
|
|
468
|
+
Map<string, { type: string; arrayDimensions?: number[] }>
|
|
469
|
+
>();
|
|
470
|
+
const pointFields = new Map<
|
|
471
|
+
string,
|
|
472
|
+
{ type: string; arrayDimensions?: number[] }
|
|
473
|
+
>();
|
|
474
|
+
pointFields.set("x", { type: "i32" });
|
|
475
|
+
pointFields.set("y", { type: "i32" });
|
|
476
|
+
structFields.set("Point", pointFields);
|
|
477
|
+
|
|
478
|
+
// Use restoreStructFields to populate the symbol table
|
|
479
|
+
CodeGenState.symbolTable.restoreStructFields(structFields);
|
|
480
|
+
|
|
481
|
+
CodeGenState.buildExternalStructFields();
|
|
482
|
+
const result = CodeGenState.getExternalStructFields();
|
|
483
|
+
|
|
484
|
+
expect(result.has("Point")).toBe(true);
|
|
485
|
+
const fields = result.get("Point");
|
|
486
|
+
expect(fields?.has("x")).toBe(true);
|
|
487
|
+
expect(fields?.has("y")).toBe(true);
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
it("excludes array fields from result (Issue #355)", () => {
|
|
491
|
+
const structFields = new Map<
|
|
492
|
+
string,
|
|
493
|
+
Map<string, { type: string; arrayDimensions?: number[] }>
|
|
494
|
+
>();
|
|
495
|
+
const bufferFields = new Map<
|
|
496
|
+
string,
|
|
497
|
+
{ type: string; arrayDimensions?: number[] }
|
|
498
|
+
>();
|
|
499
|
+
bufferFields.set("size", { type: "u32" }); // Non-array
|
|
500
|
+
bufferFields.set("data", { type: "u8", arrayDimensions: [256] }); // Array
|
|
501
|
+
|
|
502
|
+
structFields.set("Buffer", bufferFields);
|
|
503
|
+
CodeGenState.symbolTable.restoreStructFields(structFields);
|
|
504
|
+
|
|
505
|
+
CodeGenState.buildExternalStructFields();
|
|
506
|
+
const result = CodeGenState.getExternalStructFields();
|
|
507
|
+
|
|
508
|
+
expect(result.has("Buffer")).toBe(true);
|
|
509
|
+
const fields = result.get("Buffer");
|
|
510
|
+
expect(fields?.has("size")).toBe(true); // Non-array included
|
|
511
|
+
expect(fields?.has("data")).toBe(false); // Array excluded
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
it("excludes structs with only array fields", () => {
|
|
515
|
+
const structFields = new Map<
|
|
516
|
+
string,
|
|
517
|
+
Map<string, { type: string; arrayDimensions?: number[] }>
|
|
518
|
+
>();
|
|
519
|
+
const arrayOnlyFields = new Map<
|
|
520
|
+
string,
|
|
521
|
+
{ type: string; arrayDimensions?: number[] }
|
|
522
|
+
>();
|
|
523
|
+
arrayOnlyFields.set("items", { type: "u8", arrayDimensions: [10] });
|
|
524
|
+
arrayOnlyFields.set("values", { type: "i32", arrayDimensions: [5] });
|
|
525
|
+
|
|
526
|
+
structFields.set("ArrayOnly", arrayOnlyFields);
|
|
527
|
+
CodeGenState.symbolTable.restoreStructFields(structFields);
|
|
528
|
+
|
|
529
|
+
CodeGenState.buildExternalStructFields();
|
|
530
|
+
const result = CodeGenState.getExternalStructFields();
|
|
531
|
+
|
|
532
|
+
// Struct should not be included since all fields are arrays
|
|
533
|
+
expect(result.has("ArrayOnly")).toBe(false);
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
it("handles mixed structs correctly", () => {
|
|
537
|
+
const structFields = new Map<
|
|
538
|
+
string,
|
|
539
|
+
Map<string, { type: string; arrayDimensions?: number[] }>
|
|
540
|
+
>();
|
|
541
|
+
|
|
542
|
+
// Struct with mixed fields
|
|
543
|
+
const mixedFields = new Map<
|
|
544
|
+
string,
|
|
545
|
+
{ type: string; arrayDimensions?: number[] }
|
|
546
|
+
>();
|
|
547
|
+
mixedFields.set("id", { type: "u32" });
|
|
548
|
+
mixedFields.set("name", { type: "string", arrayDimensions: [32] });
|
|
549
|
+
mixedFields.set("count", { type: "u16" });
|
|
550
|
+
|
|
551
|
+
// Struct with only non-array
|
|
552
|
+
const simpleFields = new Map<
|
|
553
|
+
string,
|
|
554
|
+
{ type: string; arrayDimensions?: number[] }
|
|
555
|
+
>();
|
|
556
|
+
simpleFields.set("value", { type: "f32" });
|
|
557
|
+
|
|
558
|
+
structFields.set("Mixed", mixedFields);
|
|
559
|
+
structFields.set("Simple", simpleFields);
|
|
560
|
+
CodeGenState.symbolTable.restoreStructFields(structFields);
|
|
561
|
+
|
|
562
|
+
CodeGenState.buildExternalStructFields();
|
|
563
|
+
const result = CodeGenState.getExternalStructFields();
|
|
564
|
+
|
|
565
|
+
// Mixed struct should have only non-array fields
|
|
566
|
+
expect(result.get("Mixed")?.size).toBe(2);
|
|
567
|
+
expect(result.get("Mixed")?.has("id")).toBe(true);
|
|
568
|
+
expect(result.get("Mixed")?.has("count")).toBe(true);
|
|
569
|
+
expect(result.get("Mixed")?.has("name")).toBe(false);
|
|
570
|
+
|
|
571
|
+
// Simple struct should have its field
|
|
572
|
+
expect(result.get("Simple")?.has("value")).toBe(true);
|
|
573
|
+
});
|
|
574
|
+
});
|
|
456
575
|
});
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* AnalyzerContextBuilder
|
|
3
|
-
* Issue #591: Extracted from Transpiler.transpileSource() to reduce complexity
|
|
4
|
-
*
|
|
5
|
-
* Builds the context needed for running semantic analyzers, specifically
|
|
6
|
-
* converting struct field information from the symbol table format to
|
|
7
|
-
* the analyzer-compatible format.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import SymbolTable from "../symbols/SymbolTable";
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Builds analyzer context from symbol table data.
|
|
14
|
-
*
|
|
15
|
-
* The analyzer requires struct fields as Map<string, Set<string>> (structName -> fieldNames)
|
|
16
|
-
* while the symbol table stores more detailed IStructFieldInfo. This builder handles
|
|
17
|
-
* the conversion, including Issue #355's requirement to exclude array fields.
|
|
18
|
-
*/
|
|
19
|
-
class AnalyzerContextBuilder {
|
|
20
|
-
/**
|
|
21
|
-
* Build external struct fields map for analyzer from symbol table.
|
|
22
|
-
*
|
|
23
|
-
* Converts the symbol table's detailed struct field info to the simpler
|
|
24
|
-
* format needed by InitializationAnalyzer.
|
|
25
|
-
*
|
|
26
|
-
* Issue #355: Excludes array fields from init checking since the analyzer
|
|
27
|
-
* can't prove loop-based array initialization is complete.
|
|
28
|
-
*
|
|
29
|
-
* @param symbolTable - Symbol table containing struct definitions
|
|
30
|
-
* @returns Map of struct name to set of non-array field names
|
|
31
|
-
*/
|
|
32
|
-
static buildExternalStructFields(
|
|
33
|
-
symbolTable: SymbolTable,
|
|
34
|
-
): Map<string, Set<string>> {
|
|
35
|
-
const externalStructFields = new Map<string, Set<string>>();
|
|
36
|
-
const allStructFields = symbolTable.getAllStructFields();
|
|
37
|
-
|
|
38
|
-
for (const [structName, fieldMap] of allStructFields) {
|
|
39
|
-
const nonArrayFields = new Set<string>();
|
|
40
|
-
for (const [fieldName, fieldInfo] of fieldMap) {
|
|
41
|
-
// Only include non-array fields in init checking
|
|
42
|
-
if (
|
|
43
|
-
!fieldInfo.arrayDimensions ||
|
|
44
|
-
fieldInfo.arrayDimensions.length === 0
|
|
45
|
-
) {
|
|
46
|
-
nonArrayFields.add(fieldName);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
if (nonArrayFields.size > 0) {
|
|
50
|
-
externalStructFields.set(structName, nonArrayFields);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
return externalStructFields;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export default AnalyzerContextBuilder;
|
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Unit tests for AnalyzerContextBuilder
|
|
3
|
-
* Issue #591: Tests for struct field conversion utility
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { describe, it, expect, beforeEach } from "vitest";
|
|
7
|
-
|
|
8
|
-
import AnalyzerContextBuilder from "../AnalyzerContextBuilder";
|
|
9
|
-
import SymbolTable from "../../symbols/SymbolTable";
|
|
10
|
-
|
|
11
|
-
describe("AnalyzerContextBuilder", () => {
|
|
12
|
-
let symbolTable: SymbolTable;
|
|
13
|
-
|
|
14
|
-
beforeEach(() => {
|
|
15
|
-
symbolTable = new SymbolTable();
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
describe("buildExternalStructFields", () => {
|
|
19
|
-
it("returns empty map when no struct fields exist", () => {
|
|
20
|
-
const result =
|
|
21
|
-
AnalyzerContextBuilder.buildExternalStructFields(symbolTable);
|
|
22
|
-
|
|
23
|
-
expect(result.size).toBe(0);
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
it("includes non-array fields in result", () => {
|
|
27
|
-
// Manually add struct fields to the symbol table
|
|
28
|
-
const structFields = new Map<
|
|
29
|
-
string,
|
|
30
|
-
Map<string, { type: string; arrayDimensions?: number[] }>
|
|
31
|
-
>();
|
|
32
|
-
const pointFields = new Map<
|
|
33
|
-
string,
|
|
34
|
-
{ type: string; arrayDimensions?: number[] }
|
|
35
|
-
>();
|
|
36
|
-
pointFields.set("x", { type: "i32" });
|
|
37
|
-
pointFields.set("y", { type: "i32" });
|
|
38
|
-
structFields.set("Point", pointFields);
|
|
39
|
-
|
|
40
|
-
// Use restoreStructFields to populate the symbol table
|
|
41
|
-
symbolTable.restoreStructFields(structFields);
|
|
42
|
-
|
|
43
|
-
const result =
|
|
44
|
-
AnalyzerContextBuilder.buildExternalStructFields(symbolTable);
|
|
45
|
-
|
|
46
|
-
expect(result.has("Point")).toBe(true);
|
|
47
|
-
const fields = result.get("Point");
|
|
48
|
-
expect(fields?.has("x")).toBe(true);
|
|
49
|
-
expect(fields?.has("y")).toBe(true);
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
it("excludes array fields from result (Issue #355)", () => {
|
|
53
|
-
const structFields = new Map<
|
|
54
|
-
string,
|
|
55
|
-
Map<string, { type: string; arrayDimensions?: number[] }>
|
|
56
|
-
>();
|
|
57
|
-
const bufferFields = new Map<
|
|
58
|
-
string,
|
|
59
|
-
{ type: string; arrayDimensions?: number[] }
|
|
60
|
-
>();
|
|
61
|
-
bufferFields.set("size", { type: "u32" }); // Non-array
|
|
62
|
-
bufferFields.set("data", { type: "u8", arrayDimensions: [256] }); // Array
|
|
63
|
-
|
|
64
|
-
structFields.set("Buffer", bufferFields);
|
|
65
|
-
symbolTable.restoreStructFields(structFields);
|
|
66
|
-
|
|
67
|
-
const result =
|
|
68
|
-
AnalyzerContextBuilder.buildExternalStructFields(symbolTable);
|
|
69
|
-
|
|
70
|
-
expect(result.has("Buffer")).toBe(true);
|
|
71
|
-
const fields = result.get("Buffer");
|
|
72
|
-
expect(fields?.has("size")).toBe(true); // Non-array included
|
|
73
|
-
expect(fields?.has("data")).toBe(false); // Array excluded
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
it("excludes structs with only array fields", () => {
|
|
77
|
-
const structFields = new Map<
|
|
78
|
-
string,
|
|
79
|
-
Map<string, { type: string; arrayDimensions?: number[] }>
|
|
80
|
-
>();
|
|
81
|
-
const arrayOnlyFields = new Map<
|
|
82
|
-
string,
|
|
83
|
-
{ type: string; arrayDimensions?: number[] }
|
|
84
|
-
>();
|
|
85
|
-
arrayOnlyFields.set("items", { type: "u8", arrayDimensions: [10] });
|
|
86
|
-
arrayOnlyFields.set("values", { type: "i32", arrayDimensions: [5] });
|
|
87
|
-
|
|
88
|
-
structFields.set("ArrayOnly", arrayOnlyFields);
|
|
89
|
-
symbolTable.restoreStructFields(structFields);
|
|
90
|
-
|
|
91
|
-
const result =
|
|
92
|
-
AnalyzerContextBuilder.buildExternalStructFields(symbolTable);
|
|
93
|
-
|
|
94
|
-
// Struct should not be included since all fields are arrays
|
|
95
|
-
expect(result.has("ArrayOnly")).toBe(false);
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
it("handles mixed structs correctly", () => {
|
|
99
|
-
const structFields = new Map<
|
|
100
|
-
string,
|
|
101
|
-
Map<string, { type: string; arrayDimensions?: number[] }>
|
|
102
|
-
>();
|
|
103
|
-
|
|
104
|
-
// Struct with mixed fields
|
|
105
|
-
const mixedFields = new Map<
|
|
106
|
-
string,
|
|
107
|
-
{ type: string; arrayDimensions?: number[] }
|
|
108
|
-
>();
|
|
109
|
-
mixedFields.set("id", { type: "u32" });
|
|
110
|
-
mixedFields.set("name", { type: "string", arrayDimensions: [32] });
|
|
111
|
-
mixedFields.set("count", { type: "u16" });
|
|
112
|
-
|
|
113
|
-
// Struct with only non-array
|
|
114
|
-
const simpleFields = new Map<
|
|
115
|
-
string,
|
|
116
|
-
{ type: string; arrayDimensions?: number[] }
|
|
117
|
-
>();
|
|
118
|
-
simpleFields.set("value", { type: "f32" });
|
|
119
|
-
|
|
120
|
-
structFields.set("Mixed", mixedFields);
|
|
121
|
-
structFields.set("Simple", simpleFields);
|
|
122
|
-
symbolTable.restoreStructFields(structFields);
|
|
123
|
-
|
|
124
|
-
const result =
|
|
125
|
-
AnalyzerContextBuilder.buildExternalStructFields(symbolTable);
|
|
126
|
-
|
|
127
|
-
// Mixed struct should have only non-array fields
|
|
128
|
-
expect(result.get("Mixed")?.size).toBe(2);
|
|
129
|
-
expect(result.get("Mixed")?.has("id")).toBe(true);
|
|
130
|
-
expect(result.get("Mixed")?.has("count")).toBe(true);
|
|
131
|
-
expect(result.get("Mixed")?.has("name")).toBe(false);
|
|
132
|
-
|
|
133
|
-
// Simple struct should have its field
|
|
134
|
-
expect(result.get("Simple")?.has("value")).toBe(true);
|
|
135
|
-
});
|
|
136
|
-
});
|
|
137
|
-
});
|