Salesforce Stack Exchange is a question and answer site for Salesforce administrators, implementation experts, developers and anybody in-between. Join them; it only takes a minute:

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

Does anybody know if there is a way to evaluate a string containing boolean-logic at runtime? My logic, can only contain three operators and parenthesis (AND, OR, NOT). I can even make the string look like this: true && false && (true || !true)

I don't have the bandwidth to build a full-on parser... Any ideas?

PS: I saw some answers that pointed to the use of an API but I don't have access to use the Toolkit API in the environment this will get deployed.

share|improve this question
    
Almost, those are all the operands that are allowed in this case.. plus !. – Sebastian Kessel Mar 7 at 22:49
    
Yup, those are the ones. – Sebastian Kessel Mar 7 at 22:51
    
@SebastianKessel What's the use case? Visualforce, Trigger, or Async code? – sfdcfox Mar 7 at 23:05
    
Visualforce, right now. Eventually Apex inside of a batch. – Sebastian Kessel Mar 7 at 23:06
1  
Some old code on this that perhaps could be simplified for this requirement Evaluate expressions/conditions in apex code ? (which are stored in string/text). – Keith C Mar 7 at 23:12
up vote 7 down vote accepted

I got a fairly simple one working. It might not perform as well as a more sophisticated option, but it is easy to understand.

Logic

public class BooleanExpression
{
    static Boolean orJoin(String x, String y) { return evaluate(x) || evaluate(y); }
    static Boolean andJoin(String x, String y) { return evaluate(x) && evaluate(y); }
    static Boolean isSimpleExpression(String x) { return x == 'true' || x == 'false'; }
    static String simplify(String x)
    {
        x = x.trim();
        while (x.contains('('))
        {
            String sub = x.substringAfterLast('(').substringBefore(')');
            x = x.replace('(' + sub + ')', String.valueOf(evaluate(sub)));
        }
        return x;
    }
    static Boolean evaluate(String x)
    {
        x = simplify(x);
        if (!isSimpleExpression(x))
        {
            if (x.contains('&&')) return andJoin(x.split('&&', 2));
            if (x.contains('||')) return orJoin(x.split('||', 2));
            if (x.startsWith('!')) return !evaluate(x.substring(1));
        }
        return Boolean.valueOf(x);
    }
}

Tests

I know you can just use assert(evaluate(expression)), but that feels kind of dirty here.

@IsTest
class BooleanExpressionTests
{
    static testMethod void testExpressions()
    {
        system.assertEquals(true, evaluate('true'));
        system.assertEquals(false, evaluate('!true'));
        system.assertEquals(true, evaluate('!false'));
        system.assertEquals(false, evaluate('false'));
        system.assertEquals(false, evaluate('true && !true'));
        system.assertEquals(true, evaluate('false || !false'));
        system.assertEquals(true,
            evaluate('false || (false || (!true || (false || true)))'));
    }
}
share|improve this answer
    
This is perfect, it won't do 100% but it gets me far enough down the rabbit hole to complete it myself. Thanks! – Sebastian Kessel Mar 7 at 23:26
    
For the record, I could recursively call your function with everything between ( and ). I think that should work. – Sebastian Kessel Mar 7 at 23:27
    
Recursion deepness is my biggest worry, but in my use-cases it won't be a problem. I'll need to think on this a bit, but I think I can make it work. – Sebastian Kessel Mar 7 at 23:33
    
Wow. I JUST finished testing my solution, almost identical to yours. Here it is... check out the simplify method. gist.github.com/sebastiankessel/838773f98b33d9d24eeb – Sebastian Kessel Mar 8 at 0:03
1  
@SebastianKessel I thought that approach was at risk to replacing other matching substrings, but on second thought... I tightened up my presentation a bit. – Adrian Larson Mar 8 at 0:12

One more way may be using Reverse Polish Notation for evaluation of expressions without recursion and for linear time. My solution is just simplified version of this one.

Example of implementation some comments:

