Evaluate string giving boolean expression in JavaScript - javascript

I have a string that contains Boolean logic something like:
var test = "(true)&&(false)&&!(true||true)"
What is a good way to evaluate this string in JavaScript to get the boolean value of false in this case
I know we could use eval() or new Function().. - but is that a safe approach?
I am guessing the other option would be to write a custom parser. Being a fairly new person to JS, would that be a lot of effort? I could not find any examples of parsers for Boolean logic expressions
Any other alternatives?

As long as you can guarantee it to be safe, I think you could use eval.
Maybe by treating it before doing an eval?
var test = "(true)&&(false)&&!(true||true)"
var safe = test.replace(/true/ig, "1").replace(/false/ig, "0");
var match = safe.match(/[0-9&!|()]*/ig);
if(match) {
var result = !!eval(match[0]);
}

Javascript has a ternary operator you could use:
var i = result ? 1 : 0;
Here, result is Boolean value either True or False.
So, Your question will be something like that after this operation.
(1)&(0)&!(1||1)
I hope you can better evaluate now this Boolean logic.

you can use eval,
Eg: eval("(true)&&(false)&&!(true||true)");

Try this code
function processExpression(expr)
{
while (expr.indexOf("(" ) != -1 )
{
expr = expr.replace(/\([\w|]+\)/g, function(matched){ return processBrace(matched)});
}
return expr = processBrace( "(" + expr + ")" );
}
function processBrace(str)
{
return str.substring(1).slice(0,-1).split(/(?=&|\|)/).map(function(value,index,arr){
if ( index != 0 && index%2 == 0 ) { return arr[index-1] + value } else if(index==0){return value;} else {return ""}
}).filter(function(val){return val.length > 0}).reduce(function(prev,current){
var first = Boolean(prev);
var operator = current.substring(0,2);
var operand = current.substring(2);
while ( operand.indexOf("!") != -1 )
{
var boolval = operand.match(/\w+/)[0] == "false"; //flip the value by comparing it with false
var negations = operand.match(/\W+/)[0];
operand = negations.substring(1) + boolval;
}
var second = operand == "true";
var output = operator == "&&" ? (first && second) : (first || second);
return output;
});
}
DEMO
function processExpression(expr)
{
while (expr.indexOf("(" ) != -1 )
{
expr = expr.replace(/\([\w|]+\)/g, function(matched){ return processBrace(matched)});
}
return expr = processBrace( "(" + expr + ")" );
}
function processBrace(str)
{
return str.substring(1).slice(0,-1).split(/(?=&|\|)/).map(function(value,index,arr){
if ( index != 0 && index%2 == 0 ) { return arr[index-1] + value } else if(index==0){return value;} else {return ""}
}).filter(function(val){return val.length > 0}).reduce(function(prev,current){
var first = Boolean(prev);
var operator = current.substring(0,2);
var operand = current.substring(2);
while ( operand.indexOf("!") != -1 )
{
var boolval = operand.match(/\w+/)[0] == "false"; //flip the value by comparing it with false
var negations = operand.match(/\W+/)[0];
operand = negations.substring(1) + boolval;
}
var second = operand == "true";
var output = operator == "&&" ? (first && second) : (first || second);
return output;
});
}
var example1 = "(true)&&(false)&&!(true||true)";
document.body.innerHTML += example1 + " -- " + processExpression(example1);

Try using "".match() in ternary operator condition
"(true)&&(true)&&!(true||true)".match(/false/ig)?false:true

Related

Get function parameter length including default params

