geforkt von Mirrors/FastAsyncWorldEdit
The expression parser can now parse more than a simple expression
- Added sequencing (; and {}). - Added =, +=, -=, *=, /=, %=, ^= to the expression parser. (left-associative for now, will change later) - Added pre-increment(++) and pre-decrement(--) operators. - Adjusted/added tests.
Dieser Commit ist enthalten in:
Ursprung
9c070c323f
Commit
ee79abff67
@ -78,4 +78,8 @@ public class Expression {
|
||||
public String toString() {
|
||||
return root.toString();
|
||||
}
|
||||
|
||||
public Invokable getVariable(String name) {
|
||||
return variables.get(name);
|
||||
}
|
||||
}
|
||||
|
@ -20,6 +20,7 @@
|
||||
package com.sk89q.worldedit.expression.lexer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
@ -43,15 +44,28 @@ public class Lexer {
|
||||
}
|
||||
|
||||
private final DecisionTree operatorTree = new DecisionTree(null,
|
||||
'-', new DecisionTree("-"),
|
||||
'+', new DecisionTree("+"),
|
||||
'*', new DecisionTree("*",
|
||||
'*', new DecisionTree("^")
|
||||
'+', new DecisionTree("+",
|
||||
'=', new DecisionTree("+="),
|
||||
'+', new DecisionTree("++")
|
||||
),
|
||||
'/', new DecisionTree("/"),
|
||||
'%', new DecisionTree("%"),
|
||||
'^', new DecisionTree("^"),
|
||||
'=', new DecisionTree(null, // not implemented
|
||||
'-', new DecisionTree("-",
|
||||
'=', new DecisionTree("-="),
|
||||
'-', new DecisionTree("--")
|
||||
),
|
||||
'*', new DecisionTree("*",
|
||||
'=', new DecisionTree("*="),
|
||||
'*', new DecisionTree("**")
|
||||
),
|
||||
'/', new DecisionTree("/",
|
||||
'=', new DecisionTree("/=")
|
||||
),
|
||||
'%', new DecisionTree("%",
|
||||
'=', new DecisionTree("%=")
|
||||
),
|
||||
'^', new DecisionTree("^",
|
||||
'=', new DecisionTree("^=")
|
||||
),
|
||||
'=', new DecisionTree("=",
|
||||
'=', new DecisionTree("==")
|
||||
),
|
||||
'!', new DecisionTree("!",
|
||||
@ -81,8 +95,13 @@ public class Lexer {
|
||||
characterTokens.add(',');
|
||||
characterTokens.add('(');
|
||||
characterTokens.add(')');
|
||||
characterTokens.add('{');
|
||||
characterTokens.add('}');
|
||||
characterTokens.add(';');
|
||||
}
|
||||
|
||||
private static final Set<String> keywords = new HashSet<String>(Arrays.asList("if", "else"));
|
||||
|
||||
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_]*)");
|
||||
|
||||
@ -128,7 +147,12 @@ public class Lexer {
|
||||
if (identifierMatcher.lookingAt()) {
|
||||
String identifierPart = identifierMatcher.group(1);
|
||||
if (!identifierPart.isEmpty()) {
|
||||
tokens.add(new IdentifierToken(position, identifierPart));
|
||||
if (keywords.contains(identifierPart)) {
|
||||
tokens.add(new KeywordToken(position, identifierPart));
|
||||
}
|
||||
else {
|
||||
tokens.add(new IdentifierToken(position, identifierPart));
|
||||
}
|
||||
|
||||
position += identifierPart.length();
|
||||
continue;
|
||||
|
@ -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 KeywordToken extends Token {
|
||||
public final String value;
|
||||
|
||||
public KeywordToken(int position, String value) {
|
||||
super(position);
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public char id() {
|
||||
return 'i';
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "KeywordToken(" + value + ")";
|
||||
}
|
||||
}
|
@ -28,6 +28,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.sk89q.worldedit.expression.Identifiable;
|
||||
import com.sk89q.worldedit.expression.lexer.tokens.CharacterToken;
|
||||
import com.sk89q.worldedit.expression.lexer.tokens.IdentifierToken;
|
||||
import com.sk89q.worldedit.expression.lexer.tokens.NumberToken;
|
||||
import com.sk89q.worldedit.expression.lexer.tokens.OperatorToken;
|
||||
@ -36,6 +37,7 @@ 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;
|
||||
import com.sk89q.worldedit.expression.runtime.Sequence;
|
||||
|
||||
public class Parser {
|
||||
private final class NullToken extends Token {
|
||||
@ -66,7 +68,7 @@ public class Parser {
|
||||
}
|
||||
|
||||
private Invokable parse() throws ParserException {
|
||||
final Invokable ret = parseInternal();
|
||||
final Invokable ret = parseInternal(true);
|
||||
if (position < tokens.size()) {
|
||||
final Token token = peek();
|
||||
throw new ParserException(token.getPosition(), "Extra token at the end of the input: " + token);
|
||||
@ -74,7 +76,7 @@ public class Parser {
|
||||
return ret;
|
||||
}
|
||||
|
||||
private final Invokable parseInternal() throws ParserException {
|
||||
private final Invokable parseInternal(boolean isStatement) throws ParserException {
|
||||
LinkedList<Identifiable> halfProcessed = new LinkedList<Identifiable>();
|
||||
|
||||
// process brackets, numbers, functions, variables and detect prefix operators
|
||||
@ -112,8 +114,15 @@ public class Parser {
|
||||
expressionStart = false;
|
||||
break;
|
||||
|
||||
case '{':
|
||||
halfProcessed.add(parseBlock());
|
||||
halfProcessed.add(new CharacterToken(-1, ';'));
|
||||
expressionStart = false;
|
||||
break;
|
||||
|
||||
case ',':
|
||||
case ')':
|
||||
case '}':
|
||||
break loop;
|
||||
|
||||
case 'o':
|
||||
@ -135,8 +144,57 @@ public class Parser {
|
||||
}
|
||||
}
|
||||
|
||||
// process binary operators
|
||||
return processBinaryOps(halfProcessed, binaryOpMaps.length - 1);
|
||||
if (isStatement) {
|
||||
return processStatement(halfProcessed);
|
||||
}
|
||||
else {
|
||||
// process binary operators
|
||||
return processExpression(halfProcessed);
|
||||
}
|
||||
}
|
||||
|
||||
private Invokable processStatement(LinkedList<Identifiable> input) throws ParserException {
|
||||
LinkedList<Identifiable> lhs = new LinkedList<Identifiable>();
|
||||
LinkedList<Identifiable> rhs = new LinkedList<Identifiable>();
|
||||
boolean semicolonFound = false;
|
||||
|
||||
for (Iterator<Identifiable> it = input.descendingIterator(); it.hasNext();) {
|
||||
Identifiable identifiable = it.next();
|
||||
if (semicolonFound) {
|
||||
lhs.addFirst(identifiable);
|
||||
}
|
||||
else {
|
||||
if (identifiable.id() == ';') {
|
||||
semicolonFound = true;
|
||||
}
|
||||
else {
|
||||
rhs.addFirst(identifiable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (lhs.isEmpty()) {
|
||||
if (rhs.isEmpty()) {
|
||||
return new Sequence(semicolonFound ? input.get(0).getPosition() : -1);
|
||||
}
|
||||
|
||||
return processExpression(rhs);
|
||||
}
|
||||
else if (rhs.isEmpty()) {
|
||||
return processStatement(lhs);
|
||||
}
|
||||
else {
|
||||
assert(semicolonFound);
|
||||
|
||||
Invokable rhsInvokable = processExpression(rhs);
|
||||
Invokable lhsInvokable = processStatement(lhs);
|
||||
|
||||
return new Sequence(position, lhsInvokable, rhsInvokable);
|
||||
}
|
||||
}
|
||||
|
||||
private Invokable processExpression(LinkedList<Identifiable> input) throws ParserException {
|
||||
return processBinaryOps(input, binaryOpMaps.length - 1);
|
||||
}
|
||||
|
||||
private static final Map<String, String>[] binaryOpMaps;
|
||||
@ -146,6 +204,7 @@ public class Parser {
|
||||
final Object[][][] binaryOps = {
|
||||
{
|
||||
{ "^", "pow" },
|
||||
{ "**", "pow" },
|
||||
},
|
||||
{
|
||||
{ "*", "mul" },
|
||||
@ -177,6 +236,15 @@ public class Parser {
|
||||
{
|
||||
{ "||", "or" },
|
||||
},
|
||||
{
|
||||
{ "=", "ass" },
|
||||
{ "+=", "aadd" },
|
||||
{ "-=", "asub" },
|
||||
{ "*=", "amul" },
|
||||
{ "/=", "adiv" },
|
||||
{ "%=", "amod" },
|
||||
{ "^=", "aexp" },
|
||||
},
|
||||
};
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@ -205,6 +273,8 @@ public class Parser {
|
||||
unaryOpMap.put("-", "neg");
|
||||
unaryOpMap.put("!", "not");
|
||||
unaryOpMap.put("~", "inv");
|
||||
unaryOpMap.put("++", "inc");
|
||||
unaryOpMap.put("--", "dec");
|
||||
}
|
||||
|
||||
private Invokable processBinaryOps(LinkedList<Identifiable> input, int level) throws ParserException {
|
||||
@ -315,7 +385,7 @@ public class Parser {
|
||||
List<Invokable> args = new ArrayList<Invokable>();
|
||||
|
||||
loop: while (true) {
|
||||
args.add(parseInternal());
|
||||
args.add(parseInternal(false));
|
||||
|
||||
final Token current = peek();
|
||||
++position;
|
||||
@ -345,7 +415,7 @@ public class Parser {
|
||||
}
|
||||
++position;
|
||||
|
||||
final Invokable ret = parseInternal();
|
||||
final Invokable ret = parseInternal(false);
|
||||
|
||||
if (peek().id() != ')') {
|
||||
throw new ParserException(peek().getPosition(), "Unmatched opening bracket");
|
||||
@ -354,4 +424,20 @@ public class Parser {
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private final Invokable parseBlock() throws ParserException {
|
||||
if (peek().id() != '{') {
|
||||
throw new ParserException(peek().getPosition(), "Unexpected character in parseBlock");
|
||||
}
|
||||
++position;
|
||||
|
||||
final Invokable ret = parseInternal(true);
|
||||
|
||||
if (peek().id() != '}') {
|
||||
throw new ParserException(peek().getPosition(), "Unmatched opening brace");
|
||||
}
|
||||
++position;
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
28
src/main/java/com/sk89q/worldedit/expression/runtime/Assignable.java
Normale Datei
28
src/main/java/com/sk89q/worldedit/expression/runtime/Assignable.java
Normale Datei
@ -0,0 +1,28 @@
|
||||
// $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 abstract class Assignable extends Invokable {
|
||||
public Assignable(int position) {
|
||||
super(position);
|
||||
}
|
||||
|
||||
public abstract double assign(double value) throws EvaluationException;
|
||||
}
|
@ -21,10 +21,22 @@ 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 {
|
||||
if (lhs instanceof Assignable) {
|
||||
try {
|
||||
return new Function(position, Operators.class.getMethod(name, Assignable.class, Invokable.class), lhs, rhs);
|
||||
}
|
||||
catch (NoSuchMethodException e) {}
|
||||
}
|
||||
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 {
|
||||
if (argument instanceof Assignable) {
|
||||
try {
|
||||
return new Function(position, Operators.class.getMethod(name, Assignable.class), argument);
|
||||
}
|
||||
catch (NoSuchMethodException e) {}
|
||||
}
|
||||
return new Function(position, Operators.class.getMethod(name, Invokable.class), argument);
|
||||
}
|
||||
|
||||
@ -116,6 +128,43 @@ public final class Operators {
|
||||
}
|
||||
|
||||
|
||||
public static final double ass(Assignable lhs, Invokable rhs) throws EvaluationException {
|
||||
return lhs.assign(rhs.invoke());
|
||||
}
|
||||
|
||||
public static final double aadd(Assignable lhs, Invokable rhs) throws EvaluationException {
|
||||
return lhs.assign(lhs.invoke() + rhs.invoke());
|
||||
}
|
||||
|
||||
public static final double asub(Assignable lhs, Invokable rhs) throws EvaluationException {
|
||||
return lhs.assign(lhs.invoke() - rhs.invoke());
|
||||
}
|
||||
|
||||
public static final double amul(Assignable lhs, Invokable rhs) throws EvaluationException {
|
||||
return lhs.assign(lhs.invoke() * rhs.invoke());
|
||||
}
|
||||
|
||||
public static final double adiv(Assignable lhs, Invokable rhs) throws EvaluationException {
|
||||
return lhs.assign(lhs.invoke() / rhs.invoke());
|
||||
}
|
||||
|
||||
public static final double amod(Assignable lhs, Invokable rhs) throws EvaluationException {
|
||||
return lhs.assign(lhs.invoke() % rhs.invoke());
|
||||
}
|
||||
|
||||
public static final double aexp(Assignable lhs, Invokable rhs) throws EvaluationException {
|
||||
return lhs.assign(Math.pow(lhs.invoke(), rhs.invoke()));
|
||||
}
|
||||
|
||||
public static final double inc(Assignable x) throws EvaluationException {
|
||||
return x.assign(x.invoke() + 1);
|
||||
}
|
||||
|
||||
public static final double dec(Assignable x) throws EvaluationException {
|
||||
return x.assign(x.invoke() - 1);
|
||||
}
|
||||
|
||||
|
||||
// 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
|
||||
|
82
src/main/java/com/sk89q/worldedit/expression/runtime/Sequence.java
Normale Datei
82
src/main/java/com/sk89q/worldedit/expression/runtime/Sequence.java
Normale Datei
@ -0,0 +1,82 @@
|
||||
// $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.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class Sequence extends Invokable {
|
||||
private final Invokable[] sequence;
|
||||
|
||||
public Sequence(int position, Invokable... sequence) {
|
||||
super(position);
|
||||
|
||||
this.sequence = sequence;
|
||||
}
|
||||
|
||||
@Override
|
||||
public char id() {
|
||||
// TODO Auto-generated method stub
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double invoke() throws EvaluationException {
|
||||
double ret = 0;
|
||||
for (Invokable invokable : sequence) {
|
||||
ret = invokable.invoke();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder("seq(");
|
||||
boolean first = true;
|
||||
for (Invokable invokable : sequence) {
|
||||
if (!first) {
|
||||
sb.append(", ");
|
||||
}
|
||||
sb.append(invokable);
|
||||
first = false;
|
||||
}
|
||||
|
||||
return sb.append(')').toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Invokable optimize() throws EvaluationException {
|
||||
List<Invokable> newSequence = new ArrayList<Invokable>();
|
||||
|
||||
for (Invokable invokable : sequence) {
|
||||
invokable = invokable.optimize();
|
||||
if (invokable instanceof Sequence) {
|
||||
for (Invokable subInvokable : ((Sequence) invokable).sequence) {
|
||||
newSequence.add(subInvokable);
|
||||
}
|
||||
}
|
||||
else {
|
||||
newSequence.add(invokable);
|
||||
}
|
||||
}
|
||||
|
||||
return new Sequence(getPosition(), newSequence.toArray(new Invokable[newSequence.size()]));
|
||||
}
|
||||
}
|
@ -19,7 +19,7 @@
|
||||
|
||||
package com.sk89q.worldedit.expression.runtime;
|
||||
|
||||
public final class Variable extends Invokable {
|
||||
public final class Variable extends Assignable {
|
||||
public double value;
|
||||
|
||||
public Variable(double value) {
|
||||
@ -41,4 +41,9 @@ public final class Variable extends Invokable {
|
||||
public char id() {
|
||||
return 'v';
|
||||
}
|
||||
|
||||
@Override
|
||||
public double assign(double value) {
|
||||
return this.value = value;
|
||||
}
|
||||
}
|
||||
|
@ -15,8 +15,8 @@ public class ExpressionTest {
|
||||
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);
|
||||
assertEquals(2- -4, simpleEval("2- -4"), 0);
|
||||
assertEquals(2*-4, simpleEval("2*-4"), 0);
|
||||
|
||||
// check functions
|
||||
@ -31,7 +31,7 @@ public class ExpressionTest {
|
||||
public void testErrors() throws ExpressionException {
|
||||
// test lexer errors
|
||||
try {
|
||||
Expression.compile("{");
|
||||
Expression.compile("#");
|
||||
fail("Error expected");
|
||||
} catch (LexerException e) {
|
||||
assertEquals("Error position", 0, e.getPosition());
|
||||
@ -60,6 +60,15 @@ public class ExpressionTest {
|
||||
} catch (ParserException e) {}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAssign() throws ExpressionException {
|
||||
Expression foo = Expression.compile("{a=x} b=y; c=z", "x", "y", "z", "a", "b", "c");
|
||||
foo.evaluate(2, 3, 5);
|
||||
assertEquals(2, foo.getVariable("a").invoke(), 0);
|
||||
assertEquals(3, foo.getVariable("b").invoke(), 0);
|
||||
assertEquals(5, foo.getVariable("c").invoke(), 0);
|
||||
}
|
||||
|
||||
private double simpleEval(String expression) throws Exception {
|
||||
return Expression.compile(expression).evaluate();
|
||||
}
|
||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren