Added for loops to the expression parser, java style.

Also:
- Added a test case for for
- Fixed Identifiable.id() for the runtime Nodes and added missing elements to the list in Identifiable.java.
- Factored keyword and character consumption into a common function.
This commit is contained in:
TomyLobo 2011-11-22 16:08:15 +01:00
parent f217be0bdf
commit effbf9f79c
7 changed files with 114 additions and 32 deletions

View File

@ -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
* </pre>
*/
public abstract char id();

View File

@ -107,7 +107,7 @@ public class Lexer {
characterTokens.add(';');
}
private static final Set<String> keywords = new HashSet<String>(Arrays.asList("if", "else", "while", "do"));
private static final Set<String> keywords = new HashSet<String>(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_]*)");

View File

@ -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;
}
}

View File

@ -25,7 +25,7 @@ public class Conditional extends Node {
@Override
public char id() {
return 't';
return 'I';
}
@Override

View File

@ -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
}

View File

@ -41,7 +41,7 @@ public class While extends Node {
@Override
public char id() {
return 't';
return 'w';
}
@Override

View File

@ -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();
}