diff --git a/src/main/java/com/sk89q/worldedit/expression/Identifiable.java b/src/main/java/com/sk89q/worldedit/expression/Identifiable.java index 1ef0f15b9..f6903309e 100644 --- a/src/main/java/com/sk89q/worldedit/expression/Identifiable.java +++ b/src/main/java/com/sk89q/worldedit/expression/Identifiable.java @@ -51,6 +51,7 @@ public interface Identifiable { * r - Return * b - Break (includes continue) * S - SimpleFor + * C - Switch * */ 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 0b88670ac..a2c70f394 100644 --- a/src/main/java/com/sk89q/worldedit/expression/lexer/Lexer.java +++ b/src/main/java/com/sk89q/worldedit/expression/lexer/Lexer.java @@ -109,7 +109,7 @@ public class Lexer { characterTokens.add(':'); } - private static final Set keywords = new HashSet(Arrays.asList("if", "else", "while", "do", "for", "break", "continue", "return", "switch", "case")); + private static final Set keywords = new HashSet(Arrays.asList("if", "else", "while", "do", "for", "break", "continue", "return", "switch", "case", "default")); 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 5fce3c41c..62c54680b 100644 --- a/src/main/java/com/sk89q/worldedit/expression/parser/Parser.java +++ b/src/main/java/com/sk89q/worldedit/expression/parser/Parser.java @@ -39,6 +39,7 @@ import com.sk89q.worldedit.expression.runtime.RValue; import com.sk89q.worldedit.expression.runtime.Return; import com.sk89q.worldedit.expression.runtime.Sequence; import com.sk89q.worldedit.expression.runtime.SimpleFor; +import com.sk89q.worldedit.expression.runtime.Switch; import com.sk89q.worldedit.expression.runtime.While; /** @@ -134,7 +135,11 @@ public class Parser { break; } - case 'd': { // do + case 'd': { // do/default + if (hasKeyword("default")) { + break loop; + } + ++position; final RValue body = parseStatements(true); @@ -199,7 +204,11 @@ public class Parser { statements.add(new Break(current.getPosition(), false)); break; - case 'c': // continue + case 'c': // continue/case + if (hasKeyword("case")) { + break loop; + } + ++position; statements.add(new Break(current.getPosition(), true)); break; @@ -211,8 +220,55 @@ public class Parser { expectSemicolon = true; break; + case 's': // switch + ++position; + final RValue parameter = parseBracket(); + final List values = new ArrayList(); + final List caseStatements = new ArrayList(); + RValue defaultCase = null; + + consumeCharacter('{'); + while (peek().id() != '}') { + if (position >= tokens.size()) { + throw new ParserException(current.getPosition(), "Expected '}' instead of EOF"); + } + if (defaultCase != null) { + throw new ParserException(current.getPosition(), "Expected '}' instead of " + peek()); + } + + if (hasKeyword("case")) { + ++position; + + final Token valueToken = peek(); + if (!(valueToken instanceof NumberToken)) { + throw new ParserException(current.getPosition(), "Expected number instead of " + peek()); + } + + ++position; + + values.add(((NumberToken) valueToken).value); + + consumeCharacter(':'); + caseStatements.add(parseStatements(false)); + } else if (hasKeyword("default")) { + ++position; + + consumeCharacter(':'); + defaultCase = parseStatements(false); + } else { + throw new ParserException(current.getPosition(), "Expected 'case' or 'default' instead of " + peek()); + } + } + consumeCharacter('}'); + + statements.add(new Switch(current.getPosition(), parameter, values, caseStatements, defaultCase)); + break; + + default: + throw new ParserException(current.getPosition(), "Unexpected keyword '" + keyword + "'"); + } + switch (1) { default: - throw new ParserException(current.getPosition(), "Unimplemented keyword '" + keyword + "'"); } break; diff --git a/src/main/java/com/sk89q/worldedit/expression/runtime/Switch.java b/src/main/java/com/sk89q/worldedit/expression/runtime/Switch.java new file mode 100644 index 000000000..30714b375 --- /dev/null +++ b/src/main/java/com/sk89q/worldedit/expression/runtime/Switch.java @@ -0,0 +1,85 @@ +package com.sk89q.worldedit.expression.runtime; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +public class Switch extends Node implements RValue { + private final RValue parameter; + private final Map valueMap = new HashMap(); + private final RValue[] caseStatements; + private final RValue defaultCase; + + public Switch(int position, RValue parameter, List values, List caseStatements, RValue defaultCase) { + super(position); + this.parameter = parameter; + + assert(values.size() == caseStatements.size()); + + for (int i = 0; i < values.size(); ++i) { + valueMap.put(values.get(i), i); + } + + this.caseStatements = caseStatements.toArray(new RValue[caseStatements.size()]); + this.defaultCase = defaultCase; + } + + @Override + public char id() { + return 'W'; + } + + @Override + public double getValue() throws EvaluationException { + final double parameter = this.parameter.getValue(); + + try { + double ret = 0.0; + + final Integer index = valueMap.get(parameter); + if (index != null) { + for (int i = index; i < caseStatements.length; ++i) { + ret = caseStatements[i].getValue(); + } + } + + return defaultCase == null ? ret : defaultCase.getValue(); + } catch (BreakException e) { + return 0.0; + } + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder(); + + sb.append("switch ("); + sb.append(parameter); + sb.append(") { "); + + for (int i = 0; i < caseStatements.length; ++i) { + RValue caseStatement = caseStatements[i]; + sb.append("case "); + for (Entry entry : valueMap.entrySet()) { + if (entry.getValue() == i) { + sb.append(entry.getKey()); + break; + } + } + sb.append(": "); + sb.append(caseStatement); + sb.append(' '); + } + + if (defaultCase != null) { + sb.append("default: "); + sb.append(defaultCase); + sb.append(' '); + } + + sb.append("}"); + + return sb.toString(); + } +} diff --git a/src/test/java/com/sk89q/worldedit/expression/ExpressionTest.java b/src/test/java/com/sk89q/worldedit/expression/ExpressionTest.java index 22b4d5501..26acd4533 100644 --- a/src/test/java/com/sk89q/worldedit/expression/ExpressionTest.java +++ b/src/test/java/com/sk89q/worldedit/expression/ExpressionTest.java @@ -99,6 +99,17 @@ public class ExpressionTest { assertEquals(12345, simpleEval("y=0; for (i=1,5) { y *= 10; y += i; } y"), 0); } + @Test + public void testSwitch() throws ExpressionException { + assertEquals(523, simpleEval("x=1;y=2;z=3;switch (1) { case 1: x=5; break; case 2: y=6; break; default: z=7 } x*100+y*10+z"), 0); + assertEquals(163, simpleEval("x=1;y=2;z=3;switch (2) { case 1: x=5; break; case 2: y=6; break; default: z=7 } x*100+y*10+z"), 0); + assertEquals(127, simpleEval("x=1;y=2;z=3;switch (3) { case 1: x=5; break; case 2: y=6; break; default: z=7 } x*100+y*10+z"), 0); + + assertEquals(567, simpleEval("x=1;y=2;z=3;switch (1) { case 1: x=5; case 2: y=6; default: z=7 } x*100+y*10+z"), 0); + assertEquals(167, simpleEval("x=1;y=2;z=3;switch (2) { case 1: x=5; case 2: y=6; default: z=7 } x*100+y*10+z"), 0); + assertEquals(127, simpleEval("x=1;y=2;z=3;switch (3) { case 1: x=5; case 2: y=6; default: z=7 } x*100+y*10+z"), 0); + } + private double simpleEval(String expressionString) throws ExpressionException { final Expression expression = compile(expressionString); return expression.evaluate();