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 6618b2ed0..4680d1776 100644 --- a/src/main/java/com/sk89q/worldedit/expression/parser/Parser.java +++ b/src/main/java/com/sk89q/worldedit/expression/parser/Parser.java @@ -26,9 +26,11 @@ 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.KeywordToken; 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.Conditional; import com.sk89q.worldedit.expression.runtime.Constant; import com.sk89q.worldedit.expression.runtime.Functions; import com.sk89q.worldedit.expression.runtime.RValue; @@ -71,7 +73,7 @@ public class Parser { } private RValue parse() throws ParserException { - final RValue ret = parseMultipleStatements(); + final RValue ret = parseStatements(false); if (position < tokens.size()) { final Token token = peek(); throw new ParserException(token.getPosition(), "Extra token at the end of the input: " + token); @@ -79,28 +81,76 @@ public class Parser { return ret; } - private RValue parseMultipleStatements() throws ParserException { + private RValue parseStatements(boolean singleStatement) throws ParserException { List statements = new ArrayList(); loop: while (true) { if (position >= tokens.size()) { break; } - switch (peek().id()) { + final Token current = peek(); + switch (current.id()) { case ';': ++position; + + if (singleStatement) { + break loop; + } break; case '{': statements.add(parseBlock()); + + if (singleStatement) { + break loop; + } break; case '}': break loop; + case 'k': + final String keyword = ((KeywordToken) current).value; + switch (keyword.charAt(0)) { + case 'i': // if + ++position; + final RValue condition = parseBracket(); + final RValue truePart = parseStatements(true); + final RValue falsePart; + + final Token next = peek(); + if ((next instanceof KeywordToken) && ((KeywordToken) next).value.equals("else")) { + ++position; + falsePart = parseStatements(true); + } else { + falsePart = null; + } + + statements.add(new Conditional(current.getPosition(), condition, truePart, falsePart)); + break; + + default: + throw new ParserException(current.getPosition(), "Unimplemented keyword '" + keyword + "'"); + } + + if (singleStatement) { + break loop; + } + break; + default: statements.add(parseExpression()); - break; + + if (peek().id() == ';') { + ++position; + if (singleStatement) { + break loop; + } + break; + } + else { + break loop; + } } } @@ -258,7 +308,7 @@ public class Parser { return new Sequence(peek().getPosition()); } - final RValue ret = parseMultipleStatements(); + final RValue ret = parseStatements(false); if (peek().id() != '}') { throw new ParserException(peek().getPosition(), "Unmatched opening brace"); diff --git a/src/main/java/com/sk89q/worldedit/expression/runtime/Conditional.java b/src/main/java/com/sk89q/worldedit/expression/runtime/Conditional.java new file mode 100644 index 000000000..85a5dc788 --- /dev/null +++ b/src/main/java/com/sk89q/worldedit/expression/runtime/Conditional.java @@ -0,0 +1,37 @@ +package com.sk89q.worldedit.expression.runtime; + +public class Conditional extends Node { + RValue condition; + RValue truePart; + RValue falsePart; + + public Conditional(int position, RValue condition, RValue truePart, RValue falsePart) { + super(position); + + this.condition = condition; + this.truePart = truePart; + this.falsePart = falsePart; + } + + @Override + public double getValue() throws EvaluationException { + if (condition.getValue() > 0.0) { + return truePart.getValue(); + } + else { + return falsePart == null ? 0 : falsePart.getValue(); + } + } + + @Override + public char id() { + return 't'; + } + + @Override + public String toString() { + return "if ("+condition+") { "+truePart+" } else { "+falsePart+" }"; + } + + //TODO: optimizer +} diff --git a/src/test/java/com/sk89q/worldedit/expression/ExpressionTest.java b/src/test/java/com/sk89q/worldedit/expression/ExpressionTest.java index c8e1507ae..00323f71f 100644 --- a/src/test/java/com/sk89q/worldedit/expression/ExpressionTest.java +++ b/src/test/java/com/sk89q/worldedit/expression/ExpressionTest.java @@ -10,7 +10,7 @@ import com.sk89q.worldedit.expression.parser.ParserException; public class ExpressionTest { @Test - public void testEvaluate() throws Exception { + public void testEvaluate() throws ExpressionException { // check assertEquals(1-2+3, simpleEval("1-2+3"), 0); @@ -69,7 +69,25 @@ public class ExpressionTest { assertEquals(5, foo.getVariable("c").getValue(), 0); } - private double simpleEval(String expression) throws Exception { + @Test + public void testIf() throws ExpressionException { + assertEquals(40, simpleEval("if (1) x=4; else y=5; x*10+y;"), 0); + assertEquals(5, simpleEval("if (0) x=4; else y=5; x*10+y;"), 0); + + // test 'dangling else' + final Expression expression1 = Expression.compile("if (1) if (0) x=4; else y=5;", "x", "y"); + expression1.evaluate(1, 2); + assertEquals(1, expression1.getVariable("x").getValue(), 0); + assertEquals(5, expression1.getVariable("y").getValue(), 0); + + // test if the if construct is correctly recognized as a statement + final Expression expression2 = Expression.compile("if (0) if (1) x=5; y=4;", "x", "y"); + expression2.evaluate(1, 2); + assertEquals(4, expression2.getVariable("y").getValue(), 0); + + } + + private double simpleEval(String expression) throws ExpressionException { return Expression.compile(expression).evaluate(); } }