(Breaking) Moved some packages around.

Most of the changes should not break *most* WorldEdit-using plugins,
but implementations of WorldEdit are broken by this change.
This commit is contained in:
sk89q
2014-04-02 19:08:50 -07:00
parent 6e70e8c862
commit 469cb8c8b3
185 changed files with 13303 additions and 13270 deletions

View File

@ -0,0 +1,170 @@
// $Id$
/*
* WorldEdit
* Copyright (C) 2010, 2011 sk89q <http://www.sk89q.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU 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.List;
import java.util.Map;
import java.util.Stack;
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.ExpressionEnvironment;
import com.sk89q.worldedit.internal.expression.runtime.Constant;
import com.sk89q.worldedit.internal.expression.runtime.EvaluationException;
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;
/**
* Compiles and evaluates expressions.
*
* Supported operators:
* Logical: &&, ||, ! (unary)
* Bitwise: ~ (unary), >>, <<
* Arithmetic: +, -, *, /, % (modulo), ^ (power), - (unary), --, ++ (prefix only)
* Comparison: <=, >=, >, <, ==, !=, ~= (near)
*
* Supported functions: abs, acos, asin, atan, atan2, cbrt, ceil, cos, cosh, exp, floor, ln, log, log10, max, max, min, min, rint, round, sin, sinh, sqrt, tan, tanh and more. (See the Functions class or the wiki)
*
* Constants: e, pi
*
* To compile an equation, run <code>Expression.compile("expression here", "var1", "var2"...)</code>
* If you wish to run the equation multiple times, you can then optimize it, by calling myExpression.optimize();
* You can then run the equation as many times as you want by calling myExpression.evaluate(var1, var2...)
* You do not need to pass values for all variables specified while compiling.
* To query variables after evaluation, you can use myExpression.getVariable("variable name").
* To get a value out of these, use myVariable.getValue()
*
* Variables are also supported and can be set either by passing values to <code>evaluate</code>
*
* @author TomyLobo
*/
public class Expression {
private static final ThreadLocal<Stack<Expression>> instance = new ThreadLocal<Stack<Expression>>();
private final Map<String, RValue> variables = new HashMap<String, RValue>();
private final String[] variableNames;
private RValue root;
private final Functions functions = new Functions();
private ExpressionEnvironment environment;
public static Expression compile(String expression, String... variableNames) throws ExpressionException {
return new Expression(expression, variableNames);
}
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));
for (String variableName : variableNames) {
if (variables.containsKey(variableName)) {
throw new ExpressionException(-1, "Tried to overwrite identifier '" + variableName + "'");
}
variables.put(variableName, new Variable(0));
}
root = Parser.parse(tokens, this);
}
public double evaluate(double... values) 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 + ".");
}
((Variable) invokable).value = values[i];
}
pushInstance();
try {
return root.getValue();
} catch (ReturnException e) {
return e.getValue();
} finally {
popInstance();
}
}
public void optimize() throws EvaluationException {
root = root.optimize();
}
@Override
public String toString() {
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 static Expression getInstance() {
return instance.get().peek();
}
private void pushInstance() {
Stack<Expression> foo = instance.get();
if (foo == null) {
instance.set(foo = new Stack<Expression>());
}
foo.push(this);
}
private void popInstance() {
Stack<Expression> foo = instance.get();
foo.pop();
if (foo.isEmpty()) {
instance.set(null);
}
}
public Functions getFunctions() {
return functions;
}
public ExpressionEnvironment getEnvironment() {
return environment;
}
public void setEnvironment(ExpressionEnvironment environment) {
this.environment = environment;
}
}

View File

@ -0,0 +1,54 @@
// $Id$
/*
* WorldEdit
* Copyright (C) 2010, 2011 sk89q <http://www.sk89q.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.internal.expression;
/**
* Thrown when there's a problem during any stage of the expression compilation or evaluation.
*
* @author TomyLobo
*/
public class ExpressionException extends Exception {
private static final long serialVersionUID = 1L;
private final int position;
public ExpressionException(int position) {
this.position = position;
}
public ExpressionException(int position, String message, Throwable cause) {
super(message, cause);
this.position = position;
}
public ExpressionException(int position, String message) {
super(message);
this.position = position;
}
public ExpressionException(int position, Throwable cause) {
super(cause);
this.position = position;
}
public int getPosition() {
return position;
}
}

View File

