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.
Files changed (118) hide show
  1. package/README.md +18 -2
  2. package/dist/index.js +8897 -6260
  3. package/dist/index.js.map +4 -4
  4. package/grammar/CNext.g4 +12 -0
  5. package/package.json +4 -2
  6. package/src/transpiler/Transpiler.ts +376 -48
  7. package/src/transpiler/__tests__/DualCodePaths.test.ts +1 -1
  8. package/src/transpiler/__tests__/compileCommandsDiscovery.integration.test.ts +94 -0
  9. package/src/transpiler/__tests__/externalSymbolRecovery.integration.test.ts +215 -0
  10. package/src/transpiler/logic/__tests__/detectAssemblySyntax.test.ts +116 -0
  11. package/src/transpiler/logic/analysis/FunctionCallAnalyzer.ts +65 -10
  12. package/src/transpiler/logic/analysis/InitializationAnalyzer.ts +186 -14
  13. package/src/transpiler/logic/analysis/MixedTypeCategoryAnalyzer.ts +408 -0
  14. package/src/transpiler/logic/analysis/PassByValueAnalyzer.ts +16 -97
  15. package/src/transpiler/logic/analysis/ReturnPathAnalyzer.ts +171 -0
  16. package/src/transpiler/logic/analysis/SignedShiftAnalyzer.ts +124 -12
  17. package/src/transpiler/logic/analysis/__tests__/FunctionCallAnalyzer.test.ts +200 -0
  18. package/src/transpiler/logic/analysis/__tests__/InitializationAnalyzer.test.ts +386 -1
  19. package/src/transpiler/logic/analysis/__tests__/MixedTypeCategoryAnalyzer.test.ts +346 -0
  20. package/src/transpiler/logic/analysis/__tests__/ReturnPathAnalyzer.test.ts +232 -0
  21. package/src/transpiler/logic/analysis/__tests__/SignedShiftAnalyzer.test.ts +211 -0
  22. package/src/transpiler/logic/analysis/runAnalyzers.ts +23 -2
  23. package/src/transpiler/logic/analysis/types/IMixedTypeCategoryError.ts +17 -0
  24. package/src/transpiler/logic/analysis/types/IReturnPathError.ts +12 -0
  25. package/src/transpiler/logic/detectAssemblySyntax.ts +80 -0
  26. package/src/transpiler/logic/parser/grammar/CNext.interp +4 -1
  27. package/src/transpiler/logic/parser/grammar/CNext.tokens +169 -167
  28. package/src/transpiler/logic/parser/grammar/CNextLexer.interp +4 -1
  29. package/src/transpiler/logic/parser/grammar/CNextLexer.tokens +169 -167
  30. package/src/transpiler/logic/parser/grammar/CNextLexer.ts +545 -541
  31. package/src/transpiler/logic/parser/grammar/CNextListener.ts +11 -0
  32. package/src/transpiler/logic/parser/grammar/CNextParser.ts +1318 -1178
  33. package/src/transpiler/logic/parser/grammar/CNextVisitor.ts +7 -0
  34. package/src/transpiler/logic/preprocessor/CompileCommandsReader.ts +332 -0
  35. package/src/transpiler/logic/preprocessor/ExternalDeclarationOracle.ts +202 -0
  36. package/src/transpiler/logic/preprocessor/Preprocessor.ts +24 -6
  37. package/src/transpiler/logic/preprocessor/ToolchainDetector.ts +42 -1
  38. package/src/transpiler/logic/preprocessor/__tests__/CompileCommandsReader.test.ts +216 -0
  39. package/src/transpiler/logic/preprocessor/__tests__/ExternalDeclarationOracle.test.ts +153 -0
  40. package/src/transpiler/logic/preprocessor/__tests__/Preprocessor.test.ts +43 -18
  41. package/src/transpiler/logic/preprocessor/__tests__/ToolchainDetector.crossCompiler.test.ts +75 -0
  42. package/src/transpiler/logic/preprocessor/__tests__/fixtures/oracle/dependent.h +7 -0
  43. package/src/transpiler/logic/preprocessor/__tests__/fixtures/oracle/predecessor.h +5 -0
  44. package/src/transpiler/logic/preprocessor/types/ICompileCommandsResult.ts +14 -0
  45. package/src/transpiler/logic/preprocessor/types/IPreprocessOptions.ts +17 -0
  46. package/src/transpiler/logic/symbols/SymbolTable.ts +62 -2
  47. package/src/transpiler/logic/symbols/__tests__/SymbolTable.test.ts +55 -4
  48. package/src/transpiler/logic/symbols/cnext/collectors/VariableCollector.ts +15 -2
  49. package/src/transpiler/logic/symbols/cnext/utils/TypeUtils.ts +64 -50
  50. package/src/transpiler/output/MisraSuppressionUtils.ts +52 -0
  51. package/src/transpiler/output/__tests__/MisraSuppressionUtils.test.ts +67 -0
  52. package/src/transpiler/output/codegen/CodeGenerator.ts +196 -98
  53. package/src/transpiler/output/codegen/TypeResolver.ts +148 -0
  54. package/src/transpiler/output/codegen/TypeValidator.ts +179 -81
  55. package/src/transpiler/output/codegen/__tests__/CodeGenerator.coverage.test.ts +165 -17
  56. package/src/transpiler/output/codegen/__tests__/CodeGenerator.test.ts +163 -35
  57. package/src/transpiler/output/codegen/__tests__/TrackVariableTypeHelpers.test.ts +2 -2
  58. package/src/transpiler/output/codegen/__tests__/TypeResolver.test.ts +93 -0
  59. package/src/transpiler/output/codegen/__tests__/TypeValidator.alwaysTrueLoop.test.ts +91 -0
  60. package/src/transpiler/output/codegen/__tests__/TypeValidator.test.ts +97 -14
  61. package/src/transpiler/output/codegen/assignment/AssignmentClassifier.ts +13 -0
  62. package/src/transpiler/output/codegen/assignment/AssignmentKind.ts +1 -1
  63. package/src/transpiler/output/codegen/assignment/handlers/AccessPatternHandlers.ts +20 -12
  64. package/src/transpiler/output/codegen/assignment/handlers/ArrayHandlers.ts +424 -22
  65. package/src/transpiler/output/codegen/assignment/handlers/BitAccessHandlers.ts +31 -18
  66. package/src/transpiler/output/codegen/assignment/handlers/BitmapHandlers.ts +7 -8
  67. package/src/transpiler/output/codegen/assignment/handlers/RegisterHandlers.ts +6 -8
  68. package/src/transpiler/output/codegen/assignment/handlers/RegisterUtils.ts +12 -10
  69. package/src/transpiler/output/codegen/assignment/handlers/SimpleHandler.ts +3 -7
  70. package/src/transpiler/output/codegen/assignment/handlers/SpecialHandlers.ts +7 -9
  71. package/src/transpiler/output/codegen/assignment/handlers/StringHandlers.ts +12 -10
  72. package/src/transpiler/output/codegen/assignment/handlers/__tests__/ArrayHandlers.test.ts +479 -11
  73. package/src/transpiler/output/codegen/generators/IOrchestrator.ts +3 -0
  74. package/src/transpiler/output/codegen/generators/declarationGenerators/ScopeGenerator.ts +26 -7
  75. package/src/transpiler/output/codegen/generators/declarationGenerators/__tests__/ScopeGenerator.test.ts +86 -0
  76. package/src/transpiler/output/codegen/generators/expressions/BinaryExprGenerator.ts +43 -24
  77. package/src/transpiler/output/codegen/generators/expressions/CallExprGenerator.ts +76 -58
  78. package/src/transpiler/output/codegen/generators/expressions/ExpressionGenerator.ts +9 -2
  79. package/src/transpiler/output/codegen/generators/expressions/PostfixExpressionGenerator.ts +26 -13
  80. package/src/transpiler/output/codegen/generators/expressions/__tests__/CallExprGenerator.test.ts +44 -0
  81. package/src/transpiler/output/codegen/generators/expressions/__tests__/ExpressionGenerator.test.ts +82 -1
  82. package/src/transpiler/output/codegen/generators/expressions/__tests__/PostfixExpressionGenerator.test.ts +129 -1
  83. package/src/transpiler/output/codegen/generators/statements/ControlFlowGenerator.ts +64 -8
  84. package/src/transpiler/output/codegen/generators/statements/__tests__/ControlFlowGenerator.test.ts +8 -5
  85. package/src/transpiler/output/codegen/helpers/AssignmentExpectedTypeResolver.ts +30 -2
  86. package/src/transpiler/output/codegen/helpers/ParameterInputAdapter.ts +17 -3
  87. package/src/transpiler/output/codegen/helpers/ParameterSignatureBuilder.ts +17 -4
  88. package/src/transpiler/output/codegen/helpers/StringDeclHelper.ts +240 -42
  89. package/src/transpiler/output/codegen/helpers/SymbolLookupHelper.ts +0 -21
  90. package/src/transpiler/output/codegen/helpers/TypeGenerationHelper.ts +60 -39
  91. package/src/transpiler/output/codegen/helpers/TypeRegistrationEngine.ts +170 -36
  92. package/src/transpiler/output/codegen/helpers/VariableDeclHelper.ts +37 -39
  93. package/src/transpiler/output/codegen/helpers/__tests__/AssignmentExpectedTypeResolver.test.ts +19 -0
  94. package/src/transpiler/output/codegen/helpers/__tests__/ParameterInputAdapter.test.ts +117 -0
  95. package/src/transpiler/output/codegen/helpers/__tests__/ParameterSignatureBuilder.test.ts +94 -2
  96. package/src/transpiler/output/codegen/helpers/__tests__/StringDeclHelper.test.ts +322 -9
  97. package/src/transpiler/output/codegen/helpers/__tests__/SymbolLookupHelper.test.ts +0 -64
  98. package/src/transpiler/output/codegen/helpers/__tests__/TypeRegistrationEngine.test.ts +101 -0
  99. package/src/transpiler/output/codegen/helpers/__tests__/VariableDeclHelper.test.ts +29 -5
  100. package/src/transpiler/output/codegen/subscript/SubscriptClassifier.ts +30 -11
  101. package/src/transpiler/output/codegen/subscript/TSubscriptKind.ts +1 -1
  102. package/src/transpiler/output/codegen/subscript/__tests__/SubscriptClassifier.test.ts +38 -6
  103. package/src/transpiler/output/codegen/types/ICallbackTypeInfo.ts +2 -1
  104. package/src/transpiler/output/codegen/types/IParameterInput.ts +7 -0
  105. package/src/transpiler/output/headers/BaseHeaderGenerator.ts +8 -0
  106. package/src/transpiler/output/headers/HeaderGeneratorUtils.ts +140 -24
  107. package/src/transpiler/output/headers/__tests__/HeaderGeneratorUtils.test.ts +280 -0
  108. package/src/transpiler/output/headers/generators/IHeaderTypeInput.ts +13 -0
  109. package/src/transpiler/output/headers/generators/generateStructHeader.ts +48 -28
  110. package/src/transpiler/state/CodeGenState.ts +91 -22
  111. package/src/transpiler/state/__tests__/CodeGenState.test.ts +253 -11
  112. package/src/transpiler/types/ICachedFileEntry.ts +7 -0
  113. package/src/utils/LiteralUtils.ts +23 -0
  114. package/src/utils/__tests__/LiteralUtils.test.ts +101 -0
  115. package/src/utils/cache/CacheManager.ts +13 -2
  116. package/src/utils/cache/__tests__/CacheManager.test.ts +18 -1
  117. package/src/utils/constants/TypeConstants.ts +13 -0
  118. 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
