feat(muscript): initial implementation of muscript-optimizer
ci/woodpecker/push/woodpecker Pipeline was successful Details

This commit is contained in:
Johannes Frohnmeyer 2024-04-05 17:58:26 +02:00
parent f40ed3e4e7
commit b15058a8e8
Signed by: Johannes
GPG Key ID: E76429612C2929F4
4 changed files with 410 additions and 0 deletions

View File

@ -0,0 +1,34 @@
import io.gitlab.jfronny.scripts.*
plugins {
commons.library
}
dependencies {
implementation(projects.commons)
api(projects.muscriptCore)
api(projects.muscriptAst)
api(projects.muscriptDataAdditional)
testImplementation(libs.junit.jupiter.api)
testRuntimeOnly(libs.junit.jupiter.engine)
}
publishing {
publications {
create<MavenPublication>("maven") {
groupId = "io.gitlab.jfronny"
artifactId = "muscript-optimizer"
from(components["java"])
}
}
}
tasks.javadoc {
linksOffline("https://maven.frohnmeyer-wds.de/javadoc/artifacts/io/gitlab/jfronny/commons/$version/raw", projects.commons)
linksOffline("https://maven.frohnmeyer-wds.de/javadoc/artifacts/io/gitlab/jfronny/muscript-core/$version/raw", projects.muscriptCore)
linksOffline("https://maven.frohnmeyer-wds.de/javadoc/artifacts/io/gitlab/jfronny/muscript-ast/$version/raw", projects.muscriptAst)
linksOffline("https://maven.frohnmeyer-wds.de/javadoc/artifacts/io/gitlab/jfronny/muscript-data-dynamic/$version/raw", projects.muscriptDataDynamic)
linksOffline("https://maven.frohnmeyer-wds.de/javadoc/artifacts/io/gitlab/jfronny/muscript-data-additional/$version/raw", projects.muscriptDataAdditional)
}

View File

@ -0,0 +1,365 @@
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.dynamic.*;
import io.gitlab.jfronny.muscript.ast.extensible.*;
import io.gitlab.jfronny.muscript.ast.number.*;
import io.gitlab.jfronny.muscript.ast.string.*;
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;
import java.util.stream.Stream;
import static io.gitlab.jfronny.muscript.ast.Expr.literal;
import static io.gitlab.jfronny.muscript.ast.context.ExprUtils.*;
public class Optimizer {
public static Expr optimize(Expr expr) {
return switch (unpack(expr)) {
case null -> throw new NullPointerException();
case ExtensibleExpr e -> e.optimize();
case StringExpr stringExpr -> optimize(stringExpr);
case NumberExpr numberExpr -> optimize(numberExpr);
case BoolExpr boolExpr -> optimize(boolExpr);
case DynamicExpr dynamicExpr -> optimize(dynamicExpr);
case NullLiteral nl -> nl;
};
}
public static StringExpr optimize(StringExpr expr) {
return switch (expr) {
case null -> throw new NullPointerException();
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 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);
}
};
}
public static NumberExpr optimize(NumberExpr expr) {
return switch (expr) {
case null -> throw new NullPointerException();
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 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 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 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 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 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);
}
};
}
public static BoolExpr optimize(BoolExpr expr) {
return switch (expr) {
case null -> throw new NullPointerException();
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 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 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 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);
}
};
}
public static DynamicExpr optimize(DynamicExpr expr) {
return switch (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 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<Call.Argument>();
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<Expr> 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();
var content = new LinkedHashMap<String, DynamicExpr>();
var literalContent = new LinkedHashMap<String, Dynamic>();
boolean literal = true;
for (Map.Entry<String, DynamicExpr> entry : e.content().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);
else throw new IllegalArgumentException("Unsupported implementation of Dynamic");
} else literal = false;
content.put(entry.getKey(), de);
}
if (literal) yield new DynamicLiteral(location, DFinal.of(literalContent));
yield new ObjectLiteral(location, content);
}
};
}
public static Stream<Expr> extractSideEffects(Expr expr) {
//TODO actually implement
return Stream.of(expr);
}
}

View File

@ -0,0 +1,10 @@
module io.gitlab.jfronny.commons.muscript.optimizer {
requires io.gitlab.jfronny.commons;
requires static org.jetbrains.annotations;
requires io.gitlab.jfronny.commons.muscript.ast;
requires io.gitlab.jfronny.commons.muscript.core;
requires io.gitlab.jfronny.commons.muscript.data.dynamic;
requires io.gitlab.jfronny.commons.muscript.data;
requires io.gitlab.jfronny.commons.muscript.data.additional;
exports io.gitlab.jfronny.muscript.optimizer;
}

View File

@ -17,6 +17,7 @@ include("muscript-data-dynamic")
include("muscript-data-additional")
include("muscript-parser")
include("muscript-serialize")
include("muscript-optimizer")
// legacy muscript
include("muscript")
include("muscript-gson")