chore(muscript): delete old implementation keeping redirects in place for docs
ci/woodpecker/push/woodpecker Pipeline was successful Details

This commit is contained in:
Johannes Frohnmeyer 2024-04-07 17:25:47 +02:00
parent 4b44f9516d
commit 108a370c51
Signed by: Johannes
GPG Key ID: E76429612C2929F4
135 changed files with 0 additions and 7540 deletions

View File

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

View File

@ -1,82 +0,0 @@
package io.gitlab.jfronny.muscript;
import io.gitlab.jfronny.muscript.compiler.Decompilable;
public class StarScriptIngester {
private static final System.Logger LOGGER = System.getLogger("MuScript");
/**
* Naive conversion of starscript syntax to muscript
*/
public static String starScriptToMu(String source) {
StringBuilder result = new StringBuilder();
StringBuilder currbuf = new StringBuilder();
State state = State.Surrounding;
for (char c : source.toCharArray()) {
switch (state) {
case Surrounding -> {
if (c == '{') {
if (!currbuf.isEmpty()) {
if (!result.isEmpty()) result.append(" || ");
result.append(Decompilable.enquote(currbuf.toString()));
}
currbuf = new StringBuilder();
state = State.UnquotedInner;
} else currbuf.append(c);
}
case UnquotedInner -> {
switch (c) {
case '}' -> {
if (!currbuf.isEmpty()) {
if (!result.isEmpty()) result.append(" || ");
result.append("(").append(currbuf).append(")");
}
currbuf = new StringBuilder();
state = State.Surrounding;
}
case '\'' -> {
currbuf.append(c);
state = State.SingleQuoteInner;
}
case '"' -> {
currbuf.append(c);
state = State.DoubleQuoteInner;
}
default -> currbuf.append(c);
}
}
case SingleQuoteInner -> {
currbuf.append(c);
if (c == '\'') state = State.UnquotedInner;
}
case DoubleQuoteInner -> {
currbuf.append(c);
if (c == '"') state = State.UnquotedInner;
}
}
}
if (!currbuf.isEmpty() && !result.isEmpty()) result.append(" || ");
switch (state) {
case Surrounding -> {
if (!currbuf.isEmpty()) result.append(Decompilable.enquote(currbuf.toString()));
}
case UnquotedInner -> {
LOGGER.log(System.Logger.Level.WARNING, "Starscript code segment improperly closed, closing automatically");
if (!currbuf.isEmpty()) result.append("(").append(currbuf).append(")");
}
case SingleQuoteInner -> {
LOGGER.log(System.Logger.Level.WARNING, "Quote in starscript swallows ending, completing with closing quote");
if (!currbuf.isEmpty()) result.append("(").append(currbuf).append("')");
}
case DoubleQuoteInner -> {
LOGGER.log(System.Logger.Level.WARNING, "Quote in starscript swallows ending, completing with closing quote");
if (!currbuf.isEmpty()) result.append("(").append(currbuf).append("\")");
}
}
return result.toString();
}
enum State {
Surrounding, UnquotedInner, SingleQuoteInner, DoubleQuoteInner
}
}

View File

@ -1,12 +0,0 @@
package io.gitlab.jfronny.muscript.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
/**
* Annotation for expressions that may throw exceptions
* Any expression that doesn't have this annotation will only throw if an expression referenced from it throws
*/
@Target(ElementType.TYPE)
public @interface CanThrow {
}

View File

@ -1,12 +0,0 @@
package io.gitlab.jfronny.muscript.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
/**
* Annotation for expressions that utilize Dynamic.as*
* These expressions must also be annotated with @CanThrow
*/
@Target(ElementType.TYPE)
public @interface UncheckedDynamic {
}

View File

@ -1,23 +0,0 @@
package io.gitlab.jfronny.muscript.ast;
import io.gitlab.jfronny.muscript.ast.dynamic.DynamicCoerce;
import io.gitlab.jfronny.muscript.compiler.*;
public abstract non-sealed class BoolExpr extends Expr<Boolean> {
protected BoolExpr(Order order, CodeLocation location) {
super(order, location);
}
@Override
public Type getResultType() {
return Type.Boolean;
}
@Override
public abstract BoolExpr optimize();
@Override
public DynamicExpr asDynamicExpr() {
return new DynamicCoerce(location, this);
}
}

View File

@ -1,39 +0,0 @@
package io.gitlab.jfronny.muscript.ast;
import io.gitlab.jfronny.muscript.compiler.*;
import io.gitlab.jfronny.muscript.data.dynamic.Dynamic;
import io.gitlab.jfronny.muscript.ast.dynamic.unpack.*;
public abstract non-sealed class DynamicExpr extends Expr<Dynamic> {
protected DynamicExpr(Order order, CodeLocation location) {
super(order, location);
}
@Override
public Type getResultType() {
return Type.Dynamic;
}
@Override
public abstract DynamicExpr optimize();
@Override
public BoolExpr asBoolExpr() {
return new BoolUnpack(location, this);
}
@Override
public StringExpr asStringExpr() {
return new StringUnpack(location, this);
}
@Override
public NumberExpr asNumberExpr() {
return new NumberUnpack(location, this);
}
@Override
public DynamicExpr asDynamicExpr() {
return this;
}
}

View File

@ -1,84 +0,0 @@
package io.gitlab.jfronny.muscript.ast;
import io.gitlab.jfronny.muscript.annotations.CanThrow;
import io.gitlab.jfronny.muscript.ast.literal.*;
import io.gitlab.jfronny.muscript.ast.string.StringCoerce;
import io.gitlab.jfronny.muscript.compiler.*;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.data.dynamic.DObject;
import io.gitlab.jfronny.muscript.data.dynamic.Dynamic;
import io.gitlab.jfronny.muscript.error.TypeMismatchException;
import java.util.stream.Stream;
@CanThrow
public abstract sealed class Expr<T> extends Decompilable
permits BoolExpr, DynamicExpr, NullLiteral, NumberExpr, StringExpr {
public final CodeLocation location;
protected Expr(Order order, CodeLocation location) {
super(order);
this.location = location;
}
public abstract Type getResultType();
public abstract T get(Scope dataRoot);
public T get(DObject dataRoot) {
return get(dataRoot instanceof Scope scope ? scope : new Scope(dataRoot));
}
public abstract Expr<T> optimize();
public BoolExpr asBoolExpr() {
if (this instanceof BoolExpr e) return e;
throw new TypeMismatchException(location, Type.Boolean, getResultType());
}
public StringExpr asStringExpr() {
if (this instanceof StringExpr e) return e;
return new StringCoerce(location, this);
}
public NumberExpr asNumberExpr() {
if (this instanceof NumberExpr e) return e;
throw new TypeMismatchException(location, Type.Number, getResultType());
}
public abstract DynamicExpr asDynamicExpr();
public abstract Stream<Expr<?>> extractSideEffects();
public boolean isNull() {
return this instanceof NullLiteral;
}
public static BoolExpr literal(boolean bool) {
return literal(CodeLocation.NONE, bool);
}
public static StringExpr literal(String string) {
return literal(CodeLocation.NONE, string);
}
public static NumberExpr literal(double number) {
return literal(CodeLocation.NONE, number);
}
public static NullLiteral literalNull() {
return literalNull(CodeLocation.NONE);
}
public static BoolExpr literal(CodeLocation location, boolean bool) {
return new BoolLiteral(location, bool);
}
public static StringExpr literal(CodeLocation location, String string) {
return new StringLiteral(location, string);
}
public static NumberExpr literal(CodeLocation location, double number) {
return new NumberLiteral(location, number);
}
public static NullLiteral literalNull(CodeLocation location) {
return new NullLiteral(location);
}
}

View File

@ -1,68 +0,0 @@
package io.gitlab.jfronny.muscript.ast;
import io.gitlab.jfronny.muscript.compiler.*;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.data.dynamic.DNull;
import io.gitlab.jfronny.muscript.annotations.CanThrow;
import io.gitlab.jfronny.muscript.ast.literal.DynamicLiteral;
import io.gitlab.jfronny.muscript.error.LocationalException;
import java.io.IOException;
import java.util.stream.Stream;
@CanThrow
public final class NullLiteral extends Expr<Object> {
public NullLiteral(CodeLocation location) {
super(Order.Primary, location);
}
@Override
public Type getResultType() {
return Type.Dynamic;
}
@Override
public Object get(Scope dataRoot) {
return null;
}
@Override
public NullLiteral optimize() {
return this;
}
@Override
public void decompile(ExprWriter writer) throws IOException {
writer.append("null");
}
@Override
public DynamicExpr asDynamicExpr() {
return new DynamicLiteral<>(location, new DNull());
}
@Override
public Stream<Expr<?>> extractSideEffects() {
return Stream.empty();
}
@Override
public NumberExpr asNumberExpr() {
throw new LocationalException(location, "Attempted to convert null to a number");
}
@Override
public StringExpr asStringExpr() {
return literal(location, "null");
}
@Override
public BoolExpr asBoolExpr() {
throw new LocationalException(location, "Attempted to convert null to a boolean");
}
@Override
public boolean equals(Object obj) {
return obj instanceof NullLiteral;
}
}

View File

@ -1,23 +0,0 @@
package io.gitlab.jfronny.muscript.ast;
import io.gitlab.jfronny.muscript.ast.dynamic.DynamicCoerce;
import io.gitlab.jfronny.muscript.compiler.*;
public abstract non-sealed class NumberExpr extends Expr<Double> {
protected NumberExpr(Order order, CodeLocation location) {
super(order, location);
}
@Override
public Type getResultType() {
return Type.Number;
}
@Override
public abstract NumberExpr optimize();
@Override
public DynamicExpr asDynamicExpr() {
return new DynamicCoerce(location, this);
}
}

View File

@ -1,23 +0,0 @@
package io.gitlab.jfronny.muscript.ast;
import io.gitlab.jfronny.muscript.ast.dynamic.DynamicCoerce;
import io.gitlab.jfronny.muscript.compiler.*;
public abstract non-sealed class StringExpr extends Expr<String> {
protected StringExpr(Order order, CodeLocation location) {
super(order, location);
}
@Override
public Type getResultType() {
return Type.String;
}
@Override
public abstract StringExpr optimize();
@Override
public DynamicExpr asDynamicExpr() {
return new DynamicCoerce(location, this);
}
}

View File

@ -1,52 +0,0 @@
package io.gitlab.jfronny.muscript.ast.bool;
import io.gitlab.jfronny.muscript.compiler.*;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.ast.BoolExpr;
import io.gitlab.jfronny.muscript.ast.Expr;
import io.gitlab.jfronny.muscript.ast.literal.BoolLiteral;
import java.io.IOException;
import java.util.stream.Stream;
public class And extends BoolExpr {
private final BoolExpr left;
private final BoolExpr right;
public And(CodeLocation location, BoolExpr left, BoolExpr right) {
super(Order.And, location);
this.left = left;
this.right = right;
}
@Override
public Boolean get(Scope dataRoot) {
return left.get(dataRoot) && right.get(dataRoot);
}
@Override
public BoolExpr optimize() {
BoolExpr left = this.left.optimize();
BoolExpr right = this.right.optimize();
if (left instanceof BoolLiteral literal) return literal.value ? right : literal(location, false);
if (right instanceof BoolLiteral literal) return literal.value ? left : literal(location, false);
return new And(location, left, right);
}
@Override
public Stream<Expr<?>> extractSideEffects() {
return Stream.concat(left.extractSideEffects(), right.extractSideEffects());
}
@Override
public void decompile(ExprWriter writer) throws IOException {
parenthesize(left, writer, false);
writer.append(" & ");
parenthesize(right, writer, true);
}
@Override
public boolean equals(Object obj) {
return obj instanceof And and && left.equals(and.left) && right.equals(and.right);
}
}

View File

@ -1,67 +0,0 @@
package io.gitlab.jfronny.muscript.ast.bool;
import io.gitlab.jfronny.muscript.ast.compare.Equal;
import io.gitlab.jfronny.muscript.ast.compare.Greater;
import io.gitlab.jfronny.muscript.ast.literal.NumberLiteral;
import io.gitlab.jfronny.muscript.compiler.*;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.ast.BoolExpr;
import io.gitlab.jfronny.muscript.ast.Expr;
import io.gitlab.jfronny.muscript.ast.literal.BoolLiteral;
import java.io.IOException;
import java.util.stream.Stream;
public class Not extends BoolExpr {
public final BoolExpr inner;
public Not(CodeLocation location, BoolExpr inner) {
super(Order.Unary, location);
this.inner = inner;
}
@Override
public Boolean get(Scope dataRoot) {
return !inner.get(dataRoot);
}
@Override
public BoolExpr optimize() {
BoolExpr inner = this.inner.optimize();
if (inner instanceof Not not) return not.inner;
if (inner instanceof BoolLiteral literal) return literal(location, !literal.value);
return new Not(location, inner);
}
@Override
public Stream<Expr<?>> extractSideEffects() {
return inner.extractSideEffects();
}
@Override
public void decompile(ExprWriter writer) throws IOException {
if (inner instanceof Equal eq) {
parenthesize(eq.left, writer, false);
writer.append(" != ");
parenthesize(eq.right, writer, true);
} else if (inner instanceof Greater gt) {
if (gt.left instanceof NumberLiteral && !(gt.right instanceof NumberLiteral)) {
parenthesize(gt.right, writer, false);
writer.append(" >= ");
parenthesize(gt.left, writer, true);
} else {
parenthesize(gt.left, writer, false);
writer.append(" <= ");
parenthesize(gt.right, writer, true);
}
} else {
writer.append("!");
parenthesize(inner, writer, false);
}
}
@Override
public boolean equals(Object obj) {
return obj instanceof Not not && inner.equals(not.inner);
}
}

View File

@ -1,52 +0,0 @@
package io.gitlab.jfronny.muscript.ast.bool;
import io.gitlab.jfronny.muscript.compiler.*;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.ast.BoolExpr;
import io.gitlab.jfronny.muscript.ast.Expr;
import io.gitlab.jfronny.muscript.ast.literal.BoolLiteral;
import java.io.IOException;
import java.util.stream.Stream;
public class Or extends BoolExpr {
private final BoolExpr left;
private final BoolExpr right;
public Or(CodeLocation location, BoolExpr left, BoolExpr right) {
super(Order.Or, location);
this.left = left;
this.right = right;
}
@Override
public Boolean get(Scope dataRoot) {
return left.get(dataRoot) || right.get(dataRoot);
}
@Override
public BoolExpr optimize() {
BoolExpr left = this.left.optimize();
BoolExpr right = this.right.optimize();
if (left instanceof BoolLiteral literal) return literal.value ? literal(location, true) : right;
if (right instanceof BoolLiteral literal) return literal.value ? literal(location, true) : left;
return new Or(location, left, right);
}
@Override
public Stream<Expr<?>> extractSideEffects() {
return Stream.concat(left.extractSideEffects(), right.extractSideEffects());
}
@Override
public void decompile(ExprWriter writer) throws IOException {
parenthesize(left, writer, false);
writer.append(" | ");
parenthesize(right, writer, true);
}
@Override
public boolean equals(Object obj) {
return obj instanceof Or or && left.equals(or.left) && right.equals(or.right);
}
}

View File

@ -1,57 +0,0 @@
package io.gitlab.jfronny.muscript.ast.compare;
import io.gitlab.jfronny.muscript.ast.BoolExpr;
import io.gitlab.jfronny.muscript.ast.Expr;
import io.gitlab.jfronny.muscript.compiler.*;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.data.dynamic.Dynamic;
import java.io.IOException;
import java.util.Objects;
import java.util.stream.Stream;
public class Equal extends BoolExpr {
public final Expr<?> left;
public final Expr<?> right;
public Equal(CodeLocation location, Expr<?> left, Expr<?> right) {
super(Order.Equality, location);
this.left = left;
this.right = right;
}
@Override
public Boolean get(Scope dataRoot) {
return Objects.equals(unwrap(left.get(dataRoot)), unwrap(right.get(dataRoot)));
}
private Object unwrap(Object o) {
if (o instanceof Dynamic a) return unwrap(a.getValue());
return o;
}
@Override
public BoolExpr optimize() {
Expr<?> left = this.left.optimize();
Expr<?> right = this.right.optimize();
if (left.equals(right)) return literal(location, true);
return new Equal(location, left, right);
}
@Override
public Stream<Expr<?>> extractSideEffects() {
return Stream.concat(left.extractSideEffects(), right.extractSideEffects());
}
@Override
public void decompile(ExprWriter writer) throws IOException {
parenthesize(left, writer, false);
writer.append(" == ");
parenthesize(right, writer, true);
}
@Override
public boolean equals(Object obj) {
return obj instanceof Equal equal && left.equals(equal.left) && right.equals(equal.right);
}
}

View File

@ -1,69 +0,0 @@
package io.gitlab.jfronny.muscript.ast.compare;
import io.gitlab.jfronny.muscript.compiler.*;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.ast.*;
import io.gitlab.jfronny.muscript.ast.literal.NumberLiteral;
import io.gitlab.jfronny.muscript.ast.math.*;
import java.io.IOException;
import java.util.stream.Stream;
public class Greater extends BoolExpr {
public final NumberExpr left;
public final NumberExpr right;
public Greater(CodeLocation location, NumberExpr left, NumberExpr right) {
super(Order.Comparison, location);
this.left = left;
this.right = right;
}
@Override
public Boolean get(Scope dataRoot) {
return left.get(dataRoot) > right.get(dataRoot);
}
@Override
public BoolExpr optimize() {
NumberExpr left = this.left.optimize();
NumberExpr right = this.right.optimize();
if (left instanceof NumberLiteral litL && right instanceof NumberLiteral litR)
return literal(location, litL.value > litR.value);
if (left instanceof Divide divide)
return new Greater(location, divide.dividend, new Multiply(divide.location, right, divide.divisor)).optimize();
if (left instanceof Invert invert)
return new Greater(location, new Invert(right.location, right), invert.inner).optimize();
if (left instanceof Minus minus)
return new Greater(location, minus.minuend, new Plus(minus.location, minus.subtrahend, right)).optimize();
// 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 Plus plus)
return new Greater(location, plus.augend, new Minus(plus.location, plus.addend, right)).optimize();
// Power is left out because it can't be transformed cleanly either
return new Greater(location, left, right);
}
@Override
public Stream<Expr<?>> extractSideEffects() {
return Stream.concat(left.extractSideEffects(), right.extractSideEffects());
}
@Override
public void decompile(ExprWriter writer) throws IOException {
if (left instanceof NumberLiteral && !(right instanceof NumberLiteral)) {
parenthesize(right, writer, false);
writer.append(" < ");
parenthesize(left, writer, true);
} else {
parenthesize(left, writer, false);
writer.append(" > ");
parenthesize(right, writer, true);
}
}
@Override
public boolean equals(Object obj) {
return obj instanceof Greater greater && left.equals(greater.left) && right.equals(greater.right);
}
}

View File

@ -1,64 +0,0 @@
package io.gitlab.jfronny.muscript.ast.conditional;
import io.gitlab.jfronny.muscript.ast.Expr;
import io.gitlab.jfronny.muscript.ast.bool.Not;
import io.gitlab.jfronny.muscript.ast.dynamic.ExprGroup;
import io.gitlab.jfronny.muscript.compiler.*;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.ast.BoolExpr;
import io.gitlab.jfronny.muscript.ast.literal.BoolLiteral;
import java.io.IOException;
import java.util.List;
import java.util.stream.Stream;
public class BoolConditional extends BoolExpr {
public final BoolExpr condition;
public final BoolExpr trueExpr;
public final BoolExpr falseExpr;
public BoolConditional(CodeLocation location, BoolExpr condition, BoolExpr trueExpr, BoolExpr falseExpr) {
super(Order.Conditional, location);
this.condition = condition;
this.trueExpr = trueExpr;
this.falseExpr = falseExpr;
}
@Override
public Boolean get(Scope dataRoot) {
return condition.get(dataRoot) ? trueExpr.get(dataRoot) : falseExpr.get(dataRoot);
}
@Override
public BoolExpr optimize() {
BoolExpr condition = this.condition.optimize();
BoolExpr trueExpr = this.trueExpr.optimize();
BoolExpr falseExpr = this.falseExpr.optimize();
if (condition instanceof BoolLiteral literal) return literal.value ? trueExpr : falseExpr;
if (trueExpr.equals(falseExpr)) return trueExpr;
if (condition instanceof Not not) return new BoolConditional(location, not.inner, falseExpr, trueExpr);
return new BoolConditional(location, condition, trueExpr, falseExpr);
}
@Override
public Stream<Expr<?>> extractSideEffects() {
return UnresolvedConditional.extractSideEffects(location, condition, trueExpr, falseExpr);
}
@Override
public void decompile(ExprWriter writer) throws IOException {
parenthesize(condition, writer, true);
writer.append(" ? ");
trueExpr.decompile(writer);
writer.append(" : ");
falseExpr.decompile(writer);
}
@Override
public boolean equals(Object obj) {
return obj instanceof BoolConditional conditional
&& condition.equals(conditional.condition)
&& trueExpr.equals(conditional.trueExpr)
&& falseExpr.equals(conditional.falseExpr);
}
}

