Replacement of eval for simple expressions?

For example, you need to calculate the string "x > 5 && (y == 7.5)"
But you can't use either eval or new Function () or setTimeout () or onclick in elements, because this is not safe.
In fact, the string can be arbitrary. The point is that there are no difficulties, only > < == != && || + - * / brackets and stuff.
There is a simple alternative to eval?
March 19th 20 at 08:23
3 answers
March 19th 20 at 08:25
Solution
This is interesting. But I was hoping for an existing library. And there is no forwarding variables. But the idea got it, thanks. - Dorothea commented on March 19th 20 at 08:28
@Dorothea, you can walk through the string and replace variables with their values. Unless of course there really is a simple expression, without multi-pass logic. Well, or to calculate in parts. - Marvin.Gerhold commented on March 19th 20 at 08:31
Thank you, on the basis of these ideas at the knee made a library of 7 kilobytes. - Dorothea commented on March 19th 20 at 08:34
March 19th 20 at 08:27
Solution
You can do so. Only the output will be a string.

var res = `${x > 5 && (y == 7.5)}`;

https://jsfiddle.net/jey5xs2r/1/

PS: can offer another solution. Me has had a utility for parsing strings. It can be taught to parse as a linear structure and a tree. She will probably lose in speed specialized solutions, but it is universal, in fact, a Swiss army knife for parsing structures of any complexity, allowing you to create parser for HTML and for mathematical expressions, and even code if you wish.

And this is an example of use for your task:
code
const Splitter = require("split-tools");

var text = "x > 5 && (y == 7.5) || sin(Math.PI*a/360) || true";

const splitter = new Splitter({
 matchers: {
 function: /\s*([a-z]\w*)\(\s*(.*)\s*\)/g,
 bracket: /\(\s*(.*)\s*\)/g,
 operator: /\s*(\>|\<|\=\=|\!\=|\&\&|\|\||\+|\-|\*|\/|\%)\s*/g,
 variable: /\s*([a-z][\w\.]*)\s*/gi,
 sting: /\s*\"(.*?)\"\s*/gi,
 boolean: /\s*(true|false)\s*/gi,
 value: /\s*(\d[\d\.]*)\s*/gi
}
});


const tree = {
 "brackets": ["function", "bracket", "operator", "variable", "sting", "boolean", "value"]
};

subSplit function(id, text) {
 console.log("subSplit:", id, "[", text, "]");
 if(tree[id] && tree[id].length)
 return splitter.process(text, tree[id]);
 return text;
}


splitter.addParser("function",(match,name,body)=>{
 return {
 type: "function",
 name: name,
 body: subSplit("brackets", body)
};
});

splitter.addParser("bracket",(match,body)=>{
 return {
 type: "bracket",
 body: subSplit("brackets", body)
};
});

splitter.addParser("operator",(match,body)=>{
 return {
 type: "operator",
 body: body
};
});

splitter.addParser("variable",(match,body)=>{
 return {
 type: "variable",
 body: body
};
});

splitter.addParser("string",(match,body)=>{
 return {
 type: "string",
 body: body
};
});

splitter.addParser("boolean",(match,body)=>{
 return {
 type: "boolean",
 body: !!body
};
});

splitter.addParser("value",(match,body)=>{
 return {
 type: "value",
 body: +body
};
});


const list = splitter.process(text, tree.tags);

console.log("\n\nresult:\n",JSON.stringify(list, null, ' '));



The output from the parser will be an array of walking which will calculate the value rasparennogo mathematical expression:
the result
[
 { "type": "variable", "body": "x" },
 { "type": "operator", "body": ">" },
 { "type": "value", "body": 5 },
 { "type": "operator", "body": "&&" },
 { "type": "bracket", "body": [
 { "type": "variable", "body": "y" },
 { "type": "operator", "body": "==" },
 { "type": "value", "body": 7.5 }
]
},
 { "type": "operator", "body": "||" },
 { "type": "function", "name": "sin", "body": [
 { "type": "variable", "body": "Math.PI" },
 { "type": "operator", "body": "*" },
 { "type": "variable", "body": "a" },
 { "type": "operator", "body": "/" },
 { "type": "value", "body": 360 }
]
},
 { "type": "operator", "body": "||" },
 { "type": "variable", "body": "true" }
]
And get the user to initialize the res using the input?
P. S. something does not work... https://jsfiddle.net/mv6bq92x/ - Dorothea commented on March 19th 20 at 08:30
@Dorothea, no fail. - stanley.Ratke28 commented on March 19th 20 at 08:33
@Dorothea, Little changed answer, or rather added to it a way of solving Your problem. View all after PS: - stanley.Ratke28 commented on March 19th 20 at 08:36
Thanks, looks interesting. And, most importantly, beautiful, and not that I have.
However, there remains the problem of tree traversal. Whatever it was detailed
(for example this)
.
[-]
 / \
 [+] \___[3] 
 / \
 [5]__/ [*]
 / \
 [+] [4]
 / \ 
 [1] [2]
