c-next 0.2.17 → 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 +2 -2
- package/dist/index.js +7645 -5941
- package/dist/index.js.map +4 -4
- package/grammar/CNext.g4 +8 -0
- package/package.json +1 -3
- package/src/transpiler/Transpiler.ts +286 -26
- 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 +8 -0
- 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/__tests__/MixedTypeCategoryAnalyzer.test.ts +346 -0
- package/src/transpiler/logic/analysis/__tests__/ReturnPathAnalyzer.test.ts +232 -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 +1257 -1185
- 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 +45 -0
- package/src/transpiler/logic/symbols/__tests__/SymbolTable.test.ts +49 -0
- 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 +45 -4
- 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.test.ts +13 -1
- 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 +86 -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/expressions/CallExprGenerator.ts +28 -15
- package/src/transpiler/output/codegen/generators/expressions/PostfixExpressionGenerator.ts +26 -13
- 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/StringDeclHelper.ts +16 -13
- package/src/transpiler/output/codegen/helpers/__tests__/AssignmentExpectedTypeResolver.test.ts +19 -0
- package/src/transpiler/output/codegen/helpers/__tests__/StringDeclHelper.test.ts +57 -11
- 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/headers/HeaderGeneratorUtils.ts +65 -24
- package/src/transpiler/state/CodeGenState.ts +20 -16
- package/src/transpiler/types/ICachedFileEntry.ts +7 -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
|
@@ -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
|
// ========================================================================
|
|
@@ -8454,6 +8454,17 @@ describe("CodeGenerator", () => {
|
|
|
8454
8454
|
const generator = new CodeGenerator();
|
|
8455
8455
|
const tSymbols = CNextResolver.resolve(tree, "test.cnx");
|
|
8456
8456
|
const symbols = TSymbolInfoAdapter.convert(tSymbols);
|
|
8457
|
+
// Issue #1100: SymbolTable must be wired up (as the real Transpiler
|
|
8458
|
+
// pipeline always does, see setupGenerator() above) so
|
|
8459
|
+
// getMemberTypeInfo() can resolve struct field array-ness. Without
|
|
8460
|
+
// this, getStructFieldInfo() always returns null and the struct
|
|
8461
|
+
// field's array-ness is never determined via the correct path — this
|
|
8462
|
+
// test was previously passing only because a since-removed blanket
|
|
8463
|
+
// "parameter -> array access" rule (Issue #579) coincidentally
|
|
8464
|
+
// produced the right output for the wrong reason.
|
|
8465
|
+
const symbolTable = new SymbolTable();
|
|
8466
|
+
symbolTable.addTSymbols(tSymbols);
|
|
8467
|
+
CodeGenState.symbolTable = symbolTable;
|
|
8457
8468
|
|
|
8458
8469
|
const code = generator.generate(tree, tokenStream, {
|
|
8459
8470
|
symbolInfo: symbols,
|
|
@@ -14922,7 +14933,8 @@ describe("CodeGenerator", () => {
|
|
|
14922
14933
|
it("should reject break - not part of C-Next spec", () => {
|
|
14923
14934
|
const source = `
|
|
14924
14935
|
void test() {
|
|
14925
|
-
|
|
14936
|
+
u32 i <- 0;
|
|
14937
|
+
while (i < 10) {
|
|
14926
14938
|
break;
|
|
14927
14939
|
}
|
|
14928
14940
|
}
|
|
@@ -3,10 +3,20 @@
|
|
|
3
3
|
* Tests type classification, conversion validation, and literal validation
|
|
4
4
|
*/
|
|
5
5
|
import { describe, it, expect, beforeEach } from "vitest";
|
|
6
|
+
import { CharStream, CommonTokenStream } from "antlr4ng";
|
|
6
7
|
import TypeResolver from "../TypeResolver";
|
|
7
8
|
import SymbolTable from "../../../logic/symbols/SymbolTable";
|
|
8
9
|
import CodeGenState from "../../../state/CodeGenState";
|
|
9
10
|
import TTypeInfo from "../types/TTypeInfo";
|
|
11
|
+
import { CNextLexer } from "../../../logic/parser/grammar/CNextLexer";
|
|
12
|
+
import { CNextParser } from "../../../logic/parser/grammar/CNextParser";
|
|
13
|
+
|
|
14
|
+
/** Parse a standalone C-Next expression into an ExpressionContext. */
|
|
15
|
+
function parseExpression(source: string) {
|
|
16
|
+
const lexer = new CNextLexer(CharStream.fromString(source));
|
|
17
|
+
const parser = new CNextParser(new CommonTokenStream(lexer));
|
|
18
|
+
return parser.expression();
|
|
19
|
+
}
|
|
10
20
|
|
|
11
21
|
describe("TypeResolver", () => {
|
|
12
22
|
let symbolTable: SymbolTable;
|
|
@@ -598,6 +608,89 @@ describe("TypeResolver", () => {
|
|
|
598
608
|
});
|
|
599
609
|
});
|
|
600
610
|
|
|
611
|
+
describe("getIntegerExpressionType", () => {
|
|
612
|
+
function setInt(name: string, baseType: string, bitWidth: number): void {
|
|
613
|
+
setTypeInfo(name, { baseType, bitWidth, isArray: false, isConst: false });
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
it("resolves a signed composite to its operand category and width", () => {
|
|
617
|
+
setInt("a", "i32", 32);
|
|
618
|
+
setInt("b", "i32", 32);
|
|
619
|
+
expect(
|
|
620
|
+
TypeResolver.getIntegerExpressionType(parseExpression("a + b")),
|
|
621
|
+
).toBe("i32");
|
|
622
|
+
});
|
|
623
|
+
|
|
624
|
+
it("resolves an unsigned composite to its operand category and width", () => {
|
|
625
|
+
setInt("a", "u32", 32);
|
|
626
|
+
setInt("b", "u32", 32);
|
|
627
|
+
expect(
|
|
628
|
+
TypeResolver.getIntegerExpressionType(parseExpression("a | b")),
|
|
629
|
+
).toBe("u32");
|
|
630
|
+
});
|
|
631
|
+
|
|
632
|
+
it("uses the widest operand width across a same-category composite", () => {
|
|
633
|
+
setInt("small", "u8", 8);
|
|
634
|
+
setInt("big", "u32", 32);
|
|
635
|
+
expect(
|
|
636
|
+
TypeResolver.getIntegerExpressionType(parseExpression("small + big")),
|
|
637
|
+
).toBe("u32");
|
|
638
|
+
});
|
|
639
|
+
|
|
640
|
+
it("still resolves a simple variable via getExpressionType", () => {
|
|
641
|
+
setInt("x", "i16", 16);
|
|
642
|
+
expect(TypeResolver.getIntegerExpressionType(parseExpression("x"))).toBe(
|
|
643
|
+
"i16",
|
|
644
|
+
);
|
|
645
|
+
});
|
|
646
|
+
|
|
647
|
+
it("returns null when no integer variable leaf can be resolved", () => {
|
|
648
|
+
expect(
|
|
649
|
+
TypeResolver.getIntegerExpressionType(parseExpression("a + b")),
|
|
650
|
+
).toBeNull();
|
|
651
|
+
});
|
|
652
|
+
|
|
653
|
+
it("ignores integer literals (contextually typed, not fixed-category)", () => {
|
|
654
|
+
setInt("a", "u32", 32);
|
|
655
|
+
expect(
|
|
656
|
+
TypeResolver.getIntegerExpressionType(parseExpression("a + 5")),
|
|
657
|
+
).toBe("u32");
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
it("narrows a bit-extraction operand to the extracted width, not the variable's full width", () => {
|
|
661
|
+
setInt("a", "u32", 32);
|
|
662
|
+
setInt("b", "u64", 64);
|
|
663
|
+
// b[0, 32] is u32, so a + b[0, 32] is u32 — NOT u64. Typing it u64 would
|
|
664
|
+
// make slice codegen cast the composite to a wider type (MISRA 10.8).
|
|
665
|
+
expect(
|
|
666
|
+
TypeResolver.getIntegerExpressionType(parseExpression("a + b[0, 32]")),
|
|
667
|
+
).toBe("u32");
|
|
668
|
+
});
|
|
669
|
+
|
|
670
|
+
it("types an array-element operand by its element type, ignoring the index variable's width", () => {
|
|
671
|
+
setTypeInfo("arr", {
|
|
672
|
+
baseType: "u8",
|
|
673
|
+
bitWidth: 8,
|
|
674
|
+
isArray: true,
|
|
675
|
+
isConst: false,
|
|
676
|
+
});
|
|
677
|
+
setInt("idx", "u64", 64);
|
|
678
|
+
setInt("a", "u8", 8);
|
|
679
|
+
// arr[idx] is u8 (the element); a wide index must not inflate the width.
|
|
680
|
+
expect(
|
|
681
|
+
TypeResolver.getIntegerExpressionType(parseExpression("a + arr[idx]")),
|
|
682
|
+
).toBe("u8");
|
|
683
|
+
});
|
|
684
|
+
|
|
685
|
+
it("types a bit-extraction operand by the extracted width when it is the widest operand", () => {
|
|
686
|
+
setInt("b", "u64", 64);
|
|
687
|
+
setInt("c", "u8", 8);
|
|
688
|
+
expect(
|
|
689
|
+
TypeResolver.getIntegerExpressionType(parseExpression("b[0, 32] + c")),
|
|
690
|
+
).toBe("u32");
|
|
691
|
+
});
|
|
692
|
+
});
|
|
693
|
+
|
|
601
694
|
// ========================================================================
|
|
602
695
|
// Postfix Expression Type Detection
|
|
603
696
|
// ========================================================================
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for TypeValidator.validateLoopConditionNotAlwaysTrue
|
|
3
|
+
* ADR-113 / Issue #1075: reject always-true literal loop conditions (E0707).
|
|
4
|
+
*
|
|
5
|
+
* Only the v0.2.18 literal slice — integer/bool literal comparisons decided
|
|
6
|
+
* without symbol resolution. Named constants, non-literal operands, and
|
|
7
|
+
* always-false conditions are out of scope (#1076).
|
|
8
|
+
*/
|
|
9
|
+
import { describe, it, expect } from "vitest";
|
|
10
|
+
import { CharStream, CommonTokenStream } from "antlr4ng";
|
|
11
|
+
import { CNextLexer } from "../../../logic/parser/grammar/CNextLexer";
|
|
12
|
+
import { CNextParser } from "../../../logic/parser/grammar/CNextParser";
|
|
13
|
+
import TypeValidator from "../TypeValidator";
|
|
14
|
+
|
|
15
|
+
function parseCondition(text: string) {
|
|
16
|
+
const charStream = CharStream.fromString(text);
|
|
17
|
+
const lexer = new CNextLexer(charStream);
|
|
18
|
+
const tokenStream = new CommonTokenStream(lexer);
|
|
19
|
+
const parser = new CNextParser(tokenStream);
|
|
20
|
+
return parser.expression();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function isFlaggedAlwaysTrue(text: string): boolean {
|
|
24
|
+
try {
|
|
25
|
+
TypeValidator.validateLoopConditionNotAlwaysTrue(parseCondition(text));
|
|
26
|
+
return false;
|
|
27
|
+
} catch (error) {
|
|
28
|
+
return /E0707/.test((error as Error).message);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
describe("TypeValidator.validateLoopConditionNotAlwaysTrue (E0707)", () => {
|
|
33
|
+
describe("flags always-true literal comparisons", () => {
|
|
34
|
+
it.each([
|
|
35
|
+
"1 = 1",
|
|
36
|
+
"true = true",
|
|
37
|
+
"false = false",
|
|
38
|
+
"1 != 2",
|
|
39
|
+
"5 > 3",
|
|
40
|
+
"3 < 5",
|
|
41
|
+
"3 >= 3",
|
|
42
|
+
"5 <= 5",
|
|
43
|
+
"0x10 = 16",
|
|
44
|
+
"0XFF = 255", // uppercase hex prefix
|
|
45
|
+
"0b10 = 2",
|
|
46
|
+
"5u8 = 5", // type-suffixed literal
|
|
47
|
+
])("flags %s", (condition) => {
|
|
48
|
+
expect(isFlaggedAlwaysTrue(condition)).toBe(true);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("includes the condition text and steers to forever", () => {
|
|
52
|
+
let message = "";
|
|
53
|
+
try {
|
|
54
|
+
TypeValidator.validateLoopConditionNotAlwaysTrue(
|
|
55
|
+
parseCondition("1 = 1"),
|
|
56
|
+
);
|
|
57
|
+
} catch (error) {
|
|
58
|
+
message = (error as Error).message;
|
|
59
|
+
}
|
|
60
|
+
expect(message).toContain(
|
|
61
|
+
"Error E0707: loop condition '1=1' is always true",
|
|
62
|
+
);
|
|
63
|
+
expect(message).toContain("forever { ... }");
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
describe("does NOT flag always-false comparisons (out of scope, #1076)", () => {
|
|
68
|
+
it.each(["1 = 2", "true = false", "1 != 1", "5 < 3", "3 > 5", "5 <= 3"])(
|
|
69
|
+
"allows %s",
|
|
70
|
+
(condition) => {
|
|
71
|
+
expect(isFlaggedAlwaysTrue(condition)).toBe(false);
|
|
72
|
+
},
|
|
73
|
+
);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
describe("does NOT flag non-literal or compound conditions (needs folding, #1076)", () => {
|
|
77
|
+
it.each([
|
|
78
|
+
"x = 1", // identifier operand
|
|
79
|
+
"MAX > 0", // named constant
|
|
80
|
+
"a = b", // two identifiers
|
|
81
|
+
"5.0 > 3.0", // float operands
|
|
82
|
+
"1 + 0 = 1", // compound left operand
|
|
83
|
+
"1 = 1 || 0 > 5", // logical-or combination
|
|
84
|
+
"1 = 1 && 2 = 2", // logical-and combination
|
|
85
|
+
"0777 = 777", // leading-zero literal: C reads it as octal (511), so a
|
|
86
|
+
// decimal parse (777) would diverge — skipped, not wrongly flagged
|
|
87
|
+
])("allows %s", (condition) => {
|
|
88
|
+
expect(isFlaggedAlwaysTrue(condition)).toBe(false);
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
});
|