diff --git a/src/main/java/com/sk89q/worldedit/expression/Identifiable.java b/src/main/java/com/sk89q/worldedit/expression/Identifiable.java index 7a11c6ffd..ba6c402be 100644 --- a/src/main/java/com/sk89q/worldedit/expression/Identifiable.java +++ b/src/main/java/com/sk89q/worldedit/expression/Identifiable.java @@ -37,13 +37,17 @@ public interface Identifiable { * CharacterTokens are returned literally * * PseudoTokens: - * p - PrefixOperator + * p - UnaryOperator * - * Invokables: + * Nodes: * c - Constant * v - Variable * f - Function * l - LValueFunction + * s - Sequence + * I - Conditional + * w - While + * F - For * */ public abstract char id(); 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 8d15df541..7d2be6d7a 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", "while", "do")); + private static final Set keywords = new HashSet(Arrays.asList("if", "else", "while", "do", "for")); 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 12375c9e5..51c0d9c90 100644 --- a/src/main/java/com/sk89q/worldedit/expression/parser/Parser.java +++ b/src/main/java/com/sk89q/worldedit/expression/parser/Parser.java @@ -32,6 +32,7 @@ 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.For; import com.sk89q.worldedit.expression.runtime.Functions; import com.sk89q.worldedit.expression.runtime.RValue; import com.sk89q.worldedit.expression.runtime.Sequence; @@ -119,8 +120,7 @@ public class Parser { final RValue truePart = parseStatements(true); final RValue falsePart; - final Token next = peek(); - if ((next instanceof KeywordToken) && ((KeywordToken) next).value.equals("else")) { + if (hasKeyword("else")) { ++position; falsePart = parseStatements(true); } else { @@ -144,17 +144,29 @@ public class Parser { ++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; + consumeKeyword("while"); + final RValue condition = parseBracket(); statements.add(new While(current.getPosition(), condition, body, true)); break; } + case 'f': { // for + ++position; + consumeCharacter('('); + final RValue init = parseExpression(); + consumeCharacter(';'); + final RValue condition = parseExpression(); + consumeCharacter(';'); + final RValue increment = parseExpression(); + consumeCharacter(')'); + final RValue body = parseStatements(true); + + statements.add(new For(current.getPosition(), init, condition, increment, body)); + break; + } + default: throw new ParserException(current.getPosition(), "Unimplemented keyword '" + keyword + "'"); } @@ -272,10 +284,7 @@ public class Parser { } private Identifiable parseFunctionCall(IdentifierToken identifierToken) throws ParserException { - if (peek().id() != '(') { - throw new ParserException(peek().getPosition(), "Unexpected character in parseFunctionCall"); - } - ++position; + consumeCharacter('('); try { if (peek().id() == ')') { @@ -309,26 +318,17 @@ public class Parser { } private final RValue parseBracket() throws ParserException { - if (peek().id() != '(') { - throw new ParserException(peek().getPosition(), "Unexpected character in parseBracket"); - } - ++position; + consumeCharacter('('); final RValue ret = parseExpression(); - if (peek().id() != ')') { - throw new ParserException(peek().getPosition(), "Unmatched opening bracket"); - } - ++position; + consumeCharacter(')'); return ret; } private final RValue parseBlock() throws ParserException { - if (peek().id() != '{') { - throw new ParserException(peek().getPosition(), "Unexpected character in parseBlock"); - } - ++position; + consumeCharacter('{'); if (peek().id() == '}') { return new Sequence(peek().getPosition()); @@ -336,11 +336,39 @@ public class Parser { final RValue ret = parseStatements(false); - if (peek().id() != '}') { - throw new ParserException(peek().getPosition(), "Unmatched opening brace"); - } - ++position; + consumeCharacter('}'); return ret; } + + private boolean hasKeyword(String keyword) { + final Token next = peek(); + if (!(next instanceof KeywordToken)) { + return false; + } + return ((KeywordToken) next).value.equals(keyword); + } + + private void assertCharacter(char character) throws ParserException { + final Token next = peek(); + if (next.id() != character) { + throw new ParserException(next.getPosition(), "Expected '" + character + "'"); + } + } + + private void assertKeyword(String keyword) throws ParserException { + if (!hasKeyword(keyword)) { + throw new ParserException(peek().getPosition(), "Expected '" + keyword + "'"); + } + } + + private void consumeCharacter(char character) throws ParserException { + assertCharacter(character); + ++position; + } + + private void consumeKeyword(String keyword) throws ParserException { + assertKeyword(keyword); + ++position; + } } 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 937eaad38..be6b263c7 100644 --- a/src/main/java/com/sk89q/worldedit/expression/runtime/Conditional.java +++ b/src/main/java/com/sk89q/worldedit/expression/runtime/Conditional.java @@ -25,7 +25,7 @@ public class Conditional extends Node { @Override public char id() { - return 't'; + return 'I'; } @Override diff --git a/src/main/java/com/sk89q/worldedit/expression/runtime/For.java b/src/main/java/com/sk89q/worldedit/expression/runtime/For.java new file mode 100644 index 000000000..6178d52ab --- /dev/null +++ b/src/main/java/com/sk89q/worldedit/expression/runtime/For.java @@ -0,0 +1,45 @@ +package com.sk89q.worldedit.expression.runtime; + +public class For extends Node { + RValue init; + RValue condition; + RValue increment; + RValue body; + + public For(int position, RValue init, RValue condition, RValue increment, RValue body) { + super(position); + + this.init = init; + this.condition = condition; + this.increment = increment; + this.body = body; + } + + @Override + public double getValue() throws EvaluationException { + int iterations = 0; + double ret = 0.0; + + for (init.getValue(); condition.getValue() > 0; increment.getValue()) { + ret = body.getValue(); + ++iterations; + if (iterations > 256) { + throw new EvaluationException(getPosition(), "Loop exceeded 256 iterations."); + } + } + + return ret; + } + + @Override + public char id() { + return 'F'; + } + + @Override + public String toString() { + return "for ("+init+"; "+condition+"; "+increment+") { "+body+" }"; + } + + //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 index 809c75eb9..42df4a219 100644 --- a/src/main/java/com/sk89q/worldedit/expression/runtime/While.java +++ b/src/main/java/com/sk89q/worldedit/expression/runtime/While.java @@ -41,7 +41,7 @@ public class While extends Node { @Override public char id() { - return 't'; + return 'w'; } @Override diff --git a/src/test/java/com/sk89q/worldedit/expression/ExpressionTest.java b/src/test/java/com/sk89q/worldedit/expression/ExpressionTest.java index e9bc5773a..0dac1210a 100644 --- a/src/test/java/com/sk89q/worldedit/expression/ExpressionTest.java +++ b/src/test/java/com/sk89q/worldedit/expression/ExpressionTest.java @@ -92,6 +92,11 @@ public class ExpressionTest { assertEquals(5, simpleEval("c=5; a=0; do { ++a; --c; } while (c > 0) a"), 0); } + @Test + public void testFor() throws ExpressionException { + assertEquals(5, simpleEval("a=0; for (i=0; i<5; ++i) { ++a; } a"), 0); + } + private double simpleEval(String expression) throws ExpressionException { return Expression.compile(expression).evaluate(); }