Move terrascript 2 to separate module

This commit is contained in:
Astrash committed 2023-10-23 13:28:26 +11:00
1 parent 375f0ba60f
commit 1623a4f958
156 files changed
+1441 -1495

No files matched your search

@@ -4,8 +4,6 @@ version = version("1.1.0")
dependencies {
api("commons-io:commons-io:2.7")
api("org.ow2.asm:asm:9.5")
api("org.ow2.asm:asm-commons:9.5")
compileOnlyApi(project(":common:addons:manifest-addon-loader"))
implementation("net.jafama", "jafama", Versions.Libraries.Internal.jafama)
testImplementation("net.jafama", "jafama", Versions.Libraries.Internal.jafama)
@@ -14,232 +12,4 @@ dependencies {
tasks.named<ShadowJar>("shadowJar") {
relocate("org.apache.commons", "com.dfsek.terra.addons.terrascript.lib.commons")
relocate("net.jafama", "com.dfsek.terra.addons.terrascript.lib.jafama")
}
val astSourceSet = buildDir.resolve("generated/ast")
val astPackage = astSourceSet.resolve("com/dfsek/terra/addons/terrascript/ast")
data class ASTClass(
val name: String,
val imports: List<String>,
val nodes: List<ASTNode>,
val constructorFields: List<Pair<String, String>> = emptyList(),
)
data class ASTNode(
val name: String,
val constructorFields: List<Pair<String, String>>,
val mutableFields: List<Pair<String, String>> = emptyList() // TODO - Remove mutability from AST nodes
)
// Auto generate AST classes rather than writing them by hand
tasks.register("genTerrascriptAstClasses") {
val packageName = astPackage.toRelativeString(astSourceSet).replace('/', '.')
fun generateClass(clazz: ASTClass) {
val src = StringBuilder()
src.appendLine("package $packageName;\n");
for (imprt in clazz.imports) src.appendLine("import $imprt;")
src.appendLine("""
/**
* Auto-generated class via genTerrascriptAstClasses gradle task
*/
public abstract class ${clazz.name} {
""".trimIndent())
for (field in clazz.constructorFields) {
src.appendLine(" public final ${field.second} ${field.first};")
}
src.appendLine("""
|
| public ${clazz.name}(${clazz.constructorFields.joinToString { "${it.second} ${it.first}" }}) {
""".trimMargin())
for (field in clazz.constructorFields) {
src.appendLine(" this.${field.first} = ${field.first};")
}
src.appendLine("""
| }
|
| public interface Visitor<R> {
|
""".trimMargin())
for (node in clazz.nodes) {
src.appendLine(" R visit${node.name}${clazz.name}(${node.name} ${clazz.name.toLowerCase()});")
}
src.appendLine("""
|
| }
|
| public abstract <R> R accept(Visitor<R> visitor);
""".trimMargin())
for (node in clazz.nodes) {
src.appendLine()
// Inner class declaration
src.appendLine(" public static class ${node.name} extends ${clazz.name} {\n")
// Add fields
for (field in node.constructorFields) {
src.appendLine(" public final ${field.second} ${field.first};")
}
for (field in node.mutableFields) {
src.appendLine(" private ${field.second} ${field.first};")
}
src.appendLine()
// Add constructor
src.appendLine("""
| public ${node.name}(${node.constructorFields.plus(clazz.constructorFields).joinToString { "${it.second} ${it.first}" }}) {
| super(${clazz.constructorFields.joinToString { it.first }});
""".trimMargin())
for (field in node.constructorFields) {
src.appendLine(" this.${field.first} = ${field.first};")
}
src.appendLine(" }")
// Add getters and setters for mutable fields
for (field in node.mutableFields) {
src.appendLine("""
|
| public void set${field.first.capitalize()}(${field.second} value) {
| this.${field.first} = value;
| }
|
| public ${field.second} get${field.first.capitalize()}() {
| if (this.${field.first} == null) throw new RuntimeException("Compilation bug! Field ${field.first} has not been set yet");
| return this.${field.first};
| }
""".trimMargin())
}
src.appendLine("""
|
| @Override
| public <R> R accept(Visitor<R> visitor) {
| return visitor.visit${node.name}${clazz.name}(this);
| }
| }
""".trimMargin())
}
src.appendLine("}")
val outputFile = astPackage.resolve("${clazz.name}.java")
outputFile.writeText(src.toString())
}
doLast {
astSourceSet.deleteRecursively()
astPackage.mkdirs()
listOf(
ASTClass(
"Expr",
listOf(
"com.dfsek.terra.addons.terrascript.Type",
"com.dfsek.terra.addons.terrascript.parser.UnaryOperator",
"com.dfsek.terra.addons.terrascript.parser.BinaryOperator",
"com.dfsek.terra.addons.terrascript.Environment",
"com.dfsek.terra.addons.terrascript.Environment.Symbol",
"com.dfsek.terra.addons.terrascript.lexer.SourcePosition",
"java.util.List",
),
listOf(
ASTNode("Binary", listOf("left" to "Expr", "operator" to "BinaryOperator", "right" to "Expr")),
ASTNode("Grouping", listOf("expression" to "Expr")),
ASTNode("Literal", listOf("value" to "Object", "type" to "Type")),
ASTNode("Unary", listOf("operator" to "UnaryOperator", "operand" to "Expr")),
ASTNode("Call", listOf("identifier" to "String", "arguments" to "List<Expr>"), listOf("environment" to "Environment", "symbol" to "Symbol.Function")),
ASTNode("Variable", listOf("identifier" to "String"), listOf("symbol" to "Symbol.Variable")),
ASTNode("Assignment", listOf("lValue" to "Variable", "rValue" to "Expr")),
ASTNode("Void", listOf()),
),
listOf("position" to "SourcePosition")
),
ASTClass(
"Stmt",
listOf(
"com.dfsek.terra.addons.terrascript.Type",
"com.dfsek.terra.api.util.generic.pair.Pair",
"com.dfsek.terra.addons.terrascript.Environment.Symbol",
"com.dfsek.terra.addons.terrascript.lexer.SourcePosition",
"java.util.List",
"java.util.Optional",
),
listOf(
ASTNode("Expression", listOf("expression" to "Expr")),
ASTNode("Block", listOf("statements" to "List<Stmt>")),
ASTNode("FunctionDeclaration", listOf("identifier" to "String", "parameters" to "List<Pair<String, Type>>", "returnType" to "Type", "body" to "Block"), listOf("symbol" to "Symbol.Function")),
ASTNode("VariableDeclaration", listOf("type" to "Type", "identifier" to "String", "value" to "Expr")),
ASTNode("Return", listOf("value" to "Expr"), listOf("type" to "Type")),
ASTNode("If", listOf("condition" to "Expr", "trueBody" to "Block", "elseIfClauses" to "List<Pair<Expr, Block>>", "elseBody" to "Optional<Block>")),
ASTNode("For", listOf("initializer" to "Stmt", "condition" to "Expr", "incrementer" to "Expr", "body" to "Block")),
ASTNode("While", listOf("condition" to "Expr", "body" to "Block")),
ASTNode("NoOp", listOf()),
ASTNode("Break", listOf()),
ASTNode("Continue", listOf()),
),
listOf("position" to "SourcePosition")
),
ASTClass(
"TypedExpr",
listOf(
"com.dfsek.terra.addons.terrascript.Type",
"com.dfsek.terra.addons.terrascript.parser.UnaryOperator",
"com.dfsek.terra.addons.terrascript.parser.BinaryOperator",
"java.util.List",
),
listOf(
ASTNode("Binary", listOf("left" to "TypedExpr", "operator" to "BinaryOperator", "right" to "TypedExpr")),
ASTNode("Grouping", listOf("expression" to "TypedExpr")),
ASTNode("Literal", listOf("value" to "Object")),
ASTNode("Unary", listOf("operator" to "UnaryOperator", "operand" to "TypedExpr")),
ASTNode("Call", listOf("identifier" to "String", "arguments" to "List<TypedExpr>", "scopedIdentifier" to "String")),
ASTNode("Variable", listOf("identifier" to "String")),
ASTNode("Assignment", listOf("lValue" to "Variable", "rValue" to "TypedExpr")),
ASTNode("Void", listOf()),
),
listOf("type" to "Type")
),
ASTClass(
"TypedStmt",
listOf(
"com.dfsek.terra.addons.terrascript.Type",
"com.dfsek.terra.api.util.generic.pair.Pair",
"java.util.List",
"java.util.Optional",
),
listOf(
ASTNode("Expression", listOf("expression" to "TypedExpr")),
ASTNode("Block", listOf("statements" to "List<TypedStmt>")),
ASTNode("FunctionDeclaration", listOf("identifier" to "String", "parameters" to "List<Pair<String, Type>>", "returnType" to "Type", "body" to "Block", "scopedIdentifier" to "String")),
ASTNode("VariableDeclaration", listOf("type" to "Type", "identifier" to "String", "value" to "TypedExpr")),
ASTNode("Return", listOf("value" to "TypedExpr")),
ASTNode("If", listOf("condition" to "TypedExpr", "trueBody" to "Block", "elseIfClauses" to "List<Pair<TypedExpr, Block>>", "elseBody" to "Optional<Block>")),
ASTNode("For", listOf("initializer" to "TypedStmt", "condition" to "TypedExpr", "incrementer" to "TypedExpr", "body" to "Block")),
ASTNode("While", listOf("condition" to "TypedExpr", "body" to "Block")),
ASTNode("NoOp", listOf()),
ASTNode("Break", listOf()),
ASTNode("Continue", listOf()),
),
),
).forEach(::generateClass)
}
}
tasks.getByName("compileJava") {
dependsOn("genTerrascriptAstClasses")
}
sourceSets.getByName("main") {
java {
srcDirs(astSourceSet)
}
}
}
@@ -1,158 +0,0 @@
package com.dfsek.terra.addons.terrascript;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.dfsek.terra.addons.terrascript.Environment.ScopeException.NonexistentSymbolException;
import com.dfsek.terra.addons.terrascript.Environment.ScopeException.SymbolAlreadyExistsException;
import com.dfsek.terra.addons.terrascript.Environment.ScopeException.SymbolTypeMismatchException;
import com.dfsek.terra.addons.terrascript.Environment.Symbol.Function;
import com.dfsek.terra.addons.terrascript.Environment.Symbol.Variable;
import com.dfsek.terra.addons.terrascript.codegen.NativeFunction;
public class Environment {
private final Environment outer;
private final boolean canAccessOuterVariables;
private final Map<String, Symbol> symbolTable = new HashMap<>();
private final boolean inLoop;
private final int index;
private int innerCount = 0;
public final String name;
private Environment(@Nullable Environment outer, boolean canAccessOuterVariables, boolean inLoop, int index) {
this.outer = outer;
this.canAccessOuterVariables = canAccessOuterVariables;
this.inLoop = inLoop;
this.index = index;
this.name = String.join("_", getNestedIndexes().stream().map(Object::toString).toList());
// Populate symbol tables with built-in Java implemented methods
NativeFunction.BUILTIN_FUNCTIONS.forEach((name, function) -> symbolTable.put(name, new Symbol.Function(function.getReturnType(), function.getParameterTypes(), this)));
}
public static Environment global() {
return new Environment(null, false, false, 0);
}
public Environment lexicalInner() {
return new Environment(this, true, inLoop, innerCount++);
}
public Environment loopInner() {
return new Environment(this, true, true, innerCount++);
}
public Environment functionalInner() {
return new Environment(this, false, inLoop, innerCount++);
}
private List<Integer> getNestedIndexes() {
List<Integer> idxs = new ArrayList<>();
for (Environment env = this; env.outer != null; env = env.outer) {
idxs.add(0, env.index);
}
return idxs;
}
public Environment outer() {
if(outer == null) throw new RuntimeException("Attempted to retrieve outer scope of global scope");
return outer;
}
public Function getFunction(String id) throws NonexistentSymbolException, SymbolTypeMismatchException {
Function function;
Symbol symbol = symbolTable.get(id);
if(symbol == null) {
if(outer == null) throw new NonexistentSymbolException();
function = outer.getFunction(id);
} else {
if(!(symbol instanceof Function)) throw new SymbolTypeMismatchException();
function = (Function) symbol;
}
return function;
}
/**
* Returns symbol table entry for a variable identifier, includes enclosing scopes in lookup.
* <br>
* Does not factor context of lookup, checks for order of declaration should be done while
* symbol tables are being populated.
*
* @param id identifier used in variable declaration
*
* @return variable symbol table entry
*
* @throws NonexistentSymbolException if symbol is not declared in symbol table
* @throws SymbolTypeMismatchException if symbol is not a variable
*/
public Variable getVariable(String id) throws NonexistentSymbolException, SymbolTypeMismatchException {
Variable variable;
Symbol symbol = symbolTable.get(id);
if(symbol == null) {
if(!canAccessOuterVariables || outer == null) throw new NonexistentSymbolException();
variable = outer.getVariable(id);
} else {
if(!(symbol instanceof Variable)) throw new SymbolTypeMismatchException();
variable = (Variable) symbol;
}
return variable;
}
public void put(String id, Symbol symbol) throws SymbolAlreadyExistsException {
if(symbolTable.containsKey(id)) throw new SymbolAlreadyExistsException();
symbolTable.put(id, symbol);
}
public static abstract class Symbol {
public static class Function extends Symbol {
public final Type type;
public final List<Type> parameters;
public final Environment scope;
public Function(Type type, List<Type> parameters, Environment scope) {
this.type = type;
this.parameters = parameters;
this.scope = scope;
}
}
public static class Variable extends Symbol {
public final Type type;
public Variable(Type type) {
this.type = type;
}
}
}
public static class ScopeException extends RuntimeException {
public static class SymbolAlreadyExistsException extends ScopeException {
}
public static class NonexistentSymbolException extends ScopeException {
}
public static class SymbolTypeMismatchException extends ScopeException {
}
}
}
@@ -1,20 +0,0 @@
package com.dfsek.terra.addons.terrascript;
import java.util.ArrayList;
import java.util.List;
public class ErrorHandler {
private final List<Exception> exceptions = new ArrayList<>();
public void add(Exception e) {
exceptions.add(e);
}
public void throwAny() throws Exception {
for(Exception e : exceptions) {
throw e;
}
}
}
@@ -8,6 +8,9 @@
package com.dfsek.terra.addons.terrascript;
import com.dfsek.terra.addons.manifest.api.AddonInitializer;
import com.dfsek.terra.addons.terrascript.parser.exceptions.ParseException;
import com.dfsek.terra.addons.terrascript.parser.lang.functions.FunctionBuilder;
import com.dfsek.terra.addons.terrascript.script.StructureScript;
import com.dfsek.terra.api.Platform;
import com.dfsek.terra.api.addon.BaseAddon;
import com.dfsek.terra.api.event.events.config.pack.ConfigPackPreLoadEvent;
@@ -28,14 +31,12 @@ public class TerraScriptAddon implements AddonInitializer {
@Override
public void initialize() {
platform.getEventManager()
.getHandler(FunctionalEventHandler.class)
.register(addon, ConfigPackPreLoadEvent.class)
.then(event -> {
CheckedRegistry<Structure> structureRegistry = event.getPack().getOrCreateRegistry(Structure.class);
CheckedRegistry<LootTable> lootRegistry = event.getPack().getOrCreateRegistry(LootTable.class);
/*
event.getPack().getLoader().open("", ".tesf").thenEntries(
entries ->
entries.stream()
@@ -56,8 +57,6 @@ public class TerraScriptAddon implements AddonInitializer {
.toList()
.forEach(structureRegistry::register))
.close();
*/
})
.priority(100)
.failThrough();
@@ -1,97 +0,0 @@
package com.dfsek.terra.addons.terrascript;
import java.util.Optional;
public interface Type {
static Type fromString(String lexeme) throws TypeException {
return switch(lexeme) {
case "num" -> NUMBER;
case "int" -> INTEGER;
case "str" -> STRING;
case "bool" -> BOOLEAN;
case "()" -> VOID;
default -> throw new TypeException();
};
}
java.lang.reflect.Type javaType();
default boolean typeOf(Type type) {
return this.equals(type);
}
static Optional<Type> from(Class<?> clazz) {
return Optional.ofNullable(
clazz == double.class ? NUMBER :
clazz == String.class ? STRING :
clazz == boolean.class ? BOOLEAN :
clazz == void.class ? VOID :
null);
}
Type NUMBER = new Type() {
@Override
public java.lang.reflect.Type javaType() {
return double.class;
}
@Override
public String toString() {
return "num";
}
};
Type INTEGER = new Type() {
@Override
public java.lang.reflect.Type javaType() {
return int.class;
}
@Override
public String toString() {
return "int";
}
};
Type STRING = new Type() {
@Override
public java.lang.reflect.Type javaType() {
return String.class;
}
@Override
public String toString() {
return "str";
}
};
Type BOOLEAN = new Type() {
@Override
public java.lang.reflect.Type javaType() {
return boolean.class;
}
@Override
public String toString() {
return "bool";
}
};
Type VOID = new Type() {
@Override
public java.lang.reflect.Type javaType() {
return void.class;
}
@Override
public String toString() {
return "()";
}
};
class TypeException extends Exception {
}
}
@@ -1,141 +0,0 @@
package com.dfsek.terra.addons.terrascript.codegen;
import com.dfsek.terra.addons.terrascript.Type;
import com.dfsek.terra.addons.terrascript.exception.CompilerBugException;
import org.objectweb.asm.Opcodes;
import java.util.Map;
public class CodegenType {
private final InstructionType instructionType;
private final String descriptor;
public CodegenType(InstructionType instructionType, String descriptor) {
this.instructionType = instructionType;
this.descriptor = descriptor;
}
public InstructionType bytecodeType() {
return instructionType;
}
public String getDescriptor() {
return descriptor;
}
public static final CodegenType BOOLEAN_PRIMITIVE = new CodegenType(InstructionType.INTEGER, "Z");
private static final Map<Type, CodegenType> TYPE_MAP = Map.of(
Type.BOOLEAN, BOOLEAN_PRIMITIVE,
Type.STRING, new CodegenType(InstructionType.OBJECT, "Ljava/lang/String;"),
Type.NUMBER, new CodegenType(InstructionType.DOUBLE, "D"),
Type.VOID, new CodegenType(InstructionType.VOID, "V")
);
public static CodegenType codegenType(Type type) {
CodegenType out = TYPE_MAP.get(type);
if(out == null)
throw new CompilerBugException();
return out;
}
public enum InstructionType {
DOUBLE {
@Override
public int slotSize() {
return 2;
}
@Override
public int returnInsn() {
return Opcodes.DRETURN;
}
@Override
public int loadInsn() {
return Opcodes.DLOAD;
}
@Override
public int storeInsn() {
return Opcodes.DSTORE;
}
},
OBJECT {
@Override
public int slotSize() {
return 1;
}
@Override
public int returnInsn() {
return Opcodes.ARETURN;
}
@Override
public int loadInsn() {
return Opcodes.ALOAD;
}
@Override
public int storeInsn() {
return Opcodes.ASTORE;
}
},
INTEGER {
@Override
public int slotSize() {
return 1;
}
@Override
public int returnInsn() {
return Opcodes.IRETURN;
}
@Override
public int loadInsn() {
return Opcodes.ILOAD;
}
@Override
public int storeInsn() {
return Opcodes.ISTORE;
}
},
VOID {
@Override
public int slotSize() {
throw new UnsupportedOperationException();
}
@Override
public int returnInsn() {
return Opcodes.RETURN;
}
@Override
public int loadInsn() {
throw new UnsupportedOperationException();
}
@Override
public int storeInsn() {
throw new UnsupportedOperationException();
}
};
public abstract int slotSize();
public abstract int returnInsn();
public abstract int loadInsn();
public abstract int storeInsn();
}
}
@@ -1,86 +0,0 @@
package com.dfsek.terra.addons.terrascript.codegen;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.dfsek.terra.addons.terrascript.Type;
public interface NativeFunction {
Map<String, NativeFunction> BUILTIN_FUNCTIONS = new HashMap<>() {{
put("print", new StaticMethodOfStaticField(
"java/lang/System",
"out",
"java/io/PrintStream",
"println",
"(Ljava/lang/String;)V",
Type.VOID,
List.of(Type.STRING))
);
put("printNum", new StaticMethodOfStaticField(
"java/lang/System",
"out",
"java/io/PrintStream",
"println",
"(D)V",
Type.VOID,
List.of(Type.NUMBER))
);
}};
void pushInstance(MethodVisitor method);
void callMethod(MethodVisitor method);
Type getReturnType();
List<Type> getParameterTypes();
class StaticMethodOfStaticField implements NativeFunction {
private final String fieldOwner;
private final String fieldName;
private final String className;
private final String methodName;
private final String methodDescriptor;
private final Type returnType;
private final List<Type> parameters;
// TODO - Use reflection to obtain these automatically
public StaticMethodOfStaticField(String fieldOwner, String fieldName, String className, String methodName, String methodDescriptor,
Type returnType, List<Type> parameters) {
this.fieldOwner = fieldOwner;
this.fieldName = fieldName;
this.className = className;
this.methodName = methodName;
this.methodDescriptor = methodDescriptor;
this.returnType = returnType;
this.parameters = parameters;
}
@Override
public void pushInstance(MethodVisitor method) {
method.visitFieldInsn(Opcodes.GETSTATIC, fieldOwner, fieldName, "L" + className + ";");
}
@Override
public void callMethod(MethodVisitor method) {
method.visitMethodInsn(Opcodes.INVOKEVIRTUAL, className, methodName, methodDescriptor, false);
}
@Override
public Type getReturnType() {
return returnType;
}
@Override
public List<Type> getParameterTypes() {
return parameters;
}
}
}
@@ -1,8 +0,0 @@
package com.dfsek.terra.addons.terrascript.codegen;
public interface TerraScript {
void execute();
}
@@ -1,19 +0,0 @@
package com.dfsek.terra.addons.terrascript.codegen.asm;
import com.dfsek.terra.api.structure.Structure;
public class DynamicClassLoader extends ClassLoader {
public DynamicClassLoader(Class<?> clazz) {
super(clazz.getClassLoader());
}
public Class<?> defineClass(String name, byte[] data) {
return defineClass(name, data, 0, data.length);
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
return Class.forName(name);
}
}
@@ -1,17 +0,0 @@
package com.dfsek.terra.addons.terrascript.codegen.asm;
import org.objectweb.asm.Opcodes;
public class OpcodeAlias {
public static int CMP_GREATER_THAN = Opcodes.IFGT;
public static int CMP_GREATER_EQUALS = Opcodes.IFGE;
public static int CMP_LESS_THAN = Opcodes.IFLT;
public static int CMP_LESS_EQUALS = Opcodes.IFLE;
public static int CMP_EQUALS = Opcodes.IFEQ;
public static int CMP_NOT_EQUALS = Opcodes.IFNE;
public static int BOOL_FALSE = Opcodes.IFEQ;
public static int BOOL_TRUE = Opcodes.IFNE;
public static int INTEGERS_EQUAL = Opcodes.IF_ICMPEQ;
public static int INTEGERS_NOT_EQUAL = Opcodes.IF_ICMPNE;
}
@@ -1,565 +0,0 @@
package com.dfsek.terra.addons.terrascript.codegen.asm;
import com.dfsek.terra.addons.terrascript.Type;
import com.dfsek.terra.addons.terrascript.ast.TypedExpr;
import com.dfsek.terra.addons.terrascript.ast.TypedExpr.Assignment;
import com.dfsek.terra.addons.terrascript.ast.TypedExpr.Binary;
import com.dfsek.terra.addons.terrascript.ast.TypedExpr.Call;
import com.dfsek.terra.addons.terrascript.ast.TypedExpr.Grouping;
import com.dfsek.terra.addons.terrascript.ast.TypedExpr.Literal;
import com.dfsek.terra.addons.terrascript.ast.TypedExpr.Unary;
import com.dfsek.terra.addons.terrascript.ast.TypedExpr.Variable;
import com.dfsek.terra.addons.terrascript.ast.TypedExpr.Void;
import com.dfsek.terra.addons.terrascript.ast.TypedStmt;
import com.dfsek.terra.addons.terrascript.ast.TypedStmt.Block;
import com.dfsek.terra.addons.terrascript.ast.TypedStmt.Break;
import com.dfsek.terra.addons.terrascript.ast.TypedStmt.Continue;
import com.dfsek.terra.addons.terrascript.ast.TypedStmt.Expression;
import com.dfsek.terra.addons.terrascript.ast.TypedStmt.For;
import com.dfsek.terra.addons.terrascript.ast.TypedStmt.FunctionDeclaration;
import com.dfsek.terra.addons.terrascript.ast.TypedStmt.If;
import com.dfsek.terra.addons.terrascript.ast.TypedStmt.NoOp;
import com.dfsek.terra.addons.terrascript.ast.TypedStmt.Return;
import com.dfsek.terra.addons.terrascript.ast.TypedStmt.VariableDeclaration;
import com.dfsek.terra.addons.terrascript.ast.TypedStmt.While;
import com.dfsek.terra.addons.terrascript.codegen.CodegenType;
import com.dfsek.terra.addons.terrascript.codegen.CodegenType.InstructionType;
import com.dfsek.terra.addons.terrascript.codegen.NativeFunction;
import com.dfsek.terra.addons.terrascript.codegen.TerraScript;
import com.dfsek.terra.addons.terrascript.exception.CompilerBugException;
import com.dfsek.terra.addons.terrascript.util.ASMUtil;
import com.dfsek.terra.api.util.generic.pair.Pair;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.commons.LocalVariablesSorter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import static com.dfsek.terra.addons.terrascript.codegen.CodegenType.BOOLEAN_PRIMITIVE;
import static com.dfsek.terra.addons.terrascript.codegen.CodegenType.codegenType;
import static com.dfsek.terra.addons.terrascript.codegen.asm.OpcodeAlias.CMP_EQUALS;
import static com.dfsek.terra.addons.terrascript.codegen.asm.OpcodeAlias.BOOL_FALSE;
import static com.dfsek.terra.addons.terrascript.codegen.asm.OpcodeAlias.CMP_GREATER_EQUALS;
import static com.dfsek.terra.addons.terrascript.codegen.asm.OpcodeAlias.CMP_GREATER_THAN;
import static com.dfsek.terra.addons.terrascript.codegen.asm.OpcodeAlias.CMP_LESS_EQUALS;
import static com.dfsek.terra.addons.terrascript.codegen.asm.OpcodeAlias.CMP_LESS_THAN;
import static com.dfsek.terra.addons.terrascript.codegen.asm.OpcodeAlias.CMP_NOT_EQUALS;
import static com.dfsek.terra.addons.terrascript.codegen.asm.OpcodeAlias.BOOL_TRUE;
import static com.dfsek.terra.addons.terrascript.codegen.asm.OpcodeAlias.INTEGERS_EQUAL;
import static com.dfsek.terra.addons.terrascript.codegen.asm.OpcodeAlias.INTEGERS_NOT_EQUAL;
import static com.dfsek.terra.addons.terrascript.util.ASMUtil.dynamicName;
public class TerraScriptClassGenerator {
private static final Class<?> TARGET_CLASS = TerraScript.class;
private static final boolean DUMP = true;
private int generationCount = 0;
private final String debugPath;
public TerraScriptClassGenerator(String debugPath) {
this.debugPath = debugPath;
}
/**
*
* @param root Assumed to be semantically correct
* @return Generated TerraScript instance
* @throws IOException
*/
public TerraScript generate(Block root) throws IOException {
String targetClassName = dynamicName(TARGET_CLASS);
String generatedClassName = targetClassName + "_GENERATED_" + generationCount;
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
// Create class
classWriter.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, generatedClassName, null, "java/lang/Object", new String[]{ targetClassName });
// Generate constructor method
MethodVisitor constructor = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);
constructor.visitCode();
constructor.visitVarInsn(Opcodes.ALOAD, 0); // Put this reference on stack
constructor.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
constructor.visitInsn(Opcodes.RETURN); // Void return
constructor.visitMaxs(0, 0);
constructor.visitEnd();
// Generate execute method
String methodName = "execute";
// Extract method description
MethodExtractor extractor = new MethodExtractor(methodName);
new ClassReader(targetClassName).accept(extractor, 0);
String description = extractor.methodDescription;
int exeAcc = Opcodes.ACC_PUBLIC;
MethodVisitor executeMethod = classWriter.visitMethod(exeAcc, methodName, description, null, null);
executeMethod.visitCode(); // Start method body
new MethodBytecodeGenerator(classWriter, generatedClassName, executeMethod, exeAcc, description).generate(root); // Generate bytecode
// Finish up method
executeMethod.visitInsn(Opcodes.RETURN);
executeMethod.visitMaxs(0, 0);
executeMethod.visitEnd();
// Finished generating class
classWriter.visitEnd();
DynamicClassLoader loader = new DynamicClassLoader(TARGET_CLASS); // Instantiate a new loader every time so classes can be GC'ed when they are no longer used. (Classes cannot be GC'ed until their loaders are).
generationCount++;
byte[] bytecode = classWriter.toByteArray();
Class<?> generatedClass = loader.defineClass(generatedClassName.replace('/', '.'), bytecode);
if (DUMP) {
File dump = new File(debugPath + "/" + generatedClass.getSimpleName() + ".class");
dump.getParentFile().mkdirs();
try(FileOutputStream out = new FileOutputStream(dump)) {
out.write(bytecode);
} catch(IOException e) {
e.printStackTrace();
}
}
try {
Object instance = generatedClass.getDeclaredConstructor().newInstance();
return (TerraScript) instance;
} catch(ReflectiveOperationException e) {
throw new Error(e); // Should literally never happen
}
}
private static class MethodBytecodeGenerator implements TypedStmt.Visitor<Void>, TypedExpr.Visitor<Void> {
private final ClassWriter classWriter;
private final String className;
private final MethodVisitor method;
private final String descriptor;
private final LocalVariablesSorter lvs;
private final Map<String, Integer> lvTable = new HashMap<>();
private final Deque<Pair<Label, Label>> loopStack = new ArrayDeque<>();
public MethodBytecodeGenerator(ClassWriter classWriter, String className, MethodVisitor method, int access, String descriptor) {
this.classWriter = classWriter;
this.className = className;
this.method = method;
this.descriptor = descriptor;
this.lvs = new LocalVariablesSorter(access, descriptor, method);
}
public void generate(Block root) {
this.visitBlockTypedStmt(root);
}
@Override
public Void visitBinaryTypedExpr(Binary expr) {
switch(expr.operator) {
case EQUALS, NOT_EQUALS, BOOLEAN_AND, BOOLEAN_OR, GREATER, GREATER_EQUALS, LESS, LESS_EQUALS -> pushComparisonBool(expr);
case ADD -> {
pushBinaryOperands(expr);
CodegenType codegenType = codegenType(expr.type);
if(codegenType.bytecodeType() == InstructionType.DOUBLE)
method.visitInsn(Opcodes.DADD);
else if (Objects.equals(codegenType.getDescriptor(), "Ljava/lang/String;"))
// TODO - Optimize string concatenation
method.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/String", "concat", "(Ljava/lang/String;)Ljava/lang/String;", false);
else throw new RuntimeException("Could not generate bytecode for ADD binary operator returning type " + expr.type);
}
case SUBTRACT -> binaryInsn(expr, Opcodes.DSUB);
case MULTIPLY -> binaryInsn(expr, Opcodes.DMUL);
case DIVIDE -> binaryInsn(expr, Opcodes.DDIV);
default -> throw new RuntimeException("Unhandled binary operator " + expr.operator);
}
return null;
}
@Override
public Void visitGroupingTypedExpr(Grouping expr) {
expr.expression.accept(this);
return null;
}
@Override
public Void visitLiteralTypedExpr(Literal expr) {
if(codegenType(expr.type) == BOOLEAN_PRIMITIVE)
if ((boolean) expr.value) pushTrue(); else pushFalse();
else method.visitLdcInsn(expr.value);
return null;
}
@Override
public Void visitUnaryTypedExpr(Unary expr) {
expr.operand.accept(this);
switch (expr.operator) {
case NOT -> invertBool();
case NEGATE -> method.visitInsn(Opcodes.DNEG);
}
return null;
}
@Override
public Void visitCallTypedExpr(Call expr) {
if (NativeFunction.BUILTIN_FUNCTIONS.containsKey(expr.identifier)) {
NativeFunction function = NativeFunction.BUILTIN_FUNCTIONS.get(expr.identifier);
function.pushInstance(method);
expr.arguments.forEach(a -> a.accept(this));
function.callMethod(method);
return null;
}
expr.arguments.forEach(a -> a.accept(this));
List<Type> parameters = expr.arguments.stream().map(e -> e.type).toList();
method.visitMethodInsn(Opcodes.INVOKESTATIC, className, expr.scopedIdentifier, getFunctionDescriptor(parameters, expr.type), false);
return null;
}
@Override
public Void visitVariableTypedExpr(Variable expr) {
Type varType = expr.type;
method.visitVarInsn(codegenType(varType).bytecodeType().loadInsn(), lvTable.get(expr.identifier));
return null;
}
@Override
public Void visitAssignmentTypedExpr(Assignment expr) {
expr.rValue.accept(this);
Type type = expr.lValue.type;
method.visitVarInsn(codegenType(type).bytecodeType().storeInsn(), lvTable.get(expr.lValue.identifier));
return null;
}
@Override
public Void visitVoidTypedExpr(Void expr) {
return null;
}
@Override
public Void visitExpressionTypedStmt(Expression stmt) {
stmt.expression.accept(this);
return null;
}
@Override
public Void visitBlockTypedStmt(Block stmt) {
stmt.statements.forEach(s -> s.accept(this));
return null;
}
/**
* Writes function as a private static method of the current class
* @param stmt
* @return
*/
@Override
public Void visitFunctionDeclarationTypedStmt(FunctionDeclaration stmt) {
List<Type> parameterTypes = stmt.parameters.stream().map(Pair::getRight).toList();
int access = Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC;
MethodVisitor method = classWriter.visitMethod(access, stmt.scopedIdentifier, getFunctionDescriptor(parameterTypes, stmt.returnType), null, null);
method.visitCode(); // Start method body
MethodBytecodeGenerator funcGenerator = new MethodBytecodeGenerator(classWriter, className, method, access, descriptor);
// Add local variable indexes for each parameter
int lvidx = 0;
for (Pair<String, Type> parameter : stmt.parameters) {
funcGenerator.lvTable.put(parameter.getLeft(), lvidx);
lvidx += codegenType(parameter.getRight()).bytecodeType().slotSize(); // Increment by how many slots data type takes
}
// Generate method bytecode
funcGenerator.generate(stmt.body);
// Finish up
method.visitInsn(Opcodes.RETURN);
method.visitMaxs(0, 0);
method.visitEnd();
return null;
}
@Override
public Void visitVariableDeclarationTypedStmt(VariableDeclaration stmt) {
stmt.value.accept(this);
lvTable.put(stmt.identifier, lvs.newLocal(ASMUtil.tsTypeToAsmType(stmt.type)));
method.visitVarInsn(codegenType(stmt.type).bytecodeType().storeInsn(), lvTable.get(stmt.identifier));
return null;
}
@Override
public Void visitReturnTypedStmt(Return stmt) {
stmt.value.accept(this);
method.visitInsn(codegenType(stmt.value.type).bytecodeType().returnInsn());
return null;
}
@Override
public Void visitIfTypedStmt(If stmt) {
Label endIf = new Label();
conditionalStmt(stmt.condition, stmt.trueBody, endIf);
for(Pair<TypedExpr, Block> elseIfClause : stmt.elseIfClauses) {
conditionalStmt(elseIfClause.getLeft(), elseIfClause.getRight(), endIf);
}
stmt.elseBody.ifPresent(b -> b.accept(this));
label(endIf);
return null;
}
@Override
public Void visitForTypedStmt(For stmt) {
Label loopStart = new Label();
Label loopBody = new Label();
Label loopEnd = new Label();
stmt.initializer.accept(this);
jump(loopBody); // Skip over incrementer on first loop
label(loopStart);
stmt.incrementer.accept(this);
label(loopBody);
loopStack.push(Pair.of(loopStart, loopEnd));
conditionalStmt(stmt.condition, stmt.body, loopStart);
loopStack.pop();
label(loopEnd);
return null;
}
@Override
public Void visitWhileTypedStmt(While stmt) {
Label loopStart = new Label();
Label loopEnd = new Label();
label(loopStart);
loopStack.push(Pair.of(loopStart, loopEnd));
conditionalStmt(stmt.condition, stmt.body, loopStart);
loopStack.pop();
label(loopEnd);
return null;
}
@Override
public Void visitNoOpTypedStmt(NoOp stmt) {
return null;
}
@Override
public Void visitBreakTypedStmt(Break stmt) {
jump(loopStack.getFirst().getRight());
return null;
}
@Override
public Void visitContinueTypedStmt(Continue stmt) {
jump(loopStack.getFirst().getLeft());
return null;
}
private boolean binaryOperandsSameType(Type type, Binary expr) {
return exprTypesEqual(type, expr.left, expr.right);
}
private static boolean exprTypesEqual(Type type, TypedExpr... exprs) {
for(TypedExpr expr : exprs) {
if (expr.type != type) return false;
}
return true;
}
/**
* Inverts a boolean on the stack
*/
private void invertBool() {
Label invertToFalse = new Label();
Label finished = new Label();
jumpIf(BOOL_TRUE, invertToFalse);
pushFalse();
jump(finished);
label(invertToFalse);
pushFalse();
label(finished);
}
private void pushBinaryOperands(Binary expr) {
expr.left.accept(this);
expr.right.accept(this);
}
private void binaryInsn(Binary expr, int insn) {
pushBinaryOperands(expr);
method.visitInsn(insn);
}
/**
* Pushes boolean on to the stack based on comparison result
* @param condition
*/
private void pushComparisonBool(TypedExpr condition) {
Label trueFinished = new Label();
conditionalRunnable(condition, this::pushTrue, trueFinished);
pushFalse();
label(trueFinished);
}
/**
* Executes a statement then jumps to the exit label if the condition is true, jumps over the statement if false
* @param condition
* @param stmt
* @param exit
*/
private void conditionalStmt(TypedExpr condition, TypedStmt stmt, Label exit) {
conditionalRunnable(condition, () -> stmt.accept(this), exit);
}
private void pushTrue() {
method.visitInsn(Opcodes.ICONST_1);
}
private void pushFalse() {
method.visitInsn(Opcodes.ICONST_0);
}
private void jumpIf(int opcode, Label label) {
method.visitJumpInsn(opcode, label);
}
private void jump(Label label) {
method.visitJumpInsn(Opcodes.GOTO, label);
}
private void label(Label label) {
method.visitLabel(label);
}
private void conditionalRunnable(TypedExpr condition, Runnable trueBlock, Label trueFinished) {
Label exit = new Label(); // If the first conditional is false, jump over statement and don't execute it
if (condition instanceof Binary binaryCondition) {
switch(binaryCondition.operator) {
case BOOLEAN_AND -> {
// Operands assumed booleans
binaryCondition.left.accept(this);
jumpIf(BOOL_FALSE, exit); // If left is false, short circuit, don't evaluate right
binaryCondition.right.accept(this);
jumpIf(BOOL_FALSE, exit);
}
case BOOLEAN_OR -> {
Label skipRight = new Label();
// Operands assumed booleans
binaryCondition.left.accept(this);
jumpIf(BOOL_TRUE, skipRight);
binaryCondition.right.accept(this);
jumpIf(BOOL_FALSE, exit);
label(skipRight);
}
case EQUALS -> {
if (binaryOperandsSameType(Type.BOOLEAN, binaryCondition)) { // Operands assumed integers
pushBinaryOperands(binaryCondition);
jumpIf(INTEGERS_NOT_EQUAL, exit);
} else if (binaryOperandsSameType(Type.NUMBER, binaryCondition)) { // Operands assumed doubles
binaryInsn(binaryCondition, Opcodes.DCMPG);
jumpIf(CMP_NOT_EQUALS, exit);
} else if (binaryOperandsSameType(Type.STRING, binaryCondition)) {
pushBinaryOperands(binaryCondition);
method.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/String", "equals", "(Ljava/lang/Object;)Z", false);
jumpIf(BOOL_FALSE, exit);
} else throw new CompilerBugException();
}
case NOT_EQUALS -> {
if (binaryOperandsSameType(Type.BOOLEAN, binaryCondition)) { // Operands assumed integers
pushBinaryOperands(binaryCondition);
jumpIf(INTEGERS_EQUAL, exit);
} else if (binaryOperandsSameType(Type.NUMBER, binaryCondition)) { // Operands assumed doubles
binaryInsn(binaryCondition, Opcodes.DCMPG);
jumpIf(CMP_EQUALS, exit);
} else if (binaryOperandsSameType(Type.STRING, binaryCondition)) { // Operands assumed references
pushBinaryOperands(binaryCondition);
method.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/String", "equals", "(Ljava/lang/Object;)Z", false);
invertBool();
jumpIf(CMP_EQUALS, exit);
} else throw new CompilerBugException();
}
case GREATER, GREATER_EQUALS, LESS, LESS_EQUALS -> {
// Left and right assumed double
binaryInsn(binaryCondition, switch(binaryCondition.operator) {
case GREATER, GREATER_EQUALS -> Opcodes.DCMPL;
case LESS, LESS_EQUALS -> Opcodes.DCMPG;
default -> throw new IllegalStateException();
});
jumpIf(switch(binaryCondition.operator) {
case GREATER -> CMP_LESS_EQUALS;
case GREATER_EQUALS -> CMP_LESS_THAN;
case LESS -> CMP_GREATER_EQUALS;
case LESS_EQUALS -> CMP_GREATER_THAN;
default -> throw new IllegalStateException();
}, exit);
}
default -> throw new CompilerBugException();
}
} else {
// Assume condition returns bool
condition.accept(this);
jumpIf(BOOL_FALSE, exit);
}
trueBlock.run();
jump(trueFinished); // Jump to end of statement after execution
label(exit);
}
private String getFunctionDescriptor(List<Type> parameters, Type returnType) {
StringBuilder sb = new StringBuilder().append("(");
parameters.stream().map(parameter -> codegenType(parameter).getDescriptor()).forEach(sb::append);
sb.append(")");
sb.append(codegenType(returnType).getDescriptor());
return sb.toString();
}
}
private static class MethodExtractor extends ClassVisitor {
private final String methodName;
private String methodDescription;
protected MethodExtractor(String methodName) {
super(Opcodes.ASM9);
this.methodName = methodName;
}
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
if (name.equals(methodName))
methodDescription = descriptor;
return super.visitMethod(access, name, descriptor, signature, exceptions);
}
}
}
@@ -1,33 +0,0 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.terrascript.exception;
import java.io.Serial;
import com.dfsek.terra.addons.terrascript.lexer.SourcePosition;
public class CompilationException extends Exception {
@Serial
private static final long serialVersionUID = 6744390543046766386L;
private final SourcePosition position;
public CompilationException(String message, SourcePosition position) {
super(message);
this.position = position;
}
@Override
public String getMessage() {
return "Error at " + position + ": " + super.getMessage();
}
public SourcePosition getPosition() {
return position;
}
}
@@ -1,5 +0,0 @@
package com.dfsek.terra.addons.terrascript.exception;
public class CompilerBugException extends RuntimeException {
// TODO - Add message constructor
}
@@ -1,11 +0,0 @@
package com.dfsek.terra.addons.terrascript.exception.semanticanalysis;
import com.dfsek.terra.addons.terrascript.exception.CompilationException;
import com.dfsek.terra.addons.terrascript.lexer.SourcePosition;
public class IdentifierAlreadyDeclaredException extends CompilationException {
public IdentifierAlreadyDeclaredException(String message, SourcePosition position) {
super(message, position);
}
}
@@ -1,11 +0,0 @@
package com.dfsek.terra.addons.terrascript.exception.semanticanalysis;
import com.dfsek.terra.addons.terrascript.exception.CompilationException;
import com.dfsek.terra.addons.terrascript.lexer.SourcePosition;
public class InvalidFunctionDeclarationException extends CompilationException {
public InvalidFunctionDeclarationException(String message, SourcePosition position) {
super(message, position);
}
}
@@ -1,11 +0,0 @@
package com.dfsek.terra.addons.terrascript.exception.semanticanalysis;
import com.dfsek.terra.addons.terrascript.exception.CompilationException;
import com.dfsek.terra.addons.terrascript.lexer.SourcePosition;
public class InvalidTypeException extends CompilationException {
public InvalidTypeException(String message, SourcePosition position) {
super(message, position);
}
}
@@ -1,11 +0,0 @@
package com.dfsek.terra.addons.terrascript.exception.semanticanalysis;
import com.dfsek.terra.addons.terrascript.exception.CompilationException;
import com.dfsek.terra.addons.terrascript.lexer.SourcePosition;
public class UndefinedReferenceException extends CompilationException {
public UndefinedReferenceException(String message, SourcePosition position) {
super(message, position);
}
}
@@ -1,256 +0,0 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.terrascript.lexer;
import com.dfsek.terra.addons.terrascript.parser.ParseException;
import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import com.dfsek.terra.addons.terrascript.exception.lexer.EOFException;
import com.dfsek.terra.addons.terrascript.exception.lexer.FormatException;
import com.dfsek.terra.addons.terrascript.exception.lexer.TokenizerException;
import com.dfsek.terra.addons.terrascript.lexer.Token.TokenType;
public class Lexer {
public static final Set<Character> syntaxSignificant = Sets.newHashSet(':', ';', '(', ')', '"', ',', '\\', '=', '{', '}', '+', '-', '*',
'/',
'>', '<', '!'); // Reserved chars
private final LookaheadStream reader;
private Token current;
public Lexer(String data) {
reader = new LookaheadStream(data + '\0');
current = tokenize();
}
public List<Token> analyze() {
List<Token> tokens = new ArrayList<>();
while(hasNext()) {
tokens.add(consumeUnchecked());
}
tokens.add(current()); // Add EOF token
return tokens;
}
/**
* Get the first token.
*
* @return First token
*
* @throws ParseException If token does not exist
*/
public Token current() {
return current;
}
/**
* Consume (get and remove) the first token.
*
* @return First token
*
* @throws ParseException If token does not exist
*/
public Token consume(String wrongTypeMessage, TokenType expected, TokenType... more) {
if(!current.isType(expected) && Arrays.stream(more).noneMatch(t -> t == current.type())) throw new ParseException(wrongTypeMessage,
current.position());
return consumeUnchecked();
}
public Token consumeUnchecked() {
if(current.type() == TokenType.END_OF_FILE) return current;
Token temp = current;
current = tokenize();
return temp;
}
/**
* Whether this {@code Tokenizer} contains additional tokens.
*
* @return {@code true} if more tokens are present, otherwise {@code false}
*/
public boolean hasNext() {
return current.type() != TokenType.END_OF_FILE;
}
private Token tokenize() throws TokenizerException {
consumeWhitespace();
SourcePosition position = reader.getPosition();
// Skip line if comment
while(reader.matchesString("//", true)) skipLine();
// Skip multi line comment
if(reader.matchesString("/*", true)) skipTo("*/");
// Reached end of file
if(reader.current().isEOF()) return new Token(reader.consume().toString(), TokenType.END_OF_FILE, position);
// Check if operator token
if(reader.matchesString("==", true))
return new Token("==", TokenType.EQUALS_EQUALS, position);
if(reader.matchesString("!=", true))
return new Token("!=", TokenType.BANG_EQUALS, position);
if(reader.matchesString(">=", true))
return new Token(">=", TokenType.GREATER_EQUAL, position);
if(reader.matchesString("<=", true))
return new Token("<=", TokenType.LESS_EQUALS, position);
if(reader.matchesString(">", true))
return new Token(">", TokenType.GREATER, position);
if(reader.matchesString("<", true))
return new Token("<", TokenType.LESS, position);
// Check if logical operator
if(reader.matchesString("||", true))
return new Token("||", TokenType.BOOLEAN_OR, position);
if(reader.matchesString("&&", true))
return new Token("&&", TokenType.BOOLEAN_AND, position);
// Check if number
if(isNumberStart()) {
StringBuilder num = new StringBuilder();
while(!reader.current().isEOF() && isNumberLike()) {
num.append(reader.consume().getCharacter());
}
return new Token(num.toString(), TokenType.NUMBER, position);
}
// Check if string literal
if(reader.current().is('"')) {
reader.consume(); // Consume first quote
StringBuilder string = new StringBuilder();
boolean ignoreNext = false;
while((!reader.current().is('"')) || ignoreNext) {
if(reader.current().is('\\') && !ignoreNext) {
ignoreNext = true;
reader.consume();
continue;
} else ignoreNext = false;
if(reader.current().isEOF())
throw new FormatException("No end of string literal found. ", position);
string.append(reader.consume());
}
reader.consume(); // Consume last quote
return new Token(string.toString(), TokenType.STRING, position);
}
if(reader.current().is('('))
return new Token(reader.consume().toString(), TokenType.OPEN_PAREN, position);
if(reader.current().is(')'))
return new Token(reader.consume().toString(), TokenType.CLOSE_PAREN, position);
if(reader.current().is(':'))
return new Token(reader.consume().toString(), TokenType.COLON, position);
if(reader.current().is(';'))
return new Token(reader.consume().toString(), TokenType.STATEMENT_END, position);
if(reader.current().is(','))
return new Token(reader.consume().toString(), TokenType.SEPARATOR, position);
if(reader.current().is('{')) return new Token(reader.consume().toString(), TokenType.BLOCK_BEGIN, position);
if(reader.current().is('}')) return new Token(reader.consume().toString(), TokenType.BLOCK_END, position);
if(reader.current().is('='))
return new Token(reader.consume().toString(), TokenType.ASSIGNMENT, position);
if(reader.current().is('+'))
return new Token(reader.consume().toString(), TokenType.PLUS, position);
if(reader.current().is('-'))
return new Token(reader.consume().toString(), TokenType.MINUS,
position);
if(reader.current().is('*'))
return new Token(reader.consume().toString(), TokenType.STAR,
position);
if(reader.current().is('/'))
return new Token(reader.consume().toString(), TokenType.FORWARD_SLASH, position);
if(reader.current().is('%'))
return new Token(reader.consume().toString(), TokenType.MODULO_OPERATOR, position);
if(reader.current().is('!'))
return new Token(reader.consume().toString(), TokenType.BANG, position);
// Read word
StringBuilder token = new StringBuilder();
while(!reader.current().isEOF() && !isSyntaxSignificant(reader.current().getCharacter())) {
Char c = reader.consume();
if(c.isWhitespace()) break;
token.append(c.getCharacter());
}
String tokenString = token.toString();
// Check if word is a keyword
if(tokenString.equals("true"))
return new Token(tokenString, TokenType.BOOLEAN, position);
if(tokenString.equals("false"))
return new Token(tokenString, TokenType.BOOLEAN, position);
if(tokenString.equals("var"))
return new Token(tokenString, TokenType.VARIABLE, position);
if(tokenString.equals("fun"))
return new Token(tokenString, TokenType.FUNCTION, position);
if(tokenString.equals("if"))
return new Token(tokenString, TokenType.IF_STATEMENT, position);
if(tokenString.equals("else"))
return new Token(tokenString, TokenType.ELSE, position);
if(tokenString.equals("while"))
return new Token(tokenString, TokenType.WHILE_LOOP, position);
if(tokenString.equals("for"))
return new Token(tokenString, TokenType.FOR_LOOP, position);
if(tokenString.equals("return"))
return new Token(tokenString, TokenType.RETURN, position);
if(tokenString.equals("continue"))
return new Token(tokenString, TokenType.CONTINUE, position);
if(tokenString.equals("break"))
return new Token(tokenString, TokenType.BREAK, position);
if(tokenString.equals("fail"))
return new Token(tokenString, TokenType.FAIL, position);
// If not keyword, assume it is an identifier
return new Token(tokenString, TokenType.IDENTIFIER, position);
}
private void skipLine() {
while(!reader.current().isEOF() && !reader.current().isNewLine()) reader.consume();
consumeWhitespace();
}
private void consumeWhitespace() {
while(!reader.current().isEOF() && reader.current().isWhitespace()) reader.consume(); // Consume whitespace.
}
private void skipTo(String s) throws EOFException {
SourcePosition begin = reader.getPosition();
while(!reader.current().isEOF()) {
if(reader.matchesString(s, true)) {
consumeWhitespace();
return;
}
reader.consume();
}
throw new EOFException("Reached end of file without matching '" + s + "'", begin);
}
private boolean isNumberLike() {
return reader.current().isDigit()
|| reader.current().is('_', '.', 'E');
}
private boolean isNumberStart() {
return reader.current().isDigit()
|| reader.current().is('.') && reader.peek().isDigit();
}
public boolean isSyntaxSignificant(char c) {
return syntaxSignificant.contains(c);
}
}
@@ -1,84 +0,0 @@
package com.dfsek.terra.addons.terrascript.lexer;
public class LookaheadStream {
private final String source;
private int index;
private SourcePosition position = new SourcePosition(1, 1);
public LookaheadStream(String source) {
this.source = source;
}
/**
* Get the current character without consuming it.
*
* @return current character
*/
public Char current() {
return new Char(source.charAt(index), position);
}
/**
* Consume and return one character.
*
* @return Character that was consumed.
*/
public Char consume() {
Char consumed = current();
incrementIndex(1);
return consumed;
}
/**
* @return The next character in sequence.
*/
public Char peek() {
int index = this.index + 1;
if(index + 1 >= source.length()) return null;
return new Char(source.charAt(index), getPositionAfter(1));
}
/**
* Determines if the contained sequence of characters matches the string
*
* @param check Input string to check against
* @param consumeIfMatched Whether to consume the string if there is a match
*
* @return If the string matches
*/
public boolean matchesString(String check, boolean consumeIfMatched) {
boolean matches = check.equals(source.substring(index, Math.min(index + check.length(), source.length())));
if(matches && consumeIfMatched) incrementIndex(check.length());
return matches;
}
/**
* @return Current position within the source file
*/
public SourcePosition getPosition() {
return position;
}
private void incrementIndex(int amount) {
position = getPositionAfter(amount);
index = Math.min(index + amount, source.length() - 1);
}
private SourcePosition getPositionAfter(int chars) {
if(chars < 0) throw new IllegalArgumentException("Negative values are not allowed");
int line = position.line();
int column = position.column();
for(int i = index; i < Math.min(index + chars, source.length() - 1); i++) {
if(source.charAt(i) == '\n') {
line++;
column = 0;
}
column++;
}
return new SourcePosition(line, column);
}
}
@@ -1,32 +0,0 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.terrascript.lexer;
import java.util.Objects;
public record SourcePosition(int line, int column) {
@Override
public String toString() {
return "line " + line + ", column " + column;
}
@Override
public boolean equals(Object o) {
if(this == o) return true;
if(o == null || getClass() != o.getClass()) return false;
SourcePosition that = (SourcePosition) o;
return line == that.line && column == that.column;
}
@Override
public int hashCode() {
return Objects.hash(line, column);
}
}
@@ -1,207 +0,0 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.terrascript.lexer;
import java.util.Objects;
public class Token {
private final String lexeme;
private final TokenType type;
private final SourcePosition start;
public Token(String lexeme, TokenType type, SourcePosition start) {
this.lexeme = type == TokenType.END_OF_FILE ? "END OF FILE" : lexeme;
this.type = type;
this.start = start;
}
@Override
public boolean equals(Object o) {
if(this == o) return true;
if(o == null || getClass() != o.getClass()) return false;
Token token = (Token) o;
return Objects.equals(lexeme, token.lexeme) && type == token.type && Objects.equals(start, token.start);
}
@Override
public int hashCode() {
return Objects.hash(lexeme, type, start);
}
@Override
public String toString() {
return type + ": '" + lexeme + "'";
}
public TokenType type() {
return type;
}
public String lexeme() {
return lexeme;
}
public SourcePosition position() {
return start;
}
public boolean isType(TokenType... types) {
for(TokenType t : types) if(t == type) return true;
return false;
}
public enum TokenType {
/**
* Function identifier or language keyword
*/
IDENTIFIER,
/**
* Numeric literal
*/
NUMBER,
/**
* String literal
*/
STRING,
/**
* Boolean literal
*/
BOOLEAN,
/**
* Beginning of group
*/
OPEN_PAREN,
/**
* Ending of group
*/
CLOSE_PAREN,
/**
* End of statement
*/
STATEMENT_END,
/**
* Argument separator
*/
SEPARATOR,
/**
* Beginning of code block
*/
BLOCK_BEGIN,
/**
* End of code block
*/
BLOCK_END,
/**
* assignment operator
*/
ASSIGNMENT,
/**
* Boolean equals operator
*/
EQUALS_EQUALS,
/**
* Boolean not equals operator
*/
BANG_EQUALS,
/**
* Boolean greater than operator
*/
GREATER,
/**
* Boolean less than operator
*/
LESS,
/**
* Boolean greater than or equal to operator
*/
GREATER_EQUAL,
/**
* Boolean less than or equal to operator
*/
LESS_EQUALS,
/**
* Addition/concatenation operator
*/
PLUS,
/**
* Subtraction operator
*/
MINUS,
/**
* Multiplication operator
*/
STAR,
/**
* Division operator
*/
FORWARD_SLASH,
/**
* Modulo operator.
*/
MODULO_OPERATOR,
/**
* Boolean not operator
*/
BANG,
/**
* Boolean or
*/
BOOLEAN_OR,
/**
* Boolean and
*/
BOOLEAN_AND,
/**
* Variable declaration
*/
VARIABLE,
/**
* Function declaration
*/
FUNCTION,
COLON,
/**
* If statement declaration
*/
IF_STATEMENT,
/**
* While loop declaration
*/
WHILE_LOOP,
/**
* Return statement
*/
RETURN,
/**
* Continue statement
*/
CONTINUE,
/**
* Break statement
*/
BREAK,
/**
* Fail statement. Like return keyword, but specifies that generation has failed.
*/
FAIL,
/**
* For loop initializer token
*/
FOR_LOOP,
/**
* Else keyword
*/
ELSE,
/**
* End of file
*/
END_OF_FILE
}
}
@@ -1,25 +0,0 @@
package com.dfsek.terra.addons.terrascript.parser;
import com.dfsek.terra.addons.terrascript.lexer.Token.TokenType;
public enum BinaryOperator {
BOOLEAN_OR(TokenType.BOOLEAN_OR),
BOOLEAN_AND(TokenType.BOOLEAN_AND),
EQUALS(TokenType.EQUALS_EQUALS),
NOT_EQUALS(TokenType.BANG_EQUALS),
GREATER(TokenType.GREATER),
GREATER_EQUALS(TokenType.GREATER_EQUAL),
LESS(TokenType.LESS),
LESS_EQUALS(TokenType.LESS_EQUALS),
ADD(TokenType.PLUS),
SUBTRACT(TokenType.MINUS),
MULTIPLY(TokenType.STAR),
DIVIDE(TokenType.FORWARD_SLASH),
MODULO(TokenType.MODULO_OPERATOR);
public final TokenType tokenType;
BinaryOperator(TokenType tokenType) { this.tokenType = tokenType; }
}
@@ -1,379 +1,468 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.terrascript.parser;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Supplier;
import java.util.Map;
import com.dfsek.terra.addons.terrascript.Type;
import com.dfsek.terra.addons.terrascript.Type.TypeException;
import com.dfsek.terra.addons.terrascript.ast.Expr;
import com.dfsek.terra.addons.terrascript.ast.Expr.Variable;
import com.dfsek.terra.addons.terrascript.ast.Stmt;
import com.dfsek.terra.addons.terrascript.ast.Stmt.Block;
import com.dfsek.terra.addons.terrascript.lexer.SourcePosition;
import com.dfsek.terra.addons.terrascript.lexer.Token;
import com.dfsek.terra.addons.terrascript.lexer.Token.TokenType;
import com.dfsek.terra.addons.terrascript.parser.exceptions.ParseException;
import com.dfsek.terra.addons.terrascript.parser.lang.Block;
import com.dfsek.terra.addons.terrascript.parser.lang.Executable;
import com.dfsek.terra.addons.terrascript.parser.lang.Item;
import com.dfsek.terra.addons.terrascript.parser.lang.Keyword;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable.ReturnType;
import com.dfsek.terra.addons.terrascript.parser.lang.Scope.ScopeBuilder;
import com.dfsek.terra.addons.terrascript.parser.lang.constants.BooleanConstant;
import com.dfsek.terra.addons.terrascript.parser.lang.constants.ConstantExpression;
import com.dfsek.terra.addons.terrascript.parser.lang.constants.NumericConstant;
import com.dfsek.terra.addons.terrascript.parser.lang.constants.StringConstant;
import com.dfsek.terra.addons.terrascript.parser.lang.functions.Function;
import com.dfsek.terra.addons.terrascript.parser.lang.functions.FunctionBuilder;
import com.dfsek.terra.addons.terrascript.parser.lang.keywords.flow.BreakKeyword;
import com.dfsek.terra.addons.terrascript.parser.lang.keywords.flow.ContinueKeyword;
import com.dfsek.terra.addons.terrascript.parser.lang.keywords.flow.FailKeyword;
import com.dfsek.terra.addons.terrascript.parser.lang.keywords.flow.ReturnKeyword;
import com.dfsek.terra.addons.terrascript.parser.lang.keywords.looplike.ForKeyword;
import com.dfsek.terra.addons.terrascript.parser.lang.keywords.looplike.IfKeyword;
import com.dfsek.terra.addons.terrascript.parser.lang.keywords.looplike.WhileKeyword;
import com.dfsek.terra.addons.terrascript.parser.lang.operations.BinaryOperation;
import com.dfsek.terra.addons.terrascript.parser.lang.operations.BooleanAndOperation;
import com.dfsek.terra.addons.terrascript.parser.lang.operations.BooleanNotOperation;
import com.dfsek.terra.addons.terrascript.parser.lang.operations.BooleanOrOperation;
import com.dfsek.terra.addons.terrascript.parser.lang.operations.ConcatenationOperation;
import com.dfsek.terra.addons.terrascript.parser.lang.operations.DivisionOperation;
import com.dfsek.terra.addons.terrascript.parser.lang.operations.ModuloOperation;
import com.dfsek.terra.addons.terrascript.parser.lang.operations.MultiplicationOperation;
import com.dfsek.terra.addons.terrascript.parser.lang.operations.NegationOperation;
import com.dfsek.terra.addons.terrascript.parser.lang.operations.NumberAdditionOperation;
import com.dfsek.terra.addons.terrascript.parser.lang.operations.SubtractionOperation;
import com.dfsek.terra.addons.terrascript.parser.lang.operations.statements.EqualsStatement;
import com.dfsek.terra.addons.terrascript.parser.lang.operations.statements.GreaterOrEqualsThanStatement;
import com.dfsek.terra.addons.terrascript.parser.lang.operations.statements.GreaterThanStatement;
import com.dfsek.terra.addons.terrascript.parser.lang.operations.statements.LessThanOrEqualsStatement;
import com.dfsek.terra.addons.terrascript.parser.lang.operations.statements.LessThanStatement;
import com.dfsek.terra.addons.terrascript.parser.lang.operations.statements.NotEqualsStatement;
import com.dfsek.terra.addons.terrascript.parser.lang.variables.assign.BoolAssignmentNode;
import com.dfsek.terra.addons.terrascript.parser.lang.variables.assign.NumAssignmentNode;
import com.dfsek.terra.addons.terrascript.parser.lang.variables.assign.StrAssignmentNode;
import com.dfsek.terra.addons.terrascript.parser.lang.variables.assign.VariableAssignmentNode;
import com.dfsek.terra.addons.terrascript.parser.lang.variables.reference.BoolVariableReferenceNode;
import com.dfsek.terra.addons.terrascript.parser.lang.variables.reference.NumVariableReferenceNode;
import com.dfsek.terra.addons.terrascript.parser.lang.variables.reference.StrVariableReferenceNode;
import com.dfsek.terra.addons.terrascript.tokenizer.Position;
import com.dfsek.terra.addons.terrascript.tokenizer.Token;
import com.dfsek.terra.addons.terrascript.tokenizer.Tokenizer;
import com.dfsek.terra.api.util.generic.pair.Pair;
/**
* TerraScript recursive descent parser
*/
@SuppressWarnings("unchecked")
public class Parser {
private final String data;
private final Map<String, FunctionBuilder<? extends Function<?>>> functions = new HashMap<>();
private final List<String> ignoredFunctions = new ArrayList<>();
private final List<Token> tokens;
private int index = 0;
private Parser(List<Token> tokens) {
if(tokens.stream().noneMatch(t -> t.isType(TokenType.END_OF_FILE)))
throw new IllegalArgumentException("Token list must contain at least one token of type " + TokenType.END_OF_FILE);
this.tokens = tokens;
public Parser(String data) {
this.data = data;
}
public static Block parse(List<Token> tokens) {
return new Parser(tokens).parseTokens();
public Parser registerFunction(String name, FunctionBuilder<? extends Function<?>> functionBuilder) {
functions.put(name, functionBuilder);
return this;
}
private Block parseTokens() {
List<Stmt> statements = new ArrayList<>();
while(hasNext()) {
statements.add(statement());
}
if(hasNext()) throw new ParseException("Tokens were remaining after parsing", current().position());
return new Stmt.Block(statements, new SourcePosition(0, 0));
public Parser ignoreFunction(String name) {
ignoredFunctions.add(name);
return this;
}
private Token current() {
return tokens.get(index);
/**
* Parse input
*
* @return executable {@link Block}
*
* @throws ParseException If parsing fails.
*/
public Executable parse() {
ScopeBuilder scopeBuilder = new ScopeBuilder();
return new Executable(parseBlock(new Tokenizer(data), false, scopeBuilder), scopeBuilder);
}
private boolean hasNext() {
return !current().isType(TokenType.END_OF_FILE);
}
private Token consume(String wrongTypeMessage, TokenType expected, TokenType... more) {
if(!current().isType(expected) && Arrays.stream(more).noneMatch(t -> t == current().type())) throw new ParseException(
wrongTypeMessage, current().position());
return consumeUnchecked();
}
public Token consumeUnchecked() {
if(!hasNext()) return current();
Token temp = current();
index++;
return temp;
}
private void consumeStatementEnd(String after) {
consume("Expected ';' after " + after + ", found '" + current().lexeme() + "'", TokenType.STATEMENT_END);
}
private Stmt statement() {
return switch(current().type()) {
case BLOCK_BEGIN -> block();
case FUNCTION -> functionDeclaration();
case VARIABLE -> variableDeclaration();
case RETURN -> returnStmt();
case IF_STATEMENT -> ifStmt();
case FOR_LOOP -> forLoop();
case WHILE_LOOP -> whileLoop();
case BREAK -> breakStmt();
case CONTINUE -> continueStmt();
case STATEMENT_END -> new Stmt.NoOp(consumeUnchecked().position());
default -> expressionStatement();
private Keyword<?> parseLoopLike(Tokenizer tokens, boolean loop, ScopeBuilder scopeBuilder) throws ParseException {
Token identifier = tokens.consume();
ParserUtil.checkType(identifier, Token.Type.IF_STATEMENT, Token.Type.WHILE_LOOP, Token.Type.FOR_LOOP);
ParserUtil.checkType(tokens.consume(), Token.Type.GROUP_BEGIN);
return switch(identifier.getType()) {
case FOR_LOOP -> parseForLoop(tokens, identifier.getPosition(), scopeBuilder);
case IF_STATEMENT -> parseIfStatement(tokens, identifier.getPosition(), loop, scopeBuilder);
case WHILE_LOOP -> parseWhileLoop(tokens, identifier.getPosition(), scopeBuilder);
default -> throw new UnsupportedOperationException(
"Unknown keyword " + identifier.getContent() + ": " + identifier.getPosition());
};
}
private Stmt functionDeclaration() {
SourcePosition position = consume("Expected 'fun' keyword at start of function declaration", TokenType.FUNCTION).position();
String id = consume("Expected identifier after 'fun' keyword for function declaration", TokenType.IDENTIFIER).lexeme();
consume("Expected '(' after function identifier '" + id + "'", TokenType.OPEN_PAREN);
private WhileKeyword parseWhileLoop(Tokenizer tokens, Position start, ScopeBuilder scopeBuilder) {
Returnable<?> first = parseExpression(tokens, true, scopeBuilder);
ParserUtil.checkReturnType(first, Returnable.ReturnType.BOOLEAN);
// Parse parameters
List<Pair<String, Type>> params = new ArrayList<>();
while(!current().isType(TokenType.CLOSE_PAREN)) {
Token paramToken = consume("Expected parameter name or ')', found '" + current().lexeme() + "'", TokenType.IDENTIFIER);
String paramId = paramToken.lexeme();
if(params.stream().anyMatch(p -> Objects.equals(p.getLeft(), paramId)))
throw new ParseException("Parameter '" + paramId + "' has already been declared in function '" + id + "'",
paramToken.position());
consume("Expected type declaration after parameter name. Example: '" + paramId + ": <type>'", TokenType.COLON);
Type paramType = typeExpr();
params.add(Pair.of(paramId, paramType));
if(current().isType(TokenType.CLOSE_PAREN)) break;
consume("Expected ',' or ')' after parameter declaration '" + paramId + "' in function '" + id + "'", TokenType.SEPARATOR);
}
ParserUtil.checkType(tokens.consume(), Token.Type.GROUP_END);
Type funcReturn = Type.VOID;
consume("Expected ')' after " + (params.size() == 0 ? "')'" : "parameters") + " in declaration of function '" + id + "'",
TokenType.CLOSE_PAREN);
if(current().isType(TokenType.COLON)) {
consumeUnchecked();
funcReturn = typeExpr();
}
Stmt.Block body = blockOrSingleStatement();
return new Stmt.FunctionDeclaration(id, params, funcReturn, body, position);
return new WhileKeyword(parseStatementBlock(tokens, true, scopeBuilder), (Returnable<Boolean>) first, start); // While loop
}
private Stmt.VariableDeclaration variableDeclaration() {
SourcePosition position = consume("Expected 'var' keyword at start of variable declaration", TokenType.VARIABLE).position();
String id = consume("Expected variable name after type for variable declaration", TokenType.IDENTIFIER).lexeme();
consume("Expected ':' after variable name", TokenType.COLON);
Type type = typeExpr();
consume("Expected '=' following variable type declaration", TokenType.ASSIGNMENT);
Expr expr = expression();
consumeStatementEnd("variable declaration");
private IfKeyword parseIfStatement(Tokenizer tokens, Position start, boolean loop, ScopeBuilder scopeBuilder) {
Returnable<?> condition = parseExpression(tokens, true, scopeBuilder);
ParserUtil.checkReturnType(condition, Returnable.ReturnType.BOOLEAN);
return new Stmt.VariableDeclaration(type, id, expr, position);
}
private Type typeExpr() {
Token typeToken = consume("Expected " + TokenType.IDENTIFIER + " specified as variable type", TokenType.IDENTIFIER);
try {
return Type.fromString(typeToken.lexeme());
} catch(TypeException e) {
throw new ParseException("Failed to parse type expression", typeToken.position());
}
}
private Stmt.Return returnStmt() {
SourcePosition position = consume("Expected 'return' keyword, found '" + current().lexeme() + "'", TokenType.RETURN).position();
Expr value = new Expr.Void(position);
if(!current().isType(TokenType.STATEMENT_END))
value = expression();
consumeStatementEnd("return statement");
return new Stmt.Return(value, position);
}
private Stmt.If ifStmt() {
// Parse main if clause
SourcePosition position = consume("Expected 'if' keyword at beginning of if statement", TokenType.IF_STATEMENT).position();
consume("Expected '(' after 'if' keyword", TokenType.OPEN_PAREN);
Expr condition = expression();
consume("Expected ')' after if statement condition", TokenType.CLOSE_PAREN);
Stmt.Block trueBody = blockOrSingleStatement();
ParserUtil.checkType(tokens.consume(), Token.Type.GROUP_END);
// Parse any else clauses
Stmt.Block elseBody = null;
List<Pair<Expr, Stmt.Block>> elseIfClauses = new ArrayList<>();
while(current().isType(TokenType.ELSE)) {
consumeUnchecked(); // Consume else
if(!current().isType(TokenType.IF_STATEMENT)) {
elseBody = blockOrSingleStatement();
break; // Else clause should be last in if statement
Block elseBlock = null;
Block statement = parseStatementBlock(tokens, loop, scopeBuilder);
List<Pair<Returnable<Boolean>, Block>> elseIf = new ArrayList<>();
while(tokens.hasNext() && tokens.get().getType().equals(Token.Type.ELSE)) {
tokens.consume(); // Consume else.
if(tokens.get().getType().equals(Token.Type.IF_STATEMENT)) {
tokens.consume(); // Consume if.
Returnable<?> elseCondition = parseExpression(tokens, true, scopeBuilder);
ParserUtil.checkReturnType(elseCondition, Returnable.ReturnType.BOOLEAN);
elseIf.add(Pair.of((Returnable<Boolean>) elseCondition, parseStatementBlock(tokens, loop, scopeBuilder)));
} else {
elseBlock = parseStatementBlock(tokens, loop, scopeBuilder);
break; // Else must be last.
}
consumeUnchecked(); // Consume if
consume("Expected '(' after 'else if', e.g. 'if else (<condition>) ...'", TokenType.OPEN_PAREN);
Expr elseIfCondition = expression();
consume("Expected ')' after 'else if' clause, e.g. 'else if (<condition>) ...'", TokenType.CLOSE_PAREN);
Stmt.Block elseIfBody = blockOrSingleStatement();
elseIfClauses.add(Pair.of(elseIfCondition, elseIfBody));
}
return new Stmt.If(condition, trueBody, elseIfClauses, Optional.ofNullable(elseBody), position);
return new IfKeyword(statement, (Returnable<Boolean>) condition, elseIf, elseBlock, start); // If statement
}
private Stmt.For forLoop() {
SourcePosition position = consume("Expected 'for' keyword at beginning of for loop", TokenType.FOR_LOOP).position();
consume("Expected '(' after 'for' keyword", TokenType.OPEN_PAREN);
Stmt initializer = statement();
Expr condition;
if(current().isType(TokenType.STATEMENT_END)) {
condition = new Expr.Literal(true, Type.BOOLEAN,
current().position()); // If no condition is provided, set condition = true
consumeUnchecked();
private Block parseStatementBlock(Tokenizer tokens, boolean loop, ScopeBuilder scopeBuilder) {
if(tokens.get().getType().equals(Token.Type.BLOCK_BEGIN)) {
ParserUtil.checkType(tokens.consume(), Token.Type.BLOCK_BEGIN);
Block block = parseBlock(tokens, loop, scopeBuilder);
ParserUtil.checkType(tokens.consume(), Token.Type.BLOCK_END);
return block;
} else {
condition = expression();
consumeStatementEnd("loop condition");
Position position = tokens.get().getPosition();
Block block = new Block(Collections.singletonList(parseItem(tokens, loop, scopeBuilder)), position);
ParserUtil.checkType(tokens.consume(), Token.Type.STATEMENT_END);
return block;
}
Expr incrementer;
if(current().isType(TokenType.CLOSE_PAREN)) {
incrementer = null;
consumeUnchecked();
}
private ForKeyword parseForLoop(Tokenizer tokens, Position start, ScopeBuilder scopeBuilder) {
scopeBuilder = scopeBuilder.sub(); // new scope
Token f = tokens.get();
ParserUtil.checkType(f, Token.Type.NUMBER_VARIABLE, Token.Type.STRING_VARIABLE, Token.Type.BOOLEAN_VARIABLE, Token.Type.IDENTIFIER);
Item<?> initializer;
if(f.isVariableDeclaration()) {
VariableAssignmentNode<?> forVar = parseVariableDeclaration(tokens, scopeBuilder);
Token name = tokens.get();
if(functions.containsKey(name.getContent()) || scopeBuilder.contains(name.getContent()))
throw new ParseException(name.getContent() + " is already defined in this scope", name.getPosition());
initializer = forVar;
} else initializer = parseExpression(tokens, true, scopeBuilder);
ParserUtil.checkType(tokens.consume(), Token.Type.STATEMENT_END);
Returnable<?> conditional = parseExpression(tokens, true, scopeBuilder);
ParserUtil.checkReturnType(conditional, Returnable.ReturnType.BOOLEAN);
ParserUtil.checkType(tokens.consume(), Token.Type.STATEMENT_END);
Item<?> incrementer;
Token token = tokens.get();
if(scopeBuilder.contains(token.getContent())) { // Assume variable assignment
incrementer = parseAssignment(tokens, scopeBuilder);
} else incrementer = parseFunction(tokens, true, scopeBuilder);
ParserUtil.checkType(tokens.consume(), Token.Type.GROUP_END);
return new ForKeyword(parseStatementBlock(tokens, true, scopeBuilder), initializer, (Returnable<Boolean>) conditional, incrementer,
start);
}
private Returnable<?> parseExpression(Tokenizer tokens, boolean full, ScopeBuilder scopeBuilder) {
boolean booleanInverted = false; // Check for boolean not operator
boolean negate = false;
if(tokens.get().getType().equals(Token.Type.BOOLEAN_NOT)) {
booleanInverted = true;
tokens.consume();
} else if(tokens.get().getType().equals(Token.Type.SUBTRACTION_OPERATOR)) {
negate = true;
tokens.consume();
}
Token id = tokens.get();
ParserUtil.checkType(id, Token.Type.IDENTIFIER, Token.Type.BOOLEAN, Token.Type.STRING, Token.Type.NUMBER, Token.Type.GROUP_BEGIN);
Returnable<?> expression;
if(id.isConstant()) {
expression = parseConstantExpression(tokens);
} else if(id.getType().equals(Token.Type.GROUP_BEGIN)) { // Parse grouped expression
expression = parseGroup(tokens, scopeBuilder);
} else {
incrementer = expression();
consume("Expected ')' after for loop incrementer", TokenType.CLOSE_PAREN);
if(functions.containsKey(id.getContent()))
expression = parseFunction(tokens, false, scopeBuilder);
else if(scopeBuilder.contains(id.getContent())) {
ParserUtil.checkType(tokens.consume(), Token.Type.IDENTIFIER);
String varId = id.getContent();
ReturnType varType = scopeBuilder.getType(varId);
expression = switch(varType) {
case NUMBER -> new NumVariableReferenceNode(id.getPosition(), varType, scopeBuilder.getIndex(varId));
case STRING -> new StrVariableReferenceNode(id.getPosition(), varType, scopeBuilder.getIndex(varId));
case BOOLEAN -> new BoolVariableReferenceNode(id.getPosition(), varType, scopeBuilder.getIndex(varId));
default -> throw new ParseException("Illegal type for variable reference: " + varType, id.getPosition());
};
} else throw new ParseException("Unexpected token \" " + id.getContent() + "\"", id.getPosition());
}
Stmt.Block body = blockOrSingleStatement();
return new Stmt.For(initializer, condition, incrementer, body, position);
}
private Stmt.While whileLoop() {
SourcePosition position = consume("Expected 'for' keyword at beginning of while loop", TokenType.WHILE_LOOP).position();
consume("Expected '(' after 'while' keyword", TokenType.OPEN_PAREN);
Expr condition = expression();
consume("Expected ')' after while loop condition", TokenType.CLOSE_PAREN);
Stmt.Block body = blockOrSingleStatement();
return new Stmt.While(condition, body, position);
}
private Stmt.Break breakStmt() {
SourcePosition position = consume("Expected 'break' keyword for break statement", TokenType.BREAK).position();
consumeStatementEnd("'break' keyword");
return new Stmt.Break(position);
}
private Stmt.Continue continueStmt() {
SourcePosition position = consume("Expected 'continue' keyword for continue statement", TokenType.CONTINUE).position();
consumeStatementEnd("'continue' keyword");
return new Stmt.Continue(position);
}
private Stmt.Block blockOrSingleStatement() {
if(!current().isType(TokenType.BLOCK_BEGIN)) return new Stmt.Block(List.of(statement()), current().position());
return block();
}
private Stmt.Block block() {
SourcePosition position = consume("Expected '{' at start of block", TokenType.BLOCK_BEGIN).position();
List<Stmt> statements = new ArrayList<>();
while(!current().isType(TokenType.BLOCK_END)) {
statements.add(statement());
if(booleanInverted) { // Invert operation if boolean not detected
ParserUtil.checkReturnType(expression, Returnable.ReturnType.BOOLEAN);
expression = new BooleanNotOperation((Returnable<Boolean>) expression, expression.getPosition());
} else if(negate) {
ParserUtil.checkReturnType(expression, Returnable.ReturnType.NUMBER);
expression = new NegationOperation((Returnable<Number>) expression, expression.getPosition());
}
consume("Expected '}' at end of block", TokenType.BLOCK_END);
return new Stmt.Block(statements, position);
if(full && tokens.get().isBinaryOperator()) { // Parse binary operations
return parseBinaryOperation(expression, tokens, scopeBuilder);
}
return expression;
}
private Stmt expressionStatement() {
Expr expression = expression();
consumeStatementEnd("expression statement");
return new Stmt.Expression(expression, expression.position);
private ConstantExpression<?> parseConstantExpression(Tokenizer tokens) {
Token constantToken = tokens.consume();
Position position = constantToken.getPosition();
switch(constantToken.getType()) {
case NUMBER:
String content = constantToken.getContent();
return new NumericConstant(content.contains(".") ? Double.parseDouble(content) : Integer.parseInt(content), position);
case STRING:
return new StringConstant(constantToken.getContent(), position);
case BOOLEAN:
return new BooleanConstant(Boolean.parseBoolean(constantToken.getContent()), position);
default:
throw new UnsupportedOperationException(
"Unsupported constant token: " + constantToken.getType() + " at position: " + position);
}
}
private Expr expression() {
return assignment();
private Returnable<?> parseGroup(Tokenizer tokens, ScopeBuilder scopeBuilder) {
ParserUtil.checkType(tokens.consume(), Token.Type.GROUP_BEGIN);
Returnable<?> expression = parseExpression(tokens, true, scopeBuilder); // Parse inside of group as a separate expression
ParserUtil.checkType(tokens.consume(), Token.Type.GROUP_END);
return expression;
}
private Expr leftAssociativeBinaryExpression(Supplier<Expr> higherPrecedence, BinaryOperator... operators) {
Expr expr = higherPrecedence.get();
loop:
while(true) {
for(BinaryOperator operator : operators) {
if(current().isType(operator.tokenType)) {
SourcePosition position = consumeUnchecked().position(); // Consume operator token
expr = new Expr.Binary(expr, operator, higherPrecedence.get(), position);
continue loop;
private BinaryOperation<?, ?> parseBinaryOperation(Returnable<?> left, Tokenizer tokens,
ScopeBuilder scopeBuilder) {
Token binaryOperator = tokens.consume();
ParserUtil.checkBinaryOperator(binaryOperator);
Returnable<?> right = parseExpression(tokens, false, scopeBuilder);
Token other = tokens.get();
if(ParserUtil.hasPrecedence(binaryOperator.getType(), other.getType())) {
return assemble(left, parseBinaryOperation(right, tokens, scopeBuilder), binaryOperator);
} else if(other.isBinaryOperator()) {
return parseBinaryOperation(assemble(left, right, binaryOperator), tokens, scopeBuilder);
}
return assemble(left, right, binaryOperator);
}
private BinaryOperation<?, ?> assemble(Returnable<?> left, Returnable<?> right, Token binaryOperator) {
if(binaryOperator.isStrictNumericOperator())
ParserUtil.checkArithmeticOperation(left, right, binaryOperator); // Numeric type checking
if(binaryOperator.isStrictBooleanOperator()) ParserUtil.checkBooleanOperation(left, right, binaryOperator); // Boolean type checking
switch(binaryOperator.getType()) {
case ADDITION_OPERATOR:
if(left.returnType().equals(Returnable.ReturnType.NUMBER) && right.returnType().equals(Returnable.ReturnType.NUMBER)) {
return new NumberAdditionOperation((Returnable<Number>) left, (Returnable<Number>) right, binaryOperator.getPosition());
}
}
break; // Break if not any operator
return new ConcatenationOperation((Returnable<Object>) left, (Returnable<Object>) right, binaryOperator.getPosition());
case SUBTRACTION_OPERATOR:
return new SubtractionOperation((Returnable<Number>) left, (Returnable<Number>) right, binaryOperator.getPosition());
case MULTIPLICATION_OPERATOR:
return new MultiplicationOperation((Returnable<Number>) left, (Returnable<Number>) right, binaryOperator.getPosition());
case DIVISION_OPERATOR:
return new DivisionOperation((Returnable<Number>) left, (Returnable<Number>) right, binaryOperator.getPosition());
case EQUALS_OPERATOR:
return new EqualsStatement((Returnable<Object>) left, (Returnable<Object>) right, binaryOperator.getPosition());
case NOT_EQUALS_OPERATOR:
return new NotEqualsStatement((Returnable<Object>) left, (Returnable<Object>) right, binaryOperator.getPosition());
case GREATER_THAN_OPERATOR:
return new GreaterThanStatement((Returnable<Number>) left, (Returnable<Number>) right, binaryOperator.getPosition());
case LESS_THAN_OPERATOR:
return new LessThanStatement((Returnable<Number>) left, (Returnable<Number>) right, binaryOperator.getPosition());
case GREATER_THAN_OR_EQUALS_OPERATOR:
return new GreaterOrEqualsThanStatement((Returnable<Number>) left, (Returnable<Number>) right,
binaryOperator.getPosition());
case LESS_THAN_OR_EQUALS_OPERATOR:
return new LessThanOrEqualsStatement((Returnable<Number>) left, (Returnable<Number>) right, binaryOperator.getPosition());
case BOOLEAN_AND:
return new BooleanAndOperation((Returnable<Boolean>) left, (Returnable<Boolean>) right, binaryOperator.getPosition());
case BOOLEAN_OR:
return new BooleanOrOperation((Returnable<Boolean>) left, (Returnable<Boolean>) right, binaryOperator.getPosition());
case MODULO_OPERATOR:
return new ModuloOperation((Returnable<Number>) left, (Returnable<Number>) right, binaryOperator.getPosition());
default:
throw new UnsupportedOperationException("Unsupported binary operator: " + binaryOperator.getType());
}
return expr;
}
private Expr rightAssociativeBinaryExpression(Supplier<Expr> higherPrecedence, BinaryOperator... operators) {
Expr expr = higherPrecedence.get();
for(BinaryOperator operator : operators) {
if(current().isType(operator.tokenType)) {
SourcePosition position = consumeUnchecked().position(); // Consume operator token
return new Expr.Binary(expr, operator, rightAssociativeBinaryExpression(higherPrecedence, operators), position);
}
}
return expr;
}
private Expr assignment() {
Expr expr = logicOr();
if(current().isType(TokenType.ASSIGNMENT)) {
SourcePosition position = consumeUnchecked().position(); // Consume operator token
if(!(expr instanceof Variable variable)) throw new ParseException("Invalid assignment target", position);
return new Expr.Assignment(variable, assignment(), position);
}
return expr;
}
private Expr logicOr() {
return leftAssociativeBinaryExpression(this::logicAnd, BinaryOperator.BOOLEAN_OR);
}
private Expr logicAnd() {
return leftAssociativeBinaryExpression(this::equality, BinaryOperator.BOOLEAN_AND);
}
private Expr equality() {
return leftAssociativeBinaryExpression(this::comparison, BinaryOperator.EQUALS, BinaryOperator.NOT_EQUALS);
}
private Expr comparison() {
return leftAssociativeBinaryExpression(this::term, BinaryOperator.GREATER, BinaryOperator.GREATER_EQUALS, BinaryOperator.LESS,
BinaryOperator.LESS_EQUALS);
}
private Expr term() {
return leftAssociativeBinaryExpression(this::factor, BinaryOperator.ADD, BinaryOperator.SUBTRACT);
}
private Expr factor() {
return leftAssociativeBinaryExpression(this::unary, BinaryOperator.MULTIPLY, BinaryOperator.DIVIDE, BinaryOperator.MODULO);
}
private Expr unary() {
UnaryOperator[] operators = { UnaryOperator.NOT, UnaryOperator.NEGATE };
for(UnaryOperator operator : operators) {
if(current().isType(operator.tokenType)) {
SourcePosition position = consumeUnchecked().position();
return new Expr.Unary(operator, unary(), position);
}
}
return primary();
}
private Expr primary() {
Token token = consumeUnchecked();
SourcePosition position = token.position();
return switch(token.type()) {
case NUMBER -> new Expr.Literal(Double.parseDouble(token.lexeme()), Type.NUMBER, position);
case STRING -> new Expr.Literal(token.lexeme(), Type.STRING, position);
case BOOLEAN -> new Expr.Literal(Boolean.parseBoolean(token.lexeme()), Type.BOOLEAN, position);
case IDENTIFIER -> {
if(current().isType(TokenType.OPEN_PAREN)) yield call(token);
else yield variable(token);
}
case OPEN_PAREN -> {
if(current().isType(TokenType.CLOSE_PAREN)) {
consumeUnchecked(); // Consume ')'
yield new Expr.Void(position); // () evaluates to void
}
Expr expr = expression();
consume("Expected ')' to close '(' located at " + position, TokenType.CLOSE_PAREN);
yield new Expr.Grouping(expr, position);
}
default -> throw new ParseException("Unexpected token '" + token.lexeme() + "'", position);
private VariableAssignmentNode<?> parseVariableDeclaration(Tokenizer tokens, ScopeBuilder scopeBuilder) {
Token type = tokens.consume();
ParserUtil.checkType(type, Token.Type.STRING_VARIABLE, Token.Type.BOOLEAN_VARIABLE, Token.Type.NUMBER_VARIABLE);
Returnable.ReturnType returnType = ParserUtil.getVariableReturnType(type);
ParserUtil.checkVarType(type, returnType); // Check for type mismatch
Token identifier = tokens.consume();
ParserUtil.checkType(identifier, Token.Type.IDENTIFIER);
if(functions.containsKey(identifier.getContent()) || scopeBuilder.contains(identifier.getContent()))
throw new ParseException(identifier.getContent() + " is already defined in this scope", identifier.getPosition());
ParserUtil.checkType(tokens.consume(), Token.Type.ASSIGNMENT);
Returnable<?> value = parseExpression(tokens, true, scopeBuilder);
ParserUtil.checkReturnType(value, returnType);
String id = identifier.getContent();
return switch(value.returnType()) {
case NUMBER -> new NumAssignmentNode((Returnable<Number>) value, identifier.getPosition(), scopeBuilder.num(id));
case STRING -> new StrAssignmentNode((Returnable<String>) value, identifier.getPosition(), scopeBuilder.str(id));
case BOOLEAN -> new BoolAssignmentNode((Returnable<Boolean>) value, identifier.getPosition(), scopeBuilder.bool(id));
default -> throw new ParseException("Illegal type for variable declaration: " + type, value.getPosition());
};
}
private Expr call(Token identifier) {
String id = identifier.lexeme();
SourcePosition position = consume("Expected '(' to initiate function call on function '" + id + "'",
TokenType.OPEN_PAREN).position();
private Block parseBlock(Tokenizer tokens, boolean loop, ScopeBuilder scopeBuilder) {
List<Item<?>> parsedItems = new ArrayList<>();
List<Expr> args = new ArrayList<>();
while(!current().isType(TokenType.CLOSE_PAREN)) {
args.add(expression());
if(current().isType(TokenType.CLOSE_PAREN)) break;
consume("Expected ',' or ')' after passed argument in function call of '" + id + "'", TokenType.SEPARATOR);
scopeBuilder = scopeBuilder.sub();
Token first = tokens.get();
while(tokens.hasNext()) {
Token token = tokens.get();
if(token.getType().equals(Token.Type.BLOCK_END)) break; // Stop parsing at block end.
Item<?> parsedItem = parseItem(tokens, loop, scopeBuilder);
if(parsedItem != Function.NULL) {
parsedItems.add(parsedItem);
}
if(tokens.hasNext() && !token.isLoopLike()) ParserUtil.checkType(tokens.consume(), Token.Type.STATEMENT_END);
}
return new Block(parsedItems, first.getPosition());
}
private Item<?> parseItem(Tokenizer tokens, boolean loop, ScopeBuilder scopeBuilder) {
Token token = tokens.get();
if(loop) ParserUtil.checkType(token, Token.Type.IDENTIFIER, Token.Type.IF_STATEMENT, Token.Type.WHILE_LOOP, Token.Type.FOR_LOOP,
Token.Type.NUMBER_VARIABLE, Token.Type.STRING_VARIABLE, Token.Type.BOOLEAN_VARIABLE,
Token.Type.RETURN, Token.Type.BREAK, Token.Type.CONTINUE, Token.Type.FAIL);
else ParserUtil.checkType(token, Token.Type.IDENTIFIER, Token.Type.IF_STATEMENT, Token.Type.WHILE_LOOP, Token.Type.FOR_LOOP,
Token.Type.NUMBER_VARIABLE, Token.Type.STRING_VARIABLE, Token.Type.BOOLEAN_VARIABLE, Token.Type.RETURN,
Token.Type.FAIL);
if(token.isLoopLike()) { // Parse loop-like tokens (if, while, etc)
return parseLoopLike(tokens, loop, scopeBuilder);
} else if(token.isIdentifier()) { // Parse identifiers
if(scopeBuilder.contains(token.getContent())) { // Assume variable assignment
return parseAssignment(tokens, scopeBuilder);
} else return parseFunction(tokens, true, scopeBuilder);
} else if(token.isVariableDeclaration()) {
return parseVariableDeclaration(tokens, scopeBuilder);
} else if(token.getType().equals(Token.Type.RETURN)) return new ReturnKeyword(tokens.consume().getPosition());
else if(token.getType().equals(Token.Type.BREAK)) return new BreakKeyword(tokens.consume().getPosition());
else if(token.getType().equals(Token.Type.CONTINUE)) return new ContinueKeyword(tokens.consume().getPosition());
else if(token.getType().equals(Token.Type.FAIL)) return new FailKeyword(tokens.consume().getPosition());
else throw new UnsupportedOperationException("Unexpected token " + token.getType() + ": " + token.getPosition());
}
private VariableAssignmentNode<?> parseAssignment(Tokenizer tokens, ScopeBuilder scopeBuilder) {
Token identifier = tokens.consume();
ParserUtil.checkType(identifier, Token.Type.IDENTIFIER);
ParserUtil.checkType(tokens.consume(), Token.Type.ASSIGNMENT);
Returnable<?> value = parseExpression(tokens, true, scopeBuilder);
String id = identifier.getContent();
ParserUtil.checkReturnType(value, scopeBuilder.getType(id));
ReturnType type = value.returnType();
return switch(type) {
case NUMBER -> new NumAssignmentNode((Returnable<Number>) value, identifier.getPosition(), scopeBuilder.getIndex(id));
case STRING -> new StrAssignmentNode((Returnable<String>) value, identifier.getPosition(), scopeBuilder.getIndex(id));
case BOOLEAN -> new BoolAssignmentNode((Returnable<Boolean>) value, identifier.getPosition(), scopeBuilder.getIndex(id));
default -> throw new ParseException("Illegal type for variable assignment: " + type, value.getPosition());
};
}
private Function<?> parseFunction(Tokenizer tokens, boolean fullStatement, ScopeBuilder scopeBuilder) {
Token identifier = tokens.consume();
ParserUtil.checkType(identifier, Token.Type.IDENTIFIER); // First token must be identifier
if(!functions.containsKey(identifier.getContent()))
throw new ParseException("No such function \"" + identifier.getContent() + "\"", identifier.getPosition());
ParserUtil.checkType(tokens.consume(), Token.Type.GROUP_BEGIN); // Second is body begin
List<Returnable<?>> args = getArgs(tokens, scopeBuilder); // Extract arguments, consume the rest.
ParserUtil.checkType(tokens.consume(), Token.Type.GROUP_END); // Remove body end
if(fullStatement) ParserUtil.checkType(tokens.get(), Token.Type.STATEMENT_END);
if(ignoredFunctions.contains(identifier.getContent())) {
return Function.NULL;
}
consume("Expected ')' after " + (args.size() == 0 ? "')'" : "arguments") + " in function call of '" + id + "'",
TokenType.CLOSE_PAREN);
if(functions.containsKey(identifier.getContent())) {
FunctionBuilder<?> builder = functions.get(identifier.getContent());
if(builder.argNumber() != -1 && args.size() != builder.argNumber())
throw new ParseException("Expected " + builder.argNumber() + " arguments, found " + args.size(), identifier.getPosition());
for(int i = 0; i < args.size(); i++) {
Returnable<?> argument = args.get(i);
if(builder.getArgument(i) == null)
throw new ParseException("Unexpected argument at position " + i + " in function " + identifier.getContent(),
identifier.getPosition());
ParserUtil.checkReturnType(argument, builder.getArgument(i));
}
return builder.build(args, identifier.getPosition());
}
throw new UnsupportedOperationException("Unsupported function: " + identifier.getContent());
}
private List<Returnable<?>> getArgs(Tokenizer tokens, ScopeBuilder scopeBuilder) {
List<Returnable<?>> args = new ArrayList<>();
return new Expr.Call(id, args, position);
while(!tokens.get().getType().equals(Token.Type.GROUP_END)) {
args.add(parseExpression(tokens, true, scopeBuilder));
ParserUtil.checkType(tokens.get(), Token.Type.SEPARATOR, Token.Type.GROUP_END);
if(tokens.get().getType().equals(Token.Type.SEPARATOR)) tokens.consume();
}
return args;
}
private Expr variable(Token identifier) {
return new Expr.Variable(identifier.lexeme(), identifier.position());
}
}
@@ -0,0 +1,115 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.terrascript.parser;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.dfsek.terra.addons.terrascript.parser.exceptions.ParseException;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
import com.dfsek.terra.addons.terrascript.tokenizer.Token;
public class ParserUtil {
private static final Map<Token.Type, Map<Token.Type, Boolean>> PRECEDENCE = new HashMap<>(); // If second has precedence, true.
private static final List<Token.Type> ARITHMETIC = Arrays.asList(Token.Type.ADDITION_OPERATOR, Token.Type.SUBTRACTION_OPERATOR,
Token.Type.MULTIPLICATION_OPERATOR, Token.Type.DIVISION_OPERATOR,
Token.Type.MODULO_OPERATOR);
private static final List<Token.Type> COMPARISON = Arrays.asList(Token.Type.EQUALS_OPERATOR, Token.Type.NOT_EQUALS_OPERATOR,
Token.Type.LESS_THAN_OPERATOR, Token.Type.LESS_THAN_OR_EQUALS_OPERATOR,
Token.Type.GREATER_THAN_OPERATOR,
Token.Type.GREATER_THAN_OR_EQUALS_OPERATOR);
static { // Setup precedence
Map<Token.Type, Boolean> add = new HashMap<>(); // Addition/subtraction before Multiplication/division.
add.put(Token.Type.MULTIPLICATION_OPERATOR, true);
add.put(Token.Type.DIVISION_OPERATOR, true);
PRECEDENCE.put(Token.Type.ADDITION_OPERATOR, add);
PRECEDENCE.put(Token.Type.SUBTRACTION_OPERATOR, add);
Map<Token.Type, Boolean> numericBoolean = new HashMap<>();
ARITHMETIC.forEach(op -> numericBoolean.put(op, true)); // Numbers before comparison
COMPARISON.forEach(op -> PRECEDENCE.put(op, numericBoolean));
Map<Token.Type, Boolean> booleanOps = new HashMap<>();
ARITHMETIC.forEach(op -> booleanOps.put(op, true)); // Everything before boolean
COMPARISON.forEach(op -> booleanOps.put(op, true));
PRECEDENCE.put(Token.Type.BOOLEAN_AND, booleanOps);
PRECEDENCE.put(Token.Type.BOOLEAN_OR, booleanOps);
}
public static void checkType(Token token, Token.Type... expected) {
for(Token.Type type : expected) if(token.getType().equals(type)) return;
throw new ParseException("Expected " + Arrays.toString(expected) + " but found " + token.getType(), token.getPosition());
}
public static void checkReturnType(Returnable<?> returnable, Returnable.ReturnType... types) {
for(Returnable.ReturnType type : types) if(returnable.returnType().equals(type)) return;
throw new ParseException("Expected " + Arrays.toString(types) + " but found " + returnable.returnType(), returnable.getPosition());
}
public static void checkArithmeticOperation(Returnable<?> left, Returnable<?> right, Token operation) {
if(!left.returnType().equals(Returnable.ReturnType.NUMBER) || !right.returnType().equals(Returnable.ReturnType.NUMBER)) {
throw new ParseException(
"Operation " + operation.getType() + " not supported between " + left.returnType() + " and " + right.returnType(),
operation.getPosition());
}
}
public static void checkBooleanOperation(Returnable<?> left, Returnable<?> right, Token operation) {
if(!left.returnType().equals(Returnable.ReturnType.BOOLEAN) || !right.returnType().equals(Returnable.ReturnType.BOOLEAN)) {
throw new ParseException(
"Operation " + operation.getType() + " not supported between " + left.returnType() + " and " + right.returnType(),
operation.getPosition());
}
}
public static void checkVarType(Token token, Returnable.ReturnType returnType) {
if(returnType.equals(Returnable.ReturnType.STRING) && token.getType().equals(Token.Type.STRING_VARIABLE)) return;
if(returnType.equals(Returnable.ReturnType.NUMBER) && token.getType().equals(Token.Type.NUMBER_VARIABLE)) return;
if(returnType.equals(Returnable.ReturnType.BOOLEAN) && token.getType().equals(Token.Type.BOOLEAN_VARIABLE)) return;
throw new ParseException("Type mismatch, cannot convert from " + returnType + " to " + token.getType(), token.getPosition());
}
/**
* Checks if token is a binary operator
*
* @param token Token to check
*
* @throws ParseException If token isn't a binary operator
*/
public static void checkBinaryOperator(Token token) {
if(!token.isBinaryOperator())
throw new ParseException("Expected binary operator, found " + token.getType(), token.getPosition());
}
public static Returnable.ReturnType getVariableReturnType(Token varToken) {
return switch(varToken.getType()) {
case NUMBER_VARIABLE -> Returnable.ReturnType.NUMBER;
case STRING_VARIABLE -> Returnable.ReturnType.STRING;
case BOOLEAN_VARIABLE -> Returnable.ReturnType.BOOLEAN;
default -> throw new ParseException("Unexpected token " + varToken.getType() + "; expected variable declaration",
varToken.getPosition());
};
}
public static boolean hasPrecedence(Token.Type first, Token.Type second) {
if(!PRECEDENCE.containsKey(first)) return false;
Map<Token.Type, Boolean> pre = PRECEDENCE.get(first);
if(!pre.containsKey(second)) return false;
return pre.get(second);
}
}
@@ -1,13 +0,0 @@
package com.dfsek.terra.addons.terrascript.parser;
import com.dfsek.terra.addons.terrascript.lexer.Token.TokenType;
public enum UnaryOperator {
NOT(TokenType.BANG),
NEGATE(TokenType.MINUS);
public final TokenType tokenType;
UnaryOperator(TokenType tokenType) { this.tokenType = tokenType; }
}
@@ -5,34 +5,34 @@
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.terrascript.parser;
package com.dfsek.terra.addons.terrascript.parser.exceptions;
import java.io.Serial;
import com.dfsek.terra.addons.terrascript.lexer.SourcePosition;
import com.dfsek.terra.addons.terrascript.tokenizer.Position;
public class ParseException extends RuntimeException {
@Serial
private static final long serialVersionUID = 6744390543046766386L;
private final SourcePosition position;
private final Position position;
public ParseException(String message, SourcePosition position) {
public ParseException(String message, Position position) {
super(message);
this.position = position;
}
public ParseException(String message, SourcePosition position, Throwable cause) {
public ParseException(String message, Position position, Throwable cause) {
super(message, cause);
this.position = position;
}
@Override
public String getMessage() {
return "Error at " + position + ": " + super.getMessage();
return super.getMessage() + ": " + position;
}
public SourcePosition getPosition() {
public Position getPosition() {
return position;
}
}
@@ -0,0 +1,76 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.terrascript.parser.lang;
import java.util.List;
import com.dfsek.terra.addons.terrascript.tokenizer.Position;
public class Block implements Item<Block.ReturnInfo<?>> {
private final List<Item<?>> items;
private final Position position;
public Block(List<Item<?>> items, Position position) {
this.items = items;
this.position = position;
}
@Override
public ReturnInfo<?> apply(ImplementationArguments implementationArguments, Scope scope) {
for(Item<?> item : items) {
Object result = item.apply(implementationArguments, scope);
if(result instanceof ReturnInfo<?> level) {
if(!level.getLevel().equals(ReturnLevel.NONE)) return level;
}
}
return new ReturnInfo<>(ReturnLevel.NONE, null);
}
@Override
public Position getPosition() {
return position;
}
public enum ReturnLevel {
NONE(false),
BREAK(false),
CONTINUE(false),
RETURN(true),
FAIL(true);
private final boolean returnFast;
ReturnLevel(boolean returnFast) {
this.returnFast = returnFast;
}
public boolean isReturnFast() {
return returnFast;
}
}
public static class ReturnInfo<T> {
private final ReturnLevel level;
private final T data;
public ReturnInfo(ReturnLevel level, T data) {
this.level = level;
this.data = data;
}
public ReturnLevel getLevel() {
return level;
}
public T getData() {
return data;
}
}
}
@@ -0,0 +1,19 @@
package com.dfsek.terra.addons.terrascript.parser.lang;
import com.dfsek.terra.addons.terrascript.parser.lang.Scope.ScopeBuilder;
public class Executable {
private final Block script;
private final ThreadLocal<Scope> scope;
public Executable(Block script, ScopeBuilder scopeBuilder) {
this.script = script;
this.scope = ThreadLocal.withInitial(scopeBuilder::build);
}
public boolean execute(ImplementationArguments arguments) {
return script.apply(arguments, scope.get()).getLevel() != Block.ReturnLevel.FAIL;
}
}
@@ -0,0 +1,14 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.terrascript.parser.lang;
/**
* Arguments passed to {@link Item}s by the implementation
*/
public interface ImplementationArguments {
}
@@ -0,0 +1,25 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.terrascript.parser.lang;
import com.dfsek.terra.addons.terrascript.tokenizer.Position;
public interface Item<T> {
T apply(ImplementationArguments implementationArguments, Scope scope);
default double applyDouble(ImplementationArguments implementationArguments, Scope scope) {
throw new UnsupportedOperationException("Cannot apply " + this + " as double");
}
default boolean applyBoolean(ImplementationArguments implementationArguments, Scope scope) {
throw new UnsupportedOperationException("Cannot apply " + this + " as double");
}
Position getPosition();
}
@@ -0,0 +1,11 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.terrascript.parser.lang;
public interface Keyword<T> extends Returnable<T> {
}
@@ -0,0 +1,30 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.terrascript.parser.lang;
public interface Returnable<T> extends Item<T> {
ReturnType returnType();
enum ReturnType {
NUMBER(true),
STRING(true),
BOOLEAN(false),
VOID(false),
OBJECT(false);
private final boolean comparable;
ReturnType(boolean comparable) {
this.comparable = comparable;
}
public boolean isComparable() {
return comparable;
}
}
}
@@ -0,0 +1,138 @@
package com.dfsek.terra.addons.terrascript.parser.lang;
import net.jafama.FastMath;
import java.util.HashMap;
import java.util.Map;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable.ReturnType;
import com.dfsek.terra.api.util.generic.pair.Pair;
public class Scope {
private final double[] num;
private final boolean[] bool;
private final String[] str;
private Scope(int numSize, int boolSize, int strSize) {
this.num = new double[numSize];
this.bool = new boolean[boolSize];
this.str = new String[strSize];
}
public double getNum(int index) {
return num[index];
}
public boolean getBool(int index) {
return bool[index];
}
public String getStr(int index) {
return str[index];
}
public void setNum(int index, double value) {
num[index] = value;
}
public void setBool(int index, boolean value) {
bool[index] = value;
}
public void setStr(int index, String value) {
str[index] = value;
}
public static final class ScopeBuilder {
private final Map<String, Pair<Integer, ReturnType>> indices;
private int numSize, boolSize, strSize = 0;
private ScopeBuilder parent;
public ScopeBuilder() {
this.indices = new HashMap<>();
}
private ScopeBuilder(ScopeBuilder parent) {
this.parent = parent;
this.numSize = parent.numSize;
this.boolSize = parent.boolSize;
this.strSize = parent.strSize;
this.indices = new HashMap<>(parent.indices);
}
public Scope build() {
return new Scope(numSize, boolSize, strSize);
}
public ScopeBuilder sub() {
return new ScopeBuilder(this);
}
private String check(String id) {
if(indices.containsKey(id)) {
throw new IllegalArgumentException("Variable with ID " + id + " already registered.");
}
return id;
}
public int num(String id) {
int num = numSize;
indices.put(check(id), Pair.of(num, ReturnType.NUMBER));
numSize++;
updateNumSize(numSize);
return num;
}
public int str(String id) {
int str = strSize;
indices.put(check(id), Pair.of(str, ReturnType.STRING));
strSize++;
updateStrSize(strSize);
return str;
}
public int bool(String id) {
int bool = boolSize;
indices.put(check(id), Pair.of(bool, ReturnType.BOOLEAN));
boolSize++;
updateBoolSize(boolSize);
return bool;
}
private void updateBoolSize(int size) {
this.boolSize = FastMath.max(boolSize, size);
if(parent != null) {
parent.updateBoolSize(size);
}
}
private void updateNumSize(int size) {
this.numSize = FastMath.max(numSize, size);
if(parent != null) {
parent.updateNumSize(size);
}
}
private void updateStrSize(int size) {
this.strSize = FastMath.max(strSize, size);
if(parent != null) {
parent.updateStrSize(size);
}
}
public int getIndex(String id) {
return indices.get(id).getLeft();
}
public ReturnType getType(String id) {
return indices.get(id).getRight();
}
public boolean contains(String id) {
return indices.containsKey(id);
}
}
}
@@ -0,0 +1,11 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.terrascript.parser.lang;
public interface Statement extends Item<Boolean> {
}
@@ -0,0 +1,32 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.terrascript.parser.lang.constants;
import com.dfsek.terra.addons.terrascript.parser.lang.ImplementationArguments;
import com.dfsek.terra.addons.terrascript.parser.lang.Scope;
import com.dfsek.terra.addons.terrascript.tokenizer.Position;
public class BooleanConstant extends ConstantExpression<Boolean> {
private final boolean constant;
public BooleanConstant(Boolean constant, Position position) {
super(constant, position);
this.constant = constant;
}
@Override
public boolean applyBoolean(ImplementationArguments implementationArguments, Scope scope) {
return constant;
}
@Override
public ReturnType returnType() {
return ReturnType.BOOLEAN;
}
}
@@ -0,0 +1,38 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.terrascript.parser.lang.constants;
import com.dfsek.terra.addons.terrascript.parser.lang.ImplementationArguments;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
import com.dfsek.terra.addons.terrascript.parser.lang.Scope;
import com.dfsek.terra.addons.terrascript.tokenizer.Position;
public abstract class ConstantExpression<T> implements Returnable<T> {
private final T constant;
private final Position position;
public ConstantExpression(T constant, Position position) {
this.constant = constant;
this.position = position;
}
@Override
public T apply(ImplementationArguments implementationArguments, Scope scope) {
return constant;
}
@Override
public Position getPosition() {
return position;
}
public T getConstant() {
return constant;
}
}
@@ -0,0 +1,33 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.terrascript.parser.lang.constants;
import com.dfsek.terra.addons.terrascript.parser.lang.ImplementationArguments;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
import com.dfsek.terra.addons.terrascript.parser.lang.Scope;
import com.dfsek.terra.addons.terrascript.tokenizer.Position;
public class NumericConstant extends ConstantExpression<Number> {
private final double constant;
public NumericConstant(Number constant, Position position) {
super(constant, position);
this.constant = constant.doubleValue();
}
@Override
public double applyDouble(ImplementationArguments implementationArguments, Scope scope) {
return constant;
}
@Override
public Returnable.ReturnType returnType() {
return Returnable.ReturnType.NUMBER;
}
}
@@ -0,0 +1,23 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.terrascript.parser.lang.constants;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
import com.dfsek.terra.addons.terrascript.tokenizer.Position;
public class StringConstant extends ConstantExpression<String> {
public StringConstant(String constant, Position position) {
super(constant, position);
}
@Override
public Returnable.ReturnType returnType() {
return Returnable.ReturnType.STRING;
}
}
@@ -0,0 +1,43 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.terrascript.parser.lang.functions;
import com.dfsek.terra.addons.terrascript.parser.lang.ImplementationArguments;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
import com.dfsek.terra.addons.terrascript.parser.lang.Scope;
import com.dfsek.terra.addons.terrascript.tokenizer.Position;
public interface Function<T> extends Returnable<T> {
Function<?> NULL = new Function<>() {
@Override
public ReturnType returnType() {
return null;
}
@Override
public Object apply(ImplementationArguments implementationArguments, Scope scope) {
return null;
}
@Override
public Position getPosition() {
return null;
}
};
@Override
default double applyDouble(ImplementationArguments implementationArguments, Scope scope) {
return ((Number) apply(implementationArguments, scope)).doubleValue();
}
@Override
default boolean applyBoolean(ImplementationArguments implementationArguments, Scope scope) {
return (Boolean) apply(implementationArguments, scope);
}
}
@@ -0,0 +1,22 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.terrascript.parser.lang.functions;
import java.util.List;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
import com.dfsek.terra.addons.terrascript.tokenizer.Position;
public interface FunctionBuilder<T extends Function<?>> {
T build(List<Returnable<?>> argumentList, Position position);
int argNumber();
Returnable.ReturnType getArgument(int position);
}
@@ -0,0 +1,38 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.terrascript.parser.lang.keywords.flow;
import com.dfsek.terra.addons.terrascript.parser.lang.Block;
import com.dfsek.terra.addons.terrascript.parser.lang.ImplementationArguments;
import com.dfsek.terra.addons.terrascript.parser.lang.Keyword;
import com.dfsek.terra.addons.terrascript.parser.lang.Scope;
import com.dfsek.terra.addons.terrascript.tokenizer.Position;
public class BreakKeyword implements Keyword<Block.ReturnInfo<?>> {
private final Position position;
public BreakKeyword(Position position) {
this.position = position;
}
@Override
public Block.ReturnInfo<?> apply(ImplementationArguments implementationArguments, Scope scope) {
return new Block.ReturnInfo<>(Block.ReturnLevel.BREAK, null);
}
@Override
public Position getPosition() {
return position;
}
@Override
public ReturnType returnType() {
return ReturnType.VOID;
}
}
@@ -0,0 +1,38 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.terrascript.parser.lang.keywords.flow;
import com.dfsek.terra.addons.terrascript.parser.lang.Block;
import com.dfsek.terra.addons.terrascript.parser.lang.ImplementationArguments;
import com.dfsek.terra.addons.terrascript.parser.lang.Keyword;
import com.dfsek.terra.addons.terrascript.parser.lang.Scope;
import com.dfsek.terra.addons.terrascript.tokenizer.Position;
public class ContinueKeyword implements Keyword<Block.ReturnInfo<?>> {
private final Position position;
public ContinueKeyword(Position position) {
this.position = position;
}
@Override
public Block.ReturnInfo<?> apply(ImplementationArguments implementationArguments, Scope scope) {
return new Block.ReturnInfo<>(Block.ReturnLevel.CONTINUE, null);
}
@Override
public Position getPosition() {
return position;
}
@Override
public ReturnType returnType() {
return ReturnType.VOID;
}
}
@@ -0,0 +1,38 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.terrascript.parser.lang.keywords.flow;
import com.dfsek.terra.addons.terrascript.parser.lang.Block;
import com.dfsek.terra.addons.terrascript.parser.lang.ImplementationArguments;
import com.dfsek.terra.addons.terrascript.parser.lang.Keyword;
import com.dfsek.terra.addons.terrascript.parser.lang.Scope;
import com.dfsek.terra.addons.terrascript.tokenizer.Position;
public class FailKeyword implements Keyword<Block.ReturnInfo<?>> {
private final Position position;
public FailKeyword(Position position) {
this.position = position;
}
@Override
public Block.ReturnInfo<?> apply(ImplementationArguments implementationArguments, Scope scope) {
return new Block.ReturnInfo<>(Block.ReturnLevel.FAIL, null);
}
@Override
public Position getPosition() {
return position;
}
@Override
public ReturnType returnType() {
return ReturnType.VOID;
}
}
@@ -0,0 +1,38 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.terrascript.parser.lang.keywords.flow;
import com.dfsek.terra.addons.terrascript.parser.lang.Block;
import com.dfsek.terra.addons.terrascript.parser.lang.ImplementationArguments;
import com.dfsek.terra.addons.terrascript.parser.lang.Keyword;
import com.dfsek.terra.addons.terrascript.parser.lang.Scope;
import com.dfsek.terra.addons.terrascript.tokenizer.Position;
public class ReturnKeyword implements Keyword<Block.ReturnInfo<?>> {
private final Position position;
public ReturnKeyword(Position position) {
this.position = position;
}
@Override
public Block.ReturnInfo<?> apply(ImplementationArguments implementationArguments, Scope scope) {
return new Block.ReturnInfo<>(Block.ReturnLevel.RETURN, null);
}
@Override
public Position getPosition() {
return position;
}
@Override
public ReturnType returnType() {
return ReturnType.VOID;
}
}
@@ -0,0 +1,55 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.terrascript.parser.lang.keywords.looplike;
import com.dfsek.terra.addons.terrascript.parser.lang.Block;
import com.dfsek.terra.addons.terrascript.parser.lang.ImplementationArguments;
import com.dfsek.terra.addons.terrascript.parser.lang.Item;
import com.dfsek.terra.addons.terrascript.parser.lang.Keyword;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
import com.dfsek.terra.addons.terrascript.parser.lang.Scope;
import com.dfsek.terra.addons.terrascript.tokenizer.Position;
public class ForKeyword implements Keyword<Block.ReturnInfo<?>> {
private final Block conditional;
private final Item<?> initializer;
private final Returnable<Boolean> statement;
private final Item<?> incrementer;
private final Position position;
public ForKeyword(Block conditional, Item<?> initializer, Returnable<Boolean> statement, Item<?> incrementer, Position position) {
this.conditional = conditional;
this.initializer = initializer;
this.statement = statement;
this.incrementer = incrementer;
this.position = position;
}
@Override
public Block.ReturnInfo<?> apply(ImplementationArguments implementationArguments, Scope scope) {
for(initializer.apply(implementationArguments, scope);
statement.apply(implementationArguments, scope);
incrementer.apply(implementationArguments, scope)) {
Block.ReturnInfo<?> level = conditional.apply(implementationArguments, scope);
if(level.getLevel().equals(Block.ReturnLevel.BREAK)) break;
if(level.getLevel().isReturnFast()) return level;
}
return new Block.ReturnInfo<>(Block.ReturnLevel.NONE, null);
}
@Override
public Position getPosition() {
return position;
}
@Override
public ReturnType returnType() {
return ReturnType.VOID;
}
}
@@ -0,0 +1,62 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.terrascript.parser.lang.keywords.looplike;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import com.dfsek.terra.addons.terrascript.parser.lang.Block;
import com.dfsek.terra.addons.terrascript.parser.lang.ImplementationArguments;
import com.dfsek.terra.addons.terrascript.parser.lang.Keyword;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
import com.dfsek.terra.addons.terrascript.parser.lang.Scope;
import com.dfsek.terra.addons.terrascript.tokenizer.Position;
import com.dfsek.terra.api.util.generic.pair.Pair;
public class IfKeyword implements Keyword<Block.ReturnInfo<?>> {
private final Block conditional;
private final Returnable<Boolean> statement;
private final Position position;
private final List<Pair<Returnable<Boolean>, Block>> elseIf;
private final Block elseBlock;
public IfKeyword(Block conditional, Returnable<Boolean> statement, List<Pair<Returnable<Boolean>, Block>> elseIf,
@Nullable Block elseBlock, Position position) {
this.conditional = conditional;
this.statement = statement;
this.position = position;
this.elseIf = elseIf;
this.elseBlock = elseBlock;
}
@Override
public Block.ReturnInfo<?> apply(ImplementationArguments implementationArguments, Scope scope) {
if(statement.apply(implementationArguments, scope)) return conditional.apply(implementationArguments, scope);
else {
for(Pair<Returnable<Boolean>, Block> pair : elseIf) {
if(pair.getLeft().apply(implementationArguments, scope)) {
return pair.getRight().apply(implementationArguments, scope);
}
}
if(elseBlock != null) return elseBlock.apply(implementationArguments, scope);
}
return new Block.ReturnInfo<>(Block.ReturnLevel.NONE, null);
}
@Override
public Position getPosition() {
return position;
}
@Override
public ReturnType returnType() {
return ReturnType.VOID;
}
}
@@ -0,0 +1,48 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.terrascript.parser.lang.keywords.looplike;
import com.dfsek.terra.addons.terrascript.parser.lang.Block;
import com.dfsek.terra.addons.terrascript.parser.lang.ImplementationArguments;
import com.dfsek.terra.addons.terrascript.parser.lang.Keyword;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
import com.dfsek.terra.addons.terrascript.parser.lang.Scope;
import com.dfsek.terra.addons.terrascript.tokenizer.Position;
public class WhileKeyword implements Keyword<Block.ReturnInfo<?>> {
private final Block conditional;
private final Returnable<Boolean> statement;
private final Position position;
public WhileKeyword(Block conditional, Returnable<Boolean> statement, Position position) {
this.conditional = conditional;
this.statement = statement;
this.position = position;
}
@Override
public Block.ReturnInfo<?> apply(ImplementationArguments implementationArguments, Scope scope) {
while(statement.apply(implementationArguments, scope)) {
Block.ReturnInfo<?> level = conditional.apply(implementationArguments, scope);
if(level.getLevel().equals(Block.ReturnLevel.BREAK)) break;
if(level.getLevel().isReturnFast()) return level;
}
return new Block.ReturnInfo<>(Block.ReturnLevel.NONE, null);
}
@Override
public Position getPosition() {
return position;
}
@Override
public ReturnType returnType() {
return ReturnType.VOID;
}
}
@@ -0,0 +1,29 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.terrascript.parser.lang.operations;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
import com.dfsek.terra.addons.terrascript.tokenizer.Position;
public abstract class BinaryOperation<I, O> implements Returnable<O> {
protected final Returnable<I> left;
protected final Returnable<I> right;
private final Position start;
public BinaryOperation(Returnable<I> left, Returnable<I> right, Position start) {
this.left = left;
this.right = right;
this.start = start;
}
@Override
public Position getPosition() {
return start;
}
}
@@ -0,0 +1,35 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.terrascript.parser.lang.operations;
import com.dfsek.terra.addons.terrascript.parser.lang.ImplementationArguments;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
import com.dfsek.terra.addons.terrascript.parser.lang.Scope;
import com.dfsek.terra.addons.terrascript.tokenizer.Position;
public class BooleanAndOperation extends BinaryOperation<Boolean, Boolean> {
public BooleanAndOperation(Returnable<Boolean> left, Returnable<Boolean> right, Position start) {
super(left, right, start);
}
@Override
public ReturnType returnType() {
return ReturnType.BOOLEAN;
}
@Override
public Boolean apply(ImplementationArguments implementationArguments, Scope scope) {
return applyBoolean(implementationArguments, scope);
}
@Override
public boolean applyBoolean(ImplementationArguments implementationArguments, Scope scope) {
return left.applyBoolean(implementationArguments, scope) && right.applyBoolean(implementationArguments, scope);
}
}
@@ -0,0 +1,35 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.terrascript.parser.lang.operations;
import com.dfsek.terra.addons.terrascript.parser.lang.ImplementationArguments;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
import com.dfsek.terra.addons.terrascript.parser.lang.Scope;
import com.dfsek.terra.addons.terrascript.tokenizer.Position;
public class BooleanNotOperation extends UnaryOperation<Boolean> {
public BooleanNotOperation(Returnable<Boolean> input, Position position) {
super(input, position);
}
@Override
public Boolean apply(ImplementationArguments implementationArguments, Scope scope) {
return applyBoolean(implementationArguments, scope);
}
@Override
public boolean applyBoolean(ImplementationArguments implementationArguments, Scope scope) {
return !input.applyBoolean(implementationArguments, scope);
}
@Override
public ReturnType returnType() {
return ReturnType.BOOLEAN;
}
}
@@ -0,0 +1,35 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.terrascript.parser.lang.operations;
import com.dfsek.terra.addons.terrascript.parser.lang.ImplementationArguments;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
import com.dfsek.terra.addons.terrascript.parser.lang.Scope;
import com.dfsek.terra.addons.terrascript.tokenizer.Position;
public class BooleanOrOperation extends BinaryOperation<Boolean, Boolean> {
public BooleanOrOperation(Returnable<Boolean> left, Returnable<Boolean> right, Position start) {
super(left, right, start);
}
@Override
public Boolean apply(ImplementationArguments implementationArguments, Scope scope) {
return applyBoolean(implementationArguments, scope);
}
@Override
public boolean applyBoolean(ImplementationArguments implementationArguments, Scope scope) {
return left.applyBoolean(implementationArguments, scope) || right.applyBoolean(implementationArguments, scope);
}
@Override
public ReturnType returnType() {
return ReturnType.BOOLEAN;
}
}
@@ -0,0 +1,41 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.terrascript.parser.lang.operations;
import com.dfsek.terra.addons.terrascript.parser.lang.ImplementationArguments;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
import com.dfsek.terra.addons.terrascript.parser.lang.Scope;
import com.dfsek.terra.addons.terrascript.tokenizer.Position;
public class ConcatenationOperation extends BinaryOperation<Object, Object> {
public ConcatenationOperation(Returnable<Object> left, Returnable<Object> right, Position position) {
super(left, right, position);
}
private static String toString(Object object) {
String s = object.toString();
if(object instanceof Double) {
int l = s.length();
if(s.charAt(l - 2) == '.' && s.charAt(l - 1) == '0') {
s = s.substring(0, s.length() - 2);
}
}
return s;
}
@Override
public Returnable.ReturnType returnType() {
return Returnable.ReturnType.STRING;
}
@Override
public Object apply(ImplementationArguments implementationArguments, Scope scope) {
return toString(left.apply(implementationArguments, scope)) + toString(right.apply(implementationArguments, scope));
}
}
@@ -0,0 +1,35 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.terrascript.parser.lang.operations;
import com.dfsek.terra.addons.terrascript.parser.lang.ImplementationArguments;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
import com.dfsek.terra.addons.terrascript.parser.lang.Scope;
import com.dfsek.terra.addons.terrascript.tokenizer.Position;
public class DivisionOperation extends BinaryOperation<Number, Number> {
public DivisionOperation(Returnable<Number> left, Returnable<Number> right, Position position) {
super(left, right, position);
}
@Override
public Returnable.ReturnType returnType() {
return Returnable.ReturnType.NUMBER;
}
@Override
public Number apply(ImplementationArguments implementationArguments, Scope scope) {
return applyDouble(implementationArguments, scope);
}
@Override
public double applyDouble(ImplementationArguments implementationArguments, Scope scope) {
return left.applyDouble(implementationArguments, scope) / right.applyDouble(implementationArguments, scope);
}
}
@@ -0,0 +1,35 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.terrascript.parser.lang.operations;
import com.dfsek.terra.addons.terrascript.parser.lang.ImplementationArguments;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
import com.dfsek.terra.addons.terrascript.parser.lang.Scope;
import com.dfsek.terra.addons.terrascript.tokenizer.Position;
public class ModuloOperation extends BinaryOperation<Number, Number> {
public ModuloOperation(Returnable<Number> left, Returnable<Number> right, Position start) {
super(left, right, start);
}
@Override
public Number apply(ImplementationArguments implementationArguments, Scope scope) {
return applyDouble(implementationArguments, scope);
}
@Override
public double applyDouble(ImplementationArguments implementationArguments, Scope scope) {
return left.applyDouble(implementationArguments, scope) % right.applyDouble(implementationArguments, scope);
}
@Override
public ReturnType returnType() {
return ReturnType.NUMBER;
}
}
@@ -0,0 +1,35 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.terrascript.parser.lang.operations;
import com.dfsek.terra.addons.terrascript.parser.lang.ImplementationArguments;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
import com.dfsek.terra.addons.terrascript.parser.lang.Scope;
import com.dfsek.terra.addons.terrascript.tokenizer.Position;
public class MultiplicationOperation extends BinaryOperation<Number, Number> {
public MultiplicationOperation(Returnable<Number> left, Returnable<Number> right, Position position) {
super(left, right, position);
}
@Override
public Number apply(ImplementationArguments implementationArguments, Scope scope) {
return applyDouble(implementationArguments, scope);
}
@Override
public double applyDouble(ImplementationArguments implementationArguments, Scope scope) {
return left.applyDouble(implementationArguments, scope) * right.applyDouble(implementationArguments, scope);
}
@Override
public ReturnType returnType() {
return ReturnType.NUMBER;
}
}
@@ -0,0 +1,35 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.terrascript.parser.lang.operations;
import com.dfsek.terra.addons.terrascript.parser.lang.ImplementationArguments;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
import com.dfsek.terra.addons.terrascript.parser.lang.Scope;
import com.dfsek.terra.addons.terrascript.tokenizer.Position;
public class NegationOperation extends UnaryOperation<Number> {
public NegationOperation(Returnable<Number> input, Position position) {
super(input, position);
}
@Override
public ReturnType returnType() {
return ReturnType.NUMBER;
}
@Override
public Number apply(ImplementationArguments implementationArguments, Scope scope) {
return applyDouble(implementationArguments, scope);
}
@Override
public double applyDouble(ImplementationArguments implementationArguments, Scope scope) {
return -input.applyDouble(implementationArguments, scope);
}
}
@@ -0,0 +1,35 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.terrascript.parser.lang.operations;
import com.dfsek.terra.addons.terrascript.parser.lang.ImplementationArguments;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
import com.dfsek.terra.addons.terrascript.parser.lang.Scope;
import com.dfsek.terra.addons.terrascript.tokenizer.Position;
public class NumberAdditionOperation extends BinaryOperation<Number, Number> {
public NumberAdditionOperation(Returnable<Number> left, Returnable<Number> right, Position position) {
super(left, right, position);
}
@Override
public Number apply(ImplementationArguments implementationArguments, Scope scope) {
return applyDouble(implementationArguments, scope);
}
@Override
public double applyDouble(ImplementationArguments implementationArguments, Scope scope) {
return left.applyDouble(implementationArguments, scope) + right.applyDouble(implementationArguments, scope);
}
@Override
public ReturnType returnType() {
return ReturnType.NUMBER;
}
}
@@ -0,0 +1,35 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.terrascript.parser.lang.operations;
import com.dfsek.terra.addons.terrascript.parser.lang.ImplementationArguments;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
import com.dfsek.terra.addons.terrascript.parser.lang.Scope;
import com.dfsek.terra.addons.terrascript.tokenizer.Position;
public class SubtractionOperation extends BinaryOperation<Number, Number> {
public SubtractionOperation(Returnable<Number> left, Returnable<Number> right, Position position) {
super(left, right, position);
}
@Override
public Number apply(ImplementationArguments implementationArguments, Scope scope) {
return applyDouble(implementationArguments, scope);
}
@Override
public double applyDouble(ImplementationArguments implementationArguments, Scope scope) {
return left.applyDouble(implementationArguments, scope) - right.applyDouble(implementationArguments, scope);
}
@Override
public ReturnType returnType() {
return ReturnType.NUMBER;
}
}
@@ -0,0 +1,27 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.terrascript.parser.lang.operations;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
import com.dfsek.terra.addons.terrascript.tokenizer.Position;
public abstract class UnaryOperation<T> implements Returnable<T> {
protected final Returnable<T> input;
private final Position position;
public UnaryOperation(Returnable<T> input, Position position) {
this.input = input;
this.position = position;
}
@Override
public Position getPosition() {
return position;
}
}
@@ -0,0 +1,48 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.terrascript.parser.lang.operations.statements;
import net.jafama.FastMath;
import com.dfsek.terra.addons.terrascript.parser.lang.ImplementationArguments;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
import com.dfsek.terra.addons.terrascript.parser.lang.Scope;
import com.dfsek.terra.addons.terrascript.parser.lang.operations.BinaryOperation;
import com.dfsek.terra.addons.terrascript.tokenizer.Position;
import static com.dfsek.terra.api.util.MathUtil.EPSILON;
public class EqualsStatement extends BinaryOperation<Object, Boolean> {
public EqualsStatement(Returnable<Object> left, Returnable<Object> right, Position position) {
super(left, right, position);
}
@Override
public Returnable.ReturnType returnType() {
return Returnable.ReturnType.BOOLEAN;
}
@Override
public Boolean apply(ImplementationArguments implementationArguments, Scope scope) {
return applyBoolean(implementationArguments, scope);
}
@Override
public boolean applyBoolean(ImplementationArguments implementationArguments, Scope scope) {
Object leftValue = left.apply(implementationArguments, scope);
Object rightValue = right.apply(implementationArguments, scope);
if(leftValue instanceof Number l && rightValue instanceof Number r) {
return FastMath.abs(l.doubleValue() - r.doubleValue()) <= EPSILON;
}
return leftValue.equals(rightValue);
}
}
@@ -0,0 +1,36 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.terrascript.parser.lang.operations.statements;
import com.dfsek.terra.addons.terrascript.parser.lang.ImplementationArguments;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
import com.dfsek.terra.addons.terrascript.parser.lang.Scope;
import com.dfsek.terra.addons.terrascript.parser.lang.operations.BinaryOperation;
import com.dfsek.terra.addons.terrascript.tokenizer.Position;
public class GreaterOrEqualsThanStatement extends BinaryOperation<Number, Boolean> {
public GreaterOrEqualsThanStatement(Returnable<Number> left, Returnable<Number> right, Position position) {
super(left, right, position);
}
@Override
public Returnable.ReturnType returnType() {
return Returnable.ReturnType.BOOLEAN;
}
@Override
public Boolean apply(ImplementationArguments implementationArguments, Scope scope) {
return applyBoolean(implementationArguments, scope);
}
@Override
public boolean applyBoolean(ImplementationArguments implementationArguments, Scope scope) {
return left.applyDouble(implementationArguments, scope) >= right.applyDouble(implementationArguments, scope);
}
}
@@ -0,0 +1,37 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.terrascript.parser.lang.operations.statements;
import com.dfsek.terra.addons.terrascript.parser.lang.ImplementationArguments;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
import com.dfsek.terra.addons.terrascript.parser.lang.Scope;
import com.dfsek.terra.addons.terrascript.parser.lang.operations.BinaryOperation;
import com.dfsek.terra.addons.terrascript.tokenizer.Position;
public class GreaterThanStatement extends BinaryOperation<Number, Boolean> {
public GreaterThanStatement(Returnable<Number> left, Returnable<Number> right, Position position) {
super(left, right, position);
}
@Override
public Boolean apply(ImplementationArguments implementationArguments, Scope scope) {
return applyBoolean(implementationArguments, scope);
}
@Override
public boolean applyBoolean(ImplementationArguments implementationArguments, Scope scope) {
return left.applyDouble(implementationArguments, scope) > right.applyDouble(implementationArguments, scope);
}
@Override
public Returnable.ReturnType returnType() {
return Returnable.ReturnType.BOOLEAN;
}
}
@@ -0,0 +1,37 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.terrascript.parser.lang.operations.statements;
import com.dfsek.terra.addons.terrascript.parser.lang.ImplementationArguments;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
import com.dfsek.terra.addons.terrascript.parser.lang.Scope;
import com.dfsek.terra.addons.terrascript.parser.lang.operations.BinaryOperation;
import com.dfsek.terra.addons.terrascript.tokenizer.Position;
public class LessThanOrEqualsStatement extends BinaryOperation<Number, Boolean> {
public LessThanOrEqualsStatement(Returnable<Number> left, Returnable<Number> right, Position position) {
super(left, right, position);
}
@Override
public Boolean apply(ImplementationArguments implementationArguments, Scope scope) {
return applyBoolean(implementationArguments, scope);
}
@Override
public boolean applyBoolean(ImplementationArguments implementationArguments, Scope scope) {
return left.applyDouble(implementationArguments, scope) <= right.applyDouble(implementationArguments, scope);
}
@Override
public Returnable.ReturnType returnType() {
return Returnable.ReturnType.BOOLEAN;
}
}
@@ -0,0 +1,37 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.terrascript.parser.lang.operations.statements;
import com.dfsek.terra.addons.terrascript.parser.lang.ImplementationArguments;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
import com.dfsek.terra.addons.terrascript.parser.lang.Scope;
import com.dfsek.terra.addons.terrascript.parser.lang.operations.BinaryOperation;
import com.dfsek.terra.addons.terrascript.tokenizer.Position;
public class LessThanStatement extends BinaryOperation<Number, Boolean> {
public LessThanStatement(Returnable<Number> left, Returnable<Number> right, Position position) {
super(left, right, position);
}
@Override
public Boolean apply(ImplementationArguments implementationArguments, Scope scope) {
return applyBoolean(implementationArguments, scope);
}
@Override
public boolean applyBoolean(ImplementationArguments implementationArguments, Scope scope) {
return left.applyDouble(implementationArguments, scope) < right.applyDouble(implementationArguments, scope);
}
@Override
public Returnable.ReturnType returnType() {
return Returnable.ReturnType.BOOLEAN;
}
}
@@ -0,0 +1,46 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.terrascript.parser.lang.operations.statements;
import net.jafama.FastMath;
import com.dfsek.terra.addons.terrascript.parser.lang.ImplementationArguments;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
import com.dfsek.terra.addons.terrascript.parser.lang.Scope;
import com.dfsek.terra.addons.terrascript.parser.lang.operations.BinaryOperation;
import com.dfsek.terra.addons.terrascript.tokenizer.Position;
import static com.dfsek.terra.api.util.MathUtil.EPSILON;
public class NotEqualsStatement extends BinaryOperation<Object, Boolean> {
public NotEqualsStatement(Returnable<Object> left, Returnable<Object> right, Position position) {
super(left, right, position);
}
@Override
public Boolean apply(ImplementationArguments implementationArguments, Scope scope) {
return applyBoolean(implementationArguments, scope);
}
@Override
public boolean applyBoolean(ImplementationArguments implementationArguments, Scope scope) {
Object leftValue = left.apply(implementationArguments, scope);
Object rightValue = right.apply(implementationArguments, scope);
if(leftValue instanceof Number l && rightValue instanceof Number r) {
return FastMath.abs(l.doubleValue() - r.doubleValue()) > EPSILON;
}
return !leftValue.equals(rightValue);
}
@Override
public Returnable.ReturnType returnType() {
return Returnable.ReturnType.BOOLEAN;
}
}
@@ -0,0 +1,42 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.terrascript.parser.lang.variables;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
import com.dfsek.terra.addons.terrascript.tokenizer.Position;
public class BooleanVariable implements Variable<Boolean> {
private final Position position;
private Boolean value;
public BooleanVariable(Boolean value, Position position) {
this.value = value;
this.position = position;
}
@Override
public Boolean getValue() {
return value;
}
@Override
public void setValue(Boolean value) {
this.value = value;
}
@Override
public Returnable.ReturnType getType() {
return Returnable.ReturnType.BOOLEAN;
}
@Override
public Position getPosition() {
return position;
}
}
@@ -0,0 +1,42 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.terrascript.parser.lang.variables;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
import com.dfsek.terra.addons.terrascript.tokenizer.Position;
public class NumberVariable implements Variable<Number> {
private final Position position;
private Number value;
public NumberVariable(Number value, Position position) {
this.value = value;
this.position = position;
}
@Override
public Number getValue() {
return value;
}
@Override
public void setValue(Number value) {
this.value = value;
}
@Override
public Returnable.ReturnType getType() {
return Returnable.ReturnType.NUMBER;
}
@Override
public Position getPosition() {
return position;
}
}
@@ -0,0 +1,42 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.terrascript.parser.lang.variables;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
import com.dfsek.terra.addons.terrascript.tokenizer.Position;
public class StringVariable implements Variable<String> {
private final Position position;
private String value;
public StringVariable(String value, Position position) {
this.value = value;
this.position = position;
}
@Override
public String getValue() {
return value;
}
@Override
public void setValue(String value) {
this.value = value;
}
@Override
public Returnable.ReturnType getType() {
return Returnable.ReturnType.STRING;
}
@Override
public Position getPosition() {
return position;
}
}
@@ -0,0 +1,22 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.terrascript.parser.lang.variables;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
import com.dfsek.terra.addons.terrascript.tokenizer.Position;
public interface Variable<T> {
T getValue();
void setValue(T value);
Returnable.ReturnType getType();
Position getPosition();
}
@@ -0,0 +1,25 @@
package com.dfsek.terra.addons.terrascript.parser.lang.variables.assign;
import com.dfsek.terra.addons.terrascript.parser.lang.ImplementationArguments;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
import com.dfsek.terra.addons.terrascript.parser.lang.Scope;
import com.dfsek.terra.addons.terrascript.tokenizer.Position;
public class BoolAssignmentNode extends VariableAssignmentNode<Boolean> {
public BoolAssignmentNode(Returnable<Boolean> value, Position position, int index) {
super(value, position, index);
}
@Override
public Boolean apply(ImplementationArguments implementationArguments, Scope scope) {
return applyBoolean(implementationArguments, scope);
}
@Override
public boolean applyBoolean(ImplementationArguments implementationArguments, Scope scope) {
boolean val = value.applyBoolean(implementationArguments, scope);
scope.setBool(index, val);
return val;
}
}
@@ -0,0 +1,25 @@
package com.dfsek.terra.addons.terrascript.parser.lang.variables.assign;
import com.dfsek.terra.addons.terrascript.parser.lang.ImplementationArguments;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
import com.dfsek.terra.addons.terrascript.parser.lang.Scope;
import com.dfsek.terra.addons.terrascript.tokenizer.Position;
public class NumAssignmentNode extends VariableAssignmentNode<Number> {
public NumAssignmentNode(Returnable<Number> value, Position position, int index) {
super(value, position, index);
}
@Override
public Number apply(ImplementationArguments implementationArguments, Scope scope) {
return applyDouble(implementationArguments, scope);
}
@Override
public double applyDouble(ImplementationArguments implementationArguments, Scope scope) {
double val = value.applyDouble(implementationArguments, scope);
scope.setNum(index, val);
return val;
}
}
@@ -0,0 +1,21 @@
package com.dfsek.terra.addons.terrascript.parser.lang.variables.assign;
import com.dfsek.terra.addons.terrascript.parser.lang.ImplementationArguments;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
import com.dfsek.terra.addons.terrascript.parser.lang.Scope;
import com.dfsek.terra.addons.terrascript.tokenizer.Position;
public class StrAssignmentNode extends VariableAssignmentNode<String> {
public StrAssignmentNode(Returnable<String> value, Position position, int index) {
super(value, position, index);
}
@Override
public String apply(ImplementationArguments implementationArguments, Scope scope) {
String val = value.apply(implementationArguments, scope);
scope.setStr(index, val);
return val;
}
}
@@ -0,0 +1,31 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.terrascript.parser.lang.variables.assign;
import com.dfsek.terra.addons.terrascript.parser.lang.Item;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
import com.dfsek.terra.addons.terrascript.tokenizer.Position;
public abstract class VariableAssignmentNode<T> implements Item<T> {
protected final Returnable<T> value;
protected final int index;
private final Position position;
public VariableAssignmentNode(Returnable<T> value, Position position, int index) {
this.value = value;
this.index = index;
this.position = position;
}
@Override
public Position getPosition() {
return position;
}
}
@@ -0,0 +1,22 @@
package com.dfsek.terra.addons.terrascript.parser.lang.variables.reference;
import com.dfsek.terra.addons.terrascript.parser.lang.ImplementationArguments;
import com.dfsek.terra.addons.terrascript.parser.lang.Scope;
import com.dfsek.terra.addons.terrascript.tokenizer.Position;
public class BoolVariableReferenceNode extends VariableReferenceNode<Boolean> {
public BoolVariableReferenceNode(Position position, ReturnType type, int index) {
super(position, type, index);
}
@Override
public Boolean apply(ImplementationArguments implementationArguments, Scope scope) {
return scope.getBool(index);
}
@Override
public boolean applyBoolean(ImplementationArguments implementationArguments, Scope scope) {
return scope.getBool(index);
}
}
@@ -0,0 +1,22 @@
package com.dfsek.terra.addons.terrascript.parser.lang.variables.reference;
import com.dfsek.terra.addons.terrascript.parser.lang.ImplementationArguments;
import com.dfsek.terra.addons.terrascript.parser.lang.Scope;
import com.dfsek.terra.addons.terrascript.tokenizer.Position;
public class NumVariableReferenceNode extends VariableReferenceNode<Number> {
public NumVariableReferenceNode(Position position, ReturnType type, int index) {
super(position, type, index);
}
@Override
public Number apply(ImplementationArguments implementationArguments, Scope scope) {
return scope.getNum(index);
}
@Override
public double applyDouble(ImplementationArguments implementationArguments, Scope scope) {
return scope.getNum(index);
}
}
@@ -0,0 +1,17 @@
package com.dfsek.terra.addons.terrascript.parser.lang.variables.reference;
import com.dfsek.terra.addons.terrascript.parser.lang.ImplementationArguments;
import com.dfsek.terra.addons.terrascript.parser.lang.Scope;
import com.dfsek.terra.addons.terrascript.tokenizer.Position;
public class StrVariableReferenceNode extends VariableReferenceNode<String> {
public StrVariableReferenceNode(Position position, ReturnType type, int index) {
super(position, type, index);
}
@Override
public String apply(ImplementationArguments implementationArguments, Scope scope) {
return scope.getStr(index);
}
}
@@ -0,0 +1,34 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.terrascript.parser.lang.variables.reference;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
import com.dfsek.terra.addons.terrascript.tokenizer.Position;
public abstract class VariableReferenceNode<T> implements Returnable<T> {
protected final int index;
private final Position position;
private final ReturnType type;
public VariableReferenceNode(Position position, ReturnType type, int index) {
this.position = position;
this.type = type;
this.index = index;
}
@Override
public ReturnType returnType() {
return type;
}
@Override
public Position getPosition() {
return position;
}
}
@@ -0,0 +1,160 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.terrascript.script;
import net.jafama.FastMath;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.Random;
import com.dfsek.terra.addons.terrascript.parser.Parser;
import com.dfsek.terra.addons.terrascript.parser.lang.Executable;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
import com.dfsek.terra.addons.terrascript.parser.lang.functions.FunctionBuilder;
import com.dfsek.terra.addons.terrascript.script.builders.BinaryNumberFunctionBuilder;
import com.dfsek.terra.addons.terrascript.script.builders.BiomeFunctionBuilder;
import com.dfsek.terra.addons.terrascript.script.builders.BlockFunctionBuilder;
import com.dfsek.terra.addons.terrascript.script.builders.CheckBlockFunctionBuilder;
import com.dfsek.terra.addons.terrascript.script.builders.EntityFunctionBuilder;
import com.dfsek.terra.addons.terrascript.script.builders.GetMarkFunctionBuilder;
import com.dfsek.terra.addons.terrascript.script.builders.LootFunctionBuilder;
import com.dfsek.terra.addons.terrascript.script.builders.PullFunctionBuilder;
import com.dfsek.terra.addons.terrascript.script.builders.RandomFunctionBuilder;
import com.dfsek.terra.addons.terrascript.script.builders.RecursionsFunctionBuilder;
import com.dfsek.terra.addons.terrascript.script.builders.SetMarkFunctionBuilder;
import com.dfsek.terra.addons.terrascript.script.builders.StateFunctionBuilder;
import com.dfsek.terra.addons.terrascript.script.builders.StructureFunctionBuilder;
import com.dfsek.terra.addons.terrascript.script.builders.UnaryBooleanFunctionBuilder;
import com.dfsek.terra.addons.terrascript.script.builders.UnaryNumberFunctionBuilder;
import com.dfsek.terra.addons.terrascript.script.builders.UnaryStringFunctionBuilder;
import com.dfsek.terra.addons.terrascript.script.builders.ZeroArgFunctionBuilder;
import com.dfsek.terra.api.Platform;
import com.dfsek.terra.api.registry.Registry;
import com.dfsek.terra.api.registry.key.Keyed;
import com.dfsek.terra.api.registry.key.RegistryKey;
import com.dfsek.terra.api.structure.LootTable;
import com.dfsek.terra.api.structure.Structure;
import com.dfsek.terra.api.util.Rotation;
import com.dfsek.terra.api.util.vector.Vector3Int;
import com.dfsek.terra.api.world.WritableWorld;
public class StructureScript implements Structure, Keyed<StructureScript> {
private static final Logger LOGGER = LoggerFactory.getLogger(StructureScript.class);
private final Executable block;
private final RegistryKey id;
private final String profile;
private final Platform platform;
@SuppressWarnings("rawtypes")
public StructureScript(InputStream inputStream, RegistryKey id, Platform platform, Registry<Structure> registry,
Registry<LootTable> lootRegistry,
Registry<FunctionBuilder> functionRegistry) {
Parser parser;
try {
parser = new Parser(IOUtils.toString(inputStream, Charset.defaultCharset()));
} catch(IOException e) {
throw new RuntimeException(e);
}
this.id = id;
this.profile = "terrascript_direct:" + id;
//noinspection unchecked
functionRegistry.forEach((key, function) -> parser.registerFunction(key.getID(), function)); // Register registry functions.
parser
.registerFunction("block", new BlockFunctionBuilder(platform))
.registerFunction("debugBlock", new BlockFunctionBuilder(platform))
.registerFunction("structure", new StructureFunctionBuilder(registry, platform))
.registerFunction("randomInt", new RandomFunctionBuilder())
.registerFunction("recursions", new RecursionsFunctionBuilder())
.registerFunction("setMark", new SetMarkFunctionBuilder())
.registerFunction("getMark", new GetMarkFunctionBuilder())
.registerFunction("pull", new PullFunctionBuilder(platform))
.registerFunction("loot", new LootFunctionBuilder(platform, lootRegistry, this))
.registerFunction("entity", new EntityFunctionBuilder(platform))
.registerFunction("getBiome", new BiomeFunctionBuilder(platform))
.registerFunction("getBlock", new CheckBlockFunctionBuilder())
.registerFunction("state", new StateFunctionBuilder(platform))
.registerFunction("setWaterlog", new UnaryBooleanFunctionBuilder((waterlog, args) -> args.setWaterlog(waterlog)))
.registerFunction("originX", new ZeroArgFunctionBuilder<Number>(arguments -> arguments.getOrigin().getX(),
Returnable.ReturnType.NUMBER))
.registerFunction("originY", new ZeroArgFunctionBuilder<Number>(arguments -> arguments.getOrigin().getY(),
Returnable.ReturnType.NUMBER))
.registerFunction("originZ", new ZeroArgFunctionBuilder<Number>(arguments -> arguments.getOrigin().getZ(),
Returnable.ReturnType.NUMBER))
.registerFunction("rotation", new ZeroArgFunctionBuilder<>(arguments -> arguments.getRotation().toString(),
Returnable.ReturnType.STRING))
.registerFunction("rotationDegrees", new ZeroArgFunctionBuilder<>(arguments -> arguments.getRotation().getDegrees(),
Returnable.ReturnType.NUMBER))
.registerFunction("print",
new UnaryStringFunctionBuilder(string -> LOGGER.info("[TerraScript:{}] {}", id, string)))
.registerFunction("abs", new UnaryNumberFunctionBuilder(number -> FastMath.abs(number.doubleValue())))
.registerFunction("pow2", new UnaryNumberFunctionBuilder(number -> FastMath.pow2(number.doubleValue())))
.registerFunction("pow", new BinaryNumberFunctionBuilder(
(number, number2) -> FastMath.pow(number.doubleValue(), number2.doubleValue())))
.registerFunction("sqrt", new UnaryNumberFunctionBuilder(number -> FastMath.sqrt(number.doubleValue())))
.registerFunction("floor", new UnaryNumberFunctionBuilder(number -> FastMath.floor(number.doubleValue())))
.registerFunction("ceil", new UnaryNumberFunctionBuilder(number -> FastMath.ceil(number.doubleValue())))
.registerFunction("log", new UnaryNumberFunctionBuilder(number -> FastMath.log(number.doubleValue())))
.registerFunction("round", new UnaryNumberFunctionBuilder(number -> FastMath.round(number.doubleValue())))
.registerFunction("sin", new UnaryNumberFunctionBuilder(number -> FastMath.sin(number.doubleValue())))
.registerFunction("cos", new UnaryNumberFunctionBuilder(number -> FastMath.cos(number.doubleValue())))
.registerFunction("tan", new UnaryNumberFunctionBuilder(number -> FastMath.tan(number.doubleValue())))
.registerFunction("asin", new UnaryNumberFunctionBuilder(number -> FastMath.asin(number.doubleValue())))
.registerFunction("acos", new UnaryNumberFunctionBuilder(number -> FastMath.acos(number.doubleValue())))
.registerFunction("atan", new UnaryNumberFunctionBuilder(number -> FastMath.atan(number.doubleValue())))
.registerFunction("max", new BinaryNumberFunctionBuilder(
(number, number2) -> FastMath.max(number.doubleValue(), number2.doubleValue())))
.registerFunction("min", new BinaryNumberFunctionBuilder(
(number, number2) -> FastMath.min(number.doubleValue(), number2.doubleValue())));
if(!platform.getTerraConfig().isDebugScript()) {
parser.ignoreFunction("debugBlock");
}
block = parser.parse();
this.platform = platform;
}
@Override
@SuppressWarnings("try")
public boolean generate(Vector3Int location, WritableWorld world, Random random, Rotation rotation) {
platform.getProfiler().push(profile);
boolean result = applyBlock(new TerraImplementationArguments(location, rotation, random, world, 0));
platform.getProfiler().pop(profile);
return result;
}
public boolean generate(Vector3Int location, WritableWorld world, Random random, Rotation rotation, int recursions) {
platform.getProfiler().push(profile);
boolean result = applyBlock(new TerraImplementationArguments(location, rotation, random, world, recursions));
platform.getProfiler().pop(profile);
return result;
}
private boolean applyBlock(TerraImplementationArguments arguments) {
try {
return block.execute(arguments);
} catch(RuntimeException e) {
LOGGER.error("Failed to generate structure at {}", arguments.getOrigin(), e);
return false;
}
}
@Override
public RegistryKey getRegistryKey() {
return id;
}
}
@@ -0,0 +1,73 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.terrascript.script;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import com.dfsek.terra.addons.terrascript.parser.lang.ImplementationArguments;
import com.dfsek.terra.api.util.Rotation;
import com.dfsek.terra.api.util.vector.Vector3;
import com.dfsek.terra.api.util.vector.Vector3Int;
import com.dfsek.terra.api.world.WritableWorld;
public class TerraImplementationArguments implements ImplementationArguments {
private final Rotation rotation;
private final Random random;
private final WritableWorld world;
private final Map<Vector3, String> marks = new HashMap<>();
private final int recursions;
private final Vector3Int origin;
private boolean waterlog = false;
public TerraImplementationArguments(Vector3Int origin, Rotation rotation, Random random, WritableWorld world, int recursions) {
this.rotation = rotation;
this.random = random;
this.world = world;
this.recursions = recursions;
this.origin = origin;
}
public int getRecursions() {
return recursions;
}
public Random getRandom() {
return random;
}
public Rotation getRotation() {
return rotation;
}
public boolean isWaterlog() {
return waterlog;
}
public void setWaterlog(boolean waterlog) {
this.waterlog = waterlog;
}
public WritableWorld getWorld() {
return world;
}
public Vector3Int getOrigin() {
return origin;
}
public void setMark(Vector3 pos, String mark) {
marks.put(pos, mark);
}
public String getMark(Vector3 pos) {
return marks.get(pos);
}
}
@@ -0,0 +1,61 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.terrascript.script.builders;
import java.util.List;
import java.util.function.BiFunction;
import com.dfsek.terra.addons.terrascript.parser.lang.ImplementationArguments;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
import com.dfsek.terra.addons.terrascript.parser.lang.Scope;
import com.dfsek.terra.addons.terrascript.parser.lang.functions.Function;
import com.dfsek.terra.addons.terrascript.parser.lang.functions.FunctionBuilder;
import com.dfsek.terra.addons.terrascript.tokenizer.Position;
public class BinaryNumberFunctionBuilder implements FunctionBuilder<Function<Number>> {
private final BiFunction<Number, Number, Number> function;
public BinaryNumberFunctionBuilder(BiFunction<Number, Number, Number> function) {
this.function = function;
}
@Override
public Function<Number> build(List<Returnable<?>> argumentList, Position position) {
return new Function<>() {
@Override
public ReturnType returnType() {
return ReturnType.NUMBER;
}
@SuppressWarnings("unchecked")
@Override
public Number apply(ImplementationArguments implementationArguments, Scope scope) {
return function.apply(((Returnable<Number>) argumentList.get(0)).apply(implementationArguments, scope),
((Returnable<Number>) argumentList.get(1)).apply(implementationArguments, scope));
}
@Override
public Position getPosition() {
return position;
}
};
}
@Override
public int argNumber() {
return 2;
}
@Override
public Returnable.ReturnType getArgument(int position) {
if(position == 0 || position == 1) return Returnable.ReturnType.NUMBER;
return null;
}
}
@@ -0,0 +1,45 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.terrascript.script.builders;
import java.util.List;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
import com.dfsek.terra.addons.terrascript.parser.lang.functions.FunctionBuilder;
import com.dfsek.terra.addons.terrascript.script.functions.BiomeFunction;
import com.dfsek.terra.addons.terrascript.tokenizer.Position;
import com.dfsek.terra.api.Platform;
public class BiomeFunctionBuilder implements FunctionBuilder<BiomeFunction> {
private final Platform platform;
public BiomeFunctionBuilder(Platform platform) {
this.platform = platform;
}
@SuppressWarnings("unchecked")
@Override
public BiomeFunction build(List<Returnable<?>> argumentList, Position position) {
return new BiomeFunction((Returnable<Number>) argumentList.get(0), (Returnable<Number>) argumentList.get(1),
(Returnable<Number>) argumentList.get(2), position);
}
@Override
public int argNumber() {
return 3;
}
@Override
public Returnable.ReturnType getArgument(int position) {
return switch(position) {
case 0, 1, 2 -> Returnable.ReturnType.NUMBER;
default -> null;
};
}
}
@@ -0,0 +1,61 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.terrascript.script.builders;
import java.util.List;
import com.dfsek.terra.addons.terrascript.parser.exceptions.ParseException;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
import com.dfsek.terra.addons.terrascript.parser.lang.constants.BooleanConstant;
import com.dfsek.terra.addons.terrascript.parser.lang.constants.StringConstant;
import com.dfsek.terra.addons.terrascript.parser.lang.functions.FunctionBuilder;
import com.dfsek.terra.addons.terrascript.script.functions.BlockFunction;
import com.dfsek.terra.addons.terrascript.tokenizer.Position;
import com.dfsek.terra.api.Platform;
public class BlockFunctionBuilder implements FunctionBuilder<BlockFunction> {
private final Platform platform;
public BlockFunctionBuilder(Platform platform) {
this.platform = platform;
}
@SuppressWarnings("unchecked")
@Override
public BlockFunction build(List<Returnable<?>> argumentList, Position position) {
if(argumentList.size() < 4) throw new ParseException("Expected data", position);
Returnable<Boolean> overwrite = new BooleanConstant(true, position);
if(argumentList.size() >= 5) overwrite = (Returnable<Boolean>) argumentList.get(4);
Returnable<Boolean> physics = new BooleanConstant(false, position);
if(argumentList.size() == 6) physics = (Returnable<Boolean>) argumentList.get(5);
if(argumentList.get(3) instanceof StringConstant) {
return new BlockFunction.Constant((Returnable<Number>) argumentList.get(0), (Returnable<Number>) argumentList.get(1),
(Returnable<Number>) argumentList.get(2), (StringConstant) argumentList.get(3),
overwrite, physics, platform, position);
}
return new BlockFunction((Returnable<Number>) argumentList.get(0), (Returnable<Number>) argumentList.get(1),
(Returnable<Number>) argumentList.get(2), (Returnable<String>) argumentList.get(3), overwrite, physics,
platform, position);
}
@Override
public int argNumber() {
return -1;
}
@Override
public Returnable.ReturnType getArgument(int position) {
return switch(position) {
case 0, 1, 2 -> Returnable.ReturnType.NUMBER;
case 3 -> Returnable.ReturnType.STRING;
case 4, 5 -> Returnable.ReturnType.BOOLEAN;
default -> null;
};
}
}
@@ -0,0 +1,38 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.terrascript.script.builders;
import java.util.List;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
import com.dfsek.terra.addons.terrascript.parser.lang.functions.FunctionBuilder;
import com.dfsek.terra.addons.terrascript.script.functions.CheckBlockFunction;
import com.dfsek.terra.addons.terrascript.tokenizer.Position;
public class CheckBlockFunctionBuilder implements FunctionBuilder<CheckBlockFunction> {
@SuppressWarnings("unchecked")
@Override
public CheckBlockFunction build(List<Returnable<?>> argumentList, Position position) {
return new CheckBlockFunction((Returnable<Number>) argumentList.get(0), (Returnable<Number>) argumentList.get(1),
(Returnable<Number>) argumentList.get(2), position);
}
@Override
public int argNumber() {
return 3;
}
@Override
public Returnable.ReturnType getArgument(int position) {
return switch(position) {
case 0, 1, 2 -> Returnable.ReturnType.NUMBER;
default -> null;
};
}
}
@@ -0,0 +1,46 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.terrascript.script.builders;
import java.util.List;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
import com.dfsek.terra.addons.terrascript.parser.lang.functions.FunctionBuilder;
import com.dfsek.terra.addons.terrascript.script.functions.EntityFunction;
import com.dfsek.terra.addons.terrascript.tokenizer.Position;
import com.dfsek.terra.api.Platform;
public class EntityFunctionBuilder implements FunctionBuilder<EntityFunction> {
private final Platform platform;
public EntityFunctionBuilder(Platform platform) {
this.platform = platform;
}
@SuppressWarnings("unchecked")
@Override
public EntityFunction build(List<Returnable<?>> argumentList, Position position) {
return new EntityFunction((Returnable<Number>) argumentList.get(0), (Returnable<Number>) argumentList.get(1),
(Returnable<Number>) argumentList.get(2), (Returnable<String>) argumentList.get(3), platform, position);
}
@Override
public int argNumber() {
return 4;
}
@Override
public Returnable.ReturnType getArgument(int position) {
return switch(position) {
case 0, 1, 2 -> Returnable.ReturnType.NUMBER;
case 3 -> Returnable.ReturnType.STRING;
default -> null;
};
}
}
@@ -0,0 +1,42 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.terrascript.script.builders;
import java.util.List;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
import com.dfsek.terra.addons.terrascript.parser.lang.functions.FunctionBuilder;
import com.dfsek.terra.addons.terrascript.script.functions.GetMarkFunction;
import com.dfsek.terra.addons.terrascript.tokenizer.Position;
public class GetMarkFunctionBuilder implements FunctionBuilder<GetMarkFunction> {
public GetMarkFunctionBuilder() {
}
@SuppressWarnings("unchecked")
@Override
public GetMarkFunction build(List<Returnable<?>> argumentList, Position position) {
return new GetMarkFunction((Returnable<Number>) argumentList.get(0), (Returnable<Number>) argumentList.get(1),
(Returnable<Number>) argumentList.get(2), position);
}
@Override
public int argNumber() {
return 3;
}
@Override
public Returnable.ReturnType getArgument(int position) {
return switch(position) {
case 0, 1, 2 -> Returnable.ReturnType.NUMBER;
default -> null;
};
}
}
@@ -0,0 +1,54 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.terrascript.script.builders;
import java.util.List;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
import com.dfsek.terra.addons.terrascript.parser.lang.functions.FunctionBuilder;
import com.dfsek.terra.addons.terrascript.script.StructureScript;
import com.dfsek.terra.addons.terrascript.script.functions.LootFunction;
import com.dfsek.terra.addons.terrascript.tokenizer.Position;
import com.dfsek.terra.api.Platform;
import com.dfsek.terra.api.registry.Registry;
import com.dfsek.terra.api.structure.LootTable;
public class LootFunctionBuilder implements FunctionBuilder<LootFunction> {
private final Platform platform;
private final Registry<LootTable> registry;
private final StructureScript script;
public LootFunctionBuilder(Platform platform, Registry<LootTable> registry, StructureScript script) {
this.platform = platform;
this.registry = registry;
this.script = script;
}
@SuppressWarnings("unchecked")
@Override
public LootFunction build(List<Returnable<?>> argumentList, Position position) {
return new LootFunction(registry, (Returnable<Number>) argumentList.get(0), (Returnable<Number>) argumentList.get(1),
(Returnable<Number>) argumentList.get(2), (Returnable<String>) argumentList.get(3), platform, position,
script);
}
@Override
public int argNumber() {
return 4;
}
@Override
public Returnable.ReturnType getArgument(int position) {
return switch(position) {
case 0, 1, 2 -> Returnable.ReturnType.NUMBER;
case 3 -> Returnable.ReturnType.STRING;
default -> null;
};
}
}
@@ -0,0 +1,46 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.terrascript.script.builders;
import java.util.List;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
import com.dfsek.terra.addons.terrascript.parser.lang.functions.FunctionBuilder;
import com.dfsek.terra.addons.terrascript.script.functions.PullFunction;
import com.dfsek.terra.addons.terrascript.tokenizer.Position;
import com.dfsek.terra.api.Platform;
public class PullFunctionBuilder implements FunctionBuilder<PullFunction> {
private final Platform platform;
public PullFunctionBuilder(Platform platform) {
this.platform = platform;
}
@SuppressWarnings("unchecked")
@Override
public PullFunction build(List<Returnable<?>> argumentList, Position position) {
return new PullFunction((Returnable<Number>) argumentList.get(0), (Returnable<Number>) argumentList.get(1),
(Returnable<Number>) argumentList.get(2), (Returnable<String>) argumentList.get(3), platform, position);
}
@Override
public int argNumber() {
return 4;
}
@Override
public Returnable.ReturnType getArgument(int position) {
return switch(position) {
case 0, 1, 2 -> Returnable.ReturnType.NUMBER;
case 3 -> Returnable.ReturnType.STRING;
default -> null;
};
}
}
@@ -0,0 +1,35 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.terrascript.script.builders;
import java.util.List;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
import com.dfsek.terra.addons.terrascript.parser.lang.functions.FunctionBuilder;
import com.dfsek.terra.addons.terrascript.script.functions.RandomFunction;
import com.dfsek.terra.addons.terrascript.tokenizer.Position;
public class RandomFunctionBuilder implements FunctionBuilder<RandomFunction> {
@SuppressWarnings("unchecked")
@Override
public RandomFunction build(List<Returnable<?>> argumentList, Position position) {
return new RandomFunction((Returnable<Number>) argumentList.get(0), position);
}
@Override
public int argNumber() {
return 1;
}
@Override
public Returnable.ReturnType getArgument(int position) {
if(position == 0) return Returnable.ReturnType.NUMBER;
return null;
}
}
@@ -0,0 +1,33 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.terrascript.script.builders;
import java.util.List;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
import com.dfsek.terra.addons.terrascript.parser.lang.functions.FunctionBuilder;
import com.dfsek.terra.addons.terrascript.script.functions.RecursionsFunction;
import com.dfsek.terra.addons.terrascript.tokenizer.Position;
public class RecursionsFunctionBuilder implements FunctionBuilder<RecursionsFunction> {
@Override
public RecursionsFunction build(List<Returnable<?>> argumentList, Position position) {
return new RecursionsFunction(position);
}
@Override
public int argNumber() {
return 0;
}
@Override
public Returnable.ReturnType getArgument(int position) {
return null;
}
}
@@ -0,0 +1,43 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.terrascript.script.builders;
import java.util.List;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
import com.dfsek.terra.addons.terrascript.parser.lang.functions.FunctionBuilder;
import com.dfsek.terra.addons.terrascript.script.functions.SetMarkFunction;
import com.dfsek.terra.addons.terrascript.tokenizer.Position;
public class SetMarkFunctionBuilder implements FunctionBuilder<SetMarkFunction> {
public SetMarkFunctionBuilder() {
}
@SuppressWarnings("unchecked")
@Override
public SetMarkFunction build(List<Returnable<?>> argumentList, Position position) {
return new SetMarkFunction((Returnable<Number>) argumentList.get(0), (Returnable<Number>) argumentList.get(1),
(Returnable<Number>) argumentList.get(2), (Returnable<String>) argumentList.get(3), position);
}
@Override
public int argNumber() {
return 4;
}
@Override
public Returnable.ReturnType getArgument(int position) {
return switch(position) {
case 0, 1, 2 -> Returnable.ReturnType.NUMBER;
case 3 -> Returnable.ReturnType.STRING;
default -> null;
};
}
}
@@ -0,0 +1,48 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.terrascript.script.builders;
import java.util.List;
import com.dfsek.terra.addons.terrascript.parser.exceptions.ParseException;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
import com.dfsek.terra.addons.terrascript.parser.lang.functions.FunctionBuilder;
import com.dfsek.terra.addons.terrascript.script.functions.StateFunction;
import com.dfsek.terra.addons.terrascript.tokenizer.Position;
import com.dfsek.terra.api.Platform;
public class StateFunctionBuilder implements FunctionBuilder<StateFunction> {
private final Platform platform;
public StateFunctionBuilder(Platform platform) {
this.platform = platform;
}
@SuppressWarnings("unchecked")
@Override
public StateFunction build(List<Returnable<?>> argumentList, Position position) {
if(argumentList.size() < 4) throw new ParseException("Expected data", position);
return new StateFunction((Returnable<Number>) argumentList.get(0), (Returnable<Number>) argumentList.get(1),
(Returnable<Number>) argumentList.get(2), (Returnable<String>) argumentList.get(3), position);
}
@Override
public int argNumber() {
return 4;
}
@Override
public Returnable.ReturnType getArgument(int position) {
return switch(position) {
case 0, 1, 2 -> Returnable.ReturnType.NUMBER;
case 3 -> Returnable.ReturnType.STRING;
default -> null;
};
}
}
@@ -0,0 +1,55 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.terrascript.script.builders;
import java.util.List;
import java.util.stream.Collectors;
import com.dfsek.terra.addons.terrascript.parser.exceptions.ParseException;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
import com.dfsek.terra.addons.terrascript.parser.lang.functions.FunctionBuilder;
import com.dfsek.terra.addons.terrascript.script.functions.StructureFunction;
import com.dfsek.terra.addons.terrascript.tokenizer.Position;
import com.dfsek.terra.api.Platform;
import com.dfsek.terra.api.registry.Registry;
import com.dfsek.terra.api.structure.Structure;
public class StructureFunctionBuilder implements FunctionBuilder<StructureFunction> {
private final Registry<Structure> registry;
private final Platform platform;
public StructureFunctionBuilder(Registry<Structure> registry, Platform platform) {
this.registry = registry;
this.platform = platform;
}
@SuppressWarnings("unchecked")
@Override
public StructureFunction build(List<Returnable<?>> argumentList, Position position) {
if(argumentList.size() < 5) throw new ParseException("Expected rotations", position);
return new StructureFunction((Returnable<Number>) argumentList.remove(0), (Returnable<Number>) argumentList.remove(0),
(Returnable<Number>) argumentList.remove(0), (Returnable<String>) argumentList.remove(0),
argumentList.stream().map(item -> ((Returnable<String>) item)).collect(Collectors.toList()), registry,
position, platform);
}
@Override
public int argNumber() {
return -1;
}
@Override
public Returnable.ReturnType getArgument(int position) {
return switch(position) {
case 0, 1, 2 -> Returnable.ReturnType.NUMBER;
default -> Returnable.ReturnType.STRING;
};
}
}
@@ -0,0 +1,63 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.terrascript.script.builders;
import java.util.List;
import java.util.function.BiConsumer;
import com.dfsek.terra.addons.terrascript.parser.lang.ImplementationArguments;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
import com.dfsek.terra.addons.terrascript.parser.lang.Scope;
import com.dfsek.terra.addons.terrascript.parser.lang.functions.Function;
import com.dfsek.terra.addons.terrascript.parser.lang.functions.FunctionBuilder;
import com.dfsek.terra.addons.terrascript.script.TerraImplementationArguments;
import com.dfsek.terra.addons.terrascript.tokenizer.Position;
public class UnaryBooleanFunctionBuilder implements FunctionBuilder<Function<Void>> {
private final BiConsumer<Boolean, TerraImplementationArguments> function;
public UnaryBooleanFunctionBuilder(BiConsumer<Boolean, TerraImplementationArguments> function) {
this.function = function;
}
@Override
public Function<Void> build(List<Returnable<?>> argumentList, Position position) {
return new Function<>() {
@Override
public ReturnType returnType() {
return ReturnType.VOID;
}
@SuppressWarnings("unchecked")
@Override
public Void apply(ImplementationArguments implementationArguments, Scope scope) {
function.accept(((Returnable<Boolean>) argumentList.get(0)).apply(implementationArguments, scope),
(TerraImplementationArguments) implementationArguments);
return null;
}
@Override
public Position getPosition() {
return position;
}
};
}
@Override
public int argNumber() {
return 1;
}
@Override
public Returnable.ReturnType getArgument(int position) {
if(position == 0) return Returnable.ReturnType.BOOLEAN;
return null;
}
}
@@ -0,0 +1,59 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.terrascript.script.builders;
import java.util.List;
import com.dfsek.terra.addons.terrascript.parser.lang.ImplementationArguments;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
import com.dfsek.terra.addons.terrascript.parser.lang.Scope;
import com.dfsek.terra.addons.terrascript.parser.lang.functions.Function;
import com.dfsek.terra.addons.terrascript.parser.lang.functions.FunctionBuilder;
import com.dfsek.terra.addons.terrascript.tokenizer.Position;
public class UnaryNumberFunctionBuilder implements FunctionBuilder<Function<Number>> {
private final java.util.function.Function<Number, Number> function;
public UnaryNumberFunctionBuilder(java.util.function.Function<Number, Number> function) {
this.function = function;
}
@Override
public Function<Number> build(List<Returnable<?>> argumentList, Position position) {
return new Function<>() {
@Override
public ReturnType returnType() {
return ReturnType.NUMBER;
}
@SuppressWarnings("unchecked")
@Override
public Number apply(ImplementationArguments implementationArguments, Scope scope) {
return function.apply(((Returnable<Number>) argumentList.get(0)).apply(implementationArguments, scope));
}
@Override
public Position getPosition() {
return position;
}
};
}
@Override
public int argNumber() {
return 1;
}
@Override
public Returnable.ReturnType getArgument(int position) {
if(position == 0) return Returnable.ReturnType.NUMBER;
return null;
}
}
@@ -0,0 +1,60 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.terrascript.script.builders;
import java.util.List;
import com.dfsek.terra.addons.terrascript.parser.lang.ImplementationArguments;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
import com.dfsek.terra.addons.terrascript.parser.lang.Scope;
import com.dfsek.terra.addons.terrascript.parser.lang.functions.Function;
import com.dfsek.terra.addons.terrascript.parser.lang.functions.FunctionBuilder;
import com.dfsek.terra.addons.terrascript.tokenizer.Position;
public class UnaryStringFunctionBuilder implements FunctionBuilder<Function<Void>> {
private final java.util.function.Consumer<String> function;
public UnaryStringFunctionBuilder(java.util.function.Consumer<String> function) {
this.function = function;
}
@Override
public Function<Void> build(List<Returnable<?>> argumentList, Position position) {
return new Function<>() {
@Override
public ReturnType returnType() {
return ReturnType.VOID;
}
@SuppressWarnings("unchecked")
@Override
public Void apply(ImplementationArguments implementationArguments, Scope scope) {
function.accept(((Returnable<String>) argumentList.get(0)).apply(implementationArguments, scope));
return null;
}
@Override
public Position getPosition() {
return position;
}
};
}
@Override
public int argNumber() {
return 1;
}
@Override
public Returnable.ReturnType getArgument(int position) {
if(position == 0) return Returnable.ReturnType.STRING;
return null;
}
}
@@ -0,0 +1,60 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.terrascript.script.builders;
import java.util.List;
import com.dfsek.terra.addons.terrascript.parser.lang.ImplementationArguments;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
import com.dfsek.terra.addons.terrascript.parser.lang.Scope;
import com.dfsek.terra.addons.terrascript.parser.lang.functions.Function;
import com.dfsek.terra.addons.terrascript.parser.lang.functions.FunctionBuilder;
import com.dfsek.terra.addons.terrascript.script.TerraImplementationArguments;
import com.dfsek.terra.addons.terrascript.tokenizer.Position;
public class ZeroArgFunctionBuilder<T> implements FunctionBuilder<Function<T>> {
private final java.util.function.Function<TerraImplementationArguments, T> function;
private final Returnable.ReturnType type;
public ZeroArgFunctionBuilder(java.util.function.Function<TerraImplementationArguments, T> function, Returnable.ReturnType type) {
this.function = function;
this.type = type;
}
@Override
public Function<T> build(List<Returnable<?>> argumentList, Position position) {
return new Function<>() {
@Override
public ReturnType returnType() {
return type;
}
@Override
public T apply(ImplementationArguments implementationArguments, Scope scope) {
return function.apply((TerraImplementationArguments) implementationArguments);
}
@Override
public Position getPosition() {
return position;
}
};
}
@Override
public int argNumber() {
return 0;
}
@Override
public Returnable.ReturnType getArgument(int position) {
if(position == 0) return type;
return null;
}
}
@@ -0,0 +1,65 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.terrascript.script.functions;
import net.jafama.FastMath;
import com.dfsek.terra.addons.terrascript.parser.lang.ImplementationArguments;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
import com.dfsek.terra.addons.terrascript.parser.lang.Scope;
import com.dfsek.terra.addons.terrascript.parser.lang.functions.Function;
import com.dfsek.terra.addons.terrascript.script.TerraImplementationArguments;
import com.dfsek.terra.addons.terrascript.tokenizer.Position;
import com.dfsek.terra.api.util.RotationUtil;
import com.dfsek.terra.api.util.vector.Vector2;
import com.dfsek.terra.api.util.vector.Vector3;
import com.dfsek.terra.api.world.biome.generation.BiomeProvider;
public class BiomeFunction implements Function<String> {
private final Returnable<Number> x, y, z;
private final Position position;
public BiomeFunction(Returnable<Number> x, Returnable<Number> y, Returnable<Number> z, Position position) {
this.x = x;
this.y = y;
this.z = z;
this.position = position;
}
@Override
public String apply(ImplementationArguments implementationArguments, Scope scope) {
TerraImplementationArguments arguments = (TerraImplementationArguments) implementationArguments;
Vector2 xz = RotationUtil.rotateVector(Vector2.of(x.apply(implementationArguments, scope).doubleValue(),
z.apply(implementationArguments, scope).doubleValue()),
arguments.getRotation());
BiomeProvider grid = arguments.getWorld().getBiomeProvider();
return grid.getBiome(arguments.getOrigin()
.toVector3()
.mutable()
.add(Vector3.of(FastMath.roundToInt(xz.getX()),
y.apply(implementationArguments, scope).intValue(),
FastMath.roundToInt(xz.getZ()))).immutable(), arguments.getWorld().getSeed()).getID();
}
@Override
public Position getPosition() {
return position;
}
@Override
public ReturnType returnType() {
return ReturnType.STRING;
}
}
@@ -0,0 +1,107 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.terrascript.script.functions;
import net.jafama.FastMath;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.Map;
import com.dfsek.terra.addons.terrascript.parser.lang.ImplementationArguments;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
import com.dfsek.terra.addons.terrascript.parser.lang.Scope;
import com.dfsek.terra.addons.terrascript.parser.lang.constants.StringConstant;
import com.dfsek.terra.addons.terrascript.parser.lang.functions.Function;
import com.dfsek.terra.addons.terrascript.script.TerraImplementationArguments;
import com.dfsek.terra.addons.terrascript.tokenizer.Position;
import com.dfsek.terra.api.Platform;
import com.dfsek.terra.api.block.state.BlockState;
import com.dfsek.terra.api.util.RotationUtil;
import com.dfsek.terra.api.util.vector.Vector2;
import com.dfsek.terra.api.util.vector.Vector3;
public class BlockFunction implements Function<Void> {
private static final Logger logger = LoggerFactory.getLogger(BlockFunction.class);
protected final Returnable<Number> x, y, z;
protected final Returnable<String> blockData;
protected final Platform platform;
private final Map<String, BlockState> data = new HashMap<>();
private final Returnable<Boolean> overwrite;
private final Returnable<Boolean> physics;
private final Position position;
public BlockFunction(Returnable<Number> x, Returnable<Number> y, Returnable<Number> z, Returnable<String> blockData,
Returnable<Boolean> overwrite, Returnable<Boolean> physics, Platform platform, Position position) {
this.x = x;
this.y = y;
this.z = z;
this.blockData = blockData;
this.overwrite = overwrite;
this.platform = platform;
this.position = position;
this.physics = physics;
}
@Override
public Void apply(ImplementationArguments implementationArguments, Scope scope) {
TerraImplementationArguments arguments = (TerraImplementationArguments) implementationArguments;
BlockState rot = getBlockState(implementationArguments, scope);
setBlock(implementationArguments, scope, arguments, rot);
return null;
}
@Override
public Position getPosition() {
return position;
}
@Override
public ReturnType returnType() {
return ReturnType.VOID;
}
void setBlock(ImplementationArguments implementationArguments, Scope scope,
TerraImplementationArguments arguments, BlockState rot) {
Vector2 xz = RotationUtil.rotateVector(Vector2.of(x.apply(implementationArguments, scope).doubleValue(),
z.apply(implementationArguments, scope).doubleValue()), arguments.getRotation());
try {
Vector3.Mutable set = Vector3.of(FastMath.roundToInt(xz.getX()),
y.apply(implementationArguments, scope).doubleValue(),
FastMath.roundToInt(xz.getZ())).mutable().add(arguments.getOrigin());
BlockState current = arguments.getWorld().getBlockState(set);
if(overwrite.apply(implementationArguments, scope) || current.isAir()) {
arguments.getWorld().setBlockState(set, rot, physics.apply(implementationArguments, scope));
}
} catch(RuntimeException e) {
logger.error("Failed to place block at location {}", arguments.getOrigin(), e);
}
}
protected BlockState getBlockState(ImplementationArguments arguments, Scope scope) {
return data.computeIfAbsent(blockData.apply(arguments, scope), platform.getWorldHandle()::createBlockState);
}
public static class Constant extends BlockFunction {
private final BlockState state;
public Constant(Returnable<Number> x, Returnable<Number> y, Returnable<Number> z, StringConstant blockData,
Returnable<Boolean> overwrite, Returnable<Boolean> physics, Platform platform, Position position) {
super(x, y, z, blockData, overwrite, physics, platform, position);
this.state = platform.getWorldHandle().createBlockState(blockData.getConstant());
}
@Override
protected BlockState getBlockState(ImplementationArguments arguments, Scope scope) {
return state;
}
}
}
@@ -0,0 +1,65 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.terrascript.script.functions;
import net.jafama.FastMath;
import com.dfsek.terra.addons.terrascript.parser.lang.ImplementationArguments;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
import com.dfsek.terra.addons.terrascript.parser.lang.Scope;
import com.dfsek.terra.addons.terrascript.parser.lang.functions.Function;
import com.dfsek.terra.addons.terrascript.script.TerraImplementationArguments;
import com.dfsek.terra.addons.terrascript.tokenizer.Position;
import com.dfsek.terra.api.util.RotationUtil;
import com.dfsek.terra.api.util.vector.Vector2;
import com.dfsek.terra.api.util.vector.Vector3;
public class CheckBlockFunction implements Function<String> {
private final Returnable<Number> x, y, z;
private final Position position;
public CheckBlockFunction(Returnable<Number> x, Returnable<Number> y, Returnable<Number> z, Position position) {
this.x = x;
this.y = y;
this.z = z;
this.position = position;
}
@Override
public String apply(ImplementationArguments implementationArguments, Scope scope) {
TerraImplementationArguments arguments = (TerraImplementationArguments) implementationArguments;
Vector2 xz = RotationUtil.rotateVector(Vector2.of(x.apply(implementationArguments, scope).doubleValue(),
z.apply(implementationArguments, scope).doubleValue()),
arguments.getRotation());
String data = arguments.getWorld()
.getBlockState(arguments.getOrigin()
.toVector3()
.mutable()
.add(Vector3.of(FastMath.roundToInt(xz.getX()),
y.apply(implementationArguments, scope)
.doubleValue(), FastMath.roundToInt(xz.getZ()))))
.getAsString();
if(data.contains("[")) return data.substring(0, data.indexOf('[')); // Strip properties
else return data;
}
@Override
public Position getPosition() {
return position;
}
@Override
public ReturnType returnType() {
return ReturnType.STRING;
}
}
@@ -0,0 +1,70 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.terrascript.script.functions;
import com.dfsek.terra.addons.terrascript.parser.exceptions.ParseException;
import com.dfsek.terra.addons.terrascript.parser.lang.ImplementationArguments;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
import com.dfsek.terra.addons.terrascript.parser.lang.Scope;
import com.dfsek.terra.addons.terrascript.parser.lang.constants.ConstantExpression;
import com.dfsek.terra.addons.terrascript.parser.lang.functions.Function;
import com.dfsek.terra.addons.terrascript.script.TerraImplementationArguments;
import com.dfsek.terra.addons.terrascript.tokenizer.Position;
import com.dfsek.terra.api.Platform;
import com.dfsek.terra.api.entity.Entity;
import com.dfsek.terra.api.entity.EntityType;
import com.dfsek.terra.api.event.events.world.generation.EntitySpawnEvent;
import com.dfsek.terra.api.util.RotationUtil;
import com.dfsek.terra.api.util.vector.Vector2;
import com.dfsek.terra.api.util.vector.Vector3;
public class EntityFunction implements Function<Void> {
private final EntityType data;
private final Returnable<Number> x, y, z;
private final Position position;
private final Platform platform;
public EntityFunction(Returnable<Number> x, Returnable<Number> y, Returnable<Number> z, Returnable<String> data, Platform platform,
Position position) {
this.position = position;
this.platform = platform;
if(!(data instanceof ConstantExpression)) throw new ParseException("Entity data must be constant", data.getPosition());
this.data = platform.getWorldHandle().getEntity(((ConstantExpression<String>) data).getConstant());
this.x = x;
this.y = y;
this.z = z;
}
@Override
public Void apply(ImplementationArguments implementationArguments, Scope scope) {
TerraImplementationArguments arguments = (TerraImplementationArguments) implementationArguments;
Vector2 xz = RotationUtil.rotateVector(Vector2.of(x.apply(implementationArguments, scope).doubleValue(),
z.apply(implementationArguments, scope).doubleValue()), arguments.getRotation());
Entity entity = arguments.getWorld().spawnEntity(Vector3.of(xz.getX(), y.apply(implementationArguments, scope).doubleValue(),
xz.getZ())
.mutable()
.add(arguments.getOrigin())
.add(0.5, 0, 0.5)
.immutable(), data);
platform.getEventManager().callEvent(new EntitySpawnEvent(entity.world().getPack(), entity));
return null;
}
@Override
public Position getPosition() {
return position;
}
@Override
public ReturnType returnType() {
return ReturnType.VOID;
}
}
Loaded 100 of 156 files, more files were not shown because too many files have changed in this diff. Show more