mirror of
https://github.com/PolyhedralDev/Terra.git
synced 2026-04-23 08:38:51 +00:00
Move terrascript 2 to separate module
This commit is contained in:
@@ -1,15 +0,0 @@
|
||||
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
|
||||
|
||||
version = version("1.1.0")
|
||||
|
||||
dependencies {
|
||||
api("commons-io:commons-io:2.7")
|
||||
compileOnlyApi(project(":common:addons:manifest-addon-loader"))
|
||||
implementation("net.jafama", "jafama", Versions.Libraries.Internal.jafama)
|
||||
testImplementation("net.jafama", "jafama", Versions.Libraries.Internal.jafama)
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
@@ -1,64 +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;
|
||||
|
||||
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;
|
||||
import com.dfsek.terra.api.event.functional.FunctionalEventHandler;
|
||||
import com.dfsek.terra.api.inject.annotations.Inject;
|
||||
import com.dfsek.terra.api.registry.CheckedRegistry;
|
||||
import com.dfsek.terra.api.structure.LootTable;
|
||||
import com.dfsek.terra.api.structure.Structure;
|
||||
import com.dfsek.terra.api.util.StringUtil;
|
||||
|
||||
|
||||
public class TerraScriptAddon implements AddonInitializer {
|
||||
@Inject
|
||||
private Platform platform;
|
||||
|
||||
@Inject
|
||||
private BaseAddon addon;
|
||||
|
||||
@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()
|
||||
.parallel()
|
||||
.map(entry -> {
|
||||
try {
|
||||
String id = StringUtil.fileName(entry.getKey());
|
||||
return new StructureScript(entry.getValue(),
|
||||
addon.key(id),
|
||||
platform,
|
||||
structureRegistry,
|
||||
lootRegistry,
|
||||
event.getPack().getOrCreateRegistry(FunctionBuilder.class));
|
||||
} catch(ParseException e) {
|
||||
throw new RuntimeException("Failed to load script \"" + entry.getKey() + "\"", e);
|
||||
}
|
||||
})
|
||||
.toList()
|
||||
.forEach(structureRegistry::register))
|
||||
.close();
|
||||
})
|
||||
.priority(100)
|
||||
.failThrough();
|
||||
}
|
||||
}
|
||||
@@ -1,468 +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;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
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.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;
|
||||
|
||||
|
||||
@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<>();
|
||||
|
||||
public Parser(String data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public Parser registerFunction(String name, FunctionBuilder<? extends Function<?>> functionBuilder) {
|
||||
functions.put(name, functionBuilder);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Parser ignoreFunction(String name) {
|
||||
ignoredFunctions.add(name);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 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 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, true, scopeBuilder), (Returnable<Boolean>) first, start); // While loop
|
||||
}
|
||||
|
||||
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, 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.
|
||||
}
|
||||
}
|
||||
|
||||
return new IfKeyword(statement, (Returnable<Boolean>) condition, elseIf, elseBlock, start); // If statement
|
||||
}
|
||||
|
||||
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 {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
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());
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
if(full && tokens.get().isBinaryOperator()) { // Parse binary operations
|
||||
return parseBinaryOperation(expression, tokens, scopeBuilder);
|
||||
}
|
||||
return expression;
|
||||
}
|
||||
|
||||
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 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 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());
|
||||
}
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
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 Block parseBlock(Tokenizer tokens, boolean loop, ScopeBuilder scopeBuilder) {
|
||||
List<Item<?>> parsedItems = new ArrayList<>();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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<>();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,9 @@
|
||||
package com.dfsek.terra.addons.terrascript;
|
||||
|
||||
import com.dfsek.terra.addons.manifest.api.AddonInitializer;
|
||||
import com.dfsek.terra.addons.terrascript.parser.exceptions.ParseException;
|
||||
import com.dfsek.terra.addons.terrascript.parser.lang.functions.FunctionBuilder;
|
||||
import com.dfsek.terra.addons.terrascript.script.StructureScript;
|
||||
import com.dfsek.terra.api.Platform;
|
||||
import com.dfsek.terra.api.addon.BaseAddon;
|
||||
import com.dfsek.terra.api.event.events.config.pack.ConfigPackPreLoadEvent;
|
||||
@@ -28,14 +31,12 @@ public class TerraScriptAddon implements AddonInitializer {
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
|
||||
platform.getEventManager()
|
||||
.getHandler(FunctionalEventHandler.class)
|
||||
.register(addon, ConfigPackPreLoadEvent.class)
|
||||
.then(event -> {
|
||||
CheckedRegistry<Structure> structureRegistry = event.getPack().getOrCreateRegistry(Structure.class);
|
||||
CheckedRegistry<LootTable> lootRegistry = event.getPack().getOrCreateRegistry(LootTable.class);
|
||||
/*
|
||||
event.getPack().getLoader().open("", ".tesf").thenEntries(
|
||||
entries ->
|
||||
entries.stream()
|
||||
@@ -56,8 +57,6 @@ public class TerraScriptAddon implements AddonInitializer {
|
||||
.toList()
|
||||
.forEach(structureRegistry::register))
|
||||
.close();
|
||||
|
||||
*/
|
||||
})
|
||||
.priority(100)
|
||||
.failThrough();
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
package com.dfsek.terra.addons.terrascript.exception.semanticanalysis;
|
||||
|
||||
import com.dfsek.terra.addons.terrascript.exception.CompilationException;
|
||||
import com.dfsek.terra.addons.terrascript.lexer.SourcePosition;
|
||||
|
||||
|
||||
public class IdentifierAlreadyDeclaredException extends CompilationException {
|
||||
public IdentifierAlreadyDeclaredException(String message, SourcePosition position) {
|
||||
super(message, position);
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
package com.dfsek.terra.addons.terrascript.exception.semanticanalysis;
|
||||
|
||||
import com.dfsek.terra.addons.terrascript.exception.CompilationException;
|
||||
import com.dfsek.terra.addons.terrascript.lexer.SourcePosition;
|
||||
|
||||
|
||||
public class InvalidFunctionDeclarationException extends CompilationException {
|
||||
public InvalidFunctionDeclarationException(String message, SourcePosition position) {
|
||||
super(message, position);
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
package com.dfsek.terra.addons.terrascript.exception.semanticanalysis;
|
||||
|
||||
import com.dfsek.terra.addons.terrascript.exception.CompilationException;
|
||||
import com.dfsek.terra.addons.terrascript.lexer.SourcePosition;
|
||||
|
||||
|
||||
public class InvalidTypeException extends CompilationException {
|
||||
public InvalidTypeException(String message, SourcePosition position) {
|
||||
super(message, position);
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
package com.dfsek.terra.addons.terrascript.exception.semanticanalysis;
|
||||
|
||||
import com.dfsek.terra.addons.terrascript.exception.CompilationException;
|
||||
import com.dfsek.terra.addons.terrascript.lexer.SourcePosition;
|
||||
|
||||
|
||||
public class UndefinedReferenceException extends CompilationException {
|
||||
public UndefinedReferenceException(String message, SourcePosition position) {
|
||||
super(message, position);
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package com.dfsek.terra.addons.terrascript.parser;
|
||||
|
||||
|
||||
import com.dfsek.terra.addons.terrascript.lexer.Token.TokenType;
|
||||
|
||||
|
||||
public enum BinaryOperator {
|
||||
BOOLEAN_OR(TokenType.BOOLEAN_OR),
|
||||
BOOLEAN_AND(TokenType.BOOLEAN_AND),
|
||||
EQUALS(TokenType.EQUALS_EQUALS),
|
||||
NOT_EQUALS(TokenType.BANG_EQUALS),
|
||||
GREATER(TokenType.GREATER),
|
||||
GREATER_EQUALS(TokenType.GREATER_EQUAL),
|
||||
LESS(TokenType.LESS),
|
||||
LESS_EQUALS(TokenType.LESS_EQUALS),
|
||||
ADD(TokenType.PLUS),
|
||||
SUBTRACT(TokenType.MINUS),
|
||||
MULTIPLY(TokenType.STAR),
|
||||
DIVIDE(TokenType.FORWARD_SLASH),
|
||||
MODULO(TokenType.MODULO_OPERATOR);
|
||||
|
||||
public final TokenType tokenType;
|
||||
|
||||
BinaryOperator(TokenType tokenType) { this.tokenType = tokenType; }
|
||||
}
|
||||
@@ -1,379 +1,468 @@
|
||||
/*
|
||||
* Copyright (c) 2020-2021 Polyhedral Development
|
||||
*
|
||||
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
|
||||
* reference the LICENSE file in this module's root directory.
|
||||
*/
|
||||
|
||||
package com.dfsek.terra.addons.terrascript.parser;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.Map;
|
||||
|
||||
import com.dfsek.terra.addons.terrascript.Type;
|
||||
import com.dfsek.terra.addons.terrascript.Type.TypeException;
|
||||
import com.dfsek.terra.addons.terrascript.ast.Expr;
|
||||
import com.dfsek.terra.addons.terrascript.ast.Expr.Variable;
|
||||
import com.dfsek.terra.addons.terrascript.ast.Stmt;
|
||||
import com.dfsek.terra.addons.terrascript.ast.Stmt.Block;
|
||||
import com.dfsek.terra.addons.terrascript.lexer.SourcePosition;
|
||||
import com.dfsek.terra.addons.terrascript.lexer.Token;
|
||||
import com.dfsek.terra.addons.terrascript.lexer.Token.TokenType;
|
||||
import com.dfsek.terra.addons.terrascript.parser.exceptions.ParseException;
|
||||
import com.dfsek.terra.addons.terrascript.parser.lang.Block;
|
||||
import com.dfsek.terra.addons.terrascript.parser.lang.Executable;
|
||||
import com.dfsek.terra.addons.terrascript.parser.lang.Item;
|
||||
import com.dfsek.terra.addons.terrascript.parser.lang.Keyword;
|
||||
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable;
|
||||
import com.dfsek.terra.addons.terrascript.parser.lang.Returnable.ReturnType;
|
||||
import com.dfsek.terra.addons.terrascript.parser.lang.Scope.ScopeBuilder;
|
||||
import com.dfsek.terra.addons.terrascript.parser.lang.constants.BooleanConstant;
|
||||
import com.dfsek.terra.addons.terrascript.parser.lang.constants.ConstantExpression;
|
||||
import com.dfsek.terra.addons.terrascript.parser.lang.constants.NumericConstant;
|
||||
import com.dfsek.terra.addons.terrascript.parser.lang.constants.StringConstant;
|
||||
import com.dfsek.terra.addons.terrascript.parser.lang.functions.Function;
|
||||
import com.dfsek.terra.addons.terrascript.parser.lang.functions.FunctionBuilder;
|
||||
import com.dfsek.terra.addons.terrascript.parser.lang.keywords.flow.BreakKeyword;
|
||||
import com.dfsek.terra.addons.terrascript.parser.lang.keywords.flow.ContinueKeyword;
|
||||
import com.dfsek.terra.addons.terrascript.parser.lang.keywords.flow.FailKeyword;
|
||||
import com.dfsek.terra.addons.terrascript.parser.lang.keywords.flow.ReturnKeyword;
|
||||
import com.dfsek.terra.addons.terrascript.parser.lang.keywords.looplike.ForKeyword;
|
||||
import com.dfsek.terra.addons.terrascript.parser.lang.keywords.looplike.IfKeyword;
|
||||
import com.dfsek.terra.addons.terrascript.parser.lang.keywords.looplike.WhileKeyword;
|
||||
import com.dfsek.terra.addons.terrascript.parser.lang.operations.BinaryOperation;
|
||||
import com.dfsek.terra.addons.terrascript.parser.lang.operations.BooleanAndOperation;
|
||||
import com.dfsek.terra.addons.terrascript.parser.lang.operations.BooleanNotOperation;
|
||||
import com.dfsek.terra.addons.terrascript.parser.lang.operations.BooleanOrOperation;
|
||||
import com.dfsek.terra.addons.terrascript.parser.lang.operations.ConcatenationOperation;
|
||||
import com.dfsek.terra.addons.terrascript.parser.lang.operations.DivisionOperation;
|
||||
import com.dfsek.terra.addons.terrascript.parser.lang.operations.ModuloOperation;
|
||||
import com.dfsek.terra.addons.terrascript.parser.lang.operations.MultiplicationOperation;
|
||||
import com.dfsek.terra.addons.terrascript.parser.lang.operations.NegationOperation;
|
||||
import com.dfsek.terra.addons.terrascript.parser.lang.operations.NumberAdditionOperation;
|
||||
import com.dfsek.terra.addons.terrascript.parser.lang.operations.SubtractionOperation;
|
||||
import com.dfsek.terra.addons.terrascript.parser.lang.operations.statements.EqualsStatement;
|
||||
import com.dfsek.terra.addons.terrascript.parser.lang.operations.statements.GreaterOrEqualsThanStatement;
|
||||
import com.dfsek.terra.addons.terrascript.parser.lang.operations.statements.GreaterThanStatement;
|
||||
import com.dfsek.terra.addons.terrascript.parser.lang.operations.statements.LessThanOrEqualsStatement;
|
||||
import com.dfsek.terra.addons.terrascript.parser.lang.operations.statements.LessThanStatement;
|
||||
import com.dfsek.terra.addons.terrascript.parser.lang.operations.statements.NotEqualsStatement;
|
||||
import com.dfsek.terra.addons.terrascript.parser.lang.variables.assign.BoolAssignmentNode;
|
||||
import com.dfsek.terra.addons.terrascript.parser.lang.variables.assign.NumAssignmentNode;
|
||||
import com.dfsek.terra.addons.terrascript.parser.lang.variables.assign.StrAssignmentNode;
|
||||
import com.dfsek.terra.addons.terrascript.parser.lang.variables.assign.VariableAssignmentNode;
|
||||
import com.dfsek.terra.addons.terrascript.parser.lang.variables.reference.BoolVariableReferenceNode;
|
||||
import com.dfsek.terra.addons.terrascript.parser.lang.variables.reference.NumVariableReferenceNode;
|
||||
import com.dfsek.terra.addons.terrascript.parser.lang.variables.reference.StrVariableReferenceNode;
|
||||
import com.dfsek.terra.addons.terrascript.tokenizer.Position;
|
||||
import com.dfsek.terra.addons.terrascript.tokenizer.Token;
|
||||
import com.dfsek.terra.addons.terrascript.tokenizer.Tokenizer;
|
||||
import com.dfsek.terra.api.util.generic.pair.Pair;
|
||||
|
||||
|
||||
/**
|
||||
* TerraScript recursive descent parser
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public class Parser {
|
||||
private final String data;
|
||||
private final Map<String, FunctionBuilder<? extends Function<?>>> functions = new HashMap<>();
|
||||
private final List<String> ignoredFunctions = new ArrayList<>();
|
||||
|
||||
private final List<Token> tokens;
|
||||
|
||||
private int index = 0;
|
||||
|
||||
private Parser(List<Token> tokens) {
|
||||
if(tokens.stream().noneMatch(t -> t.isType(TokenType.END_OF_FILE)))
|
||||
throw new IllegalArgumentException("Token list must contain at least one token of type " + TokenType.END_OF_FILE);
|
||||
this.tokens = tokens;
|
||||
public Parser(String data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public static Block parse(List<Token> tokens) {
|
||||
return new Parser(tokens).parseTokens();
|
||||
public Parser registerFunction(String name, FunctionBuilder<? extends Function<?>> functionBuilder) {
|
||||
functions.put(name, functionBuilder);
|
||||
return this;
|
||||
}
|
||||
|
||||
private Block parseTokens() {
|
||||
List<Stmt> statements = new ArrayList<>();
|
||||
while(hasNext()) {
|
||||
statements.add(statement());
|
||||
}
|
||||
if(hasNext()) throw new ParseException("Tokens were remaining after parsing", current().position());
|
||||
return new Stmt.Block(statements, new SourcePosition(0, 0));
|
||||
public Parser ignoreFunction(String name) {
|
||||
ignoredFunctions.add(name);
|
||||
return this;
|
||||
}
|
||||
|
||||
private Token current() {
|
||||
return tokens.get(index);
|
||||
/**
|
||||
* Parse input
|
||||
*
|
||||
* @return executable {@link Block}
|
||||
*
|
||||
* @throws ParseException If parsing fails.
|
||||
*/
|
||||
public Executable parse() {
|
||||
ScopeBuilder scopeBuilder = new ScopeBuilder();
|
||||
return new Executable(parseBlock(new Tokenizer(data), false, scopeBuilder), scopeBuilder);
|
||||
}
|
||||
|
||||
private boolean hasNext() {
|
||||
return !current().isType(TokenType.END_OF_FILE);
|
||||
}
|
||||
|
||||
private Token consume(String wrongTypeMessage, TokenType expected, TokenType... more) {
|
||||
if(!current().isType(expected) && Arrays.stream(more).noneMatch(t -> t == current().type())) throw new ParseException(
|
||||
wrongTypeMessage, current().position());
|
||||
return consumeUnchecked();
|
||||
}
|
||||
|
||||
public Token consumeUnchecked() {
|
||||
if(!hasNext()) return current();
|
||||
Token temp = current();
|
||||
index++;
|
||||
return temp;
|
||||
}
|
||||
|
||||
private void consumeStatementEnd(String after) {
|
||||
consume("Expected ';' after " + after + ", found '" + current().lexeme() + "'", TokenType.STATEMENT_END);
|
||||
}
|
||||
|
||||
private Stmt statement() {
|
||||
return switch(current().type()) {
|
||||
case BLOCK_BEGIN -> block();
|
||||
case FUNCTION -> functionDeclaration();
|
||||
case VARIABLE -> variableDeclaration();
|
||||
case RETURN -> returnStmt();
|
||||
case IF_STATEMENT -> ifStmt();
|
||||
case FOR_LOOP -> forLoop();
|
||||
case WHILE_LOOP -> whileLoop();
|
||||
case BREAK -> breakStmt();
|
||||
case CONTINUE -> continueStmt();
|
||||
case STATEMENT_END -> new Stmt.NoOp(consumeUnchecked().position());
|
||||
default -> expressionStatement();
|
||||
private Keyword<?> parseLoopLike(Tokenizer tokens, boolean loop, ScopeBuilder scopeBuilder) throws ParseException {
|
||||
|
||||
Token identifier = tokens.consume();
|
||||
ParserUtil.checkType(identifier, Token.Type.IF_STATEMENT, Token.Type.WHILE_LOOP, Token.Type.FOR_LOOP);
|
||||
|
||||
ParserUtil.checkType(tokens.consume(), Token.Type.GROUP_BEGIN);
|
||||
|
||||
return switch(identifier.getType()) {
|
||||
case FOR_LOOP -> parseForLoop(tokens, identifier.getPosition(), scopeBuilder);
|
||||
case IF_STATEMENT -> parseIfStatement(tokens, identifier.getPosition(), loop, scopeBuilder);
|
||||
case WHILE_LOOP -> parseWhileLoop(tokens, identifier.getPosition(), scopeBuilder);
|
||||
default -> throw new UnsupportedOperationException(
|
||||
"Unknown keyword " + identifier.getContent() + ": " + identifier.getPosition());
|
||||
};
|
||||
}
|
||||
|
||||
private Stmt functionDeclaration() {
|
||||
SourcePosition position = consume("Expected 'fun' keyword at start of function declaration", TokenType.FUNCTION).position();
|
||||
String id = consume("Expected identifier after 'fun' keyword for function declaration", TokenType.IDENTIFIER).lexeme();
|
||||
consume("Expected '(' after function identifier '" + id + "'", TokenType.OPEN_PAREN);
|
||||
private WhileKeyword parseWhileLoop(Tokenizer tokens, Position start, ScopeBuilder scopeBuilder) {
|
||||
Returnable<?> first = parseExpression(tokens, true, scopeBuilder);
|
||||
ParserUtil.checkReturnType(first, Returnable.ReturnType.BOOLEAN);
|
||||
|
||||
// Parse parameters
|
||||
List<Pair<String, Type>> params = new ArrayList<>();
|
||||
while(!current().isType(TokenType.CLOSE_PAREN)) {
|
||||
Token paramToken = consume("Expected parameter name or ')', found '" + current().lexeme() + "'", TokenType.IDENTIFIER);
|
||||
String paramId = paramToken.lexeme();
|
||||
if(params.stream().anyMatch(p -> Objects.equals(p.getLeft(), paramId)))
|
||||
throw new ParseException("Parameter '" + paramId + "' has already been declared in function '" + id + "'",
|
||||
paramToken.position());
|
||||
|
||||
consume("Expected type declaration after parameter name. Example: '" + paramId + ": <type>'", TokenType.COLON);
|
||||
Type paramType = typeExpr();
|
||||
|
||||
params.add(Pair.of(paramId, paramType));
|
||||
|
||||
if(current().isType(TokenType.CLOSE_PAREN)) break;
|
||||
consume("Expected ',' or ')' after parameter declaration '" + paramId + "' in function '" + id + "'", TokenType.SEPARATOR);
|
||||
}
|
||||
ParserUtil.checkType(tokens.consume(), Token.Type.GROUP_END);
|
||||
|
||||
Type funcReturn = Type.VOID;
|
||||
|
||||
consume("Expected ')' after " + (params.size() == 0 ? "')'" : "parameters") + " in declaration of function '" + id + "'",
|
||||
TokenType.CLOSE_PAREN);
|
||||
if(current().isType(TokenType.COLON)) {
|
||||
consumeUnchecked();
|
||||
funcReturn = typeExpr();
|
||||
}
|
||||
|
||||
Stmt.Block body = blockOrSingleStatement();
|
||||
|
||||
return new Stmt.FunctionDeclaration(id, params, funcReturn, body, position);
|
||||
return new WhileKeyword(parseStatementBlock(tokens, true, scopeBuilder), (Returnable<Boolean>) first, start); // While loop
|
||||
}
|
||||
|
||||
private Stmt.VariableDeclaration variableDeclaration() {
|
||||
SourcePosition position = consume("Expected 'var' keyword at start of variable declaration", TokenType.VARIABLE).position();
|
||||
String id = consume("Expected variable name after type for variable declaration", TokenType.IDENTIFIER).lexeme();
|
||||
consume("Expected ':' after variable name", TokenType.COLON);
|
||||
Type type = typeExpr();
|
||||
consume("Expected '=' following variable type declaration", TokenType.ASSIGNMENT);
|
||||
Expr expr = expression();
|
||||
consumeStatementEnd("variable declaration");
|
||||
private IfKeyword parseIfStatement(Tokenizer tokens, Position start, boolean loop, ScopeBuilder scopeBuilder) {
|
||||
Returnable<?> condition = parseExpression(tokens, true, scopeBuilder);
|
||||
ParserUtil.checkReturnType(condition, Returnable.ReturnType.BOOLEAN);
|
||||
|
||||
return new Stmt.VariableDeclaration(type, id, expr, position);
|
||||
}
|
||||
|
||||
private Type typeExpr() {
|
||||
Token typeToken = consume("Expected " + TokenType.IDENTIFIER + " specified as variable type", TokenType.IDENTIFIER);
|
||||
try {
|
||||
return Type.fromString(typeToken.lexeme());
|
||||
} catch(TypeException e) {
|
||||
throw new ParseException("Failed to parse type expression", typeToken.position());
|
||||
}
|
||||
}
|
||||
|
||||
private Stmt.Return returnStmt() {
|
||||
SourcePosition position = consume("Expected 'return' keyword, found '" + current().lexeme() + "'", TokenType.RETURN).position();
|
||||
Expr value = new Expr.Void(position);
|
||||
if(!current().isType(TokenType.STATEMENT_END))
|
||||
value = expression();
|
||||
consumeStatementEnd("return statement");
|
||||
return new Stmt.Return(value, position);
|
||||
}
|
||||
|
||||
private Stmt.If ifStmt() {
|
||||
// Parse main if clause
|
||||
SourcePosition position = consume("Expected 'if' keyword at beginning of if statement", TokenType.IF_STATEMENT).position();
|
||||
consume("Expected '(' after 'if' keyword", TokenType.OPEN_PAREN);
|
||||
Expr condition = expression();
|
||||
consume("Expected ')' after if statement condition", TokenType.CLOSE_PAREN);
|
||||
Stmt.Block trueBody = blockOrSingleStatement();
|
||||
ParserUtil.checkType(tokens.consume(), Token.Type.GROUP_END);
|
||||
|
||||
// Parse any else clauses
|
||||
Stmt.Block elseBody = null;
|
||||
List<Pair<Expr, Stmt.Block>> elseIfClauses = new ArrayList<>();
|
||||
while(current().isType(TokenType.ELSE)) {
|
||||
consumeUnchecked(); // Consume else
|
||||
|
||||
if(!current().isType(TokenType.IF_STATEMENT)) {
|
||||
elseBody = blockOrSingleStatement();
|
||||
break; // Else clause should be last in if statement
|
||||
Block elseBlock = null;
|
||||
Block statement = parseStatementBlock(tokens, loop, scopeBuilder);
|
||||
|
||||
List<Pair<Returnable<Boolean>, Block>> elseIf = new ArrayList<>();
|
||||
|
||||
while(tokens.hasNext() && tokens.get().getType().equals(Token.Type.ELSE)) {
|
||||
tokens.consume(); // Consume else.
|
||||
if(tokens.get().getType().equals(Token.Type.IF_STATEMENT)) {
|
||||
tokens.consume(); // Consume if.
|
||||
Returnable<?> elseCondition = parseExpression(tokens, true, scopeBuilder);
|
||||
ParserUtil.checkReturnType(elseCondition, Returnable.ReturnType.BOOLEAN);
|
||||
elseIf.add(Pair.of((Returnable<Boolean>) elseCondition, parseStatementBlock(tokens, loop, scopeBuilder)));
|
||||
} else {
|
||||
elseBlock = parseStatementBlock(tokens, loop, scopeBuilder);
|
||||
break; // Else must be last.
|
||||
}
|
||||
|
||||
consumeUnchecked(); // Consume if
|
||||
consume("Expected '(' after 'else if', e.g. 'if else (<condition>) ...'", TokenType.OPEN_PAREN);
|
||||
Expr elseIfCondition = expression();
|
||||
consume("Expected ')' after 'else if' clause, e.g. 'else if (<condition>) ...'", TokenType.CLOSE_PAREN);
|
||||
Stmt.Block elseIfBody = blockOrSingleStatement();
|
||||
elseIfClauses.add(Pair.of(elseIfCondition, elseIfBody));
|
||||
}
|
||||
|
||||
return new Stmt.If(condition, trueBody, elseIfClauses, Optional.ofNullable(elseBody), position);
|
||||
return new IfKeyword(statement, (Returnable<Boolean>) condition, elseIf, elseBlock, start); // If statement
|
||||
}
|
||||
|
||||
private Stmt.For forLoop() {
|
||||
SourcePosition position = consume("Expected 'for' keyword at beginning of for loop", TokenType.FOR_LOOP).position();
|
||||
consume("Expected '(' after 'for' keyword", TokenType.OPEN_PAREN);
|
||||
Stmt initializer = statement();
|
||||
Expr condition;
|
||||
if(current().isType(TokenType.STATEMENT_END)) {
|
||||
condition = new Expr.Literal(true, Type.BOOLEAN,
|
||||
current().position()); // If no condition is provided, set condition = true
|
||||
consumeUnchecked();
|
||||
private Block parseStatementBlock(Tokenizer tokens, boolean loop, ScopeBuilder scopeBuilder) {
|
||||
|
||||
if(tokens.get().getType().equals(Token.Type.BLOCK_BEGIN)) {
|
||||
ParserUtil.checkType(tokens.consume(), Token.Type.BLOCK_BEGIN);
|
||||
Block block = parseBlock(tokens, loop, scopeBuilder);
|
||||
ParserUtil.checkType(tokens.consume(), Token.Type.BLOCK_END);
|
||||
return block;
|
||||
} else {
|
||||
condition = expression();
|
||||
consumeStatementEnd("loop condition");
|
||||
Position position = tokens.get().getPosition();
|
||||
Block block = new Block(Collections.singletonList(parseItem(tokens, loop, scopeBuilder)), position);
|
||||
ParserUtil.checkType(tokens.consume(), Token.Type.STATEMENT_END);
|
||||
return block;
|
||||
}
|
||||
Expr incrementer;
|
||||
if(current().isType(TokenType.CLOSE_PAREN)) {
|
||||
incrementer = null;
|
||||
consumeUnchecked();
|
||||
}
|
||||
|
||||
private ForKeyword parseForLoop(Tokenizer tokens, Position start, ScopeBuilder scopeBuilder) {
|
||||
scopeBuilder = scopeBuilder.sub(); // new scope
|
||||
Token f = tokens.get();
|
||||
ParserUtil.checkType(f, Token.Type.NUMBER_VARIABLE, Token.Type.STRING_VARIABLE, Token.Type.BOOLEAN_VARIABLE, Token.Type.IDENTIFIER);
|
||||
Item<?> initializer;
|
||||
if(f.isVariableDeclaration()) {
|
||||
VariableAssignmentNode<?> forVar = parseVariableDeclaration(tokens, scopeBuilder);
|
||||
Token name = tokens.get();
|
||||
if(functions.containsKey(name.getContent()) || scopeBuilder.contains(name.getContent()))
|
||||
throw new ParseException(name.getContent() + " is already defined in this scope", name.getPosition());
|
||||
initializer = forVar;
|
||||
} else initializer = parseExpression(tokens, true, scopeBuilder);
|
||||
ParserUtil.checkType(tokens.consume(), Token.Type.STATEMENT_END);
|
||||
Returnable<?> conditional = parseExpression(tokens, true, scopeBuilder);
|
||||
ParserUtil.checkReturnType(conditional, Returnable.ReturnType.BOOLEAN);
|
||||
ParserUtil.checkType(tokens.consume(), Token.Type.STATEMENT_END);
|
||||
|
||||
Item<?> incrementer;
|
||||
Token token = tokens.get();
|
||||
if(scopeBuilder.contains(token.getContent())) { // Assume variable assignment
|
||||
incrementer = parseAssignment(tokens, scopeBuilder);
|
||||
} else incrementer = parseFunction(tokens, true, scopeBuilder);
|
||||
|
||||
ParserUtil.checkType(tokens.consume(), Token.Type.GROUP_END);
|
||||
|
||||
return new ForKeyword(parseStatementBlock(tokens, true, scopeBuilder), initializer, (Returnable<Boolean>) conditional, incrementer,
|
||||
start);
|
||||
}
|
||||
|
||||
private Returnable<?> parseExpression(Tokenizer tokens, boolean full, ScopeBuilder scopeBuilder) {
|
||||
boolean booleanInverted = false; // Check for boolean not operator
|
||||
boolean negate = false;
|
||||
if(tokens.get().getType().equals(Token.Type.BOOLEAN_NOT)) {
|
||||
booleanInverted = true;
|
||||
tokens.consume();
|
||||
} else if(tokens.get().getType().equals(Token.Type.SUBTRACTION_OPERATOR)) {
|
||||
negate = true;
|
||||
tokens.consume();
|
||||
}
|
||||
|
||||
Token id = tokens.get();
|
||||
|
||||
ParserUtil.checkType(id, Token.Type.IDENTIFIER, Token.Type.BOOLEAN, Token.Type.STRING, Token.Type.NUMBER, Token.Type.GROUP_BEGIN);
|
||||
|
||||
Returnable<?> expression;
|
||||
if(id.isConstant()) {
|
||||
expression = parseConstantExpression(tokens);
|
||||
} else if(id.getType().equals(Token.Type.GROUP_BEGIN)) { // Parse grouped expression
|
||||
expression = parseGroup(tokens, scopeBuilder);
|
||||
} else {
|
||||
incrementer = expression();
|
||||
consume("Expected ')' after for loop incrementer", TokenType.CLOSE_PAREN);
|
||||
if(functions.containsKey(id.getContent()))
|
||||
expression = parseFunction(tokens, false, scopeBuilder);
|
||||
else if(scopeBuilder.contains(id.getContent())) {
|
||||
ParserUtil.checkType(tokens.consume(), Token.Type.IDENTIFIER);
|
||||
String varId = id.getContent();
|
||||
ReturnType varType = scopeBuilder.getType(varId);
|
||||
expression = switch(varType) {
|
||||
case NUMBER -> new NumVariableReferenceNode(id.getPosition(), varType, scopeBuilder.getIndex(varId));
|
||||
case STRING -> new StrVariableReferenceNode(id.getPosition(), varType, scopeBuilder.getIndex(varId));
|
||||
case BOOLEAN -> new BoolVariableReferenceNode(id.getPosition(), varType, scopeBuilder.getIndex(varId));
|
||||
default -> throw new ParseException("Illegal type for variable reference: " + varType, id.getPosition());
|
||||
};
|
||||
|
||||
} else throw new ParseException("Unexpected token \" " + id.getContent() + "\"", id.getPosition());
|
||||
}
|
||||
Stmt.Block body = blockOrSingleStatement();
|
||||
return new Stmt.For(initializer, condition, incrementer, body, position);
|
||||
}
|
||||
|
||||
private Stmt.While whileLoop() {
|
||||
SourcePosition position = consume("Expected 'for' keyword at beginning of while loop", TokenType.WHILE_LOOP).position();
|
||||
consume("Expected '(' after 'while' keyword", TokenType.OPEN_PAREN);
|
||||
Expr condition = expression();
|
||||
consume("Expected ')' after while loop condition", TokenType.CLOSE_PAREN);
|
||||
Stmt.Block body = blockOrSingleStatement();
|
||||
return new Stmt.While(condition, body, position);
|
||||
}
|
||||
|
||||
private Stmt.Break breakStmt() {
|
||||
SourcePosition position = consume("Expected 'break' keyword for break statement", TokenType.BREAK).position();
|
||||
consumeStatementEnd("'break' keyword");
|
||||
return new Stmt.Break(position);
|
||||
}
|
||||
|
||||
private Stmt.Continue continueStmt() {
|
||||
SourcePosition position = consume("Expected 'continue' keyword for continue statement", TokenType.CONTINUE).position();
|
||||
consumeStatementEnd("'continue' keyword");
|
||||
return new Stmt.Continue(position);
|
||||
}
|
||||
|
||||
private Stmt.Block blockOrSingleStatement() {
|
||||
if(!current().isType(TokenType.BLOCK_BEGIN)) return new Stmt.Block(List.of(statement()), current().position());
|
||||
return block();
|
||||
}
|
||||
|
||||
private Stmt.Block block() {
|
||||
SourcePosition position = consume("Expected '{' at start of block", TokenType.BLOCK_BEGIN).position();
|
||||
List<Stmt> statements = new ArrayList<>();
|
||||
while(!current().isType(TokenType.BLOCK_END)) {
|
||||
statements.add(statement());
|
||||
|
||||
if(booleanInverted) { // Invert operation if boolean not detected
|
||||
ParserUtil.checkReturnType(expression, Returnable.ReturnType.BOOLEAN);
|
||||
expression = new BooleanNotOperation((Returnable<Boolean>) expression, expression.getPosition());
|
||||
} else if(negate) {
|
||||
ParserUtil.checkReturnType(expression, Returnable.ReturnType.NUMBER);
|
||||
expression = new NegationOperation((Returnable<Number>) expression, expression.getPosition());
|
||||
}
|
||||
consume("Expected '}' at end of block", TokenType.BLOCK_END);
|
||||
return new Stmt.Block(statements, position);
|
||||
|
||||
if(full && tokens.get().isBinaryOperator()) { // Parse binary operations
|
||||
return parseBinaryOperation(expression, tokens, scopeBuilder);
|
||||
}
|
||||
return expression;
|
||||
}
|
||||
|
||||
private Stmt expressionStatement() {
|
||||
Expr expression = expression();
|
||||
consumeStatementEnd("expression statement");
|
||||
return new Stmt.Expression(expression, expression.position);
|
||||
private ConstantExpression<?> parseConstantExpression(Tokenizer tokens) {
|
||||
Token constantToken = tokens.consume();
|
||||
Position position = constantToken.getPosition();
|
||||
switch(constantToken.getType()) {
|
||||
case NUMBER:
|
||||
String content = constantToken.getContent();
|
||||
return new NumericConstant(content.contains(".") ? Double.parseDouble(content) : Integer.parseInt(content), position);
|
||||
case STRING:
|
||||
return new StringConstant(constantToken.getContent(), position);
|
||||
case BOOLEAN:
|
||||
return new BooleanConstant(Boolean.parseBoolean(constantToken.getContent()), position);
|
||||
default:
|
||||
throw new UnsupportedOperationException(
|
||||
"Unsupported constant token: " + constantToken.getType() + " at position: " + position);
|
||||
}
|
||||
}
|
||||
|
||||
private Expr expression() {
|
||||
return assignment();
|
||||
private Returnable<?> parseGroup(Tokenizer tokens, ScopeBuilder scopeBuilder) {
|
||||
ParserUtil.checkType(tokens.consume(), Token.Type.GROUP_BEGIN);
|
||||
Returnable<?> expression = parseExpression(tokens, true, scopeBuilder); // Parse inside of group as a separate expression
|
||||
ParserUtil.checkType(tokens.consume(), Token.Type.GROUP_END);
|
||||
return expression;
|
||||
}
|
||||
|
||||
private Expr leftAssociativeBinaryExpression(Supplier<Expr> higherPrecedence, BinaryOperator... operators) {
|
||||
Expr expr = higherPrecedence.get();
|
||||
loop:
|
||||
while(true) {
|
||||
for(BinaryOperator operator : operators) {
|
||||
if(current().isType(operator.tokenType)) {
|
||||
SourcePosition position = consumeUnchecked().position(); // Consume operator token
|
||||
expr = new Expr.Binary(expr, operator, higherPrecedence.get(), position);
|
||||
continue loop;
|
||||
private BinaryOperation<?, ?> parseBinaryOperation(Returnable<?> left, Tokenizer tokens,
|
||||
ScopeBuilder scopeBuilder) {
|
||||
Token binaryOperator = tokens.consume();
|
||||
ParserUtil.checkBinaryOperator(binaryOperator);
|
||||
|
||||
Returnable<?> right = parseExpression(tokens, false, scopeBuilder);
|
||||
|
||||
Token other = tokens.get();
|
||||
if(ParserUtil.hasPrecedence(binaryOperator.getType(), other.getType())) {
|
||||
return assemble(left, parseBinaryOperation(right, tokens, scopeBuilder), binaryOperator);
|
||||
} else if(other.isBinaryOperator()) {
|
||||
return parseBinaryOperation(assemble(left, right, binaryOperator), tokens, scopeBuilder);
|
||||
}
|
||||
return assemble(left, right, binaryOperator);
|
||||
}
|
||||
|
||||
private BinaryOperation<?, ?> assemble(Returnable<?> left, Returnable<?> right, Token binaryOperator) {
|
||||
if(binaryOperator.isStrictNumericOperator())
|
||||
ParserUtil.checkArithmeticOperation(left, right, binaryOperator); // Numeric type checking
|
||||
if(binaryOperator.isStrictBooleanOperator()) ParserUtil.checkBooleanOperation(left, right, binaryOperator); // Boolean type checking
|
||||
switch(binaryOperator.getType()) {
|
||||
case ADDITION_OPERATOR:
|
||||
if(left.returnType().equals(Returnable.ReturnType.NUMBER) && right.returnType().equals(Returnable.ReturnType.NUMBER)) {
|
||||
return new NumberAdditionOperation((Returnable<Number>) left, (Returnable<Number>) right, binaryOperator.getPosition());
|
||||
}
|
||||
}
|
||||
break; // Break if not any operator
|
||||
return new ConcatenationOperation((Returnable<Object>) left, (Returnable<Object>) right, binaryOperator.getPosition());
|
||||
case SUBTRACTION_OPERATOR:
|
||||
return new SubtractionOperation((Returnable<Number>) left, (Returnable<Number>) right, binaryOperator.getPosition());
|
||||
case MULTIPLICATION_OPERATOR:
|
||||
return new MultiplicationOperation((Returnable<Number>) left, (Returnable<Number>) right, binaryOperator.getPosition());
|
||||
case DIVISION_OPERATOR:
|
||||
return new DivisionOperation((Returnable<Number>) left, (Returnable<Number>) right, binaryOperator.getPosition());
|
||||
case EQUALS_OPERATOR:
|
||||
return new EqualsStatement((Returnable<Object>) left, (Returnable<Object>) right, binaryOperator.getPosition());
|
||||
case NOT_EQUALS_OPERATOR:
|
||||
return new NotEqualsStatement((Returnable<Object>) left, (Returnable<Object>) right, binaryOperator.getPosition());
|
||||
case GREATER_THAN_OPERATOR:
|
||||
return new GreaterThanStatement((Returnable<Number>) left, (Returnable<Number>) right, binaryOperator.getPosition());
|
||||
case LESS_THAN_OPERATOR:
|
||||
return new LessThanStatement((Returnable<Number>) left, (Returnable<Number>) right, binaryOperator.getPosition());
|
||||
case GREATER_THAN_OR_EQUALS_OPERATOR:
|
||||
return new GreaterOrEqualsThanStatement((Returnable<Number>) left, (Returnable<Number>) right,
|
||||
binaryOperator.getPosition());
|
||||
case LESS_THAN_OR_EQUALS_OPERATOR:
|
||||
return new LessThanOrEqualsStatement((Returnable<Number>) left, (Returnable<Number>) right, binaryOperator.getPosition());
|
||||
case BOOLEAN_AND:
|
||||
return new BooleanAndOperation((Returnable<Boolean>) left, (Returnable<Boolean>) right, binaryOperator.getPosition());
|
||||
case BOOLEAN_OR:
|
||||
return new BooleanOrOperation((Returnable<Boolean>) left, (Returnable<Boolean>) right, binaryOperator.getPosition());
|
||||
case MODULO_OPERATOR:
|
||||
return new ModuloOperation((Returnable<Number>) left, (Returnable<Number>) right, binaryOperator.getPosition());
|
||||
default:
|
||||
throw new UnsupportedOperationException("Unsupported binary operator: " + binaryOperator.getType());
|
||||
}
|
||||
return expr;
|
||||
}
|
||||
|
||||
private Expr rightAssociativeBinaryExpression(Supplier<Expr> higherPrecedence, BinaryOperator... operators) {
|
||||
Expr expr = higherPrecedence.get();
|
||||
for(BinaryOperator operator : operators) {
|
||||
if(current().isType(operator.tokenType)) {
|
||||
SourcePosition position = consumeUnchecked().position(); // Consume operator token
|
||||
return new Expr.Binary(expr, operator, rightAssociativeBinaryExpression(higherPrecedence, operators), position);
|
||||
}
|
||||
}
|
||||
return expr;
|
||||
}
|
||||
|
||||
private Expr assignment() {
|
||||
Expr expr = logicOr();
|
||||
if(current().isType(TokenType.ASSIGNMENT)) {
|
||||
SourcePosition position = consumeUnchecked().position(); // Consume operator token
|
||||
if(!(expr instanceof Variable variable)) throw new ParseException("Invalid assignment target", position);
|
||||
return new Expr.Assignment(variable, assignment(), position);
|
||||
}
|
||||
return expr;
|
||||
}
|
||||
|
||||
private Expr logicOr() {
|
||||
return leftAssociativeBinaryExpression(this::logicAnd, BinaryOperator.BOOLEAN_OR);
|
||||
}
|
||||
|
||||
private Expr logicAnd() {
|
||||
return leftAssociativeBinaryExpression(this::equality, BinaryOperator.BOOLEAN_AND);
|
||||
}
|
||||
|
||||
private Expr equality() {
|
||||
return leftAssociativeBinaryExpression(this::comparison, BinaryOperator.EQUALS, BinaryOperator.NOT_EQUALS);
|
||||
}
|
||||
|
||||
private Expr comparison() {
|
||||
return leftAssociativeBinaryExpression(this::term, BinaryOperator.GREATER, BinaryOperator.GREATER_EQUALS, BinaryOperator.LESS,
|
||||
BinaryOperator.LESS_EQUALS);
|
||||
}
|
||||
|
||||
private Expr term() {
|
||||
return leftAssociativeBinaryExpression(this::factor, BinaryOperator.ADD, BinaryOperator.SUBTRACT);
|
||||
}
|
||||
|
||||
private Expr factor() {
|
||||
return leftAssociativeBinaryExpression(this::unary, BinaryOperator.MULTIPLY, BinaryOperator.DIVIDE, BinaryOperator.MODULO);
|
||||
}
|
||||
|
||||
private Expr unary() {
|
||||
UnaryOperator[] operators = { UnaryOperator.NOT, UnaryOperator.NEGATE };
|
||||
for(UnaryOperator operator : operators) {
|
||||
if(current().isType(operator.tokenType)) {
|
||||
SourcePosition position = consumeUnchecked().position();
|
||||
return new Expr.Unary(operator, unary(), position);
|
||||
}
|
||||
}
|
||||
return primary();
|
||||
}
|
||||
|
||||
private Expr primary() {
|
||||
Token token = consumeUnchecked();
|
||||
SourcePosition position = token.position();
|
||||
return switch(token.type()) {
|
||||
case NUMBER -> new Expr.Literal(Double.parseDouble(token.lexeme()), Type.NUMBER, position);
|
||||
case STRING -> new Expr.Literal(token.lexeme(), Type.STRING, position);
|
||||
case BOOLEAN -> new Expr.Literal(Boolean.parseBoolean(token.lexeme()), Type.BOOLEAN, position);
|
||||
case IDENTIFIER -> {
|
||||
if(current().isType(TokenType.OPEN_PAREN)) yield call(token);
|
||||
else yield variable(token);
|
||||
}
|
||||
case OPEN_PAREN -> {
|
||||
if(current().isType(TokenType.CLOSE_PAREN)) {
|
||||
consumeUnchecked(); // Consume ')'
|
||||
yield new Expr.Void(position); // () evaluates to void
|
||||
}
|
||||
Expr expr = expression();
|
||||
consume("Expected ')' to close '(' located at " + position, TokenType.CLOSE_PAREN);
|
||||
yield new Expr.Grouping(expr, position);
|
||||
}
|
||||
default -> throw new ParseException("Unexpected token '" + token.lexeme() + "'", position);
|
||||
private VariableAssignmentNode<?> parseVariableDeclaration(Tokenizer tokens, ScopeBuilder scopeBuilder) {
|
||||
Token type = tokens.consume();
|
||||
ParserUtil.checkType(type, Token.Type.STRING_VARIABLE, Token.Type.BOOLEAN_VARIABLE, Token.Type.NUMBER_VARIABLE);
|
||||
|
||||
Returnable.ReturnType returnType = ParserUtil.getVariableReturnType(type);
|
||||
|
||||
ParserUtil.checkVarType(type, returnType); // Check for type mismatch
|
||||
Token identifier = tokens.consume();
|
||||
ParserUtil.checkType(identifier, Token.Type.IDENTIFIER);
|
||||
if(functions.containsKey(identifier.getContent()) || scopeBuilder.contains(identifier.getContent()))
|
||||
throw new ParseException(identifier.getContent() + " is already defined in this scope", identifier.getPosition());
|
||||
ParserUtil.checkType(tokens.consume(), Token.Type.ASSIGNMENT);
|
||||
|
||||
Returnable<?> value = parseExpression(tokens, true, scopeBuilder);
|
||||
ParserUtil.checkReturnType(value, returnType);
|
||||
|
||||
String id = identifier.getContent();
|
||||
|
||||
return switch(value.returnType()) {
|
||||
case NUMBER -> new NumAssignmentNode((Returnable<Number>) value, identifier.getPosition(), scopeBuilder.num(id));
|
||||
case STRING -> new StrAssignmentNode((Returnable<String>) value, identifier.getPosition(), scopeBuilder.str(id));
|
||||
case BOOLEAN -> new BoolAssignmentNode((Returnable<Boolean>) value, identifier.getPosition(), scopeBuilder.bool(id));
|
||||
default -> throw new ParseException("Illegal type for variable declaration: " + type, value.getPosition());
|
||||
};
|
||||
}
|
||||
|
||||
private Expr call(Token identifier) {
|
||||
String id = identifier.lexeme();
|
||||
SourcePosition position = consume("Expected '(' to initiate function call on function '" + id + "'",
|
||||
TokenType.OPEN_PAREN).position();
|
||||
private Block parseBlock(Tokenizer tokens, boolean loop, ScopeBuilder scopeBuilder) {
|
||||
List<Item<?>> parsedItems = new ArrayList<>();
|
||||
|
||||
List<Expr> args = new ArrayList<>();
|
||||
while(!current().isType(TokenType.CLOSE_PAREN)) {
|
||||
args.add(expression());
|
||||
if(current().isType(TokenType.CLOSE_PAREN)) break;
|
||||
consume("Expected ',' or ')' after passed argument in function call of '" + id + "'", TokenType.SEPARATOR);
|
||||
scopeBuilder = scopeBuilder.sub();
|
||||
|
||||
Token first = tokens.get();
|
||||
|
||||
while(tokens.hasNext()) {
|
||||
Token token = tokens.get();
|
||||
if(token.getType().equals(Token.Type.BLOCK_END)) break; // Stop parsing at block end.
|
||||
Item<?> parsedItem = parseItem(tokens, loop, scopeBuilder);
|
||||
if(parsedItem != Function.NULL) {
|
||||
parsedItems.add(parsedItem);
|
||||
}
|
||||
if(tokens.hasNext() && !token.isLoopLike()) ParserUtil.checkType(tokens.consume(), Token.Type.STATEMENT_END);
|
||||
}
|
||||
return new Block(parsedItems, first.getPosition());
|
||||
}
|
||||
|
||||
private Item<?> parseItem(Tokenizer tokens, boolean loop, ScopeBuilder scopeBuilder) {
|
||||
Token token = tokens.get();
|
||||
if(loop) ParserUtil.checkType(token, Token.Type.IDENTIFIER, Token.Type.IF_STATEMENT, Token.Type.WHILE_LOOP, Token.Type.FOR_LOOP,
|
||||
Token.Type.NUMBER_VARIABLE, Token.Type.STRING_VARIABLE, Token.Type.BOOLEAN_VARIABLE,
|
||||
Token.Type.RETURN, Token.Type.BREAK, Token.Type.CONTINUE, Token.Type.FAIL);
|
||||
else ParserUtil.checkType(token, Token.Type.IDENTIFIER, Token.Type.IF_STATEMENT, Token.Type.WHILE_LOOP, Token.Type.FOR_LOOP,
|
||||
Token.Type.NUMBER_VARIABLE, Token.Type.STRING_VARIABLE, Token.Type.BOOLEAN_VARIABLE, Token.Type.RETURN,
|
||||
Token.Type.FAIL);
|
||||
|
||||
if(token.isLoopLike()) { // Parse loop-like tokens (if, while, etc)
|
||||
return parseLoopLike(tokens, loop, scopeBuilder);
|
||||
} else if(token.isIdentifier()) { // Parse identifiers
|
||||
if(scopeBuilder.contains(token.getContent())) { // Assume variable assignment
|
||||
return parseAssignment(tokens, scopeBuilder);
|
||||
} else return parseFunction(tokens, true, scopeBuilder);
|
||||
} else if(token.isVariableDeclaration()) {
|
||||
|
||||
return parseVariableDeclaration(tokens, scopeBuilder);
|
||||
|
||||
} else if(token.getType().equals(Token.Type.RETURN)) return new ReturnKeyword(tokens.consume().getPosition());
|
||||
else if(token.getType().equals(Token.Type.BREAK)) return new BreakKeyword(tokens.consume().getPosition());
|
||||
else if(token.getType().equals(Token.Type.CONTINUE)) return new ContinueKeyword(tokens.consume().getPosition());
|
||||
else if(token.getType().equals(Token.Type.FAIL)) return new FailKeyword(tokens.consume().getPosition());
|
||||
else throw new UnsupportedOperationException("Unexpected token " + token.getType() + ": " + token.getPosition());
|
||||
}
|
||||
|
||||
private VariableAssignmentNode<?> parseAssignment(Tokenizer tokens, ScopeBuilder scopeBuilder) {
|
||||
Token identifier = tokens.consume();
|
||||
|
||||
ParserUtil.checkType(identifier, Token.Type.IDENTIFIER);
|
||||
|
||||
ParserUtil.checkType(tokens.consume(), Token.Type.ASSIGNMENT);
|
||||
|
||||
Returnable<?> value = parseExpression(tokens, true, scopeBuilder);
|
||||
|
||||
String id = identifier.getContent();
|
||||
|
||||
ParserUtil.checkReturnType(value, scopeBuilder.getType(id));
|
||||
|
||||
ReturnType type = value.returnType();
|
||||
|
||||
return switch(type) {
|
||||
case NUMBER -> new NumAssignmentNode((Returnable<Number>) value, identifier.getPosition(), scopeBuilder.getIndex(id));
|
||||
case STRING -> new StrAssignmentNode((Returnable<String>) value, identifier.getPosition(), scopeBuilder.getIndex(id));
|
||||
case BOOLEAN -> new BoolAssignmentNode((Returnable<Boolean>) value, identifier.getPosition(), scopeBuilder.getIndex(id));
|
||||
default -> throw new ParseException("Illegal type for variable assignment: " + type, value.getPosition());
|
||||
};
|
||||
}
|
||||
|
||||
private Function<?> parseFunction(Tokenizer tokens, boolean fullStatement, ScopeBuilder scopeBuilder) {
|
||||
Token identifier = tokens.consume();
|
||||
ParserUtil.checkType(identifier, Token.Type.IDENTIFIER); // First token must be identifier
|
||||
|
||||
if(!functions.containsKey(identifier.getContent()))
|
||||
throw new ParseException("No such function \"" + identifier.getContent() + "\"", identifier.getPosition());
|
||||
|
||||
ParserUtil.checkType(tokens.consume(), Token.Type.GROUP_BEGIN); // Second is body begin
|
||||
|
||||
|
||||
List<Returnable<?>> args = getArgs(tokens, scopeBuilder); // Extract arguments, consume the rest.
|
||||
|
||||
ParserUtil.checkType(tokens.consume(), Token.Type.GROUP_END); // Remove body end
|
||||
|
||||
if(fullStatement) ParserUtil.checkType(tokens.get(), Token.Type.STATEMENT_END);
|
||||
|
||||
if(ignoredFunctions.contains(identifier.getContent())) {
|
||||
return Function.NULL;
|
||||
}
|
||||
|
||||
consume("Expected ')' after " + (args.size() == 0 ? "')'" : "arguments") + " in function call of '" + id + "'",
|
||||
TokenType.CLOSE_PAREN);
|
||||
if(functions.containsKey(identifier.getContent())) {
|
||||
FunctionBuilder<?> builder = functions.get(identifier.getContent());
|
||||
|
||||
if(builder.argNumber() != -1 && args.size() != builder.argNumber())
|
||||
throw new ParseException("Expected " + builder.argNumber() + " arguments, found " + args.size(), identifier.getPosition());
|
||||
|
||||
for(int i = 0; i < args.size(); i++) {
|
||||
Returnable<?> argument = args.get(i);
|
||||
if(builder.getArgument(i) == null)
|
||||
throw new ParseException("Unexpected argument at position " + i + " in function " + identifier.getContent(),
|
||||
identifier.getPosition());
|
||||
ParserUtil.checkReturnType(argument, builder.getArgument(i));
|
||||
}
|
||||
return builder.build(args, identifier.getPosition());
|
||||
}
|
||||
throw new UnsupportedOperationException("Unsupported function: " + identifier.getContent());
|
||||
}
|
||||
|
||||
private List<Returnable<?>> getArgs(Tokenizer tokens, ScopeBuilder scopeBuilder) {
|
||||
List<Returnable<?>> args = new ArrayList<>();
|
||||
|
||||
return new Expr.Call(id, args, position);
|
||||
while(!tokens.get().getType().equals(Token.Type.GROUP_END)) {
|
||||
args.add(parseExpression(tokens, true, scopeBuilder));
|
||||
ParserUtil.checkType(tokens.get(), Token.Type.SEPARATOR, Token.Type.GROUP_END);
|
||||
if(tokens.get().getType().equals(Token.Type.SEPARATOR)) tokens.consume();
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
private Expr variable(Token identifier) {
|
||||
return new Expr.Variable(identifier.lexeme(), identifier.position());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user