If you make use of the Function.length property, you get the total amount of arguments that function expects.
However, according to the documentation (as well as actually trying it out), it does not include Default parameters in the count.
This number excludes the rest parameter and only includes parameters before the first one with a default value
- Function.length
Is it possible for me to somehow get a count (from outside the function) which includes Default parameters as well?
Maybe you can parse it yourself, something like:
function getNumArguments(func) {
var s = func.toString();
var index1 = s.indexOf('(');
var index2 = s.indexOf(')');
return s.substr(index1 + 1, index2 - index1 - 1).split(',').length;
}
console.log(getNumArguments(function(param1, param3 = 'test', ...param2) {})); //3
Copying my answer over to here from a duplicate question:
Well, it's a bit of a mess but I believe this should cover most edge cases.
It works by converting the function to a string and counting the commas, but ignoring commas that are in strings, in function calls, or in objects/arrays. I can't think of any scenarios where this won't return the proper amount, but I'm sure there is one, so this is in no way foolproof, but should work in most cases.
UPDATE: It's been pointed out to me that this won't work for cases such as getNumArgs(a => {}) or getNumArgs(function(a){}.bind(null)), so be aware of that if you try to use this.
function getNumArgs(func) {
var funcStr = func.toString();
var commaCount = 0;
var bracketCount = 0;
var lastParen = 0;
var inStrSingle = false;
var inStrDouble = false;
for (var i = 0; i < funcStr.length; i++) {
if (['(', '[', '{'].includes(funcStr[i]) && !inStrSingle && !inStrDouble) {
bracketCount++;
lastParen = i;
} else if ([')', ']', '}'].includes(funcStr[i]) && !inStrSingle && !inStrDouble) {
bracketCount--;
if (bracketCount < 1) {
break;
}
} else if (funcStr[i] === "'" && !inStrDouble && funcStr[i - 1] !== '\\') {
inStrSingle = !inStrSingle;
} else if (funcStr[i] === '"' && !inStrSingle && funcStr[i - 1] !== '\\') {
inStrDouble = !inStrDouble;
} else if (funcStr[i] === ',' && bracketCount === 1 && !inStrSingle && !inStrDouble) {
commaCount++;
}
}
// Handle no arguments (last opening parenthesis to the last closing one is empty)
if (commaCount === 0 && funcStr.substring(lastParen + 1, i).trim().length === 0) {
return 0;
}
return commaCount + 1;
}
Here are a few tests I tried it on: https://jsfiddle.net/ekzuvL0c/
Here is a function to retrieve the 'length' of a function (expression or object) or an arrow function expression (afe). It uses a regular expression to extract the arguments part from the stringified function/afe (the part between () or before =>) and a regular expression to cleanup default values that are strings. After the cleanups, it counts the comma's, depending on the brackets within the arguments string.
Note This will always be an approximation. There are edge cases that won't be covered. See the tests in this Stackblitz snippet
const determineFnLength = fnLenFactory();
console.log(`fnTest.length: ${determineFnLength(fnTest)}`);
function fnTest(a,
b,
c = 'with escaped \' quote and, comma',
d = "and double \" quotes, too!" ) { console.log(`test123`); }
function fnLenFactory() {
const fnExtractArgsRE = /(^[a-z_](?=(=>|=>{)))|((^\([^)].+\)|\(\))(?=(=>|{)))/g;
const valueParamsCleanupRE = /(?<=[`"'])([^\`,].+?)(?=[`"'])/g;
const countArgumentsByBrackets = params => {
let [commaCount, bracketCount, bOpen, bClose] = [0, 0, [...`([{`], [...`)]}`]];
[...params].forEach( chr => {
bracketCount += bOpen.includes(chr) ? 1 : bClose.includes(chr) ? -1 : 0;
commaCount += chr === ',' && bracketCount === 1 ? 1 : 0; } );
return commaCount + 1; };
const extractArgumentsPartFromFunction = fn => {
let fnStr = fn.toString().replace(RegExp(`\\s|function|${fn.name}`, `g`), ``);
fnStr = (fnStr.match(fnExtractArgsRE) || [fn])[0]
.replace(valueParamsCleanupRE, ``);
return !fnStr.startsWith(`(`) ? `(${fnStr})` : fnStr; };
return (func, forTest = false) => {
const params = extractArgumentsPartFromFunction(func);
const nParams = params === `()` ? 0 : countArgumentsByBrackets(params);
return forTest ? [params, nParams] : nParams;
};
}

javascript string expression as if condition

I am building a string which is to be evaluated as if condition, like
if (location_val.length == 1) {
condition = condition + " v.location === '" + location_val + "' &&";
} else {
for (var i = 0; i < location_val.length; i++) {
condition = condition + " v.location === '" + location_val[i] + "' && ";
}
}
but javascript evaluates the variable if it is not null, undefined, 0 or empty string, how to evaluate string as expression? I am using condition variable as follows, but it always returns true because of the above reason.
if(condition)
Build your expression properly - not using string concatenation, its something like this:
var condition = (location_val.length == 1 && v.location == location_val)
|| (location_val.every(function(loc){ return v.location === loc; } );
if(condition) {
....
}
I suspect there is more to this (you have && at the end of both strings) but the same applies to everything else. Boolean's can be combined in parts
var condition1 = (location_val.length == 1 && v.location == location_val)
|| (location_val.every(function(loc){ return v.location === loc; } );
var condition2 = ...
var condition3 = ...
if(condition1 && condition2 && condition3){
....
}
In JavaScript there's the eval function. You can use it to evaluate a string as an expression.
eval('3 + 2 === 5') //true
I would recommend you to rethink your solution, though, since working with strings as expressions when you could solve your problem differently, is really error prone and literally the worst practise.
Also use === instead of ==
(=== works as you'd expect == to work, == doesn't)

a regex or function to extract all the parameters passed to a function

I am looking to find the passed parameter to a function
say i already have hello as function and i have a STRING as following
hello(1,'434','hello,word',"h,g",{a:'b,u', l : { "sk" : "list", bk : 'u,93' }, c : 9},true)
Then upon that regex or function i should be able to find following 6 strings
'1'
'"434"'
'"hello,world"'
'"h,g"'
'{"a":"b,u","l":{"sk":"list","bk": "u,93"},"c":9}'
'true'
As per urs question you can do it like this:
x =Hello(1,'434','hello,word',"h,g",{a:'b,u', l : { "sk" : "list", bk : 'u,93' }, c : 9},true);
function Hello() {
for (i = 0; i <arguments.length; i++) {
console.log(arguments[i])
}
}
You can take help of argument object which is an Array-like object corresponding to the arguments passed to a function.
If that's a string then you might have to escape the double quotes first to result like
var x = "hello(1,'434','hello,word',\"h,g\",{a:'b,u', l : { \"sk\" : \"list\", bk : 'u,93' }, c : 9},true)";
and then you may invoke it like
Function(x)();
and in the hello function you should iterate over the arguments object's properties like
function hello(){
Array.prototype.forEach.call(arguments, prop => console.log(prop));
}
This is my workaround. It may be error-prone, but it should be faster than the eval solutions.
var extractParameters = function(str){
var ar = [];
if(typeof str === 'string' && str.length){
var chars = str.split(','), cl = chars.length;
var pushInto = function(n){
try {
ar.push(JSON.parse(chars[n]));
} catch(er){
ar.push(undefined);
}
};
for(var di, si, eg, fg, n = 0; n < cl; n++){
eg = chars[n].charAt(0);
fg = chars[n].charAt(chars[n].length - 1);
if(eg === fg && (eg === '"' || eg === "'")){
chars[n] = "\"" + chars[n].substring(1, chars[n].length - 1) + "\"";
}
di = chars[n].indexOf('"');
si = chars[n].indexOf("'");
if(((si === -1) && (di === -1)) || (eg === fg && (eg === '"' || eg === "'")) ||
(chars[n].charAt(0) === "{" && chars[n].charAt(chars[n].length-1) === "}" && (chars[n].match(/\{/g).length === chars[n].match(/\}/g).length))){
pushInto(n);
} else if(n < (cl-1)) {
chars[n] = chars[n] + ','+ chars[n+1];
chars.splice(n+1,1);
n--;
cl--;
continue;
}
}
}
return ar;
};
fiddle : https://jsfiddle.net/jv0328tp/16/

splitting a string based on AND OR logic in javascript

My problem is to split a string which contains a logical operation.
For example, here is my sample string:
var rule = "device2.temperature > 20 || device2.humidity>68 && device3.temperature >10"
I need to parse that string in a way that I can easily operate my logic and I am not sure which approach would be better.
PS: Please keep in mind that those rule strings can have 10 or more different condition combinations, like 4 ANDs and 6 ORs.
Assuming no parentheses, I might go with something like this (JavaScript code):
function f(v,op,w){
var ops = {
'>': function(a,b){ return a > b; },
'<': function(a,b){ return a < b; },
'||': function(a,b){ return a || b; },
'&&': function(a,b){ return a && b; },
'==': function(a,b){ return a == b;}
}
if (ops[op]){
return ops[op](v,w);
} else alert('Could not recognize the operator, "' + op + '".');
}
Now if you can manage to get a list of expressions, you can evaluate them in series:
var exps = [[6,'>',7],'||',[12,'<',22], '&&', [5,'==',5]];
var i = 0,
result = typeof exps[i] == 'object' ? f(exps[i][0],exps[i][1],exps[i][2]) : exps[i];
i++;
while (exps[i] !== undefined){
var op = exps[i++],
b = typeof exps[i] == 'object' ? f(exps[i][0],exps[i][1],exps[i][2]) : exps[i];
result = f(result,op,b);
i++;
}
console.log(result);
If you are absolutely sure that the input is always going to be valid JavaScript
var rule = "device2.temperature > 20 || device2.humidity>68 && device3.temperature >10"
var rulePassed = eval(rule);
Keep in mind that in most cases "eval" is "evil" and has the potential to introduce more problems than it solves.
function parse(rule){
return Function("ctx", "return("+rule.replace(/[a-z$_][a-z0-9$_\.]*/gi, "ctx.$&")+")");
}
a little bit better than eval, since it will most likely throw errors, when sbd. tries to inject some code.
Because it will try to access these properties on the ctx-object instead of the window-object.
var rule = parse("device2.temperature > 20 || device2.humidity>68 && device3.temperature >10");
var data = {
device2: {
temperature: 18,
humidity: 70
},
device3: {
temperature: 15,
humidity: 75
}
};
console.log( rule.toString() );
console.log( rule(data) );
Overkill:
beware, not fully tested. may still contain errors
And, code doesn't check wether syntax is valid, only throws on a few obvious errors.
var parse = (function(){
function parse(){
var cache = {};
//this may be as evil as eval, so take care how you use it.
function raw(v){ return cache[v] || (cache[v] = Function("return " + v)) }
//parses Strings and converts them to operator-tokens or functions
function parseStrings(v, prop, symbol, number, string){
if(!prop && !symbol && !number && !string){
throw new Error("unexpected/unhandled symbol", v);
}else{
var w;
switch(prop){
//keywords
case "true":
case "false":
case "null":
w = raw( v );
break;
}
tokens.push(
w ||
~unary.indexOf(prop) && v ||
prop && parse.fetch(v) ||
number && raw( number ) ||
string && raw( string ) ||
symbol
);
}
}
var tokens = [];
for(var i = 0; i < arguments.length; ++i){
var arg = arguments[i];
switch(typeof arg){
case "number":
case "boolean":
tokens.push(raw( arg ));
break;
case "function":
tokens.push( arg );
break;
case "string":
//abusing str.replace() as kind of a RegEx.forEach()
arg.replace(matchTokens, parseStrings);
break;
}
}
for(var i = tokens.lastIndexOf("("), j; i>=0; i = tokens.lastIndexOf("(")){
j = tokens.indexOf(")", i);
if(j > 0){
tokens.splice(i, j+1-i, process( tokens.slice( i+1, j ) ));
}else{
throw new Error("mismatching parantheses")
}
}
if(tokens.indexOf(")") >= 0) throw new Error("mismatching parantheses");
return process(tokens);
}
//combines tokens and functions until a single function is left
function process(tokens){
//unary operators like
unary.forEach(o => {
var i = -1;
while((i = tokens.indexOf(o, i+1)) >= 0){
if((o === "+" || o === "-") && typeof tokens[i-1] === "function") continue;
tokens.splice( i, 2, parse[ unaryMapping[o] || o ]( tokens[i+1] ));
}
})
//binary operators
binary.forEach(o => {
for(var i = tokens.lastIndexOf(o); i >= 0; i = tokens.lastIndexOf(o)){
tokens.splice( i-1, 3, parse[ o ]( tokens[i-1], tokens[i+1] ));
}
})
//ternary operator
for(var i = tokens.lastIndexOf("?"), j; i >= 0; i = tokens.lastIndexOf("?")){
if(tokens[i+2] === ":"){
tokens.splice(i-1, 5, parse.ternary(tokens[i-1], tokens[i+1], tokens[i+3] ));
}else{
throw new Error("unexpected symbol")
}
}
if(tokens.length !== 1){
throw new Error("unparsed tokens left");
}
return tokens[0];
}
var unary = "!,~,+,-,typeof".split(",");
var unaryMapping = { //to avoid collisions with the binary operators
"+": "plus",
"-": "minus"
}
var binary = "**,*,/,%,+,-,<<,>>,>>>,<,<=,>,>=,==,!=,===,!==,&,^,|,&&,||".split(",");
var matchTokens = /([a-z$_][\.a-z0-9$_]*)|([+\-*/!~^]=*|[\(\)?:]|[<>&|=]+)|(\d+(?:\.\d*)?|\.\d+)|(["](?:\\[\s\S]|[^"])+["]|['](?:\\[\s\S]|[^'])+['])|\S/gi;
(function(){
var def = { value: null };
var odp = (k,v) => { def.value = v; Object.defineProperty(parse, k, def) };
unary.forEach(o => {
var k = unaryMapping[o] || o;
k in parse || odp(k, Function("a", "return function(ctx){ return " + o + "(a(ctx)) }"));
})
//most browsers don't support this syntax yet, so I implement this manually
odp("**", (a,b) => (ctx) => Math.pow(a(ctx), b(ctx)));
binary.forEach(o => {
o in parse || odp(o, Function("a,b", "return function(ctx){ return a(ctx) "+o+" b(ctx) }"));
});
odp("ternary", (c,t,e) => ctx => c(ctx)? t(ctx): e(ctx));
odp("fetch", key => {
var a = key.split(".");
return ctx => {
//fetches a path, like devices.2.temperature
//does ctx["devices"][2]["temperature"];
for(var i=0, v = ctx /*|| window*/; i<a.length; ++i){
if(v == null) return void 0;
v = v[a[i]];
}
return v;
}
});
/* some sugar */
var aliases = {
"or": "||",
"and": "&&",
"not": "!"
}
for(var name in aliases) odp(name, parse[aliases[name]]);
})();
return parse;
})();
and your code:
var data = {
device2: {
temperature: 18,
humidity: 70
},
device3: {
temperature: 15,
humidity: 75
}
};
//you get back a function, that expects the context to work on (optional).
//aka. (in wich context/object is `device2` defined?)
var rule = parse("device2.temperature > 20 || device2.humidity>68 && device3.temperature >10");
console.log("your rule resolved:", rule(data));
sugar:
var rule1 = parse("device2.temperature > 20");
var rule2 = parse("device2.humidity>68 && device3.temperature >10");
//partials/combining rules to new ones
//only `and` (a && b), `or` (a || b), `plus` (+value), `minus` (-value) and 'not', (!value) have named aliases
var rule3 = parse.or(rule1, rule2);
//but you can access all operators like this
var rule3 = parse['||'](rule1, rule2);
//or you can combine functions and strings
var rule3 = parse(rule1, "||", rule2);
console.log( "(", rule1(data), "||", rule2(data), ") =", rule3(data) );
//ternary operator and Strings (' and " supported)
var example = parse(rule1, "? 'device2: ' + device2.temperature : 'device3: ' + device3.temperature");
console.log( example(data) )
What else to know:
Code handles operator precedence and supports round brackets
If a Path can't be fetched, it the particular function returns undefined (no Errors thrown here)
Access to Array-keys in the paths: parse("devices.2.temperature") fetches devices[2].temperature
not implemented:
parsing Arrays and parsing function-calls and everything around value modification. This engine does some computation, it expects some Value in, and gives you a value out. No more, no less.

Javascript: Date Issue while validating in JavaScript function

In Javascript function:
if (Tim2Val > Tim3Val && Tim2Val < Tim4Val)
return true;
else
return false;
IF I have Variables like
Tim1Val= 8:00;
Tim2Val= 23:00;
Tim3Val= 01:00;
Tim4Val= 05:00
It is returning true. (It should return false. Can you please tell me how we can solve this?)
IF I have Variables like
Tim1Val= 8:00;
Tim2Val= 23:00;
Tim3Val= 02:00;
Tim4Val= 05:00;
It is returning false.
function fn_ConvTo24Format(MsTimeVal)
{
if(MsTimeVal=='')
{
return -1;
}
var A = MsTimeVal.split(/\D+/);
var locAMPos = MsTimeVal.indexOf('AM');
var locPMPos = MsTimeVal.indexOf('PM');
if(locAMPos ==-1 && locPMPos ==-1)
{
return MsTimeVal;
}
if(locAMPos!= -1 && A[0] + '.' + A[1]=='12.00' )
{
return 0;
}
if(locPMPos!= -1 && A[0] + '.' + A[1]=='12.00' )
{
return 12;
}
if(locAMPos!= -1 && A[0] + '.' + A[1]=='12.00' )
{
return 0;
}
if(locAMPos!= -1)
{
return A[0] + '.' + A[1];
}
if(locPMPos!= -1)
{
return (parseFloat(A[0]) + 12) + '.' + A[1];
}
return MsTimeVal;
}
Perhaps not ever using Tim1Val is part of the problem?
I suppose these values come in as string. If you compare the two, Javascript converts them to numbers, effectively changing their value.
"8:00" becomes 8 and minutes are ignored.
Maybe convert your hours and minutes in to just minutes:
function toMinutes (value) {
var parts = value.split(":");
return Number(value[0]) * 60 + Number(value[1]);
}
var Tim1Val = "8:00";
var Tim2Val = "23:00";
var Tim3Val = "2:00";
var Tim4Val = "5:00";
if (toMinutes(Tim1Val) > toMinutes(Tim3Val) && toMinutes(Tim2Val) < toMinutes(Tim4Val))
Oh and as Peter Wilkinson says, you never use Tim1Val.
Your function returns data of several types: string and numbers.
Use one type make
...
if(locAMPos!= -1)
{
return parseFloat(A[0] + '.' + A[1]);
}
....
I think it's not good way to make time comparation.
Will be better get Date class exemplar and work with it.

Categories

Resources