@ -0,0 +1,61 @@
// $Id$
/*
* WorldEdit
* Copyright (C) 2010, 2011 sk89q <http://www.sk89q.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.internal.expression;
/**
* A common superinterface for everything passed to parser processors.
*
* @author TomyLobo
*/
public interface Identifiable {
/**
* Returns a character that helps identify the token, pseudo-token or invokable in question.
*
* <pre>
* Tokens:
* i - IdentifierToken
* 0 - NumberToken
* o - OperatorToken
* \0 - NullToken
* CharacterTokens are returned literally
*
* PseudoTokens:
* p - UnaryOperator
* V - UnboundVariable
*
* Nodes:
* c - Constant
* v - Variable
* f - Function
* l - LValueFunction
* s - Sequence
* I - Conditional
* w - While
* F - For
* r - Return
* b - Break (includes continue)
* S - SimpleFor
* C - Switch
* </pre>
*/
public abstract char id();
public int getPosition();
}

View File

@ -0,0 +1,233 @@
// $Id$
/*
* WorldEdit
* Copyright (C) 2010, 2011 sk89q <http://www.sk89q.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.internal.expression.lexer;
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;
import com.sk89q.worldedit.internal.expression.lexer.tokens.*;
/**
* Processes a string into a list of tokens.
*
* Tokens can be numbers, identifiers, operators and assorted other characters.
*
* @author TomyLobo
*/
public class Lexer {
private final String expression;
private int position = 0;
private Lexer(String expression) {
this.expression = expression;
}
public static final 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<Character>();
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<String>(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 final List<Token> tokenize() throws LexerException {
List<Token> tokens = new ArrayList<Token>();
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.length() > 0) {
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.length() > 0) {
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 final 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<Character, Lexer.DecisionTree>();
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);
}
}
}

View File

@ -0,0 +1,51 @@
// $Id$
/*
* WorldEdit
* Copyright (C) 2010, 2011 sk89q <http://www.sk89q.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU 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.ExpressionException;
/**
* Thrown when the lexer encounters a problem.
*
* @author TomyLobo
*/
public class LexerException extends ExpressionException {
private static final long serialVersionUID = 1L;
public LexerException(int position) {
super(position, getPrefix(position));
}
public LexerException(int position, String message, Throwable cause) {
super(position, getPrefix(position) + ": " + message, cause);
}
public LexerException(int position, String message) {
super(position, getPrefix(position) + ": " + message);
}
public LexerException(int position, Throwable cause) {
super(position, getPrefix(position), cause);
}
private static String getPrefix(int position) {
return position < 0 ? "Lexer error" : ("Lexer error at " + (position + 1));
}
}

View File

@ -0,0 +1,44 @@
// $Id$
/*
* WorldEdit
* Copyright (C) 2010, 2011 sk89q <http://www.sk89q.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU 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.
*
* @author TomyLobo
*/
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 + ")";
}
}

View File

@ -0,0 +1,44 @@
// $Id$
/*
* WorldEdit
* Copyright (C) 2010, 2011 sk89q <http://www.sk89q.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU 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
*
* @author TomyLobo
*/
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 + ")";
}
}

View File

@ -0,0 +1,44 @@
// $Id$
/*
* WorldEdit
* Copyright (C) 2010, 2011 sk89q <http://www.sk89q.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU 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
*
* @author TomyLobo
*/
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 + ")";
}
}

View File

@ -0,0 +1,44 @@
// $Id$
/*
* WorldEdit
* Copyright (C) 2010, 2011 sk89q <http://www.sk89q.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.internal.expression.lexer.tokens;
/**
* A number
*
* @author TomyLobo
*/
public class NumberToken extends Token {
public final double value;
public NumberToken(int position, double value) {
super(position);
this.value = value;
}
@Override
public char id() {
return '0';
}
@Override
public String toString() {
return "NumberToken(" + value + ")";
}
}

View File

@ -0,0 +1,44 @@
// $Id$
/*
* WorldEdit
* Copyright (C) 2010, 2011 sk89q <http://www.sk89q.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.internal.expression.lexer.tokens;
/**
* A unary or binary operator.
*
* @author TomyLobo
*/
public class OperatorToken extends Token {
public final String operator;
public OperatorToken(int position, String operator) {
super(position);
this.operator = operator;
}
@Override
public char id() {
return 'o';
}
@Override
public String toString() {
return "OperatorToken(" + operator + ")";
}
}

View File

