c-next 0.2.16 → 0.2.17
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 +16 -0
- package/dist/index.js +1347 -414
- package/dist/index.js.map +3 -3
- package/grammar/CNext.g4 +4 -0
- package/package.json +5 -1
- package/src/transpiler/Transpiler.ts +90 -22
- package/src/transpiler/__tests__/DualCodePaths.test.ts +1 -1
- package/src/transpiler/logic/analysis/FunctionCallAnalyzer.ts +57 -10
- package/src/transpiler/logic/analysis/InitializationAnalyzer.ts +186 -14
- package/src/transpiler/logic/analysis/SignedShiftAnalyzer.ts +124 -12
- package/src/transpiler/logic/analysis/__tests__/FunctionCallAnalyzer.test.ts +200 -0
- package/src/transpiler/logic/analysis/__tests__/InitializationAnalyzer.test.ts +386 -1
- package/src/transpiler/logic/analysis/__tests__/SignedShiftAnalyzer.test.ts +211 -0
- package/src/transpiler/logic/parser/grammar/CNext.interp +1 -1
- package/src/transpiler/logic/parser/grammar/CNextParser.ts +154 -86
- package/src/transpiler/logic/symbols/SymbolTable.ts +17 -2
- package/src/transpiler/logic/symbols/__tests__/SymbolTable.test.ts +6 -4
- package/src/transpiler/logic/symbols/cnext/collectors/VariableCollector.ts +15 -2
- package/src/transpiler/logic/symbols/cnext/utils/TypeUtils.ts +64 -50
- package/src/transpiler/output/codegen/CodeGenerator.ts +151 -94
- package/src/transpiler/output/codegen/__tests__/CodeGenerator.coverage.test.ts +165 -17
- package/src/transpiler/output/codegen/__tests__/CodeGenerator.test.ts +150 -34
- package/src/transpiler/output/codegen/__tests__/TrackVariableTypeHelpers.test.ts +2 -2
- package/src/transpiler/output/codegen/__tests__/TypeValidator.test.ts +11 -0
- package/src/transpiler/output/codegen/generators/declarationGenerators/ScopeGenerator.ts +26 -7
- package/src/transpiler/output/codegen/generators/declarationGenerators/__tests__/ScopeGenerator.test.ts +86 -0
- package/src/transpiler/output/codegen/generators/expressions/BinaryExprGenerator.ts +43 -24
- package/src/transpiler/output/codegen/generators/expressions/CallExprGenerator.ts +48 -43
- package/src/transpiler/output/codegen/generators/expressions/ExpressionGenerator.ts +9 -2
- package/src/transpiler/output/codegen/generators/expressions/__tests__/CallExprGenerator.test.ts +44 -0
- package/src/transpiler/output/codegen/generators/expressions/__tests__/ExpressionGenerator.test.ts +82 -1
- package/src/transpiler/output/codegen/helpers/ParameterInputAdapter.ts +17 -3
- package/src/transpiler/output/codegen/helpers/ParameterSignatureBuilder.ts +17 -4
- package/src/transpiler/output/codegen/helpers/StringDeclHelper.ts +227 -32
- package/src/transpiler/output/codegen/helpers/SymbolLookupHelper.ts +0 -21
- package/src/transpiler/output/codegen/helpers/TypeGenerationHelper.ts +60 -39
- package/src/transpiler/output/codegen/helpers/TypeRegistrationEngine.ts +170 -36
- package/src/transpiler/output/codegen/helpers/VariableDeclHelper.ts +37 -39
- package/src/transpiler/output/codegen/helpers/__tests__/ParameterInputAdapter.test.ts +117 -0
- package/src/transpiler/output/codegen/helpers/__tests__/ParameterSignatureBuilder.test.ts +94 -2
- package/src/transpiler/output/codegen/helpers/__tests__/StringDeclHelper.test.ts +268 -1
- package/src/transpiler/output/codegen/helpers/__tests__/SymbolLookupHelper.test.ts +0 -64
- package/src/transpiler/output/codegen/helpers/__tests__/TypeRegistrationEngine.test.ts +101 -0
- package/src/transpiler/output/codegen/helpers/__tests__/VariableDeclHelper.test.ts +29 -5
- package/src/transpiler/output/codegen/types/ICallbackTypeInfo.ts +2 -1
- package/src/transpiler/output/codegen/types/IParameterInput.ts +7 -0
- package/src/transpiler/output/headers/BaseHeaderGenerator.ts +8 -0
- package/src/transpiler/output/headers/HeaderGeneratorUtils.ts +75 -0
- package/src/transpiler/output/headers/__tests__/HeaderGeneratorUtils.test.ts +280 -0
- package/src/transpiler/output/headers/generators/IHeaderTypeInput.ts +13 -0
- package/src/transpiler/output/headers/generators/generateStructHeader.ts +48 -28
- package/src/transpiler/state/CodeGenState.ts +71 -6
- package/src/transpiler/state/__tests__/CodeGenState.test.ts +253 -11
- package/src/utils/LiteralUtils.ts +23 -0
- package/src/utils/__tests__/LiteralUtils.test.ts +101 -0
- package/src/utils/types/IParameterSymbol.ts +1 -0
|
@@ -310,9 +310,11 @@ describe("StringDeclHelper", () => {
|
|
|
310
310
|
});
|
|
311
311
|
|
|
312
312
|
it("allows assignment when source capacity fits", () => {
|
|
313
|
+
// Issue #1030: String variable initialization now uses strcpy
|
|
313
314
|
const callbacks = {
|
|
314
315
|
...defaultCallbacks,
|
|
315
316
|
getStringExprCapacity: vi.fn(() => 20),
|
|
317
|
+
generateExpression: vi.fn(() => "smallString"),
|
|
316
318
|
};
|
|
317
319
|
|
|
318
320
|
const typeCtx = {
|
|
@@ -336,7 +338,42 @@ describe("StringDeclHelper", () => {
|
|
|
336
338
|
);
|
|
337
339
|
|
|
338
340
|
expect(result.handled).toBe(true);
|
|
339
|
-
|
|
341
|
+
// Issue #1030: String variable initialization uses strcpy
|
|
342
|
+
expect(result.code).toContain('char dest[33] = "";');
|
|
343
|
+
expect(result.code).toContain("strcpy(dest, smallString)");
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
it("throws error for string variable initialization at global scope", () => {
|
|
347
|
+
// Issue #1030: String variable initialization requires function body
|
|
348
|
+
CodeGenState.inFunctionBody = false;
|
|
349
|
+
const callbacks = {
|
|
350
|
+
...defaultCallbacks,
|
|
351
|
+
getStringExprCapacity: vi.fn(() => 20),
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
const typeCtx = {
|
|
355
|
+
stringType: () => ({
|
|
356
|
+
INTEGER_LITERAL: () => ({ getText: () => "32" }),
|
|
357
|
+
}),
|
|
358
|
+
} as never;
|
|
359
|
+
|
|
360
|
+
const expression = {
|
|
361
|
+
getText: () => "sourceVar",
|
|
362
|
+
} as never;
|
|
363
|
+
|
|
364
|
+
expect(() =>
|
|
365
|
+
StringDeclHelper.generateStringDecl(
|
|
366
|
+
typeCtx,
|
|
367
|
+
"dest",
|
|
368
|
+
expression,
|
|
369
|
+
[],
|
|
370
|
+
{ extern: "", const: "", atomic: "", volatile: "" },
|
|
371
|
+
false,
|
|
372
|
+
callbacks,
|
|
373
|
+
),
|
|
374
|
+
).toThrow(
|
|
375
|
+
"String initialization from variable cannot be used at global scope",
|
|
376
|
+
);
|
|
340
377
|
});
|
|
341
378
|
});
|
|
342
379
|
|
|
@@ -1154,4 +1191,234 @@ describe("StringDeclHelper", () => {
|
|
|
1154
1191
|
expect(CodeGenState.localArrays.has("tracked")).toBe(true);
|
|
1155
1192
|
});
|
|
1156
1193
|
});
|
|
1194
|
+
|
|
1195
|
+
describe("string arrays from arrayType syntax (Issue #1029)", () => {
|
|
1196
|
+
it("generates string array from arrayType without initializer", () => {
|
|
1197
|
+
// Simulates: string<32>[4] items;
|
|
1198
|
+
const typeCtx = {
|
|
1199
|
+
stringType: () => null, // Not directly on typeCtx
|
|
1200
|
+
arrayType: () => ({
|
|
1201
|
+
stringType: () => ({
|
|
1202
|
+
INTEGER_LITERAL: () => ({ getText: () => "32" }),
|
|
1203
|
+
}),
|
|
1204
|
+
arrayTypeDimension: () => [
|
|
1205
|
+
{ expression: () => ({ getText: () => "4" }) },
|
|
1206
|
+
],
|
|
1207
|
+
}),
|
|
1208
|
+
} as never;
|
|
1209
|
+
|
|
1210
|
+
const result = StringDeclHelper.generateStringDecl(
|
|
1211
|
+
typeCtx,
|
|
1212
|
+
"items",
|
|
1213
|
+
null,
|
|
1214
|
+
[],
|
|
1215
|
+
{ extern: "", const: "", atomic: "", volatile: "" },
|
|
1216
|
+
false,
|
|
1217
|
+
defaultCallbacks,
|
|
1218
|
+
);
|
|
1219
|
+
|
|
1220
|
+
expect(result.handled).toBe(true);
|
|
1221
|
+
expect(result.code).toBe("char items[4][33] = {0};");
|
|
1222
|
+
});
|
|
1223
|
+
|
|
1224
|
+
it("generates string array from arrayType with initializer", () => {
|
|
1225
|
+
const callbacks = {
|
|
1226
|
+
...defaultCallbacks,
|
|
1227
|
+
generateExpression: vi.fn(() => {
|
|
1228
|
+
CodeGenState.lastArrayInitCount = 2;
|
|
1229
|
+
CodeGenState.lastArrayFillValue = undefined;
|
|
1230
|
+
return '{"One", "Two"}';
|
|
1231
|
+
}),
|
|
1232
|
+
};
|
|
1233
|
+
|
|
1234
|
+
// Simulates: string<10>[2] labels <- ["One", "Two"];
|
|
1235
|
+
const typeCtx = {
|
|
1236
|
+
stringType: () => null,
|
|
1237
|
+
arrayType: () => ({
|
|
1238
|
+
stringType: () => ({
|
|
1239
|
+
INTEGER_LITERAL: () => ({ getText: () => "10" }),
|
|
1240
|
+
}),
|
|
1241
|
+
arrayTypeDimension: () => [
|
|
1242
|
+
{ expression: () => ({ getText: () => "2" }) },
|
|
1243
|
+
],
|
|
1244
|
+
}),
|
|
1245
|
+
} as never;
|
|
1246
|
+
|
|
1247
|
+
const expression = {
|
|
1248
|
+
getText: () => '["One", "Two"]',
|
|
1249
|
+
} as never;
|
|
1250
|
+
|
|
1251
|
+
const result = StringDeclHelper.generateStringDecl(
|
|
1252
|
+
typeCtx,
|
|
1253
|
+
"labels",
|
|
1254
|
+
expression,
|
|
1255
|
+
[],
|
|
1256
|
+
{ extern: "", const: "", atomic: "", volatile: "" },
|
|
1257
|
+
false,
|
|
1258
|
+
callbacks,
|
|
1259
|
+
);
|
|
1260
|
+
|
|
1261
|
+
expect(result.handled).toBe(true);
|
|
1262
|
+
expect(result.code).toContain("[2]");
|
|
1263
|
+
expect(result.code).toContain("[11]"); // capacity + 1
|
|
1264
|
+
expect(result.code).toContain('{"One", "Two"}');
|
|
1265
|
+
});
|
|
1266
|
+
|
|
1267
|
+
it("generates string array from arrayType with trailing dimensions", () => {
|
|
1268
|
+
// Simulates: string<10>[2] matrix[3]; (2D string array)
|
|
1269
|
+
const typeCtx = {
|
|
1270
|
+
stringType: () => null,
|
|
1271
|
+
arrayType: () => ({
|
|
1272
|
+
stringType: () => ({
|
|
1273
|
+
INTEGER_LITERAL: () => ({ getText: () => "10" }),
|
|
1274
|
+
}),
|
|
1275
|
+
arrayTypeDimension: () => [
|
|
1276
|
+
{ expression: () => ({ getText: () => "2" }) },
|
|
1277
|
+
],
|
|
1278
|
+
}),
|
|
1279
|
+
} as never;
|
|
1280
|
+
|
|
1281
|
+
const trailingDims = [
|
|
1282
|
+
{ expression: () => ({ getText: () => "3" }) },
|
|
1283
|
+
] as never[];
|
|
1284
|
+
|
|
1285
|
+
const result = StringDeclHelper.generateStringDecl(
|
|
1286
|
+
typeCtx,
|
|
1287
|
+
"matrix",
|
|
1288
|
+
null,
|
|
1289
|
+
trailingDims,
|
|
1290
|
+
{ extern: "", const: "", atomic: "", volatile: "" },
|
|
1291
|
+
false,
|
|
1292
|
+
defaultCallbacks,
|
|
1293
|
+
);
|
|
1294
|
+
|
|
1295
|
+
expect(result.handled).toBe(true);
|
|
1296
|
+
expect(result.code).toBe("char matrix[2][3][11] = {0};");
|
|
1297
|
+
});
|
|
1298
|
+
|
|
1299
|
+
it("throws error for unsized string array from arrayType", () => {
|
|
1300
|
+
// Simulates: string[4] items; (missing capacity)
|
|
1301
|
+
const typeCtx = {
|
|
1302
|
+
stringType: () => null,
|
|
1303
|
+
arrayType: () => ({
|
|
1304
|
+
stringType: () => ({
|
|
1305
|
+
INTEGER_LITERAL: () => null, // No capacity
|
|
1306
|
+
}),
|
|
1307
|
+
arrayTypeDimension: () => [
|
|
1308
|
+
{ expression: () => ({ getText: () => "4" }) },
|
|
1309
|
+
],
|
|
1310
|
+
}),
|
|
1311
|
+
} as never;
|
|
1312
|
+
|
|
1313
|
+
expect(() =>
|
|
1314
|
+
StringDeclHelper.generateStringDecl(
|
|
1315
|
+
typeCtx,
|
|
1316
|
+
"items",
|
|
1317
|
+
null,
|
|
1318
|
+
[],
|
|
1319
|
+
{ extern: "", const: "", atomic: "", volatile: "" },
|
|
1320
|
+
false,
|
|
1321
|
+
defaultCallbacks,
|
|
1322
|
+
),
|
|
1323
|
+
).toThrow("String arrays require explicit capacity");
|
|
1324
|
+
});
|
|
1325
|
+
|
|
1326
|
+
it("validates element count matches declared size from arrayType", () => {
|
|
1327
|
+
const callbacks = {
|
|
1328
|
+
...defaultCallbacks,
|
|
1329
|
+
generateExpression: vi.fn(() => {
|
|
1330
|
+
CodeGenState.lastArrayInitCount = 2; // Only 2 elements
|
|
1331
|
+
CodeGenState.lastArrayFillValue = undefined;
|
|
1332
|
+
return '{"One", "Two"}';
|
|
1333
|
+
}),
|
|
1334
|
+
};
|
|
1335
|
+
|
|
1336
|
+
// Simulates: string<10>[4] items <- ["One", "Two"]; (size mismatch)
|
|
1337
|
+
const typeCtx = {
|
|
1338
|
+
stringType: () => null,
|
|
1339
|
+
arrayType: () => ({
|
|
1340
|
+
stringType: () => ({
|
|
1341
|
+
INTEGER_LITERAL: () => ({ getText: () => "10" }),
|
|
1342
|
+
}),
|
|
1343
|
+
arrayTypeDimension: () => [
|
|
1344
|
+
{ expression: () => ({ getText: () => "4" }) }, // Declared size = 4
|
|
1345
|
+
],
|
|
1346
|
+
}),
|
|
1347
|
+
} as never;
|
|
1348
|
+
|
|
1349
|
+
const expression = {
|
|
1350
|
+
getText: () => '["One", "Two"]',
|
|
1351
|
+
} as never;
|
|
1352
|
+
|
|
1353
|
+
expect(() =>
|
|
1354
|
+
StringDeclHelper.generateStringDecl(
|
|
1355
|
+
typeCtx,
|
|
1356
|
+
"items",
|
|
1357
|
+
expression,
|
|
1358
|
+
[],
|
|
1359
|
+
{ extern: "", const: "", atomic: "", volatile: "" },
|
|
1360
|
+
false,
|
|
1361
|
+
callbacks,
|
|
1362
|
+
),
|
|
1363
|
+
).toThrow("Array size mismatch - declared [4] but got 2 elements");
|
|
1364
|
+
});
|
|
1365
|
+
|
|
1366
|
+
it("tracks local arrays from arrayType in localArrays set", () => {
|
|
1367
|
+
const typeCtx = {
|
|
1368
|
+
stringType: () => null,
|
|
1369
|
+
arrayType: () => ({
|
|
1370
|
+
stringType: () => ({
|
|
1371
|
+
INTEGER_LITERAL: () => ({ getText: () => "20" }),
|
|
1372
|
+
}),
|
|
1373
|
+
arrayTypeDimension: () => [
|
|
1374
|
+
{ expression: () => ({ getText: () => "3" }) },
|
|
1375
|
+
],
|
|
1376
|
+
}),
|
|
1377
|
+
} as never;
|
|
1378
|
+
|
|
1379
|
+
StringDeclHelper.generateStringDecl(
|
|
1380
|
+
typeCtx,
|
|
1381
|
+
"tracked",
|
|
1382
|
+
null,
|
|
1383
|
+
[],
|
|
1384
|
+
{ extern: "", const: "", atomic: "", volatile: "" },
|
|
1385
|
+
false,
|
|
1386
|
+
defaultCallbacks,
|
|
1387
|
+
);
|
|
1388
|
+
|
|
1389
|
+
expect(CodeGenState.localArrays.has("tracked")).toBe(true);
|
|
1390
|
+
});
|
|
1391
|
+
|
|
1392
|
+
it("generates string array from arrayType with modifiers", () => {
|
|
1393
|
+
const typeCtx = {
|
|
1394
|
+
stringType: () => null,
|
|
1395
|
+
arrayType: () => ({
|
|
1396
|
+
stringType: () => ({
|
|
1397
|
+
INTEGER_LITERAL: () => ({ getText: () => "8" }),
|
|
1398
|
+
}),
|
|
1399
|
+
arrayTypeDimension: () => [
|
|
1400
|
+
{ expression: () => ({ getText: () => "2" }) },
|
|
1401
|
+
],
|
|
1402
|
+
}),
|
|
1403
|
+
} as never;
|
|
1404
|
+
|
|
1405
|
+
const result = StringDeclHelper.generateStringDecl(
|
|
1406
|
+
typeCtx,
|
|
1407
|
+
"data",
|
|
1408
|
+
null,
|
|
1409
|
+
[],
|
|
1410
|
+
{
|
|
1411
|
+
extern: "extern ",
|
|
1412
|
+
const: "const ",
|
|
1413
|
+
atomic: "",
|
|
1414
|
+
volatile: "volatile ",
|
|
1415
|
+
},
|
|
1416
|
+
true,
|
|
1417
|
+
defaultCallbacks,
|
|
1418
|
+
);
|
|
1419
|
+
|
|
1420
|
+
expect(result.handled).toBe(true);
|
|
1421
|
+
expect(result.code).toContain("extern const volatile char data[2][9]");
|
|
1422
|
+
});
|
|
1423
|
+
});
|
|
1157
1424
|
});
|
|
@@ -203,70 +203,6 @@ describe("SymbolLookupHelper", () => {
|
|
|
203
203
|
});
|
|
204
204
|
});
|
|
205
205
|
|
|
206
|
-
describe("isCppType", () => {
|
|
207
|
-
it("returns false when symbolTable is null", () => {
|
|
208
|
-
expect(SymbolLookupHelper.isCppType(null, "MyType")).toBe(false);
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
it("returns false when symbolTable is undefined", () => {
|
|
212
|
-
expect(SymbolLookupHelper.isCppType(undefined, "MyType")).toBe(false);
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
it("returns true for C++ struct", () => {
|
|
216
|
-
const mockTable = {
|
|
217
|
-
getOverloads: () => [
|
|
218
|
-
{ kind: "struct" as const, sourceLanguage: ESourceLanguage.Cpp },
|
|
219
|
-
],
|
|
220
|
-
};
|
|
221
|
-
expect(SymbolLookupHelper.isCppType(mockTable, "MyStruct")).toBe(true);
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
it("returns true for C++ class", () => {
|
|
225
|
-
const mockTable = {
|
|
226
|
-
getOverloads: () => [
|
|
227
|
-
{ kind: "class" as const, sourceLanguage: ESourceLanguage.Cpp },
|
|
228
|
-
],
|
|
229
|
-
};
|
|
230
|
-
expect(SymbolLookupHelper.isCppType(mockTable, "MyClass")).toBe(true);
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
it("returns true for C++ type alias", () => {
|
|
234
|
-
const mockTable = {
|
|
235
|
-
getOverloads: () => [
|
|
236
|
-
{ kind: "type" as const, sourceLanguage: ESourceLanguage.Cpp },
|
|
237
|
-
],
|
|
238
|
-
};
|
|
239
|
-
expect(SymbolLookupHelper.isCppType(mockTable, "MyType")).toBe(true);
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
it("returns false for C struct", () => {
|
|
243
|
-
const mockTable = {
|
|
244
|
-
getOverloads: () => [
|
|
245
|
-
{ kind: "struct" as const, sourceLanguage: ESourceLanguage.C },
|
|
246
|
-
],
|
|
247
|
-
};
|
|
248
|
-
expect(SymbolLookupHelper.isCppType(mockTable, "MyStruct")).toBe(false);
|
|
249
|
-
});
|
|
250
|
-
|
|
251
|
-
it("returns false for C++ function", () => {
|
|
252
|
-
const mockTable = {
|
|
253
|
-
getOverloads: () => [
|
|
254
|
-
{ kind: "function" as const, sourceLanguage: ESourceLanguage.Cpp },
|
|
255
|
-
],
|
|
256
|
-
};
|
|
257
|
-
expect(SymbolLookupHelper.isCppType(mockTable, "myFunc")).toBe(false);
|
|
258
|
-
});
|
|
259
|
-
|
|
260
|
-
it("returns false for C++ enum", () => {
|
|
261
|
-
const mockTable = {
|
|
262
|
-
getOverloads: () => [
|
|
263
|
-
{ kind: "enum" as const, sourceLanguage: ESourceLanguage.Cpp },
|
|
264
|
-
],
|
|
265
|
-
};
|
|
266
|
-
expect(SymbolLookupHelper.isCppType(mockTable, "MyEnum")).toBe(false);
|
|
267
|
-
});
|
|
268
|
-
});
|
|
269
|
-
|
|
270
206
|
describe("isCNextFunction", () => {
|
|
271
207
|
it("returns false when symbolTable is null", () => {
|
|
272
208
|
expect(SymbolLookupHelper.isCNextFunction(null, "myFunc")).toBe(false);
|
|
@@ -183,5 +183,106 @@ describe("TypeRegistrationEngine", () => {
|
|
|
183
183
|
|
|
184
184
|
expect(mockCallbacks.requireInclude).toHaveBeenCalledWith("string");
|
|
185
185
|
});
|
|
186
|
+
|
|
187
|
+
it("registers string array types with correct dimensions (Issue #1029)", () => {
|
|
188
|
+
const source = `string<32>[4] items;`;
|
|
189
|
+
const tree = CNextSourceParser.parse(source).tree;
|
|
190
|
+
|
|
191
|
+
TypeRegistrationEngine.register(tree, mockCallbacks);
|
|
192
|
+
|
|
193
|
+
const info = CodeGenState.getVariableTypeInfo("items");
|
|
194
|
+
expect(info).not.toBeNull();
|
|
195
|
+
expect(info?.baseType).toBe("char");
|
|
196
|
+
expect(info?.isArray).toBe(true);
|
|
197
|
+
expect(info?.isString).toBe(true);
|
|
198
|
+
expect(info?.stringCapacity).toBe(32);
|
|
199
|
+
// Dimensions: [4] for array, [33] for string capacity + null terminator
|
|
200
|
+
expect(info?.arrayDimensions).toEqual([4, 33]);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it("registers multi-dimensional string arrays (Issue #1029)", () => {
|
|
204
|
+
const source = `string<16>[2][3] matrix;`;
|
|
205
|
+
const tree = CNextSourceParser.parse(source).tree;
|
|
206
|
+
|
|
207
|
+
TypeRegistrationEngine.register(tree, mockCallbacks);
|
|
208
|
+
|
|
209
|
+
const info = CodeGenState.getVariableTypeInfo("matrix");
|
|
210
|
+
expect(info).not.toBeNull();
|
|
211
|
+
expect(info?.baseType).toBe("char");
|
|
212
|
+
expect(info?.isArray).toBe(true);
|
|
213
|
+
expect(info?.isString).toBe(true);
|
|
214
|
+
expect(info?.stringCapacity).toBe(16);
|
|
215
|
+
// Dimensions: [2][3] for array, [17] for string capacity + null terminator
|
|
216
|
+
expect(info?.arrayDimensions).toEqual([2, 3, 17]);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it("requires string include for string array types (Issue #1029)", () => {
|
|
220
|
+
const source = `string<32>[4] items;`;
|
|
221
|
+
const tree = CNextSourceParser.parse(source).tree;
|
|
222
|
+
|
|
223
|
+
TypeRegistrationEngine.register(tree, mockCallbacks);
|
|
224
|
+
|
|
225
|
+
expect(mockCallbacks.requireInclude).toHaveBeenCalledWith("string");
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it("registers global type arrays", () => {
|
|
229
|
+
// Set up a scope context to test global.Type[N] pattern
|
|
230
|
+
CodeGenState.currentScope = "Motor";
|
|
231
|
+
|
|
232
|
+
const source = `
|
|
233
|
+
scope Motor {
|
|
234
|
+
enum State { OFF, ON }
|
|
235
|
+
global.State[3] states;
|
|
236
|
+
}
|
|
237
|
+
`;
|
|
238
|
+
const tree = CNextSourceParser.parse(source).tree;
|
|
239
|
+
|
|
240
|
+
TypeRegistrationEngine.register(tree, mockCallbacks);
|
|
241
|
+
|
|
242
|
+
const info = CodeGenState.getVariableTypeInfo("Motor_states");
|
|
243
|
+
expect(info).not.toBeNull();
|
|
244
|
+
expect(info?.baseType).toBe("State");
|
|
245
|
+
expect(info?.isArray).toBe(true);
|
|
246
|
+
|
|
247
|
+
CodeGenState.currentScope = null;
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it("registers qualified type arrays (Scope.Type[N])", () => {
|
|
251
|
+
const source = `
|
|
252
|
+
scope Motor {
|
|
253
|
+
public enum State { OFF, ON }
|
|
254
|
+
}
|
|
255
|
+
Motor.State[4] allStates;
|
|
256
|
+
`;
|
|
257
|
+
const tree = CNextSourceParser.parse(source).tree;
|
|
258
|
+
|
|
259
|
+
TypeRegistrationEngine.register(tree, mockCallbacks);
|
|
260
|
+
|
|
261
|
+
const info = CodeGenState.getVariableTypeInfo("allStates");
|
|
262
|
+
expect(info).not.toBeNull();
|
|
263
|
+
expect(info?.baseType).toBe("Motor_State");
|
|
264
|
+
expect(info?.isArray).toBe(true);
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
it("registers scoped type arrays (this.Type[N])", () => {
|
|
268
|
+
CodeGenState.currentScope = "Motor";
|
|
269
|
+
|
|
270
|
+
const source = `
|
|
271
|
+
scope Motor {
|
|
272
|
+
enum State { OFF, ON }
|
|
273
|
+
this.State[2] localStates;
|
|
274
|
+
}
|
|
275
|
+
`;
|
|
276
|
+
const tree = CNextSourceParser.parse(source).tree;
|
|
277
|
+
|
|
278
|
+
TypeRegistrationEngine.register(tree, mockCallbacks);
|
|
279
|
+
|
|
280
|
+
const info = CodeGenState.getVariableTypeInfo("Motor_localStates");
|
|
281
|
+
expect(info).not.toBeNull();
|
|
282
|
+
expect(info?.baseType).toBe("Motor_State");
|
|
283
|
+
expect(info?.isArray).toBe(true);
|
|
284
|
+
|
|
285
|
+
CodeGenState.currentScope = null;
|
|
286
|
+
});
|
|
186
287
|
});
|
|
187
288
|
});
|
|
@@ -127,8 +127,8 @@ describe("VariableDeclHelper", () => {
|
|
|
127
127
|
}).not.toThrow();
|
|
128
128
|
});
|
|
129
129
|
|
|
130
|
-
it("allows empty dimension for size inference", () => {
|
|
131
|
-
const varDecl = parseVarDecl("u8
|
|
130
|
+
it("allows empty dimension for size inference in arrayType", () => {
|
|
131
|
+
const varDecl = parseVarDecl("u8[] arr <- [1, 2, 3];");
|
|
132
132
|
const typeCtx = varDecl.type();
|
|
133
133
|
expect(() => {
|
|
134
134
|
VariableDeclHelper.validateArrayDeclarationSyntax(
|
|
@@ -139,7 +139,19 @@ describe("VariableDeclHelper", () => {
|
|
|
139
139
|
}).not.toThrow();
|
|
140
140
|
});
|
|
141
141
|
|
|
142
|
-
it("
|
|
142
|
+
it("rejects empty dimension with C-style trailing brackets (Issue #1017)", () => {
|
|
143
|
+
const varDecl = parseVarDecl("u8 arr[] <- [1, 2, 3];");
|
|
144
|
+
const typeCtx = varDecl.type();
|
|
145
|
+
expect(() => {
|
|
146
|
+
VariableDeclHelper.validateArrayDeclarationSyntax(
|
|
147
|
+
varDecl,
|
|
148
|
+
typeCtx,
|
|
149
|
+
"arr",
|
|
150
|
+
);
|
|
151
|
+
}).toThrow("C-style array declaration is not allowed");
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it("rejects multi-dimensional C-style (Issue #1014)", () => {
|
|
143
155
|
const varDecl = parseVarDecl("u8 matrix[4][4];");
|
|
144
156
|
const typeCtx = varDecl.type();
|
|
145
157
|
expect(() => {
|
|
@@ -148,10 +160,22 @@ describe("VariableDeclHelper", () => {
|
|
|
148
160
|
typeCtx,
|
|
149
161
|
"matrix",
|
|
150
162
|
);
|
|
163
|
+
}).toThrow("C-style array declaration is not allowed");
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it("allows string type with arrayType syntax", () => {
|
|
167
|
+
const varDecl = parseVarDecl("string<32>[4] names;");
|
|
168
|
+
const typeCtx = varDecl.type();
|
|
169
|
+
expect(() => {
|
|
170
|
+
VariableDeclHelper.validateArrayDeclarationSyntax(
|
|
171
|
+
varDecl,
|
|
172
|
+
typeCtx,
|
|
173
|
+
"names",
|
|
174
|
+
);
|
|
151
175
|
}).not.toThrow();
|
|
152
176
|
});
|
|
153
177
|
|
|
154
|
-
it("
|
|
178
|
+
it("rejects string type with C-style trailing brackets (Issue #1016)", () => {
|
|
155
179
|
const varDecl = parseVarDecl("string<32> names[4];");
|
|
156
180
|
const typeCtx = varDecl.type();
|
|
157
181
|
expect(() => {
|
|
@@ -160,7 +184,7 @@ describe("VariableDeclHelper", () => {
|
|
|
160
184
|
typeCtx,
|
|
161
185
|
"names",
|
|
162
186
|
);
|
|
163
|
-
}).
|
|
187
|
+
}).toThrow("C-style array declaration is not allowed");
|
|
164
188
|
});
|
|
165
189
|
|
|
166
190
|
it("rejects C-style single dimension for primitives", () => {
|
|
@@ -10,7 +10,8 @@ interface ICallbackTypeInfo {
|
|
|
10
10
|
name: string;
|
|
11
11
|
type: string; // C type
|
|
12
12
|
isConst: boolean;
|
|
13
|
-
isPointer: boolean; // Non-array params become pointers
|
|
13
|
+
isPointer: boolean; // Non-array params become pointers (C mode only for structs)
|
|
14
|
+
isStruct: boolean; // True if parameter type is a struct (ADR-006 reference semantics)
|
|
14
15
|
isArray: boolean; // Array parameters pass naturally as pointers
|
|
15
16
|
arrayDims: string; // Array dimensions if applicable
|
|
16
17
|
}>;
|
|
@@ -66,6 +66,13 @@ interface IParameterInput {
|
|
|
66
66
|
* When the C typedef has `const T*`, this preserves const on the generated param.
|
|
67
67
|
*/
|
|
68
68
|
forceConst?: boolean;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Issue #995: Whether the parameter type is an opaque handle (incomplete struct typedef).
|
|
72
|
+
* This is a pass-through flag; the rule (suppress auto-const, force pointer) is applied
|
|
73
|
+
* in ParameterSignatureBuilder to avoid dual code paths.
|
|
74
|
+
*/
|
|
75
|
+
isOpaqueHandle?: boolean;
|
|
69
76
|
}
|
|
70
77
|
|
|
71
78
|
export default IParameterInput;
|
|
@@ -116,6 +116,14 @@ abstract class BaseHeaderGenerator {
|
|
|
116
116
|
...HeaderGeneratorUtils.generateEnumSection(groups.enums, typeInput),
|
|
117
117
|
...HeaderGeneratorUtils.generateBitmapSection(groups.bitmaps, typeInput),
|
|
118
118
|
...HeaderGeneratorUtils.generateTypeAliasSection(groups.types),
|
|
119
|
+
...HeaderGeneratorUtils.generateCallbackStructForwardDecls(
|
|
120
|
+
groups.structs,
|
|
121
|
+
typeInput,
|
|
122
|
+
),
|
|
123
|
+
...HeaderGeneratorUtils.generateCallbackTypedefSection(
|
|
124
|
+
typeInput,
|
|
125
|
+
options.cppMode,
|
|
126
|
+
),
|
|
119
127
|
...HeaderGeneratorUtils.generateStructSection(
|
|
120
128
|
groups.structs,
|
|
121
129
|
groups.classes,
|
|
@@ -416,6 +416,81 @@ class HeaderGeneratorUtils {
|
|
|
416
416
|
return lines;
|
|
417
417
|
}
|
|
418
418
|
|
|
419
|
+
/**
|
|
420
|
+
* ADR-029: Generate forward declarations for structs used in callback typedefs.
|
|
421
|
+
* This ensures struct types can be used in callback parameter types before
|
|
422
|
+
* the full struct definition.
|
|
423
|
+
*/
|
|
424
|
+
static generateCallbackStructForwardDecls(
|
|
425
|
+
structs: IHeaderSymbol[],
|
|
426
|
+
typeInput?: IHeaderTypeInput,
|
|
427
|
+
): string[] {
|
|
428
|
+
if (!typeInput?.callbackTypes || typeInput.callbackTypes.size === 0) {
|
|
429
|
+
return [];
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// Collect struct types that are used in callback parameters
|
|
433
|
+
const usedStructTypes = new Set<string>();
|
|
434
|
+
for (const [, cbInfo] of typeInput.callbackTypes) {
|
|
435
|
+
for (const p of cbInfo.parameters) {
|
|
436
|
+
if (p.isStruct) {
|
|
437
|
+
usedStructTypes.add(p.type);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
if (usedStructTypes.size === 0) {
|
|
443
|
+
return [];
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// Get local struct names to only forward-declare those
|
|
447
|
+
const localStructNames = new Set(structs.map((s) => s.name));
|
|
448
|
+
|
|
449
|
+
const lines: string[] = [];
|
|
450
|
+
for (const structType of usedStructTypes) {
|
|
451
|
+
if (localStructNames.has(structType)) {
|
|
452
|
+
lines.push(`typedef struct ${structType} ${structType};`);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
return lines.length > 0 ? [...lines, ""] : [];
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* ADR-029: Generate callback typedef section
|
|
461
|
+
* Generates function pointer typedefs for callbacks used as struct field types
|
|
462
|
+
*/
|
|
463
|
+
static generateCallbackTypedefSection(
|
|
464
|
+
typeInput?: IHeaderTypeInput,
|
|
465
|
+
isCppMode?: boolean,
|
|
466
|
+
): string[] {
|
|
467
|
+
if (!typeInput?.callbackTypes || typeInput.callbackTypes.size === 0) {
|
|
468
|
+
return [];
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
const lines: string[] = ["/* Callback typedefs */"];
|
|
472
|
+
for (const [, cbInfo] of typeInput.callbackTypes) {
|
|
473
|
+
const params =
|
|
474
|
+
cbInfo.parameters.length > 0
|
|
475
|
+
? cbInfo.parameters
|
|
476
|
+
.map((p) => {
|
|
477
|
+
// ADR-006: Struct parameters become pointers (C) or references (C++)
|
|
478
|
+
if (p.isStruct) {
|
|
479
|
+
const ptrOrRef = isCppMode ? "&" : "*";
|
|
480
|
+
return `${p.type}${ptrOrRef}`;
|
|
481
|
+
}
|
|
482
|
+
return p.type;
|
|
483
|
+
})
|
|
484
|
+
.join(", ")
|
|
485
|
+
: "void";
|
|
486
|
+
lines.push(
|
|
487
|
+
`typedef ${cbInfo.returnType} (*${cbInfo.typedefName})(${params});`,
|
|
488
|
+
);
|
|
489
|
+
}
|
|
490
|
+
lines.push("");
|
|
491
|
+
return lines;
|
|
492
|
+
}
|
|
493
|
+
|
|
419
494
|
/**
|
|
420
495
|
* Generate struct and class definitions section
|
|
421
496
|
*/
|