use local variable table for terrascript

This commit is contained in:
dfsek 2022-06-14 21:30:58 -07:00
parent 613b96288a
commit b2cc0d48aa
17 changed files with 410 additions and 262 deletions

View File

@ -15,9 +15,12 @@ import java.util.Map;
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;
@ -48,12 +51,17 @@ import com.dfsek.terra.addons.terrascript.parser.lang.operations.statements.Grea
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.VariableAssignmentNode;
import com.dfsek.terra.addons.terrascript.parser.lang.variables.VariableDeclarationNode;
import com.dfsek.terra.addons.terrascript.parser.lang.variables.VariableReferenceNode;
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;
@SuppressWarnings("unchecked")
@ -83,11 +91,12 @@ public class Parser {
*
* @throws ParseException If parsing fails.
*/
public Block parse() {
return parseBlock(new Tokenizer(data), new HashMap<>(), false);
public Executable parse() {
ScopeBuilder scopeBuilder = new ScopeBuilder();
return new Executable(parseBlock(new Tokenizer(data), false, scopeBuilder), scopeBuilder);
}
private Keyword<?> parseLoopLike(Tokenizer tokens, Map<String, Returnable.ReturnType> variableMap, boolean loop) throws ParseException {
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);
@ -95,43 +104,43 @@ public class Parser {
ParserUtil.checkType(tokens.consume(), Token.Type.GROUP_BEGIN);
return switch(identifier.getType()) {
case FOR_LOOP -> parseForLoop(tokens, variableMap, identifier.getPosition());
case IF_STATEMENT -> parseIfStatement(tokens, variableMap, identifier.getPosition(), loop);
case WHILE_LOOP -> parseWhileLoop(tokens, variableMap, identifier.getPosition());
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 WhileKeyword parseWhileLoop(Tokenizer tokens, Map<String, Returnable.ReturnType> variableMap, Position start) {
Returnable<?> first = parseExpression(tokens, true, variableMap);
private WhileKeyword parseWhileLoop(Tokenizer tokens, Position start, ScopeBuilder scopeBuilder) {
Returnable<?> first = parseExpression(tokens, true, scopeBuilder);
ParserUtil.checkReturnType(first, Returnable.ReturnType.BOOLEAN);
ParserUtil.checkType(tokens.consume(), Token.Type.GROUP_END);
return new WhileKeyword(parseStatementBlock(tokens, variableMap, true), (Returnable<Boolean>) first, start); // While loop
return new WhileKeyword(parseStatementBlock(tokens, true, scopeBuilder), (Returnable<Boolean>) first, start); // While loop
}
private IfKeyword parseIfStatement(Tokenizer tokens, Map<String, Returnable.ReturnType> variableMap, Position start, boolean loop) {
Returnable<?> condition = parseExpression(tokens, true, variableMap);
private IfKeyword parseIfStatement(Tokenizer tokens, Position start, boolean loop, ScopeBuilder scopeBuilder) {
Returnable<?> condition = parseExpression(tokens, true, scopeBuilder);
ParserUtil.checkReturnType(condition, Returnable.ReturnType.BOOLEAN);
ParserUtil.checkType(tokens.consume(), Token.Type.GROUP_END);
Block elseBlock = null;
Block statement = parseStatementBlock(tokens, variableMap, loop);
Block statement = parseStatementBlock(tokens, loop, scopeBuilder);
List<IfKeyword.Pair<Returnable<Boolean>, Block>> elseIf = new ArrayList<>();
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, variableMap);
Returnable<?> elseCondition = parseExpression(tokens, true, scopeBuilder);
ParserUtil.checkReturnType(elseCondition, Returnable.ReturnType.BOOLEAN);
elseIf.add(new IfKeyword.Pair<>((Returnable<Boolean>) elseCondition, parseStatementBlock(tokens, variableMap, loop)));
elseIf.add(Pair.of((Returnable<Boolean>) elseCondition, parseStatementBlock(tokens, loop, scopeBuilder)));
} else {
elseBlock = parseStatementBlock(tokens, variableMap, loop);
elseBlock = parseStatementBlock(tokens, loop, scopeBuilder);
break; // Else must be last.
}
}
@ -139,51 +148,51 @@ public class Parser {
return new IfKeyword(statement, (Returnable<Boolean>) condition, elseIf, elseBlock, start); // If statement
}
private Block parseStatementBlock(Tokenizer tokens, Map<String, Returnable.ReturnType> variableMap, boolean loop) {
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, variableMap, loop);
Block block = parseBlock(tokens, loop, scopeBuilder);
ParserUtil.checkType(tokens.consume(), Token.Type.BLOCK_END);
return block;
} else {
Position position = tokens.get().getPosition();
Block block = new Block(Collections.singletonList(parseItem(tokens, variableMap, loop)), position);
Block block = new Block(Collections.singletonList(parseItem(tokens, loop, scopeBuilder)), position);
ParserUtil.checkType(tokens.consume(), Token.Type.STATEMENT_END);
return block;
}
}
private ForKeyword parseForLoop(Tokenizer tokens, Map<String, Returnable.ReturnType> old, Position start) {
Map<String, Returnable.ReturnType> variableMap = new HashMap<>(old); // New scope
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()) {
VariableDeclarationNode<?> forVar = parseVariableDeclaration(tokens, variableMap);
VariableAssignmentNode<?> forVar = parseVariableDeclaration(tokens, scopeBuilder);
Token name = tokens.get();
if(functions.containsKey(name.getContent()) || variableMap.containsKey(name.getContent()))
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, variableMap);
} else initializer = parseExpression(tokens, true, scopeBuilder);
ParserUtil.checkType(tokens.consume(), Token.Type.STATEMENT_END);
Returnable<?> conditional = parseExpression(tokens, true, variableMap);
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(variableMap.containsKey(token.getContent())) { // Assume variable assignment
incrementer = parseAssignment(tokens, variableMap);
} else incrementer = parseFunction(tokens, true, variableMap);
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, variableMap, true), initializer, (Returnable<Boolean>) conditional, incrementer,
return new ForKeyword(parseStatementBlock(tokens, true, scopeBuilder), initializer, (Returnable<Boolean>) conditional, incrementer,
start);
}
private Returnable<?> parseExpression(Tokenizer tokens, boolean full, Map<String, Returnable.ReturnType> variableMap) {
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)) {
@ -202,13 +211,21 @@ public class Parser {
if(id.isConstant()) {
expression = parseConstantExpression(tokens);
} else if(id.getType().equals(Token.Type.GROUP_BEGIN)) { // Parse grouped expression
expression = parseGroup(tokens, variableMap);
expression = parseGroup(tokens, scopeBuilder);
} else {
if(functions.containsKey(id.getContent()))
expression = parseFunction(tokens, false, variableMap);
else if(variableMap.containsKey(id.getContent())) {
expression = parseFunction(tokens, false, scopeBuilder);
else if(scopeBuilder.contains(id.getContent())) {
ParserUtil.checkType(tokens.consume(), Token.Type.IDENTIFIER);
expression = new VariableReferenceNode(id.getContent(), id.getPosition(), variableMap.get(id.getContent()));
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());
}
@ -221,7 +238,7 @@ public class Parser {
}
if(full && tokens.get().isBinaryOperator()) { // Parse binary operations
return parseBinaryOperation(expression, tokens, variableMap);
return parseBinaryOperation(expression, tokens, scopeBuilder);
}
return expression;
}
@ -243,25 +260,25 @@ public class Parser {
}
}
private Returnable<?> parseGroup(Tokenizer tokens, Map<String, Returnable.ReturnType> variableMap) {
private Returnable<?> parseGroup(Tokenizer tokens, ScopeBuilder scopeBuilder) {
ParserUtil.checkType(tokens.consume(), Token.Type.GROUP_BEGIN);
Returnable<?> expression = parseExpression(tokens, true, variableMap); // Parse inside of group as a separate expression
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 BinaryOperation<?, ?> parseBinaryOperation(Returnable<?> left, Tokenizer tokens,
Map<String, Returnable.ReturnType> variableMap) {
ScopeBuilder scopeBuilder) {
Token binaryOperator = tokens.consume();
ParserUtil.checkBinaryOperator(binaryOperator);
Returnable<?> right = parseExpression(tokens, false, variableMap);
Returnable<?> right = parseExpression(tokens, false, scopeBuilder);
Token other = tokens.get();
if(ParserUtil.hasPrecedence(binaryOperator.getType(), other.getType())) {
return assemble(left, parseBinaryOperation(right, tokens, variableMap), binaryOperator);
return assemble(left, parseBinaryOperation(right, tokens, scopeBuilder), binaryOperator);
} else if(other.isBinaryOperator()) {
return parseBinaryOperation(assemble(left, right, binaryOperator), tokens, variableMap);
return parseBinaryOperation(assemble(left, right, binaryOperator), tokens, scopeBuilder);
}
return assemble(left, right, binaryOperator);
}
@ -306,7 +323,7 @@ public class Parser {
}
}
private VariableDeclarationNode<?> parseVariableDeclaration(Tokenizer tokens, Map<String, Returnable.ReturnType> variableMap) {
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);
@ -315,30 +332,34 @@ public class Parser {
ParserUtil.checkVarType(type, returnType); // Check for type mismatch
Token identifier = tokens.consume();
ParserUtil.checkType(identifier, Token.Type.IDENTIFIER);
if(functions.containsKey(identifier.getContent()) || variableMap.containsKey(identifier.getContent()))
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, variableMap);
Returnable<?> value = parseExpression(tokens, true, scopeBuilder);
ParserUtil.checkReturnType(value, returnType);
variableMap.put(identifier.getContent(), returnType);
return new VariableDeclarationNode<>(tokens.get().getPosition(), identifier.getContent(), value, returnType);
String id = identifier.getContent();
return switch(type.getType()) {
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 assignment: " + type, value.getPosition());
};
}
private Block parseBlock(Tokenizer tokens, Map<String, Returnable.ReturnType> superVars, boolean loop) {
private Block parseBlock(Tokenizer tokens, boolean loop, ScopeBuilder scopeBuilder) {
List<Item<?>> parsedItems = new ArrayList<>();
Map<String, Returnable.ReturnType> parsedVariables = new HashMap<>(
superVars); // New hashmap as to not mutate parent scope's declarations.
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, parsedVariables, loop);
Item<?> parsedItem = parseItem(tokens, loop, scopeBuilder);
if(parsedItem != Function.NULL) {
parsedItems.add(parsedItem);
}
@ -347,7 +368,7 @@ public class Parser {
return new Block(parsedItems, first.getPosition());
}
private Item<?> parseItem(Tokenizer tokens, Map<String, Returnable.ReturnType> variableMap, boolean loop) {
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,
@ -357,14 +378,14 @@ public class Parser {
Token.Type.FAIL);
if(token.isLoopLike()) { // Parse loop-like tokens (if, while, etc)
return parseLoopLike(tokens, variableMap, loop);
return parseLoopLike(tokens, loop, scopeBuilder);
} else if(token.isIdentifier()) { // Parse identifiers
if(variableMap.containsKey(token.getContent())) { // Assume variable assignment
return parseAssignment(tokens, variableMap);
} else return parseFunction(tokens, true, variableMap);
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, variableMap);
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());
@ -373,21 +394,30 @@ public class Parser {
else throw new UnsupportedOperationException("Unexpected token " + token.getType() + ": " + token.getPosition());
}
private VariableAssignmentNode<?> parseAssignment(Tokenizer tokens, Map<String, Returnable.ReturnType> variableMap) {
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, variableMap);
Returnable<?> value = parseExpression(tokens, true, scopeBuilder);
ParserUtil.checkReturnType(value, variableMap.get(identifier.getContent()));
String id = identifier.getContent();
return new VariableAssignmentNode<>(value, identifier.getContent(), identifier.getPosition());
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, Map<String, Returnable.ReturnType> variableMap) {
private Function<?> parseFunction(Tokenizer tokens, boolean fullStatement, ScopeBuilder scopeBuilder) {
Token identifier = tokens.consume();
ParserUtil.checkType(identifier, Token.Type.IDENTIFIER); // First token must be identifier
@ -397,7 +427,7 @@ public class Parser {
ParserUtil.checkType(tokens.consume(), Token.Type.GROUP_BEGIN); // Second is body begin
List<Returnable<?>> args = getArgs(tokens, variableMap); // Extract arguments, consume the rest.
List<Returnable<?>> args = getArgs(tokens, scopeBuilder); // Extract arguments, consume the rest.
ParserUtil.checkType(tokens.consume(), Token.Type.GROUP_END); // Remove body end
@ -425,11 +455,11 @@ public class Parser {
throw new UnsupportedOperationException("Unsupported function: " + identifier.getContent());
}
private List<Returnable<?>> getArgs(Tokenizer tokens, Map<String, Returnable.ReturnType> variableMap) {
private List<Returnable<?>> getArgs(Tokenizer tokens, ScopeBuilder scopeBuilder) {
List<Returnable<?>> args = new ArrayList<>();
while(!tokens.get().getType().equals(Token.Type.GROUP_END)) {
args.add(parseExpression(tokens, true, variableMap));
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();
}

View File

@ -21,23 +21,8 @@ public class Block implements Item<Block.ReturnInfo<?>> {
this.position = position;
}
public ReturnInfo<?> apply(ImplementationArguments implementationArguments) {
return apply(implementationArguments, new Scope());
}
@Override
public ReturnInfo<?> apply(ImplementationArguments implementationArguments, Scope scope) {
Scope sub = scope.sub();
for(Item<?> item : items) {
Object result = item.apply(implementationArguments, sub);
if(result instanceof ReturnInfo<?> level) {
if(!level.getLevel().equals(ReturnLevel.NONE)) return level;
}
}
return new ReturnInfo<>(ReturnLevel.NONE, null);
}
public ReturnInfo<?> applyNoNewScope(ImplementationArguments implementationArguments, Scope scope) {
for(Item<?> item : items) {
Object result = item.apply(implementationArguments, scope);
if(result instanceof ReturnInfo<?> level) {
@ -52,10 +37,6 @@ public class Block implements Item<Block.ReturnInfo<?>> {
return position;
}
public List<Item<?>> getItems() {
return items;
}
public enum ReturnLevel {
NONE(false),
BREAK(false),

View File

@ -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;
}
}

View File

@ -1,46 +1,124 @@
package com.dfsek.terra.addons.terrascript.parser.lang;
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable.ReturnType;
import com.dfsek.terra.addons.terrascript.parser.lang.variables.reference.VariableReferenceNode;
import com.dfsek.terra.api.util.generic.pair.Pair;
import net.jafama.FastMath;
import java.util.HashMap;
import java.util.Map;
import com.dfsek.terra.addons.terrascript.parser.lang.variables.Variable;
public class Scope {
private static final Scope NULL = new Scope() {
@Override
public Variable<?> get(String id) {
throw new IllegalStateException("Cannot get variable from null scope: " + id);
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 int numSize, boolSize, strSize = 0;
private final Map<String, Pair<Integer, ReturnType>> indices;
private ScopeBuilder parent;
public ScopeBuilder() {
this.indices = new HashMap<>();
}
@Override
public void put(String id, Variable<?> variable) {
throw new IllegalStateException("Cannot set variable in null scope: " + id);
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++;
if(parent != null) {
parent.numSize = FastMath.max(parent.numSize, numSize);
}
return num;
}
public int str(String id) {
int str = strSize;
indices.put(check(id), Pair.of(str, ReturnType.STRING));
strSize++;
if(parent != null) {
parent.strSize = FastMath.max(parent.strSize, strSize);
}
return str;
}
public int bool(String id) {
int bool = boolSize;
indices.put(check(id), Pair.of(bool, ReturnType.BOOLEAN));
boolSize++;
if(parent != null) {
parent.boolSize = FastMath.max(parent.boolSize, boolSize);
}
return bool;
}
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);
}
};
private final Scope parent;
private final Map<String, Variable<?>> variableMap = new HashMap<>();
public Scope(Scope parent) {
this.parent = parent;
}
public Scope() {
this.parent = NULL;
}
public Variable<?> get(String id) {
Variable<?> var = variableMap.get(id);
return var == null ? parent.get(id) : var;
}
public void put(String id, Variable<?> variable) {
variableMap.put(id, variable);
}
public Scope sub() {
return new Scope(this);
}
}

View File

@ -33,11 +33,10 @@ public class ForKeyword implements Keyword<Block.ReturnInfo<?>> {
@Override
public Block.ReturnInfo<?> apply(ImplementationArguments implementationArguments, Scope scope) {
Scope sub = scope.sub();
for(initializer.apply(implementationArguments, sub);
statement.apply(implementationArguments, sub);
incrementer.apply(implementationArguments, sub)) {
Block.ReturnInfo<?> level = conditional.applyNoNewScope(implementationArguments, sub);
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;
}

View File

@ -7,6 +7,8 @@
package com.dfsek.terra.addons.terrascript.parser.lang.keywords.looplike;
import com.dfsek.terra.api.util.generic.pair.Pair;
import org.jetbrains.annotations.Nullable;
import java.util.List;
@ -58,23 +60,4 @@ public class IfKeyword implements Keyword<Block.ReturnInfo<?>> {
public ReturnType returnType() {
return ReturnType.VOID;
}
public static class Pair<L, R> {
private final L left;
private final R right;
public Pair(L left, R right) {
this.left = left;
this.right = right;
}
public L getLeft() {
return left;
}
public R getRight() {
return right;
}
}
}

View File

@ -1,40 +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.parser.lang.variables;
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.Returnable;
import com.dfsek.terra.addons.terrascript.parser.lang.Scope;
import com.dfsek.terra.addons.terrascript.tokenizer.Position;
public class VariableAssignmentNode<T> implements Item<T> {
private final Returnable<T> value;
private final Position position;
private final String identifier;
public VariableAssignmentNode(Returnable<T> value, String identifier, Position position) {
this.value = value;
this.identifier = identifier;
this.position = position;
}
@SuppressWarnings("unchecked")
@Override
public synchronized T apply(ImplementationArguments implementationArguments, Scope scope) {
T val = value.apply(implementationArguments, scope);
((Variable<T>) scope.get(identifier)).setValue(val);
return val;
}
@Override
public Position getPosition() {
return position;
}
}

View File

@ -1,62 +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.parser.lang.variables;
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.Returnable;
import com.dfsek.terra.addons.terrascript.parser.lang.Scope;
import com.dfsek.terra.addons.terrascript.tokenizer.Position;
public class VariableDeclarationNode<T> implements Item<T> {
private final Position position;
private final String identifier;
private final Returnable<T> value;
private final Returnable.ReturnType type;
public VariableDeclarationNode(Position position, String identifier, Returnable<T> value, Returnable.ReturnType type) {
switch(type) {
case STRING:
case BOOLEAN:
case NUMBER:
break;
default:
throw new IllegalArgumentException("Invalid variable type: " + type);
}
this.position = position;
this.identifier = identifier;
this.value = value;
this.type = type;
}
@Override
public T apply(ImplementationArguments implementationArguments, Scope scope) {
T result = value.apply(implementationArguments, scope);
scope.put(identifier, switch(type) {
case NUMBER -> new NumberVariable((Number) result, position);
case BOOLEAN -> new BooleanVariable((Boolean) result, position);
case STRING -> new StringVariable((String) result, position);
default -> throw new IllegalStateException("Unexpected value: " + type);
});
return result;
}
@Override
public Position getPosition() {
return position;
}
public Returnable.ReturnType getType() {
return type;
}
public String getIdentifier() {
return identifier;
}
}

View File

@ -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;
}
}

View File

@ -0,0 +1,26 @@
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.parser.lang.variables.Variable;
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;
}
}

View File

@ -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;
}
}

View File

@ -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.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;
private final Position position;
protected final int index;
public VariableAssignmentNode(Returnable<T> value, Position position, int index) {
this.value = value;
this.index = index;
this.position = position;
}
@Override
public Position getPosition() {
return position;
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -5,23 +5,21 @@
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.terrascript.parser.lang.variables;
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.Returnable;
import com.dfsek.terra.addons.terrascript.parser.lang.Scope;
import com.dfsek.terra.addons.terrascript.tokenizer.Position;
public class VariableReferenceNode implements Returnable<Object> {
private final String identifier;
public abstract class VariableReferenceNode<T> implements Returnable<T> {
private final Position position;
private final ReturnType type;
protected final int index;
public VariableReferenceNode(String identifier, Position position, ReturnType type) {
this.identifier = identifier;
public VariableReferenceNode(Position position, ReturnType type, int index) {
this.position = position;
this.type = type;
this.index = index;
}
@Override
@ -29,11 +27,6 @@ public class VariableReferenceNode implements Returnable<Object> {
return type;
}
@Override
public synchronized Object apply(ImplementationArguments implementationArguments, Scope scope) {
return scope.get(identifier).getValue();
}
@Override
public Position getPosition() {
return position;

View File

@ -7,6 +7,8 @@
package com.dfsek.terra.addons.terrascript.script;
import com.dfsek.terra.addons.terrascript.parser.lang.Executable;
import net.jafama.FastMath;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
@ -51,7 +53,7 @@ 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 Block block;
private final Executable block;
private final RegistryKey id;
private final String profile;
@ -146,7 +148,7 @@ public class StructureScript implements Structure, Keyed<StructureScript> {
private boolean applyBlock(TerraImplementationArguments arguments) {
try {
return block.apply(arguments).getLevel() != Block.ReturnLevel.FAIL;
return block.execute(arguments);
} catch(RuntimeException e) {
LOGGER.error("Failed to generate structure at {}", arguments.getOrigin(), e);
return false;