@ -0,0 +1,40 @@
// $Id$
/*
* WorldEdit
* Copyright (C) 2010, 2011 sk89q <http://www.sk89q.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU 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.
*
* @author TomyLobo
*/
public abstract class Token implements Identifiable {
private final int position;
public Token(int position) {
this.position = position;
}
@Override
public int getPosition() {
return position;
}
}

View File

@ -0,0 +1,466 @@
// $Id$
/*
* WorldEdit
* Copyright (C) 2010, 2011 sk89q <http://www.sk89q.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.internal.expression.parser;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
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;
/**
* Processes a list of tokens into an executable tree.
*
* Tokens can be numbers, identifiers, operators and assorted other characters.
*
* @author TomyLobo
*/
public class Parser {
private final class NullToken extends Token {
private NullToken(int position) {
super(position);
}
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 final 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<RValue>();
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<Double>();
final List<RValue> caseStatements = new ArrayList<RValue>();
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 final RValue parseExpression(boolean canBeEmpty) throws ParserException {
LinkedList<Identifiable> halfProcessed = new LinkedList<Identifiable>();
// 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<RValue>();
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 final RValue parseBracket() throws ParserException {
consumeCharacter('(');
final RValue ret = parseExpression(false);
consumeCharacter(')');
return ret;
}
private boolean hasKeyword(String keyword) {
final Token next = peek();
if (!(next instanceof KeywordToken)) {
return false;
}
return ((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;
}
}

View File

@ -0,0 +1,51 @@
// $Id$
/*
* WorldEdit
* Copyright (C) 2010, 2011 sk89q <http://www.sk89q.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU 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.ExpressionException;
/**
* Thrown when the parser encounters a problem.
*
* @author TomyLobo
*/
public class ParserException extends ExpressionException {
private static final long serialVersionUID = 1L;
public ParserException(int position) {
super(position, getPrefix(position));
}
public ParserException(int position, String message, Throwable cause) {
super(position, getPrefix(position) + ": " + message, cause);
}
public ParserException(int position, String message) {
super(position, getPrefix(position) + ": " + message);
}
public ParserException(int position, Throwable cause) {
super(position, getPrefix(position), cause);
}
private static String getPrefix(int position) {
return position < 0 ? "Parser error" : ("Parser error at " + (position + 1));
}
}

View File

@ -0,0 +1,351 @@
// $Id$
/*
* WorldEdit
* Copyright (C) 2010, 2011 sk89q <http://www.sk89q.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.internal.expression.parser;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;
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.RValue;
import com.sk89q.worldedit.internal.expression.runtime.Operators;
/**
* Helper classfor Parser. Contains processors for statements and operators.
*
* @author TomyLobo
*/
public final class ParserProcessors {
private static final Map<String, String> unaryOpMap = new HashMap<String, String>();
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<String, String>();
for (int j = 0; j < a.length; ++j) {
final Object[] element = a[j];
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<String, String>();
for (int j = 0; j < a.length; ++j) {
final Object[] element = a[j];
m.put((String) element[0], (String) element[1]);
}
}
}
}
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<Identifiable>();
LinkedList<Identifiable> rhs = new LinkedList<Identifiable>();
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<Identifiable>();
LinkedList<Identifiable> rhs = new LinkedList<Identifiable>();
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<Identifiable>();
LinkedList<Identifiable> mhs = new LinkedList<Identifiable>();
LinkedList<Identifiable> rhs = new LinkedList<Identifiable>();
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<UnaryOperator>();
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;
}
}

View File

@ -0,0 +1,43 @@
// $Id$
/*
* WorldEdit
* Copyright (C) 2010, 2011 sk89q <http://www.sk89q.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU 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.
*
* @author TomyLobo
*/
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;
}
}

View File

@ -0,0 +1,50 @@
// $Id$
/*
* WorldEdit
* Copyright (C) 2010, 2011 sk89q <http://www.sk89q.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU 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.
*
* @author TomyLobo
*/
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 + ")";
}
}

View File

@ -0,0 +1,61 @@
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;
// TODO Auto-generated constructor stub
}
@Override
public char id() {
// TODO Auto-generated method stub
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;
}
}

View File

@ -0,0 +1,50 @@
// $Id$
/*
* WorldEdit
* Copyright (C) 2010, 2011 sk89q <http://www.sk89q.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU 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.
*
* @author TomyLobo
*/
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";
}
}

View File