View File

@ -1,66 +0,0 @@
package io.gitlab.jfronny.muscript.ast.conditional;
import io.gitlab.jfronny.muscript.ast.Expr;
import io.gitlab.jfronny.muscript.ast.bool.Not;
import io.gitlab.jfronny.muscript.ast.dynamic.ExprGroup;
import io.gitlab.jfronny.muscript.compiler.*;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.data.dynamic.Dynamic;
import io.gitlab.jfronny.muscript.ast.BoolExpr;
import io.gitlab.jfronny.muscript.ast.DynamicExpr;
import io.gitlab.jfronny.muscript.ast.literal.BoolLiteral;
import java.io.IOException;
import java.util.List;
import java.util.stream.Stream;
public class DynamicConditional extends DynamicExpr {
public final BoolExpr condition;
public final DynamicExpr trueExpr;
public final DynamicExpr falseExpr;
public DynamicConditional(CodeLocation location, BoolExpr condition, DynamicExpr trueExpr, DynamicExpr falseExpr) {
super(Order.Conditional, location);
this.condition = condition;
this.trueExpr = trueExpr;
this.falseExpr = falseExpr;
}
@Override
public Dynamic get(Scope dataRoot) {
return condition.get(dataRoot) ? trueExpr.get(dataRoot) : falseExpr.get(dataRoot);
}
@Override
public DynamicExpr optimize() {
BoolExpr condition = this.condition.optimize();
DynamicExpr trueExpr = this.trueExpr.optimize();
DynamicExpr falseExpr = this.falseExpr.optimize();
if (condition instanceof BoolLiteral literal) return literal.value ? trueExpr : falseExpr;
if (trueExpr.equals(falseExpr)) return trueExpr;
if (condition instanceof Not not) return new DynamicConditional(location, not.inner, falseExpr, trueExpr);
return new DynamicConditional(location, condition, trueExpr, falseExpr);
}
@Override
public Stream<Expr<?>> extractSideEffects() {
return UnresolvedConditional.extractSideEffects(location, condition, trueExpr, falseExpr);
}
@Override
public void decompile(ExprWriter writer) throws IOException {
parenthesize(condition, writer, true);
writer.append(" ? ");
trueExpr.decompile(writer);
writer.append(" : ");
falseExpr.decompile(writer);
}
@Override
public boolean equals(Object obj) {
return obj instanceof DynamicConditional conditional
&& condition.equals(conditional.condition)
&& trueExpr.equals(conditional.trueExpr)
&& falseExpr.equals(conditional.falseExpr);
}
}

View File

@ -1,63 +0,0 @@
package io.gitlab.jfronny.muscript.ast.conditional;
import io.gitlab.jfronny.muscript.ast.Expr;
import io.gitlab.jfronny.muscript.ast.bool.Not;
import io.gitlab.jfronny.muscript.compiler.*;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.ast.BoolExpr;
import io.gitlab.jfronny.muscript.ast.NumberExpr;
import io.gitlab.jfronny.muscript.ast.literal.BoolLiteral;
import java.io.IOException;
import java.util.stream.Stream;
public class NumberConditional extends NumberExpr {
public final BoolExpr condition;
public final NumberExpr trueExpr;
public final NumberExpr falseExpr;
public NumberConditional(CodeLocation location, BoolExpr condition, NumberExpr trueExpr, NumberExpr falseExpr) {
super(Order.Conditional, location);
this.condition = condition;
this.trueExpr = trueExpr;
this.falseExpr = falseExpr;
}
@Override
public Double get(Scope dataRoot) {
return condition.get(dataRoot) ? trueExpr.get(dataRoot) : falseExpr.get(dataRoot);
}
@Override
public NumberExpr optimize() {
BoolExpr condition = this.condition.optimize();
NumberExpr trueExpr = this.trueExpr.optimize();
NumberExpr falseExpr = this.falseExpr.optimize();
if (condition instanceof BoolLiteral literal) return literal.value ? trueExpr : falseExpr;
if (trueExpr.equals(falseExpr)) return trueExpr;
if (condition instanceof Not not) return new NumberConditional(location, not.inner, falseExpr, trueExpr);
return new NumberConditional(location, condition, trueExpr, falseExpr);
}
@Override
public Stream<Expr<?>> extractSideEffects() {
return UnresolvedConditional.extractSideEffects(location, condition, trueExpr, falseExpr);
}
@Override
public void decompile(ExprWriter writer) throws IOException {
parenthesize(condition, writer, true);
writer.append(" ? ");
trueExpr.decompile(writer);
writer.append(" : ");
falseExpr.decompile(writer);
}
@Override
public boolean equals(Object obj) {
return obj instanceof NumberConditional conditional
&& condition.equals(conditional.condition)
&& trueExpr.equals(conditional.trueExpr)
&& falseExpr.equals(conditional.falseExpr);
}
}

View File

@ -1,63 +0,0 @@
package io.gitlab.jfronny.muscript.ast.conditional;
import io.gitlab.jfronny.muscript.ast.Expr;
import io.gitlab.jfronny.muscript.ast.bool.Not;
import io.gitlab.jfronny.muscript.compiler.*;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.ast.BoolExpr;
import io.gitlab.jfronny.muscript.ast.StringExpr;
import io.gitlab.jfronny.muscript.ast.literal.BoolLiteral;
import java.io.IOException;
import java.util.stream.Stream;
public class StringConditional extends StringExpr {
public final BoolExpr condition;
public final StringExpr trueExpr;
public final StringExpr falseExpr;
public StringConditional(CodeLocation location, BoolExpr condition, StringExpr trueExpr, StringExpr falseExpr) {
super(Order.Conditional, location);
this.condition = condition;
this.trueExpr = trueExpr;
this.falseExpr = falseExpr;
}
@Override
public String get(Scope dataRoot) {
return condition.get(dataRoot) ? trueExpr.get(dataRoot) : falseExpr.get(dataRoot);
}
@Override
public StringExpr optimize() {
BoolExpr condition = this.condition.optimize();
StringExpr trueExpr = this.trueExpr.optimize();
StringExpr falseExpr = this.falseExpr.optimize();
if (condition instanceof BoolLiteral literal) return literal.value ? trueExpr : falseExpr;
if (trueExpr.equals(falseExpr)) return trueExpr;
if (condition instanceof Not not) return new StringConditional(location, not.inner, falseExpr, trueExpr);
return new StringConditional(location, condition, trueExpr, falseExpr);
}
@Override
public Stream<Expr<?>> extractSideEffects() {
return UnresolvedConditional.extractSideEffects(location, condition, trueExpr, falseExpr);
}
@Override
public void decompile(ExprWriter writer) throws IOException {
parenthesize(condition, writer, true);
writer.append(" ? ");
trueExpr.decompile(writer);
writer.append(" : ");
falseExpr.decompile(writer);
}
@Override
public boolean equals(Object obj) {
return obj instanceof StringConditional conditional
&& condition.equals(conditional.condition)
&& trueExpr.equals(conditional.trueExpr)
&& falseExpr.equals(conditional.falseExpr);
}
}

View File

@ -1,106 +0,0 @@
package io.gitlab.jfronny.muscript.ast.conditional;
import io.gitlab.jfronny.muscript.ast.bool.Not;
import io.gitlab.jfronny.muscript.ast.dynamic.ExprGroup;
import io.gitlab.jfronny.muscript.compiler.*;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.data.dynamic.DNull;
import io.gitlab.jfronny.muscript.data.dynamic.Dynamic;
import io.gitlab.jfronny.muscript.ast.*;
import io.gitlab.jfronny.muscript.ast.literal.BoolLiteral;
import java.io.IOException;
import java.util.List;
import java.util.stream.Stream;
public class UnresolvedConditional extends DynamicExpr {
private final BoolExpr condition;
private final Expr<?> trueExpr;
private final Expr<?> falseExpr;
public UnresolvedConditional(CodeLocation location, BoolExpr condition, Expr<?> trueExpr, Expr<?> falseExpr) {
super(Order.Conditional, location);
this.condition = condition;
this.trueExpr = trueExpr;
this.falseExpr = falseExpr;
}
@Override
public Type getResultType() {
return trueExpr.getResultType();
}
@Override
public DynamicExpr optimize() {
BoolExpr condition = this.condition.optimize();
Expr<?> trueExpr = this.trueExpr.optimize();
Expr<?> falseExpr = this.falseExpr.optimize();
if (condition instanceof BoolLiteral literal) return literal.value ? trueExpr.asDynamicExpr() : falseExpr.asDynamicExpr();
if (trueExpr.equals(falseExpr)) return trueExpr.asDynamicExpr();
if (condition instanceof Not not) return new UnresolvedConditional(location, not.inner, falseExpr, trueExpr);
return new UnresolvedConditional(location, condition, trueExpr, falseExpr);
}
@Override
public void decompile(ExprWriter writer) throws IOException {
parenthesize(condition, writer, true);
writer.append(" ? ");
trueExpr.decompile(writer);
writer.append(" : ");
falseExpr.decompile(writer);
}
@Override
public Dynamic get(Scope dataRoot) {
// unresolved conditionals may exist as root elements in scripts/closures
// thus, this needs to be handled, but the result will always be null
if (condition.get(dataRoot)) trueExpr.get(dataRoot);
else falseExpr.get(dataRoot);
return new DNull();
}
@Override
public DynamicExpr asDynamicExpr() {
return new DynamicConditional(location, condition, trueExpr.asDynamicExpr(), falseExpr.asDynamicExpr());
}
@Override
public Stream<Expr<?>> extractSideEffects() {
return extractSideEffects(location, condition, trueExpr, falseExpr);
}
public static Stream<Expr<?>> extractSideEffects(CodeLocation location, BoolExpr condition, Expr<?> trueExpr, Expr<?> falseExpr) {
List<Expr<?>> trueSE = trueExpr.extractSideEffects().toList();
List<Expr<?>> falseSE = falseExpr.extractSideEffects().toList();
if (trueSE.isEmpty() && falseSE.isEmpty()) return condition.extractSideEffects();
return Stream.of(new UnresolvedConditional(
location,
condition,
ExprGroup.of(trueExpr.location, trueSE, false),
ExprGroup.of(falseExpr.location, falseSE, false)
));
}
@Override
public BoolExpr asBoolExpr() {
return new BoolConditional(location, condition, trueExpr.asBoolExpr(), falseExpr.asBoolExpr());
}
@Override
public StringExpr asStringExpr() {
return new StringConditional(location, condition, trueExpr.asStringExpr(), falseExpr.asStringExpr());
}
@Override
public NumberExpr asNumberExpr() {
return new NumberConditional(location, condition, trueExpr.asNumberExpr(), falseExpr.asNumberExpr());
}
@Override
public boolean equals(Object obj) {
return obj instanceof UnresolvedConditional conditional
&& condition.equals(conditional.condition)
&& trueExpr.equals(conditional.trueExpr)
&& falseExpr.equals(conditional.falseExpr);
}
}

View File

@ -1,59 +0,0 @@
package io.gitlab.jfronny.muscript.ast.dynamic;
import io.gitlab.jfronny.muscript.ast.DynamicExpr;
import io.gitlab.jfronny.muscript.ast.Expr;
import io.gitlab.jfronny.muscript.compiler.*;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.data.dynamic.DCallable;
import io.gitlab.jfronny.muscript.data.dynamic.additional.DFinal;
import io.gitlab.jfronny.muscript.data.dynamic.Dynamic;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.Stream;
public class Bind extends DynamicExpr {
public final DynamicExpr callable;
public final DynamicExpr parameter;
public Bind(CodeLocation location, DynamicExpr callable, DynamicExpr parameter) {
super(Order.Call, location);
this.callable = callable;
this.parameter = parameter;
}
@Override
public Dynamic get(Scope dataRoot) {
Dynamic param = parameter.get(dataRoot);
DCallable clb = callable.get(dataRoot).asCallable();
return DFinal.of("<bind>", args -> {
List<Dynamic> argsWithParameter = new LinkedList<>(args.getValue());
argsWithParameter.addFirst(param);
return clb.call(DFinal.of(argsWithParameter));
}, () -> this);
}
@Override
public void decompile(ExprWriter writer) throws IOException {
parenthesize(parameter, writer, false);
writer.append("::");
if (callable instanceof Variable) {
callable.decompile(writer);
} else {
writer.append('(');
callable.decompile(writer);
writer.append(')');
}
}
@Override
public DynamicExpr optimize() {
return new Bind(location, callable.optimize(), parameter.optimize());
}
@Override
public Stream<Expr<?>> extractSideEffects() {
return Stream.concat(callable.extractSideEffects(), parameter.extractSideEffects());
}
}

View File

@ -1,125 +0,0 @@
package io.gitlab.jfronny.muscript.ast.dynamic;
import io.gitlab.jfronny.muscript.ast.DynamicExpr;
import io.gitlab.jfronny.muscript.annotations.CanThrow;
import io.gitlab.jfronny.muscript.annotations.UncheckedDynamic;
import io.gitlab.jfronny.muscript.ast.Expr;
import io.gitlab.jfronny.muscript.compiler.*;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.data.dynamic.*;
import io.gitlab.jfronny.muscript.data.dynamic.additional.DFinal;
import io.gitlab.jfronny.muscript.error.LocationalException;
import io.gitlab.jfronny.muscript.error.StackFrame;
import java.io.IOException;
import java.util.*;
import java.util.stream.Stream;
@CanThrow
@UncheckedDynamic
public class Call extends DynamicExpr {
public final DynamicExpr left;
public final List<Arg> args;
public Call(CodeLocation location, DynamicExpr left, List<Arg> args) {
super(Order.Call, location);
this.left = left;
this.args = args;
}
@Override
public Dynamic get(Scope dataRoot) {
DCallable dc;
DList arg;
try {
Dynamic lv = left.get(dataRoot);
if (Dynamic.isNull(lv)) throw new LocationalException(location, "Cannot invoke null");
dc = lv.asCallable();
arg = DFinal.of(args.stream().flatMap(e -> e.get(dataRoot)).toArray(Dynamic[]::new));
} catch (DynamicTypeConversionException e) {
throw e.locational(location);
} catch (RuntimeException e) {
throw new LocationalException(location, "Could not perform call successfully", e);
}
try {
return dc.call(arg);
} catch (LocationalException le) {
throw le.appendStack(new StackFrame.Raw(location.file(), dc.getName(), left.location.chStart()));
} catch (DynamicTypeConversionException e) {
throw e.locational(location);
} catch (RuntimeException e) {
throw new LocationalException(location, e.getMessage(), e);
}
}
@Override
public DynamicExpr optimize() {
List<Arg> args = new ArrayList<>();
DynamicExpr left = this.left.optimize();
if (left instanceof Bind bind) {
left = bind.callable;
args.add(new Arg(bind.parameter, false));
}
for (Arg arg : this.args) args.add(arg.optimize());
if (left instanceof Closure closure) {
return new ExprGroup(
closure.location,
closure.stream().toList(),
new ExprGroup.PackedArgs(
args,
closure.boundArgs,
closure.variadic
)
);
}
return new Call(location, left, args);
}
@Override
public Stream<Expr<?>> extractSideEffects() {
return Stream.of(this);
}
@Override
public void decompile(ExprWriter writer) throws IOException {
parenthesize(left, writer, false);
if (args.size() > 3) {
writer.increaseIndent();
writer.append("(\n");
boolean first = true;
for (Arg arg : args) {
if (!first) writer.append(",\n");
first = false;
arg.expr.decompile(writer);
if (arg.variadic) writer.append("...");
}
writer.decreaseIndent();
writer.append("\n)");
} else {
writer.append('(');
boolean first = true;
for (Arg arg : args) {
if (!first) writer.append(", ");
first = false;
arg.expr.decompile(writer);
if (arg.variadic) writer.append("...");
}
writer.append(')');
}
}
@Override
public boolean equals(Object obj) {
return obj instanceof Call call && left.equals(call.left) && args.equals(call.args);
}
public record Arg(DynamicExpr expr, boolean variadic) {
public Stream<? extends Dynamic> get(Scope dataRoot) {
return variadic ? expr.get(dataRoot).asList().getValue().stream() : Stream.of(expr.get(dataRoot));
}
public Arg optimize() {
return new Arg(expr.optimize(), variadic);
}
}
}

View File

@ -1,118 +0,0 @@
package io.gitlab.jfronny.muscript.ast.dynamic;
import io.gitlab.jfronny.muscript.ast.DynamicExpr;
import io.gitlab.jfronny.muscript.ast.Expr;
import io.gitlab.jfronny.muscript.compiler.*;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.data.dynamic.*;
import io.gitlab.jfronny.muscript.data.dynamic.additional.DFinal;
import io.gitlab.jfronny.muscript.data.dynamic.type.DTypeCallable;
import io.gitlab.jfronny.muscript.error.LocationalException;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.IntStream;
import java.util.stream.Stream;
public class Closure extends DynamicExpr {
public final List<String> boundArgs;
private final List<Expr<?>> steps;
private final DynamicExpr fin;
public final boolean variadic;
public Closure(CodeLocation location, List<String> boundArgs, List<Expr<?>> expressions, boolean variadic) {
this(location, boundArgs, expressions.subList(0, expressions.size() - 1), expressions.getLast().asDynamicExpr(), variadic);
}
private Closure(CodeLocation location, List<String> boundArgs, List<Expr<?>> steps, DynamicExpr fin, boolean variadic) {
super(Order.Primary, location);
this.boundArgs = List.copyOf(boundArgs);
this.steps = List.copyOf(steps);
this.fin = fin;
this.variadic = variadic;
}
private DTypeCallable getSignature() {
//TODO take into account inner instructions to properly determine type
List<DTypeCallable.Arg> args = new LinkedList<>();
for (int i = 0; i < boundArgs.size(); i++) {
args.add(new DTypeCallable.Arg(boundArgs.get(i), null, variadic && (i == boundArgs.size() - 1)));
}
return new DTypeCallable(args, null);
}
@Override
public DCallable get(Scope dataRoot) {
return DFinal.of(getSignature(), null, args -> {
// Compare with ExprGroup.get()
int ac = args.size();
int ae = boundArgs.size();
if (variadic) ae--;
if (ac < ae) throw new LocationalException(location, "Invoked with too few arguments (expected " + ae + " but got " + ac + ")");
if (!variadic && ac > ae) throw new LocationalException(location, "Invoked with too many arguments (expected " + ae + " but got " + ac + ")");
Scope fork = dataRoot.fork();
for (int i = 0; i < ae; i++) fork.set(boundArgs.get(i), args.get(i));
if (variadic) {
fork.set(boundArgs.getLast(), IntStream.range(ae, ac)
.mapToObj(args::get)
.toList());
}
for (Expr<?> step : steps) {
step.get(fork);
}
return fin.get(fork);
}, () -> this);
}
@Override
public DynamicExpr optimize() {
return new Closure(
location,
boundArgs,
steps.stream()
.map(Expr::optimize)
.flatMap(Expr::extractSideEffects)
.<Expr<?>>map(Expr::optimize)
.toList(),
fin.optimize(),
variadic
);
}
@Override
public Stream<Expr<?>> extractSideEffects() {
return Stream.empty();
}
@Override
public void decompile(ExprWriter writer) throws IOException {
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(" ->").increaseIndent();
for (Expr<?> expr : stream().toList()) {
writer.append("\n");
expr.decompile(writer);
writer.append(";");
}
writer.decreaseIndent().append("\n}");
}
@Override
public boolean equals(Object obj) {
return obj instanceof Closure closure
&& boundArgs.equals(closure.boundArgs)
&& steps.equals(closure.steps)
&& fin.equals(closure.fin);
}
public Stream<Expr<?>> stream() {
return Stream.concat(steps.stream(), Stream.of(fin));
}
}

View File

