-
Notifications
You must be signed in to change notification settings - Fork 35
Propagate Function Invocations #216
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 6 commits
b3c83ce
c7afdde
1516285
9970654
d7dbef0
2072c59
2696792
78d70cc
6b8b334
91d8699
b7c463a
647f13c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,6 +7,7 @@ | |
|
|
||
| import liquidjava.rj_language.ast.BinaryExpression; | ||
| import liquidjava.rj_language.ast.Expression; | ||
| import liquidjava.rj_language.ast.FunctionInvocation; | ||
| import liquidjava.rj_language.ast.Var; | ||
|
|
||
| public class VariableResolver { | ||
|
|
@@ -45,31 +46,50 @@ private static void resolveRecursive(Expression exp, Map<String, Expression> map | |
| if ("&&".equals(op)) { | ||
| resolveRecursive(be.getFirstOperand(), map); | ||
| resolveRecursive(be.getSecondOperand(), map); | ||
| } else if ("==".equals(op)) { | ||
| Expression left = be.getFirstOperand(); | ||
| Expression right = be.getSecondOperand(); | ||
| if (left instanceof Var var && right.isLiteral()) { | ||
| map.put(var.getName(), right.clone()); | ||
| } else if (right instanceof Var var && left.isLiteral()) { | ||
| map.put(var.getName(), left.clone()); | ||
| } else if (left instanceof Var leftVar && right instanceof Var rightVar) { | ||
| // to substitute internal variable with user-facing variable | ||
| if (isInternal(leftVar) && !isInternal(rightVar) && !isReturnVar(leftVar)) { | ||
| map.put(leftVar.getName(), right.clone()); | ||
| } else if (isInternal(rightVar) && !isInternal(leftVar) && !isReturnVar(rightVar)) { | ||
| map.put(rightVar.getName(), left.clone()); | ||
| } else if (isInternal(leftVar) && isInternal(rightVar)) { | ||
| // to substitute the lower-counter variable with the higher-counter one | ||
| boolean isLeftCounterLower = getCounter(leftVar) <= getCounter(rightVar); | ||
| Var lowerVar = isLeftCounterLower ? leftVar : rightVar; | ||
| Var higherVar = isLeftCounterLower ? rightVar : leftVar; | ||
| if (!isReturnVar(lowerVar) && !isFreshVar(higherVar)) | ||
| map.putIfAbsent(lowerVar.getName(), higherVar.clone()); | ||
| } | ||
| return; | ||
| } | ||
| if (!"==".equals(op)) | ||
| return; | ||
|
|
||
| Expression left = be.getFirstOperand(); | ||
| Expression right = be.getSecondOperand(); | ||
| String leftKey = substitutionKey(left); | ||
| String rightKey = substitutionKey(right); | ||
|
|
||
| if (leftKey != null && right.isLiteral()) { | ||
| map.put(leftKey, right.clone()); | ||
| } else if (rightKey != null && left.isLiteral()) { | ||
| map.put(rightKey, left.clone()); | ||
| } else if (left instanceof Var leftVar && right instanceof Var rightVar) { | ||
| // to substitute internal variable with user-facing variable | ||
| if (isInternal(leftVar) && !isInternal(rightVar) && !isReturnVar(leftVar)) { | ||
| map.put(leftVar.getName(), right.clone()); | ||
| } else if (isInternal(rightVar) && !isInternal(leftVar) && !isReturnVar(rightVar)) { | ||
| map.put(rightVar.getName(), left.clone()); | ||
| } else if (isInternal(leftVar) && isInternal(rightVar)) { | ||
| // to substitute the lower-counter variable with the higher-counter one | ||
| boolean isLeftCounterLower = getCounter(leftVar) <= getCounter(rightVar); | ||
| Var lowerVar = isLeftCounterLower ? leftVar : rightVar; | ||
| Var higherVar = isLeftCounterLower ? rightVar : leftVar; | ||
| if (!isReturnVar(lowerVar) && !isFreshVar(higherVar)) | ||
| map.putIfAbsent(lowerVar.getName(), higherVar.clone()); | ||
| } | ||
| } else if (left instanceof Var var && !(right instanceof Var) && canSubstitute(var, right)) { | ||
| map.put(var.getName(), right.clone()); | ||
| } else if (left instanceof FunctionInvocation && !(right instanceof Var) | ||
| && !right.toString().contains(leftKey)) { | ||
| map.put(leftKey, right.clone()); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this means we would not substitute something like: because the string on the right contains f(a) even though they are not related, right?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You're right, I'll replace that string check with an AST check. |
||
| } | ||
| } | ||
|
|
||
| private static String substitutionKey(Expression exp) { | ||
| if (exp instanceof Var var) | ||
| return var.getName(); | ||
| if (exp instanceof FunctionInvocation) | ||
| return exp.toString(); | ||
| return null; | ||
| } | ||
|
|
||
| /** | ||
| * Handles transitive variable equalities in the map (e.g. map: x -> y, y -> 1 => map: x -> 1, y -> 1) | ||
| * | ||
|
|
@@ -124,16 +144,25 @@ private static boolean hasUsage(Expression exp, String name) { | |
| if (exp instanceof BinaryExpression binary && "==".equals(binary.getOperator())) { | ||
| Expression left = binary.getFirstOperand(); | ||
| Expression right = binary.getSecondOperand(); | ||
| if (left instanceof Var v && v.getName().equals(name) && right.isLiteral()) | ||
| if (left instanceof Var v && v.getName().equals(name) | ||
| && (right.isLiteral() || (!(right instanceof Var) && canSubstitute(v, right)))) | ||
| return false; | ||
| if (left instanceof FunctionInvocation && left.toString().equals(name) | ||
| && (right.isLiteral() || (!(right instanceof Var) && !right.toString().contains(name)))) | ||
| return false; | ||
| if (right instanceof Var v && v.getName().equals(name) && left.isLiteral()) | ||
| return false; | ||
| if (right instanceof FunctionInvocation && right.toString().equals(name) && left.isLiteral()) | ||
| return false; | ||
| } | ||
|
|
||
| // usage found | ||
| if (exp instanceof Var var && var.getName().equals(name)) { | ||
| return true; | ||
| } | ||
| if (exp instanceof FunctionInvocation && exp.toString().equals(name)) { | ||
| return true; | ||
| } | ||
|
|
||
| // recurse children | ||
| if (exp.hasChildren()) { | ||
|
|
@@ -164,4 +193,22 @@ private static boolean isReturnVar(Var var) { | |
| private static boolean isFreshVar(Var var) { | ||
| return var.getName().startsWith("#fresh_"); | ||
| } | ||
| } | ||
|
|
||
| private static boolean canSubstitute(Var var, Expression value) { | ||
| return !isReturnVar(var) && !isFreshVar(var) && !containsVariable(value, var.getName()); | ||
| } | ||
|
|
||
| private static boolean containsVariable(Expression exp, String name) { | ||
| if (exp instanceof Var var) | ||
| return var.getName().equals(name); | ||
|
|
||
| if (!exp.hasChildren()) | ||
| return false; | ||
|
|
||
| for (Expression child : exp.getChildren()) { | ||
| if (containsVariable(child, name)) | ||
| return true; | ||
| } | ||
| return false; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,6 +10,7 @@ | |
| import liquidjava.rj_language.ast.AliasInvocation; | ||
| import liquidjava.rj_language.ast.BinaryExpression; | ||
| import liquidjava.rj_language.ast.Expression; | ||
| import liquidjava.rj_language.ast.FunctionInvocation; | ||
| import liquidjava.rj_language.ast.Ite; | ||
| import liquidjava.rj_language.ast.LiteralBoolean; | ||
| import liquidjava.rj_language.ast.LiteralInt; | ||
|
|
@@ -1089,4 +1090,72 @@ void testEquivalentBoundsKeepOneSide() { | |
|
|
||
| assertDerivationEquals(expected, result, "Equivalent bounds simplification should preserve conjunction origin"); | ||
| } | ||
|
|
||
| @Test | ||
| void testSubstitutesVariableDefinedByArithmeticExpression() { | ||
| // Given: z == y - 2 && y == x + 1 | ||
| // Expected: z == x - 1 | ||
|
|
||
| Expression z = new Var("z"); | ||
| Expression y = new Var("y"); | ||
| Expression x = new Var("x"); | ||
|
|
||
| Expression returnExpression = new BinaryExpression(z, "==", new BinaryExpression(y, "-", new LiteralInt(2))); | ||
| Expression yDefinition = new BinaryExpression(y, "==", new BinaryExpression(x, "+", new LiteralInt(1))); | ||
| Expression fullExpression = new BinaryExpression(returnExpression, "&&", yDefinition); | ||
|
|
||
| // When | ||
| ValDerivationNode result = ExpressionSimplifier.simplify(fullExpression); | ||
|
|
||
| // Then | ||
| assertNotNull(result, "Result should not be null"); | ||
| assertEquals("z == x - 1", result.getValue().toString(), "Expected variable definition to be substituted"); | ||
| } | ||
|
|
||
| @Test | ||
| void testFoldsAdjacentIntegerConstantsInLeftAssociatedArithmetic() { | ||
| // Given: x + 1 - 2, x - 1 + 2, x + 1 + 2, and x + 1 - 1 | ||
| // Expected: x - 1, x + 1, x + 3, and x | ||
|
|
||
| Expression x = new Var("x"); | ||
|
|
||
| Expression xPlus1Minus2 = new BinaryExpression(new BinaryExpression(x, "+", new LiteralInt(1)), "-", | ||
| new LiteralInt(2)); | ||
| Expression xMinus1Plus2 = new BinaryExpression(new BinaryExpression(x, "-", new LiteralInt(1)), "+", | ||
| new LiteralInt(2)); | ||
| Expression xPlus1Plus2 = new BinaryExpression(new BinaryExpression(x, "+", new LiteralInt(1)), "+", | ||
| new LiteralInt(2)); | ||
| Expression xPlus1Minus1 = new BinaryExpression(new BinaryExpression(x, "+", new LiteralInt(1)), "-", | ||
| new LiteralInt(1)); | ||
|
|
||
| // When / Then | ||
| assertEquals("x - 1", ExpressionSimplifier.simplify(xPlus1Minus2).getValue().toString()); | ||
| assertEquals("x + 1", ExpressionSimplifier.simplify(xMinus1Plus2).getValue().toString()); | ||
| assertEquals("x + 3", ExpressionSimplifier.simplify(xPlus1Plus2).getValue().toString()); | ||
| assertEquals("x", ExpressionSimplifier.simplify(xPlus1Minus1).getValue().toString()); | ||
| } | ||
|
|
||
| @Test | ||
| void testFunctionInvocationEqualitiesPropagateTransitively() { | ||
| // Given: size(x3) == size(x2) - 1 && size(x2) == size(x1) + 1 && size(x1) == 0 | ||
| // Expected: size(x3) == 0 | ||
| Expression x1 = new Var("x1"); | ||
| Expression x2 = new Var("x2"); | ||
| Expression x3 = new Var("x3"); | ||
| Expression sizeX1 = new FunctionInvocation("size", List.of(x1)); | ||
| Expression sizeX2 = new FunctionInvocation("size", List.of(x2)); | ||
| Expression sizeX3 = new FunctionInvocation("size", List.of(x3)); | ||
|
|
||
| Expression sizeX3EqualsSizeX2Minus1 = new BinaryExpression(sizeX3, "==", | ||
| new BinaryExpression(sizeX2, "-", new LiteralInt(1))); | ||
| Expression sizeX2EqualsSizeX1Plus1 = new BinaryExpression(sizeX2, "==", | ||
| new BinaryExpression(sizeX1, "+", new LiteralInt(1))); | ||
| Expression sizeX1Equals0 = new BinaryExpression(sizeX1, "==", new LiteralInt(0)); | ||
| Expression fullExpression = new BinaryExpression(sizeX3EqualsSizeX2Minus1, "&&", | ||
| new BinaryExpression(sizeX2EqualsSizeX1Plus1, "&&", sizeX1Equals0)); | ||
|
|
||
| ValDerivationNode result = ExpressionSimplifier.simplify(fullExpression); | ||
|
|
||
| assertEquals("size(x3) == 0", result.getValue().toString()); | ||
| } | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we need a couple more tests for this, I'm not very convinced |
||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is using strings for substitution really the best approach?