From 130e246ac228c955f16e1734fd10a7c225f73f64 Mon Sep 17 00:00:00 2001 From: JFronny Date: Sat, 20 Apr 2024 18:17:50 +0200 Subject: [PATCH] style(muscript-optimizer): split out optimization of inner expressions to permit approach based exclusively on pattern matching --- .../muscript/ast/context/ExprUtils.java | 2 +- .../jfronny/muscript/optimizer/Optimizer.java | 432 ++++++------------ 2 files changed, 140 insertions(+), 294 deletions(-) diff --git a/muscript-ast/src/main/java/io/gitlab/jfronny/muscript/ast/context/ExprUtils.java b/muscript-ast/src/main/java/io/gitlab/jfronny/muscript/ast/context/ExprUtils.java index 858e0cf..2af24a1 100644 --- a/muscript-ast/src/main/java/io/gitlab/jfronny/muscript/ast/context/ExprUtils.java +++ b/muscript-ast/src/main/java/io/gitlab/jfronny/muscript/ast/context/ExprUtils.java @@ -48,7 +48,7 @@ public class ExprUtils { public static StringExpr asString(Expr expression) { return switch (unpack(expression)) { case StringExpr string -> string; - case NumberLiteral number -> new StringLiteral(expression.location(), StringFormatter.toString(number.value())); + case NumberLiteral number -> new StringLiteral(expression.location(), StringFormatter.toStringPrecise(number.value())); case BoolLiteral bool -> new StringLiteral(expression.location(), bool.value() ? "true" : "false"); case NullLiteral nl -> new StringLiteral(expression.location(), "null"); case DynamicConditional(var location, var condition, var ifTrue, var ifFalse) -> new StringConditional(location, condition, asString(ifTrue), asString(ifFalse)); diff --git a/muscript-optimizer/src/main/java/io/gitlab/jfronny/muscript/optimizer/Optimizer.java b/muscript-optimizer/src/main/java/io/gitlab/jfronny/muscript/optimizer/Optimizer.java index 97a214e..c051698 100644 --- a/muscript-optimizer/src/main/java/io/gitlab/jfronny/muscript/optimizer/Optimizer.java +++ b/muscript-optimizer/src/main/java/io/gitlab/jfronny/muscript/optimizer/Optimizer.java @@ -1,6 +1,5 @@ package io.gitlab.jfronny.muscript.optimizer; -import io.gitlab.jfronny.commons.StringFormatter; import io.gitlab.jfronny.muscript.ast.*; import io.gitlab.jfronny.muscript.ast.bool.*; import io.gitlab.jfronny.muscript.ast.context.Script; @@ -12,7 +11,6 @@ import io.gitlab.jfronny.muscript.core.CodeLocation; import io.gitlab.jfronny.muscript.data.additional.DFinal; import io.gitlab.jfronny.muscript.data.dynamic.Dynamic; -import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -39,328 +37,140 @@ public class Optimizer { } public static StringExpr optimize(StringExpr expr) { - return switch (expr) { + return switch (optimizeInner(expr)) { case null -> throw new NullPointerException(); + case StringConditional(var location, BoolLiteral(var location1, var value), var ifTrue, var ifFalse) -> value ? ifTrue : ifFalse; + case StringConditional(var location, Not(var location1, var condition), var ifTrue, var ifFalse) -> new StringConditional(location, condition, ifFalse, ifTrue); + case Concatenate(var location, StringLiteral(var location1, var left), StringLiteral(var location2, var right)) -> literal(location, left + right); + case Concatenate(var location, Concatenate(var location1, var left1, StringLiteral(var location2, var right1)), StringLiteral(var location3, var right2)) -> new Concatenate(location1, left1, literal(location2, right1 + right2)); + case Concatenate(var location, StringLiteral(var location1, var left1), Concatenate(var location2, StringLiteral(var location3, var left2), var right)) -> new Concatenate(location2, literal(location3, left1 + left2), right); + case StringExpr fallback -> fallback; // Used instead of default to still keep applied optimizations + }; + } + + private static StringExpr optimizeInner(StringExpr expr) { + return switch (expr) { + case null -> null; case StringLiteral e -> e; case ExtensibleStringExpr e -> e.optimize(); case StringUnpack(var inner) -> asString(unpack(optimize(inner))); - case StringCoerce(var inner) -> switch (unpack(inner)) { - case NullLiteral e -> new StringLiteral(inner.location(), "null"); - case BoolLiteral(var location, var value) -> new StringLiteral(location, value ? "true" : "false"); - case NumberLiteral(var location, var value) -> new StringLiteral(location, StringFormatter.toStringPrecise(value)); - case StringExpr e -> e; - case DynamicLiteral(var location, var value) -> new StringLiteral(location, StringFormatter.toString(value)); - default -> new StringCoerce(inner); - }; + case StringCoerce(var inner) -> asString(unpack(optimize(inner))); case StringAssign(var location, var variable, var value) -> new StringAssign(location, variable, optimize(value)); - case StringConditional e -> { - var condition = optimize(e.condition()); - var ifTrue = optimize(e.ifTrue()); - var ifFalse = optimize(e.ifFalse()); - if (condition instanceof BoolLiteral(var location, var value)) yield value ? ifTrue : ifFalse; - if (ifTrue.equals(ifFalse)) yield ifTrue; - if (condition instanceof Not(var location, var inner)) yield new StringConditional(e.location(), inner, ifFalse, ifTrue); - yield new StringConditional(e.location(), condition, ifTrue, ifFalse); - } - case Concatenate e -> { - var location = e.location(); - var left = optimize(e.left()); - var right = optimize(e.right()); - if (left instanceof StringLiteral(var location1, var leftValue) - && right instanceof StringLiteral(var location2, var rightValue)) - yield literal(location, leftValue + rightValue); - if (right instanceof StringLiteral(var location1, var rightValue) - && left instanceof Concatenate(var location2, var newLeft, StringLiteral(var location3, var leftValue))) { - yield new Concatenate(location, newLeft, literal(location2, leftValue + rightValue)); - } - if (left instanceof StringLiteral(var location1, var leftValue) - && right instanceof Concatenate(var location2, StringLiteral(var location3, var rightValue), var newRight)) { - yield new Concatenate(location, literal(location2, leftValue + rightValue), newRight); - } - yield new Concatenate(location, left, right); - } + case StringConditional(var location, var condition, var ifTrue, var ifFalse) -> new StringConditional(location, optimize(condition), optimize(ifTrue), optimize(ifFalse)); + case Concatenate(var location, var left, var right) -> new Concatenate(location, optimize(left), optimize(right)); }; } public static NumberExpr optimize(NumberExpr expr) { - return switch (expr) { + return switch (optimizeInner(expr)) { case null -> throw new NullPointerException(); + case NumberConditional(var location, BoolLiteral(var location1, var value), var ifTrue, var ifFalse) -> value ? ifTrue : ifFalse; + case NumberConditional(var location, Not(var location1, var condition), var ifTrue, var ifFalse) -> new NumberConditional(location, condition, ifFalse, ifTrue); + case Add(var location, NumberLiteral(var location1, var augend), NumberLiteral(var location2, var addend)) -> literal(location, augend + addend); + case Add(var location, Negate(var location1, var augend), Negate(var location2, var addend)) -> new Negate(location, optimize(new Add(location, augend, addend))); + case Add(var location, Negate(var location1, var augend), var addend) -> optimize(new Subtract(location, addend, augend)); + case Add(var location, var augend, Negate(var location1, var addend)) -> optimize(new Subtract(location, augend, addend)); + case Subtract(var location, NumberLiteral(var location1, var minuend), NumberLiteral(var location2, var subtrahend)) -> literal(location, minuend - subtrahend); + case Subtract(var location, Subtract(var location1, var minuend1, var subtrahend1), var subtrahend) -> optimize(new Subtract(location, minuend1, new Add(location1, subtrahend1, subtrahend))); + case Subtract(var location, var minuend, Subtract(var location1, var minuend1, var subtrahend1)) -> new Subtract(location, new Add(location1, minuend, subtrahend1), minuend1); + case Subtract(var location, Negate(var location1, var minuend), Negate(var location2, var subtrahend)) -> new Subtract(location, subtrahend, minuend); + case Subtract(var location, Negate(var location1, var minuend), var subtrahend) -> optimize(new Negate(location, new Add(location, minuend, subtrahend))); + case Subtract(var location, var minuend, Negate(var location1, var subtrahend)) -> optimize(new Add(location, minuend, subtrahend)); + case Negate(var location, NumberLiteral(var location1, var value)) -> literal(location, -value); + case Negate(var location, Negate(var location1, var inner)) -> inner; + case Negate(var location, Subtract(var location1, var minuend, var subtrahend)) -> optimize(new Subtract(location, subtrahend, minuend)); + case Multiply(var location, NumberLiteral(var location1, var multiplier), NumberLiteral(var location2, var multiplicand)) -> literal(location, multiplier * multiplicand); + //TODO optimize multiplication with or division by 1 + case Divide(var location, NumberLiteral(var location1, var dividend), NumberLiteral(var location2, var divisor)) -> literal(location, dividend / divisor); + case Divide(var location, Divide(var location1, var dividend1, var divisor1), var divisor) -> new Divide(location, dividend1, new Multiply(location1, divisor1, divisor)); + case Divide(var location, var dividend, Divide(var location1, var dividend1, var divisor1)) -> new Divide(location, new Multiply(location1, dividend, divisor1), dividend1); + case Divide(var location, Negate(var location1, var dividend), Negate(var location2, var divisor)) -> new Divide(location, divisor, dividend); + case Divide(var location, Negate(var location1, var dividend), var divisor) -> new Negate(location, new Divide(location1, dividend, divisor)); + case Divide(var location, var dividend, Negate(var location1, var divisor)) -> new Negate(location, new Divide(location1, dividend, divisor)); + case Modulo(var location, NumberLiteral(var location1, var dividend), NumberLiteral(var location2, var divisor)) -> literal(location, dividend % divisor); + case Power(var location, NumberLiteral(var location1, var base), NumberLiteral(var location2, var exponent)) -> literal(location, Math.pow(base, exponent)); + case Power(var location, Multiply(var location1, NumberLiteral(var location2, var multiplier), var multiplicand), NumberLiteral(var location3, var exponent)) -> new Multiply(location1, literal(location, Math.pow(multiplier, exponent)), new Power(location, multiplicand, literal(location3, exponent))); + case Power(var location, Multiply(var location1, var multiplier, NumberLiteral(var location2, var multiplicand)), NumberLiteral(var location3, var exponent)) -> new Multiply(location1, literal(location, Math.pow(multiplicand, exponent)), new Power(location, multiplier, literal(location3, exponent))); + case NumberExpr fallback -> fallback; // Used instead of default to still keep applied optimizations + }; + } + + private static NumberExpr optimizeInner(NumberExpr expr) { + return switch (expr) { + case null -> null; case NumberLiteral e -> e; case ExtensibleNumberExpr e -> e.optimize(); case NumberUnpack(var inner) -> asNumber(unpack(optimize(inner))); case NumberAssign(var location, var variable, var value) -> new NumberAssign(location, variable, optimize(value)); - case NumberConditional e -> { - var condition = optimize(e.condition()); - var ifTrue = optimize(e.ifTrue()); - var ifFalse = optimize(e.ifFalse()); - if (condition instanceof BoolLiteral(var location, var value)) - yield value ? ifTrue : ifFalse; - if (ifTrue.equals(ifFalse)) yield ifTrue; - if (condition instanceof Not(var location, var inner)) - yield new NumberConditional(e.location(), inner, ifFalse, ifTrue); - yield new NumberConditional(e.location(), condition, ifTrue, ifFalse); - } - case Add e -> { - var location = e.location(); - var augend = optimize(e.augend()); - var addend = optimize(e.addend()); - if (augend instanceof NumberLiteral(var location1, var augendValue) - && addend instanceof NumberLiteral(var location2, var addendValue)) - yield literal(location, augendValue + addendValue); - if (augend instanceof Negate(var location1, var innerAugend) - && addend instanceof Negate(var location2, var innerAddend)) - yield new Negate(location, optimize(new Add(location, innerAugend, innerAddend))); - if (augend instanceof Negate(var location1, var innerAugend)) - yield optimize(new Subtract(location, addend, innerAugend)); - if (addend instanceof Negate(var location1, var innerAddend)) - yield optimize(new Subtract(location, augend, innerAddend)); - yield new Add(location, augend, addend); - } - case Subtract e -> { - var location = e.location(); - var minuend = optimize(e.minuend()); - var subtrahend = optimize(e.subtrahend()); - if (minuend instanceof NumberLiteral(var location1, var minuendValue) - && subtrahend instanceof NumberLiteral(var location2, var subtrahendValue)) - yield literal(location, minuendValue - subtrahendValue); - if (minuend instanceof Subtract(var location1, var minuend1, var subtrahend1)) - yield optimize(new Subtract(location, minuend1, new Add(minuend.location(), subtrahend1, subtrahend))); - if (subtrahend instanceof Subtract(var location1, var minuend1, var subtrahend1)) - yield new Subtract(location, new Add(subtrahend.location(), minuend, subtrahend1), minuend1); - if (subtrahend instanceof Negate(var location1, var inner)) { - if (minuend instanceof Negate(var location2, var inner2)) - yield new Subtract(location, inner, inner2); - yield optimize(new Add(location, minuend, inner)); - } - if (minuend instanceof Negate(var location1, var inner)) { - yield optimize(new Negate(location, new Add(location, inner, subtrahend))); - } - yield new Subtract(location, minuend, subtrahend); - } - case Negate e -> { - var location = e.location(); - var inner = optimize(e.inner()); - if (inner instanceof Negate(var location1, var innerInner)) - yield innerInner; - if (inner instanceof NumberLiteral(var location1, var value)) - yield literal(location, -value); - if (inner instanceof Subtract(var location1, var minuend, var subtrahend)) - yield optimize(new Subtract(location, subtrahend, minuend)); - yield new Negate(location, inner); - } - case Multiply e -> { - var location = e.location(); - var multiplier = optimize(e.multiplier()); - var multiplicand = optimize(e.multiplicand()); - if (multiplier instanceof NumberLiteral(var location1, var multiplierValue) - && multiplicand instanceof NumberLiteral(var location2, var multiplicandValue)) - yield literal(location, multiplierValue * multiplicandValue); - yield new Multiply(location, multiplicand, multiplier); - } - case Divide e -> { - var location = e.location(); - var dividend = optimize(e.dividend()); - var divisor = optimize(e.divisor()); - if (dividend instanceof NumberLiteral(var location1, var dividendValue) - && divisor instanceof NumberLiteral(var location2, var divisorValue)) - yield literal(location, dividendValue / divisorValue); - if (dividend instanceof Divide(var location1, var dividend1, var divisor1)) - yield new Divide(location, dividend1, new Multiply(dividend.location(), divisor1, divisor)); - if (divisor instanceof Divide(var location1, var dividend1, var divisor1)) - yield new Divide(location, new Multiply(dividend.location(), dividend, divisor1), dividend1); - yield new Divide(location, dividend, divisor); - } - case Modulo e -> { - var location = e.location(); - var dividend = optimize(e.dividend()); - var divisor = optimize(e.divisor()); - if (dividend instanceof NumberLiteral(var location1, var dividendValue) - && divisor instanceof NumberLiteral(var location2, var divisorValue)) - yield literal(location, dividendValue % divisorValue); - yield new Modulo(location, dividend, divisor); - } - case Power e -> { - var location = e.location(); - var base = optimize(e.base()); - var exponent = optimize(e.exponent()); - if (base instanceof NumberLiteral(var location1, var baseValue) - && exponent instanceof NumberLiteral(var location2, var exponentValue)) - yield literal(location, Math.pow(baseValue, exponentValue)); - if (exponent instanceof NumberLiteral(var location1, var exp) - && base instanceof Multiply(var location2, var multiplier, var multiplicand)) { - if (multiplier instanceof NumberLiteral(var location3, var value)) - yield new Multiply(location2, - literal(location, Math.pow(value, exp)), - new Power(location, multiplicand, literal(exp))); - if (multiplicand instanceof NumberLiteral(var location3, var value)) - yield new Multiply(location2, - literal(location, Math.pow(value, exp)), - new Power(location, multiplier, literal(exp))); - } - yield new Power(location, base, exponent); - } + case NumberConditional(var location, var condition, var ifTrue, var ifFalse) -> new NumberConditional(location, optimize(condition), optimize(ifTrue), optimize(ifFalse)); + case Add(var location, var augend, var addend) -> new Add(location, optimize(augend), optimize(addend)); + case Subtract(var location, var minuend, var subtrahend) -> new Subtract(location, optimize(minuend), optimize(subtrahend)); + case Negate(var location, var inner) -> new Negate(location, optimize(inner)); + case Multiply(var location, var multiplier, var multiplicand) -> new Multiply(location, optimize(multiplier), optimize(multiplicand)); + case Divide(var location, var dividend, var divisor) -> new Divide(location, optimize(dividend), optimize(divisor)); + case Modulo(var location, var dividend, var divisor) -> new Modulo(location, optimize(dividend), optimize(divisor)); + case Power(var location, var base, var exponent) -> new Power(location, optimize(base), optimize(exponent)); }; } public static BoolExpr optimize(BoolExpr expr) { - return switch (expr) { + return switch (optimizeInner(expr)) { case null -> throw new NullPointerException(); + case BoolConditional(var location, BoolLiteral(var location1, var value), var ifTrue, var ifFalse) -> value ? ifTrue : ifFalse; + case BoolConditional(var location, Not(var location1, var condition), var ifTrue, var ifFalse) -> new BoolConditional(location, condition, ifFalse, ifTrue); + case And(var location, BoolLiteral(var location1, var left), var right) -> left ? right : literal(location, false); + case And(var location, var left, BoolLiteral(var location1, var right)) -> right ? left : literal(location, false); + case Or(var location, BoolLiteral(var location1, var left), var right) -> left ? literal(location, true) : right; + case Or(var location, var left, BoolLiteral(var location1, var right)) -> right ? literal(location, true) : left; + case Not(var location, BoolLiteral(var location1, var value)) -> literal(location, !value); + case Not(var location, Not(var location1, var inner)) -> inner; + case GreaterThan(var location, NumberLiteral(var location1, var left), NumberLiteral(var location2, var right)) -> literal(location, left > right); + case GreaterThan(var location, Divide(var location1, var dividend, var divisor), var right) -> optimize(new GreaterThan(location, dividend, new Multiply(location1, right, divisor))); + case GreaterThan(var location, Negate(var location1, var inner), var right) -> optimize(new GreaterThan(location, new Negate(right.location(), right), inner)); + case GreaterThan(var location, Subtract(var location1, var minuend, var subtrahend), var right) -> optimize(new GreaterThan(location, minuend, new Add(location1, subtrahend, right))); + // Modulo is left out because it is too complicated for this naive impl + // Multiply is left out since it would transform into a division and may be 0 + case GreaterThan(var location, Add(var location1, var augend, var addend), var right) -> optimize(new GreaterThan(location, augend, new Subtract(location1, right, addend))); + // Power is left out because it can't be transformed cleanly either + case BoolExpr fallback -> fallback; // Used instead of default to still keep applied optimizations + }; + } + + private static BoolExpr optimizeInner(BoolExpr expr) { + return switch (expr) { + case null -> null; case BoolLiteral e -> e; case ExtensibleBoolExpr e -> e.optimize(); case BoolUnpack(var inner) -> asBool(unpack(optimize(inner))); case BoolAssign(var location, var variable, var value) -> new BoolAssign(location, variable, optimize(value)); - case BoolConditional e -> { - var condition = optimize(e.condition()); - var ifTrue = optimize(e.ifTrue()); - var ifFalse = optimize(e.ifFalse()); - if (condition instanceof BoolLiteral(var location, var value)) - yield value ? ifTrue : ifFalse; - if (ifTrue.equals(ifFalse)) yield ifTrue; - if (condition instanceof Not(var location, var inner)) - yield new BoolConditional(e.location(), inner, ifFalse, ifTrue); - yield new BoolConditional(e.location(), condition, ifTrue, ifFalse); - } - case And e -> { - var location = e.location(); - var left = optimize(e.left()); - var right = optimize(e.right()); - if (left instanceof BoolLiteral(var location1, var leftValue)) - yield leftValue ? right : literal(location, false); - if (right instanceof BoolLiteral(var location1, var rightValue)) - yield rightValue ? left : literal(location, false); - yield new And(location, left, right); - } - case Or e -> { - var location = e.location(); - var left = optimize(e.left()); - var right = optimize(e.right()); - if (left instanceof BoolLiteral(var location1, var leftValue)) - yield leftValue ? literal(location, true) : right; - if (right instanceof BoolLiteral(var location1, var rightValue)) - yield rightValue ? literal(location, true) : left; - yield new Or(location, left, right); - } - case Not e -> { - var location = e.location(); - var inner = optimize(e.inner()); - if (inner instanceof BoolLiteral(var location1, var value)) - yield literal(location, !value); - if (inner instanceof Not(var location1, var innerInner)) - yield innerInner; - yield new Not(location, inner); - } - case Equals e -> { - var location = e.location(); - var left = optimize(e.left()); - var right = optimize(e.right()); - if (left.equals(right)) - yield literal(location, true); - yield new Equals(location, left, right); - } - case GreaterThan e -> { - var location = e.location(); - var left = optimize(e.left()); - var right = optimize(e.right()); - if (left instanceof NumberLiteral(var location1, var leftValue) - && right instanceof NumberLiteral(var location2, var rightValue)) - yield literal(location, leftValue > rightValue); - if (left instanceof Divide(var location1, var dividend, var divisor)) - yield optimize(new GreaterThan(location, dividend, new Multiply(location1, right, divisor))); - if (left instanceof Negate(var location1, var inner)) - yield optimize(new GreaterThan(location, new Negate(right.location(), right), inner)); - if (left instanceof Subtract(var location1, var minuend, var subtrahend)) - yield optimize(new GreaterThan(location, minuend, new Add(location1, subtrahend, right))); - // Modulo is left out because it is too complicated for this naive impl - // Multiply is left out since it would transform into a division and may be 0 - if (left instanceof Add(var location1, var augend, var addend)) - yield optimize(new GreaterThan(location, augend, new Subtract(location1, addend, right))); - // Power is left out because it can't be transformed cleanly either - yield new GreaterThan(location, left, right); - } + case BoolConditional(var location, var condition, var ifTrue, var ifFalse) -> new BoolConditional(location, optimize(condition), optimize(ifTrue), optimize(ifFalse)); + case And(var location, var left, var right) -> new And(location, optimize(left), optimize(right)); + case Or(var location, var left, var right) -> new Or(location, optimize(left), optimize(right)); + case Not(var location, var inner) -> new Not(location, optimize(inner)); + case Equals(var location, var left, var right) -> new Equals(location, optimize(left), optimize(right)); + case GreaterThan(var location, var left, var right) -> new GreaterThan(location, optimize(left), optimize(right)); }; } public static DynamicExpr optimize(DynamicExpr expr) { - return switch (expr) { + return switch (optimizeInner(expr)) { case null -> throw new NullPointerException(); - case DynamicLiteral e -> e; - case ExtensibleDynamicExpr e -> e.optimize(); - case DynamicCoerce e -> asDynamic(unpack(optimize(e.inner()))); - case DynamicAssign(var location, var variable, var value) -> new DynamicAssign(location, variable, optimize(value)); - case DynamicConditional e -> { - var condition = optimize(e.condition()); - var ifTrue = optimize(e.ifTrue()); - var ifFalse = optimize(e.ifFalse()); - if (condition instanceof BoolLiteral(var location, var value)) - yield value ? ifTrue : ifFalse; - if (ifTrue.equals(ifFalse)) yield ifTrue; - if (condition instanceof Not(var location, var inner)) - yield new DynamicConditional(e.location(), inner, ifFalse, ifTrue); - yield new DynamicConditional(e.location(), condition, ifTrue, ifFalse); - } - case This e -> e; - case Variable e -> e; - case Get(var location, var left, var name) -> new Get(location, optimize(left), optimize(name)); - case At(var location, var left, var index) -> new At(location, optimize(left), optimize(index)); - case GetOrAt(var location, var leftX, var nameOrIndexX) -> { - var left = optimize(leftX); - var nameOrIndex = optimize(nameOrIndexX); - if (nameOrIndex instanceof StringExpr name) yield new Get(location, left, name); - if (nameOrIndex instanceof NumberExpr index) yield new At(location, left, index); - if (left instanceof ObjectLiteral l) yield new Get(location, optimize(l), asString(nameOrIndex)); - if (left instanceof ListLiteral l) yield new At(location, optimize(l), asNumber(nameOrIndex)); - yield new GetOrAt(location, optimize(left), optimize(nameOrIndex)); - } - case Bind(var location, var callable, var parameter) -> new Bind(location, optimize(callable), optimize(parameter)); - case Call e -> { - var location = e.location(); - var callable = optimize(e.callable()); - var args = new ArrayList(); - if (callable instanceof Bind(var location1, var callable1, var parameter1)) { - callable = callable1; - args.add(new Call.Argument(parameter1, false)); - } - for (Call.Argument arg : e.arguments()) - args.add(new Call.Argument(optimize(arg.value()), arg.variadic())); - if (callable instanceof Closure(var location1, var boundArgs, var variadic, var steps, var finish)) { - yield new ExprGroup( - location1, - Stream.concat(steps.stream(), Stream.of(finish)).toList(), - new ExprGroup.PackedArgs(args, boundArgs, variadic), - true - ); - } - yield new Call(location, callable, args); - } - case Closure(var location, var boundArgs, var variadic, var steps, var finish) -> new Closure( - location, - boundArgs, - variadic, - steps.stream() - .map(Optimizer::optimize) - .flatMap(Optimizer::extractSideEffects) - .map(Optimizer::optimize) - .toList(), - optimize(finish)); - case ExprGroup(var location, var steps, var finish, var packedArgs, var fork) -> { - List exprs = Stream.concat( - steps.stream() - .map(Optimizer::optimize) - .flatMap(Optimizer::extractSideEffects) - .map(Optimizer::optimize), - Stream.of(optimize(finish)) - ).toList(); - yield packedArgs == null - ? asDynamic(ExprGroup.of(location, exprs, fork)) - : new ExprGroup(location, exprs, packedArgs, fork); - } - case ListLiteral(var location, var elements) -> new ListLiteral(location, elements.stream().map(Optimizer::optimize).toList()); - case ObjectLiteral e -> { - var location = e.location(); + case DynamicConditional(var location, BoolLiteral(var location1, var value), var ifTrue, var ifFalse) -> value ? ifTrue : ifFalse; + case DynamicConditional(var location, Not(var location1, var inner), var ifTrue, var ifFalse) -> optimize(new DynamicConditional(location, inner, ifFalse, ifTrue)); + case DynamicConditional(var location, var condition, var ifTrue, var ifFalse) -> ifTrue.equals(ifFalse) + ? new ExprGroup(location, extractSideEffects(condition).map(Optimizer::optimize).toList(), ifTrue, null, false) + : new DynamicConditional(location, condition, ifTrue, ifFalse); + case GetOrAt(var location, ObjectLiteral l, var nameOrIndex) -> new Get(location, l, asString(nameOrIndex)); + case GetOrAt(var location, ListLiteral l, var nameOrIndex) -> new At(location, l, asNumber(nameOrIndex)); + case GetOrAt(var location, var left, StringExpr name) -> new Get(location, left, name); + case GetOrAt(var location, var left, NumberExpr index) -> new At(location, left, index); + case Call(var location, Bind(var location1, var callable, var parameter), var arguments) -> optimize(new Call(location, callable, concat(arguments, new Call.Argument(parameter, false)))); + case Call(var location, Closure(var location1, var boundArgs, var variadic, var steps, var finish), var arguments) -> new ExprGroup(location1, concat(steps, finish), new ExprGroup.PackedArgs(arguments, boundArgs, variadic), true); + case ObjectLiteral(var location, var originalContent) -> { var content = new LinkedHashMap(); var literalContent = new LinkedHashMap(); boolean literal = true; - for (Map.Entry entry : e.content().entrySet()) { + for (Map.Entry entry : originalContent.entrySet()) { DynamicExpr de = optimize(entry.getValue()); if (de instanceof DynamicLiteral(var location1, var cnt) && literal) { if (cnt instanceof Dynamic d) literalContent.put(entry.getKey(), d); @@ -371,9 +181,45 @@ public class Optimizer { if (literal) yield new DynamicLiteral(location, DFinal.of(literalContent)); yield new ObjectLiteral(location, content); } + case DynamicExpr fallback -> fallback; // Used instead of default to still keep applied optimizations }; } + private static DynamicExpr optimizeInner(DynamicExpr expr) { + return switch (expr) { + case null -> null; + case DynamicLiteral e -> e; + case ExtensibleDynamicExpr e -> e.optimize(); + case DynamicCoerce(var inner) -> asDynamic(unpack(optimize(inner))); + case DynamicAssign(var location, var variable, var value) -> new DynamicAssign(location, variable, optimize(value)); + case DynamicConditional(var location, var condition, var ifTrue, var ifFalse) -> new DynamicConditional(location, optimize(condition), optimize(ifTrue), optimize(ifFalse)); + case This e -> e; + case Variable e -> e; + case Get(var location, var left, var name) -> new Get(location, optimize(left), optimize(name)); + case At(var location, var left, var index) -> new At(location, optimize(left), optimize(index)); + case GetOrAt(var location, var left, var nameOrIndex) -> new GetOrAt(location, optimize(left), optimize(nameOrIndex)); + case Bind(var location, var callable, var parameter) -> new Bind(location, optimize(callable), optimize(parameter)); + case Call(var location, var callable, var arguments) -> new Call(location, optimize(callable), optimize(arguments)); + case Closure(var location, var boundArgs, var variadic, var steps, var finish) -> new Closure(location, boundArgs, variadic, optimize(steps, finish), optimize(finish)); + case ExprGroup(var location, var steps, var finish, ExprGroup.PackedArgs(var pFrom, var pTo, var variadic), var fork) -> new ExprGroup(location, optimize(steps, finish), new ExprGroup.PackedArgs(optimize(pFrom), pTo, variadic), fork); + case ExprGroup(var location, var steps, var finish, var packedArgs, var fork) -> asDynamic(ExprGroup.of(location, optimize(steps, finish), fork)); + case ListLiteral(var location, var elements) -> new ListLiteral(location, elements.stream().map(Optimizer::optimize).toList()); + case ObjectLiteral e -> e; // Exclusively handled in optimize even though it contains expressions, since it cannot be decomposed + }; + } + + private static List concat(List list, T element) { + return Stream.concat(list.stream(), Stream.of(element)).toList(); + } + + private static List optimize(List steps, DynamicExpr finish) { + return Stream.concat(steps.stream().map(Optimizer::optimize).flatMap(Optimizer::extractSideEffects).map(Optimizer::optimize), Stream.of(optimize(finish))).toList(); + } + + private static List optimize(List arguments) { + return arguments.stream().map(arg -> new Call.Argument(optimize(arg.value()), arg.variadic())).toList(); + } + public static Stream extractSideEffects(Expr expr) { return switch (expr) { case NullLiteral e -> Stream.empty();