feat(muscript): implement serialization
ci/woodpecker/push/woodpecker Pipeline was successful Details

This commit is contained in:
Johannes Frohnmeyer 2024-04-05 14:21:17 +02:00
parent 933a642dc3
commit 639ddfb8ea
Signed by: Johannes
GPG Key ID: E76429612C2929F4
8 changed files with 381 additions and 0 deletions

View File

@ -0,0 +1,31 @@
import io.gitlab.jfronny.scripts.*
plugins {
commons.library
}
dependencies {
implementation(projects.commons)
api(projects.muscriptAst)
api(projects.muscriptDataDynamic)
api(projects.muscriptDataAdditional)
testImplementation(libs.junit.jupiter.api)
testRuntimeOnly(libs.junit.jupiter.engine)
}
publishing {
publications {
create<MavenPublication>("maven") {
groupId = "io.gitlab.jfronny"
artifactId = "muscript-serialize"
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-ast/$version/raw", projects.muscriptAst)
}

View File

@ -0,0 +1,306 @@
package io.gitlab.jfronny.muscript.serialize;
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;
import io.gitlab.jfronny.muscript.ast.dynamic.*;
import io.gitlab.jfronny.muscript.ast.extensible.ExtensibleExpr;
import io.gitlab.jfronny.muscript.ast.number.*;
import io.gitlab.jfronny.muscript.ast.string.*;
import io.gitlab.jfronny.muscript.core.ExprWriter;
import io.gitlab.jfronny.muscript.core.MuUtil;
import io.gitlab.jfronny.muscript.data.dynamic.Dynamic;
import io.gitlab.jfronny.muscript.data.dynamic.context.DynamicSerializer;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static io.gitlab.jfronny.muscript.ast.context.ExprUtils.asString;
public abstract class Decompiler {
public static void decompile(ExprWriter writer, Script script) throws IOException {
for (Expr expr : script.stream().toList()) {
decompile(writer, expr);
writer.append(";\n");
}
}
public static void decompile(ExprWriter writer, Expr expr) throws IOException {
switch (expr) {
case DynamicCoerce(var inner) -> decompile(writer, inner);
case StringCoerce(var inner) -> decompile(writer, inner);
case BoolUnpack(var inner) -> decompile(writer, inner);
case NumberUnpack(var inner) -> decompile(writer, inner);
case StringUnpack(var inner) -> decompile(writer, inner);
case NullLiteral nl -> writer.append("null");
case BoolLiteral(var location, var value) -> writer.append(value ? "true" : "false");
case NumberLiteral(var location, var value) -> writer.append(StringFormatter.toString(value));
case StringLiteral(var location, var value) -> writer.append(enquote(value));
case DynamicLiteral(var location, Dynamic value) -> DynamicSerializer.INSTANCE.serialize(writer, value);
case DynamicLiteral dl -> throw new IllegalArgumentException("Unexpected implementation of IDynamic");
case This t -> writer.append("this");
case Variable(var location, var name) -> writer.appendLiteral(name);
case ExtensibleExpr ee -> ee.decompile(writer);
case DynamicAssign(var location, var variable, var value) -> {
writer.appendLiteral(variable).append(" = ");
decompile(writer, value);
}
case BoolAssign(var location, var variable, var value) -> {
writer.appendLiteral(variable).append(" = ");
decompile(writer, value);
}
case NumberAssign(var location, var variable, var value) -> {
writer.appendLiteral(variable).append(" = ");
decompile(writer, value);
}
case StringAssign(var location, var variable, var value) -> {
writer.appendLiteral(variable).append(" = ");
decompile(writer, value);
}
case And(var location, var left, var right) -> {
parenthesize(expr, left, writer, false);
writer.append(" && ");
parenthesize(expr, right, writer, true);
}
case Not(var location, var inner) -> {
if (inner instanceof Equals(var location1, var left, var right)) {
parenthesize(expr, left, writer, false);
writer.append(" != ");
parenthesize(expr, right, writer, true);
} else if (inner instanceof GreaterThan(var location1, var left, var right)) {
if (left instanceof NumberLiteral && !(right instanceof NumberLiteral)) {
parenthesize(expr, right, writer, false);
writer.append(" >= ");
parenthesize(expr, left, writer, true);
} else {
parenthesize(expr, left, writer, false);
writer.append(" <= ");
parenthesize(expr, right, writer, true);
}
} else {
writer.append("!");
parenthesize(expr, inner, writer, false);
}
}
case Or(var location, var left, var right) -> {
parenthesize(expr, left, writer, false);
writer.append(" | ");
parenthesize(expr, right, writer, true);
}
case Equals(var location, var left, var right) -> {
parenthesize(expr, left, writer, false);
writer.append(" == ");
parenthesize(expr, right, writer, true);
}
case GreaterThan(var location, var left, var right) -> {
if (left instanceof NumberLiteral && !(right instanceof NumberLiteral)) {
parenthesize(expr, right, writer, false);
writer.append(" < ");
parenthesize(expr, left, writer, true);
} else {
parenthesize(expr, left, writer, false);
writer.append(" > ");
parenthesize(expr, right, writer, true);
}
}
case BoolConditional(var location, var condition, var ifTrue, var ifFalse) -> {
parenthesize(expr, condition, writer, true);
writer.append(" ? ");
decompile(writer, ifTrue);
writer.append(" : ");
decompile(writer, ifFalse);
}
case DynamicConditional(var location, var condition, var ifTrue, var ifFalse) -> {
parenthesize(expr, condition, writer, true);
writer.append(" ? ");
decompile(writer, ifTrue);
writer.append(" : ");
decompile(writer, ifFalse);
}
case NumberConditional(var location, var condition, var ifTrue, var ifFalse) -> {
parenthesize(expr, condition, writer, true);
writer.append(" ? ");
decompile(writer, ifTrue);
writer.append(" : ");
decompile(writer, ifFalse);
}
case StringConditional(var location, var condition, var ifTrue, var ifFalse) -> {
parenthesize(expr, condition, writer, true);
writer.append(" ? ");
decompile(writer, ifTrue);
writer.append(" : ");
decompile(writer, ifFalse);
}
case Bind(var location, var callable, var parameter) -> {
parenthesize(expr, parameter, writer, false);
writer.append("::");
if (callable instanceof Variable) {
decompile(writer, callable);
} else {
writer.append('(');
decompile(writer, callable);
writer.append(')');
}
}
case Call(var location, var callable, var arguments) -> {
parenthesize(expr, callable, writer, false);
if (arguments.size() > 3) {
writer.increaseIndent();
writer.append("(\n");
boolean first = true;
for (Call.Argument arg : arguments) {
if (!first) writer.append(",\n");
first = false;
decompile(writer, arg.value());
if (arg.variadic()) writer.append("...");
}
writer.decreaseIndent();
writer.append("\n)");
} else {
writer.append('(');
boolean first = true;
for (Call.Argument arg : arguments) {
if (!first) writer.append(", ");
first = false;
decompile(writer, arg.value());
if (arg.variadic()) writer.append("...");
}
writer.append(')');
}
}
case Closure(var location, var boundArgs, var variadic, var steps, var finish) -> {
writer.append("{");
boolean first = true;
for (int i = 0; i < boundArgs.size(); i++) {
if (!first) writer.append(",");
first = false;
writer.append(' ').appendLiteral(boundArgs.get(i));
if (i == boundArgs.size() - 1 && variadic) writer.append("...");
}
writer.append(" ->");
if (steps.isEmpty()) {
writer.append(" ");
decompile(writer, finish);
writer.append(" }");
} else {
writer.increaseIndent();
for (Expr step : steps) {
writer.append("\n");
decompile(writer, step);
writer.append(";");
}
writer.append("\n");
decompile(writer, finish);
writer.append(";").decreaseIndent().append("\n}");
}
}
case ExprGroup(var location, var steps, var finish, var packedArgs, var fork) -> decompile(writer, fork
? new Call(
location,
new Closure(
location,
packedArgs == null
? List.of()
: packedArgs.to(),
false,
Stream.concat(steps.stream(), Stream.of(finish)).toList()
),
packedArgs == null ? List.of() : packedArgs.from())
// Use string concatenation since the result is ignored either way
: Stream.concat(steps.stream(), Stream.of(finish))
.reduce((left, right) -> new Concatenate(location, asString(left), asString(right)))
.orElseGet(() -> new NullLiteral(location)));
case Get(var location, var left, var name) -> {
parenthesize(expr, left, writer, false);
if (name instanceof StringLiteral(var location1, var value) && MuUtil.isValidId(value)) {
writer.append('.').append(value);
} else {
writer.append('[');
decompile(writer, name);
writer.append(']');
}
}
case ListLiteral(var location, var elements) -> decompile(writer, new Call(
location,
new Variable(location, "listOf"),
elements.stream().map(s -> new Call.Argument(s, false)).toList()
));
case ObjectLiteral(var location, var content) -> {
writer.increaseIndent().append("{\n");
boolean first = true;
for (Map.Entry<String, DynamicExpr> entry : content.entrySet()) {
if (!first) writer.append(",\n");
first = false;
writer.appendLiteral(entry.getKey()).append(" = ");
decompile(writer, entry.getValue());
}
writer.decreaseIndent().append("\n}");
}
case Negate(var location, var inner) -> {
writer.append('-');
parenthesize(expr, inner, writer, false);
}
case Add(var location, var augend, var addend) -> {
parenthesize(expr, augend, writer, false);
writer.append(" + ");
parenthesize(expr, addend, writer, true);
}
case Subtract(var location, var minuend, var subtrahend) -> {
parenthesize(expr, minuend, writer, false);
writer.append(" - ");
parenthesize(expr, subtrahend, writer, true);
}
case Multiply(var location, var multiplier, var multiplicand) -> {
parenthesize(expr, multiplier, writer, false);
writer.append(" * ");
parenthesize(expr, multiplicand, writer, true);
}
case Divide(var location, var dividend, var divisor) -> {
parenthesize(expr, dividend, writer, false);
writer.append(" / ");
parenthesize(expr, divisor, writer, true);
}
case Modulo(var location, var dividend, var divisor) -> {
parenthesize(expr, dividend, writer, false);
writer.append(" % ");
parenthesize(expr, divisor, writer, true);
}
case Power(var location, var base, var exponent) -> {
parenthesize(expr, base, writer, false);
writer.append(" ^ ");
parenthesize(expr, exponent, writer, true);
}
case Concatenate(var location, var left, var right) -> {
parenthesize(expr, left, writer, false);
writer.append(" || ");
parenthesize(expr, right, writer, true);
}
}
}
private static void parenthesize(Expr context, Expr toWrite, ExprWriter writer, boolean parenEqualOrder) throws IOException {
boolean wrap = !parenEqualOrder ? toWrite.order().ordinal() < context.order().ordinal() : toWrite.order().ordinal() <= context.order().ordinal();
if (wrap) writer.append('(');
decompile(writer, toWrite);
if (wrap) writer.append(')');
}
/**
* Creates quotes around a string, supports strings containing quotes
*/
public static String enquote(String literalText) {
if (!literalText.contains("'")) return "'" + literalText + "'";
if (!literalText.contains("\"")) return "\"" + literalText + "\"";
return Arrays.stream(literalText.split("'")).map(s -> "'" + s + "'")
.collect(Collectors.joining(" || \"'\" || "));
}
public static String toString(Expr expr) {
return ExprWriter.write(writer -> decompile(writer, expr), false);
}
}

View File

@ -0,0 +1,17 @@
package io.gitlab.jfronny.muscript.serialize.impl;
import io.gitlab.jfronny.muscript.core.ExprWriter;
import io.gitlab.jfronny.muscript.data.additional.DataExprMapper;
import io.gitlab.jfronny.muscript.data.dynamic.Dynamic;
import io.gitlab.jfronny.muscript.data.dynamic.context.DynamicSerializer;
import io.gitlab.jfronny.muscript.serialize.Decompiler;
import java.io.IOException;
import java.util.Objects;
public class DynamicSerializerImpl implements DynamicSerializer {
@Override
public void serialize(ExprWriter writer, Dynamic value) throws IOException {
Decompiler.decompile(writer, Objects.requireNonNull(DataExprMapper.map(value)));
}
}

View File

@ -0,0 +1,12 @@
package io.gitlab.jfronny.muscript.serialize.impl;
import io.gitlab.jfronny.muscript.ast.Expr;
import io.gitlab.jfronny.muscript.ast.context.IExprSerializer;
import io.gitlab.jfronny.muscript.serialize.Decompiler;
public class ExprSerializerImpl implements IExprSerializer {
@Override
public String serialize(Expr expr) {
return Decompiler.toString(expr);
}
}

View File

@ -0,0 +1,8 @@
module io.gitlab.jfronny.commons.muscript.serialize {
requires io.gitlab.jfronny.commons.muscript.ast;
requires io.gitlab.jfronny.commons.muscript.data;
requires io.gitlab.jfronny.commons.muscript.core;
requires io.gitlab.jfronny.commons;
requires io.gitlab.jfronny.commons.muscript.data.additional;
exports io.gitlab.jfronny.muscript.serialize;
}

View File

@ -0,0 +1 @@
io.gitlab.jfronny.muscript.serialize.impl.ExprSerializerImpl

View File

@ -0,0 +1 @@
io.gitlab.jfronny.muscript.serialize.impl.DynamicSerializerImpl

View File

@ -11,7 +11,12 @@ include("commons-http-server")
include("commons-manifold")
include("commons-unsafe")
// new muscript
include("muscript-core")
include("muscript-ast")
include("muscript-data-dynamic")
include("muscript-data-additional")
include("muscript-parser")
include("muscript-serialize")
// legacy muscript
include("muscript")
include("muscript-gson")