c-next 0.1.62 → 0.1.63
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/README.md +86 -63
- package/package.json +1 -1
- package/src/transpiler/Transpiler.ts +3 -2
- package/src/transpiler/__tests__/DualCodePaths.test.ts +1 -1
- package/src/transpiler/__tests__/Transpiler.coverage.test.ts +1 -1
- package/src/transpiler/__tests__/Transpiler.test.ts +0 -23
- package/src/transpiler/logic/symbols/cnext/collectors/StructCollector.ts +156 -70
- package/src/transpiler/logic/symbols/cnext/collectors/VariableCollector.ts +31 -6
- package/src/transpiler/logic/symbols/cnext/utils/TypeUtils.ts +43 -11
- package/src/transpiler/output/codegen/CodeGenState.ts +811 -0
- package/src/transpiler/output/codegen/CodeGenerator.ts +817 -1377
- package/src/transpiler/output/codegen/TypeResolver.ts +193 -149
- package/src/transpiler/output/codegen/TypeValidator.ts +148 -370
- package/src/transpiler/output/codegen/__tests__/CodeGenState.test.ts +446 -0
- package/src/transpiler/output/codegen/__tests__/CodeGenerator.test.ts +326 -60
- package/src/transpiler/output/codegen/__tests__/TrackVariableTypeHelpers.test.ts +1 -1
- package/src/transpiler/output/codegen/__tests__/TypeResolver.test.ts +435 -196
- package/src/transpiler/output/codegen/__tests__/TypeValidator.resolution.test.ts +51 -67
- package/src/transpiler/output/codegen/__tests__/TypeValidator.test.ts +495 -471
- package/src/transpiler/output/codegen/analysis/MemberChainAnalyzer.ts +39 -43
- package/src/transpiler/output/codegen/analysis/StringLengthCounter.ts +52 -55
- package/src/transpiler/output/codegen/analysis/__tests__/MemberChainAnalyzer.test.ts +122 -62
- package/src/transpiler/output/codegen/analysis/__tests__/StringLengthCounter.test.ts +101 -144
- package/src/transpiler/output/codegen/assignment/AssignmentClassifier.ts +143 -126
- package/src/transpiler/output/codegen/assignment/__tests__/AssignmentClassifier.test.ts +287 -320
- package/src/transpiler/output/codegen/generators/GeneratorRegistry.ts +12 -0
- package/src/transpiler/output/codegen/generators/__tests__/GeneratorRegistry.test.ts +28 -1
- package/src/transpiler/output/codegen/generators/declarationGenerators/ArrayDimensionUtils.ts +67 -0
- package/src/transpiler/output/codegen/generators/declarationGenerators/ScopeGenerator.ts +121 -51
- package/src/transpiler/output/codegen/generators/declarationGenerators/StructGenerator.ts +100 -23
- package/src/transpiler/output/codegen/generators/declarationGenerators/__tests__/ArrayDimensionUtils.test.ts +125 -0
- package/src/transpiler/output/codegen/generators/declarationGenerators/__tests__/ScopeGenerator.test.ts +157 -4
- package/src/transpiler/output/codegen/generators/support/HelperGenerator.ts +23 -22
- package/src/transpiler/output/codegen/helpers/ArrayInitHelper.ts +54 -61
- package/src/transpiler/output/codegen/helpers/AssignmentExpectedTypeResolver.ts +21 -30
- package/src/transpiler/output/codegen/helpers/AssignmentValidator.ts +56 -53
- package/src/transpiler/output/codegen/helpers/CppModeHelper.ts +22 -30
- package/src/transpiler/output/codegen/helpers/EnumAssignmentValidator.ts +108 -50
- package/src/transpiler/output/codegen/helpers/FloatBitHelper.ts +16 -31
- package/src/transpiler/output/codegen/helpers/StringDeclHelper.ts +103 -96
- package/src/transpiler/output/codegen/helpers/TypeGenerationHelper.ts +9 -0
- package/src/transpiler/output/codegen/helpers/__tests__/ArrayInitHelper.test.ts +58 -103
- package/src/transpiler/output/codegen/helpers/__tests__/AssignmentExpectedTypeResolver.test.ts +97 -40
- package/src/transpiler/output/codegen/helpers/__tests__/AssignmentValidator.test.ts +223 -128
- package/src/transpiler/output/codegen/helpers/__tests__/CppModeHelper.test.ts +68 -41
- package/src/transpiler/output/codegen/helpers/__tests__/EnumAssignmentValidator.test.ts +198 -47
- package/src/transpiler/output/codegen/helpers/__tests__/FloatBitHelper.test.ts +39 -37
- package/src/transpiler/output/codegen/helpers/__tests__/StringDeclHelper.test.ts +191 -453
- package/src/transpiler/output/codegen/resolution/EnumTypeResolver.ts +229 -0
- package/src/transpiler/output/codegen/resolution/ScopeResolver.ts +60 -0
- package/src/transpiler/output/codegen/resolution/SizeofResolver.ts +177 -0
- package/src/transpiler/output/codegen/resolution/__tests__/EnumTypeResolver.test.ts +336 -0
- package/src/transpiler/output/codegen/resolution/__tests__/SizeofResolver.test.ts +201 -0
- package/src/transpiler/output/codegen/types/ITypeResolverDeps.ts +0 -23
- package/src/transpiler/output/codegen/types/ITypeValidatorDeps.ts +0 -53
|
@@ -1,13 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* TypeResolver - Handles type inference, classification, and validation
|
|
3
|
-
*
|
|
4
|
-
* Issue #61: Now independent of CodeGenerator
|
|
3
|
+
* Static class that reads from CodeGenState directly.
|
|
5
4
|
*/
|
|
6
5
|
import * as Parser from "../../logic/parser/grammar/CNextParser";
|
|
7
|
-
import
|
|
8
|
-
import SymbolTable from "../../logic/symbols/SymbolTable";
|
|
9
|
-
import TTypeInfo from "./types/TTypeInfo";
|
|
10
|
-
import ITypeResolverDeps from "./types/ITypeResolverDeps";
|
|
6
|
+
import CodeGenState from "./CodeGenState";
|
|
11
7
|
import INTEGER_TYPES from "./types/INTEGER_TYPES";
|
|
12
8
|
import FLOAT_TYPES from "./types/FLOAT_TYPES";
|
|
13
9
|
import SIGNED_TYPES from "./types/SIGNED_TYPES";
|
|
@@ -16,64 +12,68 @@ import TYPE_WIDTH from "./types/TYPE_WIDTH";
|
|
|
16
12
|
import TYPE_RANGES from "./types/TYPE_RANGES";
|
|
17
13
|
import ExpressionUnwrapper from "./utils/ExpressionUnwrapper";
|
|
18
14
|
|
|
15
|
+
/**
|
|
16
|
+
* Internal type info tracked through postfix suffix chains.
|
|
17
|
+
* Preserves isArray so indexing can distinguish array access from bit indexing.
|
|
18
|
+
*/
|
|
19
|
+
type InternalTypeInfo = { baseType: string; isArray: boolean };
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Discriminated union for postfix suffix processing results.
|
|
23
|
+
* stop=true: return type immediately (terminal suffix like bit indexing).
|
|
24
|
+
* stop=false: continue chain with updated InternalTypeInfo.
|
|
25
|
+
*/
|
|
26
|
+
type SuffixResult =
|
|
27
|
+
| { stop: true; type: string | null }
|
|
28
|
+
| { stop: false; info: InternalTypeInfo };
|
|
29
|
+
|
|
19
30
|
class TypeResolver {
|
|
20
|
-
|
|
21
|
-
private readonly
|
|
22
|
-
|
|
23
|
-
private readonly
|
|
24
|
-
|
|
25
|
-
constructor(deps: ITypeResolverDeps) {
|
|
26
|
-
this.symbols = deps.symbols;
|
|
27
|
-
this.symbolTable = deps.symbolTable;
|
|
28
|
-
this.typeRegistry = deps.typeRegistry;
|
|
29
|
-
this.resolveIdentifierFn = deps.resolveIdentifier;
|
|
30
|
-
}
|
|
31
|
+
/** Sentinel value for `global` keyword in postfix expression type resolution */
|
|
32
|
+
private static readonly GLOBAL_SENTINEL = "__global__";
|
|
33
|
+
/** Sentinel value for `this` keyword in postfix expression type resolution */
|
|
34
|
+
private static readonly THIS_SENTINEL = "__this__";
|
|
31
35
|
|
|
32
36
|
/**
|
|
33
37
|
* ADR-024: Check if a type is any integer (signed or unsigned)
|
|
34
38
|
*/
|
|
35
|
-
isIntegerType(typeName: string): boolean {
|
|
39
|
+
static isIntegerType(typeName: string): boolean {
|
|
36
40
|
return (INTEGER_TYPES as readonly string[]).includes(typeName);
|
|
37
41
|
}
|
|
38
42
|
|
|
39
43
|
/**
|
|
40
44
|
* ADR-024: Check if a type is a floating point type
|
|
41
45
|
*/
|
|
42
|
-
isFloatType(typeName: string): boolean {
|
|
46
|
+
static isFloatType(typeName: string): boolean {
|
|
43
47
|
return (FLOAT_TYPES as readonly string[]).includes(typeName);
|
|
44
48
|
}
|
|
45
49
|
|
|
46
50
|
/**
|
|
47
51
|
* ADR-024: Check if a type is a signed integer
|
|
48
52
|
*/
|
|
49
|
-
isSignedType(typeName: string): boolean {
|
|
53
|
+
static isSignedType(typeName: string): boolean {
|
|
50
54
|
return (SIGNED_TYPES as readonly string[]).includes(typeName);
|
|
51
55
|
}
|
|
52
56
|
|
|
53
57
|
/**
|
|
54
58
|
* ADR-024: Check if a type is an unsigned integer
|
|
55
59
|
*/
|
|
56
|
-
isUnsignedType(typeName: string): boolean {
|
|
60
|
+
static isUnsignedType(typeName: string): boolean {
|
|
57
61
|
return (UNSIGNED_TYPES as readonly string[]).includes(typeName);
|
|
58
62
|
}
|
|
59
63
|
|
|
60
64
|
/**
|
|
61
65
|
* Check if a type is a user-defined struct (C-Next or C header).
|
|
62
66
|
* Issue #103: Now checks both knownStructs AND SymbolTable.
|
|
63
|
-
* Issue #60: Uses SymbolCollector for C-Next structs.
|
|
64
|
-
* Issue #61: Uses injected dependencies instead of CodeGenerator.
|
|
65
67
|
*/
|
|
66
|
-
isStructType(typeName: string): boolean {
|
|
67
|
-
|
|
68
|
-
if (this.symbols?.knownStructs.has(typeName)) {
|
|
68
|
+
static isStructType(typeName: string): boolean {
|
|
69
|
+
if (CodeGenState.symbols?.knownStructs.has(typeName)) {
|
|
69
70
|
return true;
|
|
70
71
|
}
|
|
71
72
|
// Issue #551: Bitmaps are struct-like (use pass-by-reference with -> access)
|
|
72
|
-
if (
|
|
73
|
+
if (CodeGenState.symbols?.knownBitmaps.has(typeName)) {
|
|
73
74
|
return true;
|
|
74
75
|
}
|
|
75
|
-
|
|
76
|
-
if (this.symbolTable?.getStructFields(typeName)) {
|
|
76
|
+
if (CodeGenState.symbolTable?.getStructFields(typeName)) {
|
|
77
77
|
return true;
|
|
78
78
|
}
|
|
79
79
|
return false;
|
|
@@ -81,14 +81,16 @@ class TypeResolver {
|
|
|
81
81
|
|
|
82
82
|
/**
|
|
83
83
|
* ADR-024: Check if conversion from sourceType to targetType is narrowing
|
|
84
|
-
* Narrowing occurs when target type has fewer bits than source type
|
|
85
84
|
*/
|
|
86
|
-
isNarrowingConversion(
|
|
85
|
+
static isNarrowingConversion(
|
|
86
|
+
sourceType: string,
|
|
87
|
+
targetType: string,
|
|
88
|
+
): boolean {
|
|
87
89
|
const sourceWidth = TYPE_WIDTH[sourceType] || 0;
|
|
88
90
|
const targetWidth = TYPE_WIDTH[targetType] || 0;
|
|
89
91
|
|
|
90
92
|
if (sourceWidth === 0 || targetWidth === 0) {
|
|
91
|
-
return false;
|
|
93
|
+
return false;
|
|
92
94
|
}
|
|
93
95
|
|
|
94
96
|
return targetWidth < sourceWidth;
|
|
@@ -96,13 +98,12 @@ class TypeResolver {
|
|
|
96
98
|
|
|
97
99
|
/**
|
|
98
100
|
* ADR-024: Check if conversion involves a sign change
|
|
99
|
-
* Sign change occurs when converting between signed and unsigned types
|
|
100
101
|
*/
|
|
101
|
-
isSignConversion(sourceType: string, targetType: string): boolean {
|
|
102
|
-
const sourceIsSigned =
|
|
103
|
-
const sourceIsUnsigned =
|
|
104
|
-
const targetIsSigned =
|
|
105
|
-
const targetIsUnsigned =
|
|
102
|
+
static isSignConversion(sourceType: string, targetType: string): boolean {
|
|
103
|
+
const sourceIsSigned = TypeResolver.isSignedType(sourceType);
|
|
104
|
+
const sourceIsUnsigned = TypeResolver.isUnsignedType(sourceType);
|
|
105
|
+
const targetIsSigned = TypeResolver.isSignedType(targetType);
|
|
106
|
+
const targetIsUnsigned = TypeResolver.isUnsignedType(targetType);
|
|
106
107
|
|
|
107
108
|
return (
|
|
108
109
|
(sourceIsSigned && targetIsUnsigned) ||
|
|
@@ -113,47 +114,41 @@ class TypeResolver {
|
|
|
113
114
|
/**
|
|
114
115
|
* ADR-024: Validate that a literal value fits within the target type's range.
|
|
115
116
|
* Throws an error if the value doesn't fit.
|
|
116
|
-
* @param literalText The literal text (e.g., "256", "-1", "0xFF")
|
|
117
|
-
* @param targetType The target type (e.g., "u8", "i32")
|
|
118
117
|
*/
|
|
119
|
-
validateLiteralFitsType(
|
|
118
|
+
static validateLiteralFitsType(
|
|
119
|
+
literalText: string,
|
|
120
|
+
targetType: string,
|
|
121
|
+
): void {
|
|
120
122
|
const range = TYPE_RANGES[targetType];
|
|
121
123
|
if (!range) {
|
|
122
|
-
return;
|
|
124
|
+
return;
|
|
123
125
|
}
|
|
124
126
|
|
|
125
|
-
// Parse the literal value
|
|
126
127
|
let value: bigint;
|
|
127
128
|
try {
|
|
128
129
|
const cleanText = literalText.trim();
|
|
129
130
|
|
|
130
131
|
if (/^-?\d+$/.exec(cleanText)) {
|
|
131
|
-
// Decimal integer
|
|
132
132
|
value = BigInt(cleanText);
|
|
133
133
|
} else if (/^0[xX][0-9a-fA-F]+$/.exec(cleanText)) {
|
|
134
|
-
// Hex literal
|
|
135
134
|
value = BigInt(cleanText);
|
|
136
135
|
} else if (/^0[bB][01]+$/.exec(cleanText)) {
|
|
137
|
-
// Binary literal
|
|
138
136
|
value = BigInt(cleanText);
|
|
139
137
|
} else {
|
|
140
|
-
// Not an integer literal we can validate
|
|
141
138
|
return;
|
|
142
139
|
}
|
|
143
140
|
} catch {
|
|
144
|
-
return;
|
|
141
|
+
return;
|
|
145
142
|
}
|
|
146
143
|
|
|
147
144
|
const [min, max] = range;
|
|
148
145
|
|
|
149
|
-
|
|
150
|
-
if (this.isUnsignedType(targetType) && value < 0n) {
|
|
146
|
+
if (TypeResolver.isUnsignedType(targetType) && value < 0n) {
|
|
151
147
|
throw new Error(
|
|
152
148
|
`Error: Negative value ${literalText} cannot be assigned to unsigned type ${targetType}`,
|
|
153
149
|
);
|
|
154
150
|
}
|
|
155
151
|
|
|
156
|
-
// Check if value is out of range
|
|
157
152
|
if (value < min || value > max) {
|
|
158
153
|
throw new Error(
|
|
159
154
|
`Error: Value ${literalText} exceeds ${targetType} range (${min} to ${max})`,
|
|
@@ -163,15 +158,12 @@ class TypeResolver {
|
|
|
163
158
|
|
|
164
159
|
/**
|
|
165
160
|
* ADR-024: Get the type from a literal (suffixed or unsuffixed).
|
|
166
|
-
* Returns the explicit suffix type, or null for unsuffixed literals.
|
|
167
161
|
*/
|
|
168
|
-
getLiteralType(ctx: Parser.LiteralContext): string | null {
|
|
162
|
+
static getLiteralType(ctx: Parser.LiteralContext): string | null {
|
|
169
163
|
const text = ctx.getText();
|
|
170
164
|
|
|
171
|
-
// Boolean literals
|
|
172
165
|
if (text === "true" || text === "false") return "bool";
|
|
173
166
|
|
|
174
|
-
// Check for type suffix on numeric literals
|
|
175
167
|
const suffixMatch = /([uUiI])(8|16|32|64)$/.exec(text);
|
|
176
168
|
if (suffixMatch) {
|
|
177
169
|
const signChar = suffixMatch[1].toLowerCase();
|
|
@@ -179,194 +171,252 @@ class TypeResolver {
|
|
|
179
171
|
return (signChar === "u" ? "u" : "i") + width;
|
|
180
172
|
}
|
|
181
173
|
|
|
182
|
-
// Float suffix
|
|
183
174
|
const floatMatch = /[fF](32|64)$/.exec(text);
|
|
184
175
|
if (floatMatch) {
|
|
185
176
|
return "f" + floatMatch[1];
|
|
186
177
|
}
|
|
187
178
|
|
|
188
|
-
// Unsuffixed literal - type depends on context (handled by caller)
|
|
189
179
|
return null;
|
|
190
180
|
}
|
|
191
181
|
|
|
192
182
|
/**
|
|
193
183
|
* ADR-024: Get the type of an expression for type checking.
|
|
194
|
-
* Returns the inferred type or null if type cannot be determined.
|
|
195
|
-
* Issue #61: Uses ExpressionUnwrapper utility for tree navigation.
|
|
196
184
|
*/
|
|
197
|
-
getExpressionType(ctx: Parser.ExpressionContext): string | null {
|
|
198
|
-
// Navigate through expression tree to get the actual value
|
|
185
|
+
static getExpressionType(ctx: Parser.ExpressionContext): string | null {
|
|
199
186
|
const postfix = ExpressionUnwrapper.getPostfixExpression(ctx);
|
|
200
187
|
if (postfix) {
|
|
201
|
-
return
|
|
188
|
+
return TypeResolver.getPostfixExpressionType(postfix);
|
|
202
189
|
}
|
|
203
190
|
|
|
204
|
-
// For more complex expressions (binary ops, etc.), try to infer type
|
|
205
191
|
const ternary = ctx.ternaryExpression();
|
|
206
192
|
const orExprs = ternary.orExpression();
|
|
207
|
-
// If it's a ternary, we can't easily determine the type
|
|
208
193
|
if (orExprs.length > 1) {
|
|
209
194
|
return null;
|
|
210
195
|
}
|
|
211
196
|
const or = orExprs[0];
|
|
212
197
|
if (or.andExpression().length > 1) {
|
|
213
|
-
return "bool";
|
|
198
|
+
return "bool";
|
|
214
199
|
}
|
|
215
200
|
|
|
216
201
|
const and = or.andExpression()[0];
|
|
217
202
|
if (and.equalityExpression().length > 1) {
|
|
218
|
-
return "bool";
|
|
203
|
+
return "bool";
|
|
219
204
|
}
|
|
220
205
|
|
|
221
206
|
const eq = and.equalityExpression()[0];
|
|
222
207
|
if (eq.relationalExpression().length > 1) {
|
|
223
|
-
return "bool";
|
|
208
|
+
return "bool";
|
|
224
209
|
}
|
|
225
210
|
|
|
226
211
|
const rel = eq.relationalExpression()[0];
|
|
227
212
|
if (rel.bitwiseOrExpression().length > 1) {
|
|
228
|
-
return "bool";
|
|
213
|
+
return "bool";
|
|
229
214
|
}
|
|
230
215
|
|
|
231
|
-
// For arithmetic expressions, we'd need to track operand types
|
|
232
|
-
// For now, return null for complex expressions
|
|
233
216
|
return null;
|
|
234
217
|
}
|
|
235
218
|
|
|
236
219
|
/**
|
|
237
220
|
* ADR-024: Get the type of a postfix expression.
|
|
238
|
-
*
|
|
239
|
-
*
|
|
221
|
+
* Tracks InternalTypeInfo (baseType + isArray) through the suffix chain
|
|
222
|
+
* so that array indexing is correctly distinguished from bit indexing.
|
|
240
223
|
*/
|
|
241
|
-
getPostfixExpressionType(
|
|
224
|
+
static getPostfixExpressionType(
|
|
242
225
|
ctx: Parser.PostfixExpressionContext,
|
|
243
226
|
): string | null {
|
|
244
227
|
const primary = ctx.primaryExpression();
|
|
245
228
|
if (!primary) return null;
|
|
246
229
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
if (!currentType) return null;
|
|
230
|
+
let current = TypeResolver.getPrimaryExpressionTypeInfo(primary);
|
|
231
|
+
if (!current) return null;
|
|
250
232
|
|
|
251
|
-
// Check for postfix operations: member access, array indexing, bit indexing
|
|
252
233
|
const suffixes = ctx.children?.slice(1) || [];
|
|
253
234
|
for (const suffix of suffixes) {
|
|
254
|
-
const result =
|
|
235
|
+
const result = TypeResolver.processPostfixSuffix(
|
|
236
|
+
suffix.getText(),
|
|
237
|
+
current,
|
|
238
|
+
);
|
|
255
239
|
if (result.stop) {
|
|
256
240
|
return result.type;
|
|
257
241
|
}
|
|
258
|
-
|
|
242
|
+
current = result.info;
|
|
259
243
|
}
|
|
260
244
|
|
|
261
|
-
return
|
|
245
|
+
return current.baseType;
|
|
262
246
|
}
|
|
263
247
|
|
|
264
248
|
/**
|
|
265
249
|
* Process a single postfix suffix and determine the resulting type.
|
|
266
|
-
* Returns { type, stop } where stop=true means
|
|
250
|
+
* Returns { type, stop, info } where stop=true means return type immediately.
|
|
267
251
|
*/
|
|
268
|
-
private processPostfixSuffix(
|
|
252
|
+
private static processPostfixSuffix(
|
|
269
253
|
text: string,
|
|
270
|
-
|
|
271
|
-
):
|
|
272
|
-
// Member access: .fieldName
|
|
254
|
+
current: InternalTypeInfo,
|
|
255
|
+
): SuffixResult {
|
|
273
256
|
if (text.startsWith(".")) {
|
|
274
|
-
|
|
275
|
-
const memberInfo = this.getMemberTypeInfo(currentType, memberName);
|
|
276
|
-
if (!memberInfo) {
|
|
277
|
-
return { type: null, stop: true };
|
|
278
|
-
}
|
|
279
|
-
return { type: memberInfo.baseType, stop: false };
|
|
257
|
+
return TypeResolver.processMemberSuffix(text.slice(1), current);
|
|
280
258
|
}
|
|
281
259
|
|
|
282
|
-
// Array or bit indexing: [index] or [start, width]
|
|
283
260
|
if (text.startsWith("[") && text.endsWith("]")) {
|
|
284
|
-
return
|
|
261
|
+
return TypeResolver.processIndexingSuffix(text, current);
|
|
285
262
|
}
|
|
286
263
|
|
|
287
|
-
|
|
288
|
-
|
|
264
|
+
return { stop: false, info: current };
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Process a member access suffix (.name) and resolve the resulting type.
|
|
269
|
+
* Handles global/this sentinel values and regular struct member lookups.
|
|
270
|
+
*/
|
|
271
|
+
private static processMemberSuffix(
|
|
272
|
+
memberName: string,
|
|
273
|
+
current: InternalTypeInfo,
|
|
274
|
+
): SuffixResult {
|
|
275
|
+
// Handle global.X — resolve X as a global variable name
|
|
276
|
+
if (current.baseType === TypeResolver.GLOBAL_SENTINEL) {
|
|
277
|
+
return TypeResolver.resolveRegistryLookup(memberName);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Handle this.X — resolve X as a scope member variable
|
|
281
|
+
if (
|
|
282
|
+
current.baseType === TypeResolver.THIS_SENTINEL &&
|
|
283
|
+
CodeGenState.currentScope
|
|
284
|
+
) {
|
|
285
|
+
const scopedName = `${CodeGenState.currentScope}_${memberName}`;
|
|
286
|
+
return TypeResolver.resolveRegistryLookup(scopedName);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const memberInfo = TypeResolver.getMemberTypeInfo(
|
|
290
|
+
current.baseType,
|
|
291
|
+
memberName,
|
|
292
|
+
);
|
|
293
|
+
if (!memberInfo) {
|
|
294
|
+
return { stop: true, type: null };
|
|
295
|
+
}
|
|
296
|
+
return {
|
|
297
|
+
stop: false,
|
|
298
|
+
info: { baseType: memberInfo.baseType, isArray: memberInfo.isArray },
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Look up a variable name in the type registry and return a SuffixResult.
|
|
304
|
+
*/
|
|
305
|
+
private static resolveRegistryLookup(name: string): SuffixResult {
|
|
306
|
+
const typeInfo = CodeGenState.typeRegistry.get(name);
|
|
307
|
+
if (typeInfo) {
|
|
308
|
+
return {
|
|
309
|
+
stop: false,
|
|
310
|
+
info: { baseType: typeInfo.baseType, isArray: typeInfo.isArray },
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
return { stop: true, type: null };
|
|
289
314
|
}
|
|
290
315
|
|
|
291
316
|
/**
|
|
292
317
|
* Process array or bit indexing suffix.
|
|
318
|
+
* Checks isArray BEFORE isIntegerType to correctly distinguish
|
|
319
|
+
* array element access from bit indexing.
|
|
293
320
|
*/
|
|
294
|
-
private processIndexingSuffix(
|
|
321
|
+
private static processIndexingSuffix(
|
|
295
322
|
text: string,
|
|
296
|
-
|
|
297
|
-
):
|
|
323
|
+
current: InternalTypeInfo,
|
|
324
|
+
): SuffixResult {
|
|
298
325
|
const inner = text.slice(1, -1);
|
|
299
326
|
|
|
300
|
-
// Range indexing: [start, width]
|
|
301
|
-
// ADR-024: Return null for bit indexing to skip type conversion validation
|
|
327
|
+
// Range indexing: [start, width] - always bit extraction
|
|
302
328
|
if (inner.includes(",")) {
|
|
303
|
-
return {
|
|
329
|
+
return { stop: true, type: null };
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Array access: if current type is known to be an array, index yields element
|
|
333
|
+
if (current.isArray) {
|
|
334
|
+
return {
|
|
335
|
+
stop: false,
|
|
336
|
+
info: { baseType: current.baseType, isArray: false },
|
|
337
|
+
};
|
|
304
338
|
}
|
|
305
339
|
|
|
306
|
-
//
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
return { type: "bool", stop: true };
|
|
340
|
+
// Bit indexing on integer: single bit returns bool
|
|
341
|
+
if (TypeResolver.isIntegerType(current.baseType)) {
|
|
342
|
+
return { stop: true, type: "bool" };
|
|
310
343
|
}
|
|
311
344
|
|
|
312
|
-
//
|
|
313
|
-
return {
|
|
345
|
+
// Unknown indexing - preserve current state
|
|
346
|
+
return { stop: false, info: current };
|
|
314
347
|
}
|
|
315
348
|
|
|
316
349
|
/**
|
|
317
|
-
*
|
|
318
|
-
* Issue #61: Uses injected dependencies instead of CodeGenerator.
|
|
350
|
+
* Get full InternalTypeInfo from a primary expression (preserves isArray).
|
|
319
351
|
*/
|
|
320
|
-
|
|
352
|
+
private static getPrimaryExpressionTypeInfo(
|
|
321
353
|
ctx: Parser.PrimaryExpressionContext,
|
|
322
|
-
):
|
|
323
|
-
// Check for identifier
|
|
354
|
+
): InternalTypeInfo | null {
|
|
324
355
|
const id = ctx.IDENTIFIER();
|
|
325
356
|
if (id) {
|
|
326
357
|
const name = id.getText();
|
|
327
|
-
const scopedName =
|
|
328
|
-
const typeInfo =
|
|
358
|
+
const scopedName = CodeGenState.resolveIdentifier(name);
|
|
359
|
+
const typeInfo = CodeGenState.typeRegistry.get(scopedName);
|
|
329
360
|
if (typeInfo) {
|
|
330
|
-
return typeInfo.baseType;
|
|
361
|
+
return { baseType: typeInfo.baseType, isArray: typeInfo.isArray };
|
|
331
362
|
}
|
|
332
363
|
return null;
|
|
333
364
|
}
|
|
334
365
|
|
|
335
|
-
//
|
|
366
|
+
// Handle global.X and this.X — these are scope qualifiers, not types.
|
|
367
|
+
// The actual variable name is the first .suffix after the keyword.
|
|
368
|
+
// Return a sentinel so getPostfixExpressionType knows to consume one suffix.
|
|
369
|
+
if (ctx.GLOBAL()) {
|
|
370
|
+
return { baseType: TypeResolver.GLOBAL_SENTINEL, isArray: false };
|
|
371
|
+
}
|
|
372
|
+
if (ctx.THIS()) {
|
|
373
|
+
return { baseType: TypeResolver.THIS_SENTINEL, isArray: false };
|
|
374
|
+
}
|
|
375
|
+
|
|
336
376
|
const literal = ctx.literal();
|
|
337
377
|
if (literal) {
|
|
338
|
-
|
|
378
|
+
const litType = TypeResolver.getLiteralType(literal);
|
|
379
|
+
return litType ? { baseType: litType, isArray: false } : null;
|
|
339
380
|
}
|
|
340
381
|
|
|
341
|
-
// Check for parenthesized expression
|
|
342
382
|
const expr = ctx.expression();
|
|
343
383
|
if (expr) {
|
|
344
|
-
|
|
384
|
+
const exprType = TypeResolver.getExpressionType(expr);
|
|
385
|
+
return exprType ? { baseType: exprType, isArray: false } : null;
|
|
345
386
|
}
|
|
346
387
|
|
|
347
|
-
// Check for cast expression
|
|
348
388
|
const cast = ctx.castExpression();
|
|
349
389
|
if (cast) {
|
|
350
|
-
return cast.type().getText();
|
|
390
|
+
return { baseType: cast.type().getText(), isArray: false };
|
|
351
391
|
}
|
|
352
392
|
|
|
353
393
|
return null;
|
|
354
394
|
}
|
|
355
395
|
|
|
396
|
+
/**
|
|
397
|
+
* ADR-024: Get the type of a primary expression (public API, returns baseType only).
|
|
398
|
+
*/
|
|
399
|
+
static getPrimaryExpressionType(
|
|
400
|
+
ctx: Parser.PrimaryExpressionContext,
|
|
401
|
+
): string | null {
|
|
402
|
+
const info = TypeResolver.getPrimaryExpressionTypeInfo(ctx);
|
|
403
|
+
return info?.baseType ?? null;
|
|
404
|
+
}
|
|
405
|
+
|
|
356
406
|
/**
|
|
357
407
|
* ADR-024: Get the type of a unary expression (for cast validation).
|
|
358
408
|
*/
|
|
359
|
-
getUnaryExpressionType(
|
|
360
|
-
|
|
409
|
+
static getUnaryExpressionType(
|
|
410
|
+
ctx: Parser.UnaryExpressionContext,
|
|
411
|
+
): string | null {
|
|
361
412
|
const postfix = ctx.postfixExpression();
|
|
362
413
|
if (postfix) {
|
|
363
|
-
return
|
|
414
|
+
return TypeResolver.getPostfixExpressionType(postfix);
|
|
364
415
|
}
|
|
365
416
|
|
|
366
|
-
// Check for recursive unary expression
|
|
367
417
|
const unary = ctx.unaryExpression();
|
|
368
418
|
if (unary) {
|
|
369
|
-
return
|
|
419
|
+
return TypeResolver.getUnaryExpressionType(unary);
|
|
370
420
|
}
|
|
371
421
|
|
|
372
422
|
return null;
|
|
@@ -374,21 +424,21 @@ class TypeResolver {
|
|
|
374
424
|
|
|
375
425
|
/**
|
|
376
426
|
* ADR-024: Validate that a type conversion is allowed.
|
|
377
|
-
* Throws error for narrowing or sign-changing conversions.
|
|
378
427
|
*/
|
|
379
|
-
validateTypeConversion(
|
|
380
|
-
|
|
428
|
+
static validateTypeConversion(
|
|
429
|
+
targetType: string,
|
|
430
|
+
sourceType: string | null,
|
|
431
|
+
): void {
|
|
381
432
|
if (!sourceType) return;
|
|
382
|
-
|
|
383
|
-
// Skip if types are the same
|
|
384
433
|
if (sourceType === targetType) return;
|
|
385
434
|
|
|
386
|
-
|
|
387
|
-
|
|
435
|
+
if (
|
|
436
|
+
!TypeResolver.isIntegerType(sourceType) ||
|
|
437
|
+
!TypeResolver.isIntegerType(targetType)
|
|
438
|
+
)
|
|
388
439
|
return;
|
|
389
440
|
|
|
390
|
-
|
|
391
|
-
if (this.isNarrowingConversion(sourceType, targetType)) {
|
|
441
|
+
if (TypeResolver.isNarrowingConversion(sourceType, targetType)) {
|
|
392
442
|
const targetWidth = TYPE_WIDTH[targetType] || 0;
|
|
393
443
|
throw new Error(
|
|
394
444
|
`Error: Cannot assign ${sourceType} to ${targetType} (narrowing). ` +
|
|
@@ -396,8 +446,7 @@ class TypeResolver {
|
|
|
396
446
|
);
|
|
397
447
|
}
|
|
398
448
|
|
|
399
|
-
|
|
400
|
-
if (this.isSignConversion(sourceType, targetType)) {
|
|
449
|
+
if (TypeResolver.isSignConversion(sourceType, targetType)) {
|
|
401
450
|
const targetWidth = TYPE_WIDTH[targetType] || 0;
|
|
402
451
|
throw new Error(
|
|
403
452
|
`Error: Cannot assign ${sourceType} to ${targetType} (sign change). ` +
|
|
@@ -407,18 +456,15 @@ class TypeResolver {
|
|
|
407
456
|
}
|
|
408
457
|
|
|
409
458
|
/**
|
|
410
|
-
* Get type info for a struct member field
|
|
411
|
-
*
|
|
412
|
-
* Issue #103: Now checks SymbolTable first for C header structs
|
|
413
|
-
* Issue #61: Uses injected dependencies instead of CodeGenerator.
|
|
459
|
+
* Get type info for a struct member field.
|
|
460
|
+
* Issue #103: Checks SymbolTable first for C header structs.
|
|
414
461
|
*/
|
|
415
|
-
getMemberTypeInfo(
|
|
462
|
+
static getMemberTypeInfo(
|
|
416
463
|
structType: string,
|
|
417
464
|
memberName: string,
|
|
418
465
|
): { isArray: boolean; baseType: string } | undefined {
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
const fieldInfo = this.symbolTable.getStructFieldInfo(
|
|
466
|
+
if (CodeGenState.symbolTable) {
|
|
467
|
+
const fieldInfo = CodeGenState.symbolTable.getStructFieldInfo(
|
|
422
468
|
structType,
|
|
423
469
|
memberName,
|
|
424
470
|
);
|
|
@@ -432,14 +478,12 @@ class TypeResolver {
|
|
|
432
478
|
}
|
|
433
479
|
}
|
|
434
480
|
|
|
435
|
-
|
|
436
|
-
const fieldType = this.symbols?.structFields
|
|
481
|
+
const fieldType = CodeGenState.symbols?.structFields
|
|
437
482
|
.get(structType)
|
|
438
483
|
?.get(memberName);
|
|
439
484
|
if (!fieldType) return undefined;
|
|
440
485
|
|
|
441
|
-
|
|
442
|
-
const arrayFields = this.symbols?.structFieldArrays.get(structType);
|
|
486
|
+
const arrayFields = CodeGenState.symbols?.structFieldArrays.get(structType);
|
|
443
487
|
const isArray = arrayFields?.has(memberName) ?? false;
|
|
444
488
|
|
|
445
489
|
return { isArray, baseType: fieldType };
|