mirror of
https://github.com/PolyhedralDev/Terra.git
synced 2026-10-26 14:00:14 +00:00
Move terrascript 2 to separate module
This commit is contained in:
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
-158
@@ -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 {
|
||||
}
|
||||
}
|
||||
}
|
||||
-20
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-4
@@ -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();
|
||||
|
||||
-97
@@ -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 {
|
||||
}
|
||||
}
|
||||
-141
@@ -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();
|
||||
}
|
||||
}
|
||||
-86
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
-8
@@ -1,8 +0,0 @@
|
||||
package com.dfsek.terra.addons.terrascript.codegen;
|
||||
|
||||
|
||||
public interface TerraScript {
|
||||
|
||||
void execute();
|
||||
|
||||
}
|
||||
-19
@@ -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);
|
||||
}
|
||||
}
|
||||
-17
@@ -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;
|
||||
}
|
||||
-565
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
-33
@@ -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;
|
||||
}
|
||||
}
|
||||
-5
@@ -1,5 +0,0 @@
|
||||
package com.dfsek.terra.addons.terrascript.exception;
|
||||
|
||||
public class CompilerBugException extends RuntimeException {
|
||||
// TODO - Add message constructor
|
||||
}
|
||||
-11
@@ -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);
|
||||
}
|
||||
}
|
||||
-11
@@ -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);
|
||||
}
|
||||
}
|
||||
-11
@@ -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);
|
||||
}
|
||||
}
|
||||
-11
@@ -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);
|
||||
}
|
||||
}
|
||||
-256
@@ -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);
|
||||
}
|
||||
}
|
||||
-84
@@ -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);
|
||||
}
|
||||
}
|
||||
-32
@@ -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);
|
||||
}
|
||||
}
|
||||
-207
@@ -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
|
||||
}
|
||||
}
|
||||
-25
@@ -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; }
|
||||
}
|
||||
+412
-323
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+115
@@ -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);
|
||||
}
|
||||
}
|
||||
-13
@@ -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; }
|
||||
}
|
||||
+7
-7
@@ -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;
|
||||
}
|
||||
}
|
||||
+76
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+19
@@ -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;
|
||||
}
|
||||
}
|
||||
+14
@@ -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 {
|
||||
}
|
||||
+25
@@ -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();
|
||||
}
|
||||
+11
@@ -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> {
|
||||
}
|
||||
+30
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+138
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -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> {
|
||||
}
|
||||
+32
@@ -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;
|
||||
}
|
||||
}
|
||||
+38
@@ -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;
|
||||
}
|
||||
}
|
||||
+33
@@ -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;
|
||||
}
|
||||
}
|
||||
+23
@@ -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;
|
||||
}
|
||||
}
|
||||
+43
@@ -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);
|
||||
}
|
||||
}
|
||||
+22
@@ -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);
|
||||
}
|
||||
+38
@@ -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;
|
||||
}
|
||||
}
|
||||
+38
@@ -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;
|
||||
}
|
||||
}
|
||||
+38
@@ -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;
|
||||
}
|
||||
}
|
||||
+38
@@ -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;
|
||||
}
|
||||
}
|
||||
+55
@@ -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;
|
||||
}
|
||||
}
|
||||
+62
@@ -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;
|
||||
}
|
||||
}
|
||||
+48
@@ -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;
|
||||
}
|
||||
}
|
||||
+29
@@ -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;
|
||||
}
|
||||
}
|
||||
+35
@@ -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);
|
||||
}
|
||||
}
|
||||
+35
@@ -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;
|
||||
}
|
||||
}
|
||||
+35
@@ -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;
|
||||
}
|
||||
}
|
||||
+41
@@ -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));
|
||||
}
|
||||
}
|
||||
+35
@@ -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);
|
||||
}
|
||||
}
|
||||
+35
@@ -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;
|
||||
}
|
||||
}
|
||||
+35
@@ -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;
|
||||
}
|
||||
}
|
||||
+35
@@ -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);
|
||||
}
|
||||
}
|
||||
+35
@@ -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;
|
||||
}
|
||||
}
|
||||
+35
@@ -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;
|
||||
}
|
||||
}
|
||||
+27
@@ -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;
|
||||
}
|
||||
}
|
||||
+48
@@ -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);
|
||||
}
|
||||
}
|
||||
+36
@@ -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);
|
||||
}
|
||||
}
|
||||
+37
@@ -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;
|
||||
}
|
||||
}
|
||||
+37
@@ -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;
|
||||
}
|
||||
}
|
||||
+37
@@ -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;
|
||||
}
|
||||
}
|
||||
+46
@@ -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;
|
||||
}
|
||||
}
|
||||
+42
@@ -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;
|
||||
}
|
||||
}
|
||||
+42
@@ -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;
|
||||
}
|
||||
}
|
||||
+42
@@ -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;
|
||||
}
|
||||
}
|
||||
+22
@@ -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();
|
||||
}
|
||||
+25
@@ -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;
|
||||
}
|
||||
}
|
||||
+25
@@ -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;
|
||||
}
|
||||
}
|
||||
+21
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+31
@@ -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;
|
||||
}
|
||||
}
|
||||
+22
@@ -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);
|
||||
}
|
||||
}
|
||||
+22
@@ -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);
|
||||
}
|
||||
}
|
||||
+17
@@ -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);
|
||||
}
|
||||
}
|
||||
+34
@@ -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;
|
||||
}
|
||||
}
|
||||
+160
@@ -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;
|
||||
}
|
||||
}
|
||||
+73
@@ -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);
|
||||
}
|
||||
}
|
||||
+61
@@ -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;
|
||||
}
|
||||
}
|
||||
+45
@@ -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;
|
||||
};
|
||||
}
|
||||
}
|
||||
+61
@@ -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;
|
||||
};
|
||||
}
|
||||
}
|
||||
+38
@@ -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;
|
||||
};
|
||||
}
|
||||
}
|
||||
+46
@@ -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;
|
||||
};
|
||||
}
|
||||
}
|
||||
+42
@@ -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;
|
||||
};
|
||||
}
|
||||
}
|
||||
+54
@@ -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;
|
||||
};
|
||||
}
|
||||
}
|
||||
+46
@@ -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;
|
||||
};
|
||||
}
|
||||
}
|
||||
+35
@@ -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;
|
||||
}
|
||||
}
|
||||
+33
@@ -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;
|
||||
}
|
||||
}
|
||||
+43
@@ -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;
|
||||
};
|
||||
}
|
||||
}
|
||||
+48
@@ -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;
|
||||
};
|
||||
}
|
||||
}
|
||||
+55
@@ -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;
|
||||
};
|
||||
}
|
||||
}
|
||||
+63
@@ -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;
|
||||
}
|
||||
}
|
||||
+59
@@ -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;
|
||||
}
|
||||
}
|
||||
+60
@@ -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;
|
||||
}
|
||||
}
|
||||
+60
@@ -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;
|
||||
}
|
||||
}
|
||||
+65
@@ -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;
|
||||
}
|
||||
}
|
||||
+107
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+65
@@ -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;
|
||||
}
|
||||
}
|
||||
+70
@@ -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
Reference in new issue
Block a user