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
|
@@ -147,6 +147,7 @@ function createMockOrchestrator(overrides?: {
|
|
|
147
147
|
) => TTypeInfo | null;
|
|
148
148
|
validateCrossScopeVisibility?: (scope: string, member: string) => void;
|
|
149
149
|
generateBitMask?: (width: string, is64?: boolean) => string;
|
|
150
|
+
tryEvaluateConstant?: (ctx: unknown) => number | undefined;
|
|
150
151
|
hasFloatBitShadow?: (name: string) => boolean;
|
|
151
152
|
registerFloatBitShadow?: (name: string) => void;
|
|
152
153
|
addPendingTempDeclaration?: (decl: string) => void;
|
|
@@ -172,7 +173,7 @@ function createMockOrchestrator(overrides?: {
|
|
|
172
173
|
isCNextFunction: vi.fn(),
|
|
173
174
|
isStructType: vi.fn(),
|
|
174
175
|
getTypeName: vi.fn(),
|
|
175
|
-
tryEvaluateConstant: vi.fn(),
|
|
176
|
+
tryEvaluateConstant: overrides?.tryEvaluateConstant ?? vi.fn(),
|
|
176
177
|
getZeroInitializer: vi.fn(),
|
|
177
178
|
getExpressionEnumType: vi.fn(),
|
|
178
179
|
isIntegerExpression: vi.fn(),
|
|
@@ -1265,6 +1266,98 @@ describe("PostfixExpressionGenerator", () => {
|
|
|
1265
1266
|
const result = generatePostfixExpression(ctx, input, state, orchestrator);
|
|
1266
1267
|
expect(result.code).toBe("((val) & 0xFF)");
|
|
1267
1268
|
});
|
|
1269
|
+
|
|
1270
|
+
// Issue #1094: a const/macro width must be resolved to its numeric value (with
|
|
1271
|
+
// a "U" suffix to match the literal path) before mask generation, instead of
|
|
1272
|
+
// being passed through as the identifier — otherwise the mask falls back to a
|
|
1273
|
+
// runtime ((1U << W) - 1) that is UB at full width.
|
|
1274
|
+
it("resolves a const width to a precomputed mask (#1094)", () => {
|
|
1275
|
+
const typeRegistry = new Map<string, TTypeInfo>([
|
|
1276
|
+
[
|
|
1277
|
+
"val",
|
|
1278
|
+
{ baseType: "u32", bitWidth: 32, isArray: false, isConst: false },
|
|
1279
|
+
],
|
|
1280
|
+
]);
|
|
1281
|
+
const ctx = createMockPostfixExpressionContext("val", [
|
|
1282
|
+
createMockPostfixOp({
|
|
1283
|
+
expressions: [
|
|
1284
|
+
createMockExpression("0"),
|
|
1285
|
+
createMockExpression("WIDTH"),
|
|
1286
|
+
],
|
|
1287
|
+
}),
|
|
1288
|
+
]);
|
|
1289
|
+
const input = createMockInput({ typeRegistry });
|
|
1290
|
+
const state = createMockState();
|
|
1291
|
+
const generateBitMask = vi.fn(() => "0xFFFFFFFFU");
|
|
1292
|
+
const orchestrator = createMockOrchestrator({
|
|
1293
|
+
generatePrimaryExpr: () => "val",
|
|
1294
|
+
generateExpression: (ctx) => ctx.getText(),
|
|
1295
|
+
generateBitMask,
|
|
1296
|
+
tryEvaluateConstant: () => 32,
|
|
1297
|
+
});
|
|
1298
|
+
|
|
1299
|
+
const result = generatePostfixExpression(ctx, input, state, orchestrator);
|
|
1300
|
+
// Width resolved to "32U" (not the identifier "WIDTH"); u32 operand → not 64-bit.
|
|
1301
|
+
expect(generateBitMask).toHaveBeenCalledWith("32U", false);
|
|
1302
|
+
expect(result.code).toBe("((val) & 0xFFFFFFFFU)");
|
|
1303
|
+
});
|
|
1304
|
+
|
|
1305
|
+
it("passes the raw width when it is not a const (#1094)", () => {
|
|
1306
|
+
const typeRegistry = new Map<string, TTypeInfo>([
|
|
1307
|
+
[
|
|
1308
|
+
"val",
|
|
1309
|
+
{ baseType: "u32", bitWidth: 32, isArray: false, isConst: false },
|
|
1310
|
+
],
|
|
1311
|
+
]);
|
|
1312
|
+
const ctx = createMockPostfixExpressionContext("val", [
|
|
1313
|
+
createMockPostfixOp({
|
|
1314
|
+
expressions: [createMockExpression("0"), createMockExpression("n")],
|
|
1315
|
+
}),
|
|
1316
|
+
]);
|
|
1317
|
+
const input = createMockInput({ typeRegistry });
|
|
1318
|
+
const state = createMockState();
|
|
1319
|
+
const generateBitMask = vi.fn(() => "((1U << n) - 1)");
|
|
1320
|
+
const orchestrator = createMockOrchestrator({
|
|
1321
|
+
generatePrimaryExpr: () => "val",
|
|
1322
|
+
generateExpression: (ctx) => ctx.getText(),
|
|
1323
|
+
generateBitMask,
|
|
1324
|
+
tryEvaluateConstant: () => undefined,
|
|
1325
|
+
});
|
|
1326
|
+
|
|
1327
|
+
generatePostfixExpression(ctx, input, state, orchestrator);
|
|
1328
|
+
// Non-const width: the generated expression string flows through unchanged.
|
|
1329
|
+
expect(generateBitMask).toHaveBeenCalledWith("n", false);
|
|
1330
|
+
});
|
|
1331
|
+
|
|
1332
|
+
it("marks a u64 operand as 64-bit for the mask base (#1094)", () => {
|
|
1333
|
+
const typeRegistry = new Map<string, TTypeInfo>([
|
|
1334
|
+
[
|
|
1335
|
+
"val",
|
|
1336
|
+
{ baseType: "u64", bitWidth: 64, isArray: false, isConst: false },
|
|
1337
|
+
],
|
|
1338
|
+
]);
|
|
1339
|
+
const ctx = createMockPostfixExpressionContext("val", [
|
|
1340
|
+
createMockPostfixOp({
|
|
1341
|
+
expressions: [
|
|
1342
|
+
createMockExpression("0"),
|
|
1343
|
+
createMockExpression("WIDTH"),
|
|
1344
|
+
],
|
|
1345
|
+
}),
|
|
1346
|
+
]);
|
|
1347
|
+
const input = createMockInput({ typeRegistry });
|
|
1348
|
+
const state = createMockState();
|
|
1349
|
+
const generateBitMask = vi.fn(() => "((1ULL << 40U) - 1)");
|
|
1350
|
+
const orchestrator = createMockOrchestrator({
|
|
1351
|
+
generatePrimaryExpr: () => "val",
|
|
1352
|
+
generateExpression: (ctx) => ctx.getText(),
|
|
1353
|
+
generateBitMask,
|
|
1354
|
+
tryEvaluateConstant: () => 40,
|
|
1355
|
+
});
|
|
1356
|
+
|
|
1357
|
+
generatePostfixExpression(ctx, input, state, orchestrator);
|
|
1358
|
+
// u64 operand → is64Bit true, so the mask uses a 64-bit base (1ULL).
|
|
1359
|
+
expect(generateBitMask).toHaveBeenCalledWith("40U", true);
|
|
1360
|
+
});
|
|
1268
1361
|
});
|
|
1269
1362
|
|
|
1270
1363
|
describe("float bit indexing", () => {
|
|
@@ -1371,6 +1464,41 @@ describe("PostfixExpressionGenerator", () => {
|
|
|
1371
1464
|
// Uses union member .u for bit access
|
|
1372
1465
|
expect(result.code).toBe("(__bits_f.u & 0xFF)");
|
|
1373
1466
|
});
|
|
1467
|
+
|
|
1468
|
+
// Issue #1094: the float branch must resolve a const width too, and tell the
|
|
1469
|
+
// mask generator the f64 union is 64-bit.
|
|
1470
|
+
it("resolves a const width for f64 bit indexing (#1094)", () => {
|
|
1471
|
+
const typeRegistry = new Map<string, TTypeInfo>([
|
|
1472
|
+
[
|
|
1473
|
+
"d",
|
|
1474
|
+
{ baseType: "f64", bitWidth: 64, isArray: false, isConst: false },
|
|
1475
|
+
],
|
|
1476
|
+
]);
|
|
1477
|
+
const ctx = createMockPostfixExpressionContext("d", [
|
|
1478
|
+
createMockPostfixOp({
|
|
1479
|
+
expressions: [
|
|
1480
|
+
createMockExpression("0"),
|
|
1481
|
+
createMockExpression("WIDTH"),
|
|
1482
|
+
],
|
|
1483
|
+
}),
|
|
1484
|
+
]);
|
|
1485
|
+
const input = createMockInput({ typeRegistry });
|
|
1486
|
+
const state = createMockState({ inFunctionBody: true });
|
|
1487
|
+
const generateBitMask = vi.fn(() => "0xFFFFFFFFFFFFFFFFULL");
|
|
1488
|
+
const orchestrator = createMockOrchestrator({
|
|
1489
|
+
generatePrimaryExpr: () => "d",
|
|
1490
|
+
generateExpression: (ctx) => ctx.getText(),
|
|
1491
|
+
generateBitMask,
|
|
1492
|
+
tryEvaluateConstant: () => 64,
|
|
1493
|
+
hasFloatBitShadow: () => true,
|
|
1494
|
+
isFloatShadowCurrent: () => true,
|
|
1495
|
+
});
|
|
1496
|
+
|
|
1497
|
+
const result = generatePostfixExpression(ctx, input, state, orchestrator);
|
|
1498
|
+
// Const width "64U" passed; f64 union → 64-bit mask base.
|
|
1499
|
+
expect(generateBitMask).toHaveBeenCalledWith("64U", true);
|
|
1500
|
+
expect(result.code).toBe("(__bits_d.u & 0xFFFFFFFFFFFFFFFFULL)");
|
|
1501
|
+
});
|
|
1374
1502
|
});
|
|
1375
1503
|
|
|
1376
1504
|
describe("function call", () => {
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
WhileStatementContext,
|
|
15
15
|
DoWhileStatementContext,
|
|
16
16
|
ForStatementContext,
|
|
17
|
+
ForeverStatementContext,
|
|
17
18
|
ForVarDeclContext,
|
|
18
19
|
ForAssignmentContext,
|
|
19
20
|
ExpressionContext,
|
|
@@ -167,6 +168,9 @@ const generateWhile = (
|
|
|
167
168
|
// Issue #884: Validate condition is a boolean expression (E0701)
|
|
168
169
|
orchestrator.validateConditionIsBoolean(node.expression(), "while");
|
|
169
170
|
|
|
171
|
+
// ADR-113 / #1075: reject always-true literal condition (E0707)
|
|
172
|
+
orchestrator.validateLoopConditionNotAlwaysTrue(node.expression());
|
|
173
|
+
|
|
170
174
|
const condition = orchestrator.generateExpression(node.expression());
|
|
171
175
|
|
|
172
176
|
// Issue #250: Flush any temp vars from condition BEFORE generating body
|
|
@@ -201,6 +205,9 @@ const generateDoWhile = (
|
|
|
201
205
|
// Issue #884: Validate condition is a boolean expression (E0701)
|
|
202
206
|
orchestrator.validateConditionIsBoolean(node.expression(), "do-while");
|
|
203
207
|
|
|
208
|
+
// ADR-113 / #1075: reject always-true literal condition (E0707)
|
|
209
|
+
orchestrator.validateLoopConditionNotAlwaysTrue(node.expression());
|
|
210
|
+
|
|
204
211
|
const body = orchestrator.generateBlock(node.block());
|
|
205
212
|
const condition = orchestrator.generateExpression(node.expression());
|
|
206
213
|
|
|
@@ -286,6 +293,15 @@ const generateFor = (
|
|
|
286
293
|
): IGeneratorOutput => {
|
|
287
294
|
const effects: TGeneratorEffect[] = [];
|
|
288
295
|
|
|
296
|
+
// ADR-113 / #1075 E0707: a for-loop with no controlling expression (`for (;;)`)
|
|
297
|
+
// is a disguised infinite loop. C-Next has one source form for that — `forever`.
|
|
298
|
+
if (!node.expression()) {
|
|
299
|
+
throw new Error(
|
|
300
|
+
"Error E0707: for-loop has no controlling expression (infinite loop)\n" +
|
|
301
|
+
" help: write 'forever { ... }' for an intentional infinite loop",
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
|
|
289
305
|
let init = "";
|
|
290
306
|
const forInit = node.forInit();
|
|
291
307
|
if (forInit) {
|
|
@@ -313,15 +329,20 @@ const generateFor = (
|
|
|
313
329
|
// Issue #250: Flush temps from init before generating condition
|
|
314
330
|
const initTemps = orchestrator.flushPendingTempDeclarations();
|
|
315
331
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
orchestrator.validateConditionNoFunctionCall(node.expression()!, "for");
|
|
332
|
+
// The empty-header case (`for (;;)`) already threw E0707 above, so the
|
|
333
|
+
// controlling expression is guaranteed present here.
|
|
334
|
+
const conditionExpr = node.expression()!;
|
|
320
335
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
336
|
+
// Issue #254: Validate no function calls in condition (E0702)
|
|
337
|
+
orchestrator.validateConditionNoFunctionCall(conditionExpr, "for");
|
|
338
|
+
|
|
339
|
+
// Issue #884: Validate condition is a boolean expression (E0701)
|
|
340
|
+
orchestrator.validateConditionIsBoolean(conditionExpr, "for");
|
|
341
|
+
|
|
342
|
+
// ADR-113 / #1075: reject always-true literal condition (E0707)
|
|
343
|
+
orchestrator.validateLoopConditionNotAlwaysTrue(conditionExpr);
|
|
344
|
+
|
|
345
|
+
const condition = orchestrator.generateExpression(conditionExpr);
|
|
325
346
|
|
|
326
347
|
// Issue #250: Flush temps from condition before generating update
|
|
327
348
|
const conditionTemps = orchestrator.flushPendingTempDeclarations();
|
|
@@ -357,6 +378,40 @@ const generateFor = (
|
|
|
357
378
|
return { code: result, effects };
|
|
358
379
|
};
|
|
359
380
|
|
|
381
|
+
/**
|
|
382
|
+
* Generate C code for a forever statement (ADR-113).
|
|
383
|
+
*
|
|
384
|
+
* Lowers to the MISRA C:2012 Rule 14.3-compliant infinite-loop idiom `for (;;)`
|
|
385
|
+
* (the carve-out that rule explicitly permits — no controlling expression to be
|
|
386
|
+
* flagged as invariant). A `forever` loop may appear only in a void function
|
|
387
|
+
* (E0705): a value-returning function can never honor its return type if it
|
|
388
|
+
* loops forever.
|
|
389
|
+
*/
|
|
390
|
+
const generateForever = (
|
|
391
|
+
node: ForeverStatementContext,
|
|
392
|
+
_input: IGeneratorInput,
|
|
393
|
+
_state: IGeneratorState,
|
|
394
|
+
orchestrator: IOrchestrator,
|
|
395
|
+
): IGeneratorOutput => {
|
|
396
|
+
const effects: TGeneratorEffect[] = [];
|
|
397
|
+
|
|
398
|
+
// ADR-113 E0705: forever is void-only.
|
|
399
|
+
const returnType = orchestrator.getCurrentFunctionReturnType();
|
|
400
|
+
if (returnType && returnType !== "void") {
|
|
401
|
+
throw new Error(
|
|
402
|
+
"Error E0705: forever loop in non-void function\n" +
|
|
403
|
+
" help: a forever loop never returns a value; make the function return void, " +
|
|
404
|
+
"or use a while loop with an exit condition",
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
const body = orchestrator.generateBlock(node.block());
|
|
409
|
+
const comment =
|
|
410
|
+
"/* MISRA C:2012 Rule 14.3: infinite loop is intentional (C-Next `forever`) */";
|
|
411
|
+
|
|
412
|
+
return { code: `${comment}\nfor (;;) ${body}`, effects };
|
|
413
|
+
};
|
|
414
|
+
|
|
360
415
|
// Export all control flow generators
|
|
361
416
|
const controlFlowGenerators = {
|
|
362
417
|
generateReturn,
|
|
@@ -364,6 +419,7 @@ const controlFlowGenerators = {
|
|
|
364
419
|
generateWhile,
|
|
365
420
|
generateDoWhile,
|
|
366
421
|
generateFor,
|
|
422
|
+
generateForever,
|
|
367
423
|
generateForVarDecl,
|
|
368
424
|
generateForAssignment,
|
|
369
425
|
};
|
package/src/transpiler/output/codegen/generators/statements/__tests__/ControlFlowGenerator.test.ts
CHANGED
|
@@ -365,6 +365,7 @@ function createMockOrchestrator(options?: {
|
|
|
365
365
|
flushPendingTempDeclarations: vi.fn(() => options?.tempDeclarations ?? ""),
|
|
366
366
|
validateConditionNoFunctionCall: vi.fn(),
|
|
367
367
|
validateConditionIsBoolean: vi.fn(),
|
|
368
|
+
validateLoopConditionNotAlwaysTrue: vi.fn(),
|
|
368
369
|
countStringLengthAccesses: vi.fn(() => new Map()),
|
|
369
370
|
countBlockLengthAccesses: vi.fn(),
|
|
370
371
|
setupLengthCache: vi.fn(() => options?.lengthCacheDecls ?? ""),
|
|
@@ -898,15 +899,15 @@ describe("ControlFlowGenerator", () => {
|
|
|
898
899
|
// ========================================================================
|
|
899
900
|
|
|
900
901
|
describe("generateFor", () => {
|
|
901
|
-
it("
|
|
902
|
-
const ctx = createMockForStatement();
|
|
902
|
+
it("rejects an empty for(;;) header as a disguised infinite loop (ADR-113 / #1075, E0707)", () => {
|
|
903
|
+
const ctx = createMockForStatement(); // no controlling expression
|
|
903
904
|
const input = createMockInput();
|
|
904
905
|
const state = createMockState();
|
|
905
906
|
const orchestrator = createMockOrchestrator({ statementCode: "{ }" });
|
|
906
907
|
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
908
|
+
expect(() => generateFor(ctx, input, state, orchestrator)).toThrow(
|
|
909
|
+
/E0707: for-loop has no controlling expression/,
|
|
910
|
+
);
|
|
910
911
|
});
|
|
911
912
|
|
|
912
913
|
it("generates for loop with all parts", () => {
|
|
@@ -947,6 +948,7 @@ describe("ControlFlowGenerator", () => {
|
|
|
947
948
|
operator: createMockAssignmentOperator("<-"),
|
|
948
949
|
}),
|
|
949
950
|
}),
|
|
951
|
+
expr: createMockExpression({ text: "i < 10" }),
|
|
950
952
|
});
|
|
951
953
|
const input = createMockInput();
|
|
952
954
|
const state = createMockState();
|
|
@@ -1007,6 +1009,7 @@ describe("ControlFlowGenerator", () => {
|
|
|
1007
1009
|
init: createMockForInit({
|
|
1008
1010
|
varDecl: createMockForVarDecl(),
|
|
1009
1011
|
}),
|
|
1012
|
+
expr: createMockExpression({ text: "i < 10" }),
|
|
1010
1013
|
});
|
|
1011
1014
|
const input = createMockInput();
|
|
1012
1015
|
const state = createMockState();
|
|
@@ -71,7 +71,10 @@ class AssignmentExpectedTypeResolver {
|
|
|
71
71
|
// Case 2b: Simple array element access (arr[i] <- value)
|
|
72
72
|
// Issue #872: Resolve element type for MISRA 7.2 U suffix
|
|
73
73
|
if (identifiers.length === 1 && hasSubscript) {
|
|
74
|
-
return AssignmentExpectedTypeResolver.resolveForArrayElement(
|
|
74
|
+
return AssignmentExpectedTypeResolver.resolveForArrayElement(
|
|
75
|
+
baseId,
|
|
76
|
+
postfixOps,
|
|
77
|
+
);
|
|
75
78
|
}
|
|
76
79
|
|
|
77
80
|
// Case 2c: Member chain with array access (struct.arr[i] <- value)
|
|
@@ -87,6 +90,17 @@ class AssignmentExpectedTypeResolver {
|
|
|
87
90
|
return { expectedType: null, assignmentContext: null };
|
|
88
91
|
}
|
|
89
92
|
|
|
93
|
+
/**
|
|
94
|
+
* True if any postfix subscript is the 2-expression `[offset, length]` form —
|
|
95
|
+
* an array slice or scalar bit-range write (vs a 1-expression element/bit
|
|
96
|
+
* access). The grammar models both as `'[' expression ',' expression ']'`.
|
|
97
|
+
*/
|
|
98
|
+
private static hasRangeSubscript(
|
|
99
|
+
postfixOps: Parser.PostfixTargetOpContext[],
|
|
100
|
+
): boolean {
|
|
101
|
+
return postfixOps.some((op) => op.expression().length === 2);
|
|
102
|
+
}
|
|
103
|
+
|
|
90
104
|
/**
|
|
91
105
|
* Resolve expected type for a simple identifier target.
|
|
92
106
|
*/
|
|
@@ -124,13 +138,27 @@ class AssignmentExpectedTypeResolver {
|
|
|
124
138
|
/**
|
|
125
139
|
* Resolve expected type for array element access.
|
|
126
140
|
* Issue #872: arr[i] <- value needs baseType for MISRA 7.2 U suffix.
|
|
141
|
+
*
|
|
142
|
+
* An array SLICE (2-expression subscript `arr[off, len]`) is the exception:
|
|
143
|
+
* its source serializes at the SOURCE's own width, so leaking the element type
|
|
144
|
+
* as expectedType truncates an element-width-sensitive source such as a
|
|
145
|
+
* bit-extraction (Issue #1085: `buf[0,4] <- b[0,32]` wrote only the low byte).
|
|
146
|
+
* This applies only to arrays — a 2-expression subscript on a scalar is a
|
|
147
|
+
* bit-range write, whose value is genuinely the field's type (unchanged).
|
|
127
148
|
*/
|
|
128
|
-
private static resolveForArrayElement(
|
|
149
|
+
private static resolveForArrayElement(
|
|
150
|
+
id: string,
|
|
151
|
+
postfixOps: Parser.PostfixTargetOpContext[],
|
|
152
|
+
): IExpectedTypeResult {
|
|
129
153
|
const typeInfo = CodeGenState.getVariableTypeInfo(id);
|
|
130
154
|
if (!typeInfo?.isArray) {
|
|
131
155
|
return { expectedType: null, assignmentContext: null };
|
|
132
156
|
}
|
|
133
157
|
|
|
158
|
+
if (AssignmentExpectedTypeResolver.hasRangeSubscript(postfixOps)) {
|
|
159
|
+
return { expectedType: null, assignmentContext: null };
|
|
160
|
+
}
|
|
161
|
+
|
|
134
162
|
// Element type is the baseType (e.g., u8[10] -> "u8")
|
|
135
163
|
return { expectedType: typeInfo.baseType, assignmentContext: null };
|
|
136
164
|
}
|
|
@@ -60,6 +60,13 @@ interface IFromASTDeps {
|
|
|
60
60
|
* When the C typedef has `const T*`, this preserves const on the generated param.
|
|
61
61
|
*/
|
|
62
62
|
forceConst?: boolean;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Issue #995: Check if a type is an opaque handle (incomplete struct typedef).
|
|
66
|
+
* Opaque handles should not get auto-const because they must be passed to
|
|
67
|
+
* C APIs that expect non-const pointers.
|
|
68
|
+
*/
|
|
69
|
+
isOpaqueType?: (typeName: string) => boolean;
|
|
63
70
|
}
|
|
64
71
|
|
|
65
72
|
/**
|
|
@@ -142,6 +149,8 @@ class ParameterInputAdapter {
|
|
|
142
149
|
const isKnownPrimitive = !!deps.typeMap[typeName];
|
|
143
150
|
// Issue #958: C-header typedef struct types need pointer semantics
|
|
144
151
|
const isTypedefStruct = deps.isTypedefStructType(typeName);
|
|
152
|
+
// Issue #995: Detect opaque handles — rule applied in ParameterSignatureBuilder
|
|
153
|
+
const isOpaque = deps.isOpaqueType?.(typeName) ?? false;
|
|
145
154
|
// Issue #895: Don't add auto-const for callback-compatible functions
|
|
146
155
|
// because it would change the signature and break typedef compatibility
|
|
147
156
|
const isAutoConst =
|
|
@@ -171,6 +180,8 @@ class ParameterInputAdapter {
|
|
|
171
180
|
deps.forcePassByReference || isTypedefStruct || undefined,
|
|
172
181
|
// Issue #895: Preserve const from callback typedef signature
|
|
173
182
|
forceConst: deps.forceConst,
|
|
183
|
+
// Issue #995: Pass through opaque handle detection — rule applied in builder
|
|
184
|
+
isOpaqueHandle: isOpaque || undefined,
|
|
174
185
|
};
|
|
175
186
|
}
|
|
176
187
|
|
|
@@ -236,6 +247,8 @@ class ParameterInputAdapter {
|
|
|
236
247
|
isPassByReference: isCallbackPointer ? true : !deps.isPassByValue,
|
|
237
248
|
forcePointerSyntax: isCallbackPointer || undefined,
|
|
238
249
|
forceConst: param.isCallbackConst || undefined,
|
|
250
|
+
// Issue #995: Pass through opaque handle detection — rule applied in builder
|
|
251
|
+
isOpaqueHandle: param.isOpaqueHandle || undefined,
|
|
239
252
|
};
|
|
240
253
|
}
|
|
241
254
|
|
|
@@ -296,14 +309,15 @@ class ParameterInputAdapter {
|
|
|
296
309
|
}
|
|
297
310
|
}
|
|
298
311
|
|
|
299
|
-
|
|
300
|
-
|
|
312
|
+
// ADR-006: Arrays are pass-by-reference and mutable by default.
|
|
313
|
+
// Never apply auto-const to arrays - only explicit const from source code.
|
|
314
|
+
// Auto-const would break compatibility with C APIs expecting mutable pointers.
|
|
301
315
|
return {
|
|
302
316
|
name,
|
|
303
317
|
baseType: typeName,
|
|
304
318
|
mappedType,
|
|
305
319
|
isConst,
|
|
306
|
-
isAutoConst,
|
|
320
|
+
isAutoConst: false,
|
|
307
321
|
isArray: true,
|
|
308
322
|
arrayDimensions: dims,
|
|
309
323
|
isCallback: false,
|
|
@@ -47,6 +47,11 @@ class ParameterSignatureBuilder {
|
|
|
47
47
|
return this._buildStringParam(param);
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
// Issue #995: Opaque handles are always pass-by-reference with pointer syntax
|
|
51
|
+
if (param.isOpaqueHandle) {
|
|
52
|
+
return this._buildRefParam(param, refSuffix);
|
|
53
|
+
}
|
|
54
|
+
|
|
50
55
|
// Known struct or known primitive: pass by reference
|
|
51
56
|
if (param.isPassByReference) {
|
|
52
57
|
return this._buildRefParam(param, refSuffix);
|
|
@@ -103,18 +108,21 @@ class ParameterSignatureBuilder {
|
|
|
103
108
|
/**
|
|
104
109
|
* Build pass-by-reference parameter signature.
|
|
105
110
|
* C mode: const Point* p
|
|
106
|
-
* C++ mode: const Point& p (unless forcePointerSyntax)
|
|
111
|
+
* C++ mode: const Point& p (unless forcePointerSyntax or isOpaqueHandle)
|
|
107
112
|
*
|
|
108
113
|
* Issue #895: When forcePointerSyntax is set, always use pointer syntax
|
|
109
114
|
* because C callback typedefs expect pointers, not C++ references.
|
|
115
|
+
* Issue #995: Opaque handles must use pointer syntax because C APIs
|
|
116
|
+
* expect pointers to incomplete struct types (e.g., widget_t*).
|
|
110
117
|
*/
|
|
111
118
|
private static _buildRefParam(
|
|
112
119
|
param: IParameterInput,
|
|
113
120
|
refSuffix: string,
|
|
114
121
|
): string {
|
|
115
122
|
const constPrefix = this._getConstPrefix(param);
|
|
116
|
-
// Issue #895: Override refSuffix for callback-compatible
|
|
117
|
-
const actualSuffix =
|
|
123
|
+
// Issue #895/#995: Override refSuffix for callback-compatible or opaque handle params
|
|
124
|
+
const actualSuffix =
|
|
125
|
+
param.forcePointerSyntax || param.isOpaqueHandle ? "*" : refSuffix;
|
|
118
126
|
return `${constPrefix}${param.mappedType}${actualSuffix} ${param.name}`;
|
|
119
127
|
}
|
|
120
128
|
|
|
@@ -130,10 +138,15 @@ class ParameterSignatureBuilder {
|
|
|
130
138
|
* Get const prefix combining explicit const, auto-const, and forced const.
|
|
131
139
|
* Priority: forceConst > isConst > isAutoConst
|
|
132
140
|
* Issue #895: forceConst preserves const from callback typedef signature.
|
|
141
|
+
* Issue #995: Opaque handles suppress auto-const (they must be passed to
|
|
142
|
+
* C APIs that expect non-const pointers).
|
|
133
143
|
*/
|
|
134
144
|
private static _getConstPrefix(param: IParameterInput): string {
|
|
145
|
+
// Issue #995: Opaque handles never get auto-const — they're passed to C APIs
|
|
146
|
+
// expecting mutable pointers (e.g., LVGL's lv_obj_t)
|
|
147
|
+
const effectiveAutoConst = param.isOpaqueHandle ? false : param.isAutoConst;
|
|
135
148
|
// Any source of const results in "const " prefix
|
|
136
|
-
if (param.forceConst || param.isConst ||
|
|
149
|
+
if (param.forceConst || param.isConst || effectiveAutoConst) {
|
|
137
150
|
return "const ";
|
|
138
151
|
}
|
|
139
152
|
return "";
|