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.
Dieser Commit ist enthalten in:
TomyLobo 2011-11-22 16:08:15 +01:00
Ursprung f217be0bdf
Commit effbf9f79c
7 geänderte Dateien mit 114 neuen und 32 gelöschten Zeilen

Datei anzeigen

@ -37,13 +37,17 @@ public interface Identifiable {
* CharacterTokens are returned literally * CharacterTokens are returned literally
* *
* PseudoTokens: * PseudoTokens:
* p - PrefixOperator * p - UnaryOperator
* *
* Invokables: * Nodes:
* c - Constant * c - Constant
* v - Variable * v - Variable
* f - Function * f - Function
* l - LValueFunction * l - LValueFunction
* s - Sequence
* I - Conditional
* w - While
* F - For
* </pre> * </pre>
*/ */
public abstract char id(); public abstract char id();

Datei anzeigen

@ -107,7 +107,7 @@ public class Lexer {
characterTokens.add(';'); 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 numberPattern = Pattern.compile("^([0-9]*(?:\\.[0-9]+)?(?:[eE][+-]?[0-9]+)?)");
private static final Pattern identifierPattern = Pattern.compile("^([A-Za-z][0-9A-Za-z_]*)"); private static final Pattern identifierPattern = Pattern.compile("^([A-Za-z][0-9A-Za-z_]*)");

Datei anzeigen

@ -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.lexer.tokens.Token;
import com.sk89q.worldedit.expression.runtime.Conditional; import com.sk89q.worldedit.expression.runtime.Conditional;
import com.sk89q.worldedit.expression.runtime.Constant; 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.Functions;
import com.sk89q.worldedit.expression.runtime.RValue; import com.sk89q.worldedit.expression.runtime.RValue;
import com.sk89q.worldedit.expression.runtime.Sequence; import com.sk89q.worldedit.expression.runtime.Sequence;
@ -119,8 +120,7 @@ public class Parser {
final RValue truePart = parseStatements(true); final RValue truePart = parseStatements(true);
final RValue falsePart; final RValue falsePart;
final Token next = peek(); if (hasKeyword("else")) {
if ((next instanceof KeywordToken) && ((KeywordToken) next).value.equals("else")) {
++position; ++position;
falsePart = parseStatements(true); falsePart = parseStatements(true);
} else { } else {
@ -144,17 +144,29 @@ public class Parser {
++position; ++position;
final RValue body = parseStatements(true); final RValue body = parseStatements(true);
final Token next = peek(); consumeKeyword("while");
if (!(next instanceof KeywordToken) || !((KeywordToken) next).value.equals("while")) {
throw new ParserException(current.getPosition(), "Expected while");
}
++position;
final RValue condition = parseBracket(); final RValue condition = parseBracket();
statements.add(new While(current.getPosition(), condition, body, true)); statements.add(new While(current.getPosition(), condition, body, true));
break; 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: default:
throw new ParserException(current.getPosition(), "Unimplemented keyword '" + keyword + "'"); throw new ParserException(current.getPosition(), "Unimplemented keyword '" + keyword + "'");
} }
@ -272,10 +284,7 @@ public class Parser {
} }
private Identifiable parseFunctionCall(IdentifierToken identifierToken) throws ParserException { private Identifiable parseFunctionCall(IdentifierToken identifierToken) throws ParserException {
if (peek().id() != '(') { consumeCharacter('(');
throw new ParserException(peek().getPosition(), "Unexpected character in parseFunctionCall");
}
++position;
try { try {
if (peek().id() == ')') { if (peek().id() == ')') {
@ -309,26 +318,17 @@ public class Parser {
} }
private final RValue parseBracket() throws ParserException { private final RValue parseBracket() throws ParserException {
if (peek().id() != '(') { consumeCharacter('(');
throw new ParserException(peek().getPosition(), "Unexpected character in parseBracket");
}
++position;
final RValue ret = parseExpression(); final RValue ret = parseExpression();
if (peek().id() != ')') { consumeCharacter(')');
throw new ParserException(peek().getPosition(), "Unmatched opening bracket");
}
++position;
return ret; return ret;
} }
private final RValue parseBlock() throws ParserException { private final RValue parseBlock() throws ParserException {
if (peek().id() != '{') { consumeCharacter('{');
throw new ParserException(peek().getPosition(), "Unexpected character in parseBlock");
}
++position;
if (peek().id() == '}') { if (peek().id() == '}') {
return new Sequence(peek().getPosition()); return new Sequence(peek().getPosition());
@ -336,11 +336,39 @@ public class Parser {
final RValue ret = parseStatements(false); final RValue ret = parseStatements(false);
if (peek().id() != '}') { consumeCharacter('}');
throw new ParserException(peek().getPosition(), "Unmatched opening brace");
}
++position;
return ret; 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;
}
} }

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

@ -92,6 +92,11 @@ public class ExpressionTest {
assertEquals(5, simpleEval("c=5; a=0; do { ++a; --c; } while (c > 0) a"), 0); 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 { private double simpleEval(String expression) throws ExpressionException {
return Expression.compile(expression).evaluate(); return Expression.compile(expression).evaluate();
} }