5

I have an array like this:

[true, "&&", false]

The true and false statements are generated by previous conditions and pushed onto an array (i'm trying to provide users with a way to create basic logic themselves within my web project).

What I can't figure out is how to turn this array into an actual if statement that runs like this:

if(true && false) {
 // Run Code
}

Note that depending on what the user setup the array could look like this:

[true, "||", false]

if(true || false) {
  // Run code
}

I want to allow the array to also allow parentheses:

["(", true, "&&", false, ")", "||", true]

should become:

if( (true && false) || true) {
  // RUN CODE
}
5
  • 2
    Important question: can we assume that the array always contains valid JS, or could you have an invalid array such as ['(', ';', true', ')']? Commented May 12, 2016 at 17:38
  • 1
    Hi @Jordash if any answer has solved your question please consider accepting it by clicking the check-mark. This indicates to the wider community that you've found a solution and gives some reputation to both the answerer and yourself. There is no obligation to do this. Commented May 28, 2016 at 3:56
  • @procrastinator I think this question is from May 12, this year not May '12, no? Am I missing something? Commented Nov 23, 2016 at 5:04
  • Interesting question, but since you're talking about your "users" I'm guessing it isn't purely academic. Possible X Y q..? Could provide a bit more insight on what you're going about solving? Commented Nov 23, 2016 at 5:26
  • You could use the Shunting Yard Algorithm to convert it to postfix notation, which would make everything easier. Commented Aug 12, 2017 at 10:29

4 Answers 4

10

You could, although it's evil, use an eval after joining all the array elements. i.e.

var arr = [true, '&&', false];
if(eval(arr.join(''))){
    // your code
}

Update:

I just recently thought of a much simple (not simpler than eval) but safe answer. If the only boolean operations you're using are && and || and the parentheses are properly formatted, then you could do a bunch of regex replaces until there is only one value left, either "true" or "false".

The boolean values for AND operations can only be as follows and they simplify to either true or false

true && true == true true && false == false false && true == false false && false == false

the same goes for OR operations

true || true == true true || false == true false || true == true false || false == false

As a result, we can replace the expression with their simplified values - true or false. Then, if there are parentheses around the expression it'll end up as either '(true)' or '(false)' and we can easily regex replace that as well.

We can then loop this routine until we're finally left with one value, either 'true' or 'false'.

i.e. in code

var boolArr = ["(", true, "&&", "(", false, "||", true, ")", ")", "||", true];

//Convert the array to a string "(true&&(false||true))||true"
var boolString = boolArr.join('');

//Loop while the boolean string isn't either "true" or "false"
while(!(boolString == "true" || boolString == "false")){

 //Replace all AND operations with their simpler versions
 boolString = boolString.replace(/true&&true/g,'true').replace(/true&&false/g,'false');
 boolString = boolString.replace(/false&&true/g,'false').replace(/false&&false/g,'false');

 //Replace all OR operations with their simpler versions
 boolString = boolString.replace(/true\|\|true/g,'true').replace(/true\|\|false/g,'true');
 boolString = boolString.replace(/false\|\|true/g,'true').replace(/false\|\|false/g,'false');

 //Replace '(true)' and '(false)' with 'true' and 'false' respectively
 boolString = boolString.replace(/\(true\)/g,'true').replace(/\(false\)/g,'false');

}

//Since the final value is a string of "true" or "false", we must convert it to a boolean
value = (boolString == "true"?true:false);

Annd, if you're really dangerous, you can chain all those replaces together

Also, please notice the lovely lack of recursion and use of only one loop

Sign up to request clarification or add additional context in comments.

5 Comments

It's usually called evil because people could potentially put something malicious in the eval statement.
I thought your original answer (with its caveat) was fine, but your regex answer is brilliant. I'd vote you up again if I could.
@RickHitchcock Thank you very much, I had a lot of fun writing it.
Genuine question: how evil could this realistically get with the OP's only request being that the evaluation support true, false, two operators, and some parenthesis?
@1252748 Great question. It would really just depend on what OP wanted to use it for. If it was used in a trusted environment then no problem. But, if this was for some sort of user input scenario it would be different. As long as OP remembers to sanitize the Boolean statement, everything should be cool, and eval would work just fine. In terms of evilness, at the most, if OP used this code on a JS server to validate some boolean statement that a client made, and he forgets to sanitize the client's request, well, then you would basically be able to do whatever you want. I hope this isn't wordy.
7

Another solution with building of a nested array if parentheses exist and with an object for the operations, which could be easily extended.

How it works:

The term array is iterated and a new array is build upon. For every new opening parentheses, an empty array is assigned to the next level. All next terms are now pushed to the next level.

If a closing parentheses is found, the actual level is summarized with calculate and the result is pushed to the next lower level.

At the end, the base level is pooled and the result returned.

For evaluation, an object with functions for binary function symbols is used.

function process(logic) {

    function calculate(a) {
        while (a.length > 2) {
            a.splice(0, 3, op[a[1]](a[0], a[2]));
        }
        return a[0];
    }

    var op = {
            '&&': function (a, b) { return a && b; },
            '||': function (a, b) { return a || b; }
        },
        array = [[]],
        level = 0;

    logic.forEach(function (a) {
        if (a === '(') {
            ++level;
            array[level] = [];
            return;
        }
        if (a === ')') {
            --level;
            array[level].push(calculate(array[level + 1]));
            return;
        }
        array[level].push(a);
    });
    return calculate(array[0]);
}

function print(logic) {
    document.write(logic.join(' ') + ': <strong>' + process(logic) + '</strong><br>');
}

print([true, "&&", false]); //false
print([true, "&&", true]); //true
print([false, "||", true]); //true
print([false, "||", false]); //false
print(["(", true, "&&", false, ")", "||", true]); //true
print(["(", true, "&&", "(", false, "||", true, ")", ")", "||", true]); //true
print([false, "||", "(", "(", true, "&&", "(", false, "||", true, ")", ")", "&&", false, ")"]); //false

2 Comments

Well played, and you inspired me to streamline my own answer.
@RickHitchcock and Nina Schloz, I just realized that all of this can easily be done with a bunch of Regex replaces. Idk why I didn't think of it before. I added it to my answer if either one of you are curious.
7

What you want is a recursive descent parser.

The following code will handle true, false, &&, and ||, along with parentheses (which may be nested).

You can easily add other binary operators as needed.

function process(logic) {
  var result, op, i, par, idx, token;

  for(i = 0; i < logic.length; i++) {
    token = logic[i];
    if(token === '&&' || token === '||') {
      op = token;
    }
    else {
      if(token === '(') {
        for(par = 0, idx = i ; par ; idx++) {  //handle nested parentheses
          if     (logic[idx] === '(') par++;
          else if(logic[idx] === ')') par--;
        }
        token = process(logic.slice(i + 1, idx));
        i = idx + 1;
      }
      
      if     (op === '&&') result = result && token;
      else if(op === '||') result = result || token;
      else                 result = token;
    }
  };
  return result;
} //process

function print(logic) {
  console.log(logic.join(' ') + ': ' + process(logic));
}

print([true, "&&", false]); //false
print([true, "&&", true]); //true
print([false, "||", true]); //true
print([false, "||", false]); //false
print(["(", true, "&&", false, ")", "||", true]); //true
print(["(", true, "&&", "(", false, "||", true, ")", ")", "||", true]); //true
print([false, "||", "(", "(", true, "&&", "(", false, "||", true, ")", ")", "&&", false, ")"]); //false

2 Comments

Wow, that's one hell of an answer
Yep, too bad eval() is risky, because it really simplifies things.
0

Here is another regex based solution, similar to the one by @john tomas. However, this one is more resistant to spaces in the input and will also not cause a infinite loop for invalid input:


function parseStep(expr) {
    // parenthesis
    expr = expr.replace(/\( *true *\)/g, 'true');
    expr = expr.replace(/\( *false *\)/g, 'false');

    //or
    expr = expr.replace(/true *\|\| *true/g, 'true');
    expr = expr.replace(/true *\|\| *false/g, 'true');
    expr = expr.replace(/false *\|\| *true/g, 'true');
    expr = expr.replace(/false *\|\| *false/g, 'false');

    //and
    expr = expr.replace(/true *&& *true/g, 'true');
    expr = expr.replace(/true *&& *false/g, 'false');
    expr = expr.replace(/false *&& *true/g, 'false');
    expr = expr.replace(/false *&& *false/g, 'false');

    //not
    expr = expr.replace(/! *true/g, 'false');
    expr = expr.replace(/! *false/g, 'true');

    return expr;
}

function parseExpression(expr) {
    let last_expression = expr;
    let expression = parseExpression(last_expression);
    while (last_expression !== expression) {
        console.log(last_expression);
        last_expression = expression;
        expression = parseExpression(expression);
    }

    console.log(expression);
    return expression;
}

You can remove the console.log lines in production, it will show the gradual reduction.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.