@ -1,62 +0,0 @@
package io.gitlab.jfronny.muscript.ast.dynamic;
import io.gitlab.jfronny.muscript.ast.*;
import io.gitlab.jfronny.muscript.ast.dynamic.unpack.*;
import io.gitlab.jfronny.muscript.compiler.CodeLocation;
import io.gitlab.jfronny.muscript.compiler.ExprWriter;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.data.dynamic.*;
import io.gitlab.jfronny.muscript.data.dynamic.additional.DFinal;
import java.io.IOException;
import java.util.stream.Stream;
public class DynamicCoerce extends DynamicExpr {
public final Expr<?> inner;
public DynamicCoerce(CodeLocation location, Expr<?> inner) {
super(inner.order, location);
this.inner = inner;
if (!(inner instanceof DynamicExpr)
&& !(inner instanceof BoolExpr)
&& !(inner instanceof StringExpr)
&& !(inner instanceof NumberExpr)
&& !(inner instanceof NullLiteral)) {
throw new IllegalArgumentException("A DynamicCoerce can only be created with a well-defined expression type");
}
}
@Override
public Dynamic get(Scope dataRoot) {
if (inner instanceof DynamicExpr e) return e.get(dataRoot);
if (inner instanceof BoolExpr e) return DFinal.of(e.get(dataRoot));
if (inner instanceof StringExpr e) return DFinal.of(e.get(dataRoot));
if (inner instanceof NumberExpr e) return DFinal.of(e.get(dataRoot));
if (inner instanceof NullLiteral) return new DNull();
throw new IllegalStateException("The inner expression of DynamicCoerce should never be of undefined type");
}
@Override
public DynamicExpr optimize() {
Expr<?> inner = this.inner.optimize();
if (inner instanceof BoolUnpack unpack) return unpack.inner;
if (inner instanceof NumberUnpack unpack) return unpack.inner;
if (inner instanceof StringUnpack unpack) return unpack.inner;
return new DynamicCoerce(location, inner);
}
@Override
public Stream<Expr<?>> extractSideEffects() {
return inner.extractSideEffects();
}
@Override
public void decompile(ExprWriter writer) throws IOException {
inner.decompile(writer);
}
@Override
public boolean equals(Object obj) {
return obj instanceof DynamicCoerce coerce && inner.equals(coerce.inner);
}
}

View File

@ -1,133 +0,0 @@
package io.gitlab.jfronny.muscript.ast.dynamic;
import io.gitlab.jfronny.muscript.ast.DynamicExpr;
import io.gitlab.jfronny.muscript.ast.Expr;
import io.gitlab.jfronny.muscript.ast.NullLiteral;
import io.gitlab.jfronny.muscript.ast.string.Concatenate;
import io.gitlab.jfronny.muscript.compiler.CodeLocation;
import io.gitlab.jfronny.muscript.compiler.ExprWriter;
import io.gitlab.jfronny.muscript.compiler.Order;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.data.dynamic.Dynamic;
import io.gitlab.jfronny.muscript.error.LocationalException;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.IntStream;
import java.util.stream.Stream;
public class ExprGroup extends DynamicExpr {
private final List<Expr<?>> steps;
private final DynamicExpr fin;
private final @Nullable PackedArgs packedArgs;
private final boolean fork;
public ExprGroup(CodeLocation location, List<Expr<?>> expressions) {
this(location, expressions, null);
}
public ExprGroup(CodeLocation location, List<Expr<?>> expressions, @Nullable PackedArgs packedArgs) {
this(location, expressions, packedArgs, true);
}
public ExprGroup(CodeLocation location, List<Expr<?>> expressions, @Nullable PackedArgs packedArgs, boolean fork) {
super(Order.Primary, location);
this.steps = expressions.subList(0, expressions.size() - 1);
this.fin = expressions.getLast().asDynamicExpr();
this.packedArgs = packedArgs;
this.fork = fork;
if (!fork && packedArgs != null) throw new UnsupportedOperationException("packedArgs may only be used for forking ExprGroups");
}
public static Expr<?> of(CodeLocation location, List<Expr<?>> expressions) {
return of(location, expressions, true);
}
public static Expr<?> of(CodeLocation location, List<Expr<?>> expressions, boolean fork) {
if (expressions.isEmpty()) return new NullLiteral(location);
if (!fork && expressions.size() == 1) return expressions.getFirst();
return new ExprGroup(location, expressions, null, fork);
}
@Override
public Dynamic get(Scope dataRoot) {
Scope fork = this.fork ? dataRoot.fork() : dataRoot;
if (this.fork && packedArgs != null) {
List<Dynamic> from = new LinkedList<>();
for (Call.Arg arg : packedArgs.from) {
Dynamic data = arg.expr().get(dataRoot);
if (arg.variadic()) from.addAll(data.asList().getValue());
else from.add(data);
}
List<String> to = packedArgs.to;
// Compare with Closure.get()
int ac = from.size();
int ae = to.size();
if (packedArgs.variadic) ae--;
if (ac < ae) throw new LocationalException(location, "Invoked with too few arguments (expected " + ae + " but got " + ac + ")");
if (!packedArgs.variadic && ac > ae) throw new LocationalException(location, "Invoked with too many arguments (expected " + ae + " but got " + ac + ")");
for (int i = 0; i < ae; i++) fork.set(to.get(i), from.get(i));
if (packedArgs.variadic) {
fork.set(to.get(to.size() - 1), IntStream.range(ae, ac)
.mapToObj(from::get)
.toList());
}
}
for (Expr<?> step : steps) {
step.get(fork);
}
return fin.get(fork);
}
@Override
public DynamicExpr optimize() {
List<Expr<?>> exprs = Stream.concat(
steps.stream()
.map(Expr::optimize)
.flatMap(Expr::extractSideEffects)
.map(Expr::optimize),
Stream.of(fin.optimize())
).toList();
return packedArgs == null
? of(location, exprs, fork).asDynamicExpr()
: new ExprGroup(location, exprs, packedArgs, fork);
}
@Override
public Stream<Expr<?>> extractSideEffects() {
return fork ? Stream.of(this) : Stream.of(of(location, stream().flatMap(Expr::extractSideEffects).toList()));
}
@Override
public void decompile(ExprWriter writer) throws IOException {
if (!fork) {
// Use string concatenation since the result is ignored either way
stream()
.reduce((left, right) -> new Concatenate(location, left.asStringExpr(), right.asStringExpr()))
.orElseGet(() -> new NullLiteral(location))
.optimize()
.decompile(writer);
} else {
new Call(
location,
new Closure(
location,
packedArgs == null
? List.of()
: packedArgs.to,
stream().toList(),
false
),
packedArgs == null ? List.of() : packedArgs.from
).decompile(writer);
}
}
public Stream<Expr<?>> stream() {
return Stream.concat(steps.stream(), Stream.of(fin));
}
public record PackedArgs(List<Call.Arg> from, List<String> to, boolean variadic) {}
}

View File

@ -1,78 +0,0 @@
package io.gitlab.jfronny.muscript.ast.dynamic;
import io.gitlab.jfronny.muscript.ast.DynamicExpr;
import io.gitlab.jfronny.muscript.ast.Expr;
import io.gitlab.jfronny.muscript.annotations.CanThrow;
import io.gitlab.jfronny.muscript.annotations.UncheckedDynamic;
import io.gitlab.jfronny.muscript.ast.literal.StringLiteral;
import io.gitlab.jfronny.muscript.compiler.*;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.data.dynamic.*;
import io.gitlab.jfronny.muscript.error.LocationalException;
import io.gitlab.jfronny.muscript.error.TypeMismatchException;
import java.io.IOException;
import java.util.stream.Stream;
@CanThrow
@UncheckedDynamic
public class Get extends DynamicExpr {
private final DynamicExpr left;
private final Expr<?> name;
public Get(CodeLocation location, DynamicExpr left, Expr<?> name) {
super(Order.Call, location);
this.left = left;
this.name = name;
if (name.getResultType() != Type.String && name.getResultType() != Type.Number && name.getResultType() != Type.Dynamic) {
throw new TypeMismatchException(location, Type.String, name.getResultType(), "Name must be either a string or a number");
}
}
@Override
public Dynamic get(Scope dataRoot) {
Dynamic left = this.left.get(dataRoot);
if (Dynamic.isNull(left)) throw new LocationalException(location, "Could not get \"" + name.asStringExpr().get(dataRoot) + "\" because left is null");
if (left.isObject()) {
DObject o = left.asObject();
var n = name.asStringExpr().get(dataRoot);
if (!o.has(n)) throw new LocationalException(location, "Object does not contain \"" + n + "\"");
return o.get(n);
} else if (left.isList()) {
DList l = left.asList();
int idx = name.asNumberExpr().get(dataRoot).intValue();
if (idx < 0 || idx >= l.size()) throw new LocationalException(location, "Index " + idx + " is out of range for list with size " + l.size());
return l.get(idx);
}
throw new DynamicTypeConversionException("object or list", left).locational(location);
}
@Override
public DynamicExpr optimize() {
DynamicExpr left = this.left.optimize();
Expr<?> name = this.name.optimize();
return new Get(location, left, name);
}
@Override
public Stream<Expr<?>> extractSideEffects() {
return Stream.concat(left.extractSideEffects(), name.extractSideEffects());
}
@Override
public void decompile(ExprWriter writer) throws IOException {
parenthesize(left, writer, false);
if (name instanceof StringLiteral lit && Lexer.isValidId(lit.value)) {
writer.append('.').append(lit.value);
} else {
writer.append('[');
name.decompile(writer);
writer.append(']');
}
}
@Override
public boolean equals(Object obj) {
return obj instanceof Get get && left.equals(get.left) && name.equals(get.name);
}
}

View File

@ -1,47 +0,0 @@
package io.gitlab.jfronny.muscript.ast.dynamic;
import io.gitlab.jfronny.muscript.ast.DynamicExpr;
import io.gitlab.jfronny.muscript.ast.Expr;
import io.gitlab.jfronny.muscript.compiler.CodeLocation;
import io.gitlab.jfronny.muscript.compiler.ExprWriter;
import io.gitlab.jfronny.muscript.compiler.Order;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.data.dynamic.Dynamic;
import io.gitlab.jfronny.muscript.data.dynamic.additional.DFinal;
import java.io.IOException;
import java.util.List;
import java.util.stream.Stream;
public class ListOf extends DynamicExpr {
private final List<DynamicExpr> elements;
protected ListOf(CodeLocation location, List<DynamicExpr> elements) {
super(Order.Primary, location);
this.elements = elements;
}
@Override
public Dynamic get(Scope dataRoot) {
return DFinal.of(elements.stream().map(s -> s.get(dataRoot)).toList());
}
@Override
public DynamicExpr optimize() {
return new ListOf(location, elements.stream().map(DynamicExpr::optimize).toList());
}
@Override
public Stream<Expr<?>> extractSideEffects() {
return elements.stream().flatMap(Expr::extractSideEffects);
}
@Override
public void decompile(ExprWriter writer) throws IOException {
new Call(
location,
new Variable(location, "listOf"),
elements.stream().map(s -> new Call.Arg(s, false)).toList()
).decompile(writer);
}
}

View File

@ -1,63 +0,0 @@
package io.gitlab.jfronny.muscript.ast.dynamic;
import io.gitlab.jfronny.muscript.ast.DynamicExpr;
import io.gitlab.jfronny.muscript.ast.Expr;
import io.gitlab.jfronny.muscript.ast.literal.DynamicLiteral;
import io.gitlab.jfronny.muscript.compiler.*;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.data.dynamic.Dynamic;
import io.gitlab.jfronny.muscript.data.dynamic.additional.DFinal;
import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.stream.Stream;
public class ObjectLiteral extends DynamicExpr {
public final Map<String, DynamicExpr> content;
public ObjectLiteral(CodeLocation location, Map<String, DynamicExpr> content) {
super(Order.Primary, location);
this.content = content;
}
@Override
public Dynamic get(Scope dataRoot) {
Map<String, Dynamic> result = new LinkedHashMap<>();
this.content.forEach((k, v) -> result.put(k, v.get(dataRoot)));
return DFinal.of(result);
}
@Override
public DynamicExpr optimize() {
Map<String, DynamicExpr> content = new LinkedHashMap<>();
Map<String, Dynamic> literalContent = new LinkedHashMap<>();
boolean literal = true;
for (Map.Entry<String, DynamicExpr> entry : this.content.entrySet()) {
DynamicExpr de = entry.getValue().optimize();
if (de instanceof DynamicLiteral<?> dl && literal) literalContent.put(entry.getKey(), dl.value);
else literal = false;
content.put(entry.getKey(), de);
}
if (literal) return new DynamicLiteral<>(location, DFinal.of(literalContent));
return new ObjectLiteral(location, content);
}
@Override
public Stream<Expr<?>> extractSideEffects() {
return content.values().stream().flatMap(Expr::extractSideEffects);
}
@Override
public void decompile(ExprWriter writer) throws IOException {
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(" = ");
entry.getValue().decompile(writer);
}
writer.decreaseIndent().append("\n}");
}
}

View File

@ -1,51 +0,0 @@
package io.gitlab.jfronny.muscript.ast.dynamic;
import io.gitlab.jfronny.muscript.ast.Expr;
import io.gitlab.jfronny.muscript.compiler.*;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.data.dynamic.Dynamic;
import io.gitlab.jfronny.muscript.ast.DynamicExpr;
import io.gitlab.jfronny.muscript.annotations.CanThrow;
import io.gitlab.jfronny.muscript.annotations.UncheckedDynamic;
import io.gitlab.jfronny.muscript.error.LocationalException;
import java.io.IOException;
import java.util.stream.Stream;
@CanThrow
@UncheckedDynamic
public class Variable extends DynamicExpr {
public final String name;
public Variable(CodeLocation location, String name) {
super(Order.Primary, location);
this.name = name;
}
@Override
public Dynamic get(Scope dataRoot) {
if (name.equals("this")) return dataRoot;
if (dataRoot.has(name)) return dataRoot.get(name);
else throw new LocationalException(location, "This object doesn't contain '" + name + "'");
}
@Override
public DynamicExpr optimize() {
return this;
}
@Override
public Stream<Expr<?>> extractSideEffects() {
return Stream.empty();
}
@Override
public void decompile(ExprWriter writer) throws IOException {
writer.appendLiteral(name);
}
@Override
public boolean equals(Object obj) {
return obj instanceof Variable variable && name.equals(variable.name);
}
}

View File

@ -1,52 +0,0 @@
package io.gitlab.jfronny.muscript.ast.dynamic.assign;
import io.gitlab.jfronny.muscript.ast.BoolExpr;
import io.gitlab.jfronny.muscript.ast.Expr;
import io.gitlab.jfronny.muscript.compiler.*;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.error.LocationalException;
import java.io.IOException;
import java.util.stream.Stream;
public class BoolAssign extends BoolExpr {
private final String name;
private final BoolExpr value;
protected BoolAssign(CodeLocation location, String name, BoolExpr value) {
super(Order.Primary, location);
if (name.equals("this")) throw new LocationalException(location, "Cannot reassign 'this'");
this.name = name;
this.value = value;
}
@Override
public Boolean get(Scope dataRoot) {
boolean data = value.get(dataRoot);
dataRoot.set(name, data);
return data;
}
@Override
public BoolExpr optimize() {
return new BoolAssign(location, name, value.optimize());
}
@Override
public Stream<Expr<?>> extractSideEffects() {
return Stream.of(this);
}
@Override
public void decompile(ExprWriter writer) throws IOException {
writer.appendLiteral(name).append(" = ");
value.decompile(writer);
}
@Override
public boolean equals(Object obj) {
return obj instanceof BoolAssign assign
&& name.equals(assign.name)
&& value.equals(assign.value);
}
}

View File

@ -1,63 +0,0 @@
package io.gitlab.jfronny.muscript.ast.dynamic.assign;
import io.gitlab.jfronny.muscript.ast.*;
import io.gitlab.jfronny.muscript.compiler.*;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.data.dynamic.DCallable;
import io.gitlab.jfronny.muscript.data.dynamic.Dynamic;
import io.gitlab.jfronny.muscript.error.LocationalException;
import java.io.IOException;
import java.util.stream.Stream;
public class DynamicAssign extends DynamicExpr {
private final String name;
private final DynamicExpr value;
public DynamicAssign(CodeLocation location, String name, DynamicExpr value) {
super(Order.Primary, location);
if (name.equals("this")) throw new LocationalException(location, "Cannot reassign 'this'");
this.name = name;
this.value = value;
}
@Override
public Dynamic get(Scope dataRoot) {
Dynamic data = value.get(dataRoot);
dataRoot.set(name, data.isCallable() ? data.asCallable().named(name) : data);
return data;
}
@Override
public DynamicExpr optimize() {
return new DynamicAssign(location, name, value.optimize());
}
@Override
public void decompile(ExprWriter writer) throws IOException {
writer.appendLiteral(name).append(" = ");
value.decompile(writer);
}
@Override
public BoolExpr asBoolExpr() {
return new BoolAssign(location, name, value.asBoolExpr());
}
@Override
public NumberExpr asNumberExpr() {
return new NumberAssign(location, name, value.asNumberExpr());
}
@Override
public Stream<Expr<?>> extractSideEffects() {
return Stream.of(this);
}
@Override
public boolean equals(Object obj) {
return obj instanceof DynamicAssign assign
&& name.equals(assign.name)
&& value.equals(assign.value);
}
}

View File

@ -1,52 +0,0 @@
package io.gitlab.jfronny.muscript.ast.dynamic.assign;
import io.gitlab.jfronny.muscript.ast.Expr;
import io.gitlab.jfronny.muscript.ast.NumberExpr;
import io.gitlab.jfronny.muscript.compiler.*;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.error.LocationalException;
import java.io.IOException;
import java.util.stream.Stream;
public class NumberAssign extends NumberExpr {
private final String name;
private final NumberExpr value;
protected NumberAssign(CodeLocation location, String name, NumberExpr value) {
super(Order.Primary, location);
if (name.equals("this")) throw new LocationalException(location, "Cannot reassign 'this'");
this.name = name;
this.value = value;
}
@Override
public Double get(Scope dataRoot) {
double data = value.get(dataRoot);
dataRoot.set(name, data);
return data;
}
@Override
public NumberExpr optimize() {
return new NumberAssign(location, name, value.optimize());
}
@Override
public Stream<Expr<?>> extractSideEffects() {
return Stream.of(this);
}
@Override
public void decompile(ExprWriter writer) throws IOException {
writer.appendLiteral(name).append(" = ");
value.decompile(writer);
}
@Override
public boolean equals(Object obj) {
return obj instanceof NumberAssign assign
&& name.equals(assign.name)
&& value.equals(assign.value);
}
}

View File

@ -1,52 +0,0 @@
package io.gitlab.jfronny.muscript.ast.dynamic.assign;
import io.gitlab.jfronny.muscript.ast.Expr;
import io.gitlab.jfronny.muscript.ast.StringExpr;
import io.gitlab.jfronny.muscript.compiler.*;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.error.LocationalException;
import java.io.IOException;
import java.util.stream.Stream;
public class StringAssign extends StringExpr {
private final String name;
private final StringExpr value;
protected StringAssign(CodeLocation location, String name, StringExpr value) {
super(Order.Primary, location);
if (name.equals("this")) throw new LocationalException(location, "Cannot reassign 'this'");
this.name = name;
this.value = value;
}
@Override
public String get(Scope dataRoot) {
String data = value.get(dataRoot);
dataRoot.set(name, data);
return data;
}
@Override
public StringExpr optimize() {
return new StringAssign(location, name, value.optimize());
}
@Override
public Stream<Expr<?>> extractSideEffects() {
return Stream.of(this);
}
@Override
public void decompile(ExprWriter writer) throws IOException {
writer.appendLiteral(name).append(" = ");
value.decompile(writer);
}
@Override
public boolean equals(Object obj) {
return obj instanceof StringAssign assign
&& name.equals(assign.name)
&& value.equals(assign.value);
}
}

View File

@ -1,61 +0,0 @@
package io.gitlab.jfronny.muscript.ast.dynamic.unpack;
import io.gitlab.jfronny.muscript.ast.Expr;
import io.gitlab.jfronny.muscript.compiler.CodeLocation;
import io.gitlab.jfronny.muscript.compiler.ExprWriter;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.data.dynamic.Dynamic;
import io.gitlab.jfronny.muscript.data.dynamic.DynamicTypeConversionException;
import io.gitlab.jfronny.muscript.ast.BoolExpr;
import io.gitlab.jfronny.muscript.ast.DynamicExpr;
import io.gitlab.jfronny.muscript.annotations.CanThrow;
import io.gitlab.jfronny.muscript.annotations.UncheckedDynamic;
import io.gitlab.jfronny.muscript.ast.dynamic.DynamicCoerce;
import io.gitlab.jfronny.muscript.error.LocationalException;
import java.io.IOException;
import java.util.stream.Stream;
@CanThrow
@UncheckedDynamic
public class BoolUnpack extends BoolExpr {
public final DynamicExpr inner;
public BoolUnpack(CodeLocation location, DynamicExpr inner) {
super(inner.order, location);
this.inner = inner;
}
@Override
public Boolean get(Scope dataRoot) {
try {
Dynamic iv = inner.get(dataRoot);
if (Dynamic.isNull(iv)) throw new LocationalException(location, "Cannot unpack null");
return iv.asBool().getValue();
} catch (DynamicTypeConversionException e) {
throw e.locational(location);
}
}
@Override
public BoolExpr optimize() {
DynamicExpr inner = this.inner.optimize();
if (inner instanceof DynamicCoerce coerce) return coerce.inner.asBoolExpr();
return new BoolUnpack(location, inner);
}
@Override
public Stream<Expr<?>> extractSideEffects() {
return inner.extractSideEffects();
}
@Override
public void decompile(ExprWriter writer) throws IOException {
inner.decompile(writer);
}
@Override
public boolean equals(Object obj) {
return obj instanceof BoolUnpack unpack && inner.equals(unpack.inner);
}
}

View File

@ -1,61 +0,0 @@
package io.gitlab.jfronny.muscript.ast.dynamic.unpack;
import io.gitlab.jfronny.muscript.ast.Expr;
import io.gitlab.jfronny.muscript.compiler.CodeLocation;
import io.gitlab.jfronny.muscript.compiler.ExprWriter;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.data.dynamic.Dynamic;
import io.gitlab.jfronny.muscript.data.dynamic.DynamicTypeConversionException;
import io.gitlab.jfronny.muscript.ast.DynamicExpr;
import io.gitlab.jfronny.muscript.ast.NumberExpr;
import io.gitlab.jfronny.muscript.annotations.CanThrow;
import io.gitlab.jfronny.muscript.annotations.UncheckedDynamic;
import io.gitlab.jfronny.muscript.ast.dynamic.DynamicCoerce;
import io.gitlab.jfronny.muscript.error.LocationalException;
import java.io.IOException;
import java.util.stream.Stream;
@CanThrow
@UncheckedDynamic
public class NumberUnpack extends NumberExpr {
public final DynamicExpr inner;
public NumberUnpack(CodeLocation location, DynamicExpr inner) {
super(inner.order, location);
this.inner = inner;
}
@Override
public Double get(Scope dataRoot) {
try {
Dynamic iv = inner.get(dataRoot);
if (Dynamic.isNull(iv)) throw new LocationalException(location, "Cannot unpack null");
return iv.asNumber().getValue();
} catch (DynamicTypeConversionException e) {
throw e.locational(location);
}
}
@Override
public NumberExpr optimize() {
DynamicExpr inner = this.inner.optimize();
if (inner instanceof DynamicCoerce coerce) return coerce.inner.asNumberExpr();
return new NumberUnpack(location, inner.optimize());
}
@Override
public Stream<Expr<?>> extractSideEffects() {
return inner.extractSideEffects();
}
@Override
public void decompile(ExprWriter writer) throws IOException {
inner.decompile(writer);
}
@Override
public boolean equals(Object obj) {
return obj instanceof NumberUnpack unpack && inner.equals(unpack.inner);
}
}

View File

@ -1,61 +0,0 @@
package io.gitlab.jfronny.muscript.ast.dynamic.unpack;
import io.gitlab.jfronny.muscript.ast.Expr;
import io.gitlab.jfronny.muscript.compiler.CodeLocation;
import io.gitlab.jfronny.muscript.compiler.ExprWriter;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.data.dynamic.Dynamic;
import io.gitlab.jfronny.muscript.data.dynamic.DynamicTypeConversionException;
import io.gitlab.jfronny.muscript.ast.DynamicExpr;
import io.gitlab.jfronny.muscript.ast.StringExpr;
import io.gitlab.jfronny.muscript.annotations.CanThrow;
import io.gitlab.jfronny.muscript.annotations.UncheckedDynamic;
import io.gitlab.jfronny.muscript.ast.dynamic.DynamicCoerce;
import io.gitlab.jfronny.muscript.error.LocationalException;
import java.io.IOException;
import java.util.stream.Stream;
@CanThrow
@UncheckedDynamic
public class StringUnpack extends StringExpr {
public final DynamicExpr inner;
public StringUnpack(CodeLocation location, DynamicExpr inner) {
super(inner.order, location);
this.inner = inner;
}
@Override
public String get(Scope dataRoot) {
try {
Dynamic iv = inner.get(dataRoot);
if (Dynamic.isNull(iv)) throw new LocationalException(location, "Cannot unpack null");
return iv.asString().getValue();
} catch (DynamicTypeConversionException e) {
throw e.locational(location);
}
}
@Override
public StringExpr optimize() {
DynamicExpr inner = this.inner.optimize();
if (inner instanceof DynamicCoerce coerce) return coerce.inner.asStringExpr();
return new StringUnpack(location, inner);
}
@Override
public Stream<Expr<?>> extractSideEffects() {
return inner.extractSideEffects();
}
@Override
public void decompile(ExprWriter writer) throws IOException {
inner.decompile(writer);
}
@Override
public boolean equals(Object obj) {
return obj instanceof StringUnpack unpack && inner.equals(unpack.inner);
}
}

View File

@ -1,43 +0,0 @@
package io.gitlab.jfronny.muscript.ast.literal;
import io.gitlab.jfronny.muscript.ast.Expr;
import io.gitlab.jfronny.muscript.compiler.*;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.ast.BoolExpr;
import java.io.IOException;
import java.util.stream.Stream;
public final class BoolLiteral extends BoolExpr {
public final boolean value;
public BoolLiteral(CodeLocation location, boolean value) {
super(Order.Primary, location);
this.value = value;
}
@Override
public Boolean get(Scope dataRoot) {
return value;
}
@Override
public BoolExpr optimize() {
return this;
}
@Override
public Stream<Expr<?>> extractSideEffects() {
return Stream.empty();
}
@Override
public void decompile(ExprWriter writer) throws IOException {
writer.append(value ? "true" : "false");
}
@Override
public boolean equals(Object obj) {
return obj instanceof BoolLiteral literal && value == literal.value;
}
}

View File

@ -1,44 +0,0 @@
package io.gitlab.jfronny.muscript.ast.literal;
import io.gitlab.jfronny.muscript.ast.Expr;
import io.gitlab.jfronny.muscript.compiler.*;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.data.dynamic.Dynamic;
import io.gitlab.jfronny.muscript.ast.DynamicExpr;
import java.io.IOException;
import java.util.stream.Stream;
public final class DynamicLiteral<T> extends DynamicExpr {
public final Dynamic value;
public DynamicLiteral(CodeLocation location, Dynamic value) {
super(Order.Primary, location);
this.value = value;
}
@Override
public Dynamic get(Scope dataRoot) {
return value;
}
@Override
public DynamicExpr optimize() {
return this;
}
@Override
public Stream<Expr<?>> extractSideEffects() {
return Stream.empty();
}
@Override
public void decompile(ExprWriter writer) throws IOException {
value.serialize(writer);
}
@Override
public boolean equals(Object obj) {
return obj instanceof DynamicLiteral<?> literal && value.equals(literal.value);
}
}

View File

@ -1,44 +0,0 @@
package io.gitlab.jfronny.muscript.ast.literal;
import io.gitlab.jfronny.commons.StringFormatter;
import io.gitlab.jfronny.muscript.ast.Expr;
import io.gitlab.jfronny.muscript.compiler.*;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.ast.NumberExpr;
import java.io.IOException;
import java.util.stream.Stream;
public final class NumberLiteral extends NumberExpr {
public final double value;
public NumberLiteral(CodeLocation location, double value) {
super(Order.Primary, location);
this.value = value;
}
@Override
public Double get(Scope dataRoot) {
return value;
}
@Override
public NumberExpr optimize() {
return this;
}
@Override
public Stream<Expr<?>> extractSideEffects() {
return Stream.empty();
}
@Override
public void decompile(ExprWriter writer) throws IOException {
writer.append(StringFormatter.toString(value));
}
@Override
public boolean equals(Object obj) {
return obj instanceof NumberLiteral literal && value == literal.value;
}
}

View File

@ -1,43 +0,0 @@
package io.gitlab.jfronny.muscript.ast.literal;
import io.gitlab.jfronny.muscript.ast.Expr;
import io.gitlab.jfronny.muscript.ast.StringExpr;
import io.gitlab.jfronny.muscript.compiler.*;
import io.gitlab.jfronny.muscript.data.Scope;
import java.io.IOException;
import java.util.stream.Stream;
public final class StringLiteral extends StringExpr {
public final String value;
public StringLiteral(CodeLocation location, String value) {
super(Order.Primary, location);
this.value = value;
}
@Override
public String get(Scope dataRoot) {
return value;
}
@Override
public StringExpr optimize() {
return this;
}
@Override
public Stream<Expr<?>> extractSideEffects() {
return Stream.empty();
}
@Override
public void decompile(ExprWriter writer) throws IOException {
writer.append(enquote(value));
}
@Override
public boolean equals(Object obj) {
return obj instanceof StringLiteral literal && value.equals(literal.value);
}
}

View File

@ -1,54 +0,0 @@
package io.gitlab.jfronny.muscript.ast.math;
import io.gitlab.jfronny.muscript.compiler.*;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.ast.Expr;
import io.gitlab.jfronny.muscript.ast.NumberExpr;
import io.gitlab.jfronny.muscript.ast.literal.NumberLiteral;
import java.io.IOException;
import java.util.stream.Stream;
public class Divide extends NumberExpr {
public final NumberExpr dividend;
public final NumberExpr divisor;
public Divide(CodeLocation location, NumberExpr dividend, NumberExpr divisor) {
super(Order.Factor, location);
this.dividend = dividend;
this.divisor = divisor;
}
@Override
public Double get(Scope dataRoot) {
return dividend.get(dataRoot) / divisor.get(dataRoot);
}
@Override
public NumberExpr optimize() {
NumberExpr dividend = this.dividend.optimize();
NumberExpr divisor = this.divisor.optimize();
if (dividend instanceof NumberLiteral litL && divisor instanceof NumberLiteral litR)
return literal(location, litL.value / litR.value);
if (dividend instanceof Divide divide && divide.dividend instanceof NumberLiteral literal)
return new Divide(location, divide.dividend, new Multiply(divide.location, divisor, literal)).optimize();
return new Divide(location, dividend, divisor);
}
@Override
public Stream<Expr<?>> extractSideEffects() {
return Stream.concat(dividend.extractSideEffects(), divisor.extractSideEffects());
}
@Override
public void decompile(ExprWriter writer) throws IOException {
parenthesize(dividend, writer, false);
writer.append(" / ");
parenthesize(divisor, writer, true);
}
@Override
public boolean equals(Object obj) {
return obj instanceof Divide divide && dividend.equals(divide.dividend) && divisor.equals(divide.divisor);
}
}

View File

@ -1,49 +0,0 @@
package io.gitlab.jfronny.muscript.ast.math;
import io.gitlab.jfronny.muscript.compiler.*;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.ast.Expr;
import io.gitlab.jfronny.muscript.ast.NumberExpr;
import io.gitlab.jfronny.muscript.ast.literal.NumberLiteral;
import java.io.IOException;
import java.util.stream.Stream;
public class Invert extends NumberExpr {
public final NumberExpr inner;
public Invert(CodeLocation location, NumberExpr inner) {
super(Order.Unary, location);
this.inner = inner;
}
@Override
public Double get(Scope dataRoot) {
return -inner.get(dataRoot);
}
@Override
public NumberExpr optimize() {
NumberExpr inner = this.inner.optimize();
if (inner instanceof Invert invert) return invert.inner;
if (inner instanceof NumberLiteral literal) return Expr.literal(location, -literal.value);
if (inner instanceof Minus minus) return new Minus(location, minus.subtrahend, minus.minuend).optimize();
return new Invert(location, inner);
}
@Override
public Stream<Expr<?>> extractSideEffects() {
return inner.extractSideEffects();
}
@Override
public void decompile(ExprWriter writer) throws IOException {
writer.append('-');
parenthesize(inner, writer, false);
}
@Override
public boolean equals(Object obj) {
return obj instanceof Invert invert && inner.equals(invert.inner);
}
}

View File

@ -1,60 +0,0 @@
package io.gitlab.jfronny.muscript.ast.math;
import io.gitlab.jfronny.muscript.compiler.*;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.ast.Expr;
import io.gitlab.jfronny.muscript.ast.NumberExpr;
import io.gitlab.jfronny.muscript.ast.literal.NumberLiteral;
import java.io.IOException;
import java.util.stream.Stream;
public class Minus extends NumberExpr {
public final NumberExpr minuend;
public final NumberExpr subtrahend;
public Minus(CodeLocation location, NumberExpr minuend, NumberExpr subtrahend) {
super(Order.Term, location);
this.minuend = minuend;
this.subtrahend = subtrahend;
}
@Override
public Double get(Scope dataRoot) {
return minuend.get(dataRoot) - subtrahend.get(dataRoot);
}
@Override
public NumberExpr optimize() {
NumberExpr minuend = this.minuend.optimize();
NumberExpr subtrahend = this.subtrahend.optimize();
if (minuend instanceof NumberLiteral litM && subtrahend instanceof NumberLiteral litS)
return literal(location, litM.value - litS.value);
if (minuend instanceof Minus minus)
return new Minus(location, minus.minuend, new Plus(minus.location, minus.subtrahend, subtrahend)).optimize();
if (minuend instanceof Invert invM && subtrahend instanceof Invert invS)
return new Minus(location, invS.inner, invM.inner);
if (minuend instanceof Invert inv)
return new Invert(location, new Plus(location, inv.inner, subtrahend)).optimize();
if (subtrahend instanceof Invert inv)
return new Plus(location, minuend, inv.inner).optimize();
return new Minus(location, minuend, subtrahend);
}
@Override
public Stream<Expr<?>> extractSideEffects() {
return Stream.concat(minuend.extractSideEffects(), subtrahend.extractSideEffects());
}
@Override
public void decompile(ExprWriter writer) throws IOException {
parenthesize(minuend, writer, false);
writer.append(" - ");
parenthesize(subtrahend, writer, true);
}
@Override
public boolean equals(Object obj) {
return obj instanceof Minus minus && minuend.equals(minus.minuend) && subtrahend.equals(minus.subtrahend);
}
}

View File

@ -1,52 +0,0 @@
package io.gitlab.jfronny.muscript.ast.math;
import io.gitlab.jfronny.muscript.compiler.*;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.ast.Expr;
import io.gitlab.jfronny.muscript.ast.NumberExpr;
import io.gitlab.jfronny.muscript.ast.literal.NumberLiteral;
import java.io.IOException;
import java.util.stream.Stream;
public class Modulo extends NumberExpr {
private final NumberExpr dividend;
private final NumberExpr divisor;
public Modulo(CodeLocation location, NumberExpr dividend, NumberExpr divisor) {
super(Order.Factor, location);
this.dividend = dividend;
this.divisor = divisor;
}
@Override
public Double get(Scope dataRoot) {
return dividend.get(dataRoot) % divisor.get(dataRoot);
}
@Override
public NumberExpr optimize() {
NumberExpr dividend = this.dividend.optimize();
NumberExpr divisor = this.divisor.optimize();
if (dividend instanceof NumberLiteral litD && divisor instanceof NumberLiteral litS)
return Expr.literal(location, litD.value % litS.value);
return new Modulo(location, dividend, divisor);
}
@Override
public Stream<Expr<?>> extractSideEffects() {
return Stream.concat(dividend.extractSideEffects(), divisor.extractSideEffects());
}
@Override
public void decompile(ExprWriter writer) throws IOException {
parenthesize(dividend, writer, false);
writer.append(" % ");
parenthesize(divisor, writer, true);
}
@Override
public boolean equals(Object obj) {
return obj instanceof Modulo modulo && dividend.equals(modulo.dividend) && divisor.equals(modulo.divisor);
}
}

View File

@ -1,52 +0,0 @@
package io.gitlab.jfronny.muscript.ast.math;
import io.gitlab.jfronny.muscript.compiler.*;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.ast.Expr;
import io.gitlab.jfronny.muscript.ast.NumberExpr;
import io.gitlab.jfronny.muscript.ast.literal.NumberLiteral;
import java.io.IOException;
import java.util.stream.Stream;
public class Multiply extends NumberExpr {
public final NumberExpr multiplier;
public final NumberExpr multiplicand;
public Multiply(CodeLocation location, NumberExpr multiplier, NumberExpr multiplicand) {
super(Order.Factor, location);
this.multiplier = multiplier;
this.multiplicand = multiplicand;
}
@Override
public Double get(Scope dataRoot) {
return multiplier.get(dataRoot) * multiplicand.get(dataRoot);
}
@Override
public NumberExpr optimize() {
NumberExpr multiplier = this.multiplier.optimize();
NumberExpr multiplicand = this.multiplicand.optimize();
if (multiplier instanceof NumberLiteral litEr && multiplicand instanceof NumberLiteral litAnd)
return literal(location, litEr.value * litAnd.value);
return new Multiply(location, multiplier, multiplicand);
}
@Override
public Stream<Expr<?>> extractSideEffects() {
return Stream.concat(multiplier.extractSideEffects(), multiplicand.extractSideEffects());
}
@Override
public void decompile(ExprWriter writer) throws IOException {
parenthesize(multiplier, writer, false);
writer.append(" * ");
parenthesize(multiplicand, writer, true);
}
@Override
public boolean equals(Object obj) {
return obj instanceof Multiply multiply && multiplier.equals(multiply.multiplier) && multiplicand.equals(multiply.multiplicand);
}
}

View File

@ -1,56 +0,0 @@
package io.gitlab.jfronny.muscript.ast.math;
import io.gitlab.jfronny.muscript.compiler.*;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.ast.Expr;
import io.gitlab.jfronny.muscript.ast.NumberExpr;
import io.gitlab.jfronny.muscript.ast.literal.NumberLiteral;
import java.io.IOException;
import java.util.stream.Stream;
public class Plus extends NumberExpr {
public final NumberExpr augend;
public final NumberExpr addend;
public Plus(CodeLocation location, NumberExpr augend, NumberExpr addend) {
super(Order.Term, location);
this.augend = augend;
this.addend = addend;
}
@Override
public Double get(Scope dataRoot) {
return augend.get(dataRoot) + addend.get(dataRoot);
}
@Override
public NumberExpr optimize() {
NumberExpr augend = this.augend.optimize();
NumberExpr addend = this.addend.optimize();
if (augend instanceof NumberLiteral litU && addend instanceof NumberLiteral litD)
return literal(location, litU.value + litD.value);
if (augend instanceof Invert invU && addend instanceof Invert invD)
return new Invert(location, new Plus(location, invU.inner, invD.inner)).optimize();
if (augend instanceof Invert inv) return new Minus(location, addend, inv.inner).optimize();
if (addend instanceof Invert inv) return new Minus(location, augend, inv.inner).optimize();
return new Plus(location, augend, addend);
}
@Override
public Stream<Expr<?>> extractSideEffects() {
return Stream.concat(augend.extractSideEffects(), addend.extractSideEffects());
}
@Override
public void decompile(ExprWriter writer) throws IOException {
parenthesize(augend, writer, false);
writer.append(" + ");
parenthesize(addend, writer, true);
}
@Override
public boolean equals(Object obj) {
return obj instanceof Plus plus && augend.equals(plus.augend) && addend.equals(plus.addend);
}
}

View File

@ -1,62 +0,0 @@
package io.gitlab.jfronny.muscript.ast.math;
import io.gitlab.jfronny.muscript.compiler.*;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.ast.Expr;
import io.gitlab.jfronny.muscript.ast.NumberExpr;
import io.gitlab.jfronny.muscript.ast.literal.NumberLiteral;
import java.io.IOException;
import java.util.stream.Stream;
public class Power extends NumberExpr {
private final NumberExpr base;
private final NumberExpr exponent;
public Power(CodeLocation location, NumberExpr base, NumberExpr exponent) {
super(Order.Exp, location);
this.base = base;
this.exponent = exponent;
}
@Override
public Double get(Scope dataRoot) {
return Math.pow(base.get(dataRoot), exponent.get(dataRoot));
}
@Override
public NumberExpr optimize() {
NumberExpr base = this.base.optimize();
NumberExpr exponent = this.exponent.optimize();
if (base instanceof NumberLiteral litB && exponent instanceof NumberLiteral litE)
return literal(location, Math.pow(litB.value, litE.value));
if (exponent instanceof NumberLiteral exp && base instanceof Multiply multiply) {
if (multiply.multiplier instanceof NumberLiteral literal)
return new Multiply(multiply.location,
literal(location, Math.pow(literal.value, exp.value)),
new Power(location, multiply.multiplicand, exp));
if (multiply.multiplicand instanceof NumberLiteral literal)
return new Multiply(multiply.location,
literal(location, Math.pow(literal.value, exp.value)),
new Power(location, multiply.multiplier, exp));
}
return new Power(location, base, exponent);
}
@Override
public Stream<Expr<?>> extractSideEffects() {
return Stream.concat(base.extractSideEffects(), exponent.extractSideEffects());
}
@Override
public void decompile(ExprWriter writer) throws IOException {
parenthesize(base, writer, false);
writer.append(" ^ ");
parenthesize(exponent, writer, true);
}
@Override
public boolean equals(Object obj) {
return obj instanceof Power power && base.equals(power.base) && exponent.equals(power.exponent);
}
}

View File

