Using numbers in string context

33 views
Skip to first unread message

David Maryakhin

unread,
Feb 5, 2016, 3:52:51 PM2/5/16
to Jep Java Users
Is it possible to use numbers and strings in the same expression? 

something like this:

jep.addVariable("num", 10);
jep.addVariable("str", "foo");
jep.parse("num/2+str");
Object result = jep.evaluate();

with expected result being a string - "5foo"

Richard Morris

unread,
Feb 5, 2016, 4:56:44 PM2/5/16
to Jep Java Users

Yes it is possible but you need to write your own implementation of the Add function. This is easiest to do by subclassing the Add class.

 static class AddOverload extends Add {
       
private static final long serialVersionUID = 1L;

       
@Override
       
public Object add(Object param1, Object param2)
               
throws EvaluationException {

           
if(param1 instanceof String && param2 instanceof Number ) {
               
String res = ((String) param1) + ((Number) param2).intValue();
               
return res;
           
}
           
if(param1 instanceof Number && param2 instanceof String ) {
               
// uses a method of superclass which ensures the number is
               
// actually represents an integer throwing a parse exception if it is not.
               
int l = super.asStrictInt(0, param1);
               
String res = "" + l + ((String) param2);
               
return res;
           
}                        
           
return super.add(param1, param2);
       
}        
}


Note in the second block I've used a convenience method to ensure the argument represents an integer. You might want your own method for converting numbers.

You would use this function with code something like:

Jep jep = new Jep();
jep
.getOperatorTable().getAdd().setPFMC(new AddOverload());
       
// Testing
Node n1 = jep.parse("\"foo\" + 9");
Object res1 = jep.evaluate(n1);
assertEquals
("foo9",res1);
       
Node n2 = jep.parse("8 + \"foo\"");
Object res2 = jep.evaluate(n2);
assertEquals
("8foo",res2);





On Friday, 5 February 2016 20:52:51 UTC, David Maryakhin  wrote

Larry Villeme

unread,
Mar 15, 2016, 3:57:26 PM3/15/16
to Jep Java Users
Thank you very much for the reply, David and I work together and appreciated the quick turnaround.

The exact usage of executing equations and then concatenating them with a String is slightly different.

A user can enter an equation into a field - free form - and they also have the option of adding a String to mark it up, e.g.
Formula A: 5*2-3 = 7 (Integer)
Formula B: 5*2-3+" Foo" = "7 Foo" (String)

Is there a straightforward way for us to handle this with JEP without having to tokenize equations and process/concat them piecemeal?

Any help would be greatly appreciated.

Larry

Richard Morris

unread,
Mar 15, 2016, 4:15:17 PM3/15/16
to Jep Java Users
Yes this should work fine with a minor modification of the above solution

 class AddOverload extends Add {
       
private static final long serialVersionUID = 1L;

       
@Override
       
public Object add(Object param1, Object param2)
               
throws EvaluationException {


           
if(param1 instanceof Number && param2 instanceof String ) {

               
// uses a method of superclass which ensures the number is
               
// actually represents an integer throwing a parse exception if it is not.
               
int l = super.asStrictInt(0, param1);
               
String res = "" + l + ((String) param2);
               
return res;
           
}            
           
return super.add(param1, param2);
       
}        
 
}


   
@Test
   
public void testAddOverload() throws JepException {

       
Jep jep = new Jep();
       jep
.getOperatorTable().getAdd().setPFMC(new AddOverload());

       
       
Node n1 = jep.parse("\"foo\" + 9");
       
try {
       
Object res1 = jep.evaluate(n1);
       fail
("Exception should be thrown");
       
}
       
catch(JepException e) {

           
       
}
       
           
Node n2 = jep.parse("8 + \"foo\"");
           
Object res2 = jep.evaluate(n2);
           assertEquals
("8foo",res2);



           
Node n3 = jep.parse("5*2-3+ \" Foo\"");
           
Object res3 = jep.evaluate(n3);
           assertEquals
("7 Foo",res3);


           
Node n4 = jep.parse("1+2+3+ \" Foo\"");
           
Object res4  = jep.evaluate(n4);
           assertEquals
("6 Foo",res4);
   
}


The only change I've made is so it works with number + string, but throws an exception with string+number.

This works because the precedence and associativity of the + operator.  Due to the order of operations multiplications are done before additions and additions are done left to right. This means that the expression on the left will always be calculated before the concatenation.

Richard

Larry Villeme

unread,
Mar 16, 2016, 10:11:23 AM3/16/16
to Jep Java Users
Thank you again.

The example will certainly cover numbers/numeric operations + String, e.g. 9+8+"foo" = "17 foo".

Where you outlined a String with +/- operations after it throwing an Exception (by design in the "\"foo\" + 9" test)...is that an explicit constraint using Jep? An example would be probably be easier to pose the question with:
Formula #1: 9+2+" is a larger number than "+9-2
Desired Output: "11 is a larger number than 7" after parse and evaluate
Example Output: JepException due to the the +/- operations being evaluated left to right (properly so)

//Question/Formula in code form
Object result = null;
String expression = "9+2+\" is larger than \"+9-2";

try {
Jep jep = new Jep();
jep.parse(expression);
result = jep.evaluate();
} catch (Exception e) {
System.out.println("JEP Exception: Expression " + expression + " caused " + e.toString());
e.printStackTrace();
}

Richard Morris

unread,
Mar 17, 2016, 4:23:02 AM3/17/16
to Jep Java Users
To handle expression on the right hand side we need to do some rewriting of the node tree. This is probably better done after parsing but before evaluation.

 /**
  * Class to perform the rearrangements
  * (s + a) + b -> s + (a + b)
  * (s + a) - b -> s + (a - b)
  * where s is a string and a and b are any other nodes
  */

 
static class AddReAssociate extends DeepCopyVisitor {

       
private static final long serialVersionUID = 1L;

       
Operator add,sub;

       
public AddReAssociate(Jep j) {
           
super(j);
            add
= this.ot.getAdd();
           
sub = this.ot.getSubtract();
       
}
       
       
@Override
       
public Object visit(ASTOpNode node, Object data) throws JepException {
           
// process all the children of this node
           
Node[] children=visitChildren(node,data);          
//          this.jep.print(node);     // print statements commented out, uncomment to see actions taken
//          System.out.print('\t');

   
           
Operator baseop = node.getOperator();
           
if(baseop == add) {      // test if we have an addition operator
               
Node lhs = children[0];
               
if(lhs instanceof ASTOpNode && lhs.getOperator() == add) { // Have + on lhs node
                   
// check left of lhs
                   
Node lhs_lhs = lhs.jjtGetChild(0);
                   
if(lhs_lhs instanceof ASTConstant && lhs_lhs.getValue() instanceof String) { // is it a string
                       
// now build (a+b)
                       
Node new_rhs = nf.buildOperatorNode(add, lhs.jjtGetChild(1), children[1]);
                       
// build s + (a+b)
                       
Node new_node = nf.buildOperatorNode(add, lhs_lhs, new_rhs);
//                      jep.println(new_node);
                       
return new_node;
                   
}
               
}
           
}
           
if(baseop == sub) {
               
// Have + on lhs node
               
Node lhs = children[0];
               
if(lhs instanceof ASTOpNode && lhs.getOperator() == add) {
                   
// check left of lhs
                   
Node lhs_lhs = lhs.jjtGetChild(0);
                   
if(lhs_lhs instanceof ASTConstant && lhs_lhs.getValue() instanceof String) {
                       
// now build (a+b)
                       
Node new_rhs = nf.buildOperatorNode(sub, lhs.jjtGetChild(1), children[1]);
                       
Node new_node = nf.buildOperatorNode(add, lhs_lhs, new_rhs);
//                        jep.println(new_node);
                       
return new_node;
                   
}
               
}
           
}
//          System.out.println();            
           
// default action if the above not matched. Rebuild the node.
           
return nf.buildOperatorNode(baseop,children);
       
}        
 
}


