From: hama Date: Sun, 24 Jul 2016 21:08:24 +0000 (+0200) Subject: Expression works X-Git-Tag: v0.1~7 X-Git-Url: https://gitweb.hamatoma.de/?a=commitdiff_plain;h=150b266da63d42f58f807be9176438da931d3262;p=jpinet Expression works --- diff --git a/src/main/java/de/republib/expr/Expression.java b/src/main/java/de/republib/expr/Expression.java index a995173..395de53 100644 --- a/src/main/java/de/republib/expr/Expression.java +++ b/src/main/java/de/republib/expr/Expression.java @@ -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 variables = new HashMap<>(); protected Stack values = new Stack<>(); - protected Stack operators = new Stack<>(); + protected Stack 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 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; } - } diff --git a/src/main/java/de/republib/expr/OpCode.java b/src/main/java/de/republib/expr/OpCode.java index 90007e1..1985943 100644 --- a/src/main/java/de/republib/expr/OpCode.java +++ b/src/main/java/de/republib/expr/OpCode.java @@ -72,7 +72,6 @@ public enum OpCode { } public boolean isUnary() { - // TODO Auto-generated method stub - return false; + return this.unary; } } diff --git a/src/main/java/de/republib/expr/Scanner.java b/src/main/java/de/republib/expr/Scanner.java index 9f39431..226c1ff 100644 --- a/src/main/java/de/republib/expr/Scanner.java +++ b/src/main/java/de/republib/expr/Scanner.java @@ -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; diff --git a/src/main/java/de/republib/expr/Token.java b/src/main/java/de/republib/expr/Token.java index 1aca0af..2d5cba0 100644 --- a/src/main/java/de/republib/expr/Token.java +++ b/src/main/java/de/republib/expr/Token.java @@ -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 truethe 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 truethe type matches + */ + public boolean isType(TokenType type) { + return this.type == type; + } + /** * Sets the token as identifier. * diff --git a/src/main/java/de/republib/expr/Variant.java b/src/main/java/de/republib/expr/Variant.java index 02102ac..9d9c36c 100644 --- a/src/main/java/de/republib/expr/Variant.java +++ b/src/main/java/de/republib/expr/Variant.java @@ -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 true: 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 true: 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 index 5df0fce..0000000 --- a/src/test/java/de/republib/expr/ExpressionParserTest.java +++ /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 index 0000000..d1f8285 --- /dev/null +++ b/src/test/java/de/republib/expr/ExpressionTest.java @@ -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); + } +} diff --git a/src/test/java/de/republib/expr/VariantTest.java b/src/test/java/de/republib/expr/VariantTest.java index d7880be..8acdb4d 100644 --- a/src/test/java/de/republib/expr/VariantTest.java +++ b/src/test/java/de/republib/expr/VariantTest.java @@ -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); + } + }