diff --git a/worldedit-core/src/main/antlr/com/sk89q/worldedit/antlr/Expression.g4 b/worldedit-core/src/main/antlr/com/sk89q/worldedit/antlr/Expression.g4
index b244f2c64..c98965af6 100644
--- a/worldedit-core/src/main/antlr/com/sk89q/worldedit/antlr/Expression.g4
+++ b/worldedit-core/src/main/antlr/com/sk89q/worldedit/antlr/Expression.g4
@@ -79,107 +79,158 @@ allStatements : statements EOF ;
statements : statement+ ;
statement
- : block # BlockStmt
- | ifStatement # IfStmt
- | whileStatement # WhileStmt
- | doStatement # DoStmt
- | forStatement # ForStmt
- | breakStatement # BreakStmt
- | continueStatement # ContinueStmt
- | returnStatement # ReturnStmt
- | switchStatement # SwitchStmt
- | expressionStatement # ExpressionStmt
- | SEMI_COLON # EmptyStmt
+ : ( block
+ | ifStatement
+ | whileStatement
+ | doStatement
+ | forStatement
+ | simpleForStatement
+ | breakStatement
+ | continueStatement
+ | returnStatement
+ | switchStatement
+ | expressionStatement
+ | emptyStatement
+ ) SEMI_COLON?
;
block : '{' statements '}' ;
-ifStatement : IF '(' expression ')' statement ( ELSE statement ) ;
+ifStatement : IF '(' condition=expression ')' trueBranch=statement ( ELSE falseBranch=statement )? ;
-whileStatement : WHILE '(' expression ')' statement ;
+whileStatement : WHILE '(' condition=expression ')' body=statement ;
-doStatement : DO statement WHILE '(' expression ')' SEMI_COLON ;
+doStatement : DO body=statement WHILE '(' condition=expression ')' ;
+// C-Style for loop
forStatement
- : FOR '('
- // C-style for loop
- ( expression ';' expression ';' expression
- // Range for loop
- | ID ASSIGN ID ',' ID
- )
- ')' statement ;
+ : FOR '(' init=expression ';' condition=expression ';' update=expression ')' body=statement ;
+
+// Range for loop
+simpleForStatement
+ : FOR '(' counter=ID ASSIGN first=expression ',' last=expression ')' body=statement ;
breakStatement : BREAK ;
continueStatement : CONTINUE ;
-returnStatement : RETURN expression? ;
+returnStatement : RETURN value=expression? ;
-switchStatement : SWITCH '(' expression ')' '{' (switchLabel ':' statements )+ '}' ;
+switchStatement : SWITCH '(' target=expression ')' '{' (labels+=switchLabel ':' bodies+=statements )+ '}' ;
switchLabel
- : CASE constantExpression # Case
+ : CASE constant=constantExpression # Case
| DEFAULT # Default
;
-expressionStatement : expression SEMI_COLON ;
+expressionStatement : expression ;
-expression
- : unaryOp expression # UnaryExpr
- | expression binaryOp expression # BinaryExpr
- | expression postUnaryOp # PostUnaryExpr
- | ID binaryAssignOp expression # AssignExpr
- | expression '?' expression ':' expression # TernaryExpr
- | functionCall # FunctionCallExpr
+emptyStatement: SEMI_COLON ;
+
+expression : assignmentExpression ;
+
+assignmentExpression
+ : conditionalExpression
+ | assignment
+ ;
+
+assignment
+ : target=ID assignmentOperator source=expression
+ ;
+
+assignmentOperator
+ : ASSIGN
+ | POWER_ASSIGN
+ | TIMES_ASSIGN
+ | DIVIDE_ASSIGN
+ | MODULO_ASSIGN
+ | PLUS_ASSIGN
+ | MINUS_ASSIGN
+ ;
+
+conditionalExpression
+ : conditionalOrExpression # CEFallthrough
+ | condition=conditionalOrExpression QUESTION_MARK
+ trueBranch=expression COLON falseBranch=conditionalExpression # TernaryExpr
+ ;
+
+conditionalOrExpression
+ : conditionalAndExpression # COFallthrough
+ | left=conditionalOrExpression OR_SC right=conditionalAndExpression # ConditionalOrExpr
+ ;
+
+conditionalAndExpression
+ : equalityExpression # CAFallthrough
+ | left=conditionalAndExpression AND_SC right=equalityExpression # ConditionalAndExpr
+ ;
+
+equalityExpression
+ : relationalExpression # EqFallthrough
+ | left=equalityExpression
+ op=
+ ( EQUAL
+ | NOT_EQUAL
+ | NEAR
+ ) right=relationalExpression # EqualityExpr
+ ;
+
+relationalExpression
+ : shiftExpression # ReFallthrough
+ | left=relationalExpression
+ op=
+ ( LESS_THAN
+ | GREATER_THAN
+ | LESS_THAN_OR_EQUAL
+ | GREATER_THAN_OR_EQUAL
+ ) right=shiftExpression # RelationalExpr
+ ;
+
+shiftExpression
+ : additiveExpression # ShFallthrough
+ | left=shiftExpression op=( LEFT_SHIFT | RIGHT_SHIFT ) right=additiveExpression # ShiftExpr
+ ;
+
+additiveExpression
+ : multiplicativeExpression # AdFallthrough
+ | left=additiveExpression op=( PLUS | MINUS ) right=multiplicativeExpression # AddExpr
+ ;
+
+multiplicativeExpression
+ : powerExpression # MuFallthrough
+ | left=multiplicativeExpression
+ op=
+ ( TIMES
+ | DIVIDE
+ | MODULO
+ ) right=powerExpression # MultiplicativeExpr
+ ;
+
+powerExpression
+ : unaryExpression # PwFallthrough
+ | left=powerExpression POWER right=unaryExpression # PowerExpr
+ ;
+
+unaryExpression
+ : op=( INCREMENT | DECREMENT ) target=ID # PreCrementExpr
+ | op=( PLUS | MINUS ) expr=unaryExpression # PlusMinusExpr
+ | postfixExpression # UaFallthrough
+ | COMPLEMENT expr=unaryExpression # ComplementExpr
+ | EXCLAMATION_MARK expr=unaryExpression # NotExpr
+ ;
+
+postfixExpression
+ : unprioritizedExpression # PoFallthrough
+ | target=ID op=( INCREMENT | DECREMENT) # PostCrementExpr
+ | expr=postfixExpression op=EXCLAMATION_MARK # PostfixExpr
+ ;
+
+unprioritizedExpression
+ : functionCall # FunctionCallExpr
| constantExpression # ConstantExpr
- | ID # IdExpr
+ | source=ID # IdExpr
| '(' expression ')' # WrappedExpr
;
constantExpression : NUMBER ;
-functionCall : ID '(' (expression ( ',' expression )*)? ')' ;
-
-unaryOp
- : MINUS
- | EXCLAMATION_MARK
- | COMPLEMENT
- | INCREMENT
- | DECREMENT
- ;
-
-postUnaryOp
- : INCREMENT
- | DECREMENT
- | EXCLAMATION_MARK
- ;
-
-binaryOp
- : POWER
- | TIMES
- | DIVIDE
- | MODULO
- | PLUS
- | MINUS
- | LEFT_SHIFT
- | RIGHT_SHIFT
- | LESS_THAN
- | GREATER_THAN
- | LESS_THAN_OR_EQUAL
- | GREATER_THAN_OR_EQUAL
- | EQUAL
- | NOT_EQUAL
- | NEAR
- | AND_SC
- | OR_SC
- ;
-
-binaryAssignOp
- : ASSIGN
- | PLUS_ASSIGN
- | MINUS_ASSIGN
- | TIMES_ASSIGN
- | DIVIDE_ASSIGN
- | MODULO_ASSIGN
- | POWER_ASSIGN
- ;
+functionCall : name=ID '(' (args+=expression ( ',' args+=expression )*)? ')' ;
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/EditSession.java b/worldedit-core/src/main/java/com/sk89q/worldedit/EditSession.java
index f195ed7de..c64e6ca72 100644
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/EditSession.java
+++ b/worldedit-core/src/main/java/com/sk89q/worldedit/EditSession.java
@@ -80,8 +80,8 @@ import com.sk89q.worldedit.history.changeset.BlockOptimizedHistory;
import com.sk89q.worldedit.history.changeset.ChangeSet;
import com.sk89q.worldedit.internal.expression.Expression;
import com.sk89q.worldedit.internal.expression.ExpressionException;
-import com.sk89q.worldedit.internal.expression.runtime.ExpressionTimeoutException;
-import com.sk89q.worldedit.internal.expression.runtime.RValue;
+import com.sk89q.worldedit.internal.expression.ExpressionTimeoutException;
+import com.sk89q.worldedit.internal.expression.LocalSlot.Variable;
import com.sk89q.worldedit.math.BlockVector2;
import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.math.MathUtils;
@@ -1989,8 +1989,10 @@ public class EditSession implements Extent, AutoCloseable {
final Expression expression = Expression.compile(expressionString, "x", "y", "z", "type", "data");
expression.optimize();
- final RValue typeVariable = expression.getVariable("type", false);
- final RValue dataVariable = expression.getVariable("data", false);
+ final Variable typeVariable = expression.getSlots().getVariable("type")
+ .orElseThrow(IllegalStateException::new);
+ final Variable dataVariable = expression.getSlots().getVariable("data")
+ .orElseThrow(IllegalStateException::new);
final WorldEditExpressionEnvironment environment = new WorldEditExpressionEnvironment(this, unit, zero);
expression.setEnvironment(environment);
@@ -2052,9 +2054,12 @@ public class EditSession implements Extent, AutoCloseable {
final Expression expression = Expression.compile(expressionString, "x", "y", "z");
expression.optimize();
- final RValue x = expression.getVariable("x", false);
- final RValue y = expression.getVariable("y", false);
- final RValue z = expression.getVariable("z", false);
+ final Variable x = expression.getSlots().getVariable("x")
+ .orElseThrow(IllegalStateException::new);
+ final Variable y = expression.getSlots().getVariable("y")
+ .orElseThrow(IllegalStateException::new);
+ final Variable z = expression.getSlots().getVariable("z")
+ .orElseThrow(IllegalStateException::new);
final WorldEditExpressionEnvironment environment = new WorldEditExpressionEnvironment(this, unit, zero);
expression.setEnvironment(environment);
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/function/mask/ExpressionMask.java b/worldedit-core/src/main/java/com/sk89q/worldedit/function/mask/ExpressionMask.java
index eba02d0a7..e9508bcee 100644
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/function/mask/ExpressionMask.java
+++ b/worldedit-core/src/main/java/com/sk89q/worldedit/function/mask/ExpressionMask.java
@@ -23,7 +23,7 @@ import static com.google.common.base.Preconditions.checkNotNull;
import com.sk89q.worldedit.internal.expression.Expression;
import com.sk89q.worldedit.internal.expression.ExpressionException;
-import com.sk89q.worldedit.internal.expression.runtime.EvaluationException;
+import com.sk89q.worldedit.internal.expression.EvaluationException;
import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.regions.shape.WorldEditExpressionEnvironment;
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/function/mask/ExpressionMask2D.java b/worldedit-core/src/main/java/com/sk89q/worldedit/function/mask/ExpressionMask2D.java
index a50c5e375..4ce7c1709 100644
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/function/mask/ExpressionMask2D.java
+++ b/worldedit-core/src/main/java/com/sk89q/worldedit/function/mask/ExpressionMask2D.java
@@ -23,7 +23,7 @@ import static com.google.common.base.Preconditions.checkNotNull;
import com.sk89q.worldedit.internal.expression.Expression;
import com.sk89q.worldedit.internal.expression.ExpressionException;
-import com.sk89q.worldedit.internal.expression.runtime.EvaluationException;
+import com.sk89q.worldedit.internal.expression.EvaluationException;
import com.sk89q.worldedit.math.BlockVector2;
import javax.annotation.Nullable;
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/runtime/BreakException.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/BreakException.java
similarity index 78%
rename from worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/runtime/BreakException.java
rename to worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/BreakException.java
index a3d384117..635c6d6ea 100644
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/runtime/BreakException.java
+++ b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/BreakException.java
@@ -17,18 +17,19 @@
* along with this program. If not, see .
*/
-package com.sk89q.worldedit.internal.expression.runtime;
+package com.sk89q.worldedit.internal.expression;
/**
* Thrown when a break or continue is encountered.
* Loop constructs catch this exception.
*/
-public class BreakException extends EvaluationException {
+public class BreakException extends RuntimeException {
- final boolean doContinue;
+ public final boolean doContinue;
public BreakException(boolean doContinue) {
- super(-1, doContinue ? "'continue' encountered outside a loop" : "'break' encountered outside a loop");
+ super(doContinue ? "'continue' encountered outside a loop" : "'break' encountered outside a loop",
+ null, true, false);
this.doContinue = doContinue;
}
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/EvaluatingVisitor.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/EvaluatingVisitor.java
new file mode 100644
index 000000000..bd282e9e1
--- /dev/null
+++ b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/EvaluatingVisitor.java
@@ -0,0 +1,625 @@
+/*
+ * WorldEdit, a Minecraft world manipulation toolkit
+ * Copyright (C) sk89q
+ * Copyright (C) WorldEdit team and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package com.sk89q.worldedit.internal.expression;
+
+import com.google.common.base.Throwables;
+import com.google.common.collect.SetMultimap;
+import com.sk89q.worldedit.antlr.ExpressionBaseVisitor;
+import com.sk89q.worldedit.antlr.ExpressionParser;
+import it.unimi.dsi.fastutil.doubles.Double2ObjectLinkedOpenHashMap;
+import it.unimi.dsi.fastutil.doubles.Double2ObjectMap;
+import it.unimi.dsi.fastutil.doubles.Double2ObjectMaps;
+import org.antlr.v4.runtime.ParserRuleContext;
+import org.antlr.v4.runtime.Token;
+import org.antlr.v4.runtime.tree.ParseTree;
+import org.antlr.v4.runtime.tree.RuleNode;
+import org.antlr.v4.runtime.tree.TerminalNode;
+
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodType;
+import java.util.List;
+import java.util.Optional;
+import java.util.function.DoubleBinaryOperator;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+
+import static com.sk89q.worldedit.antlr.ExpressionLexer.ASSIGN;
+import static com.sk89q.worldedit.antlr.ExpressionLexer.DIVIDE;
+import static com.sk89q.worldedit.antlr.ExpressionLexer.DIVIDE_ASSIGN;
+import static com.sk89q.worldedit.antlr.ExpressionLexer.EQUAL;
+import static com.sk89q.worldedit.antlr.ExpressionLexer.EXCLAMATION_MARK;
+import static com.sk89q.worldedit.antlr.ExpressionLexer.GREATER_THAN;
+import static com.sk89q.worldedit.antlr.ExpressionLexer.GREATER_THAN_OR_EQUAL;
+import static com.sk89q.worldedit.antlr.ExpressionLexer.INCREMENT;
+import static com.sk89q.worldedit.antlr.ExpressionLexer.LEFT_SHIFT;
+import static com.sk89q.worldedit.antlr.ExpressionLexer.LESS_THAN;
+import static com.sk89q.worldedit.antlr.ExpressionLexer.LESS_THAN_OR_EQUAL;
+import static com.sk89q.worldedit.antlr.ExpressionLexer.MINUS;
+import static com.sk89q.worldedit.antlr.ExpressionLexer.MINUS_ASSIGN;
+import static com.sk89q.worldedit.antlr.ExpressionLexer.MODULO;
+import static com.sk89q.worldedit.antlr.ExpressionLexer.MODULO_ASSIGN;
+import static com.sk89q.worldedit.antlr.ExpressionLexer.NEAR;
+import static com.sk89q.worldedit.antlr.ExpressionLexer.NOT_EQUAL;
+import static com.sk89q.worldedit.antlr.ExpressionLexer.PLUS;
+import static com.sk89q.worldedit.antlr.ExpressionLexer.PLUS_ASSIGN;
+import static com.sk89q.worldedit.antlr.ExpressionLexer.POWER_ASSIGN;
+import static com.sk89q.worldedit.antlr.ExpressionLexer.RIGHT_SHIFT;
+import static com.sk89q.worldedit.antlr.ExpressionLexer.TIMES;
+import static com.sk89q.worldedit.antlr.ExpressionLexer.TIMES_ASSIGN;
+import static com.sk89q.worldedit.internal.expression.ExpressionHelper.WRAPPED_CONSTANT;
+import static com.sk89q.worldedit.internal.expression.ExpressionHelper.check;
+import static com.sk89q.worldedit.internal.expression.ExpressionHelper.checkIterations;
+import static com.sk89q.worldedit.internal.expression.ExpressionHelper.checkTimeout;
+import static com.sk89q.worldedit.internal.expression.ExpressionHelper.evalException;
+import static com.sk89q.worldedit.internal.expression.ExpressionHelper.getArgumentHandleName;
+import static com.sk89q.worldedit.internal.expression.ExpressionHelper.resolveFunction;
+
+class EvaluatingVisitor extends ExpressionBaseVisitor {
+
+ private final SlotTable slots;
+ private final SetMultimap functions;
+
+ EvaluatingVisitor(SlotTable slots,
+ SetMultimap functions) {
+ this.slots = slots;
+ this.functions = functions;
+ }
+
+ private LocalSlot.Variable initVariable(String name, ParserRuleContext ctx) {
+ return slots.initVariable(name)
+ .orElseThrow(() -> evalException(ctx, "Cannot overwrite non-variable '" + name + "'"));
+ }
+
+ private Supplier varNotInitException(String name, ParserRuleContext ctx) {
+ return () -> evalException(ctx, "'" + name + "' is not initialized yet");
+ }
+
+ private LocalSlot.Variable getVariable(String name, ParserRuleContext ctx) {
+ LocalSlot slot = slots.getSlot(name)
+ .orElseThrow(varNotInitException(name, ctx));
+ check(slot instanceof LocalSlot.Variable, ctx, "'" + name + "' is not a variable");
+ return (LocalSlot.Variable) slot;
+ }
+
+ private double getSlotValue(String name, ParserRuleContext ctx) {
+ return slots.getSlotValue(name)
+ .orElseThrow(varNotInitException(name, ctx));
+ }
+
+ private Token extractToken(ParserRuleContext ctx) {
+ List children = ctx.children.stream()
+ .filter(TerminalNode.class::isInstance)
+ .map(TerminalNode.class::cast)
+ .collect(Collectors.toList());
+ check(children.size() == 1, ctx, "Expected exactly one token, got " + children.size());
+ return children.get(0).getSymbol();
+ }
+
+ private Double evaluate(ParserRuleContext ctx) {
+ return ctx.accept(this);
+ }
+
+ private double evaluateForValue(ParserRuleContext ctx) {
+ Double result = evaluate(ctx);
+ check(result != null, ctx, "Invalid expression for a value");
+ return result;
+ }
+
+ private boolean evaluateBoolean(ParserRuleContext boolExpression) {
+ Double bool = evaluate(boolExpression);
+ check(bool != null, boolExpression, "Invalid expression for boolean");
+ return doubleToBool(bool);
+ }
+
+ private boolean doubleToBool(double bool) {
+ return bool > 0;
+ }
+
+ private double boolToDouble(boolean bool) {
+ return bool ? 1 : 0;
+ }
+
+ private Double evaluateConditional(ParserRuleContext condition,
+ ParserRuleContext trueBranch,
+ ParserRuleContext falseBranch) {
+ ParserRuleContext ctx = evaluateBoolean(condition) ? trueBranch : falseBranch;
+ return ctx == null ? null : evaluate(ctx);
+ }
+
+ @Override
+ public Double visitIfStatement(ExpressionParser.IfStatementContext ctx) {
+ return evaluateConditional(ctx.condition, ctx.trueBranch, ctx.falseBranch);
+ }
+
+ @Override
+ public Double visitTernaryExpr(ExpressionParser.TernaryExprContext ctx) {
+ return evaluateConditional(ctx.condition, ctx.trueBranch, ctx.falseBranch);
+ }
+
+ @Override
+ public Double visitWhileStatement(ExpressionParser.WhileStatementContext ctx) {
+ Double result = defaultResult();
+ int iterations = 0;
+ while (evaluateBoolean(ctx.condition)) {
+ checkIterations(iterations, ctx.body);
+ checkTimeout();
+ iterations++;
+ try {
+ result = evaluate(ctx.body);
+ } catch (BreakException ex) {
+ if (!ex.doContinue) {
+ break;
+ }
+ }
+ }
+ return result;
+ }
+
+ @Override
+ public Double visitDoStatement(ExpressionParser.DoStatementContext ctx) {
+ Double result = defaultResult();
+ int iterations = 0;
+ do {
+ checkIterations(iterations, ctx.body);
+ checkTimeout();
+ iterations++;
+ try {
+ result = evaluate(ctx.body);
+ } catch (BreakException ex) {
+ if (!ex.doContinue) {
+ break;
+ }
+ }
+ } while (evaluateBoolean(ctx.condition));
+ return result;
+ }
+
+ @Override
+ public Double visitForStatement(ExpressionParser.ForStatementContext ctx) {
+ Double result = defaultResult();
+ int iterations = 0;
+ evaluate(ctx.init);
+ while (evaluateBoolean(ctx.condition)) {
+ checkIterations(iterations, ctx.body);
+ checkTimeout();
+ iterations++;
+ try {
+ result = evaluate(ctx.body);
+ } catch (BreakException ex) {
+ if (!ex.doContinue) {
+ break;
+ }
+ }
+ evaluate(ctx.update);
+ }
+ return result;
+ }
+
+ @Override
+ public Double visitSimpleForStatement(ExpressionParser.SimpleForStatementContext ctx) {
+ Double result = defaultResult();
+ int iterations = 0;
+ double first = evaluateForValue(ctx.first);
+ double last = evaluateForValue(ctx.last);
+ String counter = ctx.counter.getText();
+ LocalSlot.Variable variable = initVariable(counter, ctx);
+ for (double i = first; i <= last; i++) {
+ checkIterations(iterations, ctx.body);
+ checkTimeout();
+ iterations++;
+ variable.setValue(i);
+ try {
+ result = evaluate(ctx.body);
+ } catch (BreakException ex) {
+ if (!ex.doContinue) {
+ break;
+ }
+ }
+ }
+ return result;
+ }
+
+ @Override
+ public Double visitBreakStatement(ExpressionParser.BreakStatementContext ctx) {
+ throw new BreakException(false);
+ }
+
+ @Override
+ public Double visitContinueStatement(ExpressionParser.ContinueStatementContext ctx) {
+ throw new BreakException(true);
+ }
+
+ @Override
+ public Double visitReturnStatement(ExpressionParser.ReturnStatementContext ctx) {
+ if (ctx.value != null) {
+ return evaluate(ctx.value);
+ }
+ return null;
+ }
+
+ @Override
+ public Double visitSwitchStatement(ExpressionParser.SwitchStatementContext ctx) {
+ Double2ObjectMap cases = new Double2ObjectLinkedOpenHashMap<>(ctx.labels.size());
+ ParserRuleContext defaultCase = null;
+ for (int i = 0; i < ctx.labels.size(); i++) {
+ ExpressionParser.SwitchLabelContext label = ctx.labels.get(i);
+ ExpressionParser.StatementsContext body = ctx.bodies.get(i);
+ if (label instanceof ExpressionParser.CaseContext) {
+ ExpressionParser.CaseContext caseContext = (ExpressionParser.CaseContext) label;
+ double key = evaluateForValue(caseContext.constant);
+ check(!cases.containsKey(key), body, "Duplicate cases detected.");
+ cases.put(key, body);
+ } else {
+ check(defaultCase == null, body, "Duplicate default cases detected.");
+ defaultCase = body;
+ }
+ }
+ double value = evaluateForValue(ctx.target);
+ boolean matched = false;
+ Double evaluated = null;
+ boolean falling = false;
+ for (Double2ObjectMap.Entry entry : Double2ObjectMaps.fastIterable(cases)) {
+ if (falling || entry.getDoubleKey() == value) {
+ matched = true;
+ try {
+ evaluated = evaluate(entry.getValue());
+ falling = true;
+ } catch (BreakException brk) {
+ check(!brk.doContinue, entry.getValue(), "Cannot continue in a switch");
+ falling = false;
+ break;
+ }
+ }
+ }
+ // This if is like the one in the loop, default's "case" is `!matched` & present
+ if ((falling || !matched) && defaultCase != null) {
+ try {
+ evaluated = evaluate(defaultCase);
+ } catch (BreakException brk) {
+ check(!brk.doContinue, defaultCase, "Cannot continue in a switch");
+ }
+ }
+ return evaluated;
+ }
+
+ @Override
+ public Double visitExpressionStatement(ExpressionParser.ExpressionStatementContext ctx) {
+ return evaluate(ctx.expression());
+ }
+
+ @Override
+ public Double visitPostCrementExpr(ExpressionParser.PostCrementExprContext ctx) {
+ String target = ctx.target.getText();
+ LocalSlot.Variable variable = getVariable(target, ctx);
+ double value = variable.getValue();
+ if (ctx.op.getType() == INCREMENT) {
+ value++;
+ } else {
+ value--;
+ }
+ variable.setValue(value);
+ return value;
+ }
+
+ @Override
+ public Double visitPreCrementExpr(ExpressionParser.PreCrementExprContext ctx) {
+ String target = ctx.target.getText();
+ LocalSlot.Variable variable = getVariable(target, ctx);
+ double value = variable.getValue();
+ double result = value;
+ if (ctx.op.getType() == INCREMENT) {
+ value++;
+ } else {
+ value--;
+ }
+ variable.setValue(value);
+ return result;
+ }
+
+ @Override
+ public Double visitPlusMinusExpr(ExpressionParser.PlusMinusExprContext ctx) {
+ double value = evaluateForValue(ctx.expr);
+ switch (ctx.op.getType()) {
+ case PLUS:
+ return +value;
+ case MINUS:
+ return -value;
+ }
+ throw evalException(ctx, "Invalid text for plus/minus expr: " + ctx.op.getText());
+ }
+
+ @Override
+ public Double visitNotExpr(ExpressionParser.NotExprContext ctx) {
+ return boolToDouble(!evaluateBoolean(ctx.expr));
+ }
+
+ @Override
+ public Double visitComplementExpr(ExpressionParser.ComplementExprContext ctx) {
+ return (double) ~(long) evaluateForValue(ctx.expr);
+ }
+
+ @Override
+ public Double visitConditionalAndExpr(ExpressionParser.ConditionalAndExprContext ctx) {
+ if (!evaluateBoolean(ctx.left)) {
+ return boolToDouble(false);
+ }
+ return evaluateForValue(ctx.right);
+ }
+
+ @Override
+ public Double visitConditionalOrExpr(ExpressionParser.ConditionalOrExprContext ctx) {
+ double left = evaluateForValue(ctx.left);
+ if (doubleToBool(left)) {
+ return left;
+ }
+ return evaluateForValue(ctx.right);
+ }
+
+ private double evaluateBinary(ParserRuleContext left,
+ ParserRuleContext right,
+ DoubleBinaryOperator op) {
+ return op.applyAsDouble(evaluateForValue(left), evaluateForValue(right));
+ }
+
+ @Override
+ public Double visitPowerExpr(ExpressionParser.PowerExprContext ctx) {
+ return evaluateBinary(ctx.left, ctx.right, Math::pow);
+ }
+
+ @Override
+ public Double visitMultiplicativeExpr(ExpressionParser.MultiplicativeExprContext ctx) {
+ return evaluateBinary(ctx.left, ctx.right, (l, r) -> {
+ switch (ctx.op.getType()) {
+ case TIMES:
+ return l * r;
+ case DIVIDE:
+ return l / r;
+ case MODULO:
+ return l % r;
+ }
+ throw evalException(ctx, "Invalid text for multiplicative expr: " + ctx.op.getText());
+ });
+ }
+
+ @Override
+ public Double visitAddExpr(ExpressionParser.AddExprContext ctx) {
+ return evaluateBinary(ctx.left, ctx.right, (l, r) -> {
+ switch (ctx.op.getType()) {
+ case PLUS:
+ return l + r;
+ case MINUS:
+ return l - r;
+ }
+ throw evalException(ctx, "Invalid text for additive expr: " + ctx.op.getText());
+ });
+ }
+
+ @Override
+ public Double visitShiftExpr(ExpressionParser.ShiftExprContext ctx) {
+ return evaluateBinary(ctx.left, ctx.right, (l, r) -> {
+ switch (ctx.op.getType()) {
+ case LEFT_SHIFT:
+ return (double) ((long) l << (long) r);
+ case RIGHT_SHIFT:
+ return (double) ((long) l >> (long) r);
+ }
+ throw evalException(ctx, "Invalid text for shift expr: " + ctx.op.getText());
+ });
+ }
+
+ @Override
+ public Double visitRelationalExpr(ExpressionParser.RelationalExprContext ctx) {
+ return evaluateBinary(ctx.left, ctx.right, (l, r) -> {
+ switch (ctx.op.getType()) {
+ case LESS_THAN:
+ return boolToDouble(l < r);
+ case LESS_THAN_OR_EQUAL:
+ return boolToDouble(l <= r);
+ case GREATER_THAN:
+ return boolToDouble(l > r);
+ case GREATER_THAN_OR_EQUAL:
+ return boolToDouble(l >= r);
+ }
+ throw evalException(ctx, "Invalid text for relational expr: " + ctx.op.getText());
+ });
+ }
+
+ @Override
+ public Double visitEqualityExpr(ExpressionParser.EqualityExprContext ctx) {
+ return evaluateBinary(ctx.left, ctx.right, (l, r) -> {
+ switch (ctx.op.getType()) {
+ case EQUAL:
+ return boolToDouble(l == r);
+ case NOT_EQUAL:
+ return boolToDouble(l != r);
+ case NEAR:
+ return boolToDouble(almostEqual2sComplement(l, r, 450359963L));
+ case GREATER_THAN_OR_EQUAL:
+ return boolToDouble(l >= r);
+ }
+ throw evalException(ctx, "Invalid text for equality expr: " + ctx.op.getText());
+ });
+ }
+
+ // Usable AlmostEqual function, based on http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm
+ private static boolean almostEqual2sComplement(double a, double b, long maxUlps) {
+ // Make sure maxUlps is non-negative and small enough that the
+ // default NAN won't compare as equal to anything.
+ //assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024); // this is for floats, not doubles
+
+ long aLong = Double.doubleToRawLongBits(a);
+ // Make aLong lexicographically ordered as a twos-complement long
+ if (aLong < 0) aLong = 0x8000000000000000L - aLong;
+
+ long bLong = Double.doubleToRawLongBits(b);
+ // Make bLong lexicographically ordered as a twos-complement long
+ if (bLong < 0) bLong = 0x8000000000000000L - bLong;
+
+ final long longDiff = Math.abs(aLong - bLong);
+ return longDiff <= maxUlps;
+ }
+
+ @Override
+ public Double visitPostfixExpr(ExpressionParser.PostfixExprContext ctx) {
+ double value = evaluateForValue(ctx.expr);
+ if (ctx.op.getType() == EXCLAMATION_MARK) {
+ return factorial(value);
+ }
+ throw evalException(ctx,
+ "Invalid text for post-unary expr: " + ctx.op.getText());
+ }
+
+ private static final double[] factorials = new double[171];
+
+ static {
+ factorials[0] = 1;
+ for (int i = 1; i < factorials.length; ++i) {
+ factorials[i] = factorials[i - 1] * i;
+ }
+ }
+
+ private static double factorial(double x) throws EvaluationException {
+ final int n = (int) x;
+
+ if (n < 0) {
+ return 0;
+ }
+
+ if (n >= factorials.length) {
+ return Double.POSITIVE_INFINITY;
+ }
+
+ return factorials[n];
+ }
+
+ @Override
+ public Double visitAssignment(ExpressionParser.AssignmentContext ctx) {
+ int type = extractToken(ctx.assignmentOperator()).getType();
+ String target = ctx.target.getText();
+ double value;
+ double arg = evaluateForValue(ctx.expression());
+ LocalSlot.Variable variable;
+ if (type == ASSIGN) {
+ variable = initVariable(target, ctx);
+ value = arg;
+ } else {
+ variable = getVariable(target, ctx);
+ value = variable.getValue();
+ switch (type) {
+ case POWER_ASSIGN:
+ value = Math.pow(value, arg);
+ break;
+ case TIMES_ASSIGN:
+ value *= arg;
+ break;
+ case DIVIDE_ASSIGN:
+ value /= arg;
+ break;
+ case MODULO_ASSIGN:
+ value %= arg;
+ break;
+ case PLUS_ASSIGN:
+ value += arg;
+ break;
+ case MINUS_ASSIGN:
+ value -= arg;
+ break;
+ default:
+ throw evalException(ctx, "Invalid text for assign expr: " +
+ ctx.assignmentOperator().getText());
+ }
+ }
+ variable.setValue(value);
+ return value;
+ }
+
+ @Override
+ public Double visitFunctionCall(ExpressionParser.FunctionCallContext ctx) {
+ MethodHandle handle = resolveFunction(functions, ctx);
+ String fnName = ctx.name.getText();
+ Object[] arguments = new Object[ctx.args.size()];
+ for (int i = 0; i < arguments.length; i++) {
+ ExpressionParser.ExpressionContext arg = ctx.args.get(i);
+ Object transformed = getArgument(fnName, handle.type(), i, arg);
+ arguments[i] = transformed;
+ }
+ try {
+ // Some methods return other Numbers
+ Number number = (Number) handle.invokeWithArguments(arguments);
+ return number == null ? null : number.doubleValue();
+ } catch (Throwable throwable) {
+ Throwables.throwIfUnchecked(throwable);
+ throw new RuntimeException(throwable);
+ }
+ }
+
+ private Object getArgument(String fnName, MethodType type, int i, ParserRuleContext arg) {
+ // Pass variable handle in for modification?
+ String handleName = getArgumentHandleName(fnName, type, i, arg);
+ if (handleName == null) {
+ return evaluateForValue(arg);
+ }
+ if (handleName.equals(WRAPPED_CONSTANT)) {
+ return new LocalSlot.Constant(evaluateForValue(arg));
+ }
+ return getVariable(handleName, arg);
+ }
+
+ @Override
+ public Double visitConstantExpression(ExpressionParser.ConstantExpressionContext ctx) {
+ try {
+ return Double.parseDouble(ctx.getText());
+ } catch (NumberFormatException e) {
+ // Rare, but might happen, e.g. if too many digits
+ throw evalException(ctx, "Invalid constant: " + e.getMessage());
+ }
+ }
+
+ @Override
+ public Double visitIdExpr(ExpressionParser.IdExprContext ctx) {
+ String source = ctx.source.getText();
+ return getSlotValue(source, ctx);
+ }
+
+ @Override
+ public Double visitChildren(RuleNode node) {
+ Double result = defaultResult();
+ int n = node.getChildCount();
+ for (int i = 0; i < n; i++) {
+ ParseTree c = node.getChild(i);
+ if (c instanceof TerminalNode && ((TerminalNode) c).getSymbol().getType() == Token.EOF) {
+ break;
+ }
+
+ Double childResult = c.accept(this);
+ if (c instanceof ExpressionParser.ReturnStatementContext) {
+ return childResult;
+ }
+ result = aggregateResult(result, childResult);
+ }
+
+ return result;
+ }
+
+ @Override
+ protected Double aggregateResult(Double aggregate, Double nextResult) {
+ return Optional.ofNullable(nextResult).orElse(aggregate);
+ }
+}
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/runtime/EvaluationException.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/EvaluationException.java
similarity index 96%
rename from worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/runtime/EvaluationException.java
rename to worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/EvaluationException.java
index fc34a0ffa..c9042d7ca 100644
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/runtime/EvaluationException.java
+++ b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/EvaluationException.java
@@ -17,7 +17,7 @@
* along with this program. If not, see .
*/
-package com.sk89q.worldedit.internal.expression.runtime;
+package com.sk89q.worldedit.internal.expression;
import com.sk89q.worldedit.internal.expression.ExpressionException;
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/Expression.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/Expression.java
index 0e82a4750..9bca48e12 100644
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/Expression.java
+++ b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/Expression.java
@@ -19,24 +19,23 @@
package com.sk89q.worldedit.internal.expression;
+import com.google.common.base.Throwables;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.SetMultimap;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.sk89q.worldedit.WorldEdit;
-import com.sk89q.worldedit.internal.expression.lexer.Lexer;
-import com.sk89q.worldedit.internal.expression.lexer.tokens.Token;
-import com.sk89q.worldedit.internal.expression.parser.Parser;
-import com.sk89q.worldedit.internal.expression.runtime.Constant;
-import com.sk89q.worldedit.internal.expression.runtime.EvaluationException;
-import com.sk89q.worldedit.internal.expression.runtime.ExpressionEnvironment;
-import com.sk89q.worldedit.internal.expression.runtime.ExpressionTimeoutException;
-import com.sk89q.worldedit.internal.expression.runtime.Functions;
-import com.sk89q.worldedit.internal.expression.runtime.RValue;
-import com.sk89q.worldedit.internal.expression.runtime.ReturnException;
-import com.sk89q.worldedit.internal.expression.runtime.Variable;
+import com.sk89q.worldedit.antlr.ExpressionLexer;
+import com.sk89q.worldedit.antlr.ExpressionParser;
import com.sk89q.worldedit.session.request.Request;
+import org.antlr.v4.runtime.CharStream;
+import org.antlr.v4.runtime.CharStreams;
+import org.antlr.v4.runtime.CommonTokenStream;
+import org.antlr.v4.runtime.misc.ParseCancellationException;
+import org.antlr.v4.runtime.tree.ParseTreeWalker;
-import java.util.HashMap;
+import java.lang.invoke.MethodHandle;
import java.util.List;
-import java.util.Map;
+import java.util.Objects;
import java.util.Stack;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
@@ -68,13 +67,8 @@ import java.util.concurrent.TimeoutException;
* If you wish to run the equation multiple times, you can then optimize it,
* by calling {@link #optimize()}. You can then run the equation as many times
* as you want by calling {@link #evaluate(double...)}. You do not need to
- * pass values for all variables specified while compiling.
- * To query variables after evaluation, you can use
- * {@link #getVariable(String, boolean)}. To get a value out of these, use
- * {@link Variable#getValue()}.
- *
- *
Variables are also supported and can be set either by passing values
- * to {@link #evaluate(double...)}.
+ * pass values for all slots specified while compiling.
+ * To query slots after evaluation, you can use the {@linkplain #getSlots() slot table}.
*/
public class Expression {
@@ -85,10 +79,10 @@ public class Expression {
.setNameFormat("worldedit-expression-eval-%d")
.build());
- private final Map variables = new HashMap<>();
- private final String[] variableNames;
- private RValue root;
- private final Functions functions = new Functions();
+ private final SlotTable slots = new SlotTable();
+ private final List providedSlots;
+ private ExpressionParser.AllStatementsContext root;
+ private final SetMultimap functions = Functions.getFunctionMap();
private ExpressionEnvironment environment;
public static Expression compile(String expression, String... variableNames) throws ExpressionException {
@@ -96,25 +90,33 @@ public class Expression {
}
private Expression(String expression, String... variableNames) throws ExpressionException {
- this(Lexer.tokenize(expression), variableNames);
- }
-
- private Expression(List tokens, String... variableNames) throws ExpressionException {
- this.variableNames = variableNames;
-
- variables.put("e", new Constant(-1, Math.E));
- variables.put("pi", new Constant(-1, Math.PI));
- variables.put("true", new Constant(-1, 1));
- variables.put("false", new Constant(-1, 0));
+ slots.putSlot("e", new LocalSlot.Constant(Math.E));
+ slots.putSlot("pi", new LocalSlot.Constant(Math.PI));
+ slots.putSlot("true", new LocalSlot.Constant(1));
+ slots.putSlot("false", new LocalSlot.Constant(0));
for (String variableName : variableNames) {
- if (variables.containsKey(variableName)) {
- throw new ExpressionException(-1, "Tried to overwrite identifier '" + variableName + "'");
- }
- variables.put(variableName, new Variable(0));
+ slots.initVariable(variableName)
+ .orElseThrow(() -> new ExpressionException(-1,
+ "Tried to overwrite identifier '" + variableName + "'"));
}
+ this.providedSlots = ImmutableList.copyOf(variableNames);
- root = Parser.parse(tokens, this);
+ CharStream cs = CharStreams.fromString(expression, "");
+ ExpressionLexer lexer = new ExpressionLexer(cs);
+ lexer.removeErrorListeners();
+ lexer.addErrorListener(new LexerErrorListener());
+ CommonTokenStream tokens = new CommonTokenStream(lexer);
+ ExpressionParser parser = new ExpressionParser(tokens);
+ parser.removeErrorListeners();
+ parser.addErrorListener(new ParserErrorListener());
+ try {
+ root = parser.allStatements();
+ Objects.requireNonNull(root, "Unable to parse root, but no exceptions?");
+ } catch (ParseCancellationException e) {
+ throw new ParserException(parser.getState(), e);
+ }
+ ParseTreeWalker.DEFAULT.walk(new ExpressionValidator(slots.keySet(), functions), root);
}
public double evaluate(double... values) throws EvaluationException {
@@ -123,23 +125,19 @@ public class Expression {
public double evaluate(double[] values, int timeout) throws EvaluationException {
for (int i = 0; i < values.length; ++i) {
- final String variableName = variableNames[i];
- final RValue invokable = variables.get(variableName);
- if (!(invokable instanceof Variable)) {
- throw new EvaluationException(invokable.getPosition(), "Tried to assign constant " + variableName + ".");
- }
+ String slotName = providedSlots.get(i);
+ LocalSlot.Variable slot = slots.getVariable(slotName)
+ .orElseThrow(() -> new EvaluationException(-1,
+ "Tried to assign to non-variable " + slotName + "."));
- ((Variable) invokable).value = values[i];
+ slot.setValue(values[i]);
}
- try {
- if (timeout < 0) {
- return evaluateRoot();
- }
- return evaluateRootTimed(timeout);
- } catch (ReturnException e) {
- return e.getValue();
- } // other evaluation exceptions are thrown out of this method
+ // evaluation exceptions are thrown out of this method
+ if (timeout < 0) {
+ return evaluateRoot();
+ }
+ return evaluateRootTimed(timeout);
}
private double evaluateRootTimed(int timeout) throws EvaluationException {
@@ -165,12 +163,8 @@ public class Expression {
throw new ExpressionTimeoutException("Calculations exceeded time limit.");
} catch (ExecutionException e) {
Throwable cause = e.getCause();
- if (cause instanceof EvaluationException) {
- throw (EvaluationException) cause;
- }
- if (cause instanceof RuntimeException) {
- throw (RuntimeException) cause;
- }
+ Throwables.throwIfInstanceOf(cause, EvaluationException.class);
+ Throwables.throwIfUnchecked(cause);
throw new RuntimeException(cause);
}
}
@@ -178,14 +172,14 @@ public class Expression {
private Double evaluateRoot() throws EvaluationException {
pushInstance();
try {
- return root.getValue();
+ return root.accept(new EvaluatingVisitor(slots, functions));
} finally {
popInstance();
}
}
- public void optimize() throws EvaluationException {
- root = root.optimize();
+ public void optimize() {
+ // TODO optimizing
}
@Override
@@ -193,13 +187,8 @@ public class Expression {
return root.toString();
}
- public RValue getVariable(String name, boolean create) {
- RValue variable = variables.get(name);
- if (variable == null && create) {
- variables.put(name, variable = new Variable(0));
- }
-
- return variable;
+ public SlotTable getSlots() {
+ return slots;
}
public static Expression getInstance() {
@@ -225,10 +214,6 @@ public class Expression {
}
}
- public Functions getFunctions() {
- return functions;
- }
-
public ExpressionEnvironment getEnvironment() {
return environment;
}
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/runtime/ExpressionEnvironment.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/ExpressionEnvironment.java
similarity index 95%
rename from worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/runtime/ExpressionEnvironment.java
rename to worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/ExpressionEnvironment.java
index 1a9a57d4a..9fa11e923 100644
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/runtime/ExpressionEnvironment.java
+++ b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/ExpressionEnvironment.java
@@ -17,7 +17,7 @@
* along with this program. If not, see .
*/
-package com.sk89q.worldedit.internal.expression.runtime;
+package com.sk89q.worldedit.internal.expression;
/**
* Represents a way to access blocks in a world. Has to accept non-rounded coordinates.
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/ExpressionException.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/ExpressionException.java
index 2320ad99c..41c6f8863 100644
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/ExpressionException.java
+++ b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/ExpressionException.java
@@ -23,7 +23,7 @@ package com.sk89q.worldedit.internal.expression;
* Thrown when there's a problem during any stage of the expression
* compilation or evaluation.
*/
-public class ExpressionException extends Exception {
+public class ExpressionException extends RuntimeException {
private final int position;
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/ExpressionHelper.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/ExpressionHelper.java
new file mode 100644
index 000000000..f1689f78e
--- /dev/null
+++ b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/ExpressionHelper.java
@@ -0,0 +1,149 @@
+/*
+ * WorldEdit, a Minecraft world manipulation toolkit
+ * Copyright (C) sk89q
+ * Copyright (C) WorldEdit team and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package com.sk89q.worldedit.internal.expression;
+
+import com.google.common.collect.SetMultimap;
+import com.sk89q.worldedit.antlr.ExpressionParser;
+import org.antlr.v4.runtime.ParserRuleContext;
+import org.antlr.v4.runtime.Token;
+
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodType;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static com.sk89q.worldedit.antlr.ExpressionLexer.ID;
+
+class ExpressionHelper {
+
+ static void check(boolean condition, ParserRuleContext ctx, String message) {
+ if (!condition) {
+ throw evalException(ctx, message);
+ }
+ }
+
+ static EvaluationException evalException(ParserRuleContext ctx, String message) {
+ return new EvaluationException(
+ ctx.getStart().getCharPositionInLine(),
+ message
+ );
+ }
+
+ static void checkIterations(int iterations, ParserRuleContext ctx) {
+ check(iterations <= 256, ctx, "Loop exceeded 256 iterations");
+ }
+
+ static void checkTimeout() {
+ if (Thread.interrupted()) {
+ throw new ExpressionTimeoutException("Calculations exceeded time limit.");
+ }
+ }
+
+ static MethodHandle resolveFunction(SetMultimap functions,
+ ExpressionParser.FunctionCallContext ctx) {
+ String fnName = ctx.name.getText();
+ Set matchingFns = functions.get(fnName);
+ check(!matchingFns.isEmpty(), ctx, "Unknown function '" + fnName + "'");
+ for (MethodHandle function : matchingFns) {
+ MethodType type = function.type();
+ // Validate argc if not varargs
+ if (!function.isVarargsCollector() && type.parameterCount() != ctx.args.size()) {
+ // skip non-matching function
+ continue;
+ }
+ for (int i = 0; i < ctx.args.size(); i++) {
+ ExpressionParser.ExpressionContext arg = ctx.args.get(i);
+ getArgumentHandleName(fnName, type, i, arg);
+ }
+ // good match!
+ return function;
+ }
+ // We matched no function, fail with appropriate message.
+ String possibleCounts = matchingFns.stream()
+ .map(mh -> mh.isVarargsCollector()
+ ? (mh.type().parameterCount() - 1) + "+"
+ : String.valueOf(mh.type().parameterCount()))
+ .collect(Collectors.joining("/"));
+ throw evalException(ctx, "Incorrect number of arguments for function '" + fnName + "', " +
+ "expected " + possibleCounts + ", " +
+ "got " + ctx.args.size());
+ }
+
+ // Special argument handle names
+ /**
+ * The argument should be wrapped in a {@link LocalSlot.Constant} before being passed.
+ */
+ static final String WRAPPED_CONSTANT = "";
+
+ /**
+ * If this argument needs a handle, returns the name of the handle needed. Otherwise, returns
+ * {@code null}. If {@code arg} isn't a valid handle reference, throws.
+ */
+ static String getArgumentHandleName(String fnName, MethodType type, int i,
+ ParserRuleContext arg) {
+ // Pass variable handle in for modification?
+ Class> pType = type.parameterType(i);
+ Optional id = tryResolveId(arg);
+ if (pType == LocalSlot.Variable.class) {
+ // MUST be an id
+ check(id.isPresent(), arg,
+ "Function '" + fnName + "' requires a variable in parameter " + i);
+ return id.get();
+ } else if (pType == LocalSlot.class) {
+ return id.orElse(WRAPPED_CONSTANT);
+ }
+ return null;
+ }
+
+ private static Optional tryResolveId(ParserRuleContext arg) {
+ Optional wrappedExprContext =
+ tryAs(arg, ExpressionParser.WrappedExprContext.class);
+ if (wrappedExprContext.isPresent()) {
+ return tryResolveId(wrappedExprContext.get().expression());
+ }
+ Token token = arg.start;
+ int tokenType = token.getType();
+ boolean isId = arg.start == arg.stop && tokenType == ID;
+ return isId ? Optional.of(token.getText()) : Optional.empty();
+ }
+
+ private static Optional tryAs(
+ ParserRuleContext ctx,
+ Class rule
+ ) {
+ if (rule.isInstance(ctx)) {
+ return Optional.of(rule.cast(ctx));
+ }
+ if (ctx.children.size() != 1) {
+ return Optional.empty();
+ }
+ List ctxs = ctx.getRuleContexts(ParserRuleContext.class);
+ if (ctxs.size() != 1) {
+ return Optional.empty();
+ }
+ return tryAs(ctxs.get(0), rule);
+ }
+
+ private ExpressionHelper() {
+ }
+
+}
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/runtime/ExpressionTimeoutException.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/ExpressionTimeoutException.java
similarity index 94%
rename from worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/runtime/ExpressionTimeoutException.java
rename to worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/ExpressionTimeoutException.java
index ce7d55140..e0395cd7a 100644
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/runtime/ExpressionTimeoutException.java
+++ b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/ExpressionTimeoutException.java
@@ -17,7 +17,7 @@
* along with this program. If not, see .
*/
-package com.sk89q.worldedit.internal.expression.runtime;
+package com.sk89q.worldedit.internal.expression;
/**
* Thrown when an evaluation exceeds the timeout time.
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/ExpressionValidator.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/ExpressionValidator.java
new file mode 100644
index 000000000..c823df306
--- /dev/null
+++ b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/ExpressionValidator.java
@@ -0,0 +1,70 @@
+/*
+ * WorldEdit, a Minecraft world manipulation toolkit
+ * Copyright (C) sk89q
+ * Copyright (C) WorldEdit team and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package com.sk89q.worldedit.internal.expression;
+
+import com.google.common.collect.SetMultimap;
+import com.sk89q.worldedit.antlr.ExpressionBaseListener;
+import com.sk89q.worldedit.antlr.ExpressionParser;
+
+import java.lang.invoke.MethodHandle;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+import static com.sk89q.worldedit.internal.expression.ExpressionHelper.check;
+import static com.sk89q.worldedit.internal.expression.ExpressionHelper.resolveFunction;
+
+class ExpressionValidator extends ExpressionBaseListener {
+
+ private final Set variableNames = new HashSet<>();
+ private final SetMultimap functions;
+
+ ExpressionValidator(Collection variableNames,
+ SetMultimap functions) {
+ this.variableNames.addAll(variableNames);
+ this.functions = functions;
+ }
+
+ private void bindVariable(String name) {
+ variableNames.add(name);
+ }
+
+ @Override
+ public void enterAssignment(ExpressionParser.AssignmentContext ctx) {
+ bindVariable(ctx.target.getText());
+ }
+
+ @Override
+ public void enterSimpleForStatement(ExpressionParser.SimpleForStatementContext ctx) {
+ bindVariable(ctx.counter.getText());
+ }
+
+ @Override
+ public void enterIdExpr(ExpressionParser.IdExprContext ctx) {
+ String text = ctx.source.getText();
+ check(variableNames.contains(text), ctx,
+ "Variable '" + text + "' is not bound");
+ }
+
+ @Override
+ public void enterFunctionCall(ExpressionParser.FunctionCallContext ctx) {
+ resolveFunction(functions, ctx);
+ }
+}
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/Functions.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/Functions.java
new file mode 100644
index 000000000..e97f96ade
--- /dev/null
+++ b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/Functions.java
@@ -0,0 +1,337 @@
+/*
+ * WorldEdit, a Minecraft world manipulation toolkit
+ * Copyright (C) sk89q
+ * Copyright (C) WorldEdit team and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package com.sk89q.worldedit.internal.expression;
+
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSetMultimap;
+import com.google.common.collect.SetMultimap;
+import com.google.common.primitives.Doubles;
+import com.sk89q.worldedit.internal.expression.LocalSlot.Variable;
+import com.sk89q.worldedit.math.Vector3;
+import com.sk89q.worldedit.math.noise.PerlinNoise;
+import com.sk89q.worldedit.math.noise.RidgedMultiFractalNoise;
+import com.sk89q.worldedit.math.noise.VoronoiNoise;
+import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
+import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
+
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.util.concurrent.ThreadLocalRandom;
+
+import static java.lang.invoke.MethodType.methodType;
+
+/**
+ * Contains all functions that can be used in expressions.
+ */
+final class Functions {
+
+ static SetMultimap getFunctionMap() {
+ SetMultimap map = HashMultimap.create();
+ Functions functions = new Functions();
+ MethodHandles.Lookup lookup = MethodHandles.lookup();
+
+ try {
+ addMathHandles(map, lookup);
+ addStaticFunctionHandles(map, lookup);
+ functions.addInstanceFunctionHandles(map, lookup);
+ } catch (NoSuchMethodException | IllegalAccessException e) {
+ throw new IllegalStateException(e);
+ }
+
+ return ImmutableSetMultimap.copyOf(map);
+ }
+
+ private static void addMathHandles(
+ SetMultimap map,
+ MethodHandles.Lookup lookup
+ ) throws NoSuchMethodException, IllegalAccessException {
+ // double (double) functions
+ for (String name : ImmutableList.of(
+ "sin", "cos", "tan", "asin", "acos", "atan",
+ "sinh", "cosh", "tanh", "sqrt", "cbrt", "abs",
+ "ceil", "floor", "rint", "exp", "log", "log10"
+ )) {
+ map.put(name, lookup.findStatic(Math.class, name,
+ methodType(double.class, double.class)));
+ }
+ // Alias ln -> log
+ map.put("ln", lookup.findStatic(Math.class, "log",
+ methodType(double.class, double.class)));
+ map.put("round", lookup.findStatic(Math.class, "round",
+ methodType(long.class, double.class)));
+
+ map.put("atan2", lookup.findStatic(Math.class, "atan2",
+ methodType(double.class, double.class, double.class)));
+
+ // Special cases: we accept varargs for these
+ map.put("min", lookup.findStatic(Doubles.class, "min",
+ methodType(double.class, double[].class))
+ .asVarargsCollector(double[].class));
+ map.put("max", lookup.findStatic(Doubles.class, "max",
+ methodType(double.class, double[].class))
+ .asVarargsCollector(double[].class));
+ }
+
+ private static void addStaticFunctionHandles(
+ SetMultimap map,
+ MethodHandles.Lookup lookup
+ ) throws NoSuchMethodException, IllegalAccessException {
+ map.put("rotate", lookup.findStatic(Functions.class, "rotate",
+ methodType(double.class, Variable.class, Variable.class, double.class)));
+ map.put("swap", lookup.findStatic(Functions.class, "swap",
+ methodType(double.class, Variable.class, Variable.class)));
+ map.put("gmegabuf", lookup.findStatic(Functions.class, "gmegabuf",
+ methodType(double.class, double.class)));
+ map.put("gmegabuf", lookup.findStatic(Functions.class, "gmegabuf",
+ methodType(double.class, double.class, double.class)));
+ map.put("gclosest", lookup.findStatic(Functions.class, "gclosest",
+ methodType(double.class, double.class, double.class, double.class, double.class,
+ double.class, double.class)));
+ map.put("random", lookup.findStatic(Functions.class, "random",
+ methodType(double.class)));
+ map.put("randint", lookup.findStatic(Functions.class, "randint",
+ methodType(double.class, double.class)));
+ map.put("perlin", lookup.findStatic(Functions.class, "perlin",
+ methodType(double.class, double.class, double.class, double.class, double.class,
+ double.class, double.class, double.class)));
+ map.put("voronoi", lookup.findStatic(Functions.class, "voronoi",
+ methodType(double.class, double.class, double.class, double.class, double.class,
+ double.class)));
+ map.put("ridgedmulti", lookup.findStatic(Functions.class, "ridgedmulti",
+ methodType(double.class, double.class, double.class, double.class, double.class,
+ double.class, double.class)));
+ map.put("query", lookup.findStatic(Functions.class, "query",
+ methodType(double.class, double.class, double.class, double.class, LocalSlot.class,
+ LocalSlot.class)));
+ map.put("queryAbs", lookup.findStatic(Functions.class, "queryAbs",
+ methodType(double.class, double.class, double.class, double.class, LocalSlot.class,
+ LocalSlot.class)));
+ map.put("queryRel", lookup.findStatic(Functions.class, "queryRel",
+ methodType(double.class, double.class, double.class, double.class, LocalSlot.class,
+ LocalSlot.class)));
+ }
+
+ private void addInstanceFunctionHandles(
+ SetMultimap map,
+ MethodHandles.Lookup lookup
+ ) throws NoSuchMethodException, IllegalAccessException {
+ map.put("megabuf", lookup.findSpecial(Functions.class, "megabuf",
+ methodType(double.class, double.class), Functions.class)
+ .bindTo(this));
+ map.put("megabuf", lookup.findSpecial(Functions.class, "megabuf",
+ methodType(double.class, double.class, double.class), Functions.class)
+ .bindTo(this));
+ map.put("closest", lookup.findSpecial(Functions.class, "closest",
+ methodType(double.class, double.class, double.class, double.class, double.class,
+ double.class, double.class), Functions.class)
+ .bindTo(this));
+ }
+
+ private static double rotate(Variable x, Variable y, double angle) {
+ final double cosF = Math.cos(angle);
+ final double sinF = Math.sin(angle);
+
+ final double xOld = x.getValue();
+ final double yOld = y.getValue();
+
+ x.setValue(xOld * cosF - yOld * sinF);
+ y.setValue(xOld * sinF + yOld * cosF);
+
+ return 0.0;
+ }
+
+ private static double swap(Variable x, Variable y) {
+ final double tmp = x.getValue();
+
+ x.setValue(y.getValue());
+ y.setValue(tmp);
+
+ return 0.0;
+ }
+
+
+ private static final Int2ObjectMap globalMegaBuffer = new Int2ObjectOpenHashMap<>();
+ private final Int2ObjectMap megaBuffer = new Int2ObjectOpenHashMap<>();
+
+ private static double[] getSubBuffer(Int2ObjectMap megabuf, int key) {
+ return megabuf.computeIfAbsent(key, k -> new double[1024]);
+ }
+
+ private static double getBufferItem(final Int2ObjectMap megabuf, final int index) {
+ return getSubBuffer(megabuf, index & ~1023)[index & 1023];
+ }
+
+ private static double setBufferItem(final Int2ObjectMap megabuf, final int index, double value) {
+ return getSubBuffer(megabuf, index & ~1023)[index & 1023] = value;
+ }
+
+ private static double gmegabuf(double index) {
+ return getBufferItem(globalMegaBuffer, (int) index);
+ }
+
+ private static double gmegabuf(double index, double value) {
+ return setBufferItem(globalMegaBuffer, (int) index, value);
+ }
+
+ private double megabuf(double index) {
+ return getBufferItem(megaBuffer, (int) index);
+ }
+
+ private double megabuf(double index, double value) {
+ return setBufferItem(megaBuffer, (int) index, value);
+ }
+
+ private double closest(double x, double y, double z, double index, double count, double stride) {
+ return findClosest(
+ megaBuffer, x, y, z, (int) index, (int) count, (int) stride
+ );
+ }
+
+ private static double gclosest(double x, double y, double z, double index, double count, double stride) {
+ return findClosest(
+ globalMegaBuffer, x, y, z, (int) index, (int) count, (int) stride
+ );
+ }
+
+ private static double findClosest(Int2ObjectMap megabuf, double x, double y, double z, int index, int count, int stride) {
+ int closestIndex = -1;
+ double minDistanceSquared = Double.MAX_VALUE;
+
+ for (int i = 0; i < count; ++i) {
+ double currentX = getBufferItem(megabuf, index) - x;
+ double currentY = getBufferItem(megabuf, index+1) - y;
+ double currentZ = getBufferItem(megabuf, index+2) - z;
+
+ double currentDistanceSquared = currentX*currentX + currentY*currentY + currentZ*currentZ;
+
+ if (currentDistanceSquared < minDistanceSquared) {
+ minDistanceSquared = currentDistanceSquared;
+ closestIndex = index;
+ }
+
+ index += stride;
+ }
+
+ return closestIndex;
+ }
+
+ private static double random() {
+ return ThreadLocalRandom.current().nextDouble();
+ }
+
+ private static double randint(double max) {
+ return ThreadLocalRandom.current().nextInt((int) Math.floor(max));
+ }
+
+ private static final ThreadLocal localPerlin = ThreadLocal.withInitial(PerlinNoise::new);
+
+ private static double perlin(double seed, double x, double y, double z,
+ double frequency, double octaves, double persistence) {
+ PerlinNoise perlin = localPerlin.get();
+ try {
+ perlin.setSeed((int) seed);
+ perlin.setFrequency(frequency);
+ perlin.setOctaveCount((int) octaves);
+ perlin.setPersistence(persistence);
+ } catch (IllegalArgumentException e) {
+ throw new EvaluationException(0, "Perlin noise error: " + e.getMessage());
+ }
+ return perlin.noise(Vector3.at(x, y, z));
+ }
+
+ private static final ThreadLocal localVoronoi = ThreadLocal.withInitial(VoronoiNoise::new);
+
+ private static double voronoi(double seed, double x, double y, double z, double frequency) {
+ VoronoiNoise voronoi = localVoronoi.get();
+ try {
+ voronoi.setSeed((int) seed);
+ voronoi.setFrequency(frequency);
+ } catch (IllegalArgumentException e) {
+ throw new EvaluationException(0, "Voronoi error: " + e.getMessage());
+ }
+ return voronoi.noise(Vector3.at(x, y, z));
+ }
+
+ private static final ThreadLocal localRidgedMulti = ThreadLocal.withInitial(RidgedMultiFractalNoise::new);
+
+ private static double ridgedmulti(double seed, double x, double y, double z,
+ double frequency, double octaves) {
+ RidgedMultiFractalNoise ridgedMulti = localRidgedMulti.get();
+ try {
+ ridgedMulti.setSeed((int) seed);
+ ridgedMulti.setFrequency(frequency);
+ ridgedMulti.setOctaveCount((int) octaves);
+ } catch (IllegalArgumentException e) {
+ throw new EvaluationException(0, "Ridged multi error: " + e.getMessage());
+ }
+ return ridgedMulti.noise(Vector3.at(x, y, z));
+ }
+
+ private static double queryInternal(LocalSlot type, LocalSlot data, double typeId, double dataValue) {
+ // Compare to input values and determine return value
+ // -1 is a wildcard, always true
+ double ret = ((type.getValue() == -1 || typeId == type.getValue())
+ && (data.getValue() == -1 || dataValue == data.getValue())) ? 1.0 : 0.0;
+
+ if (type instanceof Variable) {
+ ((Variable) type).setValue(typeId);
+ }
+ if (data instanceof Variable) {
+ ((Variable) data).setValue(dataValue);
+ }
+
+ return ret;
+ }
+
+ private static double query(double x, double y, double z, LocalSlot type, LocalSlot data) {
+ final ExpressionEnvironment environment = Expression.getInstance().getEnvironment();
+
+ // Read values from world
+ final double typeId = environment.getBlockType(x, y, z);
+ final double dataValue = environment.getBlockData(x, y, z);
+
+ return queryInternal(type, data, typeId, dataValue);
+ }
+
+ private static double queryAbs(double x, double y, double z, LocalSlot type, LocalSlot data) {
+ final ExpressionEnvironment environment = Expression.getInstance().getEnvironment();
+
+ // Read values from world
+ final double typeId = environment.getBlockTypeAbs(x, y, z);
+ final double dataValue = environment.getBlockDataAbs(x, y, z);
+
+ return queryInternal(type, data, typeId, dataValue);
+ }
+
+ private static double queryRel(double x, double y, double z, LocalSlot type, LocalSlot data) {
+ final ExpressionEnvironment environment = Expression.getInstance().getEnvironment();
+
+ // Read values from world
+ final double typeId = environment.getBlockTypeRel(x, y, z);
+ final double dataValue = environment.getBlockDataRel(x, y, z);
+
+ return queryInternal(type, data, typeId, dataValue);
+ }
+
+ private Functions() {
+ }
+
+}
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/lexer/tokens/NumberToken.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/LexerErrorListener.java
similarity index 65%
rename from worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/lexer/tokens/NumberToken.java
rename to worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/LexerErrorListener.java
index 44cc70665..cb4b41ce5 100644
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/lexer/tokens/NumberToken.java
+++ b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/LexerErrorListener.java
@@ -17,28 +17,15 @@
* along with this program. If not, see .
*/
-package com.sk89q.worldedit.internal.expression.lexer.tokens;
+package com.sk89q.worldedit.internal.expression;
-/**
- * A number.
- */
-public class NumberToken extends Token {
-
- public final double value;
-
- public NumberToken(int position, double value) {
- super(position);
- this.value = value;
- }
+import org.antlr.v4.runtime.BaseErrorListener;
+import org.antlr.v4.runtime.RecognitionException;
+import org.antlr.v4.runtime.Recognizer;
+class LexerErrorListener extends BaseErrorListener {
@Override
- public char id() {
- return '0';
+ public void syntaxError(Recognizer, ?> recognizer, Object offendingSymbol, int line, int charPositionInLine, String msg, RecognitionException e) {
+ throw new LexerException(charPositionInLine, msg);
}
-
- @Override
- public String toString() {
- return "NumberToken(" + value + ")";
- }
-
}
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/lexer/LexerException.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/LexerException.java
similarity index 92%
rename from worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/lexer/LexerException.java
rename to worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/LexerException.java
index 3e08b2732..ede88dee8 100644
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/lexer/LexerException.java
+++ b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/LexerException.java
@@ -17,9 +17,7 @@
* along with this program. If not, see .
*/
-package com.sk89q.worldedit.internal.expression.lexer;
-
-import com.sk89q.worldedit.internal.expression.ExpressionException;
+package com.sk89q.worldedit.internal.expression;
/**
* Thrown when the lexer encounters a problem.
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/LocalSlot.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/LocalSlot.java
new file mode 100644
index 000000000..7bfe5517c
--- /dev/null
+++ b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/LocalSlot.java
@@ -0,0 +1,69 @@
+/*
+ * WorldEdit, a Minecraft world manipulation toolkit
+ * Copyright (C) sk89q
+ * Copyright (C) WorldEdit team and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package com.sk89q.worldedit.internal.expression;
+
+/**
+ * Represents the metadata for a named local slot.
+ */
+public interface LocalSlot {
+
+ final class Constant implements LocalSlot {
+ private final double value;
+
+ public Constant(double value) {
+ this.value = value;
+ }
+
+ @Override
+ public double getValue() {
+ return value;
+ }
+
+ @Override
+ public String toString() {
+ return String.valueOf(value);
+ }
+ }
+
+ final class Variable implements LocalSlot {
+ private double value;
+
+ public Variable(double value) {
+ this.value = value;
+ }
+
+ public void setValue(double value) {
+ this.value = value;
+ }
+
+ @Override
+ public double getValue() {
+ return value;
+ }
+
+ @Override
+ public String toString() {
+ return String.valueOf(value);
+ }
+ }
+
+ double getValue();
+
+}
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/lexer/tokens/OperatorToken.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/ParserErrorListener.java
similarity index 65%
rename from worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/lexer/tokens/OperatorToken.java
rename to worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/ParserErrorListener.java
index c4b70e475..937c00154 100644
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/lexer/tokens/OperatorToken.java
+++ b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/ParserErrorListener.java
@@ -17,28 +17,15 @@
* along with this program. If not, see .
*/
-package com.sk89q.worldedit.internal.expression.lexer.tokens;
+package com.sk89q.worldedit.internal.expression;
-/**
- * A unary or binary operator.
- */
-public class OperatorToken extends Token {
-
- public final String operator;
-
- public OperatorToken(int position, String operator) {
- super(position);
- this.operator = operator;
- }
+import org.antlr.v4.runtime.BaseErrorListener;
+import org.antlr.v4.runtime.RecognitionException;
+import org.antlr.v4.runtime.Recognizer;
+class ParserErrorListener extends BaseErrorListener {
@Override
- public char id() {
- return 'o';
+ public void syntaxError(Recognizer, ?> recognizer, Object offendingSymbol, int line, int charPositionInLine, String msg, RecognitionException e) {
+ throw new ParserException(charPositionInLine, msg);
}
-
- @Override
- public String toString() {
- return "OperatorToken(" + operator + ")";
- }
-
}
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/parser/ParserException.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/ParserException.java
similarity index 92%
rename from worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/parser/ParserException.java
rename to worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/ParserException.java
index 9a04fc914..30a3eace8 100644
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/parser/ParserException.java
+++ b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/ParserException.java
@@ -17,9 +17,7 @@
* along with this program. If not, see .
*/
-package com.sk89q.worldedit.internal.expression.parser;
-
-import com.sk89q.worldedit.internal.expression.ExpressionException;
+package com.sk89q.worldedit.internal.expression;
/**
* Thrown when the parser encounters a problem.
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/SlotTable.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/SlotTable.java
new file mode 100644
index 000000000..e47d4200e
--- /dev/null
+++ b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/SlotTable.java
@@ -0,0 +1,64 @@
+/*
+ * WorldEdit, a Minecraft world manipulation toolkit
+ * Copyright (C) sk89q
+ * Copyright (C) WorldEdit team and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package com.sk89q.worldedit.internal.expression;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.OptionalDouble;
+import java.util.Set;
+
+public class SlotTable {
+
+ private final Map slots = new HashMap<>();
+
+ public Set keySet() {
+ return slots.keySet();
+ }
+
+ public void putSlot(String name, LocalSlot slot) {
+ slots.put(name, slot);
+ }
+
+ public boolean containsSlot(String name) {
+ return slots.containsKey(name);
+ }
+
+ public Optional initVariable(String name) {
+ slots.computeIfAbsent(name, n -> new LocalSlot.Variable(0));
+ return getVariable(name);
+ }
+
+ public Optional getSlot(String name) {
+ return Optional.ofNullable(slots.get(name));
+ }
+
+ public Optional getVariable(String name) {
+ return getSlot(name)
+ .filter(LocalSlot.Variable.class::isInstance)
+ .map(LocalSlot.Variable.class::cast);
+ }
+
+ public OptionalDouble getSlotValue(String name) {
+ LocalSlot slot = slots.get(name);
+ return slot == null ? OptionalDouble.empty() : OptionalDouble.of(slot.getValue());
+ }
+
+}
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/lexer/Lexer.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/lexer/Lexer.java
deleted file mode 100644
index 8e5b3bbec..000000000
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/lexer/Lexer.java
+++ /dev/null
@@ -1,240 +0,0 @@
-/*
- * WorldEdit, a Minecraft world manipulation toolkit
- * Copyright (C) sk89q
- * Copyright (C) WorldEdit team and contributors
- *
- * This program is free software: you can redistribute it and/or modify it
- * under the terms of the GNU Lesser General Public License as published by the
- * Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
- * for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program. If not, see .
- */
-
-package com.sk89q.worldedit.internal.expression.lexer;
-
-import com.sk89q.worldedit.internal.expression.lexer.tokens.CharacterToken;
-import com.sk89q.worldedit.internal.expression.lexer.tokens.IdentifierToken;
-import com.sk89q.worldedit.internal.expression.lexer.tokens.KeywordToken;
-import com.sk89q.worldedit.internal.expression.lexer.tokens.NumberToken;
-import com.sk89q.worldedit.internal.expression.lexer.tokens.OperatorToken;
-import com.sk89q.worldedit.internal.expression.lexer.tokens.Token;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * Processes a string into a list of tokens.
- *
- *
Tokens can be numbers, identifiers, operators and assorted other
- * characters.
- */
-public class Lexer {
-
- private final String expression;
- private int position = 0;
-
- private Lexer(String expression) {
- this.expression = expression;
- }
-
- public static List tokenize(String expression) throws LexerException {
- return new Lexer(expression).tokenize();
- }
-
- private final DecisionTree operatorTree = new DecisionTree(null,
- '+', new DecisionTree("+",
- '=', new DecisionTree("+="),
- '+', new DecisionTree("++")
- ),
- '-', new DecisionTree("-",
- '=', new DecisionTree("-="),
- '-', new DecisionTree("--")
- ),
- '*', new DecisionTree("*",
- '=', new DecisionTree("*="),
- '*', new DecisionTree("**")
- ),
- '/', new DecisionTree("/",
- '=', new DecisionTree("/=")
- ),
- '%', new DecisionTree("%",
- '=', new DecisionTree("%=")
- ),
- '^', new DecisionTree("^",
- '=', new DecisionTree("^=")
- ),
- '=', new DecisionTree("=",
- '=', new DecisionTree("==")
- ),
- '!', new DecisionTree("!",
- '=', new DecisionTree("!=")
- ),
- '<', new DecisionTree("<",
- '<', new DecisionTree("<<"),
- '=', new DecisionTree("<=")
- ),
- '>', new DecisionTree(">",
- '>', new DecisionTree(">>"),
- '=', new DecisionTree(">=")
- ),
- '&', new DecisionTree(null, // not implemented
- '&', new DecisionTree("&&")
- ),
- '|', new DecisionTree(null, // not implemented
- '|', new DecisionTree("||")
- ),
- '~', new DecisionTree("~",
- '=', new DecisionTree("~=")
- )
- );
-
- private static final Set characterTokens = new HashSet<>();
- static {
- characterTokens.add(',');
- characterTokens.add('(');
- characterTokens.add(')');
- characterTokens.add('{');
- characterTokens.add('}');
- characterTokens.add(';');
- characterTokens.add('?');
- characterTokens.add(':');
- }
-
- private static final Set keywords =
- new HashSet<>(Arrays.asList("if", "else", "while", "do", "for", "break", "continue", "return", "switch", "case", "default"));
-
- private static final Pattern numberPattern = Pattern.compile("^([0-9]*(?:\\.[0-9]+)?(?:[eE][+-]?[0-9]+)?)");
- private static final Pattern identifierPattern = Pattern.compile("^([A-Za-z][0-9A-Za-z_]*)");
-
- private List tokenize() throws LexerException {
- List tokens = new ArrayList<>();
-
- do {
- skipWhitespace();
- if (position >= expression.length()) {
- break;
- }
-
- Token token = operatorTree.evaluate(position);
- if (token != null) {
- tokens.add(token);
- continue;
- }
-
- final char ch = peek();
-
- if (characterTokens.contains(ch)) {
- tokens.add(new CharacterToken(position++, ch));
- continue;
- }
-
- final Matcher numberMatcher = numberPattern.matcher(expression.substring(position));
- if (numberMatcher.lookingAt()) {
- String numberPart = numberMatcher.group(1);
- if (!numberPart.isEmpty()) {
- try {
- tokens.add(new NumberToken(position, Double.parseDouble(numberPart)));
- } catch (NumberFormatException e) {
- throw new LexerException(position, "Number parsing failed", e);
- }
-
- position += numberPart.length();
- continue;
- }
- }
-
- final Matcher identifierMatcher = identifierPattern.matcher(expression.substring(position));
- if (identifierMatcher.lookingAt()) {
- String identifierPart = identifierMatcher.group(1);
- if (!identifierPart.isEmpty()) {
- if (keywords.contains(identifierPart)) {
- tokens.add(new KeywordToken(position, identifierPart));
- } else {
- tokens.add(new IdentifierToken(position, identifierPart));
- }
-
- position += identifierPart.length();
- continue;
- }
- }
-
- throw new LexerException(position, "Unknown character '" + ch + "'");
- } while (position < expression.length());
-
- return tokens;
- }
-
- private char peek() {
- return expression.charAt(position);
- }
-
- private void skipWhitespace() {
- while (position < expression.length() && Character.isWhitespace(peek())) {
- ++position;
- }
- }
-
- public class DecisionTree {
- private final String tokenName;
- private final Map subTrees = new HashMap<>();
-
- private DecisionTree(String tokenName, Object... args) {
- this.tokenName = tokenName;
-
- if (args.length % 2 != 0) {
- throw new UnsupportedOperationException("You need to pass an even number of arguments.");
- }
-
- for (int i = 0; i < args.length; i += 2) {
- if (!(args[i] instanceof Character)) {
- throw new UnsupportedOperationException("Argument #" + i + " expected to be 'Character', not '" + args[i].getClass().getName() + "'.");
- }
- if (!(args[i + 1] instanceof DecisionTree)) {
- throw new UnsupportedOperationException("Argument #" + (i + 1) + " expected to be 'DecisionTree', not '" + args[i + 1].getClass().getName() + "'.");
- }
-
- Character next = (Character) args[i];
- DecisionTree subTree = (DecisionTree) args[i + 1];
-
- subTrees.put(next, subTree);
- }
- }
-
- private Token evaluate(int startPosition) throws LexerException {
- if (position < expression.length()) {
- final char next = peek();
-
- final DecisionTree subTree = subTrees.get(next);
- if (subTree != null) {
- ++position;
- final Token subTreeResult = subTree.evaluate(startPosition);
- if (subTreeResult != null) {
- return subTreeResult;
- }
- --position;
- }
- }
-
- if (tokenName == null) {
- return null;
- }
-
- return new OperatorToken(startPosition, tokenName);
- }
- }
-
-}
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/lexer/tokens/CharacterToken.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/lexer/tokens/CharacterToken.java
deleted file mode 100644
index 51bf757da..000000000
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/lexer/tokens/CharacterToken.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * WorldEdit, a Minecraft world manipulation toolkit
- * Copyright (C) sk89q
- * Copyright (C) WorldEdit team and contributors
- *
- * This program is free software: you can redistribute it and/or modify it
- * under the terms of the GNU Lesser General Public License as published by the
- * Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
- * for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program. If not, see .
- */
-
-package com.sk89q.worldedit.internal.expression.lexer.tokens;
-
-/**
- * A single character that doesn't fit any of the other token categories.
- */
-public class CharacterToken extends Token {
-
- public final char character;
-
- public CharacterToken(int position, char character) {
- super(position);
- this.character = character;
- }
-
- @Override
- public char id() {
- return character;
- }
-
- @Override
- public String toString() {
- return "CharacterToken(" + character + ")";
- }
-
-}
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/lexer/tokens/IdentifierToken.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/lexer/tokens/IdentifierToken.java
deleted file mode 100644
index 4a840f754..000000000
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/lexer/tokens/IdentifierToken.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * WorldEdit, a Minecraft world manipulation toolkit
- * Copyright (C) sk89q
- * Copyright (C) WorldEdit team and contributors
- *
- * This program is free software: you can redistribute it and/or modify it
- * under the terms of the GNU Lesser General Public License as published by the
- * Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
- * for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program. If not, see .
- */
-
-package com.sk89q.worldedit.internal.expression.lexer.tokens;
-
-/**
- * An identifier.
- */
-public class IdentifierToken extends Token {
-
- public final String value;
-
- public IdentifierToken(int position, String value) {
- super(position);
- this.value = value;
- }
-
- @Override
- public char id() {
- return 'i';
- }
-
- @Override
- public String toString() {
- return "IdentifierToken(" + value + ")";
- }
-
-}
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/lexer/tokens/KeywordToken.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/lexer/tokens/KeywordToken.java
deleted file mode 100644
index 208e7280c..000000000
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/lexer/tokens/KeywordToken.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * WorldEdit, a Minecraft world manipulation toolkit
- * Copyright (C) sk89q
- * Copyright (C) WorldEdit team and contributors
- *
- * This program is free software: you can redistribute it and/or modify it
- * under the terms of the GNU Lesser General Public License as published by the
- * Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
- * for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program. If not, see .
- */
-
-package com.sk89q.worldedit.internal.expression.lexer.tokens;
-
-/**
- * A keyword.
- */
-public class KeywordToken extends Token {
-
- public final String value;
-
- public KeywordToken(int position, String value) {
- super(position);
- this.value = value;
- }
-
- @Override
- public char id() {
- return 'k';
- }
-
- @Override
- public String toString() {
- return "KeywordToken(" + value + ")";
- }
-
-}
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/lexer/tokens/Token.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/lexer/tokens/Token.java
deleted file mode 100644
index c6427f0c5..000000000
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/lexer/tokens/Token.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * WorldEdit, a Minecraft world manipulation toolkit
- * Copyright (C) sk89q
- * Copyright (C) WorldEdit team and contributors
- *
- * This program is free software: you can redistribute it and/or modify it
- * under the terms of the GNU Lesser General Public License as published by the
- * Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
- * for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program. If not, see .
- */
-
-package com.sk89q.worldedit.internal.expression.lexer.tokens;
-
-import com.sk89q.worldedit.internal.expression.Identifiable;
-
-/**
- * A token. The lexer generates these to make the parser's job easier.
- */
-public abstract class Token implements Identifiable {
-
- private final int position;
-
- public Token(int position) {
- this.position = position;
- }
-
- @Override
- public int getPosition() {
- return position;
- }
-
-}
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/parser/Parser.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/parser/Parser.java
deleted file mode 100644
index 24f3dafa9..000000000
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/parser/Parser.java
+++ /dev/null
@@ -1,464 +0,0 @@
-/*
- * WorldEdit, a Minecraft world manipulation toolkit
- * Copyright (C) sk89q
- * Copyright (C) WorldEdit team and contributors
- *
- * This program is free software: you can redistribute it and/or modify it
- * under the terms of the GNU Lesser General Public License as published by the
- * Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
- * for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program. If not, see .
- */
-
-package com.sk89q.worldedit.internal.expression.parser;
-
-import com.sk89q.worldedit.internal.expression.Expression;
-import com.sk89q.worldedit.internal.expression.Identifiable;
-import com.sk89q.worldedit.internal.expression.lexer.tokens.IdentifierToken;
-import com.sk89q.worldedit.internal.expression.lexer.tokens.KeywordToken;
-import com.sk89q.worldedit.internal.expression.lexer.tokens.NumberToken;
-import com.sk89q.worldedit.internal.expression.lexer.tokens.OperatorToken;
-import com.sk89q.worldedit.internal.expression.lexer.tokens.Token;
-import com.sk89q.worldedit.internal.expression.runtime.Break;
-import com.sk89q.worldedit.internal.expression.runtime.Conditional;
-import com.sk89q.worldedit.internal.expression.runtime.Constant;
-import com.sk89q.worldedit.internal.expression.runtime.For;
-import com.sk89q.worldedit.internal.expression.runtime.Function;
-import com.sk89q.worldedit.internal.expression.runtime.Functions;
-import com.sk89q.worldedit.internal.expression.runtime.LValue;
-import com.sk89q.worldedit.internal.expression.runtime.RValue;
-import com.sk89q.worldedit.internal.expression.runtime.Return;
-import com.sk89q.worldedit.internal.expression.runtime.Sequence;
-import com.sk89q.worldedit.internal.expression.runtime.SimpleFor;
-import com.sk89q.worldedit.internal.expression.runtime.Switch;
-import com.sk89q.worldedit.internal.expression.runtime.While;
-
-import java.util.ArrayList;
-import java.util.LinkedList;
-import java.util.List;
-
-/**
- * Processes a list of tokens into an executable tree.
- *
- *
Tokens can be numbers, identifiers, operators and assorted other characters.
- */
-public class Parser {
- private final class NullToken extends Token {
- private NullToken(int position) {
- super(position);
- }
-
- @Override
- public char id() {
- return '\0';
- }
-
- @Override
- public String toString() {
- return "NullToken";
- }
- }
-
- private final List tokens;
- private int position = 0;
- private Expression expression;
-
- private Parser(List tokens, Expression expression) {
- this.tokens = tokens;
- this.expression = expression;
- }
-
- public static RValue parse(List tokens, Expression expression) throws ParserException {
- return new Parser(tokens, expression).parse();
- }
-
- private RValue parse() throws ParserException {
- final RValue ret = parseStatements(false);
- if (position < tokens.size()) {
- final Token token = peek();
- throw new ParserException(token.getPosition(), "Extra token at the end of the input: " + token);
- }
-
- ret.bindVariables(expression, false);
-
- return ret;
- }
-
- private RValue parseStatements(boolean singleStatement) throws ParserException {
- List statements = new ArrayList<>();
- loop: while (position < tokens.size()) {
- boolean expectSemicolon = false;
-
- final Token current = peek();
- switch (current.id()) {
- case '{':
- consumeCharacter('{');
-
- statements.add(parseStatements(false));
-
- consumeCharacter('}');
-
- break;
-
- case '}':
- break loop;
-
- case 'k':
- final String keyword = ((KeywordToken) current).value;
- switch (keyword.charAt(0)) {
- case 'i': { // if
- ++position;
- final RValue condition = parseBracket();
- final RValue truePart = parseStatements(true);
- final RValue falsePart;
-
- if (hasKeyword("else")) {
- ++position;
- falsePart = parseStatements(true);
- } else {
- falsePart = null;
- }
-
- statements.add(new Conditional(current.getPosition(), condition, truePart, falsePart));
- break;
- }
-
- case 'w': { // while
- ++position;
- final RValue condition = parseBracket();
- final RValue body = parseStatements(true);
-
- statements.add(new While(current.getPosition(), condition, body, false));
- break;
- }
-
- case 'd': { // do/default
- if (hasKeyword("default")) {
- break loop;
- }
-
- ++position;
- final RValue body = parseStatements(true);
-
- consumeKeyword("while");
-
- final RValue condition = parseBracket();
-
- statements.add(new While(current.getPosition(), condition, body, true));
-
- expectSemicolon = true;
- break;
- }
-
- case 'f': { // for
- ++position;
- consumeCharacter('(');
- int oldPosition = position;
- final RValue init = parseExpression(true);
- //if ((init instanceof LValue) && )
- if (peek().id() == ';') {
- ++position;
- final RValue condition = parseExpression(true);
- consumeCharacter(';');
- final RValue increment = parseExpression(true);
- consumeCharacter(')');
- final RValue body = parseStatements(true);
-
- statements.add(new For(current.getPosition(), init, condition, increment, body));
- } else {
- position = oldPosition;
-
- final Token variableToken = peek();
- if (!(variableToken instanceof IdentifierToken)) {
- throw new ParserException(variableToken.getPosition(), "Expected identifier");
- }
-
- RValue variable = expression.getVariable(((IdentifierToken) variableToken).value, true);
- if (!(variable instanceof LValue)) {
- throw new ParserException(variableToken.getPosition(), "Expected variable");
- }
- ++position;
-
- final Token equalsToken = peek();
- if (!(equalsToken instanceof OperatorToken) || !((OperatorToken) equalsToken).operator.equals("=")) {
- throw new ParserException(variableToken.getPosition(), "Expected '=' or a term and ';'");
- }
- ++position;
-
- final RValue first = parseExpression(true);
- consumeCharacter(',');
- final RValue last = parseExpression(true);
- consumeCharacter(')');
- final RValue body = parseStatements(true);
-
- statements.add(new SimpleFor(current.getPosition(), (LValue) variable, first, last, body));
- } // switch (keyword.charAt(0))
- break;
- }
-
- case 'b': // break
- ++position;
- statements.add(new Break(current.getPosition(), false));
- break;
-
- case 'c': // continue/case
- if (hasKeyword("case")) {
- break loop;
- }
-
- ++position;
- statements.add(new Break(current.getPosition(), true));
- break;
-
- case 'r': // return
- ++position;
- statements.add(new Return(current.getPosition(), parseExpression(true)));
-
- expectSemicolon = true;
- break;
-
- case 's': // switch
- ++position;
- final RValue parameter = parseBracket();
- final List values = new ArrayList<>();
- final List caseStatements = new ArrayList<>();
- RValue defaultCase = null;
-
- consumeCharacter('{');
- while (peek().id() != '}') {
- if (position >= tokens.size()) {
- throw new ParserException(current.getPosition(), "Expected '}' instead of EOF");
- }
- if (defaultCase != null) {
- throw new ParserException(current.getPosition(), "Expected '}' instead of " + peek());
- }
-
- if (hasKeyword("case")) {
- ++position;
-
- final Token valueToken = peek();
- if (!(valueToken instanceof NumberToken)) {
- throw new ParserException(current.getPosition(), "Expected number instead of " + peek());
- }
-
- ++position;
-
- values.add(((NumberToken) valueToken).value);
-
- consumeCharacter(':');
- caseStatements.add(parseStatements(false));
- } else if (hasKeyword("default")) {
- ++position;
-
- consumeCharacter(':');
- defaultCase = parseStatements(false);
- } else {
- throw new ParserException(current.getPosition(), "Expected 'case' or 'default' instead of " + peek());
- }
- }
- consumeCharacter('}');
-
- statements.add(new Switch(current.getPosition(), parameter, values, caseStatements, defaultCase));
- break;
-
- default:
- throw new ParserException(current.getPosition(), "Unexpected keyword '" + keyword + "'");
- }
-
- break;
-
- default:
- statements.add(parseExpression(true));
-
- expectSemicolon = true;
- } // switch (current.id())
-
- if (expectSemicolon) {
- if (peek().id() == ';') {
- ++position;
- } else {
- break;
- }
- }
-
- if (singleStatement) {
- break;
- }
- } // while (position < tokens.size())
-
- switch (statements.size()) {
- case 0:
- if (singleStatement) {
- throw new ParserException(peek().getPosition(), "Statement expected.");
- }
-
- return new Sequence(peek().getPosition());
-
- case 1:
- return statements.get(0);
-
- default:
- return new Sequence(peek().getPosition(), statements.toArray(new RValue[statements.size()]));
- }
- }
-
- private RValue parseExpression(boolean canBeEmpty) throws ParserException {
- LinkedList halfProcessed = new LinkedList<>();
-
- // process brackets, numbers, functions, variables and detect prefix operators
- boolean expressionStart = true;
- loop: while (position < tokens.size()) {
- final Token current = peek();
-
- switch (current.id()) {
- case '0':
- halfProcessed.add(new Constant(current.getPosition(), ((NumberToken) current).value));
- ++position;
- expressionStart = false;
- break;
-
- case 'i':
- final IdentifierToken identifierToken = (IdentifierToken) current;
- ++position;
-
- final Token next = peek();
- if (next.id() == '(') {
- halfProcessed.add(parseFunctionCall(identifierToken));
- } else {
- final RValue variable = expression.getVariable(identifierToken.value, false);
- if (variable == null) {
- halfProcessed.add(new UnboundVariable(identifierToken.getPosition(), identifierToken.value));
- } else {
- halfProcessed.add(variable);
- }
- }
- expressionStart = false;
- break;
-
- case '(':
- halfProcessed.add(parseBracket());
- expressionStart = false;
- break;
-
- case ',':
- case ')':
- case '}':
- case ';':
- break loop;
-
- case 'o':
- if (expressionStart) {
- // Preprocess prefix operators into unary operators
- halfProcessed.add(new UnaryOperator((OperatorToken) current));
- } else {
- halfProcessed.add(current);
- }
- ++position;
- expressionStart = true;
- break;
-
- default:
- halfProcessed.add(current);
- ++position;
- expressionStart = false;
- break;
- }
- }
-
- if (halfProcessed.isEmpty() && canBeEmpty) {
- return new Sequence(peek().getPosition());
- }
-
- return ParserProcessors.processExpression(halfProcessed);
- }
-
-
- private Token peek() {
- if (position >= tokens.size()) {
- return new NullToken(tokens.get(tokens.size() - 1).getPosition() + 1);
- }
-
- return tokens.get(position);
- }
-
- private Function parseFunctionCall(IdentifierToken identifierToken) throws ParserException {
- consumeCharacter('(');
-
- try {
- if (peek().id() == ')') {
- ++position;
- return Functions.getFunction(identifierToken.getPosition(), identifierToken.value);
- }
-
- List args = new ArrayList<>();
-
- loop: while (true) {
- args.add(parseExpression(false));
-
- final Token current = peek();
- ++position;
-
- switch (current.id()) {
- case ',':
- continue;
-
- case ')':
- break loop;
-
- default:
- throw new ParserException(current.getPosition(), "Unmatched opening bracket");
- }
- }
-
- return Functions.getFunction(identifierToken.getPosition(), identifierToken.value, args.toArray(new RValue[args.size()]));
- } catch (NoSuchMethodException e) {
- throw new ParserException(identifierToken.getPosition(), "Function '" + identifierToken.value + "' not found", e);
- }
- }
-
- private RValue parseBracket() throws ParserException {
- consumeCharacter('(');
-
- final RValue ret = parseExpression(false);
-
- consumeCharacter(')');
-
- return ret;
- }
-
- private boolean hasKeyword(String keyword) {
- final Token next = peek();
-
- return next instanceof KeywordToken && ((KeywordToken) next).value.equals(keyword);
- }
-
- private void assertCharacter(char character) throws ParserException {
- final Token next = peek();
- if (next.id() != character) {
- throw new ParserException(next.getPosition(), "Expected '" + character + "'");
- }
- }
-
- private void assertKeyword(String keyword) throws ParserException {
- if (!hasKeyword(keyword)) {
- throw new ParserException(peek().getPosition(), "Expected '" + keyword + "'");
- }
- }
-
- private void consumeCharacter(char character) throws ParserException {
- assertCharacter(character);
- ++position;
- }
-
- private void consumeKeyword(String keyword) throws ParserException {
- assertKeyword(keyword);
- ++position;
- }
-}
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/parser/ParserProcessors.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/parser/ParserProcessors.java
deleted file mode 100644
index b50997cc6..000000000
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/parser/ParserProcessors.java
+++ /dev/null
@@ -1,352 +0,0 @@
-/*
- * WorldEdit, a Minecraft world manipulation toolkit
- * Copyright (C) sk89q
- * Copyright (C) WorldEdit team and contributors
- *
- * This program is free software: you can redistribute it and/or modify it
- * under the terms of the GNU Lesser General Public License as published by the
- * Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
- * for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program. If not, see .
- */
-
-package com.sk89q.worldedit.internal.expression.parser;
-
-import com.sk89q.worldedit.internal.expression.Identifiable;
-import com.sk89q.worldedit.internal.expression.lexer.tokens.OperatorToken;
-import com.sk89q.worldedit.internal.expression.lexer.tokens.Token;
-import com.sk89q.worldedit.internal.expression.runtime.Conditional;
-import com.sk89q.worldedit.internal.expression.runtime.Operators;
-import com.sk89q.worldedit.internal.expression.runtime.RValue;
-
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.Map;
-
-/**
- * Helper classfor Parser. Contains processors for statements and operators.
- */
-public final class ParserProcessors {
-
- private static final Map unaryOpMap = new HashMap<>();
-
- private static final Map[] binaryOpMapsLA;
- private static final Map[] binaryOpMapsRA;
-
- static {
- unaryOpMap.put("-", "neg");
- unaryOpMap.put("!", "not");
- unaryOpMap.put("~", "inv");
- unaryOpMap.put("++", "inc");
- unaryOpMap.put("--", "dec");
- unaryOpMap.put("x++", "postinc");
- unaryOpMap.put("x--", "postdec");
- unaryOpMap.put("x!", "fac");
-
- final Object[][][] binaryOpsLA = {
- {
- { "^", "pow" },
- { "**", "pow" },
- },
- {
- { "*", "mul" },
- { "/", "div" },
- { "%", "mod" },
- },
- {
- { "+", "add" },
- { "-", "sub" },
- },
- {
- { "<<", "shl" },
- { ">>", "shr" },
- },
- {
- { "<", "lth" },
- { ">", "gth" },
- { "<=", "leq" },
- { ">=", "geq" },
- },
- {
- { "==", "equ" },
- { "!=", "neq" },
- { "~=", "near" },
- },
- {
- { "&&", "and" },
- },
- {
- { "||", "or" },
- },
- };
- final Object[][][] binaryOpsRA = {
- {
- { "=", "ass" },
- { "+=", "aadd" },
- { "-=", "asub" },
- { "*=", "amul" },
- { "/=", "adiv" },
- { "%=", "amod" },
- { "^=", "aexp" },
- },
- };
-
- @SuppressWarnings("unchecked")
- final Map[] lBinaryOpMapsLA = binaryOpMapsLA = new Map[binaryOpsLA.length];
- for (int i = 0; i < binaryOpsLA.length; ++i) {
- final Object[][] a = binaryOpsLA[i];
- switch (a.length) {
- case 0:
- lBinaryOpMapsLA[i] = Collections.emptyMap();
- break;
-
- case 1:
- final Object[] first = a[0];
- lBinaryOpMapsLA[i] = Collections.singletonMap((String) first[0], (String) first[1]);
- break;
-
- default:
- Map m = lBinaryOpMapsLA[i] = new HashMap<>();
- for (final Object[] element : a) {
- m.put((String) element[0], (String) element[1]);
- }
- }
- }
-
- @SuppressWarnings("unchecked")
- final Map[] lBinaryOpMapsRA = binaryOpMapsRA = new Map[binaryOpsRA.length];
- for (int i = 0; i < binaryOpsRA.length; ++i) {
- final Object[][] a = binaryOpsRA[i];
- switch (a.length) {
- case 0:
- lBinaryOpMapsRA[i] = Collections.emptyMap();
- break;
-
- case 1:
- final Object[] first = a[0];
- lBinaryOpMapsRA[i] = Collections.singletonMap((String) first[0], (String) first[1]);
- break;
-
- default:
- Map m = lBinaryOpMapsRA[i] = new HashMap<>();
- for (final Object[] element : a) {
- m.put((String) element[0], (String) element[1]);
- }
- }
- }
- }
-
- private ParserProcessors() {
- }
-
- static RValue processExpression(LinkedList input) throws ParserException {
- return processBinaryOpsRA(input, binaryOpMapsRA.length - 1);
- }
-
- private static RValue processBinaryOpsLA(LinkedList input, int level) throws ParserException {
- if (level < 0) {
- return processUnaryOps(input);
- }
-
- LinkedList lhs = new LinkedList<>();
- LinkedList rhs = new LinkedList<>();
- String operator = null;
-
- for (Iterator it = input.descendingIterator(); it.hasNext();) {
- Identifiable identifiable = it.next();
- if (operator == null) {
- rhs.addFirst(identifiable);
-
- if (!(identifiable instanceof OperatorToken)) {
- continue;
- }
-
- operator = binaryOpMapsLA[level].get(((OperatorToken) identifiable).operator);
- if (operator == null) {
- continue;
- }
-
- rhs.removeFirst();
- } else {
- lhs.addFirst(identifiable);
- }
- }
-
- RValue rhsInvokable = processBinaryOpsLA(rhs, level - 1);
- if (operator == null) return rhsInvokable;
-
- RValue lhsInvokable = processBinaryOpsLA(lhs, level);
-
- try {
- return Operators.getOperator(input.get(0).getPosition(), operator, lhsInvokable, rhsInvokable);
- } catch (NoSuchMethodException e) {
- final Token operatorToken = (Token) input.get(lhs.size());
- throw new ParserException(operatorToken.getPosition(), "Couldn't find operator '" + operator + "'");
- }
- }
-
- private static RValue processBinaryOpsRA(LinkedList input, int level) throws ParserException {
- if (level < 0) {
- return processTernaryOps(input);
- }
-
- LinkedList lhs = new LinkedList<>();
- LinkedList rhs = new LinkedList<>();
- String operator = null;
-
- for (Identifiable identifiable : input) {
- if (operator == null) {
- lhs.addLast(identifiable);
-
- if (!(identifiable instanceof OperatorToken)) {
- continue;
- }
-
- operator = binaryOpMapsRA[level].get(((OperatorToken) identifiable).operator);
- if (operator == null) {
- continue;
- }
-
- lhs.removeLast();
- } else {
- rhs.addLast(identifiable);
- }
- }
-
- RValue lhsInvokable = processBinaryOpsRA(lhs, level - 1);
- if (operator == null) return lhsInvokable;
-
- RValue rhsInvokable = processBinaryOpsRA(rhs, level);
-
- try {
- return Operators.getOperator(input.get(0).getPosition(), operator, lhsInvokable, rhsInvokable);
- } catch (NoSuchMethodException e) {
- final Token operatorToken = (Token) input.get(lhs.size());
- throw new ParserException(operatorToken.getPosition(), "Couldn't find operator '" + operator + "'");
- }
- }
-
- private static RValue processTernaryOps(LinkedList input) throws ParserException {
- LinkedList lhs = new LinkedList<>();
- LinkedList mhs = new LinkedList<>();
- LinkedList rhs = new LinkedList<>();
-
- int partsFound = 0;
- int conditionalsFound = 0;
-
- for (Identifiable identifiable : input) {
- final char character = identifiable.id();
- switch (character) {
- case '?':
- ++conditionalsFound;
- break;
- case ':':
- --conditionalsFound;
- break;
- }
-
- if (conditionalsFound < 0) {
- throw new ParserException(identifiable.getPosition(), "Unexpected ':'");
- }
-
- switch (partsFound) {
- case 0:
- if (character == '?') {
- partsFound = 1;
- } else {
- lhs.addLast(identifiable);
- }
- break;
-
- case 1:
- if (conditionalsFound == 0 && character == ':') {
- partsFound = 2;
- } else {
- mhs.addLast(identifiable);
- }
- break;
-
- case 2:
- rhs.addLast(identifiable);
- }
- }
-
- if (partsFound < 2) {
- return processBinaryOpsLA(input, binaryOpMapsLA.length - 1);
- }
-
- RValue lhsInvokable = processBinaryOpsLA(lhs, binaryOpMapsLA.length - 1);
- RValue mhsInvokable = processTernaryOps(mhs);
- RValue rhsInvokable = processTernaryOps(rhs);
-
- return new Conditional(input.get(lhs.size()).getPosition(), lhsInvokable, mhsInvokable, rhsInvokable);
- }
-
- private static RValue processUnaryOps(LinkedList input) throws ParserException {
- // Preprocess postfix operators into unary operators
- final Identifiable center;
- LinkedList postfixes = new LinkedList<>();
- do {
- if (input.isEmpty()) {
- throw new ParserException(-1, "Expression missing.");
- }
-
- final Identifiable last = input.removeLast();
- if (last instanceof OperatorToken) {
- postfixes.addLast(new UnaryOperator(last.getPosition(), "x" + ((OperatorToken) last).operator));
- } else if (last instanceof UnaryOperator) {
- postfixes.addLast(new UnaryOperator(last.getPosition(), "x" + ((UnaryOperator) last).operator));
- } else {
- center = last;
- break;
- }
- } while (true);
-
- if (!(center instanceof RValue)) {
- throw new ParserException(center.getPosition(), "Expected expression, found " + center);
- }
-
- input.addAll(postfixes);
-
- RValue ret = (RValue) center;
- while (!input.isEmpty()) {
- final Identifiable last = input.removeLast();
- final int lastPosition = last.getPosition();
- if (last instanceof UnaryOperator) {
- final String operator = ((UnaryOperator) last).operator;
- if (operator.equals("+")) {
- continue;
- }
-
- String opName = unaryOpMap.get(operator);
- if (opName != null) {
- try {
- ret = Operators.getOperator(lastPosition, opName, ret);
- continue;
- } catch (NoSuchMethodException e) {
- throw new ParserException(lastPosition, "No such prefix operator: " + operator);
- }
- }
- }
-
- if (last instanceof Token) {
- throw new ParserException(lastPosition, "Extra token found in expression: " + last);
- } else if (last instanceof RValue) {
- throw new ParserException(lastPosition, "Extra expression found: " + last);
- } else {
- throw new ParserException(lastPosition, "Extra element found: " + last);
- }
- }
- return ret;
- }
-
-}
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/parser/PseudoToken.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/parser/PseudoToken.java
deleted file mode 100644
index cee19e8b3..000000000
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/parser/PseudoToken.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * WorldEdit, a Minecraft world manipulation toolkit
- * Copyright (C) sk89q
- * Copyright (C) WorldEdit team and contributors
- *
- * This program is free software: you can redistribute it and/or modify it
- * under the terms of the GNU Lesser General Public License as published by the
- * Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
- * for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program. If not, see .
- */
-
-package com.sk89q.worldedit.internal.expression.parser;
-
-import com.sk89q.worldedit.internal.expression.Identifiable;
-
-/**
- * A pseudo-token, inserted by the parser instead of the lexer.
- */
-public abstract class PseudoToken implements Identifiable {
-
- private final int position;
-
- public PseudoToken(int position) {
- this.position = position;
- }
-
- @Override
- public abstract char id();
-
- @Override
- public int getPosition() {
- return position;
- }
-
-}
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/parser/UnaryOperator.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/parser/UnaryOperator.java
deleted file mode 100644
index d6a54b321..000000000
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/parser/UnaryOperator.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * WorldEdit, a Minecraft world manipulation toolkit
- * Copyright (C) sk89q
- * Copyright (C) WorldEdit team and contributors
- *
- * This program is free software: you can redistribute it and/or modify it
- * under the terms of the GNU Lesser General Public License as published by the
- * Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
- * for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program. If not, see .
- */
-
-package com.sk89q.worldedit.internal.expression.parser;
-
-import com.sk89q.worldedit.internal.expression.lexer.tokens.OperatorToken;
-
-/**
- * The parser uses this pseudo-token to mark operators as unary operators.
- */
-public class UnaryOperator extends PseudoToken {
-
- final String operator;
-
- public UnaryOperator(OperatorToken operatorToken) {
- this(operatorToken.getPosition(), operatorToken.operator);
- }
-
- public UnaryOperator(int position, String operator) {
- super(position);
- this.operator = operator;
- }
-
- @Override
- public char id() {
- return 'p';
- }
-
- @Override
- public String toString() {
- return "UnaryOperator(" + operator + ")";
- }
-
-}
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/parser/UnboundVariable.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/parser/UnboundVariable.java
deleted file mode 100644
index a128bc866..000000000
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/parser/UnboundVariable.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * WorldEdit, a Minecraft world manipulation toolkit
- * Copyright (C) sk89q
- * Copyright (C) WorldEdit team and contributors
- *
- * This program is free software: you can redistribute it and/or modify it
- * under the terms of the GNU Lesser General Public License as published by the
- * Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
- * for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program. If not, see .
- */
-
-package com.sk89q.worldedit.internal.expression.parser;
-
-import com.sk89q.worldedit.internal.expression.Expression;
-import com.sk89q.worldedit.internal.expression.runtime.EvaluationException;
-import com.sk89q.worldedit.internal.expression.runtime.LValue;
-import com.sk89q.worldedit.internal.expression.runtime.RValue;
-
-public class UnboundVariable extends PseudoToken implements LValue {
-
- public final String name;
-
- public UnboundVariable(int position, String name) {
- super(position);
- this.name = name;
- }
-
- @Override
- public char id() {
- return 'V';
- }
-
- @Override
- public String toString() {
- return "UnboundVariable(" + name + ")";
- }
-
- @Override
- public double getValue() throws EvaluationException {
- throw new EvaluationException(getPosition(), "Tried to evaluate unbound variable!");
- }
-
- @Override
- public LValue optimize() throws EvaluationException {
- throw new EvaluationException(getPosition(), "Tried to optimize unbound variable!");
- }
-
- @Override
- public double assign(double value) throws EvaluationException {
- throw new EvaluationException(getPosition(), "Tried to assign unbound variable!");
- }
-
- public RValue bind(Expression expression, boolean isLValue) throws ParserException {
- final RValue variable = expression.getVariable(name, isLValue);
- if (variable == null) {
- throw new ParserException(getPosition(), "Variable '" + name + "' not found");
- }
-
- return variable;
- }
-
- @Override
- public LValue bindVariables(Expression expression, boolean preferLValue) throws ParserException {
- final RValue variable = expression.getVariable(name, preferLValue);
- if (variable == null) {
- throw new ParserException(getPosition(), "Variable '" + name + "' not found");
- }
-
- return (LValue) variable;
- }
-
-}
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/runtime/Break.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/runtime/Break.java
deleted file mode 100644
index 00bcf9936..000000000
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/runtime/Break.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * WorldEdit, a Minecraft world manipulation toolkit
- * Copyright (C) sk89q
- * Copyright (C) WorldEdit team and contributors
- *
- * This program is free software: you can redistribute it and/or modify it
- * under the terms of the GNU Lesser General Public License as published by the
- * Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
- * for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program. If not, see .
- */
-
-package com.sk89q.worldedit.internal.expression.runtime;
-
-/**
- * A break or continue statement.
- */
-public class Break extends Node {
-
- boolean doContinue;
-
- public Break(int position, boolean doContinue) {
- super(position);
-
- this.doContinue = doContinue;
- }
-
- @Override
- public double getValue() throws EvaluationException {
- throw new BreakException(doContinue);
- }
-
- @Override
- public char id() {
- return 'b';
- }
-
- @Override
- public String toString() {
- return doContinue ? "continue" : "break";
- }
-
-}
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/runtime/Conditional.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/runtime/Conditional.java
deleted file mode 100644
index 4579004af..000000000
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/runtime/Conditional.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * WorldEdit, a Minecraft world manipulation toolkit
- * Copyright (C) sk89q
- * Copyright (C) WorldEdit team and contributors
- *
- * This program is free software: you can redistribute it and/or modify it
- * under the terms of the GNU Lesser General Public License as published by the
- * Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
- * for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program. If not, see .
- */
-
-package com.sk89q.worldedit.internal.expression.runtime;
-
-import com.sk89q.worldedit.internal.expression.Expression;
-import com.sk89q.worldedit.internal.expression.parser.ParserException;
-
-/**
- * An if/else statement or a ternary operator.
- */
-public class Conditional extends Node {
-
- private RValue condition;
- private RValue truePart;
- private RValue falsePart;
-
- public Conditional(int position, RValue condition, RValue truePart, RValue falsePart) {
- super(position);
-
- this.condition = condition;
- this.truePart = truePart;
- this.falsePart = falsePart;
- }
-
- @Override
- public double getValue() throws EvaluationException {
- if (condition.getValue() > 0.0) {
- return truePart.getValue();
- } else {
- return falsePart == null ? 0.0 : falsePart.getValue();
- }
- }
-
- @Override
- public char id() {
- return 'I';
- }
-
- @Override
- public String toString() {
- if (falsePart == null) {
- return "if (" + condition + ") { " + truePart + " }";
- } else if (truePart instanceof Sequence || falsePart instanceof Sequence) {
- return "if (" + condition + ") { " + truePart + " } else { " + falsePart + " }";
- } else {
- return "(" + condition + ") ? (" + truePart + ") : (" + falsePart + ")";
- }
- }
-
- @Override
- public RValue optimize() throws EvaluationException {
- final RValue newCondition = condition.optimize();
-
- if (newCondition instanceof Constant) {
- if (newCondition.getValue() > 0) {
- return truePart.optimize();
- } else {
- return falsePart == null ? new Constant(getPosition(), 0.0) : falsePart.optimize();
- }
- }
-
- return new Conditional(getPosition(), newCondition, truePart.optimize(), falsePart == null ? null : falsePart.optimize());
- }
-
- @Override
- public RValue bindVariables(Expression expression, boolean preferLValue) throws ParserException {
- condition = condition.bindVariables(expression, false);
- truePart = truePart.bindVariables(expression, false);
- if (falsePart != null) {
- falsePart = falsePart.bindVariables(expression, false);
- }
-
- return this;
- }
-
-}
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/runtime/Constant.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/runtime/Constant.java
deleted file mode 100644
index 5bdab9e1c..000000000
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/runtime/Constant.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * WorldEdit, a Minecraft world manipulation toolkit
- * Copyright (C) sk89q
- * Copyright (C) WorldEdit team and contributors
- *
- * This program is free software: you can redistribute it and/or modify it
- * under the terms of the GNU Lesser General Public License as published by the
- * Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
- * for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program. If not, see .
- */
-
-package com.sk89q.worldedit.internal.expression.runtime;
-
-/**
- * A constant.
- */
-public final class Constant extends Node {
-
- private final double value;
-
- public Constant(int position, double value) {
- super(position);
- this.value = value;
- }
-
- @Override
- public double getValue() {
- return value;
- }
-
- @Override
- public String toString() {
- return String.valueOf(value);
- }
-
- @Override
- public char id() {
- return 'c';
- }
-
-}
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/runtime/For.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/runtime/For.java
deleted file mode 100644
index 6334a7350..000000000
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/runtime/For.java
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * WorldEdit, a Minecraft world manipulation toolkit
- * Copyright (C) sk89q
- * Copyright (C) WorldEdit team and contributors
- *
- * This program is free software: you can redistribute it and/or modify it
- * under the terms of the GNU Lesser General Public License as published by the
- * Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
- * for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program. If not, see .
- */
-
-package com.sk89q.worldedit.internal.expression.runtime;
-
-import com.sk89q.worldedit.internal.expression.Expression;
-import com.sk89q.worldedit.internal.expression.parser.ParserException;
-
-/**
- * A Java/C-style for loop.
- */
-public class For extends Node {
-
- RValue init;
- RValue condition;
- RValue increment;
- RValue body;
-
- public For(int position, RValue init, RValue condition, RValue increment, RValue body) {
- super(position);
-
- this.init = init;
- this.condition = condition;
- this.increment = increment;
- this.body = body;
- }
-
- @Override
- public double getValue() throws EvaluationException {
- int iterations = 0;
- double ret = 0.0;
-
- for (init.getValue(); condition.getValue() > 0; increment.getValue()) {
- if (iterations > 256) {
- throw new EvaluationException(getPosition(), "Loop exceeded 256 iterations.");
- }
- if (Thread.interrupted()) {
- throw new EvaluationException(getPosition(), "Calculations exceeded time limit.");
- }
- ++iterations;
-
- try {
- ret = body.getValue();
- } catch (BreakException e) {
- if (e.doContinue) {
- //noinspection UnnecessaryContinue
- continue;
- } else {
- break;
- }
- }
- }
-
- return ret;
- }
-
- @Override
- public char id() {
- return 'F';
- }
-
- @Override
- public String toString() {
- return "for (" + init + "; " + condition + "; " + increment + ") { " + body + " }";
- }
-
- @Override
- public RValue optimize() throws EvaluationException {
- final RValue newCondition = condition.optimize();
-
- if (newCondition instanceof Constant && newCondition.getValue() <= 0) {
- // If the condition is always false, the loop can be flattened.
- // So we run the init part and then return 0.0.
- return new Sequence(getPosition(), init, new Constant(getPosition(), 0.0)).optimize();
- }
-
- //return new Sequence(getPosition(), init.optimize(), new While(getPosition(), condition, new Sequence(getPosition(), body, increment), false)).optimize();
- return new For(getPosition(), init.optimize(), newCondition, increment.optimize(), body.optimize());
- }
-
- @Override
- public RValue bindVariables(Expression expression, boolean preferLValue) throws ParserException {
- init = init.bindVariables(expression, false);
- condition = condition.bindVariables(expression, false);
- increment = increment.bindVariables(expression, false);
- body = body.bindVariables(expression, false);
-
- return this;
- }
-
-}
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/runtime/Function.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/runtime/Function.java
deleted file mode 100644
index 1fef4a580..000000000
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/runtime/Function.java
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- * WorldEdit, a Minecraft world manipulation toolkit
- * Copyright (C) sk89q
- * Copyright (C) WorldEdit team and contributors
- *
- * This program is free software: you can redistribute it and/or modify it
- * under the terms of the GNU Lesser General Public License as published by the
- * Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
- * for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program. If not, see .
- */
-
-package com.sk89q.worldedit.internal.expression.runtime;
-
-import com.sk89q.worldedit.internal.expression.Expression;
-import com.sk89q.worldedit.internal.expression.parser.ParserException;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-
-/**
- * Wrapper for a Java method and its arguments (other Nodes).
- */
-public class Function extends Node {
-
- /**
- * Add this annotation on functions that don't always return the same value
- * for the same inputs and on functions with side-effects.
- */
- @Retention(RetentionPolicy.RUNTIME)
- public @interface Dynamic { }
-
- final Method method;
- final RValue[] args;
-
- Function(int position, Method method, RValue... args) {
- super(position);
- this.method = method;
- this.args = args;
- }
-
- @Override
- public final double getValue() throws EvaluationException {
- return invokeMethod(method, args);
- }
-
- protected static double invokeMethod(Method method, Object[] args) throws EvaluationException {
- try {
- return (Double) method.invoke(null, args);
- } catch (InvocationTargetException e) {
- if (e.getTargetException() instanceof EvaluationException) {
- throw (EvaluationException) e.getTargetException();
- }
- throw new EvaluationException(-1, "Exception caught while evaluating expression", e.getTargetException());
- } catch (IllegalAccessException e) {
- throw new EvaluationException(-1, "Internal error while evaluating expression", e);
- }
- }
-
- @Override
- public String toString() {
- final StringBuilder ret = new StringBuilder(method.getName()).append('(');
- boolean first = true;
- for (Object obj : args) {
- if (!first) {
- ret.append(", ");
- }
- first = false;
- ret.append(obj);
- }
- return ret.append(')').toString();
- }
-
- @Override
- public char id() {
- return 'f';
- }
-
- @Override
- public RValue optimize() throws EvaluationException {
- final RValue[] optimizedArgs = new RValue[args.length];
- boolean optimizable = !method.isAnnotationPresent(Dynamic.class);
- int position = getPosition();
- for (int i = 0; i < args.length; ++i) {
- final RValue optimized = optimizedArgs[i] = args[i].optimize();
-
- if (!(optimized instanceof Constant)) {
- optimizable = false;
- }
-
- if (optimized.getPosition() < position) {
- position = optimized.getPosition();
- }
- }
-
- if (optimizable) {
- return new Constant(position, invokeMethod(method, optimizedArgs));
- } else {
- return new Function(position, method, optimizedArgs);
- }
- }
-
- @Override
- public RValue bindVariables(Expression expression, boolean preferLValue) throws ParserException {
- final Class>[] parameters = method.getParameterTypes();
- for (int i = 0; i < args.length; ++i) {
- final boolean argumentPrefersLValue = LValue.class.isAssignableFrom(parameters[i]);
- args[i] = args[i].bindVariables(expression, argumentPrefersLValue);
- }
-
- return this;
- }
-
-}
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/runtime/Functions.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/runtime/Functions.java
deleted file mode 100644
index d2aede416..000000000
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/runtime/Functions.java
+++ /dev/null
@@ -1,487 +0,0 @@
-/*
- * WorldEdit, a Minecraft world manipulation toolkit
- * Copyright (C) sk89q
- * Copyright (C) WorldEdit team and contributors
- *
- * This program is free software: you can redistribute it and/or modify it
- * under the terms of the GNU Lesser General Public License as published by the
- * Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
- * for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program. If not, see .
- */
-
-package com.sk89q.worldedit.internal.expression.runtime;
-
-import com.sk89q.worldedit.internal.expression.Expression;
-import com.sk89q.worldedit.internal.expression.runtime.Function.Dynamic;
-import com.sk89q.worldedit.math.Vector3;
-import com.sk89q.worldedit.math.noise.PerlinNoise;
-import com.sk89q.worldedit.math.noise.RidgedMultiFractalNoise;
-import com.sk89q.worldedit.math.noise.VoronoiNoise;
-
-import java.lang.reflect.Method;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Random;
-
-/**
- * Contains all functions that can be used in expressions.
- */
-@SuppressWarnings("UnusedDeclaration")
-public final class Functions {
-
- private static class Overload {
- private final Method method;
- private final int mask;
- private final boolean isSetter;
-
- private Overload(Method method) throws IllegalArgumentException {
- this.method = method;
-
- boolean isSetter = false;
- int accum = 0;
- Class>[] parameters = method.getParameterTypes();
- for (Class> parameter : parameters) {
- if (isSetter) {
- throw new IllegalArgumentException("Method takes arguments that can't be cast to RValue.");
- }
-
- if (double.class.equals(parameter)) {
- isSetter = true;
- continue;
- }
-
- if (!RValue.class.isAssignableFrom(parameter)) {
- throw new IllegalArgumentException("Method takes arguments that can't be cast to RValue.");
- }
-
- accum <<= 2;
-
- if (LValue.class.isAssignableFrom(parameter)) {
- accum |= 3;
- } else {
- accum |= 1;
- }
- }
- mask = accum;
- this.isSetter = isSetter;
- }
-
- public boolean matches(boolean isSetter, RValue... args) {
- if (this.isSetter != isSetter) {
- return false;
- }
-
- if (this.method.getParameterTypes().length != args.length) { // TODO: optimize
- return false;
- }
-
- int accum = 0;
- for (RValue argument : args) {
- accum <<= 2;
-
- if (argument instanceof LValue) {
- accum |= 3;
- } else {
- accum |= 1;
- }
- }
-
- return (accum & mask) == mask;
- }
- }
-
- public static Function getFunction(int position, String name, RValue... args) throws NoSuchMethodException {
- final Method getter = getMethod(name, false, args);
- try {
- Method setter = getMethod(name, true, args);
- return new LValueFunction(position, getter, setter, args);
- } catch (NoSuchMethodException e) {
- return new Function(position, getter, args);
- }
- }
-
- private static Method getMethod(String name, boolean isSetter, RValue... args) throws NoSuchMethodException {
- final List overloads = functions.get(name);
- if (overloads != null) {
- for (Overload overload : overloads) {
- if (overload.matches(isSetter, args)) {
- return overload.method;
- }
- }
- }
-
- throw new NoSuchMethodException(); // TODO: return null (check for side-effects first)
- }
-
- private static final Map> functions = new HashMap<>();
- static {
- for (Method method : Functions.class.getMethods()) {
- try {
- addFunction(method);
- } catch (IllegalArgumentException ignored) { }
- }
- }
-
-
- public static void addFunction(Method method) throws IllegalArgumentException {
- final String methodName = method.getName();
-
- Overload overload = new Overload(method);
-
- List overloads = functions.computeIfAbsent(methodName, k -> new ArrayList<>());
-
- overloads.add(overload);
- }
-
-
- public static double sin(RValue x) throws EvaluationException {
- return Math.sin(x.getValue());
- }
-
- public static double cos(RValue x) throws EvaluationException {
- return Math.cos(x.getValue());
- }
-
- public static double tan(RValue x) throws EvaluationException {
- return Math.tan(x.getValue());
- }
-
-
- public static double asin(RValue x) throws EvaluationException {
- return Math.asin(x.getValue());
- }
-
- public static double acos(RValue x) throws EvaluationException {
- return Math.acos(x.getValue());
- }
-
- public static double atan(RValue x) throws EvaluationException {
- return Math.atan(x.getValue());
- }
-
- public static double atan2(RValue y, RValue x) throws EvaluationException {
- return Math.atan2(y.getValue(), x.getValue());
- }
-
-
- public static double sinh(RValue x) throws EvaluationException {
- return Math.sinh(x.getValue());
- }
-
- public static double cosh(RValue x) throws EvaluationException {
- return Math.cosh(x.getValue());
- }
-
- public static double tanh(RValue x) throws EvaluationException {
- return Math.tanh(x.getValue());
- }
-
-
- public static double sqrt(RValue x) throws EvaluationException {
- return Math.sqrt(x.getValue());
- }
-
- public static double cbrt(RValue x) throws EvaluationException {
- return Math.cbrt(x.getValue());
- }
-
-
- public static double abs(RValue x) throws EvaluationException {
- return Math.abs(x.getValue());
- }
-
- public static double min(RValue a, RValue b) throws EvaluationException {
- return Math.min(a.getValue(), b.getValue());
- }
-
- public static double min(RValue a, RValue b, RValue c) throws EvaluationException {
- return Math.min(a.getValue(), Math.min(b.getValue(), c.getValue()));
- }
-
- public static double max(RValue a, RValue b) throws EvaluationException {
- return Math.max(a.getValue(), b.getValue());
- }
-
- public static double max(RValue a, RValue b, RValue c) throws EvaluationException {
- return Math.max(a.getValue(), Math.max(b.getValue(), c.getValue()));
- }
-
-
- public static double ceil(RValue x) throws EvaluationException {
- return Math.ceil(x.getValue());
- }
-
- public static double floor(RValue x) throws EvaluationException {
- return Math.floor(x.getValue());
- }
-
- public static double rint(RValue x) throws EvaluationException {
- return Math.rint(x.getValue());
- }
-
- public static double round(RValue x) throws EvaluationException {
- return Math.round(x.getValue());
- }
-
-
- public static double exp(RValue x) throws EvaluationException {
- return Math.exp(x.getValue());
- }
-
- public static double ln(RValue x) throws EvaluationException {
- return Math.log(x.getValue());
- }
-
- public static double log(RValue x) throws EvaluationException {
- return Math.log(x.getValue());
- }
-
- public static double log10(RValue x) throws EvaluationException {
- return Math.log10(x.getValue());
- }
-
-
- public static double rotate(LValue x, LValue y, RValue angle) throws EvaluationException {
- final double f = angle.getValue();
-
- final double cosF = Math.cos(f);
- final double sinF = Math.sin(f);
-
- final double xOld = x.getValue();
- final double yOld = y.getValue();
-
- x.assign(xOld * cosF - yOld * sinF);
- y.assign(xOld * sinF + yOld * cosF);
-
- return 0.0;
- }
-
- public static double swap(LValue x, LValue y) throws EvaluationException {
- final double tmp = x.getValue();
-
- x.assign(y.getValue());
- y.assign(tmp);
-
- return 0.0;
- }
-
-
- private static final Map gmegabuf = new HashMap<>();
- private final Map megabuf = new HashMap<>();
-
- public Map getMegabuf() {
- return megabuf;
- }
-
- private static double[] getSubBuffer(Map megabuf, Integer key) {
- double[] ret = megabuf.get(key);
- if (ret == null) {
- megabuf.put(key, ret = new double[1024]);
- }
- return ret;
- }
-
- private static double getBufferItem(final Map megabuf, final int index) {
- return getSubBuffer(megabuf, index & ~1023)[index & 1023];
- }
-
- private static double setBufferItem(final Map megabuf, final int index, double value) {
- return getSubBuffer(megabuf, index & ~1023)[index & 1023] = value;
- }
-
- @Dynamic
- public static double gmegabuf(RValue index) throws EvaluationException {
- return getBufferItem(gmegabuf, (int) index.getValue());
- }
-
- @Dynamic
- public static double gmegabuf(RValue index, double value) throws EvaluationException {
- return setBufferItem(gmegabuf, (int) index.getValue(), value);
- }
-
- @Dynamic
- public static double megabuf(RValue index) throws EvaluationException {
- return getBufferItem(Expression.getInstance().getFunctions().megabuf, (int) index.getValue());
- }
-
- @Dynamic
- public static double megabuf(RValue index, double value) throws EvaluationException {
- return setBufferItem(Expression.getInstance().getFunctions().megabuf, (int) index.getValue(), value);
- }
-
- @Dynamic
- public static double closest(RValue x, RValue y, RValue z, RValue index, RValue count, RValue stride) throws EvaluationException {
- return findClosest(
- Expression.getInstance().getFunctions().megabuf,
- x.getValue(),
- y.getValue(),
- z.getValue(),
- (int) index.getValue(),
- (int) count.getValue(),
- (int) stride.getValue()
- );
- }
-
- @Dynamic
- public static double gclosest(RValue x, RValue y, RValue z, RValue index, RValue count, RValue stride) throws EvaluationException {
- return findClosest(
- gmegabuf,
- x.getValue(),
- y.getValue(),
- z.getValue(),
- (int) index.getValue(),
- (int) count.getValue(),
- (int) stride.getValue()
- );
- }
-
- private static double findClosest(Map megabuf, double x, double y, double z, int index, int count, int stride) {
- int closestIndex = -1;
- double minDistanceSquared = Double.MAX_VALUE;
-
- for (int i = 0; i < count; ++i) {
- double currentX = getBufferItem(megabuf, index+0) - x;
- double currentY = getBufferItem(megabuf, index+1) - y;
- double currentZ = getBufferItem(megabuf, index+2) - z;
-
- double currentDistanceSquared = currentX*currentX + currentY*currentY + currentZ*currentZ;
-
- if (currentDistanceSquared < minDistanceSquared) {
- minDistanceSquared = currentDistanceSquared;
- closestIndex = index;
- }
-
- index += stride;
- }
-
- return closestIndex;
- }
-
-
- private static final Random random = new Random();
-
- @Dynamic
- public static double random() {
- return random.nextDouble();
- }
-
- @Dynamic
- public static double randint(RValue max) throws EvaluationException {
- return random.nextInt((int) Math.floor(max.getValue()));
- }
-
- private static final ThreadLocal localPerlin = ThreadLocal.withInitial(PerlinNoise::new);
-
- public static double perlin(RValue seed, RValue x, RValue y, RValue z, RValue frequency, RValue octaves, RValue persistence) throws EvaluationException {
- PerlinNoise perlin = localPerlin.get();
- try {
- perlin.setSeed((int) seed.getValue());
- perlin.setFrequency(frequency.getValue());
- perlin.setOctaveCount((int) octaves.getValue());
- perlin.setPersistence(persistence.getValue());
- } catch (IllegalArgumentException e) {
- throw new EvaluationException(0, "Perlin noise error: " + e.getMessage());
- }
- return perlin.noise(Vector3.at(x.getValue(), y.getValue(), z.getValue()));
- }
-
- private static final ThreadLocal localVoronoi = ThreadLocal.withInitial(VoronoiNoise::new);
-
- public static double voronoi(RValue seed, RValue x, RValue y, RValue z, RValue frequency) throws EvaluationException {
- VoronoiNoise voronoi = localVoronoi.get();
- try {
- voronoi.setSeed((int) seed.getValue());
- voronoi.setFrequency(frequency.getValue());
- } catch (IllegalArgumentException e) {
- throw new EvaluationException(0, "Voronoi error: " + e.getMessage());
- }
- return voronoi.noise(Vector3.at(x.getValue(), y.getValue(), z.getValue()));
- }
-
- private static final ThreadLocal localRidgedMulti = ThreadLocal.withInitial(RidgedMultiFractalNoise::new);
-
- public static double ridgedmulti(RValue seed, RValue x, RValue y, RValue z, RValue frequency, RValue octaves) throws EvaluationException {
- RidgedMultiFractalNoise ridgedMulti = localRidgedMulti.get();
- try {
- ridgedMulti.setSeed((int) seed.getValue());
- ridgedMulti.setFrequency(frequency.getValue());
- ridgedMulti.setOctaveCount((int) octaves.getValue());
- } catch (IllegalArgumentException e) {
- throw new EvaluationException(0, "Ridged multi error: " + e.getMessage());
- }
- return ridgedMulti.noise(Vector3.at(x.getValue(), y.getValue(), z.getValue()));
- }
-
- private static double queryInternal(RValue type, RValue data, double typeId, double dataValue) throws EvaluationException {
- // Compare to input values and determine return value
- // -1 is a wildcard, always true
- final double ret = ((type.getValue() == -1 || typeId == type.getValue())
- && (data.getValue() == -1 || dataValue == data.getValue())) ? 1.0 : 0.0;
-
- if (type instanceof LValue) {
- ((LValue) type).assign(typeId);
- }
-
- if (data instanceof LValue) {
- ((LValue) data).assign(dataValue);
- }
-
- return ret;
- }
-
- @Dynamic
- public static double query(RValue x, RValue y, RValue z, RValue type, RValue data) throws EvaluationException {
- final double xp = x.getValue();
- final double yp = y.getValue();
- final double zp = z.getValue();
-
- final ExpressionEnvironment environment = Expression.getInstance().getEnvironment();
-
- // Read values from world
- final double typeId = environment.getBlockType(xp, yp, zp);
- final double dataValue = environment.getBlockData(xp, yp, zp);
-
- return queryInternal(type, data, typeId, dataValue);
- }
-
- @Dynamic
- public static double queryAbs(RValue x, RValue y, RValue z, RValue type, RValue data) throws EvaluationException {
- final double xp = x.getValue();
- final double yp = y.getValue();
- final double zp = z.getValue();
-
- final ExpressionEnvironment environment = Expression.getInstance().getEnvironment();
-
- // Read values from world
- final double typeId = environment.getBlockTypeAbs(xp, yp, zp);
- final double dataValue = environment.getBlockDataAbs(xp, yp, zp);
-
- return queryInternal(type, data, typeId, dataValue);
- }
-
- @Dynamic
- public static double queryRel(RValue x, RValue y, RValue z, RValue type, RValue data) throws EvaluationException {
- final double xp = x.getValue();
- final double yp = y.getValue();
- final double zp = z.getValue();
-
- final ExpressionEnvironment environment = Expression.getInstance().getEnvironment();
-
- // Read values from world
- final double typeId = environment.getBlockTypeRel(xp, yp, zp);
- final double dataValue = environment.getBlockDataRel(xp, yp, zp);
-
- return queryInternal(type, data, typeId, dataValue);
- }
-
-}
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/runtime/LValue.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/runtime/LValue.java
deleted file mode 100644
index 338cc74b2..000000000
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/runtime/LValue.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * WorldEdit, a Minecraft world manipulation toolkit
- * Copyright (C) sk89q
- * Copyright (C) WorldEdit team and contributors
- *
- * This program is free software: you can redistribute it and/or modify it
- * under the terms of the GNU Lesser General Public License as published by the
- * Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
- * for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program. If not, see .
- */
-
-package com.sk89q.worldedit.internal.expression.runtime;
-
-import com.sk89q.worldedit.internal.expression.Expression;
-import com.sk89q.worldedit.internal.expression.parser.ParserException;
-
-/**
- * A value that can be used on the left side of an assignment.
- */
-public interface LValue extends RValue {
-
- double assign(double value) throws EvaluationException;
-
- @Override
- LValue optimize() throws EvaluationException;
-
- @Override
- LValue bindVariables(Expression expression, boolean preferLValue) throws ParserException;
-
-}
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/runtime/LValueFunction.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/runtime/LValueFunction.java
deleted file mode 100644
index de145514a..000000000
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/runtime/LValueFunction.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * WorldEdit, a Minecraft world manipulation toolkit
- * Copyright (C) sk89q
- * Copyright (C) WorldEdit team and contributors
- *
- * This program is free software: you can redistribute it and/or modify it
- * under the terms of the GNU Lesser General Public License as published by the
- * Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
- * for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program. If not, see .
- */
-
-package com.sk89q.worldedit.internal.expression.runtime;
-
-import com.sk89q.worldedit.internal.expression.Expression;
-import com.sk89q.worldedit.internal.expression.parser.ParserException;
-
-import java.lang.reflect.Method;
-
-/**
- * Wrapper for a pair of Java methods and their arguments (other Nodes),
- * forming an LValue.
- */
-public class LValueFunction extends Function implements LValue {
-
- private final Object[] setterArgs;
- private final Method setter;
-
- LValueFunction(int position, Method getter, Method setter, RValue... args) {
- super(position, getter, args);
- assert (getter.isAnnotationPresent(Dynamic.class));
-
- setterArgs = new Object[args.length + 1];
- System.arraycopy(args, 0, setterArgs, 0, args.length);
- this.setter = setter;
- }
-
- @Override
- public char id() {
- return 'l';
- }
-
- @Override
- public double assign(double value) throws EvaluationException {
- setterArgs[setterArgs.length - 1] = value;
- return invokeMethod(setter, setterArgs);
- }
-
- @Override
- public LValue optimize() throws EvaluationException {
- final RValue optimized = super.optimize();
- if (optimized == this) {
- return this;
- }
-
- if (optimized instanceof Function) {
- return new LValueFunction(optimized.getPosition(), method, setter, ((Function) optimized).args);
- }
-
- return (LValue) optimized;
- }
-
- @Override
- public LValue bindVariables(Expression expression, boolean preferLValue) throws ParserException {
- super.bindVariables(expression, preferLValue);
-
- return this;
- }
-
-}
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/runtime/Node.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/runtime/Node.java
deleted file mode 100644
index 1c1836c4d..000000000
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/runtime/Node.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * WorldEdit, a Minecraft world manipulation toolkit
- * Copyright (C) sk89q
- * Copyright (C) WorldEdit team and contributors
- *
- * This program is free software: you can redistribute it and/or modify it
- * under the terms of the GNU Lesser General Public License as published by the
- * Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
- * for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program. If not, see .
- */
-
-package com.sk89q.worldedit.internal.expression.runtime;
-
-import com.sk89q.worldedit.internal.expression.Expression;
-import com.sk89q.worldedit.internal.expression.parser.ParserException;
-
-/**
- * A node in the execution tree of an expression.
- */
-public abstract class Node implements RValue {
-
- private final int position;
-
- public Node(int position) {
- this.position = position;
- }
-
- @Override
- public abstract String toString();
-
- @Override
- public RValue optimize() throws EvaluationException {
- return this;
- }
-
- @Override
- public final int getPosition() {
- return position;
- }
-
- @Override
- public RValue bindVariables(Expression expression, boolean preferLValue) throws ParserException {
- return this;
- }
-
-}
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/runtime/Operators.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/runtime/Operators.java
deleted file mode 100644
index cb4f9d78b..000000000
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/runtime/Operators.java
+++ /dev/null
@@ -1,228 +0,0 @@
-/*
- * WorldEdit, a Minecraft world manipulation toolkit
- * Copyright (C) sk89q
- * Copyright (C) WorldEdit team and contributors
- *
- * This program is free software: you can redistribute it and/or modify it
- * under the terms of the GNU Lesser General Public License as published by the
- * Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
- * for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program. If not, see .
- */
-
-package com.sk89q.worldedit.internal.expression.runtime;
-
-/**
- * Contains all unary and binary operators.
- */
-@SuppressWarnings("UnusedDeclaration")
-public final class Operators {
-
- private Operators() {
- }
-
- public static Function getOperator(int position, String name, RValue lhs, RValue rhs) throws NoSuchMethodException {
- if (lhs instanceof LValue) {
- try {
- return new Function(position, Operators.class.getMethod(name, LValue.class, RValue.class), lhs, rhs);
- } catch (NoSuchMethodException ignored) { }
- }
- return new Function(position, Operators.class.getMethod(name, RValue.class, RValue.class), lhs, rhs);
- }
-
- public static Function getOperator(int position, String name, RValue argument) throws NoSuchMethodException {
- if (argument instanceof LValue) {
- try {
- return new Function(position, Operators.class.getMethod(name, LValue.class), argument);
- } catch (NoSuchMethodException ignored) { }
- }
- return new Function(position, Operators.class.getMethod(name, RValue.class), argument);
- }
-
-
- public static double add(RValue lhs, RValue rhs) throws EvaluationException {
- return lhs.getValue() + rhs.getValue();
- }
-
- public static double sub(RValue lhs, RValue rhs) throws EvaluationException {
- return lhs.getValue() - rhs.getValue();
- }
-
- public static double mul(RValue lhs, RValue rhs) throws EvaluationException {
- return lhs.getValue() * rhs.getValue();
- }
-
- public static double div(RValue lhs, RValue rhs) throws EvaluationException {
- return lhs.getValue() / rhs.getValue();
- }
-
- public static double mod(RValue lhs, RValue rhs) throws EvaluationException {
- return lhs.getValue() % rhs.getValue();
- }
-
- public static double pow(RValue lhs, RValue rhs) throws EvaluationException {
- return Math.pow(lhs.getValue(), rhs.getValue());
- }
-
-
- public static double neg(RValue x) throws EvaluationException {
- return -x.getValue();
- }
-
- public static double not(RValue x) throws EvaluationException {
- return x.getValue() > 0.0 ? 0.0 : 1.0;
- }
-
- public static double inv(RValue x) throws EvaluationException {
- return ~(long) x.getValue();
- }
-
-
- public static double lth(RValue lhs, RValue rhs) throws EvaluationException {
- return lhs.getValue() < rhs.getValue() ? 1.0 : 0.0;
- }
-
- public static double gth(RValue lhs, RValue rhs) throws EvaluationException {
- return lhs.getValue() > rhs.getValue() ? 1.0 : 0.0;
- }
-
- public static double leq(RValue lhs, RValue rhs) throws EvaluationException {
- return lhs.getValue() <= rhs.getValue() ? 1.0 : 0.0;
- }
-
- public static double geq(RValue lhs, RValue rhs) throws EvaluationException {
- return lhs.getValue() >= rhs.getValue() ? 1.0 : 0.0;
- }
-
-
- public static double equ(RValue lhs, RValue rhs) throws EvaluationException {
- return lhs.getValue() == rhs.getValue() ? 1.0 : 0.0;
- }
-
- public static double neq(RValue lhs, RValue rhs) throws EvaluationException {
- return lhs.getValue() != rhs.getValue() ? 1.0 : 0.0;
- }
-
- public static double near(RValue lhs, RValue rhs) throws EvaluationException {
- return almostEqual2sComplement(lhs.getValue(), rhs.getValue(), 450359963L) ? 1.0 : 0.0;
- //return Math.abs(lhs.invoke() - rhs.invoke()) < 1e-7 ? 1.0 : 0.0;
- }
-
-
- public static double or(RValue lhs, RValue rhs) throws EvaluationException {
- return lhs.getValue() > 0.0 || rhs.getValue() > 0.0 ? 1.0 : 0.0;
- }
-
- public static double and(RValue lhs, RValue rhs) throws EvaluationException {
- return lhs.getValue() > 0.0 && rhs.getValue() > 0.0 ? 1.0 : 0.0;
- }
-
-
- public static double shl(RValue lhs, RValue rhs) throws EvaluationException {
- return (long) lhs.getValue() << (long) rhs.getValue();
- }
-
- public static double shr(RValue lhs, RValue rhs) throws EvaluationException {
- return (long) lhs.getValue() >> (long) rhs.getValue();
- }
-
-
- public static double ass(LValue lhs, RValue rhs) throws EvaluationException {
- return lhs.assign(rhs.getValue());
- }
-
- public static double aadd(LValue lhs, RValue rhs) throws EvaluationException {
- return lhs.assign(lhs.getValue() + rhs.getValue());
- }
-
- public static double asub(LValue lhs, RValue rhs) throws EvaluationException {
- return lhs.assign(lhs.getValue() - rhs.getValue());
- }
-
- public static double amul(LValue lhs, RValue rhs) throws EvaluationException {
- return lhs.assign(lhs.getValue() * rhs.getValue());
- }
-
- public static double adiv(LValue lhs, RValue rhs) throws EvaluationException {
- return lhs.assign(lhs.getValue() / rhs.getValue());
- }
-
- public static double amod(LValue lhs, RValue rhs) throws EvaluationException {
- return lhs.assign(lhs.getValue() % rhs.getValue());
- }
-
- public static double aexp(LValue lhs, RValue rhs) throws EvaluationException {
- return lhs.assign(Math.pow(lhs.getValue(), rhs.getValue()));
- }
-
-
- public static double inc(LValue x) throws EvaluationException {
- return x.assign(x.getValue() + 1);
- }
-
- public static double dec(LValue x) throws EvaluationException {
- return x.assign(x.getValue() - 1);
- }
-
- public static double postinc(LValue x) throws EvaluationException {
- final double oldValue = x.getValue();
- x.assign(oldValue + 1);
- return oldValue;
- }
-
- public static double postdec(LValue x) throws EvaluationException {
- final double oldValue = x.getValue();
- x.assign(oldValue - 1);
- return oldValue;
- }
-
-
- private static final double[] factorials = new double[171];
- static {
- double accum = 1;
- factorials[0] = 1;
- for (int i = 1; i < factorials.length; ++i) {
- factorials[i] = accum *= i;
- }
- }
-
- public static double fac(RValue x) throws EvaluationException {
- final int n = (int) x.getValue();
-
- if (n < 0) {
- return 0;
- }
-
- if (n >= factorials.length) {
- return Double.POSITIVE_INFINITY;
- }
-
- return factorials[n];
- }
-
- // Usable AlmostEqual function, based on http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm
- private static boolean almostEqual2sComplement(double a, double b, long maxUlps) {
- // Make sure maxUlps is non-negative and small enough that the
- // default NAN won't compare as equal to anything.
- //assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024); // this is for floats, not doubles
-
- long aLong = Double.doubleToRawLongBits(a);
- // Make aLong lexicographically ordered as a twos-complement long
- if (aLong < 0) aLong = 0x8000000000000000L - aLong;
-
- long bLong = Double.doubleToRawLongBits(b);
- // Make bLong lexicographically ordered as a twos-complement long
- if (bLong < 0) bLong = 0x8000000000000000L - bLong;
-
- final long longDiff = Math.abs(aLong - bLong);
- return longDiff <= maxUlps;
- }
-
-}
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/runtime/RValue.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/runtime/RValue.java
deleted file mode 100644
index 45a654bcd..000000000
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/runtime/RValue.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * WorldEdit, a Minecraft world manipulation toolkit
- * Copyright (C) sk89q
- * Copyright (C) WorldEdit team and contributors
- *
- * This program is free software: you can redistribute it and/or modify it
- * under the terms of the GNU Lesser General Public License as published by the
- * Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
- * for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program. If not, see .
- */
-
-package com.sk89q.worldedit.internal.expression.runtime;
-
-import com.sk89q.worldedit.internal.expression.Expression;
-import com.sk89q.worldedit.internal.expression.Identifiable;
-import com.sk89q.worldedit.internal.expression.parser.ParserException;
-
-/**
- * A value that can be used on the right side of an assignment.
- */
-public interface RValue extends Identifiable {
-
- double getValue() throws EvaluationException;
-
- RValue optimize() throws EvaluationException;
-
- RValue bindVariables(Expression expression, boolean preferLValue) throws ParserException;
-
-}
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/runtime/Return.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/runtime/Return.java
deleted file mode 100644
index 455a09a3b..000000000
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/runtime/Return.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * WorldEdit, a Minecraft world manipulation toolkit
- * Copyright (C) sk89q
- * Copyright (C) WorldEdit team and contributors
- *
- * This program is free software: you can redistribute it and/or modify it
- * under the terms of the GNU Lesser General Public License as published by the
- * Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
- * for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program. If not, see .
- */
-
-package com.sk89q.worldedit.internal.expression.runtime;
-
-import com.sk89q.worldedit.internal.expression.Expression;
-import com.sk89q.worldedit.internal.expression.parser.ParserException;
-
-/**
- * A return statement.
- */
-public class Return extends Node {
-
- RValue value;
-
- public Return(int position, RValue value) {
- super(position);
-
- this.value = value;
- }
-
- @Override
- public double getValue() throws EvaluationException {
- throw new ReturnException(value.getValue());
- }
-
- @Override
- public char id() {
- return 'r';
- }
-
- @Override
- public String toString() {
- return "return " + value;
- }
-
- @Override
- public RValue bindVariables(Expression expression, boolean preferLValue) throws ParserException {
- value = value.bindVariables(expression, false);
-
- return this;
- }
-
-}
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/runtime/ReturnException.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/runtime/ReturnException.java
deleted file mode 100644
index 84b60021b..000000000
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/runtime/ReturnException.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * WorldEdit, a Minecraft world manipulation toolkit
- * Copyright (C) sk89q
- * Copyright (C) WorldEdit team and contributors
- *
- * This program is free software: you can redistribute it and/or modify it
- * under the terms of the GNU Lesser General Public License as published by the
- * Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
- * for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program. If not, see .
- */
-
-package com.sk89q.worldedit.internal.expression.runtime;
-
-/**
- * Thrown when a return statement is encountered.
- * {@link com.sk89q.worldedit.internal.expression.Expression#evaluate}
- * catches this exception and returns the enclosed value.
- */
-public class ReturnException extends EvaluationException {
-
- final double value;
-
- public ReturnException(double value) {
- super(-1);
-
- this.value = value;
- }
-
- public double getValue() {
- return value;
- }
-
-}
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/runtime/Sequence.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/runtime/Sequence.java
deleted file mode 100644
index f6458acce..000000000
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/runtime/Sequence.java
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * WorldEdit, a Minecraft world manipulation toolkit
- * Copyright (C) sk89q
- * Copyright (C) WorldEdit team and contributors
- *
- * This program is free software: you can redistribute it and/or modify it
- * under the terms of the GNU Lesser General Public License as published by the
- * Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
- * for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program. If not, see .
- */
-
-package com.sk89q.worldedit.internal.expression.runtime;
-
-import com.sk89q.worldedit.internal.expression.Expression;
-import com.sk89q.worldedit.internal.expression.parser.ParserException;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * A sequence of operations, usually separated by semicolons in the
- * input stream.
- */
-public class Sequence extends Node {
-
- final RValue[] sequence;
-
- public Sequence(int position, RValue... sequence) {
- super(position);
-
- this.sequence = sequence;
- }
-
- @Override
- public char id() {
- return 's';
- }
-
- @Override
- public double getValue() throws EvaluationException {
- double ret = 0;
- for (RValue invokable : sequence) {
- ret = invokable.getValue();
- }
- return ret;
- }
-
- @Override
- public String toString() {
- StringBuilder sb = new StringBuilder("seq(");
- boolean first = true;
- for (RValue invokable : sequence) {
- if (!first) {
- sb.append(", ");
- }
- sb.append(invokable);
- first = false;
- }
-
- return sb.append(')').toString();
- }
-
- @Override
- public RValue optimize() throws EvaluationException {
- final List newSequence = new ArrayList<>();
-
- RValue droppedLast = null;
- for (RValue invokable : sequence) {
- droppedLast = null;
- invokable = invokable.optimize();
- if (invokable instanceof Sequence) {
- Collections.addAll(newSequence, ((Sequence) invokable).sequence);
- } else if (invokable instanceof Constant) {
- droppedLast = invokable;
- } else {
- newSequence.add(invokable);
- }
- }
-
- if (droppedLast != null) {
- newSequence.add(droppedLast);
- }
-
- if (newSequence.size() == 1) {
- return newSequence.get(0);
- }
-
- return new Sequence(getPosition(), newSequence.toArray(new RValue[newSequence.size()]));
- }
-
- @Override
- public RValue bindVariables(Expression expression, boolean preferLValue) throws ParserException {
- for (int i = 0; i < sequence.length; ++i) {
- sequence[i] = sequence[i].bindVariables(expression, false);
- }
-
- return this;
- }
-
-}
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/runtime/SimpleFor.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/runtime/SimpleFor.java
deleted file mode 100644
index 1576c3484..000000000
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/runtime/SimpleFor.java
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * WorldEdit, a Minecraft world manipulation toolkit
- * Copyright (C) sk89q
- * Copyright (C) WorldEdit team and contributors
- *
- * This program is free software: you can redistribute it and/or modify it
- * under the terms of the GNU Lesser General Public License as published by the
- * Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
- * for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program. If not, see .
- */
-
-package com.sk89q.worldedit.internal.expression.runtime;
-
-import com.sk89q.worldedit.internal.expression.Expression;
-import com.sk89q.worldedit.internal.expression.parser.ParserException;
-
-/**
- * A simple-style for loop.
- */
-public class SimpleFor extends Node {
-
- LValue counter;
- RValue first;
- RValue last;
- RValue body;
-
- public SimpleFor(int position, LValue counter, RValue first, RValue last, RValue body) {
- super(position);
-
- this.counter = counter;
- this.first = first;
- this.last = last;
- this.body = body;
- }
-
- @Override
- public double getValue() throws EvaluationException {
- int iterations = 0;
- double ret = 0.0;
-
- double firstValue = first.getValue();
- double lastValue = last.getValue();
-
- for (double i = firstValue; i <= lastValue; ++i) {
- if (iterations > 256) {
- throw new EvaluationException(getPosition(), "Loop exceeded 256 iterations.");
- }
- if (Thread.interrupted()) {
- throw new EvaluationException(getPosition(), "Calculations exceeded time limit.");
- }
- ++iterations;
-
- try {
- counter.assign(i);
- ret = body.getValue();
- } catch (BreakException e) {
- if (e.doContinue) {
- //noinspection UnnecessaryContinue
- continue;
- } else {
- break;
- }
- }
- }
-
- return ret;
- }
-
- @Override
- public char id() {
- return 'S';
- }
-
- @Override
- public String toString() {
- return "for (" + counter + " = " + first + ", " + last + ") { " + body + " }";
- }
-
- @Override
- public RValue optimize() throws EvaluationException {
- // TODO: unroll small loops into Sequences
-
- return new SimpleFor(getPosition(), counter.optimize(), first.optimize(), last.optimize(), body.optimize());
- }
-
- @Override
- public RValue bindVariables(Expression expression, boolean preferLValue) throws ParserException {
- counter = counter.bindVariables(expression, true);
- first = first.bindVariables(expression, false);
- last = last.bindVariables(expression, false);
- body = body.bindVariables(expression, false);
-
- return this;
- }
-
-}
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/runtime/Switch.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/runtime/Switch.java
deleted file mode 100644
index 9f2f35764..000000000
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/runtime/Switch.java
+++ /dev/null
@@ -1,206 +0,0 @@
-/*
- * WorldEdit, a Minecraft world manipulation toolkit
- * Copyright (C) sk89q
- * Copyright (C) WorldEdit team and contributors
- *
- * This program is free software: you can redistribute it and/or modify it
- * under the terms of the GNU Lesser General Public License as published by the
- * Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
- * for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program. If not, see .
- */
-
-package com.sk89q.worldedit.internal.expression.runtime;
-
-import com.sk89q.worldedit.internal.expression.Expression;
-import com.sk89q.worldedit.internal.expression.parser.ParserException;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-
-/**
- * A switch/case construct.
- */
-public class Switch extends Node implements RValue {
-
- private RValue parameter;
- private final Map valueMap;
- private final RValue[] caseStatements;
- private RValue defaultCase;
-
- public Switch(int position, RValue parameter, List values, List caseStatements, RValue defaultCase) {
- this(position, parameter, invertList(values), caseStatements, defaultCase);
-
- }
-
- private static Map invertList(List values) {
- Map valueMap = new HashMap<>();
- for (int i = 0; i < values.size(); ++i) {
- valueMap.put(values.get(i), i);
- }
- return valueMap;
- }
-
- private Switch(int position, RValue parameter, Map valueMap, List caseStatements, RValue defaultCase) {
- super(position);
-
- this.parameter = parameter;
- this.valueMap = valueMap;
- this.caseStatements = caseStatements.toArray(new RValue[caseStatements.size()]);
- this.defaultCase = defaultCase;
- }
-
- @Override
- public char id() {
- return 'W';
- }
-
- @Override
- public double getValue() throws EvaluationException {
- final double parameter = this.parameter.getValue();
-
- try {
- double ret = 0.0;
-
- final Integer index = valueMap.get(parameter);
- if (index != null) {
- for (int i = index; i < caseStatements.length; ++i) {
- ret = caseStatements[i].getValue();
- }
- }
-
- return defaultCase == null ? ret : defaultCase.getValue();
- } catch (BreakException e) {
- if (e.doContinue) throw e;
-
- return 0.0;
- }
- }
-
- @Override
- public String toString() {
- final StringBuilder sb = new StringBuilder();
-
- sb.append("switch (");
- sb.append(parameter);
- sb.append(") { ");
-
- for (int i = 0; i < caseStatements.length; ++i) {
- RValue caseStatement = caseStatements[i];
- sb.append("case ");
- for (Entry entry : valueMap.entrySet()) {
- if (entry.getValue() == i) {
- sb.append(entry.getKey());
- break;
- }
- }
- sb.append(": ");
- sb.append(caseStatement);
- sb.append(' ');
- }
-
- if (defaultCase != null) {
- sb.append("default: ");
- sb.append(defaultCase);
- sb.append(' ');
- }
-
- sb.append("}");
-
- return sb.toString();
- }
-
- @Override
- public RValue optimize() throws EvaluationException {
- final RValue optimizedParameter = parameter.optimize();
- final List newSequence = new ArrayList<>();
-
- if (optimizedParameter instanceof Constant) {
- final double parameter = optimizedParameter.getValue();
-
- final Integer index = valueMap.get(parameter);
- if (index == null) {
- return defaultCase == null ? new Constant(getPosition(), 0.0) : defaultCase.optimize();
- }
-
- boolean breakDetected = false;
- for (int i = index; i < caseStatements.length && !breakDetected; ++i) {
- final RValue invokable = caseStatements[i].optimize();
-
- if (invokable instanceof Sequence) {
- for (RValue subInvokable : ((Sequence) invokable).sequence) {
- if (subInvokable instanceof Break) {
- breakDetected = true;
- break;
- }
-
- newSequence.add(subInvokable);
- }
- } else {
- newSequence.add(invokable);
- }
- }
-
- if (defaultCase != null && !breakDetected) {
- final RValue invokable = defaultCase.optimize();
-
- if (invokable instanceof Sequence) {
- Collections.addAll(newSequence, ((Sequence) invokable).sequence);
- } else {
- newSequence.add(invokable);
- }
- }
-
- return new Switch(getPosition(), optimizedParameter, Collections.singletonMap(parameter, 0), newSequence, null);
- }
-
- final Map newValueMap = new HashMap<>();
-
- Map backMap = new HashMap<>();
- for (Entry entry : valueMap.entrySet()) {
- backMap.put(entry.getValue(), entry.getKey());
- }
-
- for (int i = 0; i < caseStatements.length; ++i) {
- final RValue invokable = caseStatements[i].optimize();
-
- final Double caseValue = backMap.get(i);
- if (caseValue != null) {
- newValueMap.put(caseValue, newSequence.size());
- }
-
- if (invokable instanceof Sequence) {
- Collections.addAll(newSequence, ((Sequence) invokable).sequence);
- } else {
- newSequence.add(invokable);
- }
- }
-
- return new Switch(getPosition(), optimizedParameter, newValueMap, newSequence, defaultCase.optimize());
- }
-
- @Override
- public RValue bindVariables(Expression expression, boolean preferLValue) throws ParserException {
- parameter = parameter.bindVariables(expression, false);
-
- for (int i = 0; i < caseStatements.length; ++i) {
- caseStatements[i] = caseStatements[i].bindVariables(expression, false);
- }
-
- defaultCase = defaultCase.bindVariables(expression, false);
-
- return this;
- }
-
-}
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/runtime/Variable.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/runtime/Variable.java
deleted file mode 100644
index 01fe6764f..000000000
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/runtime/Variable.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * WorldEdit, a Minecraft world manipulation toolkit
- * Copyright (C) sk89q
- * Copyright (C) WorldEdit team and contributors
- *
- * This program is free software: you can redistribute it and/or modify it
- * under the terms of the GNU Lesser General Public License as published by the
- * Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
- * for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program. If not, see .
- */
-
-package com.sk89q.worldedit.internal.expression.runtime;
-
-import com.sk89q.worldedit.internal.expression.Expression;
-import com.sk89q.worldedit.internal.expression.parser.ParserException;
-
-/**
- * A variable.
- */
-public final class Variable extends Node implements LValue {
-
- public double value;
-
- public Variable(double value) {
- super(-1);
- this.value = value;
- }
-
- @Override
- public double getValue() {
- return value;
- }
-
- @Override
- public String toString() {
- return "var";
- }
-
- @Override
- public char id() {
- return 'v';
- }
-
- @Override
- public double assign(double value) {
- return this.value = value;
- }
-
- @Override
- public LValue optimize() throws EvaluationException {
- return this;
- }
-
- @Override
- public LValue bindVariables(Expression expression, boolean preferLValue) throws ParserException {
- return this;
- }
-
-}
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/runtime/While.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/runtime/While.java
deleted file mode 100644
index 5da3dae01..000000000
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/runtime/While.java
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * WorldEdit, a Minecraft world manipulation toolkit
- * Copyright (C) sk89q
- * Copyright (C) WorldEdit team and contributors
- *
- * This program is free software: you can redistribute it and/or modify it
- * under the terms of the GNU Lesser General Public License as published by the
- * Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
- * for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program. If not, see .
- */
-
-package com.sk89q.worldedit.internal.expression.runtime;
-
-import com.sk89q.worldedit.internal.expression.Expression;
-import com.sk89q.worldedit.internal.expression.parser.ParserException;
-
-/**
- * A while loop.
- */
-public class While extends Node {
-
- RValue condition;
- RValue body;
- boolean footChecked;
-
- public While(int position, RValue condition, RValue body, boolean footChecked) {
- super(position);
-
- this.condition = condition;
- this.body = body;
- this.footChecked = footChecked;
- }
-
- @Override
- public double getValue() throws EvaluationException {
- int iterations = 0;
- double ret = 0.0;
-
- if (footChecked) {
- do {
- if (iterations > 256) {
- throw new EvaluationException(getPosition(), "Loop exceeded 256 iterations.");
- }
- if (Thread.interrupted()) {
- throw new EvaluationException(getPosition(), "Calculations exceeded time limit.");
- }
- ++iterations;
-
- try {
- ret = body.getValue();
- } catch (BreakException e) {
- if (e.doContinue) {
- continue;
- } else {
- break;
- }
- }
- } while (condition.getValue() > 0.0);
- } else {
- while (condition.getValue() > 0.0) {
- if (iterations > 256) {
- throw new EvaluationException(getPosition(), "Loop exceeded 256 iterations.");
- }
- if (Thread.interrupted()) {
- throw new EvaluationException(getPosition(), "Calculations exceeded time limit.");
- }
- ++iterations;
-
- try {
- ret = body.getValue();
- } catch (BreakException e) {
- if (e.doContinue) {
- //noinspection UnnecessaryContinue
- continue;
- } else {
- break;
- }
- }
- }
- }
-
- return ret;
- }
-
- @Override
- public char id() {
- return 'w';
- }
-
- @Override
- public String toString() {
- if (footChecked) {
- return "do { " + body + " } while (" + condition + ")";
- } else {
- return "while (" + condition + ") { " + body + " }";
- }
- }
-
- @Override
- public RValue optimize() throws EvaluationException {
- final RValue newCondition = condition.optimize();
-
- if (newCondition instanceof Constant && newCondition.getValue() <= 0) {
- // If the condition is always false, the loop can be flattened.
- if (footChecked) {
- // Foot-checked loops run at least once.
- return body.optimize();
- } else {
- // Loops that never run always return 0.0.
- return new Constant(getPosition(), 0.0);
- }
- }
-
- return new While(getPosition(), newCondition, body.optimize(), footChecked);
- }
-
- @Override
- public RValue bindVariables(Expression expression, boolean preferLValue) throws ParserException {
- condition = condition.bindVariables(expression, false);
- body = body.bindVariables(expression, false);
-
- return this;
- }
-
-}
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/regions/shape/WorldEditExpressionEnvironment.java b/worldedit-core/src/main/java/com/sk89q/worldedit/regions/shape/WorldEditExpressionEnvironment.java
index 2304e4876..d4719fc52 100644
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/regions/shape/WorldEditExpressionEnvironment.java
+++ b/worldedit-core/src/main/java/com/sk89q/worldedit/regions/shape/WorldEditExpressionEnvironment.java
@@ -20,7 +20,7 @@
package com.sk89q.worldedit.regions.shape;
import com.sk89q.worldedit.extent.Extent;
-import com.sk89q.worldedit.internal.expression.runtime.ExpressionEnvironment;
+import com.sk89q.worldedit.internal.expression.ExpressionEnvironment;
import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.math.Vector3;
diff --git a/worldedit-core/src/test/java/com/sk89q/worldedit/internal/expression/ExpressionTest.java b/worldedit-core/src/test/java/com/sk89q/worldedit/internal/expression/ExpressionTest.java
index de9cce3cc..c33112f05 100644
--- a/worldedit-core/src/test/java/com/sk89q/worldedit/internal/expression/ExpressionTest.java
+++ b/worldedit-core/src/test/java/com/sk89q/worldedit/internal/expression/ExpressionTest.java
@@ -22,20 +22,17 @@ package com.sk89q.worldedit.internal.expression;
import com.sk89q.worldedit.LocalConfiguration;
import com.sk89q.worldedit.WorldEdit;
import com.sk89q.worldedit.extension.platform.Platform;
-import com.sk89q.worldedit.internal.expression.lexer.LexerException;
-import com.sk89q.worldedit.internal.expression.parser.ParserException;
-import com.sk89q.worldedit.internal.expression.runtime.EvaluationException;
-import com.sk89q.worldedit.internal.expression.runtime.ExpressionEnvironment;
-import com.sk89q.worldedit.internal.expression.runtime.ExpressionTimeoutException;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
+import java.time.Duration;
+
import static java.lang.Math.atan2;
import static java.lang.Math.sin;
-import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -79,71 +76,71 @@ public class ExpressionTest {
@Test
public void testErrors() {
- assertAll(
- // test lexer errors
- () -> {
- LexerException e = assertThrows(LexerException.class,
- () -> compile("#"));
- assertEquals(0, e.getPosition(), "Error position");
- },
- // test parser errors
- () -> {
- ParserException e = assertThrows(ParserException.class,
- () -> compile("x"));
- assertEquals(0, e.getPosition(), "Error position");
- },
- () -> {
- ParserException e = assertThrows(ParserException.class,
- () -> compile("x()"));
- assertEquals(0, e.getPosition(), "Error position");
- },
- () -> assertThrows(ParserException.class,
- () -> compile("(")),
- () -> assertThrows(ParserException.class,
- () -> compile("x(")),
- // test overloader errors
- () -> {
- ParserException e = assertThrows(ParserException.class,
- () -> compile("atan2(1)"));
- assertEquals(0, e.getPosition(), "Error position");
- },
- () -> {
- ParserException e = assertThrows(ParserException.class,
- () -> compile("atan2(1, 2, 3)"));
- assertEquals(0, e.getPosition(), "Error position");
- },
- () -> {
- ParserException e = assertThrows(ParserException.class,
- () -> compile("rotate(1, 2, 3)"));
- assertEquals(0, e.getPosition(), "Error position");
- }
- );
+ // test lexer errors
+ {
+ ExpressionException e = assertThrows(ExpressionException.class,
+ () -> compile("#"));
+ assertEquals(0, e.getPosition(), "Error position");
+ }
+ // test parser errors
+ {
+ ExpressionException e = assertThrows(ExpressionException.class,
+ () -> compile("x"));
+ assertEquals(0, e.getPosition(), "Error position");
+ }
+ {
+ ExpressionException e = assertThrows(ExpressionException.class,
+ () -> compile("x()"));
+ assertEquals(0, e.getPosition(), "Error position");
+ }
+ assertThrows(ExpressionException.class,
+ () -> compile("("));
+ assertThrows(ExpressionException.class,
+ () -> compile("x("));
+ // test overloader errors
+ {
+ ExpressionException e = assertThrows(ExpressionException.class,
+ () -> compile("atan2(1)"));
+ assertEquals(0, e.getPosition(), "Error position");
+ }
+ {
+ ExpressionException e = assertThrows(ExpressionException.class,
+ () -> compile("atan2(1, 2, 3)"));
+ assertEquals(0, e.getPosition(), "Error position");
+ }
+ {
+ ExpressionException e = assertThrows(ExpressionException.class,
+ () -> compile("rotate(1, 2, 3)"));
+ e.printStackTrace();
+ assertEquals(7, e.getPosition(), "Error position");
+ }
+
}
@Test
public void testAssign() throws ExpressionException {
Expression foo = compile("{a=x} b=y; c=z", "x", "y", "z", "a", "b", "c");
foo.evaluate(2D, 3D, 5D);
- assertEquals(2, foo.getVariable("a", false).getValue(), 0);
- assertEquals(3, foo.getVariable("b", false).getValue(), 0);
- assertEquals(5, foo.getVariable("c", false).getValue(), 0);
+ assertEquals(2, foo.getSlots().getSlotValue("a").orElse(-1), 0);
+ assertEquals(3, foo.getSlots().getSlotValue("b").orElse(-1), 0);
+ assertEquals(5, foo.getSlots().getSlotValue("c").orElse(-1), 0);
}
@Test
public void testIf() throws ExpressionException {
- assertEquals(40, simpleEval("if (1) x=4; else y=5; x*10+y;"), 0);
- assertEquals(5, simpleEval("if (0) x=4; else y=5; x*10+y;"), 0);
+ assertEquals(40, simpleEval("y=0; if (1) x=4; else y=5; x*10+y;"), 0);
+ assertEquals(5, simpleEval("x=0; if (0) x=4; else y=5; x*10+y;"), 0);
// test 'dangling else'
final Expression expression1 = compile("if (1) if (0) x=4; else y=5;", "x", "y");
expression1.evaluate(1D, 2D);
- assertEquals(1, expression1.getVariable("x", false).getValue(), 0);
- assertEquals(5, expression1.getVariable("y", false).getValue(), 0);
+ assertEquals(1, expression1.getSlots().getSlotValue("x").orElse(-1), 0);
+ assertEquals(5, expression1.getSlots().getSlotValue("y").orElse(-1), 0);
// test if the if construct is correctly recognized as a statement
final Expression expression2 = compile("if (0) if (1) x=5; y=4;", "x", "y");
expression2.evaluate(1D, 2D);
- assertEquals(4, expression2.getVariable("y", false).getValue(), 0);
+ assertEquals(4, expression2.getSlots().getSlotValue("y").orElse(-1), 0);
}
@Test
@@ -182,9 +179,11 @@ public class ExpressionTest {
@Test
public void testTimeout() {
- ExpressionTimeoutException e = assertThrows(ExpressionTimeoutException.class,
- () -> simpleEval("for(i=0;i<256;i++){for(j=0;j<256;j++){for(k=0;k<256;k++){for(l=0;l<256;l++){ln(pi)}}}}"),
- "Loop was not stopped.");
+ ExpressionTimeoutException e = assertTimeoutPreemptively(Duration.ofSeconds(10), () ->
+ assertThrows(ExpressionTimeoutException.class,
+ () -> simpleEval("for(i=0;i<256;i++){for(j=0;j<256;j++){for(k=0;k<256;k++){for(l=0;l<256;l++){ln(pi)}}}}"),
+ "Loop was not stopped.")
+ );
assertTrue(e.getMessage().contains("Calculations exceeded time limit"));
}
@@ -226,7 +225,7 @@ public class ExpressionTest {
return expression.evaluate();
}
- private Expression compile(String expressionString, String... variableNames) throws ExpressionException, EvaluationException {
+ private Expression compile(String expressionString, String... variableNames) throws ExpressionException {
final Expression expression = Expression.compile(expressionString, variableNames);
expression.optimize();
return expression;