Parser improvements

- After a closing brace or a semicolon, a new expression starts. This fixes "{}-1" and ";-1" returning an error.
- Empty statements and empty block statements are now fully supported
- Renamed PrefixOperator to UnaryOperator
- Added postincrement(x++), postdecrement(x--) and factorial(x!) operators
This commit is contained in:
TomyLobo 2011-10-30 04:16:43 +01:00
parent 77d1317964
commit 8e0539adf1
5 changed files with 150 additions and 86 deletions

View File

@ -25,7 +25,6 @@ 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;
@ -33,6 +32,7 @@ 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.RValue;
import com.sk89q.worldedit.expression.runtime.Sequence;
import com.sk89q.worldedit.expression.runtime.Variable;
/**
@ -71,7 +71,7 @@ public class Parser {
}
private RValue parse() throws ParserException {
final RValue ret = parseInternal(true);
final RValue ret = parseMultipleStatements();
if (position < tokens.size()) {
final Token token = peek();
throw new ParserException(token.getPosition(), "Extra token at the end of the input: " + token);
@ -79,7 +79,44 @@ public class Parser {
return ret;
}
private final RValue parseInternal(boolean isStatement) throws ParserException {
private RValue parseMultipleStatements() throws ParserException {
List<RValue> statements = new ArrayList<RValue>();
loop: while (true) {
if (position >= tokens.size()) {
break;
}
switch (peek().id()) {
case ';':
++position;
break;
case '{':
statements.add(parseBlock());
break;
case '}':
break loop;
default:
statements.add(parseExpression());
break;
}
}
switch (statements.size()) {
case 0:
throw new ParserException(-1, "No statement found.");
case 1:
return statements.get(0);
default:
return new Sequence(position, statements.toArray(new RValue[statements.size()]));
}
}
private final RValue parseExpression() throws ParserException {
LinkedList<Identifiable> halfProcessed = new LinkedList<Identifiable>();
// process brackets, numbers, functions, variables and detect prefix operators
@ -121,20 +158,15 @@ public class Parser {
expressionStart = false;
break;
case '{':
halfProcessed.add(parseBlock());
halfProcessed.add(new CharacterToken(-1, ';'));
expressionStart = false;
break;
case ',':
case ')':
case '}':
case ';':
break loop;
case 'o':
if (expressionStart) {
halfProcessed.add(new PrefixOperator((OperatorToken) current));
halfProcessed.add(new UnaryOperator((OperatorToken) current));
} else {
halfProcessed.add(current);
}
@ -150,11 +182,7 @@ public class Parser {
}
}
if (isStatement) {
return ParserProcessors.processStatement(halfProcessed);
} else {
return ParserProcessors.processExpression(halfProcessed);
}
return ParserProcessors.processExpression(halfProcessed);
}
@ -180,7 +208,7 @@ public class Parser {
List<RValue> args = new ArrayList<RValue>();
loop: while (true) {
args.add(parseInternal(false));
args.add(parseExpression());
final Token current = peek();
++position;
@ -209,7 +237,7 @@ public class Parser {
}
++position;
final RValue ret = parseInternal(false);
final RValue ret = parseExpression();
if (peek().id() != ')') {
throw new ParserException(peek().getPosition(), "Unmatched opening bracket");
@ -225,7 +253,11 @@ public class Parser {
}
++position;
final RValue ret = parseInternal(true);
if (peek().id() == '}') {
return new Sequence(peek().getPosition());
}
final RValue ret = parseMultipleStatements();
if (peek().id() != '}') {
throw new ParserException(peek().getPosition(), "Unmatched opening brace");

View File

@ -11,7 +11,6 @@ import com.sk89q.worldedit.expression.lexer.tokens.OperatorToken;
import com.sk89q.worldedit.expression.lexer.tokens.Token;
import com.sk89q.worldedit.expression.runtime.RValue;
import com.sk89q.worldedit.expression.runtime.Operators;
import com.sk89q.worldedit.expression.runtime.Sequence;
/**
* Helper classfor Parser. Contains processors for statements and operators.
@ -30,6 +29,9 @@ public final class ParserProcessors {
unaryOpMap.put("~", "inv");
unaryOpMap.put("++", "inc");
unaryOpMap.put("--", "dec");
unaryOpMap.put("x++", "postinc");
unaryOpMap.put("x--", "postdec");
unaryOpMap.put("x!", "fac");
final Object[][][] binaryOpsLA = {
{
@ -126,41 +128,6 @@ public final class ParserProcessors {
}
}
static RValue processStatement(LinkedList<Identifiable> input) throws ParserException {
LinkedList<Identifiable> lhs = new LinkedList<Identifiable>();
LinkedList<Identifiable> rhs = new LinkedList<Identifiable>();
boolean semicolonFound = false;
for (Identifiable identifiable : input) {
if (semicolonFound) {
rhs.addLast(identifiable);
} else {
if (identifiable.id() == ';') {
semicolonFound = true;
} else {
lhs.addLast(identifiable);
}
}
}
if (rhs.isEmpty()) {
if (lhs.isEmpty()) {
return new Sequence(semicolonFound ? input.get(0).getPosition() : -1);
}
return processExpression(lhs);
} else if (lhs.isEmpty()) {
return processStatement(rhs);
} else {
assert (semicolonFound);
RValue lhsInvokable = processExpression(lhs);
RValue rhsInvokable = processStatement(rhs);
return new Sequence(lhsInvokable.getPosition(), lhsInvokable, rhsInvokable);
}
}
static RValue processExpression(LinkedList<Identifiable> input) throws ParserException {
return processBinaryOpsRA(input, binaryOpMapsRA.length - 1);
}
@ -253,16 +220,41 @@ public final class ParserProcessors {
}
private static RValue processUnaryOps(LinkedList<Identifiable> input) throws ParserException {
if (input.isEmpty()) {
throw new ParserException(-1, "Expression missing.");
// Preprocess postfix operators into prefix operators
final Identifiable center;
LinkedList<UnaryOperator> postfixes = new LinkedList<UnaryOperator>();
do {
if (input.isEmpty()) {
throw new ParserException(-1, "Expression missing.");
}
final Identifiable last = input.removeLast();
if (last instanceof OperatorToken) {
System.out.println("Found postfix: "+last);
postfixes.addFirst(new UnaryOperator(last.getPosition(), "x"+((OperatorToken)last).operator));
}
else if (last instanceof UnaryOperator) {
System.out.println("Found postfix: "+last);
postfixes.addFirst(new UnaryOperator(last.getPosition(), "x"+((UnaryOperator)last).operator));
}
else {
center = last;
break;
}
} while (true);
if (!(center instanceof RValue)) {
throw new ParserException(center.getPosition(), "Expected expression, found "+center);
}
RValue ret = (RValue) input.removeLast();
input.addAll(postfixes);
RValue ret = (RValue) center;
while (!input.isEmpty()) {
final Identifiable last = input.removeLast();
final int lastPosition = last.getPosition();
if (last instanceof PrefixOperator) {
final String operator = ((PrefixOperator) last).operator;
if (last instanceof UnaryOperator) {
final String operator = ((UnaryOperator) last).operator;
if (operator.equals("+")) {
continue;
}

View File

@ -1,27 +0,0 @@
package com.sk89q.worldedit.expression.parser;
import com.sk89q.worldedit.expression.lexer.tokens.OperatorToken;
/**
* The parser uses this pseudo-token to mark operators as prefix operators.
*
* @author TomyLobo
*/
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 + ")";
}
}

