Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
e38d99d
Add SimplifiedExpression
rcosta358 Jun 8, 2026
208249f
Change SimplifiedExpression to SimplifiedPredicate
rcosta358 Jun 8, 2026
92359f3
Requested Changes
rcosta358 Jun 9, 2026
de68c47
Formatting
rcosta358 Jun 9, 2026
8bb6fb4
Minor Changes
rcosta358 Jun 9, 2026
ba977dc
Add VC Substitution
rcosta358 Jun 8, 2026
82eb3be
Add Comments
rcosta358 Jun 8, 2026
63a1c21
SimplifiedPredicate Follow-Up
rcosta358 Jun 8, 2026
36fd1fd
Add `simplifyOnce`
rcosta358 Jun 8, 2026
e8e07d5
Code Refactoring
rcosta358 Jun 9, 2026
27061e9
Add Comment
rcosta358 Jun 9, 2026
47c1844
Code Refactoring
rcosta358 Jun 9, 2026
a3b4f29
Add Fixed Point Iteration
rcosta358 Jun 9, 2026
659a139
Requested Changes
rcosta358 Jun 9, 2026
7f6f9d2
Rename
rcosta358 Jun 9, 2026
2e145d3
Replace `SimplifiedPredicate` with `SimplifiedVCImplication`
rcosta358 Jun 9, 2026
ba81c3a
Refactoring
rcosta358 Jun 9, 2026
43a0bef
Minor Change
rcosta358 Jun 9, 2026
fa7eff5
Add Tests
rcosta358 Jun 11, 2026
607e1b5
Change SimplifiedExpression to SimplifiedPredicate
rcosta358 Jun 8, 2026
0b060bb
Add VC Folding
rcosta358 Jun 9, 2026
7bb0632
Add Fixed Point Iteration
rcosta358 Jun 9, 2026
4c39689
Use SimplifiedVCImplication
rcosta358 Jun 9, 2026
981c63f
Refactoring
rcosta358 Jun 9, 2026
b374c66
Fix
rcosta358 Jun 9, 2026
67b05ce
Refactoring
rcosta358 Jun 9, 2026
9ffbe14
Add Comments
rcosta358 Jun 10, 2026
68c3b42
Refactoring
rcosta358 Jun 10, 2026
eab59f7
Refactoring
rcosta358 Jun 10, 2026
11be88b
Simplify VCFolding
rcosta358 Jun 10, 2026
84f9727
Add Tests
rcosta358 Jun 11, 2026
99131d4
Fixes
rcosta358 Jun 11, 2026
8cde2d2
Update Tests
rcosta358 Jun 11, 2026
ee3919c
Refactor Tests
rcosta358 Jun 11, 2026
cb0cb2e
Update VCImplicationGenerator
rcosta358 Jun 11, 2026
35895a3
Minor Changes
rcosta358 Jun 11, 2026
e4258aa
Minor Changes
rcosta358 Jun 12, 2026
804c7a6
Update Tests
rcosta358 Jun 12, 2026
1043d1a
Refactor Tests
rcosta358 Jun 12, 2026
9fc831a
Add Comments
rcosta358 Jun 13, 2026
85d9a86
Fix Origins
rcosta358 Jun 13, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import java.util.Objects;

import liquidjava.rj_language.Predicate;
import liquidjava.rj_language.opt.VCSimplification;
import liquidjava.utils.Utils;
import spoon.reflect.reference.CtTypeReference;

Expand Down Expand Up @@ -85,6 +86,10 @@ public String toString() {
return String.format("%-20s %s", "", refinement.toString());
}

public VCImplication simplify() {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

return VCSimplification.simplifyToFixedPoint(this);
}

public Predicate toConjunctions() {
Predicate c = new Predicate();
if (name == null && type == null && next == null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ else if (e instanceof FunctionInvocation)
return functionType(ctx, (FunctionInvocation) e);
else if (e instanceof AliasInvocation)
return boolType(factory);

return Optional.empty();
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,283 @@
package liquidjava.rj_language.opt;

import liquidjava.processor.SimplifiedVCImplication;
import liquidjava.processor.VCImplication;
import liquidjava.rj_language.Predicate;
import liquidjava.rj_language.ast.BinaryExpression;
import liquidjava.rj_language.ast.Enum;
import liquidjava.rj_language.ast.Expression;
import liquidjava.rj_language.ast.GroupExpression;
import liquidjava.rj_language.ast.Ite;
import liquidjava.rj_language.ast.LiteralBoolean;
import liquidjava.rj_language.ast.LiteralInt;
import liquidjava.rj_language.ast.LiteralReal;
import liquidjava.rj_language.ast.UnaryExpression;

/**
* Simplifies VCImplication chains by folding constant expressions and other foldable patterns inside refinements
*/
public class VCFolding {

/**
* Applies folding to the first foldable predicate in a VC chain
*/
public static VCImplication apply(VCImplication implication) {
if (implication == null)
return null;

Expression expression = implication.getRefinement().getExpression();
Expression folded = fold(expression);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So with this architecture, each VCImplication only has one origin->simplification right?
Its not possible for one VCImplication to formulate 2 "independent" simplifications for different parts of the expression like you had planned before.
Like

           VC: forall x: x == 3 -> forall y: y == 4 -> x < y
            // there are 2 independent simplifcations possible
VC: forall y: y == 4 -> 3 < y      e      forall x: x == 3 -> x < 4
           // but just one outcome for each of the next ones
                             3 < 4
                              true

If you want to keep this structure, I think this architecture does not work 😕
Nevertheless, this current option works for me if you want to leave that for a followup but it might require some more deep changes

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, you are correct, but I think we should keep this approach because it stays consistent with the VCSubstitution, and even though it can be a bit more verbose than the previous one, each simplification step is atomic and easier to debug.

if (!expression.equals(folded)) {
VCImplication result = new SimplifiedVCImplication(implication, new Predicate(folded), implication);
result.setNext(implication.getNext() == null ? null : implication.getNext().clone());
return result;
}

VCImplication next = apply(implication.getNext());
if (implication.getNext() == null || implication.getNext().equals(next))
return implication;

VCImplication result = implication.copyWithRefinement(implication.getRefinement().clone());
result.setNext(next);
return result;
}

/**
* Folds the first foldable expression found
*/
private static Expression fold(Expression expression) {
// enum constant -> literal
if (expression instanceof Enum en && en.getResolvedLiteral() != null)
return en.getResolvedLiteral().clone();
if (expression instanceof BinaryExpression binary)
return foldBinary(binary);
if (expression instanceof UnaryExpression unary)
return foldUnary(unary);
if (expression instanceof Ite ite)
return foldIte(ite);
// (x) -> x
if (expression instanceof GroupExpression group && group.getChildren().size() == 1)
return group.getExpression().clone();
return expression.clone();
}

/**
* Folds a binary expression and its operands
*/
private static Expression foldBinary(BinaryExpression binary) {
Expression left = binary.getFirstOperand();
Expression foldedLeft = fold(left);
if (!left.equals(foldedLeft))
return new BinaryExpression(foldedLeft, binary.getOperator(), binary.getSecondOperand().clone());

Expression right = binary.getSecondOperand();
Expression foldedRight = fold(right);
if (!right.equals(foldedRight))
return new BinaryExpression(left.clone(), binary.getOperator(), foldedRight);

String op = binary.getOperator();

Expression foldedBinary = foldLiteralBinary(left, right, op);
if (foldedBinary != null)
return foldedBinary;

Expression foldedAdjacentInts = foldAdjacentInts(left, right, op);
if (foldedAdjacentInts != null)
return foldedAdjacentInts;

return new BinaryExpression(left, op, right);
}

/**
* Folds a unary expression and its operand
*/
private static Expression foldUnary(UnaryExpression unary) {
Expression operand = unary.getExpression();
Expression foldedOperand = fold(operand);
if (!operand.equals(foldedOperand))
return new UnaryExpression(unary.getOp(), foldedOperand);

String op = unary.getOp();

// !true -> false
// !false -> true
if ("!".equals(op) && operand instanceof LiteralBoolean literal)
return new LiteralBoolean(!literal.isBooleanTrue());

if ("-".equals(op)) {
// -(x) -> -x
if (operand instanceof LiteralInt literal)
return new LiteralInt(-literal.getValue());
if (operand instanceof LiteralReal literal)
return new LiteralReal(-literal.getValue());
}

return new UnaryExpression(op, operand);
}

/**
* Folds a conditional expression and its branches
*/
private static Expression foldIte(Ite ite) {
Expression condition = ite.getCondition();
Expression foldedCondition = fold(condition);
if (!condition.equals(foldedCondition))
return new Ite(foldedCondition, ite.getThen().clone(), ite.getElse().clone());

Expression thenExpression = ite.getThen();
Expression foldedThen = fold(thenExpression);
if (!thenExpression.equals(foldedThen))
return new Ite(condition.clone(), foldedThen, ite.getElse().clone());

Expression elseExpression = ite.getElse();
Expression foldedElse = fold(elseExpression);
if (!elseExpression.equals(foldedElse))
return new Ite(condition.clone(), thenExpression.clone(), foldedElse);

// true ? x : y -> x
// false ? x : y -> y
if (condition instanceof LiteralBoolean literal)
return literal.isBooleanTrue() ? thenExpression : elseExpression;

// y ? x : x -> x
if (thenExpression.equals(elseExpression))
return thenExpression;

return new Ite(condition, thenExpression, elseExpression);
}

/**
* Folds a binary expression whose operands are both literals
*/
private static Expression foldLiteralBinary(Expression left, Expression right, String op) {
if (left instanceof LiteralInt leftInt && right instanceof LiteralInt rightInt)
return foldInts(leftInt.getValue(), rightInt.getValue(), op);

if (left instanceof LiteralReal leftReal && right instanceof LiteralReal rightReal)
return foldReals(leftReal.getValue(), rightReal.getValue(), op);

if (isMixedNumeric(left, right)) {
double l = numericValue(left);
double r = numericValue(right);
return foldReals(l, r, op);
}

if (left instanceof LiteralBoolean leftBool && right instanceof LiteralBoolean rightBool)
return foldBooleans(leftBool.isBooleanTrue(), rightBool.isBooleanTrue(), op);

if (left instanceof Enum leftEnum && right instanceof Enum rightEnum
&& leftEnum.getTypeName().equals(rightEnum.getTypeName())) {
boolean equal = leftEnum.getConstName().equals(rightEnum.getConstName());
// Enum.A == Enum.A -> true
// Enum.A != Enum.B -> true
return switch (op) {
case "==" -> new LiteralBoolean(equal);
case "!=" -> new LiteralBoolean(!equal);
default -> null;
};
}

return null;
}

/**
* Combines adjacent integer constants in additions and subtractions
*/
private static Expression foldAdjacentInts(Expression left, Expression right, String op) {
if (!"+".equals(op) && !"-".equals(op))
return null;
if (!(right instanceof LiteralInt rightLiteral))
return null;
if (!(left instanceof BinaryExpression leftBinary))
return null;
if (!"+".equals(leftBinary.getOperator()) && !"-".equals(leftBinary.getOperator()))
return null;
if (!(leftBinary.getSecondOperand()instanceof LiteralInt leftLiteral))
return null;

// treat subtraction as adding a negative constant and then add the two
int signedLeft = "+".equals(leftBinary.getOperator()) ? leftLiteral.getValue() : -leftLiteral.getValue();
int signedRight = "+".equals(op) ? rightLiteral.getValue() : -rightLiteral.getValue();
int constant = signedLeft + signedRight;
Expression base = leftBinary.getFirstOperand().clone();
// x + n - n -> x
if (constant == 0)
return base;
// x + n + m -> x + k
if (constant > 0)
return new BinaryExpression(base, "+", new LiteralInt(constant));
// x + n - m -> x - k
return new BinaryExpression(base, "-", new LiteralInt(-constant));
}

/**
* Folds integer operations
*/
private static Expression foldInts(int left, int right, String op) {
return switch (op) {
case "+" -> new LiteralInt(left + right);
case "-" -> new LiteralInt(left - right);
case "*" -> new LiteralInt(left * right);
case "/" -> right != 0 ? new LiteralInt(left / right) : null;
case "%" -> right != 0 ? new LiteralInt(left % right) : null;
case "<" -> new LiteralBoolean(left < right);
case "<=" -> new LiteralBoolean(left <= right);
case ">" -> new LiteralBoolean(left > right);
case ">=" -> new LiteralBoolean(left >= right);
case "==" -> new LiteralBoolean(left == right);
case "!=" -> new LiteralBoolean(left != right);
default -> null;
};
}

/**
* Folds real number operations
*/
private static Expression foldReals(double left, double right, String op) {
return switch (op) {
case "+" -> new LiteralReal(left + right);
case "-" -> new LiteralReal(left - right);
case "*" -> new LiteralReal(left * right);
case "/" -> right != 0.0 ? new LiteralReal(left / right) : null;
case "%" -> right != 0.0 ? new LiteralReal(left % right) : null;
case "<" -> new LiteralBoolean(left < right);
case "<=" -> new LiteralBoolean(left <= right);
case ">" -> new LiteralBoolean(left > right);
case ">=" -> new LiteralBoolean(left >= right);
case "==" -> new LiteralBoolean(left == right);
case "!=" -> new LiteralBoolean(left != right);
default -> null;
};
}

/**
* Folds boolean operations
*/
private static Expression foldBooleans(boolean left, boolean right, String op) {
return switch (op) {
case "&&" -> new LiteralBoolean(left && right);
case "||" -> new LiteralBoolean(left || right);
case "-->" -> new LiteralBoolean(!left || right);
case "==" -> new LiteralBoolean(left == right);
case "!=" -> new LiteralBoolean(left != right);
default -> null;
};
}

/**
* Checks whether two expressions mix integer and real literals
*/
private static boolean isMixedNumeric(Expression left, Expression right) {
return left instanceof LiteralInt && right instanceof LiteralReal
|| left instanceof LiteralReal && right instanceof LiteralInt;
}

/**
* Reads a numeric literal as a double
*/
private static double numericValue(Expression expression) {
if (expression instanceof LiteralInt literal)
return literal.getValue();
return ((LiteralReal) expression).getValue();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
public class VCSimplification {

/**
* Applies all available simplification steps to a VC chain
* Applies all available simplification steps to a VC chain until a fixed point is reached
*/
public static VCImplication simplifyToFixedPoint(VCImplication implication) {
if (implication == null)
Expand All @@ -18,8 +18,8 @@ public static VCImplication simplifyToFixedPoint(VCImplication implication) {
VCImplication current = implication.clone();
while (true) {
VCImplication simplified = simplifyOnce(current);
if (current.equals(simplified)) // fixed point reached
return simplified;
if (current.equals(simplified))
return simplified; // fixed point reached
current = simplified;
}
}
Expand All @@ -36,7 +36,6 @@ public static VCImplication simplifyOnce(VCImplication implication) {
if (!implication.equals(substituted))
return substituted;

// TODO: add more simplification steps here (e.g., folding)
return substituted;
return VCFolding.apply(implication);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It could be cool to have like implication.fold() and have that method call the VCFolding. In terms of API thats a bit more ergonomic since the user doenst need to know that there is another class resposible for the folding itself

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe just implication.simplify() to call VCSimplification.simplifyToFixedPoint?

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ private static VCImplication substitute(VCImplication implication, VCImplication
return null;

// skip the source node to remove it from the chain and start substitution from the next node
// ∀x. x == v => P(x) -> P(v)
if (implication == node)
return substitute(implication.getNext(), node, replacement);

Expand Down
Loading
Loading