Mirror von
https://github.com/IntellectualSites/FastAsyncWorldEdit.git
synchronisiert 2024-12-26 02:50:06 +01:00
Merge pull request #527 from EngineHub/feature/extended-expressions
Expressions Part 1: ANTLR
Dieser Commit ist enthalten in:
Commit
1fbb7a70f9
@ -110,6 +110,7 @@ fun Project.applyShadowConfiguration() {
|
||||
exclude("GradleStart**")
|
||||
exclude(".cache")
|
||||
exclude("LICENSE*")
|
||||
exclude("META-INF/maven/**")
|
||||
minimize()
|
||||
}
|
||||
}
|
||||
|
@ -42,6 +42,10 @@
|
||||
<allow pkg="com.google.auto"/>
|
||||
<allow pkg="it.unimi.dsi.fastutil"/>
|
||||
|
||||
<subpackage name="internal.expression">
|
||||
<allow pkg="org.antlr.v4"/>
|
||||
</subpackage>
|
||||
|
||||
<subpackage name="bukkit">
|
||||
<allow pkg="org.bukkit"/>
|
||||
<allow pkg="org.bstats.bukkit"/>
|
||||
|
@ -54,9 +54,11 @@ tasks.named<ShadowJar>("shadowJar") {
|
||||
dependencies {
|
||||
relocate("org.slf4j", "com.sk89q.worldedit.slf4j")
|
||||
relocate("org.apache.logging.slf4j", "com.sk89q.worldedit.log4jbridge")
|
||||
relocate("org.antlr.v4", "com.sk89q.worldedit.antlr4")
|
||||
include(dependency(":worldedit-core"))
|
||||
include(dependency("org.slf4j:slf4j-api"))
|
||||
include(dependency("org.apache.logging.log4j:log4j-slf4j-impl"))
|
||||
include(dependency("org.antlr:antlr4-runtime"))
|
||||
relocate("org.bstats", "com.sk89q.worldedit.bukkit.bstats") {
|
||||
include(dependency("org.bstats:bstats-bukkit:1.5"))
|
||||
}
|
||||
|
@ -1,7 +1,11 @@
|
||||
import net.minecrell.gradle.licenser.LicenseExtension
|
||||
import org.gradle.plugins.ide.idea.model.IdeaModel
|
||||
|
||||
plugins {
|
||||
id("java-library")
|
||||
id("net.ltgt.apt-eclipse")
|
||||
id("net.ltgt.apt-idea")
|
||||
id("antlr")
|
||||
}
|
||||
|
||||
applyPlatformAndCoreConfiguration()
|
||||
@ -23,6 +27,10 @@ dependencies {
|
||||
"compile"("org.slf4j:slf4j-api:1.7.26")
|
||||
"compile"("it.unimi.dsi:fastutil:8.2.1")
|
||||
|
||||
val antlrVersion = "4.7.2"
|
||||
"antlr"("org.antlr:antlr4:$antlrVersion")
|
||||
"implementation"("org.antlr:antlr4-runtime:$antlrVersion")
|
||||
|
||||
"compileOnly"(project(":worldedit-libs:core:ap"))
|
||||
"annotationProcessor"(project(":worldedit-libs:core:ap"))
|
||||
// ensure this is on the classpath for the AP
|
||||
@ -36,6 +44,35 @@ tasks.withType<JavaCompile>().configureEach {
|
||||
options.compilerArgs.add("-Aarg.name.key.prefix=")
|
||||
}
|
||||
|
||||
tasks.named<AntlrTask>("generateGrammarSource").configure {
|
||||
val pkg = "com.sk89q.worldedit.antlr"
|
||||
outputDirectory = file("build/generated-src/antlr/main/${pkg.replace('.', '/')}")
|
||||
arguments = listOf(
|
||||
"-visitor", "-package", pkg,
|
||||
"-Xexact-output-dir"
|
||||
)
|
||||
}
|
||||
|
||||
configure<LicenseExtension> {
|
||||
exclude {
|
||||
it.file.startsWith(project.buildDir)
|
||||
}
|
||||
}
|
||||
tasks.withType<Checkstyle>().configureEach {
|
||||
exclude("com/sk89q/worldedit/antlr/**/*.java")
|
||||
}
|
||||
|
||||
// Give intellij info about where ANTLR code comes from
|
||||
plugins.withId("idea") {
|
||||
configure<IdeaModel> {
|
||||
afterEvaluate {
|
||||
module.sourceDirs.add(file("src/main/antlr"))
|
||||
module.sourceDirs.add(file("build/generated-src/antlr/main"))
|
||||
module.generatedSourceDirs.add(file("build/generated-src/antlr/main"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
java {
|
||||
|
236
worldedit-core/src/main/antlr/com/sk89q/worldedit/antlr/Expression.g4
Normale Datei
236
worldedit-core/src/main/antlr/com/sk89q/worldedit/antlr/Expression.g4
Normale Datei
@ -0,0 +1,236 @@
|
||||
grammar Expression;
|
||||
|
||||
// Lexer tokens:
|
||||
|
||||
PLUS : '+' ;
|
||||
MINUS : '-' ;
|
||||
TIMES : '*' ;
|
||||
DIVIDE : '/' ;
|
||||
MODULO : '%' ;
|
||||
POWER : '^' | '**' ;
|
||||
LEFT_SHIFT : '<<' ;
|
||||
RIGHT_SHIFT : '>>' ;
|
||||
ASSIGN : '=' ;
|
||||
COMPLEMENT : '~' ;
|
||||
|
||||
PLUS_ASSIGN : '+=' ;
|
||||
MINUS_ASSIGN : '-=' ;
|
||||
TIMES_ASSIGN : '*=' ;
|
||||
DIVIDE_ASSIGN : '/=' ;
|
||||
MODULO_ASSIGN : '%=' ;
|
||||
POWER_ASSIGN : '^=' ;
|
||||
|
||||
EQUAL : '==' ;
|
||||
NOT_EQUAL : '!=' ;
|
||||
NEAR : '~=' ;
|
||||
LESS_THAN : '<' ;
|
||||
LESS_THAN_OR_EQUAL : '<=' ;
|
||||
GREATER_THAN : '>' ;
|
||||
GREATER_THAN_OR_EQUAL : '>=' ;
|
||||
// SC = "Short Circuit"
|
||||
// Non-SC variants not currently implemented.
|
||||
AND_SC : '&&' ;
|
||||
OR_SC : '||' ;
|
||||
|
||||
INCREMENT : '++' ;
|
||||
DECREMENT : '--' ;
|
||||
|
||||
COMMA : ',' ;
|
||||
OPEN_PAREN : '(' ;
|
||||
CLOSE_PAREN : ')' ;
|
||||
OPEN_BRACKET : '{' ;
|
||||
CLOSE_BRACKET : '}' ;
|
||||
SEMI_COLON : ';' ;
|
||||
QUESTION_MARK : '?' ;
|
||||
COLON : ':' ;
|
||||
EXCLAMATION_MARK : '!' ;
|
||||
|
||||
IF : 'if' ;
|
||||
ELSE : 'else' ;
|
||||
WHILE : 'while' ;
|
||||
DO : 'do' ;
|
||||
FOR : 'for' ;
|
||||
BREAK : 'break' ;
|
||||
CONTINUE : 'continue' ;
|
||||
RETURN : 'return' ;
|
||||
SWITCH : 'switch' ;
|
||||
CASE : 'case' ;
|
||||
DEFAULT : 'default' ;
|
||||
|
||||
fragment DIGIT : [0-9] ;
|
||||
fragment SIGN : [+-] ;
|
||||
fragment EXP_CHAR : [eE] ;
|
||||
fragment DECIMAL : '.' DIGIT+ ( EXP_CHAR SIGN? DIGIT+ )? ;
|
||||
|
||||
// All numbers are treated the same. No int/dec divide.
|
||||
NUMBER : ( DIGIT+ DECIMAL? | DECIMAL ) ;
|
||||
|
||||
ID : [A-Za-z] [0-9A-Za-z_]* ;
|
||||
|
||||
WS : [ \t\r\n\u000C]+ -> skip ;
|
||||
|
||||
// Parser rules:
|
||||
|
||||
/**
|
||||
* All statements parseable from the input. Forces consumption of EOF token.
|
||||
*/
|
||||
allStatements : statements EOF ;
|
||||
|
||||
statements : statement+ ;
|
||||
|
||||
statement
|
||||
: ( block
|
||||
| ifStatement
|
||||
| whileStatement
|
||||
| doStatement
|
||||
| forStatement
|
||||
| simpleForStatement
|
||||
| breakStatement
|
||||
| continueStatement
|
||||
| returnStatement
|
||||
| switchStatement
|
||||
| expressionStatement
|
||||
| emptyStatement
|
||||
) SEMI_COLON?
|
||||
;
|
||||
|
||||
block : '{' statements '}' ;
|
||||
|
||||
ifStatement : IF '(' condition=expression ')' trueBranch=statement ( ELSE falseBranch=statement )? ;
|
||||
|
||||
whileStatement : WHILE '(' condition=expression ')' body=statement ;
|
||||
|
||||
doStatement : DO body=statement WHILE '(' condition=expression ')' ;
|
||||
|
||||
// C-Style for loop
|
||||
forStatement
|
||||
: 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 value=expression? ;
|
||||
|
||||
switchStatement : SWITCH '(' target=expression ')' '{' (labels+=switchLabel ':' bodies+=statements )+ '}' ;
|
||||
|
||||
switchLabel
|
||||
: CASE constant=constantExpression # Case
|
||||
| DEFAULT # Default
|
||||
;
|
||||
|
||||
expressionStatement : expression ;
|
||||
|
||||
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
|
||||
| source=ID # IdExpr
|
||||
| '(' expression ')' # WrappedExpr
|
||||
;
|
||||
|
||||
constantExpression : NUMBER ;
|
||||
|
||||
functionCall : name=ID '(' (args+=expression ( ',' args+=expression )*)? ')' ;
|
@ -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);
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
@ -17,18 +17,19 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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;
|
||||
}
|
@ -0,0 +1,625 @@
|
||||
/*
|
||||
* WorldEdit, a Minecraft world manipulation toolkit
|
||||
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<Double> {
|
||||
|
||||
private final SlotTable slots;
|
||||
private final SetMultimap<String, MethodHandle> functions;
|
||||
|
||||
EvaluatingVisitor(SlotTable slots,
|
||||
SetMultimap<String, MethodHandle> 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<EvaluationException> 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<TerminalNode> 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<ParserRuleContext> 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<ParserRuleContext> 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);
|
||||
}
|
||||
}
|
@ -17,7 +17,7 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.sk89q.worldedit.internal.expression.runtime;
|
||||
package com.sk89q.worldedit.internal.expression;
|
||||
|
||||
import com.sk89q.worldedit.internal.expression.ExpressionException;
|
||||
|
@ -19,25 +19,25 @@
|
||||
|
||||
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.CountDownLatch;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
@ -68,27 +68,23 @@ 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()}.</p>
|
||||
*
|
||||
* <p>Variables are also supported and can be set either by passing values
|
||||
* to {@link #evaluate(double...)}.</p>
|
||||
* pass values for all slots specified while compiling.
|
||||
* To query slots after evaluation, you can use the {@linkplain #getSlots() slot table}.
|
||||
*/
|
||||
public class Expression {
|
||||
|
||||
private static final ThreadLocal<Stack<Expression>> instance = new ThreadLocal<>();
|
||||
private static final ExecutorService evalThread = Executors.newCachedThreadPool(
|
||||
private static final ExecutorService evalThread = Executors.newFixedThreadPool(
|
||||
Runtime.getRuntime().availableProcessors(),
|
||||
new ThreadFactoryBuilder()
|
||||
.setDaemon(true)
|
||||
.setNameFormat("worldedit-expression-eval-%d")
|
||||
.build());
|
||||
|
||||
private final Map<String, RValue> 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<String> providedSlots;
|
||||
private ExpressionParser.AllStatementsContext root;
|
||||
private final SetMultimap<String, MethodHandle> functions = Functions.getFunctionMap();
|
||||
private ExpressionEnvironment environment;
|
||||
|
||||
public static Expression compile(String expression, String... variableNames) throws ExpressionException {
|
||||
@ -96,25 +92,33 @@ public class Expression {
|
||||
}
|
||||
|
||||
private Expression(String expression, String... variableNames) throws ExpressionException {
|
||||
this(Lexer.tokenize(expression), variableNames);
|
||||
}
|
||||
|
||||
private Expression(List<Token> 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, "<input>");
|
||||
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,26 +127,23 @@ 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 + "."));
|
||||
|
||||
slot.setValue(values[i]);
|
||||
}
|
||||
|
||||
((Variable) invokable).value = values[i];
|
||||
}
|
||||
|
||||
try {
|
||||
// evaluation exceptions are thrown out of this method
|
||||
if (timeout < 0) {
|
||||
return evaluateRoot();
|
||||
}
|
||||
return evaluateRootTimed(timeout);
|
||||
} catch (ReturnException e) {
|
||||
return e.getValue();
|
||||
} // other evaluation exceptions are thrown out of this method
|
||||
}
|
||||
|
||||
private double evaluateRootTimed(int timeout) throws EvaluationException {
|
||||
CountDownLatch startLatch = new CountDownLatch(1);
|
||||
Request request = Request.request();
|
||||
Future<Double> result = evalThread.submit(() -> {
|
||||
Request local = Request.request();
|
||||
@ -150,12 +151,14 @@ public class Expression {
|
||||
local.setWorld(request.getWorld());
|
||||
local.setEditSession(request.getEditSession());
|
||||
try {
|
||||
startLatch.countDown();
|
||||
return Expression.this.evaluateRoot();
|
||||
} finally {
|
||||
Request.reset();
|
||||
}
|
||||
});
|
||||
try {
|
||||
startLatch.await();
|
||||
return result.get(timeout, TimeUnit.MILLISECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
@ -165,12 +168,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 +177,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 +192,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 +219,6 @@ public class Expression {
|
||||
}
|
||||
}
|
||||
|
||||
public Functions getFunctions() {
|
||||
return functions;
|
||||
}
|
||||
|
||||
public ExpressionEnvironment getEnvironment() {
|
||||
return environment;
|
||||
}
|
||||
|
@ -17,7 +17,7 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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.
|
@ -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;
|
||||
|
||||
|
@ -0,0 +1,149 @@
|
||||
/*
|
||||
* WorldEdit, a Minecraft world manipulation toolkit
|
||||
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<String, MethodHandle> functions,
|
||||
ExpressionParser.FunctionCallContext ctx) {
|
||||
String fnName = ctx.name.getText();
|
||||
Set<MethodHandle> 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 = "<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<String> 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<String> tryResolveId(ParserRuleContext arg) {
|
||||
Optional<ExpressionParser.WrappedExprContext> 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 <T extends ParserRuleContext> Optional<T> tryAs(
|
||||
ParserRuleContext ctx,
|
||||
Class<T> rule
|
||||
) {
|
||||
if (rule.isInstance(ctx)) {
|
||||
return Optional.of(rule.cast(ctx));
|
||||
}
|
||||
if (ctx.children.size() != 1) {
|
||||
return Optional.empty();
|
||||
}
|
||||
List<ParserRuleContext> ctxs = ctx.getRuleContexts(ParserRuleContext.class);
|
||||
if (ctxs.size() != 1) {
|
||||
return Optional.empty();
|
||||
}
|
||||
return tryAs(ctxs.get(0), rule);
|
||||
}
|
||||
|
||||
private ExpressionHelper() {
|
||||
}
|
||||
|
||||
}
|
@ -17,7 +17,7 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.sk89q.worldedit.internal.expression.runtime;
|
||||
package com.sk89q.worldedit.internal.expression;
|
||||
|
||||
/**
|
||||
* Thrown when an evaluation exceeds the timeout time.
|
@ -0,0 +1,70 @@
|
||||
/*
|
||||
* WorldEdit, a Minecraft world manipulation toolkit
|
||||
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<String> variableNames = new HashSet<>();
|
||||
private final SetMultimap<String, MethodHandle> functions;
|
||||
|
||||
ExpressionValidator(Collection<String> variableNames,
|
||||
SetMultimap<String, MethodHandle> 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);
|
||||
}
|
||||
}
|
@ -0,0 +1,337 @@
|
||||
/*
|
||||
* WorldEdit, a Minecraft world manipulation toolkit
|
||||
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<String, MethodHandle> getFunctionMap() {
|
||||
SetMultimap<String, MethodHandle> 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<String, MethodHandle> map,
|
||||
MethodHandles.Lookup lookup
|
||||
) throws NoSuchMethodException, IllegalAccessException {
|
||||
// double <name>(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<String, MethodHandle> 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<String, MethodHandle> 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<double[]> globalMegaBuffer = new Int2ObjectOpenHashMap<>();
|
||||
private final Int2ObjectMap<double[]> megaBuffer = new Int2ObjectOpenHashMap<>();
|
||||
|
||||
private static double[] getSubBuffer(Int2ObjectMap<double[]> megabuf, int key) {
|
||||
return megabuf.computeIfAbsent(key, k -> new double[1024]);
|
||||
}
|
||||
|
||||
private static double getBufferItem(final Int2ObjectMap<double[]> megabuf, final int index) {
|
||||
return getSubBuffer(megabuf, index & ~1023)[index & 1023];
|
||||
}
|
||||
|
||||
private static double setBufferItem(final Int2ObjectMap<double[]> 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<double[]> 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<PerlinNoise> 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<VoronoiNoise> 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<RidgedMultiFractalNoise> 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() {
|
||||
}
|
||||
|
||||
}
|
@ -17,28 +17,15 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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 + ")";
|
||||
}
|
||||
|
||||
}
|
@ -17,9 +17,7 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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.
|
@ -0,0 +1,69 @@
|
||||
/*
|
||||
* WorldEdit, a Minecraft world manipulation toolkit
|
||||
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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();
|
||||
|
||||
}
|
@ -17,28 +17,15 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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 + ")";
|
||||
}
|
||||
|
||||
}
|
@ -17,9 +17,7 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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.
|
@ -0,0 +1,64 @@
|
||||
/*
|
||||
* WorldEdit, a Minecraft world manipulation toolkit
|
||||
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<String, LocalSlot> slots = new HashMap<>();
|
||||
|
||||
public Set<String> 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<LocalSlot.Variable> initVariable(String name) {
|
||||
slots.computeIfAbsent(name, n -> new LocalSlot.Variable(0));
|
||||
return getVariable(name);
|
||||
}
|
||||
|
||||
public Optional<LocalSlot> getSlot(String name) {
|
||||
return Optional.ofNullable(slots.get(name));
|
||||
}
|
||||
|
||||
public Optional<LocalSlot.Variable> 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());
|
||||
}
|
||||
|
||||
}
|
@ -1,240 +0,0 @@
|
||||
/*
|
||||
* WorldEdit, a Minecraft world manipulation toolkit
|
||||
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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.
|
||||
*
|
||||
* <p>Tokens can be numbers, identifiers, operators and assorted other
|
||||
* characters.</p>
|
||||
*/
|
||||
public class Lexer {
|
||||
|
||||
private final String expression;
|
||||
private int position = 0;
|
||||
|
||||
private Lexer(String expression) {
|
||||
this.expression = expression;
|
||||
}
|
||||
|
||||
public static List<Token> 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<Character> characterTokens = new HashSet<>();
|
||||
static {
|
||||
characterTokens.add(',');
|
||||
characterTokens.add('(');
|
||||
characterTokens.add(')');
|
||||
characterTokens.add('{');
|
||||
characterTokens.add('}');
|
||||
characterTokens.add(';');
|
||||
characterTokens.add('?');
|
||||
characterTokens.add(':');
|
||||
}
|
||||
|
||||
private static final Set<String> 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<Token> tokenize() throws LexerException {
|
||||
List<Token> 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<Character, DecisionTree> 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,44 +0,0 @@
|
||||
/*
|
||||
* WorldEdit, a Minecraft world manipulation toolkit
|
||||
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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 + ")";
|
||||
}
|
||||
|
||||
}
|
@ -1,44 +0,0 @@
|
||||
/*
|
||||
* WorldEdit, a Minecraft world manipulation toolkit
|
||||
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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 + ")";
|
||||
}
|
||||
|
||||
}
|
@ -1,44 +0,0 @@
|
||||
/*
|
||||
* WorldEdit, a Minecraft world manipulation toolkit
|
||||
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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 + ")";
|
||||
}
|
||||
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
/*
|
||||
* WorldEdit, a Minecraft world manipulation toolkit
|
||||
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
@ -1,464 +0,0 @@
|
||||
/*
|
||||
* WorldEdit, a Minecraft world manipulation toolkit
|
||||
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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.
|
||||
*
|
||||
* <p>Tokens can be numbers, identifiers, operators and assorted other characters.</p>
|
||||
*/
|
||||
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<Token> tokens;
|
||||
private int position = 0;
|
||||
private Expression expression;
|
||||
|
||||
private Parser(List<Token> tokens, Expression expression) {
|
||||
this.tokens = tokens;
|
||||
this.expression = expression;
|
||||
}
|
||||
|
||||
public static RValue parse(List<Token> 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<RValue> 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<Double> values = new ArrayList<>();
|
||||
final List<RValue> 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<Identifiable> 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<RValue> 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;
|
||||
}
|
||||
}
|
@ -1,352 +0,0 @@
|
||||
/*
|
||||
* WorldEdit, a Minecraft world manipulation toolkit
|
||||
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<String, String> unaryOpMap = new HashMap<>();
|
||||
|
||||
private static final Map<String, String>[] binaryOpMapsLA;
|
||||
private static final Map<String, String>[] 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<String, String>[] 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<String, String> m = lBinaryOpMapsLA[i] = new HashMap<>();
|
||||
for (final Object[] element : a) {
|
||||
m.put((String) element[0], (String) element[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
final Map<String, String>[] 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<String, String> m = lBinaryOpMapsRA[i] = new HashMap<>();
|
||||
for (final Object[] element : a) {
|
||||
m.put((String) element[0], (String) element[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ParserProcessors() {
|
||||
}
|
||||
|
||||
static RValue processExpression(LinkedList<Identifiable> input) throws ParserException {
|
||||
return processBinaryOpsRA(input, binaryOpMapsRA.length - 1);
|
||||
}
|
||||
|
||||
private static RValue processBinaryOpsLA(LinkedList<Identifiable> input, int level) throws ParserException {
|
||||
if (level < 0) {
|
||||
return processUnaryOps(input);
|
||||
}
|
||||
|
||||
LinkedList<Identifiable> lhs = new LinkedList<>();
|
||||
LinkedList<Identifiable> rhs = new LinkedList<>();
|
||||
String operator = null;
|
||||
|
||||
for (Iterator<Identifiable> 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<Identifiable> input, int level) throws ParserException {
|
||||
if (level < 0) {
|
||||
return processTernaryOps(input);
|
||||
}
|
||||
|
||||
LinkedList<Identifiable> lhs = new LinkedList<>();
|
||||
LinkedList<Identifiable> 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<Identifiable> input) throws ParserException {
|
||||
LinkedList<Identifiable> lhs = new LinkedList<>();
|
||||
LinkedList<Identifiable> mhs = new LinkedList<>();
|
||||
LinkedList<Identifiable> 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<Identifiable> input) throws ParserException {
|
||||
// Preprocess postfix operators into unary operators
|
||||
final Identifiable center;
|
||||
LinkedList<UnaryOperator> 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;
|
||||
}
|
||||
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
/*
|
||||
* WorldEdit, a Minecraft world manipulation toolkit
|
||||
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
/*
|
||||
* WorldEdit, a Minecraft world manipulation toolkit
|
||||
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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 + ")";
|
||||
}
|
||||
|
||||
}
|
@ -1,80 +0,0 @@
|
||||
/*
|
||||
* WorldEdit, a Minecraft world manipulation toolkit
|
||||
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
/*
|
||||
* WorldEdit, a Minecraft world manipulation toolkit
|
||||
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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";
|
||||
}
|
||||
|
||||
}
|
@ -1,93 +0,0 @@
|
||||
/*
|
||||
* WorldEdit, a Minecraft world manipulation toolkit
|
||||
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
/*
|
||||
* WorldEdit, a Minecraft world manipulation toolkit
|
||||
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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';
|
||||
}
|
||||
|
||||
}
|
@ -1,107 +0,0 @@
|
||||
/*
|
||||
* WorldEdit, a Minecraft world manipulation toolkit
|
||||
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
@ -1,123 +0,0 @@
|
||||
/*
|
||||
* WorldEdit, a Minecraft world manipulation toolkit
|
||||
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
@ -1,487 +0,0 @@
|
||||
/*
|
||||
* WorldEdit, a Minecraft world manipulation toolkit
|
||||
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<Overload> 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<String, List<Overload>> 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<Overload> 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<Integer, double[]> gmegabuf = new HashMap<>();
|
||||
private final Map<Integer, double[]> megabuf = new HashMap<>();
|
||||
|
||||
public Map<Integer, double[]> getMegabuf() {
|
||||
return megabuf;
|
||||
}
|
||||
|
||||
private static double[] getSubBuffer(Map<Integer, double[]> 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<Integer, double[]> megabuf, final int index) {
|
||||
return getSubBuffer(megabuf, index & ~1023)[index & 1023];
|
||||
}
|
||||
|
||||
private static double setBufferItem(final Map<Integer, double[]> 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<Integer, double[]> 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<PerlinNoise> 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<VoronoiNoise> 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<RidgedMultiFractalNoise> 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);
|
||||
}
|
||||
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
/*
|
||||
* WorldEdit, a Minecraft world manipulation toolkit
|
||||
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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;
|
||||
|
||||
}
|
@ -1,77 +0,0 @@
|
||||
/*
|
||||
* WorldEdit, a Minecraft world manipulation toolkit
|
||||
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
@ -1,54 +0,0 @@
|
||||
/*
|
||||
* WorldEdit, a Minecraft world manipulation toolkit
|
||||
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
@ -1,228 +0,0 @@
|
||||
/*
|
||||
* WorldEdit, a Minecraft world manipulation toolkit
|
||||
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
/*
|
||||
* WorldEdit, a Minecraft world manipulation toolkit
|
||||
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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;
|
||||
|
||||
}
|
@ -1,60 +0,0 @@
|
||||
/*
|
||||
* WorldEdit, a Minecraft world manipulation toolkit
|
||||
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
/*
|
||||
* WorldEdit, a Minecraft world manipulation toolkit
|
||||
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
@ -1,109 +0,0 @@
|
||||
/*
|
||||
* WorldEdit, a Minecraft world manipulation toolkit
|
||||
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<RValue> 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;
|
||||
}
|
||||
|
||||
}
|
@ -1,104 +0,0 @@
|
||||
/*
|
||||
* WorldEdit, a Minecraft world manipulation toolkit
|
||||
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
@ -1,206 +0,0 @@
|
||||
/*
|
||||
* WorldEdit, a Minecraft world manipulation toolkit
|
||||
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<Double, Integer> valueMap;
|
||||
private final RValue[] caseStatements;
|
||||
private RValue defaultCase;
|
||||
|
||||
public Switch(int position, RValue parameter, List<Double> values, List<RValue> caseStatements, RValue defaultCase) {
|
||||
this(position, parameter, invertList(values), caseStatements, defaultCase);
|
||||
|
||||
}
|
||||
|
||||
private static Map<Double, Integer> invertList(List<Double> values) {
|
||||
Map<Double, Integer> 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<Double, Integer> valueMap, List<RValue> 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<Double, Integer> 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<RValue> 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<Double, Integer> newValueMap = new HashMap<>();
|
||||
|
||||
Map<Integer, Double> backMap = new HashMap<>();
|
||||
for (Entry<Double, Integer> 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;
|
||||
}
|
||||
|
||||
}
|
@ -1,67 +0,0 @@
|
||||
/*
|
||||
* WorldEdit, a Minecraft world manipulation toolkit
|
||||
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
@ -1,133 +0,0 @@
|
||||
/*
|
||||
* WorldEdit, a Minecraft world manipulation toolkit
|
||||
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
|
||||
|
@ -0,0 +1,100 @@
|
||||
/*
|
||||
* WorldEdit, a Minecraft world manipulation toolkit
|
||||
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.sk89q.worldedit.internal.expression;
|
||||
|
||||
import com.sk89q.worldedit.LocalConfiguration;
|
||||
import com.sk89q.worldedit.WorldEdit;
|
||||
import com.sk89q.worldedit.extension.platform.Platform;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* Common setup code for expression tests.
|
||||
*/
|
||||
class BaseExpressionTest {
|
||||
|
||||
static double readSlot(Expression expr, String name) {
|
||||
return expr.getSlots().getSlotValue(name).orElseThrow(IllegalStateException::new);
|
||||
}
|
||||
|
||||
private Platform mockPlat = mock(Platform.class);
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
when(mockPlat.getConfiguration()).thenReturn(new LocalConfiguration() {
|
||||
@Override
|
||||
public void load() {
|
||||
}
|
||||
});
|
||||
WorldEdit.getInstance().getPlatformManager().register(mockPlat);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
WorldEdit.getInstance().getPlatformManager().unregister(mockPlat);
|
||||
}
|
||||
|
||||
double simpleEval(String expressionString) throws ExpressionException {
|
||||
final Expression expression = compile(expressionString);
|
||||
|
||||
expression.setEnvironment(new ExpressionEnvironment() {
|
||||
@Override
|
||||
public int getBlockType(double x, double y, double z) {
|
||||
return (int) x;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBlockData(double x, double y, double z) {
|
||||
return (int) y;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBlockTypeAbs(double x, double y, double z) {
|
||||
return (int) x*10;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBlockDataAbs(double x, double y, double z) {
|
||||
return (int) y*10;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBlockTypeRel(double x, double y, double z) {
|
||||
return (int) x*100;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBlockDataRel(double x, double y, double z) {
|
||||
return (int) y*100;
|
||||
}
|
||||
});
|
||||
|
||||
return expression.evaluate();
|
||||
}
|
||||
|
||||
Expression compile(String expressionString, String... variableNames) throws ExpressionException {
|
||||
final Expression expression = Expression.compile(expressionString, variableNames);
|
||||
expression.optimize();
|
||||
return expression;
|
||||
}
|
||||
}
|
@ -19,45 +19,18 @@
|
||||
|
||||
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;
|
||||
|
||||
public class ExpressionTest {
|
||||
|
||||
private Platform mockPlat = mock(Platform.class);
|
||||
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
when(mockPlat.getConfiguration()).thenReturn(new LocalConfiguration() {
|
||||
@Override
|
||||
public void load() {
|
||||
}
|
||||
});
|
||||
WorldEdit.getInstance().getPlatformManager().register(mockPlat);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void tearDown() {
|
||||
WorldEdit.getInstance().getPlatformManager().unregister(mockPlat);
|
||||
}
|
||||
class ExpressionTest extends BaseExpressionTest {
|
||||
|
||||
@Test
|
||||
public void testEvaluate() throws ExpressionException {
|
||||
@ -77,73 +50,78 @@ public class ExpressionTest {
|
||||
assertEquals(8, compile("foo+bar", "foo", "bar").evaluate(5D, 3D), 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testTightTokenization() {
|
||||
assertEquals(4, simpleEval("3+1"), 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testErrors() {
|
||||
assertAll(
|
||||
// test lexer errors
|
||||
() -> {
|
||||
LexerException e = assertThrows(LexerException.class,
|
||||
{
|
||||
ExpressionException e = assertThrows(ExpressionException.class,
|
||||
() -> compile("#"));
|
||||
assertEquals(0, e.getPosition(), "Error position");
|
||||
},
|
||||
}
|
||||
// test parser errors
|
||||
() -> {
|
||||
ParserException e = assertThrows(ParserException.class,
|
||||
{
|
||||
ExpressionException e = assertThrows(ExpressionException.class,
|
||||
() -> compile("x"));
|
||||
assertEquals(0, e.getPosition(), "Error position");
|
||||
},
|
||||
() -> {
|
||||
ParserException e = assertThrows(ParserException.class,
|
||||
}
|
||||
{
|
||||
ExpressionException e = assertThrows(ExpressionException.class,
|
||||
() -> compile("x()"));
|
||||
assertEquals(0, e.getPosition(), "Error position");
|
||||
},
|
||||
() -> assertThrows(ParserException.class,
|
||||
() -> compile("(")),
|
||||
() -> assertThrows(ParserException.class,
|
||||
() -> compile("x(")),
|
||||
}
|
||||
assertThrows(ExpressionException.class,
|
||||
() -> compile("("));
|
||||
assertThrows(ExpressionException.class,
|
||||
() -> compile("x("));
|
||||
// test overloader errors
|
||||
() -> {
|
||||
ParserException e = assertThrows(ParserException.class,
|
||||
{
|
||||
ExpressionException e = assertThrows(ExpressionException.class,
|
||||
() -> compile("atan2(1)"));
|
||||
assertEquals(0, e.getPosition(), "Error position");
|
||||
},
|
||||
() -> {
|
||||
ParserException e = assertThrows(ParserException.class,
|
||||
}
|
||||
{
|
||||
ExpressionException e = assertThrows(ExpressionException.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");
|
||||
}
|
||||
);
|
||||
{
|
||||
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,53 +160,12 @@ public class ExpressionTest {
|
||||
|
||||
@Test
|
||||
public void testTimeout() {
|
||||
ExpressionTimeoutException e = assertThrows(ExpressionTimeoutException.class,
|
||||
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.");
|
||||
"Loop was not stopped.")
|
||||
);
|
||||
assertTrue(e.getMessage().contains("Calculations exceeded time limit"));
|
||||
}
|
||||
|
||||
private double simpleEval(String expressionString) throws ExpressionException {
|
||||
final Expression expression = compile(expressionString);
|
||||
|
||||
expression.setEnvironment(new ExpressionEnvironment() {
|
||||
@Override
|
||||
public int getBlockType(double x, double y, double z) {
|
||||
return (int) x;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBlockData(double x, double y, double z) {
|
||||
return (int) y;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBlockTypeAbs(double x, double y, double z) {
|
||||
return (int) x*10;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBlockDataAbs(double x, double y, double z) {
|
||||
return (int) y*10;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBlockTypeRel(double x, double y, double z) {
|
||||
return (int) x*100;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBlockDataRel(double x, double y, double z) {
|
||||
return (int) y*100;
|
||||
}
|
||||
});
|
||||
|
||||
return expression.evaluate();
|
||||
}
|
||||
|
||||
private Expression compile(String expressionString, String... variableNames) throws ExpressionException, EvaluationException {
|
||||
final Expression expression = Expression.compile(expressionString, variableNames);
|
||||
expression.optimize();
|
||||
return expression;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,171 @@
|
||||
/*
|
||||
* WorldEdit, a Minecraft world manipulation toolkit
|
||||
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.sk89q.worldedit.internal.expression;
|
||||
|
||||
import com.sk89q.worldedit.math.Vector3;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
/**
|
||||
* Test class for various real-world expressions.
|
||||
*/
|
||||
class RealExpressionTest extends BaseExpressionTest {
|
||||
|
||||
private static final class TestCase {
|
||||
|
||||
final Vector3 loc;
|
||||
final double result;
|
||||
final Consumer<Expression> postChecks;
|
||||
|
||||
private TestCase(Vector3 loc, double result, Consumer<Expression> postChecks) {
|
||||
this.loc = loc;
|
||||
this.result = result;
|
||||
this.postChecks = postChecks;
|
||||
}
|
||||
|
||||
TestCase withData(int expectedData) {
|
||||
return new TestCase(loc, result, expr -> {
|
||||
postChecks.accept(expr);
|
||||
double data = readSlot(expr, "data");
|
||||
assertEquals(expectedData, (int) data,
|
||||
"Test case " + this + " failed (data)");
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return loc + " -> " + result;
|
||||
}
|
||||
}
|
||||
|
||||
private static TestCase testCase(Vector3 loc, double result) {
|
||||
return testCase(loc, result, e -> {
|
||||
});
|
||||
}
|
||||
|
||||
private static TestCase testCase(Vector3 loc, double result, Consumer<Expression> postChecks) {
|
||||
return new TestCase(loc, result, postChecks);
|
||||
}
|
||||
|
||||
private void checkExpression(String expr, TestCase... cases) {
|
||||
Expression compiled = compile(expr, "x", "y", "z");
|
||||
for (TestCase aCase : cases) {
|
||||
Vector3 loc = aCase.loc;
|
||||
assertEquals(aCase.result, compiled.evaluate(loc.getX(), loc.getY(), loc.getZ()), 0,
|
||||
"Test case " + aCase + " failed (result)");
|
||||
aCase.postChecks.accept(compiled);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void torus() {
|
||||
checkExpression("(0.75-sqrt(x^2+y^2))^2+z^2 < 0.25^2",
|
||||
testCase(Vector3.at(0, 0, 0), 0),
|
||||
testCase(Vector3.at(0.5, 0.5, 0.5), 0),
|
||||
testCase(Vector3.at(1, 0, 0), 0),
|
||||
testCase(Vector3.at(0.5, 0.5, 0), 1),
|
||||
testCase(Vector3.at(0.75, 0.5, 0), 1),
|
||||
testCase(Vector3.at(0.75, 0, 0), 1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void gnarledOakTree() {
|
||||
checkExpression("(0.5+sin(atan2(x,z)*8)*0.2)*(sqrt(x*x+z*z)/0.5)^(-2)-1.2 < y",
|
||||
testCase(Vector3.at(-1, -1, -1), 1),
|
||||
testCase(Vector3.at(-1, 0, 1), 1),
|
||||
testCase(Vector3.at(1, 1, 1), 1),
|
||||
testCase(Vector3.at(0, 0, -1), 1),
|
||||
testCase(Vector3.at(0, 0, 0), 0),
|
||||
testCase(Vector3.at(0, 1, 0), 0),
|
||||
testCase(Vector3.at(0, 0, 0.32274), 0),
|
||||
testCase(Vector3.at(0, 0, 0.32275), 1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void rainbowTorus() {
|
||||
checkExpression("data=(32+15/2/pi*atan2(x,y))%16; (0.75-sqrt(x^2+y^2))^2+z^2 < 0.25^2",
|
||||
testCase(Vector3.at(0, 0, 0), 0),
|
||||
testCase(Vector3.at(0.5, 0.5, 0.5), 0),
|
||||
testCase(Vector3.at(1, 0, 0), 0),
|
||||
testCase(Vector3.at(0.5, 0.5, 0), 1).withData(1),
|
||||
testCase(Vector3.at(0.75, 0.5, 0), 1).withData(2),
|
||||
testCase(Vector3.at(0.75, 0, 0), 1).withData(3));
|
||||
}
|
||||
|
||||
@Test
|
||||
void rainbowEgg() {
|
||||
TestCase[] testCases = new TestCase[15];
|
||||
for (int i = 0; i < testCases.length; i++) {
|
||||
testCases[i] = testCase(Vector3.at(0, i / 16.0 - 0.5, 0), 1)
|
||||
.withData((i + 9) % 16);
|
||||
}
|
||||
testCases = Stream.concat(Stream.of(testCases), Stream.of(
|
||||
testCase(Vector3.at(0, 1, 0), 0)
|
||||
)).toArray(TestCase[]::new);
|
||||
checkExpression("data=(32+y*16+1)%16; y^2/9+x^2/6*(1/(1-0.4*y))+z^2/6*(1/(1-0.4*y))<0.08",
|
||||
testCases);
|
||||
}
|
||||
|
||||
@Test
|
||||
void heart() {
|
||||
checkExpression("(z/2)^2+x^2+(5*y/4-sqrt(abs(x)))^2<0.6",
|
||||
testCase(Vector3.at(0, 0, -1), 1),
|
||||
testCase(Vector3.at(0, 1, -1), 0),
|
||||
testCase(Vector3.at(-0.5, 1, 0), 1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void sineWave() {
|
||||
checkExpression("sin(x*5)/2<y",
|
||||
testCase(Vector3.at(1, -0.47947, 0), 0),
|
||||
testCase(Vector3.at(1, -0.47946, 0), 1),
|
||||
testCase(Vector3.at(2, -0.27202, 0), 0),
|
||||
testCase(Vector3.at(2, -0.27201, 0), 1),
|
||||
testCase(Vector3.at(3, 0.32513, 0), 0),
|
||||
testCase(Vector3.at(3, 0.32515, 0), 1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void radialCosine() {
|
||||
checkExpression("cos(sqrt(x^2+z^2)*5)/2<y",
|
||||
testCase(Vector3.at(0, 0.5, 0), 0),
|
||||
testCase(Vector3.at(0, 0.51, 0), 1),
|
||||
testCase(Vector3.at(Math.PI / 5, -0.5, 0), 0),
|
||||
testCase(Vector3.at(Math.PI / 5, -0.49, 0), 1),
|
||||
testCase(Vector3.at(Math.PI / 10, 0, 0), 0),
|
||||
testCase(Vector3.at(Math.PI / 10, 0.1, 0), 1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void circularHyperboloid() {
|
||||
checkExpression("-(z^2/12)+(y^2/4)-(x^2/12)>-0.03",
|
||||
testCase(Vector3.at(0, 0, 0), 1),
|
||||
testCase(Vector3.at(0, 1, 0), 1),
|
||||
testCase(Vector3.at(0, 1, 1), 1),
|
||||
testCase(Vector3.at(1, 1, 1), 1),
|
||||
testCase(Vector3.at(0, 0, 1), 0),
|
||||
testCase(Vector3.at(1, 0, 1), 0));
|
||||
}
|
||||
}
|
@ -2,4 +2,4 @@ junit.jupiter.execution.parallel.enabled=true
|
||||
junit.jupiter.execution.parallel.mode.default=concurrent
|
||||
junit.jupiter.execution.parallel.mode.classes.default=same_thread
|
||||
junit.jupiter.execution.parallel.config.strategy=dynamic
|
||||
junit.jupiter.execution.parallel.config.dynamic.factor=4
|
||||
junit.jupiter.execution.parallel.config.dynamic.factor=1
|
||||
|
@ -73,9 +73,11 @@ tasks.named<ShadowJar>("shadowJar") {
|
||||
dependencies {
|
||||
relocate("org.slf4j", "com.sk89q.worldedit.slf4j")
|
||||
relocate("org.apache.logging.slf4j", "com.sk89q.worldedit.log4jbridge")
|
||||
relocate("org.antlr.v4", "com.sk89q.worldedit.antlr4")
|
||||
|
||||
include(dependency("org.slf4j:slf4j-api"))
|
||||
include(dependency("org.apache.logging.log4j:log4j-slf4j-impl"))
|
||||
include(dependency("org.antlr:antlr4-runtime"))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -87,9 +87,11 @@ tasks.named<ShadowJar>("shadowJar") {
|
||||
dependencies {
|
||||
relocate("org.slf4j", "com.sk89q.worldedit.slf4j")
|
||||
relocate("org.apache.logging.slf4j", "com.sk89q.worldedit.log4jbridge")
|
||||
relocate("org.antlr.v4", "com.sk89q.worldedit.antlr4")
|
||||
|
||||
include(dependency("org.slf4j:slf4j-api"))
|
||||
include(dependency("org.apache.logging.log4j:log4j-slf4j-impl"))
|
||||
include(dependency("org.antlr:antlr4-runtime"))
|
||||
include(dependency("de.schlichtherle:truezip"))
|
||||
include(dependency("org.mozilla:rhino"))
|
||||
}
|
||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren