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 java.util.Map;
import com.sk89q.worldedit.expression.Identifiable; 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.IdentifierToken;
import com.sk89q.worldedit.expression.lexer.tokens.NumberToken; import com.sk89q.worldedit.expression.lexer.tokens.NumberToken;
import com.sk89q.worldedit.expression.lexer.tokens.OperatorToken; 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.Constant;
import com.sk89q.worldedit.expression.runtime.Functions; import com.sk89q.worldedit.expression.runtime.Functions;
import com.sk89q.worldedit.expression.runtime.RValue; import com.sk89q.worldedit.expression.runtime.RValue;
import com.sk89q.worldedit.expression.runtime.Sequence;
import com.sk89q.worldedit.expression.runtime.Variable; import com.sk89q.worldedit.expression.runtime.Variable;
/** /**
@ -71,7 +71,7 @@ public class Parser {
} }
private RValue parse() throws ParserException { private RValue parse() throws ParserException {
final RValue ret = parseInternal(true); final RValue ret = parseMultipleStatements();
if (position < tokens.size()) { if (position < tokens.size()) {
final Token token = peek(); final Token token = peek();
throw new ParserException(token.getPosition(), "Extra token at the end of the input: " + token); throw new ParserException(token.getPosition(), "Extra token at the end of the input: " + token);
@ -79,7 +79,44 @@ public class Parser {
return ret; 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>(); LinkedList<Identifiable> halfProcessed = new LinkedList<Identifiable>();
// process brackets, numbers, functions, variables and detect prefix operators // process brackets, numbers, functions, variables and detect prefix operators
@ -121,20 +158,15 @@ public class Parser {
expressionStart = false; expressionStart = false;
break; break;
case '{':
halfProcessed.add(parseBlock());
halfProcessed.add(new CharacterToken(-1, ';'));
expressionStart = false;
break;
case ',': case ',':
case ')': case ')':
case '}': case '}':
case ';':
break loop; break loop;
case 'o': case 'o':
if (expressionStart) { if (expressionStart) {
halfProcessed.add(new PrefixOperator((OperatorToken) current)); halfProcessed.add(new UnaryOperator((OperatorToken) current));
} else { } else {
halfProcessed.add(current); halfProcessed.add(current);
} }
@ -150,12 +182,8 @@ public class Parser {
} }
} }
if (isStatement) {
return ParserProcessors.processStatement(halfProcessed);
} else {
return ParserProcessors.processExpression(halfProcessed); return ParserProcessors.processExpression(halfProcessed);
} }
}
private Token peek() { private Token peek() {
@ -180,7 +208,7 @@ public class Parser {
List<RValue> args = new ArrayList<RValue>(); List<RValue> args = new ArrayList<RValue>();
loop: while (true) { loop: while (true) {
args.add(parseInternal(false)); args.add(parseExpression());
final Token current = peek(); final Token current = peek();
++position; ++position;
@ -209,7 +237,7 @@ public class Parser {
} }
++position; ++position;
final RValue ret = parseInternal(false); final RValue ret = parseExpression();
if (peek().id() != ')') { if (peek().id() != ')') {
throw new ParserException(peek().getPosition(), "Unmatched opening bracket"); throw new ParserException(peek().getPosition(), "Unmatched opening bracket");
@ -225,7 +253,11 @@ public class Parser {
} }
++position; ++position;
final RValue ret = parseInternal(true); if (peek().id() == '}') {
return new Sequence(peek().getPosition());
}
final RValue ret = parseMultipleStatements();
if (peek().id() != '}') { if (peek().id() != '}') {
throw new ParserException(peek().getPosition(), "Unmatched opening brace"); 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.lexer.tokens.Token;
import com.sk89q.worldedit.expression.runtime.RValue; import com.sk89q.worldedit.expression.runtime.RValue;
import com.sk89q.worldedit.expression.runtime.Operators; import com.sk89q.worldedit.expression.runtime.Operators;
import com.sk89q.worldedit.expression.runtime.Sequence;
/** /**
* Helper classfor Parser. Contains processors for statements and operators. * Helper classfor Parser. Contains processors for statements and operators.
@ -30,6 +29,9 @@ public final class ParserProcessors {
unaryOpMap.put("~", "inv"); unaryOpMap.put("~", "inv");
unaryOpMap.put("++", "inc"); unaryOpMap.put("++", "inc");
unaryOpMap.put("--", "dec"); unaryOpMap.put("--", "dec");
unaryOpMap.put("x++", "postinc");
unaryOpMap.put("x--", "postdec");
unaryOpMap.put("x!", "fac");
final Object[][][] binaryOpsLA = { 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 { static RValue processExpression(LinkedList<Identifiable> input) throws ParserException {
return processBinaryOpsRA(input, binaryOpMapsRA.length - 1); return processBinaryOpsRA(input, binaryOpMapsRA.length - 1);
} }
@ -253,16 +220,41 @@ public final class ParserProcessors {
} }
private static RValue processUnaryOps(LinkedList<Identifiable> input) throws ParserException { private static RValue processUnaryOps(LinkedList<Identifiable> input) throws ParserException {
// Preprocess postfix operators into prefix operators
final Identifiable center;
LinkedList<UnaryOperator> postfixes = new LinkedList<UnaryOperator>();
do {
if (input.isEmpty()) { if (input.isEmpty()) {
throw new ParserException(-1, "Expression missing."); throw new ParserException(-1, "Expression missing.");
} }
RValue ret = (RValue) input.removeLast(); 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);
}
input.addAll(postfixes);
RValue ret = (RValue) center;
while (!input.isEmpty()) { while (!input.isEmpty()) {
final Identifiable last = input.removeLast(); final Identifiable last = input.removeLast();
final int lastPosition = last.getPosition(); final int lastPosition = last.getPosition();
if (last instanceof PrefixOperator) { if (last instanceof UnaryOperator) {
final String operator = ((PrefixOperator) last).operator; final String operator = ((UnaryOperator) last).operator;
if (operator.equals("+")) { if (operator.equals("+")) {
continue; 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())); return lhs.assign(Math.pow(lhs.getValue(), rhs.getValue()));
} }
public static final double inc(LValue x) throws EvaluationException { public static final double inc(LValue x) throws EvaluationException {
return x.assign(x.getValue() + 1); return x.assign(x.getValue() + 1);
} }
@ -169,6 +170,41 @@ public final class Operators {
return x.assign(x.getValue() - 1); 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 // Usable AlmostEqual function, based on http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm
private static boolean almostEqual2sComplement(double A, double B, long maxUlps) { private static boolean almostEqual2sComplement(double A, double B, long maxUlps) {