@ -0,0 +1,38 @@
// $Id$
/*
* WorldEdit
* Copyright (C) 2010, 2011 sk89q <http://www.sk89q.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU 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 break or continue is encountered.
* Loop constructs catch this exception.
*
* @author TomyLobo
*/
public class BreakException extends EvaluationException {
private static final long serialVersionUID = 1L;
final boolean doContinue;
public BreakException(boolean doContinue) {
super(-1, doContinue ? "'continue' encountered outside a loop" : "'break' encountered outside a loop");
this.doContinue = doContinue;
}
}

View File

@ -0,0 +1,93 @@
// $Id$
/*
* WorldEdit
* Copyright (C) 2010, 2011 sk89q <http://www.sk89q.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU 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.
*
* @author TomyLobo
*/
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;
}
}

View File

@ -0,0 +1,49 @@
// $Id$
/*
* WorldEdit
* Copyright (C) 2010, 2011 sk89q <http://www.sk89q.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.internal.expression.runtime;
/**
* A constant.
*
* @author TomyLobo
*/
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';
}
}

View File

@ -0,0 +1,51 @@
// $Id$
/*
* WorldEdit
* Copyright (C) 2010, 2011 sk89q <http://www.sk89q.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU 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.ExpressionException;
/**
* Thrown when there's a problem during expression evaluation.
*
* @author TomyLobo
*/
public class EvaluationException extends ExpressionException {
private static final long serialVersionUID = 1L;
public EvaluationException(int position) {
super(position, getPrefix(position));
}
public EvaluationException(int position, String message, Throwable cause) {
super(position, getPrefix(position) + ": " + message, cause);
}
public EvaluationException(int position, String message) {
super(position, getPrefix(position) + ": " + message);
}
public EvaluationException(int position, Throwable cause) {
super(position, getPrefix(position), cause);
}
private static String getPrefix(int position) {
return position < 0 ? "Evaluation error" : ("Evaluation error at " + (position + 1));
}
}

View File

@ -0,0 +1,13 @@
package com.sk89q.worldedit.internal.expression.runtime;
/**
* Represents a way to access blocks in a world. Has to accept non-rounded coordinates.
*/
public interface ExpressionEnvironment {
int getBlockType(double x, double y, double z);
int getBlockData(double x, double y, double z);
int getBlockTypeAbs(double x, double y, double z);
int getBlockDataAbs(double x, double y, double z);
int getBlockTypeRel(double x, double y, double z);
int getBlockDataRel(double x, double y, double z);
}

View File

@ -0,0 +1,104 @@
// $Id$
/*
* WorldEdit
* Copyright (C) 2010, 2011 sk89q <http://www.sk89q.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU 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.
*
* @author TomyLobo
*/
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.");
}
++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;
}
}

View File

@ -0,0 +1,123 @@
// $Id$
/*
* WorldEdit
* Copyright (C) 2010, 2011 sk89q <http://www.sk89q.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.internal.expression.runtime;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import com.sk89q.worldedit.internal.expression.Expression;
import com.sk89q.worldedit.internal.expression.parser.ParserException;
/**
* Wrapper for a Java method and its arguments (other Nodes)
*
* @author TomyLobo
*/
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 final 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;
}
}

View File

@ -0,0 +1,439 @@
// $Id$
/*
* WorldEdit
* Copyright (C) 2010, 2011 sk89q <http://www.sk89q.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.internal.expression.runtime;
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;
import com.sk89q.worldedit.internal.expression.Expression;
import com.sk89q.worldedit.internal.expression.runtime.Function.Dynamic;
/**
* Contains all functions that can be used in expressions.
*
* @author TomyLobo
*/
@SuppressWarnings("UnusedDeclaration")
public final class Functions {
private static class Overload {
private final Method method;
private final int mask;
private final boolean isSetter;
public 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<String, List<Overload>>();
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.get(methodName);
if (overloads == null) {
functions.put(methodName, overloads = new ArrayList<Overload>());
}
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<Integer, double[]>();
private final Map<Integer, double[]> megabuf = new HashMap<Integer, double[]>();
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 double queryInternal(RValue type, RValue data, double typeId, double dataValue) throws EvaluationException {
// Compare to input values and determine return value
final double ret = (typeId == type.getValue() && 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);
}
}

View File

@ -0,0 +1,36 @@
// $Id$
/*
* WorldEdit
* Copyright (C) 2010, 2011 sk89q <http://www.sk89q.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU 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.
*
* @author TomyLobo
*/
public interface LValue extends RValue {
public double assign(double value) throws EvaluationException;
public LValue optimize() throws EvaluationException;
public LValue bindVariables(Expression expression, boolean preferLValue) throws ParserException;
}

View File

@ -0,0 +1,76 @@
// $Id$
/*
* WorldEdit
* Copyright (C) 2010, 2011 sk89q <http://www.sk89q.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.internal.expression.runtime;
import java.lang.reflect.Method;
import com.sk89q.worldedit.internal.expression.Expression;
import com.sk89q.worldedit.internal.expression.parser.ParserException;
/**
* Wrapper for a pair of Java methods and their arguments (other Nodes), forming an LValue
*
* @author TomyLobo
*/
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;
}
}

