feat(muscript): implement serialization
ci/woodpecker/push/woodpecker Pipeline was successful
Details
ci/woodpecker/push/woodpecker Pipeline was successful
Details
This commit is contained in:
parent
933a642dc3
commit
639ddfb8ea
|
@ -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)
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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)));
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
io.gitlab.jfronny.muscript.serialize.impl.ExprSerializerImpl
|
|
@ -0,0 +1 @@
|
|||
io.gitlab.jfronny.muscript.serialize.impl.DynamicSerializerImpl
|
|
@ -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")
|
||||
|
|
Loading…
Reference in New Issue