- const text = ctx.getText();
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
- const orExpr = orExprs[0];
734
- const text = orExpr.getText();
735
-
736
- if (orExpr.andExpression().length > 1) {
737
- return;
738
- }
692
+ TypeValidator._validateConditionOrExpression(orExprs[0], conditionType);
693
+ }
739
694
 
740
- const andExpr = orExpr.andExpression(0);
741
- if (!andExpr) {
742
- throw new Error(
743
- `Error E0701: ${conditionType} condition must be a boolean expression (comparison or logical operation), not '${text}' (MISRA C:2012 Rule 14.4)`,
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
- if (andExpr.equalityExpression().length > 1) {
748
- return;
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
- const equalityExpr = andExpr.equalityExpression(0);
752
- if (!equalityExpr) {
753
- throw new Error(
754
- `Error E0701: ${conditionType} condition must be a boolean expression (comparison or logical operation), not '${text}' (MISRA C:2012 Rule 14.4)`,
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
- throw new Error(
765
- `Error E0701: ${conditionType} condition must be a boolean expression (comparison or logical operation), not '${text}' (MISRA C:2012 Rule 14.4)`,
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
- const bitwiseOrExpr = relationalExpr.bitwiseOrExpression(0);
774
- if (bitwiseOrExpr && TypeValidator._isBooleanExpression(bitwiseOrExpr)) {
775
- return;
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: use explicit comparison: ${text} > 0 or ${text} != 0`,
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
- private static _isBooleanExpression(
784
- ctx: Parser.BitwiseOrExpressionContext,
785
- ): boolean {
786
- const text = ctx.getText();
764
+ // ========================================================================
765
+ // Disguised Infinite Loop Validation (ADR-113 / #1075, E0707)
766
+ // ========================================================================
787
767
 
788
- if (text === "true" || text === "false") {
789
- return true;
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
- if (text.startsWith("!")) {
793
- return true;
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
- const typeInfo = CodeGenState.getVariableTypeInfo(text);
797
- if (typeInfo?.baseType === "bool") {
798
- return true;
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
- return false;
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
  // ========================================================================