Reputation:
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
// ...
Upvotes: 0
Views: 873
Reputation: 2445
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.
Upvotes: 1
Reputation: 138267
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); }
};
Upvotes: 1