View File

@ -0,0 +1,53 @@
// $Id$
/*
* WorldEdit
* Copyright (C) 2010, 2011 sk89q <http://www.sk89q.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU 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.
*
* @author TomyLobo
*/
public abstract class Node implements RValue {
private final int position;
public Node(int position) {
this.position = position;
}
@Override
public abstract String toString();
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;
}
}

View File

@ -0,0 +1,225 @@
// $Id$
/*
* WorldEdit
* Copyright (C) 2010, 2011 sk89q <http://www.sk89q.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU 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.
*
* @author TomyLobo
*/
@SuppressWarnings("UnusedDeclaration")
public final class 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;
}
}

View File

@ -0,0 +1,37 @@
// $Id$
/*
* WorldEdit
* Copyright (C) 2010, 2011 sk89q <http://www.sk89q.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU 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.
*
* @author TomyLobo
*/
public interface RValue extends Identifiable {
public double getValue() throws EvaluationException;
public RValue optimize() throws EvaluationException;
public RValue bindVariables(Expression expression, boolean preferLValue) throws ParserException;
}

View File

@ -0,0 +1,60 @@
// $Id$
/*
* WorldEdit
* Copyright (C) 2010, 2011 sk89q <http://www.sk89q.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU 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.
*
* @author TomyLobo
*/
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;
}
}

View File

@ -0,0 +1,42 @@
// $Id$
/*
* WorldEdit
* Copyright (C) 2010, 2011 sk89q <http://www.sk89q.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU 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.
*
* @author TomyLobo
*/
public class ReturnException extends EvaluationException {
private static final long serialVersionUID = 1L;
final double value;
public ReturnException(double value) {
super(-1);
this.value = value;
}
public double getValue() {
return value;
}
}

View File

@ -0,0 +1,109 @@
// $Id$
/*
* WorldEdit
* Copyright (C) 2010, 2011 sk89q <http://www.sk89q.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.internal.expression.runtime;
import java.util.ArrayList;
import java.util.List;
import com.sk89q.worldedit.internal.expression.Expression;
import com.sk89q.worldedit.internal.expression.parser.ParserException;
/**
* A sequence of operations, usually separated by semicolons in the input stream.
*
* @author TomyLobo
*/
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>();
RValue droppedLast = null;
for (RValue invokable : sequence) {
droppedLast = null;
invokable = invokable.optimize();
if (invokable instanceof Sequence) {
for (RValue subInvokable : ((Sequence) invokable).sequence) {
newSequence.add(subInvokable);
}
} 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;
}
}

View File

@ -0,0 +1,101 @@
// $Id$
/*
* WorldEdit
* Copyright (C) 2010, 2011 sk89q <http://www.sk89q.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU 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.
*
* @author TomyLobo
*/
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.");
}
++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(), (LValue) 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;
}
}

View File

@ -0,0 +1,208 @@
// $Id$
/*
* WorldEdit
* Copyright (C) 2010, 2011 sk89q <http://www.sk89q.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.internal.expression.runtime;
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;
import com.sk89q.worldedit.internal.expression.Expression;
import com.sk89q.worldedit.internal.expression.parser.ParserException;
/**
* A switch/case construct.
*
* @author TomyLobo
*/
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<Double, Integer>();
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<RValue>();
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<Double, Integer>();
Map<Integer, Double> backMap = new HashMap<Integer, Double>();
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) {
for (RValue subInvokable : ((Sequence) invokable).sequence) {
newSequence.add(subInvokable);
}
} 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;
}
}

View File

@ -0,0 +1,67 @@
// $Id$
/*
* WorldEdit
* Copyright (C) 2010, 2011 sk89q <http://www.sk89q.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU 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.
*
* @author TomyLobo
*/
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;
}
}

View File

@ -0,0 +1,127 @@
// $Id$
/*
* WorldEdit
* Copyright (C) 2010, 2011 sk89q <http://www.sk89q.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU 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.
*
* @author TomyLobo
*/
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.");
}
++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.");
}
++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;
}
}