public class RPN {
    //map with priorities    
    public static Map<String,Integer> OperatorsPriorities = new Map<String,Integer>{
         '*' =>  2, // AND binary
         '+' =>  1,// OR binary
         '(' => -1, //
         ')' => -2,
         '-' => 3  //NOT - unary
    };

    public static Boolean evaluateString(String param){
        String rpn_string  = buildRPN(param);
        return evalRPN(rpn_string);
    }

    public static string buildRPN(String param){
        param = param.replace('true','t').replace('false','f').replace(' ','')
            .replace('&&','*').replace('||','+').replace('!','-');
        //to make input string smaller, do some nice replacements
        List<String> stack = new List<String>();//represent stack
        String result = '';//Reverse Polish Notation string
        Integer counter = 0;//current character position
        while(counter < param.length()){//go through input string
            String p = param.substring(counter,counter+1);//get current character
            if (p == 't' || p == 'f'){//if that is a value
                result += p;//add to RPN
            }else{
                if (OperatorsPriorities.get(p) == -1){//if that is open bracket
                    stack.add(p);//add to stack
                }else if(OperatorsPriorities.get(p) == -2){//if that is closed bracked
                    while(stack.get(stack.size() -1) != '('){//pop stack until open bracket
                        result += stack.remove(stack.size() - 1);
                    }
                    stack.remove(stack.size() - 1);//remove bracket from stack
                }else {//if that is operator
                    while (   stack.size() > 0 
                           && OperatorsPriorities.get(p) <= OperatorsPriorities.get(stack.get(stack.size() - 1))){
                        result += stack.remove(stack.size() - 1);//pop from stack to RPN until operator with lower priority meet
                    }
                    stack.add(p);
                }
            }
            counter++;
        }
        while(stack.size()> 0){//pop stack to RPN result string
            result += stack.remove(stack.size() - 1);
        }
        return result;
    }


    public static string opposite(string param){
        return (param == 't')? 'f': 't';
    }

    public static boolean evalRPN(string prn){
        List<String> elements = prn.split('');
        Integer position = 0;
        while(elements.size() > 1){//evaluate rpn string unless 1 value left
            String element = elements.get(position);//get current position
            if (element == 't' || element == 'f'){//if that is value - go forward
                position++;
            }else{
                if (element == '-'){//unary operator - replace previous value and remove it
                    elements.set(position-1,opposite(elements.get(position-1)));
                    elements.remove(position);
                    position--;
                }else if (element == '*'){//binary operator - replace prev prev value and remove prev and current 
                    String par1 = elements.get(position - 2);
                    String par2 = elements.get(position - 1);
                    String res = (par1 == 'f' || par2 == 'f')? 'f' : 't';
                    elements.set(position-2,res);
                    elements.remove(position);
                    elements.remove(position-1);
                    position -= 2;
                }else if (element == '+'){//another binary operator
                    String par1 = elements.get(position - 2);
                    String par2 = elements.get(position - 1);
                    String res = (par1 == 't' || par2 == 't')? 't' : 'f';
                    elements.set(position-2,res);
                    elements.remove(position);
                    elements.remove(position-1);
                    position -= 2;
                }
            }
        }
        return  (elements.get(0) == 't')?true:false;
    }
}
share|improve this answer
    
Thanks for this, but RPN Won't help me, I have a routine that writes this string and i can't change it. Besides, humans will read it and RPN is not as easy to understand. – Sebastian Kessel Mar 8 at 0:44
    
Certainly harder for me to wrap my head around. Still, interesting approach. +1 – Adrian Larson Mar 8 at 0:45
    
Human doesn't need to read that (except in errors case). This consists of 2 modules, first one build rpn, second evaluate it. And, in case of understanding this simple version, it would be easier to build more complex one in future – kurunve Mar 8 at 0:47
4  
+1 for RPN - along with its friend, the HP-45 calculator - without which I couldn't have done all my engineering problem sets as rapidly. – cropredy Mar 8 at 0:50
    
@kurunve, in my use case, I absolutely need a human to read it. Thanks, but this is not what I was looking for. – Sebastian Kessel Mar 8 at 15:23

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.