geforkt von Mirrors/FastAsyncWorldEdit
Added an expression parser.
Dieser Commit ist enthalten in:
Ursprung
99002c786b
Commit
d93d85cd37
81
src/main/java/com/sk89q/worldedit/expression/Expression.java
Normale Datei
81
src/main/java/com/sk89q/worldedit/expression/Expression.java
Normale Datei
@ -0,0 +1,81 @@
|
|||||||
|
// $Id$
|
||||||
|
/*
|
||||||
|
* WorldEdit
|
||||||
|
* Copyright (C) 2010, 2011 sk89q <http://www.sk89q.com>
|
||||||
|
*
|
||||||
|
* 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.expression;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import com.sk89q.worldedit.expression.lexer.Lexer;
|
||||||
|
import com.sk89q.worldedit.expression.lexer.tokens.Token;
|
||||||
|
import com.sk89q.worldedit.expression.parser.Parser;
|
||||||
|
import com.sk89q.worldedit.expression.parser.ParserException;
|
||||||
|
import com.sk89q.worldedit.expression.runtime.Constant;
|
||||||
|
import com.sk89q.worldedit.expression.runtime.EvaluationException;
|
||||||
|
import com.sk89q.worldedit.expression.runtime.Invokable;
|
||||||
|
import com.sk89q.worldedit.expression.runtime.Variable;
|
||||||
|
|
||||||
|
public class Expression {
|
||||||
|
private final Map<String, Invokable> variables = new HashMap<String, Invokable>();
|
||||||
|
private final String[] variableNames;
|
||||||
|
private Invokable root;
|
||||||
|
|
||||||
|
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 ParserException {
|
||||||
|
this.variableNames = variableNames;
|
||||||
|
variables.put("e", new Constant(-1, Math.E));
|
||||||
|
variables.put("pi", new Constant(-1, Math.PI));
|
||||||
|
for (String variableName : variableNames) {
|
||||||
|
variables.put(variableName, new Variable(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
root = Parser.parse(tokens, variables);
|
||||||
|
}
|
||||||
|
|
||||||
|
public double evaluate(double... values) throws EvaluationException {
|
||||||
|
for (int i = 0; i < values.length; ++i) {
|
||||||
|
final String variableName = variableNames[i];
|
||||||
|
final Invokable invokable = variables.get(variableName);
|
||||||
|
if (!(invokable instanceof Variable)) {
|
||||||
|
throw new EvaluationException(invokable.getPosition(), "Tried to assign constant " + variableName + ".");
|
||||||
|
}
|
||||||
|
|
||||||
|
((Variable) invokable).value = values[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return root.invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void optimize() throws EvaluationException {
|
||||||
|
root = root.optimize();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return root.toString();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,49 @@
|
|||||||
|
// $Id$
|
||||||
|
/*
|
||||||
|
* WorldEdit
|
||||||
|
* Copyright (C) 2010, 2011 sk89q <http://www.sk89q.com>
|
||||||
|
*
|
||||||
|
* 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.expression;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
46
src/main/java/com/sk89q/worldedit/expression/Identifiable.java
Normale Datei
46
src/main/java/com/sk89q/worldedit/expression/Identifiable.java
Normale Datei
@ -0,0 +1,46 @@
|
|||||||
|
// $Id$
|
||||||
|
/*
|
||||||
|
* WorldEdit
|
||||||
|
* Copyright (C) 2010, 2011 sk89q <http://www.sk89q.com>
|
||||||
|
*
|
||||||
|
* 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.expression;
|
||||||
|
|
||||||
|
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 - PrefixOperator
|
||||||
|
*
|
||||||
|
* Invokables:
|
||||||
|
* c - Constant
|
||||||
|
* f - Function
|
||||||
|
* v - Variable
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
public abstract char id();
|
||||||
|
|
||||||
|
public int getPosition();
|
||||||
|
}
|
197
src/main/java/com/sk89q/worldedit/expression/lexer/Lexer.java
Normale Datei
197
src/main/java/com/sk89q/worldedit/expression/lexer/Lexer.java
Normale Datei
@ -0,0 +1,197 @@
|
|||||||
|
// $Id$
|
||||||
|
/*
|
||||||
|
* WorldEdit
|
||||||
|
* Copyright (C) 2010, 2011 sk89q <http://www.sk89q.com>
|
||||||
|
*
|
||||||
|
* 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.expression.lexer;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import com.sk89q.worldedit.expression.lexer.tokens.*;
|
||||||
|
|
||||||
|
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(null, // not implemented
|
||||||
|
'=', 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 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();
|
||||||
|
switch (ch) {
|
||||||
|
case ',':
|
||||||
|
case '(':
|
||||||
|
case ')':
|
||||||
|
tokens.add(new CharacterToken(position++, ch));
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
final Matcher numberMatcher = numberPattern.matcher(expression.substring(position));
|
||||||
|
if (numberMatcher.lookingAt()) {
|
||||||
|
String numberPart = numberMatcher.group(1);
|
||||||
|
if (!numberPart.isEmpty()) {
|
||||||
|
try {
|
||||||
|
tokens.add(new NumberToken(position, Double.parseDouble(numberPart)));
|
||||||
|
}
|
||||||
|
catch (NumberFormatException e) {
|
||||||
|
throw new LexerException(position, "Number parsing failed", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
position += numberPart.length();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final Matcher identifierMatcher = identifierPattern.matcher(expression.substring(position));
|
||||||
|
if (identifierMatcher.lookingAt()) {
|
||||||
|
String identifierPart = identifierMatcher.group(1);
|
||||||
|
if (!identifierPart.isEmpty()) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,46 @@
|
|||||||
|
// $Id$
|
||||||
|
/*
|
||||||
|
* WorldEdit
|
||||||
|
* Copyright (C) 2010, 2011 sk89q <http://www.sk89q.com>
|
||||||
|
*
|
||||||
|
* 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.expression.lexer;
|
||||||
|
|
||||||
|
import com.sk89q.worldedit.expression.ExpressionException;
|
||||||
|
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
// $Id$
|
||||||
|
/*
|
||||||
|
* WorldEdit
|
||||||
|
* Copyright (C) 2010, 2011 sk89q <http://www.sk89q.com>
|
||||||
|
*
|
||||||
|
* 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.expression.lexer.tokens;
|
||||||
|
|
||||||
|
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 + ")";
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
// $Id$
|
||||||
|
/*
|
||||||
|
* WorldEdit
|
||||||
|
* Copyright (C) 2010, 2011 sk89q <http://www.sk89q.com>
|
||||||
|
*
|
||||||
|
* 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.expression.lexer.tokens;
|
||||||
|
|
||||||
|
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 + ")";
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
// $Id$
|
||||||
|
/*
|
||||||
|
* WorldEdit
|
||||||
|
* Copyright (C) 2010, 2011 sk89q <http://www.sk89q.com>
|
||||||
|
*
|
||||||
|
* 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.expression.lexer.tokens;
|
||||||
|
|
||||||
|
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 + ")";
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
// $Id$
|
||||||
|
/*
|
||||||
|
* WorldEdit
|
||||||
|
* Copyright (C) 2010, 2011 sk89q <http://www.sk89q.com>
|
||||||
|
*
|
||||||
|
* 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.expression.lexer.tokens;
|
||||||
|
|
||||||
|
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 + ")";
|
||||||
|
}
|
||||||
|
}
|
35
src/main/java/com/sk89q/worldedit/expression/lexer/tokens/Token.java
Normale Datei
35
src/main/java/com/sk89q/worldedit/expression/lexer/tokens/Token.java
Normale Datei
@ -0,0 +1,35 @@
|
|||||||
|
// $Id$
|
||||||
|
/*
|
||||||
|
* WorldEdit
|
||||||
|
* Copyright (C) 2010, 2011 sk89q <http://www.sk89q.com>
|
||||||
|
*
|
||||||
|
* 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.expression.lexer.tokens;
|
||||||
|
|
||||||
|
import com.sk89q.worldedit.expression.Identifiable;
|
||||||
|
|
||||||
|
public abstract class Token implements Identifiable {
|
||||||
|
private final int position;
|
||||||
|
|
||||||
|
public Token(int position) {
|
||||||
|
this.position = position;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getPosition() {
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
}
|
357
src/main/java/com/sk89q/worldedit/expression/parser/Parser.java
Normale Datei
357
src/main/java/com/sk89q/worldedit/expression/parser/Parser.java
Normale Datei
@ -0,0 +1,357 @@
|
|||||||
|
// $Id$
|
||||||
|
/*
|
||||||
|
* WorldEdit
|
||||||
|
* Copyright (C) 2010, 2011 sk89q <http://www.sk89q.com>
|
||||||
|
*
|
||||||
|
* 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.expression.parser;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import com.sk89q.worldedit.expression.Identifiable;
|
||||||
|
import com.sk89q.worldedit.expression.lexer.tokens.IdentifierToken;
|
||||||
|
import com.sk89q.worldedit.expression.lexer.tokens.NumberToken;
|
||||||
|
import com.sk89q.worldedit.expression.lexer.tokens.OperatorToken;
|
||||||
|
import com.sk89q.worldedit.expression.lexer.tokens.Token;
|
||||||
|
import com.sk89q.worldedit.expression.runtime.Constant;
|
||||||
|
import com.sk89q.worldedit.expression.runtime.Functions;
|
||||||
|
import com.sk89q.worldedit.expression.runtime.Invokable;
|
||||||
|
import com.sk89q.worldedit.expression.runtime.Operators;
|
||||||
|
|
||||||
|
public class Parser {
|
||||||
|
private final class NullToken extends Token {
|
||||||
|
private NullToken(int position) {
|
||||||
|
super(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
public char id() {
|
||||||
|
return '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString() {
|
||||||
|
return "NullToken";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final List<Token> tokens;
|
||||||
|
private int position = 0;
|
||||||
|
private Map<String, Invokable> variables;
|
||||||
|
|
||||||
|
private Parser(List<Token> tokens, Map<String, Invokable> variables) {
|
||||||
|
this.tokens = tokens;
|
||||||
|
this.variables = variables;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Invokable parse(List<Token> tokens, Map<String, Invokable> variables) throws ParserException {
|
||||||
|
return new Parser(tokens, variables).parse();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Invokable parse() throws ParserException {
|
||||||
|
final Invokable ret = parseInternal();
|
||||||
|
if (position < tokens.size()) {
|
||||||
|
final Token token = peek();
|
||||||
|
throw new ParserException(token.getPosition(), "Extra token at the end of the input: " + token);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Invokable parseInternal() 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(parseFunction(identifierToken));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Invokable variable = variables.get(identifierToken.value);
|
||||||
|
if (variable == null) {
|
||||||
|
throw new ParserException(current.getPosition(), "Variable '" + identifierToken.value + "' not found");
|
||||||
|
}
|
||||||
|
halfProcessed.add(variable);
|
||||||
|
}
|
||||||
|
expressionStart = false;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '(':
|
||||||
|
halfProcessed.add(parseBracket());
|
||||||
|
expressionStart = false;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ',':
|
||||||
|
case ')':
|
||||||
|
break loop;
|
||||||
|
|
||||||
|
case 'o':
|
||||||
|
if (expressionStart) {
|
||||||
|
halfProcessed.add(new PrefixOperator((OperatorToken) current));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
halfProcessed.add(current);
|
||||||
|
}
|
||||||
|
++position;
|
||||||
|
expressionStart = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
halfProcessed.add(current);
|
||||||
|
++position;
|
||||||
|
expressionStart = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// process binary operators
|
||||||
|
return processBinaryOps(halfProcessed, binaryOpMaps.length - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Map<String, String>[] binaryOpMaps;
|
||||||
|
|
||||||
|
private static final Map<String, String> unaryOpMap = new HashMap<String, String>();
|
||||||
|
static {
|
||||||
|
final Object[][][] binaryOps = {
|
||||||
|
{
|
||||||
|
{ "^", "pow" },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{ "*", "mul" },
|
||||||
|
{ "/", "div" },
|
||||||
|
{ "%", "mod" },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{ "+", "add" },
|
||||||
|
{ "-", "sub" },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{ "<<", "shl" },
|
||||||
|
{ ">>", "shr" },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{ "<", "lth" },
|
||||||
|
{ ">", "gth" },
|
||||||
|
{ "<=", "leq" },
|
||||||
|
{ ">=", "geq" },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{ "==", "equ" },
|
||||||
|
{ "!=", "neq" },
|
||||||
|
{ "~=", "near" },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{ "&&", "and" },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{ "||", "or" },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
final Map<String, String>[] tmp = binaryOpMaps = new Map[binaryOps.length];
|
||||||
|
for (int i = 0; i < binaryOps.length; ++i) {
|
||||||
|
final Object[][] a = binaryOps[i];
|
||||||
|
switch (a.length) {
|
||||||
|
case 0:
|
||||||
|
tmp[i] = Collections.emptyMap();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
final Object[] first = a[0];
|
||||||
|
tmp[i] = Collections.singletonMap((String) first[0], (String) first[1]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
Map<String, String> m = tmp[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]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unaryOpMap.put("-", "neg");
|
||||||
|
unaryOpMap.put("!", "not");
|
||||||
|
unaryOpMap.put("~", "inv");
|
||||||
|
}
|
||||||
|
|
||||||
|
private Invokable processBinaryOps(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 (rhs.isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(identifiable instanceof OperatorToken)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
operator = binaryOpMaps[level].get(((OperatorToken) identifiable).operator);
|
||||||
|
if (operator == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
rhs.removeFirst();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
lhs.addFirst(identifiable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Invokable rhsInvokable = processBinaryOps(rhs, level - 1);
|
||||||
|
if (operator == null) return rhsInvokable;
|
||||||
|
|
||||||
|
Invokable lhsInvokable = processBinaryOps(lhs, level);
|
||||||
|
|
||||||
|
try {
|
||||||
|
return Operators.getOperator(-1, operator, lhsInvokable, rhsInvokable); // TODO: get real position
|
||||||
|
}
|
||||||
|
catch (NoSuchMethodException e) {
|
||||||
|
final Token operatorToken = (Token) input.get(lhs.size());
|
||||||
|
throw new ParserException(operatorToken.getPosition(), "Couldn't find operator '" + operator + "'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Invokable processUnaryOps(LinkedList<Identifiable> input) throws ParserException {
|
||||||
|
if (input.isEmpty()) {
|
||||||
|
throw new ParserException(-1, "Expression missing.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Invokable ret = (Invokable) input.removeLast();
|
||||||
|
while (!input.isEmpty()) {
|
||||||
|
final Identifiable last = input.removeLast();
|
||||||
|
if (last instanceof PrefixOperator) {
|
||||||
|
final String operator = ((PrefixOperator) last).operator;
|
||||||
|
if (operator.equals("+")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
String opName = unaryOpMap.get(operator);
|
||||||
|
if (opName != null) {
|
||||||
|
try {
|
||||||
|
ret = Operators.getOperator(last.getPosition(), opName, ret);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
catch (NoSuchMethodException e) {
|
||||||
|
throw new ParserException(last.getPosition(), "No such prefix operator: " + operator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (last instanceof Token) {
|
||||||
|
throw new ParserException(last.getPosition(), "Extra token found in expression: " + last);
|
||||||
|
}
|
||||||
|
else if (last instanceof Invokable) {
|
||||||
|
throw new ParserException(last.getPosition(), "Extra expression found: " + last);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new ParserException(last.getPosition(), "Extra element found: " + last);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Token peek() {
|
||||||
|
if (position >= tokens.size()) {
|
||||||
|
return new NullToken(tokens.get(tokens.size() - 1).getPosition() + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return tokens.get(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Identifiable parseFunction(IdentifierToken identifierToken) throws ParserException {
|
||||||
|
if (peek().id() != '(') {
|
||||||
|
throw new ParserException(peek().getPosition(), "Unexpected character in parseBracket");
|
||||||
|
}
|
||||||
|
++position;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (peek().id() == ')') {
|
||||||
|
return Functions.getFunction(identifierToken.getPosition(), identifierToken.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Invokable> args = new ArrayList<Invokable>();
|
||||||
|
|
||||||
|
loop: while (true) {
|
||||||
|
args.add(parseInternal());
|
||||||
|
|
||||||
|
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 Invokable[args.size()]));
|
||||||
|
}
|
||||||
|
catch (NoSuchMethodException e) {
|
||||||
|
throw new ParserException(identifierToken.getPosition(), "Function not found", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Invokable parseBracket() throws ParserException {
|
||||||
|
if (peek().id() != '(') {
|
||||||
|
throw new ParserException(peek().getPosition(), "Unexpected character in parseBracket");
|
||||||
|
}
|
||||||
|
++position;
|
||||||
|
|
||||||
|
final Invokable ret = parseInternal();
|
||||||
|
|
||||||
|
if (peek().id() != ')') {
|
||||||
|
throw new ParserException(peek().getPosition(), "Unmatched opening bracket");
|
||||||
|
}
|
||||||
|
++position;
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,46 @@
|
|||||||
|
// $Id$
|
||||||
|
/*
|
||||||
|
* WorldEdit
|
||||||
|
* Copyright (C) 2010, 2011 sk89q <http://www.sk89q.com>
|
||||||
|
*
|
||||||
|
* 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.expression.parser;
|
||||||
|
|
||||||
|
import com.sk89q.worldedit.expression.ExpressionException;
|
||||||
|
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
package com.sk89q.worldedit.expression.parser;
|
||||||
|
|
||||||
|
import com.sk89q.worldedit.expression.lexer.tokens.OperatorToken;
|
||||||
|
|
||||||
|
public class PrefixOperator extends PseudoToken {
|
||||||
|
final String operator;
|
||||||
|
|
||||||
|
public PrefixOperator(OperatorToken operatorToken) {
|
||||||
|
super(operatorToken.getPosition());
|
||||||
|
operator = operatorToken.operator;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public char id() {
|
||||||
|
return 'p';
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "PrefixOperator(" + operator + ")";
|
||||||
|
}
|
||||||
|
}
|
38
src/main/java/com/sk89q/worldedit/expression/parser/PseudoToken.java
Normale Datei
38
src/main/java/com/sk89q/worldedit/expression/parser/PseudoToken.java
Normale Datei
@ -0,0 +1,38 @@
|
|||||||
|
// $Id$
|
||||||
|
/*
|
||||||
|
* WorldEdit
|
||||||
|
* Copyright (C) 2010, 2011 sk89q <http://www.sk89q.com>
|
||||||
|
*
|
||||||
|
* 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.expression.parser;
|
||||||
|
|
||||||
|
import com.sk89q.worldedit.expression.Identifiable;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
44
src/main/java/com/sk89q/worldedit/expression/runtime/Constant.java
Normale Datei
44
src/main/java/com/sk89q/worldedit/expression/runtime/Constant.java
Normale Datei
@ -0,0 +1,44 @@
|
|||||||
|
// $Id$
|
||||||
|
/*
|
||||||
|
* WorldEdit
|
||||||
|
* Copyright (C) 2010, 2011 sk89q <http://www.sk89q.com>
|
||||||
|
*
|
||||||
|
* 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.expression.runtime;
|
||||||
|
|
||||||
|
public final class Constant extends Invokable {
|
||||||
|
private final double value;
|
||||||
|
|
||||||
|
public Constant(int position, double value) {
|
||||||
|
super(position);
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double invoke() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return String.valueOf(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public char id() {
|
||||||
|
return 'c';
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
package com.sk89q.worldedit.expression.runtime;
|
||||||
|
|
||||||
|
import com.sk89q.worldedit.expression.ExpressionException;
|
||||||
|
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
94
src/main/java/com/sk89q/worldedit/expression/runtime/Function.java
Normale Datei
94
src/main/java/com/sk89q/worldedit/expression/runtime/Function.java
Normale Datei
@ -0,0 +1,94 @@
|
|||||||
|
// $Id$
|
||||||
|
/*
|
||||||
|
* WorldEdit
|
||||||
|
* Copyright (C) 2010, 2011 sk89q <http://www.sk89q.com>
|
||||||
|
*
|
||||||
|
* 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.expression.runtime;
|
||||||
|
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
|
public class Function extends Invokable {
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
public @interface Dynamic { }
|
||||||
|
|
||||||
|
final Method method;
|
||||||
|
final Invokable[] args;
|
||||||
|
|
||||||
|
Function(int position, Method method, Invokable... args) {
|
||||||
|
super(position);
|
||||||
|
this.method = method;
|
||||||
|
this.args = args;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final double invoke() throws EvaluationException {
|
||||||
|
try {
|
||||||
|
return (Double) method.invoke(null, (Object[]) 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 Invokable optimize() throws EvaluationException {
|
||||||
|
final Invokable[] optimizedArgs = new Invokable[args.length];
|
||||||
|
boolean optimizable = !method.isAnnotationPresent(Dynamic.class);
|
||||||
|
for (int i = 0; i < args.length; ++i) {
|
||||||
|
final Invokable optimized = optimizedArgs[i] = args[i].optimize();
|
||||||
|
|
||||||
|
if (!(optimized instanceof Constant)) {
|
||||||
|
optimizable = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (optimizable) {
|
||||||
|
return new Constant(getPosition(), invoke());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return new Function(getPosition(), method, optimizedArgs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
129
src/main/java/com/sk89q/worldedit/expression/runtime/Functions.java
Normale Datei
129
src/main/java/com/sk89q/worldedit/expression/runtime/Functions.java
Normale Datei
@ -0,0 +1,129 @@
|
|||||||
|
// $Id$
|
||||||
|
/*
|
||||||
|
* WorldEdit
|
||||||
|
* Copyright (C) 2010, 2011 sk89q <http://www.sk89q.com>
|
||||||
|
*
|
||||||
|
* 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.expression.runtime;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
public final class Functions {
|
||||||
|
public static final Function getFunction(int position, String name, Invokable... args) throws NoSuchMethodException {
|
||||||
|
final Class<?>[] parameterTypes = (Class<?>[]) new Class[args.length];
|
||||||
|
Arrays.fill(parameterTypes, Invokable.class);
|
||||||
|
return new Function(position, Functions.class.getMethod(name, parameterTypes), args);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static final double sin(Invokable x) throws Exception {
|
||||||
|
return Math.sin(x.invoke());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final double cos(Invokable x) throws Exception {
|
||||||
|
return Math.cos(x.invoke());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final double tan(Invokable x) throws Exception {
|
||||||
|
return Math.tan(x.invoke());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static final double asin(Invokable x) throws Exception {
|
||||||
|
return Math.asin(x.invoke());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final double acos(Invokable x) throws Exception {
|
||||||
|
return Math.acos(x.invoke());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final double atan(Invokable x) throws Exception {
|
||||||
|
return Math.atan(x.invoke());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final double atan2(Invokable y, Invokable x) throws Exception {
|
||||||
|
return Math.atan2(y.invoke(), x.invoke());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static final double sinh(Invokable x) throws Exception {
|
||||||
|
return Math.sinh(x.invoke());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final double cosh(Invokable x) throws Exception {
|
||||||
|
return Math.cosh(x.invoke());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final double tanh(Invokable x) throws Exception {
|
||||||
|
return Math.tanh(x.invoke());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static final double sqrt(Invokable x) throws Exception {
|
||||||
|
return Math.sqrt(x.invoke());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final double cbrt(Invokable x) throws Exception {
|
||||||
|
return Math.cbrt(x.invoke());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static final double abs(Invokable x) throws Exception {
|
||||||
|
return Math.abs(x.invoke());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final double min(Invokable a, Invokable b) throws Exception {
|
||||||
|
return Math.min(a.invoke(), b.invoke());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final double max(Invokable a, Invokable b) throws Exception {
|
||||||
|
return Math.max(a.invoke(), b.invoke());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static final double ceil(Invokable x) throws Exception {
|
||||||
|
return Math.ceil(x.invoke());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final double floor(Invokable x) throws Exception {
|
||||||
|
return Math.floor(x.invoke());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final double rint(Invokable x) throws Exception {
|
||||||
|
return Math.rint(x.invoke());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final double round(Invokable x) throws Exception {
|
||||||
|
return Math.round(x.invoke());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static final double exp(Invokable x) throws Exception {
|
||||||
|
return Math.exp(x.invoke());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final double ln(Invokable x) throws Exception {
|
||||||
|
return Math.log(x.invoke());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final double log(Invokable x) throws Exception {
|
||||||
|
return Math.log(x.invoke());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final double log10(Invokable x) throws Exception {
|
||||||
|
return Math.log10(x.invoke());
|
||||||
|
}
|
||||||
|
}
|
45
src/main/java/com/sk89q/worldedit/expression/runtime/Invokable.java
Normale Datei
45
src/main/java/com/sk89q/worldedit/expression/runtime/Invokable.java
Normale Datei
@ -0,0 +1,45 @@
|
|||||||
|
// $Id$
|
||||||
|
/*
|
||||||
|
* WorldEdit
|
||||||
|
* Copyright (C) 2010, 2011 sk89q <http://www.sk89q.com>
|
||||||
|
*
|
||||||
|
* 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.expression.runtime;
|
||||||
|
|
||||||
|
import com.sk89q.worldedit.expression.Identifiable;
|
||||||
|
|
||||||
|
public abstract class Invokable implements Identifiable {
|
||||||
|
private final int position;
|
||||||
|
|
||||||
|
public Invokable(int position) {
|
||||||
|
super();
|
||||||
|
this.position = position;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract double invoke() throws EvaluationException;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public abstract String toString();
|
||||||
|
|
||||||
|
public Invokable optimize() throws EvaluationException {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getPosition() {
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
}
|
136
src/main/java/com/sk89q/worldedit/expression/runtime/Operators.java
Normale Datei
136
src/main/java/com/sk89q/worldedit/expression/runtime/Operators.java
Normale Datei
@ -0,0 +1,136 @@
|
|||||||
|
// $Id$
|
||||||
|
/*
|
||||||
|
* WorldEdit
|
||||||
|
* Copyright (C) 2010, 2011 sk89q <http://www.sk89q.com>
|
||||||
|
*
|
||||||
|
* 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.expression.runtime;
|
||||||
|
|
||||||
|
public final class Operators {
|
||||||
|
public static final Function getOperator(int position, String name, Invokable lhs, Invokable rhs) throws NoSuchMethodException {
|
||||||
|
return new Function(position, Operators.class.getMethod(name, Invokable.class, Invokable.class), lhs, rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Function getOperator(int position, String name, Invokable argument) throws NoSuchMethodException {
|
||||||
|
return new Function(position, Operators.class.getMethod(name, Invokable.class), argument);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static final double add(Invokable lhs, Invokable rhs) throws EvaluationException {
|
||||||
|
return lhs.invoke() + rhs.invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final double sub(Invokable lhs, Invokable rhs) throws EvaluationException {
|
||||||
|
return lhs.invoke() - rhs.invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final double mul(Invokable lhs, Invokable rhs) throws EvaluationException {
|
||||||
|
return lhs.invoke() * rhs.invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final double div(Invokable lhs, Invokable rhs) throws EvaluationException {
|
||||||
|
return lhs.invoke() / rhs.invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final double mod(Invokable lhs, Invokable rhs) throws EvaluationException {
|
||||||
|
return lhs.invoke() % rhs.invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final double pow(Invokable lhs, Invokable rhs) throws EvaluationException {
|
||||||
|
return Math.pow(lhs.invoke(), rhs.invoke());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static final double neg(Invokable x) throws EvaluationException {
|
||||||
|
return -x.invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final double not(Invokable x) throws EvaluationException {
|
||||||
|
return x.invoke() > 0.0 ? 0.0 : 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final double inv(Invokable x) throws EvaluationException {
|
||||||
|
return ~(long) x.invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static final double lth(Invokable lhs, Invokable rhs) throws EvaluationException {
|
||||||
|
return lhs.invoke() < rhs.invoke() ? 1.0 : 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final double gth(Invokable lhs, Invokable rhs) throws EvaluationException {
|
||||||
|
return lhs.invoke() > rhs.invoke() ? 1.0 : 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final double leq(Invokable lhs, Invokable rhs) throws EvaluationException {
|
||||||
|
return lhs.invoke() <= rhs.invoke() ? 1.0 : 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final double geq(Invokable lhs, Invokable rhs) throws EvaluationException {
|
||||||
|
return lhs.invoke() >= rhs.invoke() ? 1.0 : 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static final double equ(Invokable lhs, Invokable rhs) throws EvaluationException {
|
||||||
|
return lhs.invoke() == rhs.invoke() ? 1.0 : 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final double neq(Invokable lhs, Invokable rhs) throws EvaluationException {
|
||||||
|
return lhs.invoke() != rhs.invoke() ? 1.0 : 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final double near(Invokable lhs, Invokable rhs) throws EvaluationException {
|
||||||
|
return almostEqual2sComplement(lhs.invoke(), rhs.invoke(), 450359963L) ? 1.0 : 0.0;
|
||||||
|
//return Math.abs(lhs.invoke() - rhs.invoke()) < 1e-7 ? 1.0 : 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static final double or(Invokable lhs, Invokable rhs) throws EvaluationException {
|
||||||
|
return lhs.invoke() > 0.0 || rhs.invoke() > 0.0 ? 1.0 : 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final double and(Invokable lhs, Invokable rhs) throws EvaluationException {
|
||||||
|
return lhs.invoke() > 0.0 && rhs.invoke() > 0.0 ? 1.0 : 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static final double shl(Invokable lhs, Invokable rhs) throws EvaluationException {
|
||||||
|
return (long) lhs.invoke() << (long) rhs.invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final double shr(Invokable lhs, Invokable rhs) throws EvaluationException {
|
||||||
|
return (long) lhs.invoke() >> (long) rhs.invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
long longDiff = Math.abs(aLong - bLong);
|
||||||
|
return longDiff <= maxUlps;
|
||||||
|
}
|
||||||
|
}
|
44
src/main/java/com/sk89q/worldedit/expression/runtime/Variable.java
Normale Datei
44
src/main/java/com/sk89q/worldedit/expression/runtime/Variable.java
Normale Datei
@ -0,0 +1,44 @@
|
|||||||
|
// $Id$
|
||||||
|
/*
|
||||||
|
* WorldEdit
|
||||||
|
* Copyright (C) 2010, 2011 sk89q <http://www.sk89q.com>
|
||||||
|
*
|
||||||
|
* 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.expression.runtime;
|
||||||
|
|
||||||
|
public final class Variable extends Invokable {
|
||||||
|
public double value;
|
||||||
|
|
||||||
|
public Variable(double value) {
|
||||||
|
super(-1);
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double invoke() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "var";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public char id() {
|
||||||
|
return 'v';
|
||||||
|
}
|
||||||
|
}
|
66
src/test/java/com/sk89q/worldedit/expression/ExpressionTest.java
Normale Datei
66
src/test/java/com/sk89q/worldedit/expression/ExpressionTest.java
Normale Datei
@ -0,0 +1,66 @@
|
|||||||
|
package com.sk89q.worldedit.expression;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
import static java.lang.Math.*;
|
||||||
|
|
||||||
|
import org.junit.*;
|
||||||
|
|
||||||
|
import com.sk89q.worldedit.expression.lexer.LexerException;
|
||||||
|
import com.sk89q.worldedit.expression.parser.ParserException;
|
||||||
|
|
||||||
|
public class ExpressionTest {
|
||||||
|
@Test
|
||||||
|
public void testEvaluate() throws Exception {
|
||||||
|
// check
|
||||||
|
assertEquals(1-2+3, simpleEval("1-2+3"), 0);
|
||||||
|
|
||||||
|
// check unary ops
|
||||||
|
assertEquals(2+ +4, simpleEval("2++4"), 0);
|
||||||
|
assertEquals(2- -4, simpleEval("2--4"), 0);
|
||||||
|
assertEquals(2*-4, simpleEval("2*-4"), 0);
|
||||||
|
|
||||||
|
// check functions
|
||||||
|
assertEquals(sin(5), simpleEval("sin(5)"), 0);
|
||||||
|
assertEquals(atan2(3,4), simpleEval("atan2(3,4)"), 0);
|
||||||
|
|
||||||
|
// check variables
|
||||||
|
assertEquals(8, Expression.compile("foo+bar", "foo", "bar").evaluate(5, 3), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testErrors() throws ExpressionException {
|
||||||
|
// test lexer errors
|
||||||
|
try {
|
||||||
|
Expression.compile("{");
|
||||||
|
fail("Error expected");
|
||||||
|
} catch (LexerException e) {
|
||||||
|
assertEquals("Error position", 0, e.getPosition());
|
||||||
|
}
|
||||||
|
|
||||||
|
// test parser errors
|
||||||
|
try {
|
||||||
|
Expression.compile("x");
|
||||||
|
fail("Error expected");
|
||||||
|
} catch (ParserException e) {
|
||||||
|
assertEquals("Error position", 0, e.getPosition());
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Expression.compile("x()");
|
||||||
|
fail("Error expected");
|
||||||
|
} catch (ParserException e) {
|
||||||
|
assertEquals("Error position", 0, e.getPosition());
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Expression.compile("(");
|
||||||
|
fail("Error expected");
|
||||||
|
} catch (ParserException e) {}
|
||||||
|
try {
|
||||||
|
Expression.compile("x(");
|
||||||
|
fail("Error expected");
|
||||||
|
} catch (ParserException e) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
private double simpleEval(String expression) throws Exception {
|
||||||
|
return Expression.compile(expression).evaluate();
|
||||||
|
}
|
||||||
|
}
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren