Mirror von
https://github.com/IntellectualSites/FastAsyncWorldEdit.git
synchronisiert 2024-12-25 02:20:07 +01:00
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