@ -1,58 +0,0 @@
package io.gitlab.jfronny.muscript.ast.string;
import io.gitlab.jfronny.muscript.compiler.*;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.ast.Expr;
import io.gitlab.jfronny.muscript.ast.StringExpr;
import io.gitlab.jfronny.muscript.ast.literal.StringLiteral;
import java.io.IOException;
import java.util.stream.Stream;
public class Concatenate extends StringExpr {
public final StringExpr left;
public final StringExpr right;
public Concatenate(CodeLocation location, StringExpr left, StringExpr right) {
super(Order.Concat, location);
this.left = left;
this.right = right;
}
@Override
public String get(Scope dataRoot) {
return left.get(dataRoot) + right.get(dataRoot);
}
@Override
public StringExpr optimize() {
StringExpr left = this.left.optimize();
StringExpr right = this.right.optimize();
if (left instanceof StringLiteral litL && right instanceof StringLiteral litR)
return literal(location, litL.value + litR.value);
if (right instanceof StringLiteral litR && left instanceof Concatenate concatenate && concatenate.right instanceof StringLiteral litL) {
return new Concatenate(location, concatenate.left, literal(concatenate.location, litL.value + litR.value));
}
if (left instanceof StringLiteral litL && right instanceof Concatenate concatenate && concatenate.left instanceof StringLiteral litR) {
return new Concatenate(location, literal(concatenate.location, litL.value + litR.value), concatenate.right);
}
return new Concatenate(location, left, right);
}
@Override
public Stream<Expr<?>> extractSideEffects() {
return Stream.concat(left.extractSideEffects(), right.extractSideEffects());
}
@Override
public void decompile(ExprWriter writer) throws IOException {
parenthesize(left, writer, false);
writer.append(" || ");
parenthesize(right, writer, true);
}
@Override
public boolean equals(Object obj) {
return obj instanceof Concatenate concatenate && left.equals(concatenate.left) && right.equals(concatenate.right);
}
}

View File

@ -1,51 +0,0 @@
package io.gitlab.jfronny.muscript.ast.string;
import io.gitlab.jfronny.commons.StringFormatter;
import io.gitlab.jfronny.muscript.compiler.CodeLocation;
import io.gitlab.jfronny.muscript.compiler.ExprWriter;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.ast.*;
import io.gitlab.jfronny.muscript.ast.literal.*;
import java.io.IOException;
import java.util.stream.Stream;
public class StringCoerce extends StringExpr {
private final Expr<?> inner;
public StringCoerce(CodeLocation location, Expr<?> inner) {
super(inner.order, location);
this.inner = inner;
}
@Override
public String get(Scope dataRoot) {
return StringFormatter.toString(inner.get(dataRoot));
}
@Override
public StringExpr optimize() {
Expr<?> inner = this.inner.optimize();
if (inner instanceof NullLiteral) return literal(location, "null");
if (inner instanceof BoolLiteral expr) return literal(location, StringFormatter.toString(expr.value));
if (inner instanceof DynamicLiteral<?> expr) return literal(location, StringFormatter.toString(expr.value));
if (inner instanceof NumberLiteral expr) return literal(location, StringFormatter.toStringPrecise(expr.value));
if (inner instanceof StringExpr expr) return expr;
return new StringCoerce(location, inner);
}
@Override
public Stream<Expr<?>> extractSideEffects() {
return inner.extractSideEffects();
}
@Override
public void decompile(ExprWriter writer) throws IOException {
inner.decompile(writer);
}
@Override
public boolean equals(Object obj) {
return obj instanceof StringCoerce coerce && inner.equals(coerce.inner);
}
}

View File

@ -1,25 +0,0 @@
package io.gitlab.jfronny.muscript.compiler;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
public record CodeLocation(int chStart, int chEnd, @Nullable String source, @Nullable String file) {
public static final CodeLocation NONE = new CodeLocation(-1, -1);
public CodeLocation(int chStart, int chEnd) {
this(chStart, chEnd, null, null);
}
public CodeLocation(int ch) {
this(ch, ch);
}
public CodeLocation withSource(String source) {
return new CodeLocation(chStart, chEnd, source, file);
}
public CodeLocation withFile(String file) {
return new CodeLocation(chStart, chEnd, source, file);
}
}

View File

@ -1,43 +0,0 @@
package io.gitlab.jfronny.muscript.compiler;
import java.io.IOException;
import java.util.Arrays;
import java.util.stream.Collectors;
public abstract class Decompilable {
public final Order order;
protected Decompilable(Order order) {
this.order = order;
}
public abstract void decompile(ExprWriter writer) throws IOException;
protected void parenthesize(Decompilable val, ExprWriter writer, boolean parenEqualOrder) throws IOException {
boolean wrap = !parenEqualOrder ? val.order.ordinal() < this.order.ordinal() : val.order.ordinal() <= this.order.ordinal();
if (wrap) writer.append('(');
val.decompile(writer);
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(" || \"'\" || "));
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
try (ExprWriter ew = new ExprWriter(sb, false)) {
decompile(ew);
} catch (IOException e) {
throw new RuntimeException("Could not decompile", e);
}
return sb.toString();
}
}

View File

@ -1,66 +0,0 @@
package io.gitlab.jfronny.muscript.compiler;
import java.io.Closeable;
import java.io.IOException;
public class ExprWriter implements Appendable, Closeable {
private final Appendable target;
private final boolean compact;
private int indent = 0;
public ExprWriter(Appendable target, boolean compact) {
this.target = target;
this.compact = compact;
}
@Override
public ExprWriter append(CharSequence csq) throws IOException {
target.append(csq.toString().replace("\r", "").replace("\n", compact ? " " : "\n" + indent()));
return this;
}
@Override
public ExprWriter append(CharSequence csq, int start, int end) throws IOException {
return append(csq.subSequence(start, end));
}
@Override
public ExprWriter append(char c) throws IOException {
switch (c) {
case '\r' -> {}
case '\n' -> {
if (compact) target.append(" ");
else target.append("\n").append(indent());
}
default -> target.append(c);
}
return this;
}
public ExprWriter appendLiteral(String s) throws IOException {
if (!Lexer.isValidId(s)) {
if (s.contains("`")) throw new IllegalArgumentException("Not a valid literal: " + s);
else return append('`').append(s).append('`');
} else return append(s);
}
private String indent() {
return " ".repeat(indent);
}
public ExprWriter increaseIndent() {
indent += 2;
return this;
}
public ExprWriter decreaseIndent() {
if (indent <= 1) throw new IllegalStateException("Attempted to decrease indent lower than 0");
indent -= 2;
return this;
}
@Override
public void close() {
if (indent != 0) throw new IllegalStateException("Attempted to close ExprWriter before end");
}
}

View File

@ -1,273 +0,0 @@
package io.gitlab.jfronny.muscript.compiler;
import java.util.Objects;
import java.util.Set;
import java.util.regex.Pattern;
/**
* The lexer for muScript, heavily inspired by starscript
* @deprecated To be moved into the lexer module and refactored
*/
@Deprecated(forRemoval = true)
public class Lexer extends VersionedComponent {
public final String file;
/**
* The type of the token
*/
public Token token;
/**
* The string representation of the token
*/
public String lexeme;
public char ch;
public final String source;
public int start, current;
public boolean passedNewline = false;
public Lexer(MuScriptVersion version, String source) {
this(version, source, null);
}
public Lexer(MuScriptVersion version, String source, String file) {
super(version);
this.source = Objects.requireNonNull(source);
this.file = file;
}
public CodeLocation location() {
return new CodeLocation(start, current - 1, source, file);
}
/**
* Scans for the next token storing it in {@link Lexer#token} and {@link Lexer#lexeme}. Produces {@link Token#EOF} if the end of source code has been reached and {@link Token#Error} if there has been an error
*/
public void next() {
start = current;
passedNewline = false;
if (isAtEnd()) {
createToken(Token.EOF);
return;
}
// Scan expression
skipWhitespaceAndComments();
if (isAtEnd()) {
createToken(Token.EOF);
return;
}
char c = advance();
if (isDigit(c)) number();
else if (isIdentifier(c)) identifier(false);
else {
switch (c) {
case '\'', '"' -> string(c);
case '`' -> identifier(true);
case '=' -> {
if (match('=')) createToken(Token.EqualEqual);
else createToken(Token.Assign);
}
case '!' -> createToken(match('=') ? Token.BangEqual : Token.Bang);
case '+' -> createToken(Token.Plus);
case '-' -> {
if (match('>')) createToken(Token.Arrow);
else createToken(Token.Minus);
}
case '*' -> createToken(Token.Star);
case '/' -> createToken(Token.Slash);
case '%' -> createToken(Token.Percentage);
case '>' -> createToken(match('=') ? Token.GreaterEqual : Token.Greater);
case '<' -> createToken(match('=') ? Token.LessEqual : Token.Less);
case '&' -> createToken(Token.And);
case '|' -> createToken(match('|') ? Token.Concat : Token.Or);
case '^' -> createToken(Token.UpArrow);
case '.' -> {
if (match('.')) {
if (match('.')) createToken(Token.Ellipsis);
else createToken(Token.Error, "Unexpected '..', did you mean '...'?");
} else createToken(Token.Dot);
}
case ',' -> createToken(Token.Comma);
case '?' -> createToken(Token.QuestionMark);
case ':' -> createToken(match(':') ? Token.DoubleColon : Token.Colon);
case '(' -> createToken(Token.LeftParen);
case ')' -> createToken(Token.RightParen);
case '[' -> createToken(Token.LeftBracket);
case ']' -> createToken(Token.RightBracket);
case '{' -> createToken(Token.LeftBrace);
case '}' -> createToken(Token.RightBrace);
case ';' -> {
createToken(Token.Semicolon);
passedNewline = true;
}
default -> unexpected();
}
}
}
private void string(char stringChar) {
while (!isAtEnd() && peek() != stringChar) {
advance();
}
if (isAtEnd()) {
createToken(Token.Error, "Unterminated expression");
} else {
advance();
createToken(Token.String, source.substring(start + 1, current - 1));
}
}
private void number() {
while (isDigit(peek())) advance();
if (peek() == '.' && isDigit(peekNext())) {
advance();
while (isDigit(peek())) advance();
}
createToken(Token.Number);
}
private void identifier(boolean explicit) {
if (explicit) {
while (!isAtEnd() && peek() != '`') {
advance();
}
if (isAtEnd()) {
createToken(Token.Error, "Unterminated expression");
} else {
advance();
createToken(Token.Identifier, source.substring(start + 1, current - 1));
}
} else {
while (!isAtEnd()) {
char c = peek();
if (isIdentifier(c) || isDigit(c)) {
advance();
} else break;
}
createToken(Token.Identifier);
switch (lexeme) {
case "null" -> token = Token.Null;
case "true" -> token = Token.True;
case "false" -> token = Token.False;
}
}
}
private void skipWhitespaceAndComments() {
while (true) {
if (isAtEnd()) return;
switch (peek()) {
case ' ', '\t' -> advance();
case '\r', '\n' -> {
advance();
passedNewline = true;
}
case '/' -> {
switch (peekNext()) {
case '/' -> {
while (!isAtEnd() && peek() != '\r' && peek() != '\n') advance();
}
case '*' -> {
advance();
advance();
while (!isAtEnd() && (peek() != '*' || peekNext() != '/')) advance();
if (!isAtEnd()) {
advance();
advance();
}
}
default -> {
start = current;
return;
}
}
}
default -> {
start = current;
return;
}
}
}
}
// Helpers
private void unexpected() {
createToken(Token.Error, "Unexpected character");
}
private void createToken(Token token, String lexeme) {
this.token = token;
this.lexeme = lexeme;
}
private void createToken(Token token) {
createToken(token, source.substring(start, current));
}
private boolean match(char expected) {
if (isAtEnd()) return false;
if (source.charAt(current) != expected) return false;
advance();
return true;
}
private char advance() {
return ch = source.charAt(current++);
}
private char peek() {
if (isAtEnd()) return '\0';
return source.charAt(current);
}
private char peekNext() {
if (current + 1 >= source.length()) return '\0';
return source.charAt(current + 1);
}
private boolean isAtEnd() {
return current >= source.length();
}
private boolean isDigit(char c) {
return c >= '0' && c <= '9';
}
private boolean isIdentifier(char c) {
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' || c == '$';
}
public static boolean isValidId(String id) {
return IDENTIFIER.matcher(id).matches() && !RESERVED_IDS.contains(id);
}
public static final Set<String> RESERVED_IDS = Set.of("null", "true", "false");
public static final Pattern IDENTIFIER = Pattern.compile("[a-zA-Z_$][a-zA-Z_$0-9]*");
// Visualization
@Override
public String toString() {
return source.substring(0, start) + '[' + source.substring(start, current) + ']' + source.substring(current);
}
}

View File

@ -1,11 +0,0 @@
package io.gitlab.jfronny.muscript.compiler;
public enum MuScriptVersion {
V1, V2, V3;
public static final MuScriptVersion DEFAULT = V3;
public boolean contains(MuScriptVersion version) {
return compareTo(version) >= 0;
}
}

View File

@ -1,17 +0,0 @@
package io.gitlab.jfronny.muscript.compiler;
public enum Order {
Script,
Conditional,
And,
Or,
Equality,
Concat,
Comparison,
Term,
Factor,
Exp,
Unary,
Call,
Primary;
}

View File

@ -1,508 +0,0 @@
package io.gitlab.jfronny.muscript.compiler;
import io.gitlab.jfronny.muscript.ast.*;
import io.gitlab.jfronny.muscript.ast.bool.*;
import io.gitlab.jfronny.muscript.ast.compare.Equal;
import io.gitlab.jfronny.muscript.ast.compare.Greater;
import io.gitlab.jfronny.muscript.ast.conditional.UnresolvedConditional;
import io.gitlab.jfronny.muscript.ast.dynamic.*;
import io.gitlab.jfronny.muscript.ast.dynamic.assign.DynamicAssign;
import io.gitlab.jfronny.muscript.ast.literal.DynamicLiteral;
import io.gitlab.jfronny.muscript.ast.math.*;
import io.gitlab.jfronny.muscript.ast.string.Concatenate;
import io.gitlab.jfronny.muscript.compiler.lexer.LegacyLexer;
import io.gitlab.jfronny.muscript.compiler.lexer.Lexer;
import io.gitlab.jfronny.muscript.data.Script;
import io.gitlab.jfronny.muscript.data.dynamic.additional.DFinal;
import io.gitlab.jfronny.muscript.error.*;
import org.jetbrains.annotations.Nullable;
import java.util.*;
public class Parser extends VersionedComponent {
private final Lexer lexer;
private Lexer.Token previous = null;
public static Expr<?> parse(MuScriptVersion version, String source) {
return parse(version, source, null);
}
public static Expr<?> parse(MuScriptVersion version, String source, String file) {
return new Parser(new LegacyLexer(version, source, file)).parse().optimize();
}
public static Script parseScript(MuScriptVersion version, String source) {
return parseScript(version, source, null);
}
public static Script parseScript(MuScriptVersion version, String source, String file) {
return new Parser(new LegacyLexer(version, source, file)).parseScript().optimize();
}
public static Script parseMultiScript(MuScriptVersion version, String startFile, SourceFS filesystem) {
return new Script(parseMultiScript(version, startFile, filesystem, new HashSet<>()).stream().flatMap(Script::stream).toList());
}
private static List<Script> parseMultiScript(MuScriptVersion version, String startFile, SourceFS filesystem, Set<String> alreadyIncluded) {
alreadyIncluded.add(startFile);
boolean isIncludes = true;
StringBuilder src = new StringBuilder();
List<Script> includes = new LinkedList<>();
int row = 0;
final String includePrefix = "#include ";
for (String s : filesystem.read(startFile).split("\n")) {
row++;
if (s.isBlank()) {
src.append("\n");
} else if (s.startsWith(includePrefix)) {
if (isIncludes) {
String file = s.substring(includePrefix.length());
src.append("// include ").append(file).append("\n");
if (!alreadyIncluded.contains(file)) {
includes.addAll(parseMultiScript(version, file, filesystem, alreadyIncluded));
}
} else {
throw new ParseException(PrettyPrintError.builder()
.setLocation(new PrettyPrintError.Location(s, 0, row), new PrettyPrintError.Location(s, s.length() - 1, row))
.setMessage("Includes MUST be located at the top of the file")
.build());
}
} else {
isIncludes = false;
src.append(s).append("\n");
}
}
includes.add(parseScript(version, src.toString(), startFile));
return includes;
}
public Parser(io.gitlab.jfronny.muscript.compiler.Lexer lexer) {
this(new LegacyLexer(lexer));
}
public Parser(Lexer lexer) {
super(lexer.version());
this.lexer = lexer;
}
/**
* Generate a single expression.
* Must exhaust the source!
*
* @return the resulting expression
*/
public Expr<?> parse() {
advance();
Expr<?> expr = expression();
if (!isAtEnd() && version.contains(MuScriptVersion.V2))
throw new ParseException(PrettyPrintError.builder(lexer.location()).setMessage("Unexpected element after end of expression").build());
return expr;
}
/**
* Generate a script instance.
* Multiple instructions will be executed in sequence and the result of the last one will be returned.
*
* @return the resulting expression
*/
public Script parseScript() {
advance();
List<Expr<?>> expressions = new LinkedList<>();
while (!isAtEnd()) {
expressions.add(expression());
// Consume semicolon if present
if (!lexer.wasNewlinePassed() & !match(Token.Semicolon) & !isAtEnd() & version.contains(MuScriptVersion.V3)) {
throw error("Either a semicolon or a new line must separate expressions in scripts");
}
}
if (expressions.isEmpty()) throw new ParseException(PrettyPrintError.builder(lexer.location()).setMessage("Missing any elements in closure").build());
return new Script(expressions);
}
// Expressions
private Expr<?> expression() {
try {
return conditional();
} catch (RuntimeException e) {
if (e instanceof ParseException) throw e;
else if (e instanceof LocationalException le) {
throw new ParseException(le.asPrintable(), le.getCause());
} else throw error(e.getMessage());
}
}
private Expr<?> conditional() {
Expr<?> expr = and();
if (match(Token.QuestionMark)) {
CodeLocation location = previous.location();
Expr<?> trueExpr = expression();
consume(Token.Colon, "Expected ':' after first part of condition");
Expr<?> falseExpr = expression();
expr = new UnresolvedConditional(location, asBool(expr), trueExpr, falseExpr);
}
return expr;
}
private Expr<?> and() {
Expr<?> expr = or();
while (match(Token.And)) {
CodeLocation location = previous.location();
Expr<?> right = or();
expr = new And(location, asBool(expr), asBool(right));
}
return expr;
}
private Expr<?> or() {
Expr<?> expr = equality();
while (match(Token.Or)) {
CodeLocation location = previous.location();
Expr<?> right = equality();
expr = new Or(location, asBool(expr), asBool(right));
}
return expr;
}
private Expr<?> equality() {
Expr<?> expr = concat();
while (match(Token.EqualEqual, Token.BangEqual)) {
Token op = previous.token();
CodeLocation location = previous.location();
Expr<?> right = concat();
BoolExpr e = new Equal(location, expr, right);
if (op == Token.BangEqual) e = new Not(location, e);
expr = e;
}
return expr;
}
private Expr<?> concat() {
Expr<?> expr = comparison();
while (match(Token.Concat)) {
CodeLocation location = previous.location();
Expr<?> right = comparison();
expr = new Concatenate(location, asString(expr), asString(right));
}
return expr;
}
private Expr<?> comparison() {
Expr<?> expr = term();
while (match(Token.Greater, Token.GreaterEqual, Token.Less, Token.LessEqual)) {
Token op = previous.token();
CodeLocation location = previous.location();
NumberExpr right = asNumber(term());
expr = switch (op) {
case Greater -> new Greater(location, asNumber(expr), right);
case GreaterEqual -> new Not(location, new Greater(location, right, asNumber(expr)));
case Less -> new Greater(location, right, asNumber(expr));
case LessEqual -> new Not(location, new Greater(location, asNumber(expr), right));
default -> throw new IllegalStateException();
};
}
return expr;
}
private Expr<?> term() {
Expr<?> expr = factor();
while (match(Token.Plus, Token.Minus)) {
Token op = previous.token();
CodeLocation location = previous.location();
NumberExpr right = asNumber(factor());
expr = switch (op) {
case Plus -> new Plus(location, asNumber(expr), right);
case Minus -> new Minus(location, asNumber(expr), right);
default -> throw new IllegalStateException();
};
}
return expr;
}
private Expr<?> factor() {
Expr<?> expr = exp();
while (match(Token.Star, Token.Slash, Token.Percentage)) {
Token op = previous.token();
CodeLocation location = previous.location();
NumberExpr right = asNumber(exp());
expr = switch (op) {
case Star -> new Multiply(location, asNumber(expr), right);
case Slash -> new Divide(location, asNumber(expr), right);
case Percentage -> new Modulo(location, asNumber(expr), right);
default -> throw new IllegalStateException();
};
}
return expr;
}
private Expr<?> exp() {
Expr<?> expr = unary();
while (match(Token.UpArrow)) {
CodeLocation location = previous.location();
NumberExpr right = asNumber(unary());
expr = new Power(location, asNumber(expr), right);
}
return expr;
}
private Expr<?> unary() {
if (match(Token.Bang, Token.Minus)) {
Token op = previous.token();
CodeLocation location = previous.location();
Expr<?> right = unary();
return switch (op) {
case Bang -> new Not(location, asBool(right));
case Minus -> new Invert(location, asNumber(right));
default -> throw new IllegalStateException();
};
}
return call();
}
private Expr<?> call() {
Expr<?> expr = primary();
while (match(Token.LeftParen, Token.Dot, Token.LeftBracket, Token.DoubleColon)) {
CodeLocation location = previous.location();
expr = switch (previous.token()) {
case LeftParen -> finishCall(location, expr);
case Dot -> {
Lexer.Token name = consume(Token.Identifier, "Expected field name after '.'");
yield new Get(location, asDynamic(expr), Expr.literal(name.location(), name.lexeme()));
}
case DoubleColon -> {
DynamicExpr callable;
if (match(Token.Identifier)) {
callable = new Variable(previous.location(), previous.lexeme());
} else if (match(Token.LeftParen)) {
callable = expression().asDynamicExpr();
consume(Token.RightParen, "Expected ')' after expression");
} else throw error("Bind operator requires right side to be a literal identifier or to be wrapped in parentheses.");
yield new Bind(location, callable, expr.asDynamicExpr());
}
case LeftBracket -> {
expr = new Get(location, asDynamic(expr), expression());
consume(Token.RightBracket, "Expected closing bracket");
yield expr;
}
default -> throw new IllegalStateException();
};
}
return expr;
}
private Expr<?> finishCall(CodeLocation location, Expr<?> callee) {
List<Call.Arg> args = new ArrayList<>(2);
if (!check(Token.RightParen)) {
do {
args.add(new Call.Arg(asDynamic(expression()), match(Token.Ellipsis)));
} while (match(Token.Comma));
}
consume(Token.RightParen, "Expected ')' after function arguments");
return new Call(location, asDynamic(callee), args);
}
private Expr<?> primary() {
if (match(Token.Null)) return Expr.literalNull(previous.location());
if (match(Token.String)) return Expr.literal(previous.location(), previous.lexeme());
if (match(Token.True, Token.False)) return Expr.literal(previous.location(), previous.lexeme().equals("true"));
if (match(Token.Number)) return Expr.literal(previous.location(), Double.parseDouble(previous.lexeme()));
if (match(Token.Identifier)) {
CodeLocation location = previous.location();
String name = previous.lexeme();
if (match(Token.Assign)) return new DynamicAssign(location, name, expression().asDynamicExpr());
else return new Variable(location, name);
}
if (match(Token.LeftParen)) {
Expr<?> expr = expression();
consume(Token.RightParen, "Expected ')' after expression");
return expr;
}
if (match(Token.LeftBrace)) {
int start = previous.start();
if (match(Token.Arrow)) return finishClosure(start, null, false);
if (match(Token.RightBrace)) return new DynamicLiteral<>(location(start, previous.start()), DFinal.of(Map.of()));
consume(Token.Identifier, "Expected arrow or identifier as first element in closure or object");
String first = previous.lexeme();
if (check(Token.Arrow)) return finishClosure(start, first, false);
if (match(Token.Ellipsis)) return finishClosure(start, first, true);
if (check(Token.Comma)) return finishClosure(start, first, false);
if (match(Token.Assign)) {
return finishObject(start, first, expression().asDynamicExpr());
}
throw error("Unexpected");
}
throw error("Expected expression.");
}
private Expr<?> finishClosure(int start, @Nullable String firstArg, boolean firstVariadic) {
List<String> boundArgs = new LinkedList<>();
boolean variadic = false;
if (firstArg != null) {
boundArgs.add(firstArg);
if (firstVariadic) {
consume(Token.Arrow, "Variadic argument MUST be the last argument");
variadic = true;
} else {
while (!match(Token.Arrow)) {
consume(Token.Comma, "Closure parameters MUST be comma-seperated");
consume(Token.Identifier, "Closure arguments MUST be identifiers");
boundArgs.add(previous.lexeme());
if (match(Token.Ellipsis)) {
variadic = true;
consume(Token.Arrow, "Variadic argument MUST be the last argument");
break;
}
}
}
}
List<Expr<?>> expressions = new LinkedList<>();
while (!match(Token.RightBrace)) {
expressions.add(expression());
// Consume semicolon if present
if (!lexer.wasNewlinePassed() & !match(Token.Semicolon) & version.contains(MuScriptVersion.V3)) {
if (match(Token.RightBrace)) break;
throw error("Either a semicolon or a new line must separate expressions in closures");
}
}
int end = previous.start();
return new Closure(location(start, end), boundArgs, expressions, variadic);
}
private Expr<?> finishObject(int start, @Nullable String firstArg, @Nullable DynamicExpr firstValue) {
Map<String, DynamicExpr> content = new LinkedHashMap<>();
content.put(firstArg, firstValue);
while (match(Token.Comma)) {
consume(Token.Identifier, "Object element MUST start with an identifier");
String name = previous.lexeme();
consume(Token.Assign, "Object element name and value MUST be seperated with '='");
content.put(name, expression().asDynamicExpr());
}
consume(Token.RightBrace, "Expected end of object");
return new ObjectLiteral(location(start, previous.start()), content);
}
// Type conversion
private BoolExpr asBool(Expr<?> expression) {
try {
return expression.asBoolExpr();
} catch (TypeMismatchException e) {
throw error(e.getMessage(), expression);
}
}
private NumberExpr asNumber(Expr<?> expression) {
try {
return expression.asNumberExpr();
} catch (TypeMismatchException e) {
throw error(e.getMessage(), expression);
}
}
private StringExpr asString(Expr<?> expression) {
try {
return expression.asStringExpr();
} catch (TypeMismatchException e) {
throw error(e.getMessage(), expression);
}
}
private DynamicExpr asDynamic(Expr<?> expression) {
try {
return expression.asDynamicExpr();
} catch (TypeMismatchException e) {
throw error(e.getMessage(), expression);
}
}
// Helpers
private CodeLocation location(int chStart, int chEnd) {
return new CodeLocation(chStart, chEnd, lexer.getSource(), lexer.getFile());
}
private ParseException error(String message) {
int loc = lexer.getPrevious().current() - 1;
return new ParseException(PrettyPrintError.builder(location(loc, loc)).setMessage(message).build());
}
private ParseException error(String message, Expr<?> expr) {
return new ParseException(PrettyPrintError.builder(expr.location).setMessage(message).build());
}
private Lexer.Token consume(Token token, String message) {
if (check(token)) return advance();
throw error(message + " but got " + lexer.getPrevious().token());
}
private boolean match(Token... tokens) {
for (Token token : tokens) {
if (check(token)) {
advance();
return true;
}
}
return false;
}
private boolean check(Token token) {
if (isAtEnd()) return false;
return lexer.getPrevious().token() == token;
}
private Lexer.Token advance() {
previous = lexer.getPrevious();
lexer.advance();
if (lexer.getPrevious().token() == Token.Error) {
throw error(lexer.getPrevious().lexeme());
}
return previous;
}
private boolean isAtEnd() {
return lexer.getPrevious().token() == Token.EOF;
}
// Parse Exception
public static class ParseException extends RuntimeException {
public final PrettyPrintError error;
public ParseException(PrettyPrintError error) {
super(error.toString());
this.error = error;
}
public ParseException(PrettyPrintError error, Throwable cause) {
super(error.toString(), cause);
this.error = error;
}
}
}

View File

@ -1,14 +0,0 @@
package io.gitlab.jfronny.muscript.compiler;
import java.util.Map;
public interface SourceFS {
String read(String file);
record Immutable(Map<String, String> files) implements SourceFS {
@Override
public String read(String file) {
return files.get(file);
}
}
}

View File

@ -1,35 +0,0 @@
package io.gitlab.jfronny.muscript.compiler;
public enum Token {
String, Identifier, Number,
Null,
True, False,
And, Or,
Assign,
EqualEqual, BangEqual,
Concat,
Greater, GreaterEqual,
Less, LessEqual,
Plus, Minus,
Star, Slash, Percentage, UpArrow,
Bang,
Dot, Ellipsis, Comma,
QuestionMark, Colon,
LeftParen, RightParen,
LeftBracket, RightBracket,
LeftBrace, RightBrace,
Arrow,
Semicolon,
DoubleColon,
Error, EOF
}

View File

@ -1,5 +0,0 @@
package io.gitlab.jfronny.muscript.compiler;
public enum Type {
String, Number, Boolean, Dynamic
}

View File

@ -1,15 +0,0 @@
package io.gitlab.jfronny.muscript.compiler;
import java.util.Objects;
public abstract class VersionedComponent {
protected final MuScriptVersion version;
public VersionedComponent(MuScriptVersion version) {
this.version = Objects.requireNonNull(version);
}
public MuScriptVersion getComponentVersion() {
return version;
}
}

View File

@ -1,67 +0,0 @@
package io.gitlab.jfronny.muscript.compiler.lexer;
import io.gitlab.jfronny.muscript.compiler.*;
import org.jetbrains.annotations.Nullable;
/**
* Wraps the old Lexer implementation in the new Lexer interface for compatibility
*/
public class LegacyLexer implements Lexer {
private final io.gitlab.jfronny.muscript.compiler.Lexer backend;
private Lexer.Token previous = null;
public LegacyLexer(MuScriptVersion version, String source) {
this(new io.gitlab.jfronny.muscript.compiler.Lexer(version, source));
}
public LegacyLexer(MuScriptVersion version, String source, String file) {
this(new io.gitlab.jfronny.muscript.compiler.Lexer(version, source, file));
}
public LegacyLexer(io.gitlab.jfronny.muscript.compiler.Lexer backend) {
this.backend = backend;
}
@Override
public CodeLocation location() {
return backend.location();
}
@Override
public MuScriptVersion version() {
return backend.getComponentVersion();
}
@Override
public boolean wasNewlinePassed() {
return backend.passedNewline;
}
@Override
public Token getPrevious() {
return previous;
}
@Override
public Token advance() {
backend.next();
return previous = new Token(
backend.lexeme,
backend.token,
backend.start,
backend.current,
backend.ch,
new CodeLocation(backend.start, backend.current - 1, backend.source, backend.file)
);
}
@Override
public @Nullable String getSource() {
return backend.source;
}
@Override
public @Nullable String getFile() {
return backend.file;
}
}

View File

@ -1,22 +0,0 @@
package io.gitlab.jfronny.muscript.compiler.lexer;
import io.gitlab.jfronny.muscript.compiler.*;
import org.jetbrains.annotations.Nullable;
public interface Lexer {
CodeLocation location();
MuScriptVersion version();
boolean wasNewlinePassed();
Token getPrevious();
Token advance();
@Nullable String getSource();
@Nullable String getFile();
record Token(String lexeme, io.gitlab.jfronny.muscript.compiler.Token token, int start, int current, char ch, CodeLocation location) {
@Override
public String toString() {
return token + " '" + lexeme + "'";
}
}
}

View File

@ -1,115 +0,0 @@
package io.gitlab.jfronny.muscript.data;
import io.gitlab.jfronny.commons.data.ImmCollection;
import io.gitlab.jfronny.muscript.data.dynamic.*;
import io.gitlab.jfronny.muscript.data.dynamic.additional.DFinal;
import io.gitlab.jfronny.muscript.data.dynamic.type.*;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.function.Function;
public class Scope implements DObject {
private final @Nullable Scope source;
private final Map<String, Dynamic> override = new HashMap<>();
public Scope() {
this(null);
}
public Scope(@Nullable DObject source) {
if (source == null) {
this.source = null;
} else if (source instanceof Scope src) {
this.source = src;
} else {
this.source = new Scope();
source.getValue().forEach(this::set);
}
}
public Scope(@Nullable Scope source) {
this.source = source;
}
@Override
public Map<String, Dynamic> getValue() {
if (source == null) return ImmCollection.of(override);
var map = new HashMap<>(source.getValue());
map.putAll(override);
return ImmCollection.of(map);
}
/**
* @deprecated Using the convenience methods is recommended wherever possible.
*/
@Deprecated(forRemoval = false)
public Scope set(String key, Dynamic value) {
if (key.startsWith("$")) {
if (!setGlobal(key, value)) override.put(key, value);
} else {
override.put(key, value);
}
return this;
}
private boolean setGlobal(String key, Dynamic value) {
if (override.containsKey(key)) {
override.put(key, value);
return true;
} else if (source != null) {
return source.setGlobal(key, value);
} else {
return false;
}
}
public Scope set(String key, boolean value) {
return set(key, DFinal.of(value));
}
public Scope set(String key, double value) {
return set(key, DFinal.of(value));
}
public Scope set(String key, String value) {
return set(key, DFinal.of(value));
}
public Scope set(String key, Map<String, ? extends Dynamic> value) {
return set(key, DFinal.of(value));
}
public Scope set(String key, DTypeObject signature, Map<String, ? extends Dynamic> value) {
return set(key, DFinal.of(signature, value));
}
public Scope set(String key, List<? extends Dynamic> value) {
return set(key, DFinal.of(value));
}
public Scope set(String key, DTypeList signature, List<? extends Dynamic> value) {
return set(key, DFinal.of(signature, value));
}
public Scope set(String key, Function<DList, ? extends Dynamic> value) {
return set(key, DFinal.of(value, () -> key, key));
}
public Scope set(String key, DType signature, Function<DList, ? extends Dynamic> value) {
return set(key, DFinal.of(signature, value, () -> key, key));
}
public Scope fork() {
return new Scope(this);
}
public DObject getOverrides() {
return DFinal.of(override);
}
@Override
public String toString() {
return Dynamic.serialize(this);
}
}

View File

@ -1,73 +0,0 @@
package io.gitlab.jfronny.muscript.data;
import io.gitlab.jfronny.muscript.ast.DynamicExpr;
import io.gitlab.jfronny.muscript.ast.Expr;
import io.gitlab.jfronny.muscript.ast.dynamic.Call;
import io.gitlab.jfronny.muscript.ast.dynamic.Closure;
import io.gitlab.jfronny.muscript.ast.dynamic.ExprGroup;
import io.gitlab.jfronny.muscript.compiler.*;
import io.gitlab.jfronny.muscript.data.dynamic.*;
import io.gitlab.jfronny.muscript.data.dynamic.additional.DFinal;
import java.io.IOException;
import java.util.List;
import java.util.Objects;
import java.util.stream.Stream;
public class Script extends Decompilable {
private final DynamicExpr content;
public Script(List<Expr<?>> expressions) {
this(expressions, null);
}
public Script(List<Expr<?>> expressions, String file) {
this(new ExprGroup(CodeLocation.NONE.withFile(file), expressions));
}
private Script(DynamicExpr content) {
super(Order.Script);
this.content = Objects.requireNonNull(content);
}
public Dynamic run(DObject scope) {
return content.get(scope);
}
public Dynamic run(Scope scope) {
return content.get(scope);
}
public DCallable bindTo(Scope scope) {
return DFinal.of("<root>", args -> {
scope.set("args", args);
return run(scope);
}, this::asExpr);
}
public DynamicExpr asExpr() {
return content;
}
public Script optimize() {
return new Script(content.optimize());
}
public Script concat(Script other) {
return new Script(Stream.concat(this.stream(), other.stream()).toList());
}
public Stream<Expr<?>> stream() {
return content instanceof ExprGroup g
? g.stream()
: Stream.of(content);
}
@Override
public void decompile(ExprWriter writer) throws IOException {
for (Expr<?> expr : stream().toList()) {
expr.decompile(writer);
writer.append(";\n");
}
}
}

View File

@ -1,15 +0,0 @@
package io.gitlab.jfronny.muscript.data.dynamic;
import io.gitlab.jfronny.muscript.ast.BoolExpr;
import io.gitlab.jfronny.muscript.ast.literal.BoolLiteral;
import io.gitlab.jfronny.muscript.compiler.CodeLocation;
public non-sealed interface DBool extends Dynamic {
@Override
default BoolExpr toExpr() {
return new BoolLiteral(CodeLocation.NONE, getValue());
}
@Override
Boolean getValue();
}

View File

@ -1,40 +0,0 @@
package io.gitlab.jfronny.muscript.data.dynamic;
import io.gitlab.jfronny.muscript.data.dynamic.additional.DFinal;
import io.gitlab.jfronny.muscript.data.dynamic.additional.NamedDCallable;
import io.gitlab.jfronny.muscript.data.dynamic.type.*;
import java.util.List;
import java.util.function.Function;
public non-sealed interface DCallable extends Dynamic {
String DEFAULT_NAME = "<unnamed>";
default Dynamic call(DList args) {
DType signature = getSignature();
if (signature != null) {
List<DType> argTypes = args.getValue().stream().map(Dynamic::getSignature).toList();
if (!TypeMatcher.match(signature, argTypes)) {
List<DTypeCallable.Arg> extraArgs = argTypes.stream().map(s -> new DTypeCallable.Arg(null, s, false)).toList();
DTypeCallable callSignature = new DTypeCallable(extraArgs, null);
throw new IllegalArgumentException("Signature mismatch for " + getName() + ": expected <" + signature + "> but got <" + callSignature + ">");
}
}
return getValue().apply(args);
}
default Dynamic call(Dynamic... args) {
return call(DFinal.of(args));
}
default String getName() {
return DEFAULT_NAME;
}
default DCallable named(String name) {
return new NamedDCallable(this, name);
}
@Override
Function<DList, Dynamic> getValue();
}

View File

@ -1,30 +0,0 @@
package io.gitlab.jfronny.muscript.data.dynamic;
import io.gitlab.jfronny.muscript.ast.DynamicExpr;
import io.gitlab.jfronny.muscript.ast.dynamic.Call;
import io.gitlab.jfronny.muscript.ast.dynamic.Variable;
import io.gitlab.jfronny.muscript.compiler.CodeLocation;
import java.util.List;
public non-sealed interface DList extends Dynamic {
default Dynamic get(int i) {
return getValue().get(i);
}
default int size() {
return getValue().size();
}
default boolean isEmpty() {
return getValue().isEmpty();
}
@Override
default DynamicExpr toExpr() {
return new Call(CodeLocation.NONE, new Variable(CodeLocation.NONE, "listOf"), getValue().stream().map(s -> new Call.Arg(s.toExpr().asDynamicExpr(), false)).toList());
}
@Override
List<? extends Dynamic> getValue();
}

View File

@ -1,28 +0,0 @@
package io.gitlab.jfronny.muscript.data.dynamic;
import io.gitlab.jfronny.muscript.ast.NullLiteral;
import io.gitlab.jfronny.muscript.compiler.CodeLocation;
import io.gitlab.jfronny.muscript.data.dynamic.type.DType;
import io.gitlab.jfronny.muscript.data.dynamic.type.DTypePrimitive;
public final class DNull implements Dynamic {
@Override
public Object getValue() {
return null;
}
@Override
public NullLiteral toExpr() {
return new NullLiteral(CodeLocation.NONE);
}
@Override
public DType getSignature() {
return DTypePrimitive.NULL;
}
@Override
public String toString() {
return "null";
}
}

View File

@ -1,15 +0,0 @@
package io.gitlab.jfronny.muscript.data.dynamic;
import io.gitlab.jfronny.muscript.ast.NumberExpr;
import io.gitlab.jfronny.muscript.ast.literal.NumberLiteral;
import io.gitlab.jfronny.muscript.compiler.CodeLocation;
public non-sealed interface DNumber extends Dynamic {
@Override
default NumberExpr toExpr() {
return new NumberLiteral(CodeLocation.NONE, getValue());
}
@Override
Double getValue();
}

View File

@ -1,37 +0,0 @@
package io.gitlab.jfronny.muscript.data.dynamic;
import io.gitlab.jfronny.muscript.ast.DynamicExpr;
import io.gitlab.jfronny.muscript.ast.dynamic.ObjectLiteral;
import io.gitlab.jfronny.muscript.compiler.CodeLocation;
import io.gitlab.jfronny.muscript.data.dynamic.additional.DFinal;
import org.jetbrains.annotations.Nullable;
import java.util.Map;
import java.util.stream.Collectors;
public non-sealed interface DObject extends Dynamic {
default @Nullable Dynamic get(String key) {
return getValue().get(key);
}
default boolean has(String key) {
return getValue().containsKey(key);
}
@Override
default DynamicExpr toExpr() {
return new ObjectLiteral(CodeLocation.NONE, getValue().entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, s -> s.getValue().toExpr().asDynamicExpr())));
}
@Override
Map<String, ? extends Dynamic> getValue();
@Override
default DString asString() {
return DFinal.of(getValue()
.entrySet()
.stream()
.map(e -> e.getKey() + " = " + e.getValue().asString().getValue())
.collect(Collectors.joining(", ", "{", "}")));
}
}

