c-next 0.2.16 → 0.2.18
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 +18 -2
- package/dist/index.js +8897 -6260
- package/dist/index.js.map +4 -4
- package/grammar/CNext.g4 +12 -0
- package/package.json +4 -2
- package/src/transpiler/Transpiler.ts +376 -48
- package/src/transpiler/__tests__/DualCodePaths.test.ts +1 -1
- package/src/transpiler/__tests__/compileCommandsDiscovery.integration.test.ts +94 -0
- package/src/transpiler/__tests__/externalSymbolRecovery.integration.test.ts +215 -0
- package/src/transpiler/logic/__tests__/detectAssemblySyntax.test.ts +116 -0
- package/src/transpiler/logic/analysis/FunctionCallAnalyzer.ts +65 -10
- package/src/transpiler/logic/analysis/InitializationAnalyzer.ts +186 -14
- package/src/transpiler/logic/analysis/MixedTypeCategoryAnalyzer.ts +408 -0
- package/src/transpiler/logic/analysis/PassByValueAnalyzer.ts +16 -97
- package/src/transpiler/logic/analysis/ReturnPathAnalyzer.ts +171 -0
- 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__/MixedTypeCategoryAnalyzer.test.ts +346 -0
- package/src/transpiler/logic/analysis/__tests__/ReturnPathAnalyzer.test.ts +232 -0
- package/src/transpiler/logic/analysis/__tests__/SignedShiftAnalyzer.test.ts +211 -0
- package/src/transpiler/logic/analysis/runAnalyzers.ts +23 -2
- package/src/transpiler/logic/analysis/types/IMixedTypeCategoryError.ts +17 -0
- package/src/transpiler/logic/analysis/types/IReturnPathError.ts +12 -0
- package/src/transpiler/logic/detectAssemblySyntax.ts +80 -0
- package/src/transpiler/logic/parser/grammar/CNext.interp +4 -1
- package/src/transpiler/logic/parser/grammar/CNext.tokens +169 -167
- package/src/transpiler/logic/parser/grammar/CNextLexer.interp +4 -1
- package/src/transpiler/logic/parser/grammar/CNextLexer.tokens +169 -167
- package/src/transpiler/logic/parser/grammar/CNextLexer.ts +545 -541
- package/src/transpiler/logic/parser/grammar/CNextListener.ts +11 -0
- package/src/transpiler/logic/parser/grammar/CNextParser.ts +1318 -1178
- package/src/transpiler/logic/parser/grammar/CNextVisitor.ts +7 -0
- package/src/transpiler/logic/preprocessor/CompileCommandsReader.ts +332 -0
- package/src/transpiler/logic/preprocessor/ExternalDeclarationOracle.ts +202 -0
- package/src/transpiler/logic/preprocessor/Preprocessor.ts +24 -6
- package/src/transpiler/logic/preprocessor/ToolchainDetector.ts +42 -1
- package/src/transpiler/logic/preprocessor/__tests__/CompileCommandsReader.test.ts +216 -0
- package/src/transpiler/logic/preprocessor/__tests__/ExternalDeclarationOracle.test.ts +153 -0
- package/src/transpiler/logic/preprocessor/__tests__/Preprocessor.test.ts +43 -18
- package/src/transpiler/logic/preprocessor/__tests__/ToolchainDetector.crossCompiler.test.ts +75 -0
- package/src/transpiler/logic/preprocessor/__tests__/fixtures/oracle/dependent.h +7 -0
- package/src/transpiler/logic/preprocessor/__tests__/fixtures/oracle/predecessor.h +5 -0
- package/src/transpiler/logic/preprocessor/types/ICompileCommandsResult.ts +14 -0
- package/src/transpiler/logic/preprocessor/types/IPreprocessOptions.ts +17 -0
- package/src/transpiler/logic/symbols/SymbolTable.ts +62 -2
- package/src/transpiler/logic/symbols/__tests__/SymbolTable.test.ts +55 -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/MisraSuppressionUtils.ts +52 -0
- package/src/transpiler/output/__tests__/MisraSuppressionUtils.test.ts +67 -0
- package/src/transpiler/output/codegen/CodeGenerator.ts +196 -98
- package/src/transpiler/output/codegen/TypeResolver.ts +148 -0
- package/src/transpiler/output/codegen/TypeValidator.ts +179 -81
- package/src/transpiler/output/codegen/__tests__/CodeGenerator.coverage.test.ts +165 -17
- package/src/transpiler/output/codegen/__tests__/CodeGenerator.test.ts +163 -35
- package/src/transpiler/output/codegen/__tests__/TrackVariableTypeHelpers.test.ts +2 -2
- package/src/transpiler/output/codegen/__tests__/TypeResolver.test.ts +93 -0
- package/src/transpiler/output/codegen/__tests__/TypeValidator.alwaysTrueLoop.test.ts +91 -0
- package/src/transpiler/output/codegen/__tests__/TypeValidator.test.ts +97 -14
- package/src/transpiler/output/codegen/assignment/AssignmentClassifier.ts +13 -0
- package/src/transpiler/output/codegen/assignment/AssignmentKind.ts +1 -1
- package/src/transpiler/output/codegen/assignment/handlers/AccessPatternHandlers.ts +20 -12
- package/src/transpiler/output/codegen/assignment/handlers/ArrayHandlers.ts +424 -22
- package/src/transpiler/output/codegen/assignment/handlers/BitAccessHandlers.ts +31 -18
- package/src/transpiler/output/codegen/assignment/handlers/BitmapHandlers.ts +7 -8
- package/src/transpiler/output/codegen/assignment/handlers/RegisterHandlers.ts +6 -8
- package/src/transpiler/output/codegen/assignment/handlers/RegisterUtils.ts +12 -10
- package/src/transpiler/output/codegen/assignment/handlers/SimpleHandler.ts +3 -7
- package/src/transpiler/output/codegen/assignment/handlers/SpecialHandlers.ts +7 -9
- package/src/transpiler/output/codegen/assignment/handlers/StringHandlers.ts +12 -10
- package/src/transpiler/output/codegen/assignment/handlers/__tests__/ArrayHandlers.test.ts +479 -11
- package/src/transpiler/output/codegen/generators/IOrchestrator.ts +3 -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 +76 -58
- package/src/transpiler/output/codegen/generators/expressions/ExpressionGenerator.ts +9 -2
- package/src/transpiler/output/codegen/generators/expressions/PostfixExpressionGenerator.ts +26 -13
- 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/generators/expressions/__tests__/PostfixExpressionGenerator.test.ts +129 -1
- package/src/transpiler/output/codegen/generators/statements/ControlFlowGenerator.ts +64 -8
- package/src/transpiler/output/codegen/generators/statements/__tests__/ControlFlowGenerator.test.ts +8 -5
- package/src/transpiler/output/codegen/helpers/AssignmentExpectedTypeResolver.ts +30 -2
- 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 +240 -42
- 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__/AssignmentExpectedTypeResolver.test.ts +19 -0
- 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 +322 -9
- 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/subscript/SubscriptClassifier.ts +30 -11
- package/src/transpiler/output/codegen/subscript/TSubscriptKind.ts +1 -1
- package/src/transpiler/output/codegen/subscript/__tests__/SubscriptClassifier.test.ts +38 -6
- 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 +140 -24
- 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 +91 -22
- package/src/transpiler/state/__tests__/CodeGenState.test.ts +253 -11
- package/src/transpiler/types/ICachedFileEntry.ts +7 -0
- package/src/utils/LiteralUtils.ts +23 -0
- package/src/utils/__tests__/LiteralUtils.test.ts +101 -0
- package/src/utils/cache/CacheManager.ts +13 -2
- package/src/utils/cache/__tests__/CacheManager.test.ts +18 -1
- package/src/utils/constants/TypeConstants.ts +13 -0
- package/src/utils/types/IParameterSymbol.ts +1 -0
|
@@ -310,9 +310,12 @@ describe("StringDeclHelper", () => {
|
|
|
310
310
|
});
|
|
311
311
|
|
|
312
312
|
it("allows assignment when source capacity fits", () => {
|
|
313
|
+
// Issue #1044: String variable initialization uses a bounded strncpy
|
|
314
|
+
// (shared with the reassignment path), not an unsafe strcpy (CWE-120).
|
|
313
315
|
const callbacks = {
|
|
314
316
|
...defaultCallbacks,
|
|
315
317
|
getStringExprCapacity: vi.fn(() => 20),
|
|
318
|
+
generateExpression: vi.fn(() => "smallString"),
|
|
316
319
|
};
|
|
317
320
|
|
|
318
321
|
const typeCtx = {
|
|
@@ -336,7 +339,82 @@ describe("StringDeclHelper", () => {
|
|
|
336
339
|
);
|
|
337
340
|
|
|
338
341
|
expect(result.handled).toBe(true);
|
|
339
|
-
|
|
342
|
+
// Issue #1044: bounded copy, not strcpy
|
|
343
|
+
expect(result.code).toContain('char dest[33] = "";');
|
|
344
|
+
expect(result.code).toContain("strncpy(dest, smallString, 32);");
|
|
345
|
+
expect(result.code).toContain("dest[32] = '\\0';");
|
|
346
|
+
expect(result.code).not.toContain("strcpy(dest, smallString)");
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
it("does not indent continuation lines (the block emitter owns indentation) — Issue #1037", () => {
|
|
350
|
+
// indentLevel = 1 (beforeEach). generateBlock prefixes every line of a
|
|
351
|
+
// statement, so the helper must NOT add its own indent or continuation
|
|
352
|
+
// lines double-indent.
|
|
353
|
+
const callbacks = {
|
|
354
|
+
...defaultCallbacks,
|
|
355
|
+
getStringExprCapacity: vi.fn(() => 20),
|
|
356
|
+
generateExpression: vi.fn(() => "smallString"),
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
const typeCtx = {
|
|
360
|
+
stringType: () => ({
|
|
361
|
+
INTEGER_LITERAL: () => ({ getText: () => "32" }),
|
|
362
|
+
}),
|
|
363
|
+
} as never;
|
|
364
|
+
|
|
365
|
+
const expression = {
|
|
366
|
+
getText: () => "smallString",
|
|
367
|
+
} as never;
|
|
368
|
+
|
|
369
|
+
const result = StringDeclHelper.generateStringDecl(
|
|
370
|
+
typeCtx,
|
|
371
|
+
"dest",
|
|
372
|
+
expression,
|
|
373
|
+
[],
|
|
374
|
+
{ extern: "", const: "", atomic: "", volatile: "" },
|
|
375
|
+
false,
|
|
376
|
+
callbacks,
|
|
377
|
+
);
|
|
378
|
+
|
|
379
|
+
const lines = result.code.split("\n");
|
|
380
|
+
expect(lines[0]).toBe('char dest[33] = "";');
|
|
381
|
+
// Continuation line must have no leading whitespace of its own.
|
|
382
|
+
expect(lines[1]).toBe(
|
|
383
|
+
"strncpy(dest, smallString, 32); dest[32] = '\\0';",
|
|
384
|
+
);
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
it("throws error for string variable initialization at global scope", () => {
|
|
388
|
+
// Issue #1030: String variable initialization requires function body
|
|
389
|
+
CodeGenState.inFunctionBody = false;
|
|
390
|
+
const callbacks = {
|
|
391
|
+
...defaultCallbacks,
|
|
392
|
+
getStringExprCapacity: vi.fn(() => 20),
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
const typeCtx = {
|
|
396
|
+
stringType: () => ({
|
|
397
|
+
INTEGER_LITERAL: () => ({ getText: () => "32" }),
|
|
398
|
+
}),
|
|
399
|
+
} as never;
|
|
400
|
+
|
|
401
|
+
const expression = {
|
|
402
|
+
getText: () => "sourceVar",
|
|
403
|
+
} as never;
|
|
404
|
+
|
|
405
|
+
expect(() =>
|
|
406
|
+
StringDeclHelper.generateStringDecl(
|
|
407
|
+
typeCtx,
|
|
408
|
+
"dest",
|
|
409
|
+
expression,
|
|
410
|
+
[],
|
|
411
|
+
{ extern: "", const: "", atomic: "", volatile: "" },
|
|
412
|
+
false,
|
|
413
|
+
callbacks,
|
|
414
|
+
),
|
|
415
|
+
).toThrow(
|
|
416
|
+
"String initialization from variable cannot be used at global scope",
|
|
417
|
+
);
|
|
340
418
|
});
|
|
341
419
|
});
|
|
342
420
|
|
|
@@ -374,12 +452,15 @@ describe("StringDeclHelper", () => {
|
|
|
374
452
|
);
|
|
375
453
|
|
|
376
454
|
expect(result.handled).toBe(true);
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
455
|
+
// Issue #1037: every line carries no indent of its own (the block emitter
|
|
456
|
+
// prefixes each line); continuation lines must not double-indent.
|
|
457
|
+
const concatLines = result.code.split("\n");
|
|
458
|
+
expect(concatLines[0]).toBe('char combined[33] = "";');
|
|
459
|
+
expect(concatLines[1]).toBe("strncpy(combined, str1, 32);");
|
|
460
|
+
expect(concatLines[2]).toBe(
|
|
461
|
+
"strncat(combined, str2, 32 - strlen(combined));",
|
|
381
462
|
);
|
|
382
|
-
expect(
|
|
463
|
+
expect(concatLines[3]).toBe("combined[32] = '\\0';");
|
|
383
464
|
});
|
|
384
465
|
|
|
385
466
|
it("throws error for concatenation at global scope", () => {
|
|
@@ -523,9 +604,11 @@ describe("StringDeclHelper", () => {
|
|
|
523
604
|
);
|
|
524
605
|
|
|
525
606
|
expect(result.handled).toBe(true);
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
expect(
|
|
607
|
+
// Issue #1037: continuation lines carry no indent of their own.
|
|
608
|
+
const subLines = result.code.split("\n");
|
|
609
|
+
expect(subLines[0]).toBe('char sub[11] = "";');
|
|
610
|
+
expect(subLines[1]).toBe("strncpy(sub, srcStr + 0, 5);");
|
|
611
|
+
expect(subLines[2]).toBe("sub[5] = '\\0';");
|
|
529
612
|
});
|
|
530
613
|
|
|
531
614
|
it("throws error for substring at global scope", () => {
|
|
@@ -1154,4 +1237,234 @@ describe("StringDeclHelper", () => {
|
|
|
1154
1237
|
expect(CodeGenState.localArrays.has("tracked")).toBe(true);
|
|
1155
1238
|
});
|
|
1156
1239
|
});
|
|
1240
|
+
|
|
1241
|
+
describe("string arrays from arrayType syntax (Issue #1029)", () => {
|
|
1242
|
+
it("generates string array from arrayType without initializer", () => {
|
|
1243
|
+
// Simulates: string<32>[4] items;
|
|
1244
|
+
const typeCtx = {
|
|
1245
|
+
stringType: () => null, // Not directly on typeCtx
|
|
1246
|
+
arrayType: () => ({
|
|
1247
|
+
stringType: () => ({
|
|
1248
|
+
INTEGER_LITERAL: () => ({ getText: () => "32" }),
|
|
1249
|
+
}),
|
|
1250
|
+
arrayTypeDimension: () => [
|
|
1251
|
+
{ expression: () => ({ getText: () => "4" }) },
|
|
1252
|
+
],
|
|
1253
|
+
}),
|
|
1254
|
+
} as never;
|
|
1255
|
+
|
|
1256
|
+
const result = StringDeclHelper.generateStringDecl(
|
|
1257
|
+
typeCtx,
|
|
1258
|
+
"items",
|
|
1259
|
+
null,
|
|
1260
|
+
[],
|
|
1261
|
+
{ extern: "", const: "", atomic: "", volatile: "" },
|
|
1262
|
+
false,
|
|
1263
|
+
defaultCallbacks,
|
|
1264
|
+
);
|
|
1265
|
+
|
|
1266
|
+
expect(result.handled).toBe(true);
|
|
1267
|
+
expect(result.code).toBe("char items[4][33] = {0};");
|
|
1268
|
+
});
|
|
1269
|
+
|
|
1270
|
+
it("generates string array from arrayType with initializer", () => {
|
|
1271
|
+
const callbacks = {
|
|
1272
|
+
...defaultCallbacks,
|
|
1273
|
+
generateExpression: vi.fn(() => {
|
|
1274
|
+
CodeGenState.lastArrayInitCount = 2;
|
|
1275
|
+
CodeGenState.lastArrayFillValue = undefined;
|
|
1276
|
+
return '{"One", "Two"}';
|
|
1277
|
+
}),
|
|
1278
|
+
};
|
|
1279
|
+
|
|
1280
|
+
// Simulates: string<10>[2] labels <- ["One", "Two"];
|
|
1281
|
+
const typeCtx = {
|
|
1282
|
+
stringType: () => null,
|
|
1283
|
+
arrayType: () => ({
|
|
1284
|
+
stringType: () => ({
|
|
1285
|
+
INTEGER_LITERAL: () => ({ getText: () => "10" }),
|
|
1286
|
+
}),
|
|
1287
|
+
arrayTypeDimension: () => [
|
|
1288
|
+
{ expression: () => ({ getText: () => "2" }) },
|
|
1289
|
+
],
|
|
1290
|
+
}),
|
|
1291
|
+
} as never;
|
|
1292
|
+
|
|
1293
|
+
const expression = {
|
|
1294
|
+
getText: () => '["One", "Two"]',
|
|
1295
|
+
} as never;
|
|
1296
|
+
|
|
1297
|
+
const result = StringDeclHelper.generateStringDecl(
|
|
1298
|
+
typeCtx,
|
|
1299
|
+
"labels",
|
|
1300
|
+
expression,
|
|
1301
|
+
[],
|
|
1302
|
+
{ extern: "", const: "", atomic: "", volatile: "" },
|
|
1303
|
+
false,
|
|
1304
|
+
callbacks,
|
|
1305
|
+
);
|
|
1306
|
+
|
|
1307
|
+
expect(result.handled).toBe(true);
|
|
1308
|
+
expect(result.code).toContain("[2]");
|
|
1309
|
+
expect(result.code).toContain("[11]"); // capacity + 1
|
|
1310
|
+
expect(result.code).toContain('{"One", "Two"}');
|
|
1311
|
+
});
|
|
1312
|
+
|
|
1313
|
+
it("generates string array from arrayType with trailing dimensions", () => {
|
|
1314
|
+
// Simulates: string<10>[2] matrix[3]; (2D string array)
|
|
1315
|
+
const typeCtx = {
|
|
1316
|
+
stringType: () => null,
|
|
1317
|
+
arrayType: () => ({
|
|
1318
|
+
stringType: () => ({
|
|
1319
|
+
INTEGER_LITERAL: () => ({ getText: () => "10" }),
|
|
1320
|
+
}),
|
|
1321
|
+
arrayTypeDimension: () => [
|
|
1322
|
+
{ expression: () => ({ getText: () => "2" }) },
|
|
1323
|
+
],
|
|
1324
|
+
}),
|
|
1325
|
+
} as never;
|
|
1326
|
+
|
|
1327
|
+
const trailingDims = [
|
|
1328
|
+
{ expression: () => ({ getText: () => "3" }) },
|
|
1329
|
+
] as never[];
|
|
1330
|
+
|
|
1331
|
+
const result = StringDeclHelper.generateStringDecl(
|
|
1332
|
+
typeCtx,
|
|
1333
|
+
"matrix",
|
|
1334
|
+
null,
|
|
1335
|
+
trailingDims,
|
|
1336
|
+
{ extern: "", const: "", atomic: "", volatile: "" },
|
|
1337
|
+
false,
|
|
1338
|
+
defaultCallbacks,
|
|
1339
|
+
);
|
|
1340
|
+
|
|
1341
|
+
expect(result.handled).toBe(true);
|
|
1342
|
+
expect(result.code).toBe("char matrix[2][3][11] = {0};");
|
|
1343
|
+
});
|
|
1344
|
+
|
|
1345
|
+
it("throws error for unsized string array from arrayType", () => {
|
|
1346
|
+
// Simulates: string[4] items; (missing capacity)
|
|
1347
|
+
const typeCtx = {
|
|
1348
|
+
stringType: () => null,
|
|
1349
|
+
arrayType: () => ({
|
|
1350
|
+
stringType: () => ({
|
|
1351
|
+
INTEGER_LITERAL: () => null, // No capacity
|
|
1352
|
+
}),
|
|
1353
|
+
arrayTypeDimension: () => [
|
|
1354
|
+
{ expression: () => ({ getText: () => "4" }) },
|
|
1355
|
+
],
|
|
1356
|
+
}),
|
|
1357
|
+
} as never;
|
|
1358
|
+
|
|
1359
|
+
expect(() =>
|
|
1360
|
+
StringDeclHelper.generateStringDecl(
|
|
1361
|
+
typeCtx,
|
|
1362
|
+
"items",
|
|
1363
|
+
null,
|
|
1364
|
+
[],
|
|
1365
|
+
{ extern: "", const: "", atomic: "", volatile: "" },
|
|
1366
|
+
false,
|
|
1367
|
+
defaultCallbacks,
|
|
1368
|
+
),
|
|
1369
|
+
).toThrow("String arrays require explicit capacity");
|
|
1370
|
+
});
|
|
1371
|
+
|
|
1372
|
+
it("validates element count matches declared size from arrayType", () => {
|
|
1373
|
+
const callbacks = {
|
|
1374
|
+
...defaultCallbacks,
|
|
1375
|
+
generateExpression: vi.fn(() => {
|
|
1376
|
+
CodeGenState.lastArrayInitCount = 2; // Only 2 elements
|
|
1377
|
+
CodeGenState.lastArrayFillValue = undefined;
|
|
1378
|
+
return '{"One", "Two"}';
|
|
1379
|
+
}),
|
|
1380
|
+
};
|
|
1381
|
+
|
|
1382
|
+
// Simulates: string<10>[4] items <- ["One", "Two"]; (size mismatch)
|
|
1383
|
+
const typeCtx = {
|
|
1384
|
+
stringType: () => null,
|
|
1385
|
+
arrayType: () => ({
|
|
1386
|
+
stringType: () => ({
|
|
1387
|
+
INTEGER_LITERAL: () => ({ getText: () => "10" }),
|
|
1388
|
+
}),
|
|
1389
|
+
arrayTypeDimension: () => [
|
|
1390
|
+
{ expression: () => ({ getText: () => "4" }) }, // Declared size = 4
|
|
1391
|
+
],
|
|
1392
|
+
}),
|
|
1393
|
+
} as never;
|
|
1394
|
+
|
|
1395
|
+
const expression = {
|
|
1396
|
+
getText: () => '["One", "Two"]',
|
|
1397
|
+
} as never;
|
|
1398
|
+
|
|
1399
|
+
expect(() =>
|
|
1400
|
+
StringDeclHelper.generateStringDecl(
|
|
1401
|
+
typeCtx,
|
|
1402
|
+
"items",
|
|
1403
|
+
expression,
|
|
1404
|
+
[],
|
|
1405
|
+
{ extern: "", const: "", atomic: "", volatile: "" },
|
|
1406
|
+
false,
|
|
1407
|
+
callbacks,
|
|
1408
|
+
),
|
|
1409
|
+
).toThrow("Array size mismatch - declared [4] but got 2 elements");
|
|
1410
|
+
});
|
|
1411
|
+
|
|
1412
|
+
it("tracks local arrays from arrayType in localArrays set", () => {
|
|
1413
|
+
const typeCtx = {
|
|
1414
|
+
stringType: () => null,
|
|
1415
|
+
arrayType: () => ({
|
|
1416
|
+
stringType: () => ({
|
|
1417
|
+
INTEGER_LITERAL: () => ({ getText: () => "20" }),
|
|
1418
|
+
}),
|
|
1419
|
+
arrayTypeDimension: () => [
|
|
1420
|
+
{ expression: () => ({ getText: () => "3" }) },
|
|
1421
|
+
],
|
|
1422
|
+
}),
|
|
1423
|
+
} as never;
|
|
1424
|
+
|
|
1425
|
+
StringDeclHelper.generateStringDecl(
|
|
1426
|
+
typeCtx,
|
|
1427
|
+
"tracked",
|
|
1428
|
+
null,
|
|
1429
|
+
[],
|
|
1430
|
+
{ extern: "", const: "", atomic: "", volatile: "" },
|
|
1431
|
+
false,
|
|
1432
|
+
defaultCallbacks,
|
|
1433
|
+
);
|
|
1434
|
+
|
|
1435
|
+
expect(CodeGenState.localArrays.has("tracked")).toBe(true);
|
|
1436
|
+
});
|
|
1437
|
+
|
|
1438
|
+
it("generates string array from arrayType with modifiers", () => {
|
|
1439
|
+
const typeCtx = {
|
|
1440
|
+
stringType: () => null,
|
|
1441
|
+
arrayType: () => ({
|
|
1442
|
+
stringType: () => ({
|
|
1443
|
+
INTEGER_LITERAL: () => ({ getText: () => "8" }),
|
|
1444
|
+
}),
|
|
1445
|
+
arrayTypeDimension: () => [
|
|
1446
|
+
{ expression: () => ({ getText: () => "2" }) },
|
|
1447
|
+
],
|
|
1448
|
+
}),
|
|
1449
|
+
} as never;
|
|
1450
|
+
|
|
1451
|
+
const result = StringDeclHelper.generateStringDecl(
|
|
1452
|
+
typeCtx,
|
|
1453
|
+
"data",
|
|
1454
|
+
null,
|
|
1455
|
+
[],
|
|
1456
|
+
{
|
|
1457
|
+
extern: "extern ",
|
|
1458
|
+
const: "const ",
|
|
1459
|
+
atomic: "",
|
|
1460
|
+
volatile: "volatile ",
|
|
1461
|
+
},
|
|
1462
|
+
true,
|
|
1463
|
+
defaultCallbacks,
|
|
1464
|
+
);
|
|
1465
|
+
|
|
1466
|
+
expect(result.handled).toBe(true);
|
|
1467
|
+
expect(result.code).toContain("extern const volatile char data[2][9]");
|
|
1468
|
+
});
|
|
1469
|
+
});
|
|
1157
1470
|
});
|
|
@@ -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", () => {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Issue #579: Shared subscript classifier for array vs bit access
|
|
2
|
+
* Issue #579 / Issue #1100: Shared subscript classifier for array vs bit access
|
|
3
3
|
*
|
|
4
4
|
* This utility unifies the classification logic used by:
|
|
5
5
|
* - AssignmentClassifier (assignment path)
|
|
@@ -7,8 +7,28 @@
|
|
|
7
7
|
*
|
|
8
8
|
* The classification rule is:
|
|
9
9
|
* 1. If isArray or isString -> array access
|
|
10
|
-
* 2.
|
|
11
|
-
*
|
|
10
|
+
* 2. Otherwise -> bit manipulation
|
|
11
|
+
*
|
|
12
|
+
* This rule is identical for parameters and local variables. ADR-006 requires
|
|
13
|
+
* array parameters to use explicit array syntax (`u8[8] buf`), which sets
|
|
14
|
+
* isArray on the parameter's type info exactly like a local array declaration
|
|
15
|
+
* does. A scalar-typed parameter (no array brackets) is therefore classified
|
|
16
|
+
* the same way a scalar local variable is: bit manipulation (ADR-007 — "Any
|
|
17
|
+
* integer type can be indexed to access individual bits", with no carve-out
|
|
18
|
+
* for parameters).
|
|
19
|
+
*
|
|
20
|
+
* Issue #1100: A prior version of this classifier treated ANY function
|
|
21
|
+
* parameter without explicit array syntax as array access (`isParameter &&
|
|
22
|
+
* !isArray -> array`), added in #579 to support buffer-style parameters
|
|
23
|
+
* declared without brackets (e.g. `void fillBuffer(u8 buf)`). That blanket
|
|
24
|
+
* rule was a divergent decision path from local-variable classification: it
|
|
25
|
+
* silently broke ADR-007 bit-indexing for scalar parameters (`u32 v; ...
|
|
26
|
+
* v[4]` inside a function became `v[4]` array-subscript C code on a pointer
|
|
27
|
+
* instead of a shift-and-mask bit read), producing invalid/incorrect C
|
|
28
|
+
* for the extremely common embedded pattern of reading a bit out of a
|
|
29
|
+
* hardware-register-shaped scalar parameter. Buffer-style parameters must now
|
|
30
|
+
* use explicit array syntax (`u8[N] buf`), which was already the ADR-006
|
|
31
|
+
* documented and supported spelling — see tests/params/param-array-indexing.test.cnx.
|
|
12
32
|
*/
|
|
13
33
|
import TSubscriptKind from "./TSubscriptKind";
|
|
14
34
|
import TTypeInfo from "../types/TTypeInfo";
|
|
@@ -63,9 +83,14 @@ class SubscriptClassifier {
|
|
|
63
83
|
* Determine if a type should use array access semantics.
|
|
64
84
|
*
|
|
65
85
|
* Array access is used when:
|
|
66
|
-
* - Type is explicitly an array (isArray: true)
|
|
86
|
+
* - Type is explicitly an array (isArray: true) — including array
|
|
87
|
+
* parameters declared with explicit syntax, e.g. `u8[8] buf` (ADR-006)
|
|
67
88
|
* - Type is a string (strings are char arrays)
|
|
68
|
-
*
|
|
89
|
+
*
|
|
90
|
+
* Parameters are NOT special-cased here (Issue #1100): whether a subscript
|
|
91
|
+
* is array access or bit access depends solely on the declared type, the
|
|
92
|
+
* same rule used for local variables. A scalar parameter without array
|
|
93
|
+
* syntax is bit-indexable exactly like a scalar local variable (ADR-007).
|
|
69
94
|
*
|
|
70
95
|
* @param typeInfo - Type information, or null if unknown
|
|
71
96
|
* @returns true if subscript should be treated as array access
|
|
@@ -82,12 +107,6 @@ class SubscriptClassifier {
|
|
|
82
107
|
return true;
|
|
83
108
|
}
|
|
84
109
|
|
|
85
|
-
// Issue #579: Non-array parameter becomes pointer in C (ADR-006)
|
|
86
|
-
// So buf[i] is array access, not bit access
|
|
87
|
-
if (typeInfo.isParameter) {
|
|
88
|
-
return true;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
110
|
// Otherwise it's a scalar - use bit access
|
|
92
111
|
return false;
|
|
93
112
|
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
type TSubscriptKind =
|
|
8
8
|
| "array_element" // Single array element access: arr[i]
|
|
9
|
-
| "array_slice" // Array slice
|
|
9
|
+
| "array_slice" // Array slice: arr[offset, length] (per-element little-endian writes, ADR-007/#1081)
|
|
10
10
|
| "bit_single" // Single bit access: flags[3]
|
|
11
11
|
| "bit_range"; // Bit range extraction: value[start, width]
|
|
12
12
|
|