the question remains, where to start and where to go.
In the nearby response of the proposed algorithm without a tree in which elements are sorted in the order in which will be calculated, and are arranged linearly in an array. - Dorothea commented on March 19th 20 at 08:39
Now I already use something like this:
result = eval_lite("x > 5 && (y == 7.5)", {x: 10, y: 7}); //false

Price of 7 kilobytes (horrible code). - Dorothea commented on March 19th 20 at 08:42
@Dorothea, still a little tweaked, added function of the row and believe values
On the bypass now I think to create the structure, convenient to bypass, in terms of operator precedence - stanley.Ratke28 commented on March 19th 20 at 08:45
convenient to bypass, in terms of priority

There is not only a priority. Different story with the unary operators:
1 + (-1)
5c98b696e91ef796111642.png
I minus and failed.
And then there are function calls like sin(x) - here it is possible to realize, but the truth is I only need one specific function. Hence an important item in the feature list, adding custom functions. - Dorothea commented on March 19th 20 at 08:48
@Dorothea, now working on it - stanley.Ratke28 commented on March 19th 20 at 08:51
@Dorothea, it Seems to have won, though it went by traversing the array with the results of parsing the string

test string:
"x > 5 && (y == 7.5) || sin(PI*a/360) || isNumber(y) && true "


code
const Splitter = require("split-tools");

var text = "x > 5 && (y == 7.5) || sin(PI*a/360) || isNumber(y) && true ";
// var text = "x > 5 && (y == 7.5) || sin(PI*a/360)";

const splitter = new Splitter({
 matchers: {
 function: /\s*([a-z]\w*)\(\s*(.*)\s*\)/g,
 bracket: /\(\s*(.*)\s*\)/g,
 operator: /\s*(\!|\*|\/|\%|\+|\-|\=\=\=|\!\=\=|\<\=|\<|\>\=|\>|\=\=|\!\=|\&\&|\|\||\&|\|)\s*/g,
 boolean: /\s*(true|false)\s*/gi,
 sting: /\s*\"(.*?)\"\s*/gi,
 variable: /\s*([a-z][\w\.]*)\s*/gi,
 value: /\s*(\d[\d\.]*)\s*/gi
}
});

const tree = {
 "brackets": ["function", "bracket", "operator", "boolean", "variable", "sting", "value"]
};

subSplit function(id, text) {
 console.log("subSplit:", id, "[", text, "]");
 if(tree[id] && tree[id].length)
 return splitter.process(text, tree[id]);
 return text;
}


splitter.addParser("function",(match,name,body)=>{
 return {
 type: "function",
 name: name,
 body: subSplit("brackets", body)
};
});

splitter.addParser("bracket",(match,body)=>{
 return {
 type: "bracket",
 body: subSplit("brackets", body)
};
});

splitter.addParser("operator",(match,body)=>{
 return {
 type: "operator",
 body: body
};
});

splitter.addParser("variable",(match,body)=>{
 return {
 type: "variable",
 body: body
};
});

splitter.addParser("string",(match,body)=>{
 return {
 type: "string",
 body: body
};
});

splitter.addParser("boolean",(match,body)=>{
 return {
 type: "boolean",
 body: body==="true"?true:false
};
});

splitter.addParser("value",(match,body)=>{
 return {
 type: "value",
 body: +body
};
});


const list = splitter.process(text, tree.tags);

console.log("\n\nresult:\n",JSON.stringify(list, null, ' '));




