0

I'm creating a microlibrary for switching CSS classes in a way similar to what Vue.js does, based on a string of JavaScript that is evaluated against some state.

I have an object with the variables that I want to check and want to cover some basic expressions, without having to use some external package (I tried jexl, expr-eval but they are huge and kind of outdated).

Is there any quick or even dirty way for achieving this, for very simple expressions, without eval or other libraries?

Here are some examples:

const evaluate = (expr, state) => /* ? */

const state = {
  isActive: true,
  isRed: false,
  count: 637
}

evaluate('isActive', state) // -> true
evaluate('!isRed', state) // -> true
evaluate('isRed == true', state) // -> false
evaluate('isActive && isRed', state) //-> false
evaluate('count > 100', state) // -> true
// ...
7
  • 2
    eval() is the quick-and-dirty way. Anything better will be complicated and require custom code. Commented Jun 7, 2018 at 16:06
  • 1
    The problem you are going to have is the fact that the items you are trying to parse are in the object and not variables. Commented Jun 7, 2018 at 16:07
  • @JonasW. And in strict mode it is not going to fly Commented Jun 7, 2018 at 16:11
  • 1
    Where does this string of JavaScript come from? Who writes it? Where does it live? Commented Jun 7, 2018 at 16:16
  • @torazaburo "in a way similar to what Vue.js does, based on a string of JavaScript that is evaluated against some state" br.vuejs.org/v2/guide/class-and-style.html Commented Jun 7, 2018 at 16:21

2 Answers 2

1

Why not just use arrow functions?:

const evaluate = (fn, state) => fn(state);

evaluate(s => s.isActive, state);
evaluate(s => !s.isRed, state);

Or if you really wanna evaluate a string the bad way:

const evaluate = (expr, state) => {
  with(state) { eval(expr); }
};
Sign up to request clarification or add additional context in comments.

1 Comment

I went with arrow functions. Hadn't thought of using them in this case. Thanks!
1

This one can't handle more than a comparison of 2 values and an evaluation of a single value - also it can't handle nested objects, but you said you need a very primitive one, so here you go:

const matchers = [
        [/^\s*(!?\w+)\s*==\s*(!?\w+)\s*$/,
          function(a, b) { return semieval(a, this) == semieval(b, this) }],
        [/^\s*(!?\w+)\s*===\s*(!?\w+)\s*$/, 
          function(a, b) { return semieval(a, this) === semieval(b, this) }],
        [/^\s*(!?\w+)\s*>=\s*(!?\w+)\s*$/, 
          function(a, b) { return semieval(a, this) >= semieval(b, this) }],
        [/^\s*(!?\w+)\s*<=\s*(!?\w+)\s*$/, 
          function(a, b) { return semieval(a, this) <= semieval(b, this) }],
        [/^\s*(!?\w+)\s*>\s*(!?\w+)\s*$/, 
          function(a, b) { return semieval(a, this) > semieval(b, this) }],
        [/^\s*(!?\w+)\s*<\s*(!?\w+)\s*$/, 
          function(a, b) { return semieval(a, this) < semieval(b, this) }],
        [/^\s*(!?\w+)\s*\|\|\s*(!?\w+)\s*$/, 
          function(a, b) { return semieval(a, this) || semieval(b, this) }],
        [/^\s*(!?\w+)\s*&&\s*(!?\w+)\s*$/, 
          function(a, b) { return semieval(a, this) && semieval(b, this) }],
        [/^\s*!(!?\w+)\s*$/,
          function(a) { return !semieval(a, this) }],
        [/^\s*true\s*$/, 
          function() { return true }],
        [/^\s*false\s*$/, 
          function() { return false }],
        [/^\s*([\d]+)\s*$/, 
          function(a) { return parseInt(a, 10) }],
        [/^\s*(!?\w+)\s*$/, 
          function(a) { return this.hasOwnProperty(a) ? this[a] : a }]
      ];

function semieval(statement, object) {
  for(let matcher of matchers) {
    if(matcher[0].test(statement)) {
      let parts = statement.match(matcher[0]);
      return matcher[1].apply(object, parts.slice(1));
    }
  }
}

const state = {
  isActive: true,
  isRed: false,
  count: 637
}

console.log(
  semieval('isActive', state) // -> true
)
console.log(
  semieval('!isRed', state) // -> true
)
console.log(
  semieval('isRed == true', state) // -> false
)
console.log(
  semieval('!!isRed', state) // -> false
)
console.log(
  semieval('isActive && isRed', state) //-> false
)
console.log(
  semieval('count > 100', state) // -> true
)

What this does is compare the statements to RegExp that fit into one of: a==b, a===b, a<=b, a>=b, a<b, a>b, a&&b, a||b, !a, a

When it finds a matching pattern, it tries to find out what value a and if given, b are, it does so by first checking if the value is true, false, a number - and only then checks the passed object for an existing key and gives back its value for comparison.

2 Comments

Can i get a reason for the downvote? Script does exactly what was required
Sure, this basic version didn't take me too long to write, and some parts certainly can be expanded easily (i think nested attributes with a lookup based on subobj.key in the last matcher, but for a comparison with more than a and b you will need some time

Your Answer

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