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;
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.
* @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()),
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);
}
} 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;
}
/**
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.
*
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());
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;
}
-
}
}
public boolean isUnary() {
- // TODO Auto-generated method stub
- return false;
+ return this.unary;
}
}
* @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;
* @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;
/**
* Returns a flat copy of the instance.
- *
+ *
* @return a copy of th instance
*/
public Token copy() {
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.
*
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.
*
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.
*
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;
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.
*
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.
*
+++ /dev/null
-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);
- }
-}
--- /dev/null
+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);
+ }
+}
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);
+ }
+
}