]> gitweb.hamatoma.de Git - jpinet/commitdiff
Expression works
authorhama <hama@siduction.net>
Sun, 24 Jul 2016 21:08:24 +0000 (23:08 +0200)
committerhama <hama@siduction.net>
Sun, 24 Jul 2016 21:08:24 +0000 (23:08 +0200)
src/main/java/de/republib/expr/Expression.java
src/main/java/de/republib/expr/OpCode.java
src/main/java/de/republib/expr/Scanner.java
src/main/java/de/republib/expr/Token.java
src/main/java/de/republib/expr/Variant.java
src/test/java/de/republib/expr/ExpressionParserTest.java [deleted file]
src/test/java/de/republib/expr/ExpressionTest.java [new file with mode: 0644]
src/test/java/de/republib/expr/VariantTest.java

index a995173eb4d88c3b478d210f005626dea30978b7..395de537d287a50c4f9588fee743c660f48ab9b8 100644 (file)
@@ -4,6 +4,8 @@
 package de.republib.expr;
 
 import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
 import java.util.Stack;
 
 import de.republib.util.I18N;
@@ -26,7 +28,7 @@ public class Expression {
        protected Scanner scanner;
        protected HashMap<String, Variable> variables = new HashMap<>();
        protected Stack<Variant> values = new Stack<>();
-       protected Stack<OpCode> operators = new Stack<>();
+       protected Stack<Token> operators = new Stack<>();
 
        /**
         * Constructor.
@@ -50,13 +52,17 @@ public class Expression {
         * @throws VariantException
         */
        public Variant expr() throws ParserException {
-               final Variant rc = term();
-               if (rc == null) {
+               final int entryStackLevel = this.operators.size();
+               Variant value1 = term();
+               if (value1 == null) {
                        throw new ParserException(I18N.tr("unexpected end of string"), this.scanner.getLastToken(), false);
                }
+               this.values.push(value1);
                Token token;
-               final boolean again = true;
-               while (again && !(token = this.scanner.nextNotSpaceToken()).isType(TokenType.END_OF_STRING)) {
+               while (!(token = this.scanner.nextNotSpaceToken()).isType(TokenType.END_OF_STRING)) {
+                       if (token.isOp(OpCode.RIGHT_PARENT)) {
+                               break;
+                       }
                        if (!token.isType(TokenType.OP)) {
                                throw new ParserException(
                                                String.format(I18N.tr("operator expected (like '+', '-'...). Found: %s"), token.getType()),
@@ -66,7 +72,7 @@ public class Expression {
                        Variant value2;
                        final Token tokenOp = token.copy();
                        if (op == OpCode.ASSIGN) {
-                               if (!rc.isType(DataType.VARIABLE)) {
+                               if (!value1.isType(DataType.VARIABLE)) {
                                        throw new ParserException(I18N.tr("Assignment misses a rvalue (e.g. a variable) on the left side"),
                                                        token);
                                }
@@ -74,14 +80,18 @@ public class Expression {
                        } else {
                                value2 = term();
                        }
-                       try {
-                               rc.binaryOperation(op, value2);
-                       } catch (final VariantException e) {
-                               // transform in a message with position info:
-                               throw new ParserException(e.getMessage(), tokenOp);
+                       while (this.operators.size() > entryStackLevel
+                                       && this.operators.lastElement().getOpCode().getPriority() >= op.getPriority()) {
+                               reduceStack();
                        }
+                       this.operators.push(tokenOp);
+                       this.values.push(value2);
                }
-               return rc;
+               while (this.operators.size() > entryStackLevel) {
+                       reduceStack();
+               }
+               value1 = this.values.pop();
+               return value1;
        }
 
        /**
@@ -101,6 +111,23 @@ public class Expression {
                return rc;
        }
 
+       /**
+        * Finishes the last stored binary operation.
+        *
+        * @throws VariantException
+        */
+       private void reduceStack() throws ParserException {
+               final Variant operand2 = this.values.pop();
+               final Variant operand1 = this.values.lastElement();
+               final Token op = this.operators.pop();
+               try {
+                       operand1.binaryOperation(op.getOpCode(), operand2);
+               } catch (final VariantException e) {
+                       // transform in a message with position info:
+                       throw new ParserException(e.getMessage(), op);
+               }
+       }
+
        /**
         * Sets a new text for parsing.
         *
@@ -128,11 +155,14 @@ public class Expression {
        public Variant term() throws ParserException {
                Variant rc = null;
                Token token;
-               boolean isNegative = false;
+               List<Token> unaryList = null;
                do {
                        token = this.scanner.nextNotSpaceToken();
-                       if (token.isOp(OpCode.MINUS)) {
-                               isNegative = !isNegative;
+                       if (token.isType(TokenType.OP) && token.getOpCode().isUnary()) {
+                               if (unaryList == null) {
+                                       unaryList = new LinkedList<>();
+                               }
+                               unaryList.add(token.copy());
                        }
                } while (token.isType(TokenType.OP) && token.getOpCode().isUnary());
 
@@ -165,14 +195,16 @@ public class Expression {
                default:
                        break;
                }
-               if (isNegative) {
-                       if (rc.isType(DataType.LONG)) {
-                               rc.setLongValue(-rc.getLongValue());
-                       } else if (rc.isType(DataType.DOUBLE)) {
-                               rc.setDoubleValue(-rc.getDoubleValue());
+               if (unaryList != null) {
+                       for (final Token token2 : unaryList) {
+                               try {
+                                       rc.unaryOperation(token2.getOpCode());
+                               } catch (final VariantException e) {
+                                       // Append position info:
+                                       throw new ParserException(e.getMessage(), token2);
+                               }
                        }
                }
                return rc;
        }
-
 }
index 90007e16923cce53cf94c902bc67c41ed5d97870..1985943f7555a85ba04b26c8093c919826f7bb1b 100644 (file)
@@ -72,7 +72,6 @@ public enum OpCode {
        }
 
        public boolean isUnary() {
-               // TODO Auto-generated method stub
-               return false;
+               return this.unary;
        }
 }
index 9f39431ea8d2fbeec2c1abd423a31ba7ca5fa0d5..226c1ff35b9b22e20a65d3295727aa50a33f720e 100644 (file)
@@ -170,7 +170,7 @@ public class Scanner {
         * @throws ParserException
         */
        public Token nextToken() throws ParserException {
-               final Token token = this.tokens[this.indexToken++];
+               final Token token = this.lastToken = this.tokens[this.indexToken++];
                final int tokenStart = this.position;
                if (this.indexToken >= this.tokens.length) {
                        this.indexToken = 0;
index 1aca0afa515de544ad18ec5ce01c46b5d8bc7083..2d5cba0eef2f52a501e100ab52062d5df9037391 100644 (file)
@@ -12,7 +12,7 @@ import org.slf4j.LoggerFactory;
  * @author hm
  *
  */
-public class Token {
+public class Token implements Cloneable {
        static Logger logger = LoggerFactory.getLogger(Token.class);
        protected TokenType type = TokenType.UNDEF;
        protected long longValue = 0;
@@ -34,7 +34,7 @@ public class Token {
 
        /**
         * Returns a flat copy of the instance.
-        * 
+        *
         * @return a copy of th instance
         */
        public Token copy() {
@@ -103,17 +103,6 @@ public class Token {
                return this.type;
        }
 
-       /**
-        * Tests whether the token has a given type.
-        *
-        * @param type
-        *            token type to compare
-        * @return <i>true</i>the type matches
-        */
-       public boolean isType(TokenType type) {
-               return this.type == type;
-       }
-
        /**
         * Tests whether the token is a given operator.
         *
@@ -126,6 +115,17 @@ public class Token {
                return rc;
        }
 
+       /**
+        * Tests whether the token has a given type.
+        *
+        * @param type
+        *            token type to compare
+        * @return <i>true</i>the type matches
+        */
+       public boolean isType(TokenType type) {
+               return this.type == type;
+       }
+
        /**
         * Sets the token as identifier.
         *
index 02102acc144268c38cf8dd90a5ec781edd9c8b7f..9d9c36cb7b2b3d0e7a0da2e2b991a5f71ad91d1c 100644 (file)
@@ -161,66 +161,6 @@ public class Variant {
                return this.stringValue;
        }
 
-       /**
-        * @return the boolValue
-        */
-       public Boolean getBoolValue() {
-               return this.boolValue;
-       }
-
-       /**
-        * @return the doubleValue
-        */
-       public double getDoubleValue() {
-               return this.doubleValue;
-       }
-
-       /**
-        * @return the longValue
-        */
-       public long getLongValue() {
-               return this.longValue;
-       }
-
-       /**
-        * @return the objectValue
-        */
-       public Object getObjectValue() {
-               return this.objectValue;
-       }
-
-       /**
-        * @return the string
-        */
-       public String getStringValue() {
-               return this.stringValue;
-       }
-
-       /**
-        * @return the type
-        */
-       public DataType getType() {
-               return this.type;
-       }
-
-       /**
-        * @return the variableValue
-        */
-       public Variable getVariableValue() {
-               return this.variableValue;
-       }
-
-       /**
-        * Tests whether the data type is equal to a given type.
-        *
-        * @param expected
-        *            the type to compare
-        * @return <i>true</i>: the type is the expected
-        */
-       public boolean isType(DataType expected) {
-               return this.type == expected;
-       }
-
        /**
         * Does an operation with another value.
         *
@@ -281,7 +221,7 @@ public class Variant {
                                        throw new VariantException(I18N.tr("exponent < 0: ") + operand.asString());
                                } else if (op2 == 0) {
                                        this.longValue = 1;
-                               } else if (op2 > 100) {
+                               } else if (op2 > maxExponent(this.longValue)) {
                                        throw new VariantException(I18N.tr("exponent too large: ") + operand.asString());
                                } else {
                                        long value = this.longValue;
@@ -319,6 +259,144 @@ public class Variant {
                return this;
        }
 
+       /**
+        * @return the boolValue
+        */
+       public Boolean getBoolValue() {
+               return this.boolValue;
+       }
+
+       /**
+        * @return the doubleValue
+        */
+       public double getDoubleValue() {
+               return this.doubleValue;
+       }
+
+       /**
+        * @return the longValue
+        */
+       public long getLongValue() {
+               return this.longValue;
+       }
+
+       /**
+        * @return the objectValue
+        */
+       public Object getObjectValue() {
+               return this.objectValue;
+       }
+
+       /**
+        * @return the string
+        */
+       public String getStringValue() {
+               return this.stringValue;
+       }
+
+       /**
+        * @return the type
+        */
+       public DataType getType() {
+               return this.type;
+       }
+
+       /**
+        * @return the variableValue
+        */
+       public Variable getVariableValue() {
+               return this.variableValue;
+       }
+
+       /**
+        * Tests whether the data type is equal to a given type.
+        *
+        * @param expected
+        *            the type to compare
+        * @return <i>true</i>: the type is the expected
+        */
+       public boolean isType(DataType expected) {
+               return this.type == expected;
+       }
+
+       /**
+        * Finds the largest exponent for a given base.
+        *
+        * The power operation must result in a value < 2**63
+        *
+        * @param base
+        * @return the largest exponent rc with base**rc < 2**63
+        */
+       private long maxExponent(long base) {
+               long rc;
+               if (base < 0) {
+                       base = -base;
+               }
+               if (base >= Integer.MAX_VALUE) {
+                       rc = 1L;
+               } else {
+                       final int base2 = (int) base;
+                       switch (base2) {
+                       case 0:
+                       case 1:
+                               rc = Long.MAX_VALUE;
+                               break;
+                       case 2:
+                               rc = 63;
+                               break;
+                       case 3:
+                               rc = 39;
+                               break;
+                       case 4:
+                               rc = 31;
+                               break;
+                       case 5:
+                               rc = 27;
+                               break;
+                       case 6:
+                               rc = 24;
+                               break;
+                       case 7:
+                               rc = 22;
+                               break;
+                       case 8:
+                               rc = 21;
+                               break;
+                       case 9:
+                               rc = 19;
+                               break;
+                       case 10:
+                       case 11:
+                               rc = 18;
+                               break;
+                       case 12:
+                       case 13:
+                               rc = 17;
+                               break;
+                       case 14:
+                       case 15:
+                               rc = 16;
+                               break;
+                       case 16:
+                       case 17:
+                       case 18:
+                               rc = 15;
+                               break;
+                       case 19:
+                       case 20:
+                       case 21:
+                       case 22:
+                               rc = 14;
+                               break;
+                       default:
+                               // Math.log(Long.MAX_VALUE) = 43.66...)
+                               rc = (int) Math.floor(43.66827237527655 / Math.log(base2));
+                               break;
+                       }
+               }
+               return rc;
+       }
+
        /**
         * Takes the datat ype from another instance.
         *
@@ -523,6 +601,96 @@ public class Variant {
                this.variableValue = variableValue;
        }
 
+       /**
+        * Does an operation with another value.
+        *
+        * @param op
+        *            the operation to do
+        * @param operand
+        *            the other value
+        * @return the instance (for chaining)
+        * @throws VariantException
+        */
+       Variant unaryOperation(OpCode op) throws VariantException {
+               switch (op) {
+               case MINUS:
+               case PLUS:
+                       switch (this.type) {
+                       case LONG:
+                               if (op == OpCode.MINUS) {
+                                       redefineLong(-this.longValue);
+                               }
+                               break;
+                       case DOUBLE:
+                               if (op == OpCode.MINUS) {
+                                       redefineDouble(-this.doubleValue);
+                               }
+                               break;
+                       case VARIABLE:
+                               // We transform rvalue to "normal" value:
+                               switch (this.variableValue.getValue().getType()) {
+                               case LONG:
+                                       if (op == OpCode.MINUS) {
+                                               redefineLong(-this.variableValue.getValue().longValue);
+                                       } else {
+                                               redefineLong(this.variableValue.getValue().longValue);
+                                       }
+                                       break;
+                               case DOUBLE:
+                                       if (op == OpCode.MINUS) {
+                                               redefineDouble(-this.variableValue.getValue().doubleValue);
+                                       } else {
+                                               redefineDouble(this.variableValue.getValue().doubleValue);
+                                       }
+                                       break;
+                               default:
+                                       wrongDataType(this.variableValue.getValue(), op);
+                               }
+                               break;
+                       default:
+                               wrongDataType(this, op);
+                       }
+                       break;
+               case NOT:
+                       switch (this.type) {
+                       case BOOLEAN:
+                               redefineBool(!this.boolValue);
+                               break;
+                       case VARIABLE:
+                               switch (this.variableValue.getValue().getType()) {
+                               case BOOLEAN:
+                                       redefineBool(!this.variableValue.getValue().getBoolValue());
+                                       break;
+                               default:
+                                       wrongDataType(this.variableValue.getValue(), op);
+                                       break;
+                               }
+                               break;
+                       default:
+                               wrongDataType(this, op);
+                               break;
+                       }
+                       break;
+               default:
+                       throw new VariantException(String.format(I18N.tr("unexpected unary operation %s"), op.name()));
+               }
+               return this;
+       }
+
+       /**
+        * Informs about a wrong combination of data type and unary operator.
+        *
+        * @param operand
+        *            operand to check
+        * @param op
+        *            unary operator to check
+        * @throws VariantException
+        */
+       protected void wrongDataType(Variant operand, OpCode op) throws VariantException {
+               throw new VariantException(String.format(I18N.tr("unexpected unary operation %s (not matching %s)"), op.name(),
+                               operand.getType().name()));
+       }
+
        /**
         * Informs about a wrong combination of data types and/or operator.
         *
diff --git a/src/test/java/de/republib/expr/ExpressionParserTest.java b/src/test/java/de/republib/expr/ExpressionParserTest.java
deleted file mode 100644 (file)
index 5df0fce..0000000
+++ /dev/null
@@ -1,58 +0,0 @@
-package de.republib.expr;
-
-import org.testng.Assert;
-import org.testng.annotations.Test;
-
-public class ExpressionParserTest {
-
-       @Test
-       public void shouldConstruct() throws ParserException {
-               final Expression expr = new Expression("1");
-               final Variant result = expr.expr();
-               Assert.assertEquals(result.getType(), DataType.LONG);
-               Assert.assertEquals(result.getLongValue(), 1L);
-       }
-
-       @Test
-       public void shouldFindVariable() {
-               final Expression expr = new Expression("");
-               final String name = "aLong";
-               final Variable var = expr.findVariable(name);
-               var.assign(new Variant(47L));
-               Assert.assertEquals(expr.findVariable(name).getValue().getType(), DataType.LONG);
-               Assert.assertEquals(expr.findVariable(name).getValue().getLongValue(), 47L);
-       }
-
-       @Test
-       public void shouldWorkExpr() throws ParserException {
-               final Expression expr = new Expression("1 + 2*3");
-               Variant result = expr.expr();
-               Assert.assertEquals(result.getType(), DataType.LONG);
-               Assert.assertEquals(result.getLongValue(), 163L);
-
-               result = expr.reset("2 + 2*3**4 - 15 / 3 % 4").expr();
-               Assert.assertEquals(result.getType(), DataType.LONG);
-               Assert.assertEquals(result.getLongValue(), 163L);
-       }
-
-       @Test
-       public void shouldWorkTerm() throws ParserException {
-               final Expression expr = new Expression("2");
-
-               Variant result = expr.term();
-               Assert.assertEquals(result.getType(), DataType.LONG);
-               Assert.assertEquals(result.getLongValue(), 2L);
-
-               result = expr.reset("-2").term();
-               Assert.assertEquals(result.getType(), DataType.LONG);
-               Assert.assertEquals(result.getLongValue(), -2L);
-
-               result = expr.reset("'x'").term();
-               Assert.assertEquals(result.getType(), DataType.STRING);
-               Assert.assertEquals(result.getStringValue(), "x");
-
-               result = expr.reset("(44)").term();
-               Assert.assertEquals(result.getType(), DataType.LONG);
-               Assert.assertEquals(result.getLongValue(), 44L);
-       }
-}
diff --git a/src/test/java/de/republib/expr/ExpressionTest.java b/src/test/java/de/republib/expr/ExpressionTest.java
new file mode 100644 (file)
index 0000000..d1f8285
--- /dev/null
@@ -0,0 +1,98 @@
+package de.republib.expr;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+public class ExpressionTest {
+
+       @Test
+       public void shouldConstruct() throws ParserException {
+               final Expression expr = new Expression("1");
+               final Variant result = expr.expr();
+               Assert.assertEquals(result.getType(), DataType.LONG);
+               Assert.assertEquals(result.getLongValue(), 1L);
+       }
+
+       @Test
+       public void shouldFindVariable() {
+               final Expression expr = new Expression("");
+               final String name = "aLong";
+               final Variable var = expr.findVariable(name);
+               var.assign(new Variant(47L));
+               Assert.assertEquals(expr.findVariable(name).getValue().getType(), DataType.LONG);
+               Assert.assertEquals(expr.findVariable(name).getValue().getLongValue(), 47L);
+       }
+
+       @Test
+       public void shouldWorkExpr() throws ParserException {
+               final Expression expr = new Expression("1 + 2*3");
+               Variant result = expr.expr();
+               Assert.assertEquals(result.getType(), DataType.LONG);
+               Assert.assertEquals(result.getLongValue(), 7L);
+
+               result = expr.reset("2*(11)").expr();
+               Assert.assertEquals(result.getType(), DataType.LONG);
+               Assert.assertEquals(result.getLongValue(), 22);
+
+               result = expr.reset("2 + 2*3**4 - 15 / 3 % 4").expr();
+               Assert.assertEquals(result.getType(), DataType.LONG);
+               Assert.assertEquals(result.getLongValue(), 163L);
+
+               result = expr.reset("-(-1)**(1+2)").expr();
+               Assert.assertEquals(result.getType(), DataType.LONG);
+               Assert.assertEquals(result.getLongValue(), 1L);
+
+               result = expr.reset("2*(-1)**(1+2)").expr();
+               Assert.assertEquals(result.getType(), DataType.LONG);
+               Assert.assertEquals(result.getLongValue(), -2L);
+
+               result = expr.reset("-2*(3-4)**(1+2*1)").expr();
+               Assert.assertEquals(result.getType(), DataType.LONG);
+               Assert.assertEquals(result.getLongValue(), 2L);
+
+               result = expr.reset("2*(11 + 18 % 5)").expr();
+               Assert.assertEquals(result.getType(), DataType.LONG);
+               Assert.assertEquals(result.getLongValue(), 28L);
+
+               result = expr.reset("(11 + 18 % 5)/7").expr();
+               Assert.assertEquals(result.getType(), DataType.LONG);
+               Assert.assertEquals(result.getLongValue(), 2L);
+
+               result = expr.reset("-2*(11 + 18 % 5)/7").expr();
+               Assert.assertEquals(result.getType(), DataType.LONG);
+               Assert.assertEquals(result.getLongValue(), -4L);
+
+               result = expr.reset("-2*(3-4)**(1+2*1)-2*(11 + 18 % 5)/7").expr();
+               Assert.assertEquals(result.getType(), DataType.LONG);
+               Assert.assertEquals(result.getLongValue(), -2L);
+       }
+
+       @Test
+       public void shouldWorkTerm() throws ParserException {
+               final Expression expr = new Expression("2");
+
+               Variant result = expr.term();
+               Assert.assertEquals(result.getType(), DataType.LONG);
+               Assert.assertEquals(result.getLongValue(), 2L);
+
+               result = expr.reset("-2").term();
+               Assert.assertEquals(result.getType(), DataType.LONG);
+               Assert.assertEquals(result.getLongValue(), -2L);
+
+               result = expr.reset("'x'").term();
+               Assert.assertEquals(result.getType(), DataType.STRING);
+               Assert.assertEquals(result.getStringValue(), "x");
+
+               result = expr.reset("(44)").term();
+               Assert.assertEquals(result.getType(), DataType.LONG);
+               Assert.assertEquals(result.getLongValue(), 44L);
+
+               result = expr.reset("-+-+-+(67)").term();
+               Assert.assertEquals(result.getType(), DataType.LONG);
+               Assert.assertEquals(result.getLongValue(), -67);
+
+               result = expr.reset("++++(10**2 - 97**1)").term();
+               Assert.assertEquals(result.getType(), DataType.LONG);
+               Assert.assertEquals(result.getLongValue(), 3L);
+       }
+}
index d7880be8a014ba294bc0430ec54eff3ce78ca6aa..8acdb4d173e7d49c049d79b90974cb0189b450e3 100644 (file)
@@ -223,4 +223,53 @@ public class VariantTest extends Variant {
                final Variant varString = new Variant("Hi!");
                Assert.assertEquals(varString.asString(), "Hi!");
        }
+
+       @Test
+       public void shouldWorkUnaryOpNot() throws VariantException {
+               final Variant var1 = new Variant(true);
+
+               var1.unaryOperation(OpCode.NOT);
+               Assert.assertFalse(var1.getBoolValue());
+               var1.unaryOperation(OpCode.NOT);
+               Assert.assertTrue(var1.getBoolValue());
+
+               final Variable var2 = new Variable("bValue");
+               var2.assign(new Variant(true));
+               var1.redefineVariable(var2);
+
+               var1.unaryOperation(OpCode.NOT);
+               Assert.assertEquals(var1.getType(), DataType.BOOLEAN);
+               Assert.assertFalse(var1.getBoolValue());
+       }
+
+       @Test
+       public void shouldWorkUnaryOpSigns() throws VariantException {
+               final Variant var1 = new Variant(477L);
+
+               var1.unaryOperation(OpCode.PLUS);
+               Assert.assertEquals(var1.getLongValue(), 477L);
+
+               var1.unaryOperation(OpCode.MINUS);
+               Assert.assertEquals(var1.getLongValue(), -477L);
+
+               var1.unaryOperation(OpCode.MINUS);
+               Assert.assertEquals(var1.getLongValue(), 477L);
+
+               final Variable var2 = new Variable("lValue");
+               var2.assign(new Variant(17L));
+               var1.redefineVariable(var2);
+
+               var1.unaryOperation(OpCode.PLUS);
+               Assert.assertEquals(var1.getType(), DataType.LONG);
+               Assert.assertEquals(var1.getLongValue(), 17L);
+
+               var1.redefineVariable(var2);
+
+               var1.unaryOperation(OpCode.MINUS);
+               Assert.assertEquals(var1.getType(), DataType.LONG);
+               Assert.assertEquals(var1.getLongValue(), -17L);
+               // Variable must be unchanged:
+               Assert.assertEquals(var2.getValue().getLongValue(), 17L);
+       }
+
 }