If we now use the version of Add which can do number + string and string + number

static class AddOverload extends Add {
        private static final long serialVersionUID = 1L;

        @Override
        public Object add(Object param1, Object param2)
                throws EvaluationException {

            if(param1 instanceof Number && param2 instanceof String ) {
                // uses a method of superclass which ensures the number is 
                // actually represents an integer throwing a parse exception if it is not.
                int l = super.asStrictInt(0, param1); 
                String res = "" + l + ((String) param2);
                return res;
            }
            if(param1 instanceof String && param2 instanceof Number ) {
                // uses a method of superclass which ensures the number is 
                // actually represents an integer throwing a parse exception if it is not.
                int r = super.asStrictInt(1, param2); 
                String res = ((String) param1) + r; 
                return res;
            }
            
            
            return super.add(param1, param2);
        }       
}


and we can test it with

  @Test
   public void testAddOverload() throws JepException {
      Jep jep = new Jep();
      jep.getOperatorTable().getAdd().setPFMC(new AddOverload());
      AddReAssociate ara = new AddReAssociate(jep);
      jep.getPrintVisitor().setMode(PrintVisitor.FULL_BRACKET, true);
      
      Node n1 = jep.parse("\"foo\" + 9");
      Object res1 = jep.evaluate(n1);
           assertEquals("foo9",res1);
      
           Node n2 = jep.parse("8 + \"foo\"");
           Object res2 = jep.evaluate(n2);
           assertEquals("8foo",res2);

           Node n3 = jep.parse("5*2-3+ \" Foo\"");
           Object res3 = jep.evaluate(n3);
           assertEquals("7 Foo",res3);

           Node n4 = jep.parse("1+2+3+ \" Foo\"");
           Object res4  = jep.evaluate(n4);
           assertEquals("6 Foo",res4);
           
           Node n5 = jep.parse("\"Foo \"+1+2+3");
           Node n5a = ara.visit(n5);
           Object res5  = jep.evaluate(n5a);
           assertEquals("Foo 6",res5);
           {
           Node n6 = jep.parse("\"Foo \"+1-2+3");
           Node n6a = ara.visit(n6);
           Object res6  = jep.evaluate(n6a);
           assertEquals("Foo 2",res6);
           }
           {
               Node n6 = jep.parse("\"Foo \"+9-2-3");
               Node n6a = ara.visit(n6);
               Object res6  = jep.evaluate(n6a);
               assertEquals("Foo 4",res6);
           }
  }



Note that we call the tree rewrite methods after parsing and before evaluation.

 Node n6 = jep.parse("\"Foo \"+9-2-3");
 Node n6a = ara.visit(n6);
 Object res6  = jep.evaluate(n6a);

Larry Villeme

unread,
Mar 22, 2016, 10:41:12 AM3/22/16
to Jep Java Users
Thank you again...I'm willing to admit I've had some fun working with Jep. We ripped out a velocity-based math engine and replaced it with Jep...much simpler, more robust and we've barely scratched the surface of the API.

Below is what I came up with to solve our specific concerns of mixing Integers, Double and Strings - it's you first example with a helper method/icing. There may be a cleaner way, but it's handling values in a way that met our acceptance criteria.

static class AddOverload extends Add {
private static final long serialVersionUID = 1L;

@Override
public Object add(Object param1, Object param2)
throws EvaluationException {

if(param1 instanceof String && param2 instanceof Number ) {
return ((String) param1) + getProperNumberFormat((Double)param2);


}
if(param1 instanceof Number && param2 instanceof String ) {
// uses a method of superclass which ensures the number is
// actually represents an integer throwing a parse exception if it is not.

return "" + getProperNumberFormat((Double)param1) + ((String) param2);
}
return super.add(param1, param2);
}
}

public static String getProperNumberFormat(Double rawNumber) {
if (rawNumber == Math.ceil(rawNumber)) {
return new Integer(rawNumber.intValue()).toString();
} else {
return rawNumber.toString();
}
}

Reply all
Reply to author
Forward
0 new messages