From 389a37297d6a75052c8852002ba6288c3b02f26c Mon Sep 17 00:00:00 2001 From: James Ahlborn Date: Sat, 14 Oct 2017 14:38:17 +0000 Subject: [PATCH] handle plus as a unary number modifier; handle precedence of plus/minus in more confusing math expressions git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/branches/exprs@1127 f203690c-595d-4dc9-a70b-905162fa7fd2 --- .../impl/expr/ExpressionTokenizer.java | 7 --- .../jackcess/impl/expr/Expressionator.java | 55 ++++++++++++++++++- .../impl/expr/ExpressionatorTest.java | 17 +++++- 3 files changed, 67 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/ExpressionTokenizer.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/ExpressionTokenizer.java index 0efd21a..0535332 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/ExpressionTokenizer.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/ExpressionTokenizer.java @@ -107,13 +107,6 @@ class ExpressionTokenizer switch(charFlag) { case IS_OP_FLAG: - // special case '-' for negative number - Token numLit = maybeParseNumberLiteral(c, buf); - if(numLit != null) { - tokens.add(numLit); - continue; - } - // all simple operator chars are single character operators tokens.add(new Token(TokenType.OP, String.valueOf(c))); break; diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/Expressionator.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/Expressionator.java index 9e65c5e..b8681c6 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/Expressionator.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/Expressionator.java @@ -111,11 +111,36 @@ public class Expressionator @Override public Value eval(EvalContext ctx, Value param1) { return BuiltinOperators.negate(ctx, param1); } + @Override public UnaryOp getUnaryNumOp() { + return UnaryOp.NEG_NUM; + } + }, + POS("+", false) { + @Override public Value eval(EvalContext ctx, Value param1) { + // basically a no-op + return param1; + } + @Override public UnaryOp getUnaryNumOp() { + return UnaryOp.POS_NUM; + } }, NOT("Not", true) { @Override public Value eval(EvalContext ctx, Value param1) { return BuiltinOperators.not(param1); } + }, + // when a '-' immediately precedes a number, it needs "highest" precedence + NEG_NUM("-", false) { + @Override public Value eval(EvalContext ctx, Value param1) { + return BuiltinOperators.negate(ctx, param1); + } + }, + // when a '+' immediately precedes a number, it needs "highest" precedence + POS_NUM("+", false) { + @Override public Value eval(EvalContext ctx, Value param1) { + // basically a no-op + return param1; + } }; private final String _str; @@ -135,6 +160,10 @@ public class Expressionator return _str; } + public UnaryOp getUnaryNumOp() { + return null; + } + public abstract Value eval(EvalContext ctx, Value param1); } @@ -341,8 +370,9 @@ public class Expressionator private static final Map PRECENDENCE = buildPrecedenceMap( + new OpType[]{UnaryOp.NEG_NUM, UnaryOp.POS_NUM}, new OpType[]{BinaryOp.EXP}, - new OpType[]{UnaryOp.NEG}, + new OpType[]{UnaryOp.NEG, UnaryOp.POS}, new OpType[]{BinaryOp.MULT, BinaryOp.DIV}, new OpType[]{BinaryOp.INT_DIV}, new OpType[]{BinaryOp.MOD}, @@ -714,10 +744,11 @@ public class Expressionator private static void parseOperatorExpression(Token t, TokBuf buf) { - // most ops are two argument except that '-' could be negation + // most ops are two argument except that '-' could be negation, "+" could + // be pos-ation if(buf.hasPendingExpr()) { parseBinaryOpExpression(t, buf); - } else if(isOp(t, "-")) { + } else if(isEitherOp(t, "-", "+")) { parseUnaryOpExpression(t, buf); } else { throw new IllegalArgumentException( @@ -736,6 +767,18 @@ public class Expressionator private static void parseUnaryOpExpression(Token firstTok, TokBuf buf) { UnaryOp op = getOpType(firstTok, UnaryOp.class); + + UnaryOp numOp = op.getUnaryNumOp(); + if(numOp != null) { + // if this operator is immediately preceding a number, it has a higher + // precedence + Token nextTok = buf.peekNext(); + if((nextTok != null) && (nextTok.getType() == TokenType.LITERAL) && + nextTok.getValueType().isNumeric()) { + op = numOp; + } + } + Expr val = parseExpression(buf, true); buf.setPendingExpr(new EUnaryOp(op, val)); @@ -935,6 +978,12 @@ public class Expressionator opStr.equalsIgnoreCase(t.getValueStr())); } + private static boolean isEitherOp(Token t, String opStr1, String opStr2) { + return ((t != null) && (t.getType() == TokenType.OP) && + (opStr1.equalsIgnoreCase(t.getValueStr()) || + opStr2.equalsIgnoreCase(t.getValueStr()))); + } + private static boolean isDelim(Token t, String opStr) { return ((t != null) && (t.getType() == TokenType.DELIM) && opStr.equalsIgnoreCase(t.getValueStr())); diff --git a/src/test/java/com/healthmarketscience/jackcess/impl/expr/ExpressionatorTest.java b/src/test/java/com/healthmarketscience/jackcess/impl/expr/ExpressionatorTest.java index f0e5397..6463106 100644 --- a/src/test/java/com/healthmarketscience/jackcess/impl/expr/ExpressionatorTest.java +++ b/src/test/java/com/healthmarketscience/jackcess/impl/expr/ExpressionatorTest.java @@ -52,7 +52,9 @@ public class ExpressionatorTest extends TestCase validateExpr("13", "{13}"); - validateExpr("-42", "{-42}"); + validateExpr("-42", "{- {42}}"); + + validateExpr("(+37)", "{({+ {37}})}"); doTestSimpleBinOp("EBinaryOp", "+", "-", "*", "/", "\\", "^", "&", "Mod"); doTestSimpleBinOp("ECompOp", "<", "<=", ">", ">=", "=", "<>"); @@ -136,10 +138,18 @@ public class ExpressionatorTest extends TestCase assertEquals(-i, eval("=-(" + i + ")")); } + for(int i = -10; i <= 10; ++i) { + assertEquals(i, eval("=+(" + i + ")")); + } + for(double i : DBLS) { assertEquals(-i, eval("=-(" + i + ")")); } + for(double i : DBLS) { + assertEquals(i, eval("=+(" + i + ")")); + } + for(int i = -10; i <= 10; ++i) { for(int j = -10; j <= 10; ++j) { assertEquals((i + j), eval("=" + i + " + " + j)); @@ -254,7 +264,10 @@ public class ExpressionatorTest extends TestCase } } - + assertEquals(37, eval("=30+7")); + assertEquals(23, eval("=30+-7")); + assertEquals(23, eval("=30-+7")); + assertEquals(23, eval("=30-7")); } public void testTypeCoercion() throws Exception -- 2.39.5