// I can't think of anything more intelligent :)))
var OPERATOR_PRIORITY = {
 '!': (src, dst)=>{ dst.push({type: "value", body: !src.shift()}) },
 '*': (src, dst)=>{ dst[dst.length-1] = {type: "value", body: dst[dst.length-1].body*src.shift().body} },
 '/': (src, dst)=>{ dst[dst.length-1] = {type: "value", body: dst[dst.length-1].body/src.shift().body} },
 '%': (src, dst)=>{ dst[dst.length-1] = {type: "value", body: dst[dst.length-1].body%src.shift().body} },
 '-': (src, dst)=>{ dst[dst.length-1] = {type: "operator", body: "+" }; dst.push({type: "value", body: 0-src.shift().body}) },
 '+': (src, dst)=>{ dst[dst.length-1] = {type: "value", body: dst[dst.length-1].body+src.shift().body} },
 '<': (src, dst)=>{ dst[dst.length-1] = {type: boolean, body: (dst[dst.length-1].body<src.shift().body?true:false) } },
 '<=': (src, dst)=>{ dst[dst.length-1] = {type: boolean, body: (dst[dst.length-1].body<=src.shift().body?true:false) } },
 '>': (src, dst)=>{ dst[dst.length-1] = {type: boolean, body: (dst[dst.length-1].body>src.shift().body?true:false) } },
 '>=': (src, dst)=>{ dst[dst.length-1] = {type: boolean, body: (dst[dst.length-1].body>=src.shift().body?true:false) } },
 '==': (src, dst)=>{ dst[dst.length-1] = {type: boolean, body: (dst[dst.length-1].body==src.shift().body?true:false) } },
 '!=': (src, dst)=>{ dst[dst.length-1] = {type: boolean, body: (dst[dst.length-1].body!=src.shift().body?true:false) } },
 '===': (src, dst)=>{ dst[dst.length-1] = {type: boolean, body: (dst[dst.length-1].body===src.shift().body?true:false) } },
 '!==': (src, dst)=>{ dst[dst.length-1] = {type: boolean, body: (dst[dst.length-1].body!==src.shift().body?true:false) } },
 '&': (src, dst)=>{ dst[dst.length-1] = {type: "value", body: dst[dst.length-1].body&src.shift().body} },
 '|': (src, dst)=>{ dst[dst.length-1] = {type: "value", body: dst[dst.length-1].body|src.shift().body} },
 '&&': (src, dst)=>{ dst[dst.length-1] = {type: boolean, body: (dst[dst.length-1].body&&src.shift().body) } },
 '||': (src, dst)=>{ dst[dst.length-1] = {type: boolean, body: (dst[dst.length-1].body||src.shift().body) } }
};


// this is the set of allowed functions
var ALLOWED_FUNCTIONS = {
 "sin": (v)=>{ return {type: "value", body: Math.sin(v)} },
 "cos": (v)=>{ return {type: "value", body: Math.cos(v)} },
 "isNumber": (v)=>{ return {type: "boolean", body: !isNaN(parseFloat(v)) && isFinite(v) } }
};
// this is the set of allowed variables
var ALLOWED_VARIABLES = {
 "PI" Math.PI
 "x": 6,
 "y": 7.5,
 "a": 180
};


// the parsing results of the parsing
function resCalc(src){
 // replace variables with their values
 src = src.map(item=>{
 // if no variable is returned as is
 if(item.type !== "variable")
 return item;

 // otherwise, convert the variable to its value
 // here we must also check for the presence of the variable in the allowed list
 const value = ALLOWED_VARIABLES[item.body];
 let type = "value";
 if( typeof value !== "number" ) type = typeof value;
 return {
 type: type
 body: ALLOWED_VARIABLES[item.body]
};
});

 let dst;
Object.keys(OPERATOR_PRIORITY).forEach(operator=>{
 const _calc = OPERATOR_PRIORITY[operator];
 dst = [];
 while( src.length ){
 const item = src.shift();


 if(item.type === "function"){
 // if the function
 // here we must also check for the presence of features in the list of allowed
 dst.push(ALLOWED_FUNCTIONS[item.name]( resCalc(item.body).body ) );
 }else if(item.type === "bracket"){
 // if brackets
 dst.push( resCalc(item.body).body );
 }else if(item.type === "operator" && item.body === operator){
 // if the operator
 // here we must also check for the presence handler operator
 _calc(src, dst);
}else{
 // if all else
dst.push(item);
}

}
 src = dst;
});
 return src[0];
}


console.log("\n\nresult:\n",JSON.stringify(resCalc(list), null, ' '));
the result of parsing
[
 { "type": "variable", "body": "x" },
 { "type": "operator", "body": ">" },
 { "type": "value", "body": 5 },
 { "type": "operator", "body": "&&" },
 { "type": "bracket", "body": [
 { "type": "variable", "body": "y" },
 { "type": "operator", "body": "==" },
 { "type": "value", "body": 7.5 }
]
},
 { "type": "operator", "body": "||" },
 { "type": "function", "name": "sin", "body": [
 { "type": "variable", "body": "PI" },
 { "type": "operator", "body": "*" },
 { "type": "variable", "body": "a" },
 { "type": "operator", "body": "/" },
 { "type": "value", "body": 360 },
 { "type": "operator", "body": "||" },
 { "type": "variable", "body": "isNumber" },
 { "type": "variable", "body": "y" }
]
},
 { "type": "operator", "body": "&&" },
 { "type": "boolean", "body": true }
]
the result of the calculation values
{
 "type": "boolean",
 "body": true
}
- stanley.Ratke28 commented on March 19th 20 at 08:54
@Dorothea, corrected code:

