From 8e0539adf11da256901003c462ff975808d3110b Mon Sep 17 00:00:00 2001 From: TomyLobo Date: Sun, 30 Oct 2011 04:16:43 +0100 Subject: [PATCH] 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 --- .../worldedit/expression/parser/Parser.java | 68 ++++++++++++----- .../expression/parser/ParserProcessors.java | 74 +++++++++---------- .../expression/parser/PrefixOperator.java | 27 ------- .../expression/parser/UnaryOperator.java | 31 ++++++++ .../expression/runtime/Operators.java | 36 +++++++++ 5 files changed, 150 insertions(+), 86 deletions(-) delete mode 100644 src/main/java/com/sk89q/worldedit/expression/parser/PrefixOperator.java create mode 100644 src/main/java/com/sk89q/worldedit/expression/parser/UnaryOperator.java diff --git a/src/main/java/com/sk89q/worldedit/expression/parser/Parser.java b/src/main/java/com/sk89q/worldedit/expression/parser/Parser.java index 65413c3d1..f4d9afcae 100644 --- a/src/main/java/com/sk89q/worldedit/expression/parser/Parser.java +++ b/src/main/java/com/sk89q/worldedit/expression/parser/Parser.java @@ -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 statements = new ArrayList(); + 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 halfProcessed = new LinkedList(); // 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 args = new ArrayList(); 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"); diff --git a/src/main/java/com/sk89q/worldedit/expression/parser/ParserProcessors.java b/src/main/java/com/sk89q/worldedit/expression/parser/ParserProcessors.java index 872ad7bda..4a1c47dac 100644 --- a/src/main/java/com/sk89q/worldedit/expression/parser/ParserProcessors.java +++ b/src/main/java/com/sk89q/worldedit/expression/parser/ParserProcessors.java @@ -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 input) throws ParserException { - LinkedList lhs = new LinkedList(); - LinkedList rhs = new LinkedList(); - 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 input) throws ParserException { return processBinaryOpsRA(input, binaryOpMapsRA.length - 1); } @@ -253,16 +220,41 @@ public final class ParserProcessors { } private static RValue processUnaryOps(LinkedList input) throws ParserException { - if (input.isEmpty()) { - throw new ParserException(-1, "Expression missing."); + // Preprocess postfix operators into prefix operators + final Identifiable center; + LinkedList postfixes = new LinkedList(); + 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; } diff --git a/src/main/java/com/sk89q/worldedit/expression/parser/PrefixOperator.java b/src/main/java/com/sk89q/worldedit/expression/parser/PrefixOperator.java deleted file mode 100644 index 2a67bcde8..000000000 --- a/src/main/java/com/sk89q/worldedit/expression/parser/PrefixOperator.java +++ /dev/null @@ -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 + ")"; - } -} diff --git a/src/main/java/com/sk89q/worldedit/expression/parser/UnaryOperator.java b/src/main/java/com/sk89q/worldedit/expression/parser/UnaryOperator.java new file mode 100644 index 000000000..5f03e83bd --- /dev/null +++ b/src/main/java/com/sk89q/worldedit/expression/parser/UnaryOperator.java @@ -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 + ")"; + } +} diff --git a/src/main/java/com/sk89q/worldedit/expression/runtime/Operators.java b/src/main/java/com/sk89q/worldedit/expression/runtime/Operators.java index 20219872f..0ac2ab0c1 100644 --- a/src/main/java/com/sk89q/worldedit/expression/runtime/Operators.java +++ b/src/main/java/com/sk89q/worldedit/expression/runtime/Operators.java @@ -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) {