View File

@ -1,15 +0,0 @@
package io.gitlab.jfronny.muscript.data.dynamic;
import io.gitlab.jfronny.muscript.ast.StringExpr;
import io.gitlab.jfronny.muscript.ast.literal.StringLiteral;
import io.gitlab.jfronny.muscript.compiler.CodeLocation;
public non-sealed interface DString extends Dynamic {
@Override
default StringExpr toExpr() {
return new StringLiteral(CodeLocation.NONE, getValue());
}
@Override
String getValue();
}

View File

@ -1,123 +0,0 @@
package io.gitlab.jfronny.muscript.data.dynamic;
import io.gitlab.jfronny.commons.StringFormatter;
import io.gitlab.jfronny.muscript.compiler.MuScriptVersion;
import io.gitlab.jfronny.muscript.data.dynamic.type.*;
import io.gitlab.jfronny.muscript.libs.StandardLib;
import io.gitlab.jfronny.muscript.ast.DynamicExpr;
import io.gitlab.jfronny.muscript.ast.Expr;
import io.gitlab.jfronny.muscript.compiler.ExprWriter;
import io.gitlab.jfronny.muscript.compiler.Parser;
import io.gitlab.jfronny.muscript.data.dynamic.additional.*;
import io.gitlab.jfronny.muscript.data.dynamic.lens.DStringLens;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.util.*;
/**
* Represents a value of an unknown type
* Override toString(StringBuilder) to support custom serialization (note: the serialized form is ran with muScript to generate the tree)
*/
public sealed interface Dynamic permits DBool, DCallable, DList, DNull, DNumber, DObject, DString, DynamicBase {
/**
* Deserialize simply dynamic values. DOES NOT SUPPORT CALLABLES!
* Use Parser.parse() if you truly need to deserialize them, but be aware that that might enable DOS attacks
*/
static Dynamic deserialize(String source) {
DynamicExpr expr = Parser.parse(MuScriptVersion.DEFAULT, source).asDynamicExpr();
if (!DirectPreconditionVisitor.visit(expr)) throw new IllegalArgumentException("This expression does not directly express a dynamic and may not be loaded this way");
return expr.get(StandardLib.createScope(MuScriptVersion.DEFAULT));
}
static String serialize(Dynamic dynamic) {
StringBuilder sb = new StringBuilder();
try (ExprWriter ew = new ExprWriter(sb, true)) {
dynamic.serialize(ew);
} catch (IOException e) {
throw new RuntimeException("Could not stringify", e);
}
return sb.toString();
}
static boolean isNull(Dynamic dynamic) {
return dynamic == null || dynamic instanceof DNull;
}
static @NotNull Dynamic fromNullable(@Nullable Dynamic dynamic) {
return dynamic == null ? new DNull() : dynamic;
}
default void serialize(ExprWriter writer) throws IOException {
toExpr().decompile(writer);
}
Expr<?> toExpr();
Object getValue();
default boolean isBool() {
return this instanceof DBool;
}
default DBool asBool() {
if (this instanceof DBool bool) return bool;
else throw new DynamicTypeConversionException("bool", this);
}
default boolean isNumber() {
return this instanceof DNumber;
}
default DNumber asNumber() {
if (this instanceof DNumber number) return number;
else throw new DynamicTypeConversionException("number", this);
}
default boolean isString() {
return this instanceof DString;
}
default DString asString() {
if (this instanceof DString string) return string;
else return new DStringLens(this, () -> StringFormatter.toString(getValue()));
}
default boolean isObject() {
return this instanceof DObject;
}
default DObject asObject() {
if (this instanceof DObject object) return object;
else throw new DynamicTypeConversionException("object", this);
}
default boolean isList() {
return this instanceof DList;
}
default DList asList() {
if (this instanceof DList list) return list;
else throw new DynamicTypeConversionException("list", this);
}
default boolean isCallable() {
return this instanceof DCallable;
}
default DCallable asCallable() {
if (this instanceof DCallable callable) return callable;
else throw new DynamicTypeConversionException("callable", this);
}
default DType getSignature() {
Set<DType> variants = new HashSet<>();
if (isBool()) variants.add(DTypePrimitive.BOOL);
if (isNumber()) variants.add(DTypePrimitive.NUMBER);
if (isString()) variants.add(DTypePrimitive.STRING);
if (isObject()) variants.add(new DTypeObject(null));
if (isList()) variants.add(new DTypeList(null));
if (isCallable()) variants.add(new DTypeCallable(null, null));
return variants.size() == 1 ? variants.stream().findFirst().orElseThrow() : new DTypeAnd(variants);
}
}

View File

@ -1,8 +0,0 @@
package io.gitlab.jfronny.muscript.data.dynamic;
/**
* Use this for ABSTRACT (!) implementations of custom dynamic containers.
* Any concrete implementation MUST still implement a valid Dynamic subtype (DBool, DString, ...)
*/
public non-sealed interface DynamicBase extends Dynamic {
}

View File

@ -1,21 +0,0 @@
package io.gitlab.jfronny.muscript.data.dynamic;
import io.gitlab.jfronny.muscript.compiler.CodeLocation;
import io.gitlab.jfronny.muscript.error.LocationalException;
public class DynamicTypeConversionException extends RuntimeException {
private static final String MESSAGE1 = "Could not convert dynamic of type ";
private static final String MESSAGE2 = " to ";
private final String target;
private final String actual;
public DynamicTypeConversionException(String target, Dynamic dynamic) {
super(MESSAGE1 + (dynamic == null ? "null" : dynamic.getClass().getSimpleName()) + MESSAGE2 + target);
this.target = target;
this.actual = dynamic == null ? "null" : dynamic.getClass().getSimpleName();
}
public LocationalException locational(CodeLocation location) {
return new LocationalException(location, MESSAGE1 + actual + MESSAGE2 + target, this);
}
}

View File

@ -1,52 +0,0 @@
package io.gitlab.jfronny.muscript.data.dynamic.additional;
import io.gitlab.jfronny.muscript.ast.DynamicExpr;
import io.gitlab.jfronny.muscript.ast.dynamic.*;
import io.gitlab.jfronny.muscript.compiler.CodeLocation;
import io.gitlab.jfronny.muscript.data.dynamic.*;
import io.gitlab.jfronny.muscript.data.dynamic.lens.DCallableLens;
import io.gitlab.jfronny.muscript.data.dynamic.type.*;
import java.util.*;
public record DCallableObject(Map<String, ? extends Dynamic> value, DTypeObject objectSignature, DCallable callable) implements DObject {
public DCallableObject(Map<String, ? extends Dynamic> value, DCallable callable) {
this(value, new DTypeObject(null), callable);
}
public DCallableObject {
Objects.requireNonNull(value);
Objects.requireNonNull(objectSignature);
Objects.requireNonNull(callable);
}
@Override
public Map<String, ? extends Dynamic> getValue() {
return value;
}
@Override
public boolean isCallable() {
return true;
}
@Override
public DCallable asCallable() {
return new DCallableLens(this, callable::getValue);
}
@Override
public DynamicExpr toExpr() {
return new Call(CodeLocation.NONE, new Bind(CodeLocation.NONE, new Variable(CodeLocation.NONE, "callableObject"), DObject.super.toExpr()), List.of(new Call.Arg(callable.toExpr().asDynamicExpr(), false)));
}
@Override
public DType getSignature() {
return new DTypeAnd(Set.of(objectSignature, callable.getSignature()));
}
@Override
public String toString() {
return Dynamic.serialize(this);
}
}

View File

@ -1,24 +0,0 @@
package io.gitlab.jfronny.muscript.data.dynamic.additional;
import io.gitlab.jfronny.muscript.data.dynamic.Dynamic;
import io.gitlab.jfronny.muscript.data.dynamic.DynamicBase;
public abstract class DContainer<T> implements DynamicBase {
private T value;
@Override
public T getValue() {
return value;
}
public T setValue(T value) {
if (value != null)
this.value = value;
return this.value;
}
@Override
public String toString() {
return Dynamic.serialize(this);
}
}

View File

@ -1,70 +0,0 @@
package io.gitlab.jfronny.muscript.data.dynamic.additional;
import io.gitlab.jfronny.muscript.ast.DynamicExpr;
import io.gitlab.jfronny.muscript.ast.dynamic.Call;
import io.gitlab.jfronny.muscript.ast.dynamic.Variable;
import io.gitlab.jfronny.muscript.ast.literal.NumberLiteral;
import io.gitlab.jfronny.muscript.compiler.CodeLocation;
import io.gitlab.jfronny.muscript.data.dynamic.*;
import io.gitlab.jfronny.muscript.data.dynamic.lens.DNumberLens;
import io.gitlab.jfronny.muscript.data.dynamic.lens.DStringLens;
import io.gitlab.jfronny.muscript.data.dynamic.type.*;
import java.time.LocalDate;
import java.util.*;
import java.util.function.Supplier;
import static io.gitlab.jfronny.muscript.data.dynamic.type.DSL.*;
import static io.gitlab.jfronny.muscript.data.dynamic.type.DSL.NUMBER;
public record DDate(Supplier<LocalDate> date) implements DObject {
public static final DType SIGNATURE = object(NUMBER).or(STRING).or(NUMBER);
@Override
public Map<String, Dynamic> getValue() {
return Map.of(
"year", DFinal.of(date.get().getYear()),
"month", DFinal.of(date.get().getMonthValue()),
"day", DFinal.of(date.get().getDayOfMonth())
);
}
@Override
public boolean isString() {
return true;
}
@Override
public DString asString() {
return new DStringLens(this, this::toString);
}
@Override
public boolean isNumber() {
return true;
}
@Override
public DNumber asNumber() {
return new DNumberLens(this, () -> date.get().toEpochDay());
}
@Override
public DynamicExpr toExpr() {
return new Call(CodeLocation.NONE, new Variable(CodeLocation.NONE, "date"), List.of(
new Call.Arg(new NumberLiteral(CodeLocation.NONE, date.get().getYear()).asDynamicExpr(), false),
new Call.Arg(new NumberLiteral(CodeLocation.NONE, date.get().getMonthValue()).asDynamicExpr(), false),
new Call.Arg(new NumberLiteral(CodeLocation.NONE, date.get().getDayOfMonth()).asDynamicExpr(), false)
));
}
@Override
public DType getSignature() {
return SIGNATURE;
}
@Override
public String toString() {
return date.get().toString();
}
}

View File

@ -1,31 +0,0 @@
package io.gitlab.jfronny.muscript.data.dynamic.additional;
import io.gitlab.jfronny.muscript.data.dynamic.*;
import io.gitlab.jfronny.muscript.data.dynamic.lens.DListLens;
import java.util.List;
import java.util.Map;
public enum DEmpty implements DObject {
INSTANCE;
@Override
public String toString() {
return "[]";
}
@Override
public Map<String, ? extends Dynamic> getValue() {
return Map.of();
}
@Override
public boolean isList() {
return true;
}
@Override
public DList asList() {
return new DListLens(this, List::of);
}
}

View File

@ -1,135 +0,0 @@
package io.gitlab.jfronny.muscript.data.dynamic.additional;
import io.gitlab.jfronny.commons.StringFormatter;
import io.gitlab.jfronny.commons.data.ImmCollection;
import io.gitlab.jfronny.muscript.ast.DynamicExpr;
import io.gitlab.jfronny.muscript.ast.dynamic.Call;
import io.gitlab.jfronny.muscript.ast.dynamic.Variable;
import io.gitlab.jfronny.muscript.ast.literal.StringLiteral;
import io.gitlab.jfronny.muscript.compiler.CodeLocation;
import io.gitlab.jfronny.muscript.data.dynamic.*;
import io.gitlab.jfronny.muscript.data.dynamic.lens.*;
import io.gitlab.jfronny.muscript.data.dynamic.type.*;
import org.jetbrains.annotations.Nullable;
import java.util.*;
/**
* An enum represented as an OObject.
* May also have a selected value with automatic conversions to a number (index) and string
*/
public record DEnum(Map<String, ? extends Dynamic> values, @Nullable DEnumEntry value, @Nullable DType valueSignature) implements DObject {
public DEnum(Map<String, ? extends Dynamic> values) {
this(null, values);
}
public DEnum(DType valueSignature, Map<String, ? extends Dynamic> values) {
this(values, null, valueSignature);
}
public DEnum(Map<String, ? extends Dynamic> values, @Nullable String value) {
this(null, values, value);
}
public DEnum(DType valueSignature, Map<String, ? extends Dynamic> values, @Nullable String value) {
this(values, value == null ? null : new DEnumEntry(value, values.keySet().stream().toList().indexOf(value), true), valueSignature);
}
public DEnum(List<String> values, String value) {
this(createMap(values, value), value == null ? null : new DEnumEntry(value, values.indexOf(value), true), DTypePrimitive.STRING);
}
public DEnum(List<String> values) {
this(values, null);
}
@Override
public Map<String, ? extends Dynamic> getValue() {
return values;
}
@Override
public boolean isList() {
return true;
}
@Override
public DList asList() {
return new DListLens(this, () -> List.copyOf(values.values()));
}
@Override
public boolean isString() {
return true;
}
@Override
public DString asString() {
return value != null ? value : new DStringLens(this, () -> StringFormatter.toString(values));
}
@Override
public boolean isNumber() {
return value != null;
}
@Override
public DNumber asNumber() {
return value != null ? new DNumberLens(this, value::index) : DObject.super.asNumber();
}
@Override
public DynamicExpr toExpr() {
List<Call.Arg> args = new LinkedList<>();
args.add(new Call.Arg(DObject.super.toExpr(), false));
if (value != null) args.add(new Call.Arg(new StringLiteral(CodeLocation.NONE, value.value).asDynamicExpr(), false));
return new Call(CodeLocation.NONE, new Variable(CodeLocation.NONE, "enum"), args);
}
@Override
public DType getSignature() {
Set<DType> types = new HashSet<>();
types.add(new DTypeObject(valueSignature));
types.add(new DTypeList(valueSignature));
types.add(DTypePrimitive.STRING);
if (value != null) types.add(DTypePrimitive.NUMBER);
return new DTypeAnd(types);
}
private static Map<String, Dynamic> createMap(List<String> values, String value) {
Map<String, Dynamic> result = new LinkedHashMap<>();
DEnumEntry v = new DEnumEntry(value, values.indexOf(value), true);
result.put("value", v);
for (int i = 0; i < values.size(); i++) {
result.put(values.get(i), new DEnumEntry(values.get(i), i, values.get(i).equals(value)));
}
return ImmCollection.of(result);
}
public record DEnumEntry(String value, int index, boolean selected) implements DString {
@Override
public String getValue() {
return value;
}
@Override
public boolean isNumber() {
return true;
}
@Override
public DNumber asNumber() {
return new DNumberLens(this, this::index);
}
@Override
public boolean isBool() {
return true;
}
@Override
public DBool asBool() {
return new DBoolLens(this, this::selected);
}
}
}

View File

@ -1,207 +0,0 @@
package io.gitlab.jfronny.muscript.data.dynamic.additional;
import io.gitlab.jfronny.commons.LazySupplier;
import io.gitlab.jfronny.commons.StringFormatter;
import io.gitlab.jfronny.commons.data.ImmCollection;
import io.gitlab.jfronny.muscript.ast.Expr;
import io.gitlab.jfronny.muscript.ast.dynamic.Variable;
import io.gitlab.jfronny.muscript.compiler.*;
import io.gitlab.jfronny.muscript.data.dynamic.*;
import io.gitlab.jfronny.muscript.data.dynamic.type.*;
import java.util.*;
import java.util.function.Function;
import java.util.function.Supplier;
public class DFinal {
public static DBool of(boolean b) {
return new FBool(b);
}
public static DNumber of(double b) {
return new FNumber(b);
}
public static DString of(String b) {
return new FString(b);
}
public static DObject of(Map<String, ? extends Dynamic> b) {
return of(new DTypeObject(null), b);
}
public static DObject of(DTypeObject signature, Map<String, ? extends Dynamic> b) {
return new FObject(ImmCollection.copyOf((Map<String, Dynamic>) b), Objects.requireNonNull(signature));
}
public static DList of(Dynamic... b) {
return of(new DTypeList(null), b);
}
public static DList of(DTypeList signature, Dynamic... b) {
return new FList(List.of(b), Objects.requireNonNull(signature));
}
public static DList of(List<? extends Dynamic> b) {
return of(new DTypeList(null), b);
}
public static DList of(DTypeList signature, List<? extends Dynamic> b) {
return new FList(ImmCollection.copyOf((List<Dynamic>) b), Objects.requireNonNull(signature));
}
public static DCallable of(Function<DList, ? extends Dynamic> b, Supplier<String> serialized) {
return of(new DTypeCallable(null, null), b, Objects.requireNonNull(serialized));
}
public static DCallable of(DType signature, Function<DList, ? extends Dynamic> b, Supplier<String> serialized) {
return of(Objects.requireNonNull(signature), b, serialized, null);
}
public static DCallable of(Function<DList, ? extends Dynamic> b, String name) {
return of(new DTypeCallable(null, null), b, name);
}
public static DCallable of(DType signature, Function<DList, ? extends Dynamic> b, String name) {
return of(Objects.requireNonNull(signature), name, b, () -> new Variable(CodeLocation.NONE, name));
}
public static DCallable of(Function<DList, ? extends Dynamic> b, Supplier<String> serialized, String name) {
return of(new DTypeCallable(null, null), b, serialized, name);
}
public static DCallable of(DType signature, Function<DList, ? extends Dynamic> b, Supplier<String> serialized, String name) {
return of(Objects.requireNonNull(signature), name, b, () -> Parser.parse(MuScriptVersion.DEFAULT, serialized.get()));
}
public static DCallable of(String name, Function<DList, ? extends Dynamic> b, Supplier<Expr<?>> serialized) {
return of(new DTypeCallable(null, null), name, b, serialized);
}
public static DCallable of(DType signature, String name, Function<DList, ? extends Dynamic> b, Supplier<Expr<?>> serialized) {
return new FCallable((Function<DList, Dynamic>) b, new LazySupplier<>(serialized), name, Objects.requireNonNull(signature));
}
/**
* Marks all DFinal implementation classes. Use this in instanceof checks
*/
public sealed interface FImpl {
}
private record FBool(boolean value) implements DBool, FImpl {
@Override
public Boolean getValue() {
return value;
}
@Override
public String toString() {
return StringFormatter.toString(value);
}
@Override
public DType getSignature() {
return DTypePrimitive.BOOL;
}
}
private record FNumber(double value) implements DNumber, FImpl {
@Override
public Double getValue() {
return value;
}
@Override
public String toString() {
return StringFormatter.toStringPrecise(value);
}
@Override
public DType getSignature() {
return DTypePrimitive.NUMBER;
}
}
private record FString(String value) implements DString, FImpl {
@Override
public String getValue() {
return value;
}
@Override
public String toString() {
return Dynamic.serialize(this);
}
@Override
public DType getSignature() {
return DTypePrimitive.STRING;
}
}
private record FObject(Map<String, Dynamic> value, DTypeObject signature) implements DObject, FImpl {
@Override
public Map<String, Dynamic> getValue() {
return value;
}
@Override
public String toString() {
return Dynamic.serialize(this);
}
@Override
public DType getSignature() {
return signature;
}
}
private record FList(List<Dynamic> value, DTypeList signature) implements DList, FImpl {
@Override
public List<Dynamic> getValue() {
return value;
}
@Override
public String toString() {
return value.toString();
}
@Override
public DType getSignature() {
return signature;
}
}
private record FCallable(Function<DList, Dynamic> value, Supplier<Expr<?>> gen, String name, DType signature) implements DCallable, FImpl {
@Override
public Expr<?> toExpr() {
return gen.get();
}
@Override
public Function<DList, Dynamic> getValue() {
return value;
}
@Override
public String toString() {
return toExpr().toString();
}
@Override
public String getName() {
return name == null ? DCallable.super.getName() : name;
}
@Override
public DCallable named(String name) {
return new FCallable(value, gen, name, signature);
}
@Override
public DType getSignature() {
return signature;
}
}
}

View File

@ -1,69 +0,0 @@
package io.gitlab.jfronny.muscript.data.dynamic.additional;
import io.gitlab.jfronny.muscript.ast.DynamicExpr;
import io.gitlab.jfronny.muscript.ast.dynamic.Call;
import io.gitlab.jfronny.muscript.ast.dynamic.Variable;
import io.gitlab.jfronny.muscript.ast.literal.NumberLiteral;
import io.gitlab.jfronny.muscript.compiler.CodeLocation;
import io.gitlab.jfronny.muscript.data.dynamic.*;
import io.gitlab.jfronny.muscript.data.dynamic.lens.DNumberLens;
import io.gitlab.jfronny.muscript.data.dynamic.lens.DStringLens;
import io.gitlab.jfronny.muscript.data.dynamic.type.*;
import java.time.LocalTime;
import java.util.*;
import java.util.function.Supplier;
import static io.gitlab.jfronny.muscript.data.dynamic.type.DSL.*;
public record DTime(Supplier<LocalTime> time) implements DObject {
public static final DType SIGNATURE = object(NUMBER).or(STRING).or(NUMBER);
@Override
public Map<String, Dynamic> getValue() {
return Map.of(
"hour", DFinal.of(time.get().getHour()),
"minute", DFinal.of(time.get().getMinute()),
"second", DFinal.of(time.get().getSecond())
);
}
@Override
public boolean isString() {
return true;
}
@Override
public DString asString() {
return new DStringLens(this, this::toString);
}
@Override
public boolean isNumber() {
return true;
}
@Override
public DNumber asNumber() {
return new DNumberLens(this, () -> time.get().toSecondOfDay());
}
@Override
public DynamicExpr toExpr() {
return new Call(CodeLocation.NONE, new Variable(CodeLocation.NONE, "time"), List.of(
new Call.Arg(new NumberLiteral(CodeLocation.NONE, time.get().getHour()).asDynamicExpr(), false),
new Call.Arg(new NumberLiteral(CodeLocation.NONE, time.get().getMinute()).asDynamicExpr(), false),
new Call.Arg(new NumberLiteral(CodeLocation.NONE, time.get().getSecond()).asDynamicExpr(), false)
));
}
@Override
public DType getSignature() {
return SIGNATURE;
}
@Override
public String toString() {
return time.get().toString();
}
}

View File

@ -1,92 +0,0 @@
package io.gitlab.jfronny.muscript.data.dynamic.additional;
import io.gitlab.jfronny.muscript.ast.Expr;
import io.gitlab.jfronny.muscript.compiler.ExprWriter;
import io.gitlab.jfronny.muscript.data.dynamic.*;
import io.gitlab.jfronny.muscript.data.dynamic.type.DType;
import java.io.IOException;
public interface DelegateDynamic extends DynamicBase {
Dynamic getDelegate();
@Override
default void serialize(ExprWriter writer) throws IOException {
getDelegate().serialize(writer);
}
@Override
default Expr<?> toExpr() {
return getDelegate().toExpr();
}
@Override
default Object getValue() {
return getDelegate().getValue();
}
@Override
default boolean isBool() {
return getDelegate().isBool();
}
@Override
default DBool asBool() {
return getDelegate().asBool();
}
@Override
default boolean isNumber() {
return getDelegate().isNumber();
}
@Override
default DNumber asNumber() {
return getDelegate().asNumber();
}
@Override
default boolean isString() {
return getDelegate().isString();
}
@Override
default DString asString() {
return getDelegate().asString();
}
@Override
default boolean isObject() {
return getDelegate().isObject();
}
@Override
default DObject asObject() {
return getDelegate().asObject();
}
@Override
default boolean isList() {
return getDelegate().isList();
}
@Override
default DList asList() {
return getDelegate().asList();
}
@Override
default boolean isCallable() {
return getDelegate().isCallable();
}
@Override
default DCallable asCallable() {
return getDelegate().asCallable();
}
@Override
default DType getSignature() {
return getDelegate().getSignature();
}
}

View File

@ -1,47 +0,0 @@
package io.gitlab.jfronny.muscript.data.dynamic.additional;
import io.gitlab.jfronny.muscript.ast.*;
import io.gitlab.jfronny.muscript.ast.dynamic.*;
import io.gitlab.jfronny.muscript.ast.literal.*;
import io.gitlab.jfronny.muscript.ast.string.Concatenate;
import io.gitlab.jfronny.muscript.data.dynamic.*;
import java.util.Set;
public class DirectPreconditionVisitor {
public static boolean visit(Expr<?> expr) {
//TODO replace this with switch pattern matching
if (expr instanceof BoolLiteral) return true;
if (expr instanceof NullLiteral) return true;
if (expr instanceof NumberLiteral) return true;
if (expr instanceof StringLiteral) return true;
if (expr instanceof DynamicLiteral<?> e) return visit(e.value);
if (expr instanceof DynamicCoerce e) return visit(e.inner);
if (expr instanceof ObjectLiteral e) return e.content.values().stream().allMatch(DirectPreconditionVisitor::visit);
if (expr instanceof Concatenate e) return visit(e.left) && visit(e.right);
// if (expr instanceof BoolUnpack e) return visit(e.inner);
// if (expr instanceof NumberUnpack e) return visit(e.inner);
// if (expr instanceof StringUnpack e) return visit(e.inner);
if (expr instanceof Bind e) return visitCallable(e.callable) && visit(e.parameter);
if (expr instanceof Call e) return visitCallable(e.left) && e.args.stream().allMatch(a -> !a.variadic() && visit(a.expr()));
return false; // unknown expression
}
public static boolean visit(Dynamic dynamic) {
if (dynamic instanceof DNull) return true;
if (dynamic instanceof DFinal.FImpl) {
if (dynamic instanceof DBool) return true;
if (dynamic instanceof DNumber) return true;
if (dynamic instanceof DString) return true;
if (dynamic instanceof DObject ob) return ob.getValue().values().stream().allMatch(DirectPreconditionVisitor::visit);
if (dynamic instanceof DList ob) return ob.getValue().stream().allMatch(DirectPreconditionVisitor::visit);
}
return false;
}
private static final Set<String> allowedVariables = Set.of("date", "time", "enum", "listOf");
private static boolean visitCallable(DynamicExpr expr) {
if (expr instanceof Variable v) return allowedVariables.contains(v.name);
return false;
}
}

View File

@ -1,34 +0,0 @@
package io.gitlab.jfronny.muscript.data.dynamic.additional;
import io.gitlab.jfronny.muscript.ast.Expr;
import io.gitlab.jfronny.muscript.data.dynamic.*;
import io.gitlab.jfronny.muscript.data.dynamic.type.DType;
import java.util.function.Function;
public record NamedDCallable(DCallable inner, String name) implements DCallable {
@Override
public String getName() {
return name;
}
@Override
public DCallable named(String name) {
return new NamedDCallable(inner, name);
}
@Override
public Expr<?> toExpr() {
return inner.toExpr();
}
@Override
public Function<DList, Dynamic> getValue() {
return inner.getValue();
}
@Override
public DType getSignature() {
return inner.getSignature();
}
}

View File

@ -1,27 +0,0 @@
package io.gitlab.jfronny.muscript.data.dynamic.lens;
import io.gitlab.jfronny.muscript.ast.BoolExpr;
import io.gitlab.jfronny.muscript.data.dynamic.DBool;
import io.gitlab.jfronny.muscript.data.dynamic.Dynamic;
import java.util.Objects;
import java.util.function.BooleanSupplier;
public final class DBoolLens extends DLens implements DBool {
private final BooleanSupplier value;
public DBoolLens(Dynamic source, BooleanSupplier value) {
super(source);
this.value = Objects.requireNonNull(value);
}
@Override
public Boolean getValue() {
return value.getAsBoolean();
}
@Override
public BoolExpr toExpr() {
return source.toExpr().asBoolExpr();
}
}

View File

@ -1,27 +0,0 @@
package io.gitlab.jfronny.muscript.data.dynamic.lens;
import io.gitlab.jfronny.muscript.ast.Expr;
import io.gitlab.jfronny.muscript.data.dynamic.*;
import java.util.Objects;
import java.util.function.Function;
import java.util.function.Supplier;
public final class DCallableLens extends DLens implements DCallable {
private final Supplier<Function<DList, Dynamic>> value;
public DCallableLens(Dynamic source, Supplier<Function<DList, Dynamic>> value) {
super(source);
this.value = Objects.requireNonNull(value);
}
@Override
public Function<DList, Dynamic> getValue() {
return value.get();
}
@Override
public Expr<?> toExpr() {
return source.toExpr();
}
}

View File

@ -1,102 +0,0 @@
package io.gitlab.jfronny.muscript.data.dynamic.lens;
import io.gitlab.jfronny.muscript.compiler.ExprWriter;
import io.gitlab.jfronny.muscript.data.dynamic.*;
import io.gitlab.jfronny.muscript.data.dynamic.type.DType;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.util.Objects;
public abstract sealed class DLens implements DynamicBase permits DBoolLens, DCallableLens, DListLens, DNumberLens, DObjectLens, DStringLens {
protected final Dynamic source;
protected DLens(Dynamic source) {
this.source = Objects.requireNonNull(source);
}
@Override
public void serialize(ExprWriter writer) throws IOException {
source.serialize(writer);
}
@Override
public boolean isBool() {
return source.isBool();
}
@Override
public DBool asBool() {
return source.asBool();
}
@Override
public boolean isNumber() {
return source.isNumber();
}
@Override
public DNumber asNumber() {
return source.asNumber();
}
@Override
public boolean isString() {
return source.isString();
}
@Override
public DString asString() {
return source.asString();
}
@Override
public boolean isObject() {
return source.isObject();
}
@Override
public DObject asObject() {
return source.asObject();
}
@Override
public boolean isList() {
return source.isList();
}
@Override
public DList asList() {
return source.asList();
}
@Override
public boolean isCallable() {
return source.isCallable();
}
@Override
public DCallable asCallable() {
return source.asCallable();
}
@Override
public @Nullable DType getSignature() {
return source.getSignature();
}
@Override
public int hashCode() {
return source.hashCode();
}
@Override
public boolean equals(Object obj) {
return obj instanceof DLens lens ? source.equals(lens.source) : source.equals(obj);
}
@Override
public String toString() {
return source.toString();
}
}

View File

@ -1,28 +0,0 @@
package io.gitlab.jfronny.muscript.data.dynamic.lens;
import io.gitlab.jfronny.muscript.ast.DynamicExpr;
import io.gitlab.jfronny.muscript.data.dynamic.DList;
import io.gitlab.jfronny.muscript.data.dynamic.Dynamic;
import java.util.List;
import java.util.Objects;
import java.util.function.Supplier;
public final class DListLens extends DLens implements DList {
private final Supplier<List<? extends Dynamic>> value;
public DListLens(Dynamic source, Supplier<List<? extends Dynamic>> value) {
super(source);
this.value = Objects.requireNonNull(value);
}
@Override
public List<? extends Dynamic> getValue() {
return value.get();
}
@Override
public DynamicExpr toExpr() {
return source.toExpr().asDynamicExpr();
}
}

View File

@ -1,27 +0,0 @@
package io.gitlab.jfronny.muscript.data.dynamic.lens;
import io.gitlab.jfronny.muscript.ast.NumberExpr;
import io.gitlab.jfronny.muscript.data.dynamic.DNumber;
import io.gitlab.jfronny.muscript.data.dynamic.Dynamic;
import java.util.Objects;
import java.util.function.DoubleSupplier;
public final class DNumberLens extends DLens implements DNumber {
private final DoubleSupplier value;
public DNumberLens(Dynamic source, DoubleSupplier value) {
super(source);
this.value = Objects.requireNonNull(value);
}
@Override
public Double getValue() {
return value.getAsDouble();
}
@Override
public NumberExpr toExpr() {
return source.toExpr().asNumberExpr();
}
}

View File

@ -1,28 +0,0 @@
package io.gitlab.jfronny.muscript.data.dynamic.lens;
import io.gitlab.jfronny.muscript.ast.DynamicExpr;
import io.gitlab.jfronny.muscript.data.dynamic.DObject;
import io.gitlab.jfronny.muscript.data.dynamic.Dynamic;
import java.util.Map;
import java.util.Objects;
import java.util.function.Supplier;
public final class DObjectLens extends DLens implements DObject {
private final Supplier<Map<String, ? extends Dynamic>> value;
public DObjectLens(Dynamic source, Supplier<Map<String, ? extends Dynamic>> value) {
super(source);
this.value = Objects.requireNonNull(value);
}
@Override
public Map<String, ? extends Dynamic> getValue() {
return value.get();
}
@Override
public DynamicExpr toExpr() {
return source.toExpr().asDynamicExpr();
}
}

View File

@ -1,27 +0,0 @@
package io.gitlab.jfronny.muscript.data.dynamic.lens;
import io.gitlab.jfronny.muscript.ast.StringExpr;
import io.gitlab.jfronny.muscript.data.dynamic.DString;
import io.gitlab.jfronny.muscript.data.dynamic.Dynamic;
import java.util.Objects;
import java.util.function.Supplier;
public final class DStringLens extends DLens implements DString {
private final Supplier<String> value;
public DStringLens(Dynamic source, Supplier<String> value) {
super(source);
this.value = Objects.requireNonNull(value);
}
@Override
public String getValue() {
return value.get();
}
@Override
public StringExpr toExpr() {
return source.toExpr().asStringExpr();
}
}

View File

@ -1,40 +0,0 @@
package io.gitlab.jfronny.muscript.data.dynamic.type;
import org.jetbrains.annotations.Nullable;
import java.util.List;
public class DSL {
public static final DTypePrimitive BOOL = DTypePrimitive.BOOL;
public static final DTypePrimitive STRING = DTypePrimitive.STRING;
public static final DTypePrimitive NUMBER = DTypePrimitive.NUMBER;
public static final DTypePrimitive NULL = DTypePrimitive.NULL;
public static DTypeGeneric generic(int index) {
return new DTypeGeneric(index);
}
public static DTypeList list(@Nullable DType entryType) {
return new DTypeList(entryType);
}
public static DTypeObject object(@Nullable DType entryType) {
return new DTypeObject(entryType);
}
public static DTypeCallable callable(@Nullable List<DTypeCallable.Arg> from, @Nullable DType to) {
return new DTypeCallable(from, to);
}
public static DTypeCallable callable(@Nullable DType to, DTypeCallable.Arg... from) {
return new DTypeCallable(List.of(from), to);
}
public static DTypeCallable.Arg arg(@Nullable String name, @Nullable DType type) {
return new DTypeCallable.Arg(name, type, false);
}
public static DTypeCallable.Arg arg(@Nullable String name, @Nullable DType type, boolean variadic) {
return new DTypeCallable.Arg(name, type, variadic);
}
}

View File

@ -1,43 +0,0 @@
package io.gitlab.jfronny.muscript.data.dynamic.type;
import org.jetbrains.annotations.Nullable;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
public sealed interface DType permits DTypeCallable, DTypeGeneric, DTypeList, DTypeObject, DTypePrimitive, DTypeSum, DTypeAnd {
static String toString(@Nullable DType type) {
return toString(type, false);
}
default DType or(DType alternative) {
return new DTypeSum(Set.of(this, alternative));
}
default DType and(DType additional) {
return new DTypeAnd(Set.of(this, additional));
}
static String toString(@Nullable DType type, boolean wrapComplex) {
//TODO replace with switch pattern
if (type == null) return "any";
else if (type instanceof DTypePrimitive t) return switch (t) {
case BOOL -> "bool";
case NUMBER -> "number";
case STRING -> "string";
case NULL -> "null";
};
else if (type instanceof DTypeGeneric t) return "T" + t.index();
else if (type instanceof DTypeList t) return "[" + toString(t.entryType(), false) + "]";
else if (type instanceof DTypeObject t) return "{" + toString(t.entryType(), false) + "}";
else if (type instanceof DTypeCallable t) {
String args = t.from() == null
? "any"
: "(" + t.from().stream().map(Objects::toString).collect(Collectors.joining(", ")) + ")";
return args + " -> " + toString(t.to(), true);
} else if (type instanceof DTypeSum t) return (wrapComplex ? "<" : "") + t.elements().stream().map(s -> toString(s, true)).collect(Collectors.joining(" | ")) + (wrapComplex ? ">" : "");
else if (type instanceof DTypeAnd t) return (wrapComplex ? "<" : "") + t.elements().stream().map(s -> toString(s, true)).collect(Collectors.joining(" & ")) + (wrapComplex ? ">" : "");
else throw new IllegalArgumentException("Unexpected DType implementation: " + type.getClass());
}
}

View File

@ -1,45 +0,0 @@
package io.gitlab.jfronny.muscript.data.dynamic.type;
import io.gitlab.jfronny.commons.data.ImmCollection;
import java.util.*;
public record DTypeAnd(Set<DType> elements) implements DType {
public DTypeAnd(Set<DType> elements) {
if (elements.isEmpty()) throw new IllegalArgumentException("Cannot create union type without elements");
Set<DType> simple = new LinkedHashSet<>();
Set<DType> list = new LinkedHashSet<>();
Set<DType> object = new LinkedHashSet<>();
boolean foundNullList = false, foundNullObject = false;
Queue<DType> toProcess = new LinkedList<>(elements);
while (!toProcess.isEmpty()) {
DType type = toProcess.remove();
//TODO replace with pattern match
if (type instanceof DTypePrimitive || type instanceof DTypeSum || type instanceof DTypeCallable || type instanceof DTypeGeneric) simple.add(type);
else if (type instanceof DTypeList u) {
if (u.entryType() == null) {
if (!foundNullList) simple.add(new DTypeList(null));
foundNullList = true;
} else list.add(u.entryType());
} else if (type instanceof DTypeObject u) {
if (u.entryType() == null) {
if (!foundNullObject) simple.add(new DTypeObject(null));
foundNullObject = true;
} else object.add(u.entryType());
} else if (type instanceof DTypeAnd u) toProcess.addAll(u.elements);
else throw new IllegalArgumentException("Unexpected DType implementation: " + type.getClass());
}
if (!list.isEmpty()) simple.add(new DTypeList(new DTypeAnd(list)));
if (!object.isEmpty()) simple.add(new DTypeObject(new DTypeAnd(object)));
this.elements = ImmCollection.copyOf(simple);
}
@Override
public String toString() {
return DType.toString(this);
}
}

View File

@ -1,27 +0,0 @@
package io.gitlab.jfronny.muscript.data.dynamic.type;
import org.jetbrains.annotations.Nullable;
import java.util.List;
public record DTypeCallable(@Nullable List<Arg> from, @Nullable DType to) implements DType {
public DTypeCallable {
if (from != null) {
for (Arg arg : from) {
if (arg == null) throw new IllegalArgumentException("Contents of 'from' cannot be null");
}
}
}
@Override
public String toString() {
return DType.toString(this);
}
public record Arg(@Nullable String name, @Nullable DType type, boolean variadic) {
@Override
public String toString() {
return (name == null ? DType.toString(type) : name + ": " + DType.toString(type)) + (variadic ? "..." : "");
}
}
}

View File

@ -1,8 +0,0 @@
package io.gitlab.jfronny.muscript.data.dynamic.type;
public record DTypeGeneric(int index) implements DType {
@Override
public String toString() {
return DType.toString(this);
}
}

View File

@ -1,10 +0,0 @@
package io.gitlab.jfronny.muscript.data.dynamic.type;
import org.jetbrains.annotations.Nullable;
public record DTypeList(@Nullable DType entryType) implements DType {
@Override
public String toString() {
return DType.toString(this);
}
}

View File

@ -1,11 +0,0 @@
package io.gitlab.jfronny.muscript.data.dynamic.type;
import org.jetbrains.annotations.Nullable;
//TODO redesign for complex objects
public record DTypeObject(@Nullable DType entryType) implements DType {
@Override
public String toString() {
return DType.toString(this);
}
}

View File

@ -1,10 +0,0 @@
package io.gitlab.jfronny.muscript.data.dynamic.type;
public enum DTypePrimitive implements DType {
BOOL, NUMBER, STRING, NULL;
@Override
public String toString() {
return DType.toString(this);
}
}

View File

@ -1,35 +0,0 @@
package io.gitlab.jfronny.muscript.data.dynamic.type;
import io.gitlab.jfronny.commons.data.ImmCollection;
import java.util.*;
public record DTypeSum(Set<DType> elements) implements DType {
public DTypeSum(Set<DType> elements) {
if (elements.isEmpty()) throw new IllegalArgumentException("Cannot create sum type without elements");
Set<DType> simple = new LinkedHashSet<>();
Set<DType> list = new LinkedHashSet<>();
Set<DType> object = new LinkedHashSet<>();
Queue<DType> toProcess = new LinkedList<>(elements);
while (!toProcess.isEmpty()) {
DType type = toProcess.remove();
//TODO replace with pattern match
if (type instanceof DTypePrimitive || type instanceof DTypeAnd || type instanceof DTypeCallable) simple.add(type);
else if (type instanceof DTypeList u) list.add(u.entryType());
else if (type instanceof DTypeObject u) object.add(u.entryType());
else if (type instanceof DTypeSum u) toProcess.addAll(u.elements);
else throw new IllegalArgumentException("Unexpected DType implementation: " + type.getClass());
}
if (!list.isEmpty()) simple.add(new DTypeList(list.contains(null) ? null : new DTypeSum(list)));
if (!object.isEmpty()) simple.add(new DTypeObject(object.contains(null) ? null : new DTypeSum(object)));
this.elements = ImmCollection.copyOf(simple);
}
@Override
public String toString() {
return DType.toString(this);
}
}

Some files were not shown because too many files have changed in this diff Show More