code
const Splitter = require("split-tools");

var text = "x > 5 && (y == 7.5) || sin(PI*a/360) || isNumber(y) && true ";
// var text = "x+-y"; // this works too


const splitter = new Splitter({
 matchers: {
 function: /\s*([a-z]\w*)\(\s*(.*)\s*\)/g,
 bracket: /\(\s*(.*)\s*\)/g,
 operator: /\s*(\!|\*|\/|\%|\+|\-|\=\=\=|\!\=\=|\<\=|\<|\>\=|\>|\=\=|\!\=|\&\&|\|\||\&|\|)\s*/g,
 boolean: /\s*(true|false)\s*/gi,
 sting: /\s*\"(.*?)\"\s*/gi,
 variable: /\s*([a-z][\w\.]*)\s*/gi,
 value: /\s*(\d[\d\.]*)\s*/gi
}
});

const tree = {
 "brackets": ["function", "bracket", "operator", "boolean", "variable", "sting", "value"]
};

subSplit function(id, text) {
 console.log("subSplit:", id, "[", text, "]");
 if(tree[id] && tree[id].length)
 return splitter.process(text, tree[id]);
 return text;
}


splitter.addParser("function",(match,name,body)=>{
 return {
 type: "function",
 name: name,
 body: subSplit("brackets", body)
};
});

splitter.addParser("bracket",(match,body)=>{
 return {
 type: "bracket",
 body: subSplit("brackets", body)
};
});

splitter.addParser("operator",(match,body)=>{
 return {
 type: "operator",
 body: body
};
});

splitter.addParser("variable",(match,body)=>{
 return {
 type: "variable",
 body: body
};
});

splitter.addParser("string",(match,body)=>{
 return {
 type: "string",
 body: body
};
});

splitter.addParser("boolean",(match,body)=>{
 return {
 type: "boolean",
 body: body==="true"?true:false
};
});

splitter.addParser("value",(match,body)=>{
 return {
 type: "value",
 body: +body
};
});


const list = splitter.process(text, tree.brackets);

console.log("\n\nresult:\n",JSON.stringify(list, null, ' '));







// I can't think of anything more intelligent :)))
var OPERATOR_PRIORITY = {
 '!': (src, dst)=>{ dst.push({type: "value", body: !src.shift()}) },
 '*': (src, dst)=>{ dst[dst.length-1] = {type: "value", body: dst[dst.length-1].body*src.shift().body} },
 '/': (src, dst)=>{ dst[dst.length-1] = {type: "value", body: dst[dst.length-1].body/src.shift().body} },
 '%': (src, dst)=>{ dst[dst.length-1] = {type: "value", body: dst[dst.length-1].body%src.shift().body} },
 '-': (src, dst)=>{ dst[dst.length-1] = {type: "operator", body: "+" }; dst.push({type: "value", body: 0-src.shift().body}) },
 '+': (src, dst)=>{ dst[dst.length-1] = {type: "value", body: dst[dst.length-1].body+src.shift().body} },
 '<': (src, dst)=>{ dst[dst.length-1] = {type: boolean, body: (dst[dst.length-1].body<src.shift().body?true:false) } },
 '<=': (src, dst)=>{ dst[dst.length-1] = {type: boolean, body: (dst[dst.length-1].body<=src.shift().body?true:false) } },
 '>': (src, dst)=>{ dst[dst.length-1] = {type: boolean, body: (dst[dst.length-1].body>src.shift().body?true:false) } },
 '>=': (src, dst)=>{ dst[dst.length-1] = {type: boolean, body: (dst[dst.length-1].body>=src.shift().body?true:false) } },
 '==': (src, dst)=>{ dst[dst.length-1] = {type: boolean, body: (dst[dst.length-1].body==src.shift().body?true:false) } },
 '!=': (src, dst)=>{ dst[dst.length-1] = {type: boolean, body: (dst[dst.length-1].body!=src.shift().body?true:false) } },
 '===': (src, dst)=>{ dst[dst.length-1] = {type: boolean, body: (dst[dst.length-1].body===src.shift().body?true:false) } },
 '!==': (src, dst)=>{ dst[dst.length-1] = {type: boolean, body: (dst[dst.length-1].body!==src.shift().body?true:false) } },
 '&': (src, dst)=>{ dst[dst.length-1] = {type: "value", body: dst[dst.length-1].body&src.shift().body} },
 '|': (src, dst)=>{ dst[dst.length-1] = {type: "value", body: dst[dst.length-1].body|src.shift().body} },
 '&&': (src, dst)=>{ dst[dst.length-1] = {type: boolean, body: (dst[dst.length-1].body&&src.shift().body) } },
 '||': (src, dst)=>{ dst[dst.length-1] = {type: boolean, body: (dst[dst.length-1].body||src.shift().body) } }
};


