const Splitter = require("split-tools");
var text = "x > 5 && (y == 7.5) || sin(PI*a/360) || isNumber(y) && true ";
// var text = "x+-y"; // это тоже работает
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"]
};
function subSplit(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, ' '));
// тут я не придумал ничего умнее :)))
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) } }
};
// это набор разрешенных функций
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) } }
};
// это набор разрешенных переменных
var ALLOWED_VARIABLES = {
"PI": Math.PI,
"x": 6,
"y": 7.5,
"a": 180
};
// парсер результатов разбора строки
function resCalc(src){
src = src
// заменяем переменные на их значения
.map(item=>{
// если не переменная возвращаем как есть
if(item.type !== "variable")
return item;
// иначе преобразуем переменную в ее значение
// тут надо еще сделать проверку на наличие переменной в списке разрешенных
const value = ALLOWED_VARIABLES[item.body];
let type = "value";
if( typeof value !== "number" ) type = typeof value;
return {
type: type,
body: ALLOWED_VARIABLES[item.body]
};
})
// заменяем функции на их значения
.map(item=>{
// если не функция возвращаем как есть
if(item.type !== "function")
return item;
// иначе преобразуем функции в ее значение
// тут надо еще сделать проверку на наличие функции в списке разрешенных
return ALLOWED_FUNCTIONS[item.name]( resCalc(item.body).body );
})
// заменяем скобки на их значения
.map(item=>{
// если не переменная возвращаем как есть
if(item.type !== "bracket")
return item;
// иначе преобразуем содержимое скобки в ее значение
return resCalc(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){
// если оператор
// тут надо еще сделать проверку на наличие обработчика оператора
_calc(src, dst);
}else{
// если все остальное
dst.push(item);
}
}
src = dst;
});
return src[0];
}
console.log("\n\nresult:\n",JSON.stringify(resCalc(list), null, ' '));
"x > 5 && (y == 7.5) || sin(PI*a/360) || isNumber(y) && true "
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"]
};
function subSplit(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, ' '));
// тут я не придумал ничего умнее :)))
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) } }
};
// это набор разрешенных функций
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) } }
};
// это набор разрешенных переменных
var ALLOWED_VARIABLES = {
"PI": Math.PI,
"x": 6,
"y": 7.5,
"a": 180
};
// парсер результатов разбора строки
function resCalc(src){
// заменяем переменные на их значения
src = src.map(item=>{
// если не переменная возвращаем как есть
if(item.type !== "variable")
return item;
// иначе преобразуем переменную в ее значение
// тут надо еще сделать проверку на наличие переменной в списке разрешенных
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"){
// если функция
// тут надо еще сделать проверку на наличие функции в списке разрешенных
dst.push(ALLOWED_FUNCTIONS[item.name]( resCalc(item.body).body ) );
}else if(item.type === "bracket"){
// если скобки
dst.push( resCalc(item.body).body );
}else if(item.type === "operator" && item.body === operator){
// если оператор
// тут надо еще сделать проверку на наличие обработчика оператора
_calc(src, dst);
}else{
// если все остальное
dst.push(item);
}
}
src = dst;
});
return src[0];
}
console.log("\n\nresult:\n",JSON.stringify(resCalc(list), null, ' '));
[
{ "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 }
]
{
"type": "boolean",
"body": true
}