diff --git a/src/main/java/com/sk89q/worldedit/expression/lexer/Lexer.java b/src/main/java/com/sk89q/worldedit/expression/lexer/Lexer.java index c2f9e170c..8d15df541 100644 --- a/src/main/java/com/sk89q/worldedit/expression/lexer/Lexer.java +++ b/src/main/java/com/sk89q/worldedit/expression/lexer/Lexer.java @@ -107,7 +107,7 @@ public class Lexer { characterTokens.add(';'); } - private static final Set keywords = new HashSet(Arrays.asList("if", "else")); + private static final Set keywords = new HashSet(Arrays.asList("if", "else", "while", "do")); 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_]*)"); 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 4680d1776..12375c9e5 100644 --- a/src/main/java/com/sk89q/worldedit/expression/parser/Parser.java +++ b/src/main/java/com/sk89q/worldedit/expression/parser/Parser.java @@ -36,6 +36,7 @@ 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; +import com.sk89q.worldedit.expression.runtime.While; /** * Processes a list of tokens into an executable tree. @@ -112,7 +113,7 @@ public class Parser { case 'k': final String keyword = ((KeywordToken) current).value; switch (keyword.charAt(0)) { - case 'i': // if + case 'i': { // if ++position; final RValue condition = parseBracket(); final RValue truePart = parseStatements(true); @@ -128,6 +129,31 @@ public class Parser { statements.add(new Conditional(current.getPosition(), condition, truePart, falsePart)); break; + } + + case 'w': { // while + ++position; + final RValue condition = parseBracket(); + final RValue body = parseStatements(true); + + statements.add(new While(current.getPosition(), condition, body, false)); + break; + } + + case 'd': { // do + ++position; + final RValue body = parseStatements(true); + + final Token next = peek(); + if (!(next instanceof KeywordToken) || !((KeywordToken) next).value.equals("while")) { + throw new ParserException(current.getPosition(), "Expected while"); + } + ++position; + final RValue condition = parseBracket(); + + statements.add(new While(current.getPosition(), condition, body, true)); + break; + } default: throw new ParserException(current.getPosition(), "Unimplemented keyword '" + keyword + "'"); diff --git a/src/main/java/com/sk89q/worldedit/expression/runtime/Conditional.java b/src/main/java/com/sk89q/worldedit/expression/runtime/Conditional.java index 85a5dc788..937eaad38 100644 --- a/src/main/java/com/sk89q/worldedit/expression/runtime/Conditional.java +++ b/src/main/java/com/sk89q/worldedit/expression/runtime/Conditional.java @@ -30,7 +30,11 @@ public class Conditional extends Node { @Override public String toString() { - return "if ("+condition+") { "+truePart+" } else { "+falsePart+" }"; + if (falsePart == null) { + return "if ("+condition+") { "+truePart+" }"; + } else { + return "if ("+condition+") { "+truePart+" } else { "+falsePart+" }"; + } } //TODO: optimizer diff --git a/src/main/java/com/sk89q/worldedit/expression/runtime/While.java b/src/main/java/com/sk89q/worldedit/expression/runtime/While.java new file mode 100644 index 000000000..809c75eb9 --- /dev/null +++ b/src/main/java/com/sk89q/worldedit/expression/runtime/While.java @@ -0,0 +1,57 @@ +package com.sk89q.worldedit.expression.runtime; + +public class While extends Node { + RValue condition; + RValue body; + boolean footChecked; + + public While(int position, RValue condition, RValue body, boolean footChecked) { + super(position); + + this.condition = condition; + this.body = body; + this.footChecked = footChecked; + } + + @Override + public double getValue() throws EvaluationException { + int iterations = 0; + double ret = 0.0; + + if (footChecked) { + do { + ret = body.getValue(); + ++iterations; + if (iterations > 256) { + throw new EvaluationException(getPosition(), "Loop exceeded 256 iterations."); + } + } while (condition.getValue() > 0.0); + } else { + while (condition.getValue() > 0.0) { + ret = body.getValue(); + ++iterations; + if (iterations > 256) { + throw new EvaluationException(getPosition(), "Loop exceeded 256 iterations."); + } + } + } + + return ret; + } + + @Override + public char id() { + return 't'; + } + + @Override + public String toString() { + if (footChecked) { + return "do { "+body+" } while ("+condition+")"; + } else { + return "while ("+condition+") { "+body+" }"; + } + } + + //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 00323f71f..e9bc5773a 100644 --- a/src/test/java/com/sk89q/worldedit/expression/ExpressionTest.java +++ b/src/test/java/com/sk89q/worldedit/expression/ExpressionTest.java @@ -84,7 +84,12 @@ public class ExpressionTest { 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); - + } + + @Test + public void testWhile() throws ExpressionException { + assertEquals(5, simpleEval("c=5; a=0; while (c > 0) { ++a; --c; } a"), 0); + assertEquals(5, simpleEval("c=5; a=0; do { ++a; --c; } while (c > 0) a"), 0); } private double simpleEval(String expression) throws ExpressionException {