// this is the set of allowed functions
var ALLOWED_FUNCTIONS = {
 "sin": (v)=>{ return {type: "value", body: Math.sin(v)} },
 "cos": (v)=>{ return {type: "value", body: Math.cos(v)} },
 "isNumber": (v)=>{ return {type: "boolean", body: !isNaN(parseFloat(v)) && isFinite(v) } }
};
// this is the set of allowed variables
var ALLOWED_VARIABLES = {
 "PI" Math.PI
 "x": 6,
 "y": 7.5,
 "a": 180
};


// the parsing results of the parsing
function resCalc(src){

 src = src
 // replace variables with their values
.map(item=>{
 // if no variable is returned as is
 if(item.type !== "variable")
 return item;

 // otherwise, convert the variable to its value
 // here we must also check for the presence of the variable in the allowed list
 const value = ALLOWED_VARIABLES[item.body];
 let type = "value";
 if( typeof value !== "number" ) type = typeof value;
 return {
 type: type
 body: ALLOWED_VARIABLES[item.body]
};
})
 // model function values
.map(item=>{
 // if not the function is returned as is
 if(item.type !== "function")
 return item;
 // otherwise, convert the function to its value
 // here we must also check for the presence of features in the list of allowed
 ALLOWED_FUNCTIONS return[item.name]( resCalc(item.body).body );
})
 // replace brackets with their values
.map(item=>{
 // if no variable is returned as is
 if(item.type !== "bracket")
 return item;
 // otherwise, convert the contents of the brackets in its value
 resCalc return(item.body);
});

 let dst;
Object.keys(OPERATOR_PRIORITY).forEach(operator=>{
 const _calc = OPERATOR_PRIORITY[operator];
 dst = [];
 while( src.length ){
 const item = src.shift();

 if(item.type === "operator" && item.body === operator){
 // if the operator
 // here we must also check for the presence handler operator
 _calc(src, dst);
}else{
 // if all else
dst.push(item);
}

}
 src = dst;
});
 return src[0];
}

console.log("\n\nresult:\n",JSON.stringify(resCalc(list), null, ' '));
  • the unary minus implemented
  • adding custom functions implemented
  • adding custom variables implemented
- stanley.Ratke28 commented on March 19th 20 at 08:57
@Dorothea, so sorry to You, misled You, this code does not properly handle nested brackets. Working on it.

PS: was surprised to find that in js regexp-Oh, no recursion, no balancing. - stanley.Ratke28 commented on March 19th 20 at 09:00
Yeah, that changes everything. And I was beginning to believe in magic...) - Dorothea commented on March 19th 20 at 09:03
The algorithm is actually simple well-known.
1. Tokenization - the allocation of tokens, parentheses, operations, etc. - that you make through recenty.
2. Building and traversing the tree, or the variant is easier - to sort in the order in which the expression is evaluated. The algorithm has been invented before - RPN. So that is nothing new. But I don't see how it will help of the regular season.
3. Actually counting - almost linear passage on the finished array, and calculating.

That's what I did about 5 hours straight until I got the script, and all the while couldn't stop thinking: maybe it is possible somehow easier? - Dorothea commented on March 19th 20 at 09:06
@DorotheaYes, that's the plan, only one of the Doobie in the most Libe. Tomorrow will fix that. Today already forces are not present.

PS: do not leave a stone flower. But sure to come.

ZYZY: deeply stuck. save the library. how to do - unsubscribe. - stanley.Ratke28 commented on March 19th 20 at 09:09
March 19th 20 at 08:29
Solution
Made the decision in haste - eval_lite.js.
It's quiet horror, but it works.

Find more questions by tags JavaScript