View File

@ -0,0 +1,31 @@
package com.sk89q.worldedit.expression.parser;
import com.sk89q.worldedit.expression.lexer.tokens.OperatorToken;
/**
* The parser uses this pseudo-token to mark operators as unary operators.
*
* @author TomyLobo
*/
public class UnaryOperator extends PseudoToken {
final String operator;
public UnaryOperator(OperatorToken operatorToken) {
this(operatorToken.getPosition(), operatorToken.operator);
}
public UnaryOperator(int position, String operator) {
super(position);
this.operator = operator;
}
@Override
public char id() {
return 'p';
}
@Override
public String toString() {
return "UnaryOperator(" + operator + ")";
}
}

View File

@ -161,6 +161,7 @@ public final class Operators {
return lhs.assign(Math.pow(lhs.getValue(), rhs.getValue()));
}
public static final double inc(LValue x) throws EvaluationException {
return x.assign(x.getValue() + 1);
}
@ -169,6 +170,41 @@ public final class Operators {
return x.assign(x.getValue() - 1);
}
public static final double postinc(LValue x) throws EvaluationException {
final double oldValue = x.getValue();
x.assign(oldValue + 1);
return oldValue;
}
public static final double postdec(LValue x) throws EvaluationException {
final double oldValue = x.getValue();
x.assign(oldValue - 1);
return oldValue;
}
private static final double[] factorials = new double[171];
static {
double accum = 1;
factorials[0] = 1;
for (int i = 1; i < factorials.length; ++i) {
factorials[i] = accum *= i;
}
}
public static final double fac(RValue x) throws EvaluationException {
int n = (int) x.getValue();
if (n < 0) {
return 0;
}
if (n >= factorials.length) {
return Double.POSITIVE_INFINITY;
}
return factorials[n];
}
// Usable AlmostEqual function, based on http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm
private static boolean almostEqual2sComplement(double A, double B, long maxUlps) {