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
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* TypeResolver - Handles type inference, classification, and validation
|
|
3
3
|
* Static class that reads from CodeGenState directly.
|
|
4
4
|
*/
|
|
5
|
+
import { ParserRuleContext } from "antlr4ng";
|
|
5
6
|
import * as Parser from "../../logic/parser/grammar/CNextParser";
|
|
6
7
|
import CodeGenState from "../../state/CodeGenState";
|
|
7
8
|
import INTEGER_TYPES from "./types/INTEGER_TYPES";
|
|
@@ -230,6 +231,153 @@ class TypeResolver {
|
|
|
230
231
|
return null;
|
|
231
232
|
}
|
|
232
233
|
|
|
234
|
+
/**
|
|
235
|
+
* Resolve the C-Next integer type of an expression, including composite
|
|
236
|
+
* arithmetic/bitwise expressions that `getExpressionType` leaves unresolved.
|
|
237
|
+
*
|
|
238
|
+
* MISRA C:2012 Rule 10.4 (enforced by MixedTypeCategoryAnalyzer) guarantees a
|
|
239
|
+
* binary operator's operands share an essential type category, so a composite
|
|
240
|
+
* integer expression's category is uniform; its essential width is the widest
|
|
241
|
+
* integer operand. This is what lets slice-assignment serialize an arithmetic
|
|
242
|
+
* source (e.g. `a + b`) MISRA Rule 10.8-clean instead of guessing a width.
|
|
243
|
+
*
|
|
244
|
+
* Returns null when no integer-typed variable leaf can be resolved (e.g. a
|
|
245
|
+
* struct-field or function-call composite — left for a later pass).
|
|
246
|
+
*/
|
|
247
|
+
static getIntegerExpressionType(
|
|
248
|
+
ctx: Parser.ExpressionContext,
|
|
249
|
+
): string | null {
|
|
250
|
+
const direct = TypeResolver.getExpressionType(ctx);
|
|
251
|
+
if (direct !== null) return direct;
|
|
252
|
+
return TypeResolver.resolveCompositeIntegerType(ctx);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Combine the leaf VALUE operands of a composite expression into a single
|
|
257
|
+
* C-Next type: the (uniform, per Rule 10.4) category at the widest width.
|
|
258
|
+
*
|
|
259
|
+
* Operands are typed by their value (Issue #1085 review) — an array index
|
|
260
|
+
* (`arr[i]`), bit offset (`x[off, w]`) or struct member name is NOT a value
|
|
261
|
+
* operand and must not contribute to the width. A bit-extraction contributes
|
|
262
|
+
* its EXTRACTED width, not the variable's full width (typing `a + b[0, 32]`
|
|
263
|
+
* as u64 would cast the composite to a wider type — MISRA Rule 10.8).
|
|
264
|
+
*/
|
|
265
|
+
private static resolveCompositeIntegerType(
|
|
266
|
+
ctx: Parser.ExpressionContext,
|
|
267
|
+
): string | null {
|
|
268
|
+
let category: "i" | "u" | null = null;
|
|
269
|
+
let width = 0;
|
|
270
|
+
|
|
271
|
+
for (const operand of TypeResolver.collectOperandPostfixes(ctx)) {
|
|
272
|
+
const operandType = TypeResolver.typeOperandPostfix(operand);
|
|
273
|
+
const match = operandType
|
|
274
|
+
? /^([iu])(8|16|32|64)$/.exec(operandType)
|
|
275
|
+
: null;
|
|
276
|
+
if (!match) continue;
|
|
277
|
+
category ??= match[1] as "i" | "u";
|
|
278
|
+
width = Math.max(width, Number.parseInt(match[2], 10));
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return category && width > 0 ? `${category}${width}` : null;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Type one leaf operand of a composite by its VALUE type. A bit-extraction
|
|
286
|
+
* `x[start, width]` yields an unsigned value of the extracted width; a simple
|
|
287
|
+
* function call `name(...)` yields its declared return type; everything else
|
|
288
|
+
* (variable, array element, struct field, member chain) defers to
|
|
289
|
+
* getPostfixExpressionType. Returns null for an operand it cannot classify
|
|
290
|
+
* (e.g. a literal, which is contextually typed).
|
|
291
|
+
*/
|
|
292
|
+
private static typeOperandPostfix(
|
|
293
|
+
postfix: Parser.PostfixExpressionContext,
|
|
294
|
+
): string | null {
|
|
295
|
+
const extractionWidth = TypeResolver.bitExtractionWidth(postfix);
|
|
296
|
+
if (extractionWidth !== null) {
|
|
297
|
+
return TypeResolver.unsignedTypeForBits(extractionWidth);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const direct = TypeResolver.getPostfixExpressionType(postfix);
|
|
301
|
+
if (direct !== null) return direct;
|
|
302
|
+
|
|
303
|
+
return TypeResolver.callReturnType(postfix);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Collect the leaf operand postfix expressions of a composite WITHOUT
|
|
308
|
+
* descending into a postfix's own internals — so an array index (`arr[i]`) or
|
|
309
|
+
* bit offset (`x[off, w]`) variable is never mistaken for a value operand.
|
|
310
|
+
*/
|
|
311
|
+
private static collectOperandPostfixes(
|
|
312
|
+
node: ParserRuleContext,
|
|
313
|
+
): Parser.PostfixExpressionContext[] {
|
|
314
|
+
if (node instanceof Parser.PostfixExpressionContext) return [node];
|
|
315
|
+
const operands: Parser.PostfixExpressionContext[] = [];
|
|
316
|
+
for (let i = 0; i < node.getChildCount(); i += 1) {
|
|
317
|
+
const child = node.getChild(i);
|
|
318
|
+
if (child instanceof ParserRuleContext) {
|
|
319
|
+
operands.push(...TypeResolver.collectOperandPostfixes(child));
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
return operands;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* If a postfix expression's terminal suffix is a bit-range extraction
|
|
327
|
+
* `[start, width]` with a compile-time-constant width, return that width in
|
|
328
|
+
* bits; else null.
|
|
329
|
+
*/
|
|
330
|
+
private static bitExtractionWidth(
|
|
331
|
+
postfix: Parser.PostfixExpressionContext,
|
|
332
|
+
): number | null {
|
|
333
|
+
const ops = postfix.postfixOp();
|
|
334
|
+
const last = ops.at(-1);
|
|
335
|
+
if (last?.expression().length !== 2) return null;
|
|
336
|
+
const widthExpr = last.expression()[1];
|
|
337
|
+
|
|
338
|
+
// Resolve the width through the constant evaluator — the same path the slice
|
|
339
|
+
// offset/length use — so a named const or any-base literal width
|
|
340
|
+
// (`b[0, WIDTH]`, `b[0, 0b100000]`) is sized at its real width rather than
|
|
341
|
+
// dropped, which would mis-type a composite slice source (Issue #1085 review).
|
|
342
|
+
const evaluated = CodeGenState.generator?.tryEvaluateConstant(widthExpr);
|
|
343
|
+
if (evaluated !== undefined) {
|
|
344
|
+
return evaluated > 0 ? evaluated : null;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Fallback for contexts with no generator (e.g. isolated unit tests): accept
|
|
348
|
+
// a plain decimal/hex literal width directly.
|
|
349
|
+
const widthText = widthExpr.getText();
|
|
350
|
+
if (!/^(0x[0-9a-fA-F]+|\d+)$/.test(widthText)) return null;
|
|
351
|
+
const value = Number.parseInt(
|
|
352
|
+
widthText,
|
|
353
|
+
widthText.startsWith("0x") ? 16 : 10,
|
|
354
|
+
);
|
|
355
|
+
return Number.isNaN(value) || value <= 0 ? null : value;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/** Smallest standard unsigned C-Next type holding `bits` bits, or null if >64. */
|
|
359
|
+
private static unsignedTypeForBits(bits: number): string | null {
|
|
360
|
+
if (bits <= 8) return "u8";
|
|
361
|
+
if (bits <= 16) return "u16";
|
|
362
|
+
if (bits <= 32) return "u32";
|
|
363
|
+
if (bits <= 64) return "u64";
|
|
364
|
+
return null;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* If a postfix expression is a simple function call `name(...)`, return the
|
|
369
|
+
* function's declared return type — a call operand's width comes from its
|
|
370
|
+
* return type, not from being ignored (Issue #1085 review).
|
|
371
|
+
*/
|
|
372
|
+
private static callReturnType(
|
|
373
|
+
postfix: Parser.PostfixExpressionContext,
|
|
374
|
+
): string | null {
|
|
375
|
+
const ops = postfix.postfixOp();
|
|
376
|
+
if (ops.length !== 1 || !ops[0].getText().startsWith("(")) return null;
|
|
377
|
+
const name = postfix.primaryExpression()?.IDENTIFIER()?.getText();
|
|
378
|
+
return name ? (CodeGenState.getFunctionReturnType(name) ?? null) : null;
|
|
379
|
+
}
|
|
380
|
+
|
|
233
381
|
/**
|
|
234
382
|
* ADR-024: Get the type of a postfix expression.
|
|
235
383
|
* Tracks InternalTypeInfo (baseType + isArray) through the suffix chain
|
|
@@ -657,48 +657,7 @@ class TypeValidator {
|
|
|
657
657
|
// ========================================================================
|
|
658
658
|
|
|
659
659
|
static validateTernaryCondition(ctx: Parser.OrExpressionContext): void {
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
if (ctx.andExpression().length > 1) {
|
|
663
|
-
return;
|
|
664
|
-
}
|
|
665
|
-
|
|
666
|
-
const andExpr = ctx.andExpression(0);
|
|
667
|
-
if (!andExpr) {
|
|
668
|
-
throw new Error(
|
|
669
|
-
`Error: Ternary condition must be a boolean expression (comparison or logical operation), not '${text}'`,
|
|
670
|
-
);
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
if (andExpr.equalityExpression().length > 1) {
|
|
674
|
-
return;
|
|
675
|
-
}
|
|
676
|
-
|
|
677
|
-
const equalityExpr = andExpr.equalityExpression(0);
|
|
678
|
-
if (!equalityExpr) {
|
|
679
|
-
throw new Error(
|
|
680
|
-
`Error: Ternary condition must be a boolean expression (comparison or logical operation), not '${text}'`,
|
|
681
|
-
);
|
|
682
|
-
}
|
|
683
|
-
|
|
684
|
-
if (equalityExpr.relationalExpression().length > 1) {
|
|
685
|
-
return;
|
|
686
|
-
}
|
|
687
|
-
|
|
688
|
-
const relationalExpr = equalityExpr.relationalExpression(0);
|
|
689
|
-
if (!relationalExpr) {
|
|
690
|
-
throw new Error(
|
|
691
|
-
`Error: Ternary condition must be a boolean expression (comparison or logical operation), not '${text}'`,
|
|
692
|
-
);
|
|
693
|
-
}
|
|
694
|
-
|
|
695
|
-
if (relationalExpr.bitwiseOrExpression().length > 1) {
|
|
696
|
-
return;
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
throw new Error(
|
|
700
|
-
`Error: Ternary condition must be a boolean expression (comparison or logical operation), not '${text}'`,
|
|
701
|
-
);
|
|
660
|
+
TypeValidator._validateConditionOrExpression(ctx, "ternary");
|
|
702
661
|
}
|
|
703
662
|
|
|
704
663
|
static validateNoNestedTernary(
|
|
@@ -730,75 +689,214 @@ class TypeValidator {
|
|
|
730
689
|
);
|
|
731
690
|
}
|
|
732
691
|
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
if (orExpr.andExpression().length > 1) {
|
|
737
|
-
return;
|
|
738
|
-
}
|
|
692
|
+
TypeValidator._validateConditionOrExpression(orExprs[0], conditionType);
|
|
693
|
+
}
|
|
739
694
|
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
695
|
+
/**
|
|
696
|
+
* MISRA C:2012 Rule 14.4 (Issue #1042): a controlling expression must be an
|
|
697
|
+
* explicit comparison. Every leaf operand — after decomposing `||` and `&&` —
|
|
698
|
+
* must itself be an equality (`=`, `!=`) or relational (`<`, `>`, `<=`, `>=`)
|
|
699
|
+
* comparison. A bare value, a bare boolean (local, parameter, or `this.`/
|
|
700
|
+
* `global.` member), a literal, or a negation (`!x`) is rejected; use an
|
|
701
|
+
* explicit form such as `x = true`. This is the single decision point shared
|
|
702
|
+
* by `if`/`while`/`for`/`do-while` and ternary conditions.
|
|
703
|
+
*/
|
|
704
|
+
private static _validateConditionOrExpression(
|
|
705
|
+
orExpr: Parser.OrExpressionContext,
|
|
706
|
+
conditionType: string,
|
|
707
|
+
): void {
|
|
708
|
+
const andExprs = orExpr.andExpression();
|
|
709
|
+
if (andExprs.length === 0) {
|
|
710
|
+
TypeValidator._throwConditionNotBoolean(orExpr, conditionType);
|
|
745
711
|
}
|
|
746
712
|
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
713
|
+
for (const andExpr of andExprs) {
|
|
714
|
+
const equalityExprs = andExpr.equalityExpression();
|
|
715
|
+
if (equalityExprs.length === 0) {
|
|
716
|
+
TypeValidator._throwConditionNotBoolean(orExpr, conditionType);
|
|
717
|
+
}
|
|
750
718
|
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
719
|
+
for (const equalityExpr of equalityExprs) {
|
|
720
|
+
TypeValidator._validateConditionIsComparison(
|
|
721
|
+
equalityExpr,
|
|
722
|
+
orExpr,
|
|
723
|
+
conditionType,
|
|
724
|
+
);
|
|
725
|
+
}
|
|
756
726
|
}
|
|
727
|
+
}
|
|
757
728
|
|
|
729
|
+
private static _validateConditionIsComparison(
|
|
730
|
+
equalityExpr: Parser.EqualityExpressionContext,
|
|
731
|
+
orExpr: Parser.OrExpressionContext,
|
|
732
|
+
conditionType: string,
|
|
733
|
+
): void {
|
|
734
|
+
// An equality operator (`=`, `!=`) makes this operand a comparison.
|
|
758
735
|
if (equalityExpr.relationalExpression().length > 1) {
|
|
759
736
|
return;
|
|
760
737
|
}
|
|
761
738
|
|
|
762
739
|
const relationalExpr = equalityExpr.relationalExpression(0);
|
|
763
740
|
if (!relationalExpr) {
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
);
|
|
741
|
+
TypeValidator._throwConditionNotBoolean(orExpr, conditionType);
|
|
742
|
+
return;
|
|
767
743
|
}
|
|
768
744
|
|
|
745
|
+
// A relational operator (`<`, `>`, `<=`, `>=`) makes this operand a comparison.
|
|
769
746
|
if (relationalExpr.bitwiseOrExpression().length > 1) {
|
|
770
747
|
return;
|
|
771
748
|
}
|
|
772
749
|
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
}
|
|
750
|
+
// No comparison operator: a bare value, bare boolean, literal, or negation.
|
|
751
|
+
TypeValidator._throwConditionNotBoolean(equalityExpr, conditionType);
|
|
752
|
+
}
|
|
777
753
|
|
|
754
|
+
private static _throwConditionNotBoolean(
|
|
755
|
+
node: Parser.OrExpressionContext | Parser.EqualityExpressionContext,
|
|
756
|
+
conditionType: string,
|
|
757
|
+
): void {
|
|
758
|
+
const text = node.getText();
|
|
778
759
|
throw new Error(
|
|
779
|
-
`Error E0701: ${conditionType} condition must be a boolean expression (comparison or logical operation), not '${text}' (MISRA C:2012 Rule 14.4)\n help:
|
|
760
|
+
`Error E0701: ${conditionType} condition must be a boolean expression (comparison or logical operation), not '${text}' (MISRA C:2012 Rule 14.4)\n help: ${TypeValidator._conditionHelp(text)}`,
|
|
780
761
|
);
|
|
781
762
|
}
|
|
782
763
|
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
const text = ctx.getText();
|
|
764
|
+
// ========================================================================
|
|
765
|
+
// Disguised Infinite Loop Validation (ADR-113 / #1075, E0707)
|
|
766
|
+
// ========================================================================
|
|
787
767
|
|
|
788
|
-
|
|
789
|
-
|
|
768
|
+
/**
|
|
769
|
+
* ADR-113 / #1075 (E0707): reject a loop whose controlling expression is an
|
|
770
|
+
* always-TRUE comparison of literal operands (`while (1 = 1)`, `5 > 3`,
|
|
771
|
+
* `true = true`). C-Next has one source form for an intentional infinite loop —
|
|
772
|
+
* `forever`. This is the v0.2.18 *literal* slice only: named constants and
|
|
773
|
+
* non-literal operands need symbol resolution (out of scope), and always-FALSE
|
|
774
|
+
* conditions are a separate MISRA 14.3 case — both tracked in #1076.
|
|
775
|
+
*
|
|
776
|
+
* Runs after the E0701 boolean check, so the condition is already a comparison.
|
|
777
|
+
*/
|
|
778
|
+
static validateLoopConditionNotAlwaysTrue(
|
|
779
|
+
ctx: Parser.ExpressionContext,
|
|
780
|
+
): void {
|
|
781
|
+
const comparison = TypeValidator._asSingleLiteralComparison(ctx);
|
|
782
|
+
if (comparison && TypeValidator._comparisonIsAlwaysTrue(comparison)) {
|
|
783
|
+
throw new Error(
|
|
784
|
+
`Error E0707: loop condition '${ctx.getText()}' is always true\n` +
|
|
785
|
+
" help: write 'forever { ... }' for an intentional infinite loop",
|
|
786
|
+
);
|
|
790
787
|
}
|
|
788
|
+
}
|
|
791
789
|
|
|
792
|
-
|
|
793
|
-
|
|
790
|
+
/**
|
|
791
|
+
* If `ctx` is a single comparison of two literal operands (no `||`/`&&`, no
|
|
792
|
+
* ternary, no chaining), return its operator and the two literal values.
|
|
793
|
+
* Otherwise null — anything involving identifiers, floats, or sub-expressions
|
|
794
|
+
* is left to the full MISRA 14.3 effort (#1076).
|
|
795
|
+
*/
|
|
796
|
+
private static _asSingleLiteralComparison(
|
|
797
|
+
ctx: Parser.ExpressionContext,
|
|
798
|
+
): { operator: string; left: number; right: number } | null {
|
|
799
|
+
const orExprs = ctx.ternaryExpression().orExpression();
|
|
800
|
+
if (orExprs.length !== 1) return null;
|
|
801
|
+
const andExprs = orExprs[0].andExpression();
|
|
802
|
+
if (andExprs.length !== 1) return null;
|
|
803
|
+
const equalityExprs = andExprs[0].equalityExpression();
|
|
804
|
+
if (equalityExprs.length !== 1) return null;
|
|
805
|
+
const equalityExpr = equalityExprs[0];
|
|
806
|
+
|
|
807
|
+
const relationalExprs = equalityExpr.relationalExpression();
|
|
808
|
+
if (relationalExprs.length === 2) {
|
|
809
|
+
// Equality comparison: relExpr ('=' | '!=') relExpr
|
|
810
|
+
return TypeValidator._buildLiteralComparison(
|
|
811
|
+
equalityExpr.getChild(1)?.getText(),
|
|
812
|
+
relationalExprs[0].getText(),
|
|
813
|
+
relationalExprs[1].getText(),
|
|
814
|
+
);
|
|
815
|
+
}
|
|
816
|
+
if (relationalExprs.length === 1) {
|
|
817
|
+
const bitwiseOrExprs = relationalExprs[0].bitwiseOrExpression();
|
|
818
|
+
if (bitwiseOrExprs.length === 2) {
|
|
819
|
+
// Relational comparison: orExpr ('<' | '>' | '<=' | '>=') orExpr
|
|
820
|
+
return TypeValidator._buildLiteralComparison(
|
|
821
|
+
relationalExprs[0].getChild(1)?.getText(),
|
|
822
|
+
bitwiseOrExprs[0].getText(),
|
|
823
|
+
bitwiseOrExprs[1].getText(),
|
|
824
|
+
);
|
|
825
|
+
}
|
|
794
826
|
}
|
|
827
|
+
return null;
|
|
828
|
+
}
|
|
795
829
|
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
830
|
+
private static _buildLiteralComparison(
|
|
831
|
+
operator: string | undefined,
|
|
832
|
+
leftText: string,
|
|
833
|
+
rightText: string,
|
|
834
|
+
): { operator: string; left: number; right: number } | null {
|
|
835
|
+
const left = TypeValidator._literalValue(leftText);
|
|
836
|
+
const right = TypeValidator._literalValue(rightText);
|
|
837
|
+
if (operator === undefined || left === null || right === null) return null;
|
|
838
|
+
return { operator, left, right };
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
/**
|
|
842
|
+
* Strict literal-to-number for the E0707 literal slice: integer literals
|
|
843
|
+
* (decimal/hex/binary, optional type suffix) and `true`/`false`. Floats,
|
|
844
|
+
* strings, chars, identifiers, and any compound text return null so they are
|
|
845
|
+
* not treated as compile-time-known.
|
|
846
|
+
*/
|
|
847
|
+
private static _literalValue(text: string): number | null {
|
|
848
|
+
if (text === "true") return 1;
|
|
849
|
+
if (text === "false") return 0;
|
|
850
|
+
if (text.includes(".")) return null;
|
|
851
|
+
// A leading-zero integer (`0777`) is emitted verbatim and read by C as an
|
|
852
|
+
// OCTAL constant, so a decimal parse would diverge from the generated code's
|
|
853
|
+
// value. Skip it (defer to #1076) rather than risk a wrong verdict. `0x`/`0b`
|
|
854
|
+
// and a bare `0` are unambiguous and still handled.
|
|
855
|
+
if (/^0\d/.test(text)) return null;
|
|
856
|
+
if (/^(0[xX][\da-fA-F]+|0[bB][01]+|\d+)([uUiI]\d+)?$/.test(text)) {
|
|
857
|
+
return LiteralEvaluator.parseLiteral(text);
|
|
799
858
|
}
|
|
859
|
+
return null;
|
|
860
|
+
}
|
|
800
861
|
|
|
801
|
-
|
|
862
|
+
private static _comparisonIsAlwaysTrue(comparison: {
|
|
863
|
+
operator: string;
|
|
864
|
+
left: number;
|
|
865
|
+
right: number;
|
|
866
|
+
}): boolean {
|
|
867
|
+
switch (comparison.operator) {
|
|
868
|
+
case "=":
|
|
869
|
+
return comparison.left === comparison.right;
|
|
870
|
+
case "!=":
|
|
871
|
+
return comparison.left !== comparison.right;
|
|
872
|
+
case "<":
|
|
873
|
+
return comparison.left < comparison.right;
|
|
874
|
+
case ">":
|
|
875
|
+
return comparison.left > comparison.right;
|
|
876
|
+
case "<=":
|
|
877
|
+
return comparison.left <= comparison.right;
|
|
878
|
+
case ">=":
|
|
879
|
+
return comparison.left >= comparison.right;
|
|
880
|
+
default:
|
|
881
|
+
return false;
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
/**
|
|
886
|
+
* Builds a context-aware "help" suggestion for a rejected condition. For a
|
|
887
|
+
* boolean operand the correct explicit form is `flag = true` (or `flag = false`
|
|
888
|
+
* for a negated `!flag`), not a numeric `> 0` comparison. Non-boolean operands
|
|
889
|
+
* keep the generic `> 0 or != 0` guidance. A member access (`this.flag`) that
|
|
890
|
+
* does not resolve to a known type falls back to the generic form.
|
|
891
|
+
*/
|
|
892
|
+
private static _conditionHelp(text: string): string {
|
|
893
|
+
const isNegated = text.startsWith("!");
|
|
894
|
+
const base = isNegated ? text.slice(1) : text;
|
|
895
|
+
const typeInfo = CodeGenState.getVariableTypeInfo(base);
|
|
896
|
+
if (typeInfo?.baseType === "bool") {
|
|
897
|
+
return `use explicit comparison: ${base} = ${isNegated ? "false" : "true"}`;
|
|
898
|
+
}
|
|
899
|
+
return `use explicit comparison: ${text} > 0 or ${text} != 0`;
|
|
802
900
|
}
|
|
803
901
|
|
|
804
902
|
// ========================================================================
|