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