Reputation: 855
I have two variables
var sequence = '(1 AND 2) OR (2 AND 3)'
var filters =[{
"name": "title",
"opr": "=",
"value": "My Post",
"sid": "1"
},
{
"name": "content",
"opr": "like",
"value": "Hello",
"sid": "2"
},
{
"name": "id",
"opr": "gt",
"value": "100",
"sid": "3"
}
]
now I am looking for to replace sequence and convert it into the format like
var output = {
"OR": [{
"AND": [{
"title": "My Post"
},
{
"content": {
"like": "Hello"
}
}
]
},
{
"AND": [{
"content": {
"like": "Hello"
}
},
{
"id": {
"gt": "1000"
}
}
]
}
]
}
}
For Sequence String each number represent sid from array of object And for opr from array of object if opr is equals to "=" it should create simple key value pair object like
{"title": "My Post"}
and for other operators it should create like
{"content": {"like": "Hello"}}
Upvotes: 0
Views: 75
Reputation: 372
You want to create a parser.
Your use case may vary and change the trade-offs that you want to make, but here is a naive implementation that accomplishes what you've outlined above.
const SEQUENCE = "(1 AND 2) OR (2 AND 3)";
const FILTERS = [{
name: "title",
opr: "=",
value: "My Post",
sid: "1"
},
{
name: "content",
opr: "like",
value: "Hello",
sid: "2"
},
{
name: "id",
opr: "gt",
value: "1000",
sid: "3"
}
];
const result = compiler(SEQUENCE, FILTERS);
console.log(result);
function compiler(sequence, filters) {
let tokens = _tokenizer(sequence);
let ast = _parser(tokens);
let newAst = _transformer(ast);
let output = _generator(newAst, filters, {});
return output;
}
//////////////////////////
function _tokenizer(input) {
let current = 0;
let tokens = [];
let expr = /^[(].*[)]$/;
let needsParens;
let parenCounter;
[].forEach.call(input, function(char, index) {
if (char === '(') parenCounter = (parenCounter == null) ? 1 : parenCounter + 1;
if (char === ')') --parenCounter;
if (parenCounter === 0 && index != input.length - 1) needsParens = true;
});
if (!expr.test(input) || needsParens) input = "(" + input + ")";
while (current < input.length) {
let char = input[current];
if (char === '(') {
tokens.push({
type: 'paren',
value: '(',
});
current++;
continue;
}
if (char === ')') {
tokens.push({
type: 'paren',
value: ')',
});
current++;
continue;
}
let WHITESPACE = /\s/;
if (WHITESPACE.test(char)) {
current++;
continue;
}
let NUMBERS = /[0-9]/;
if (NUMBERS.test(char)) {
let value = '';
while (NUMBERS.test(char)) {
value += char;
char = input[++current];
}
tokens.push({
type: 'number',
value
});
continue;
}
let LETTERS = /[A-Z]/i;
if (LETTERS.test(char)) {
let value = '';
while (LETTERS.test(char)) {
value += char;
char = input[++current];
}
tokens.push({
type: 'name',
value
});
continue;
}
throw new TypeError('Unknown token: ' + char);
}
return tokens;
}
//////////////////////////
//////////////////////////
function _parser(tokens) {
let current = 0;
function walk() {
let token = tokens[current];
if (token.type === 'number') {
current++;
return {
type: 'NumberLiteral',
value: token.value,
};
}
if (token.type === 'name') {
current++;
return {
type: 'NameLiteral',
value: token.value,
};
}
if (token.type === 'paren' && token.value === '(') {
token = tokens[++current];
let node = {
type: 'Expression',
params: [],
};
while ((token.type !== 'paren') || (token.type === 'paren' && token.value !== ')')) {
node.params.push(walk());
token = tokens[current];
}
current++;
return node;
}
throw new TypeError(token.type);
}
let ast = {
type: 'Program',
body: [],
};
while (current < tokens.length) {
ast.body.push(walk());
}
return ast;
}
//////////////////////////
//////////////////////////
function _traverser(ast, visitor) {
function _traverseArray(array, parent) {
array.forEach(child => {
_traverseNode(child, parent);
});
}
function _traverseNode(node, parent) {
let methods = visitor[node.type];
if (methods && methods.enter) methods.enter(node, parent);
switch (node.type) {
case 'Program':
_traverseArray(node.body, node);
break;
case 'Expression':
_traverseArray(node.params, node);
break;
case 'NumberLiteral':
case 'NameLiteral':
break;
default:
throw new TypeError(node.type);
}
if (methods && methods.exit) methods.exit(node, parent);
}
_traverseNode(ast, null);
}
//////////////////////////
//////////////////////////
function _transformer(ast) {
let newAst = {
type: 'Program',
body: [],
};
ast._context = newAst.body;
_traverser(ast, {
NumberLiteral: {
enter(node, parent) {
parent._context.push({
type: 'NumberLiteral',
value: node.value,
});
},
},
NameLiteral: {
enter(node, parent) {
parent._expression.bool = node.value;
},
},
Expression: {
enter(node, parent) {
let expression = {
type: 'Expression',
arguments: [],
};
node._expression = expression;
node._context = expression.arguments;
parent._context.push(expression);
},
}
});
return newAst;
}
//////////////////////////
//////////////////////////
function _generator(node, filters, map) {
if (filters) filters.forEach(filter => map[filter.sid] = filter);
switch (node.type) {
case 'Program':
return JSON.parse(_generator(node.body[0], null, map));
case 'Expression':
return '{"' + node.bool + '":[' + node.arguments.map(arg => _generator(arg, null, map)) + ']}';
case 'NumberLiteral':
let filter = map[node.value];
if (filter.opr === '=') return '{"' + filter.name + '":"' + filter.value + '"}';
return '{"' + filter.name + '":{"' + filter.opr + '":"' + filter.value + '"}}';
default:
throw new TypeError(node.type);
}
}
//////////////////////////
This is based off of James Kyle's Super Tiny Compiler.
I've removed all of the comments for brevity and have made a decent amount of edits to fit your requirements. You should definitely take a look at that repo though if you want to understand how this works or to have any chance of debugging or extending it in the future.
From a high level, this is what is happening.
Once again, can't recommend James Kyle's Super Tiny Compiler enough.
Upvotes: 1