Skip to content

Instantly share code, notes, and snippets.

@aldy505
Created October 11, 2021 17:14
Show Gist options
  • Save aldy505/7fd862e506a5b6b2984e32ec4af7c832 to your computer and use it in GitHub Desktop.
Save aldy505/7fd862e506a5b6b2984e32ec4af7c832 to your computer and use it in GitHub Desktop.
go-jason!
const { test } = require('uvu')
const assert = require('uvu/assert')
const {main} = require('../src/parser')
test('1', () => {
const p = main('price lt 10000')
assert.equal(p, {
value: 'lt',
children: [
{
value: 'price'
},
{
value: 10000
}
]
})
})
test('2', () => {
const p = main("(category eq 'elektronik' or category eq 'buku') and price lt 500000");
assert.equal(p, {
value: 'and',
children: [
{
value: 'or',
children: [
{
value: 'eq',
children: [
{
value: 'category'
},
{
value: 'elektronik'
}
]
},
{
value: 'eq',
children: [
{
value: 'category'
},
{
value: 'buku'
}
]
}
]
},
{
value: 'lt',
children: [
{
value: 'price'
},
{
value: 500000
}
]
},
]
})
})
test('3', () => {
const p = main("((category eq 'musik' or category eq 'gaming') and price lt 300000 and is_new eq true) or ((category eq 'olahraga' or category eq 'otomotif') and price lt 500000 and is_new eq false)")
assert.equal(p, {
"children": [
{
"children": [
{
"children": [
{
"children": [
{
"children": [
{
"value": "category"
},
{
"value": "musik"
}
],
"value": "eq"
},
{
"children": [
{
"value": "category"
},
{
"value": "gaming"
}
],
"value": "eq"
}
],
"value": "or"
},
{
"children": [
{
"value": "price"
},
{
"value": 300000
}
],
"value": "lt"
}
],
"value": "and"
},
{
"children": [
{
"value": "is_new"
},
{
"value": true
}
],
"value": "eq"
}
],
"value": "and"
},
{
"children": [
{
"children": [
{
"children": [
{
"children": [
{
"value": "category"
},
{
"value": "olahraga"
}
],
"value": "eq"
},
{
"children": [
{
"value": "category"
},
{
"value": "otomotif"
}
],
"value": "eq"
}
],
"value": "or"
},
{
"children": [
{
"value": "price"
},
{
"value": 500000
}
],
"value": "lt"
}
],
"value": "and"
},
{
"children": [
{
"value": "is_new"
},
{
"value": false
}
],
"value": "eq"
}
],
"value": "and"
}
],
"value": "or"
})
})
test.run()
{
"type": "commonjs",
"scripts": {
"test:unit": "uvu test \".(test|spec).js\"",
"test:tdd": "npm run test:unit; watchlist src test -- npm run test:unit"
},
"devDependencies": {
"uvu": "^0.5.1",
"watchlist": "^0.3.1"
}
}
const { test } = require('uvu')
const assert = require('uvu/assert')
const {findParenPair} = require('../src/parser')
test('1', () => {
const f = findParenPair(['(', 'bla', 'a', ')']);
assert.equal(f, [[0, 3]])
})
test('2', () => {
const f = findParenPair(["(", "(", "category", "eq", "'elektronik'", ")", "or", "category", "eq", "'buku'", ")", "and", "price", "lt", "500000"])
assert.equal(f, [[0, 10], [1,5]])
})
test.run()
const { test } = require('uvu')
const assert = require('uvu/assert')
const {parse} = require('../src/parser')
test.skip('1', () => {
const p = parse(["(", "category", "eq", "'elektronik'", "or", "category", "eq", "'buku'", ")", "and", "price", "lt", "500000"])
assert.equal(p, {
value: 'and',
children: [
{
value: 'or',
children: [
{
value: 'eq',
children: [
{
value: 'category'
},
{
value: 'elektronik'
}
]
},
{
value: 'eq',
children: [
{
value: 'category'
},
{
value: 'buku'
}
]
}
]
},
{
value: 'lt',
children: [
{
value: 'price'
},
{
value: 500000
}
]
}
]
})
})
test.run()
// DISCLAIMER!
// This code doesn't work lol
/**
*
* @param {String[]} split
* @returns {Object}
*/
function parse(tokens) {
const stack = [];
const rpn = [];
for (const token of tokens) {
const prec = precedence(token);
if (prec === -1) {
if (token === "true") {
rpn.push(true);
} else if (token === "false") {
rpn.push(false);
} else if (token[0] === "'") {
rpn.push(token.substring(1, token.length - 1));
} else {
const float = parseFloat(token);
if (!isNaN(float)) {
rpn.push(float);
} else {
rpn.push(token);
}
}
} else {
switch (token) {
case "(":
stack.push(token);
break;
case ")":
while (stack.length > 0) {
const op = stack.pop();
if (op === "(") {
break;
} else {
rpn.push(op);
}
}
break;
default:
while (stack.length > 0 && prec <= precedence(stack[stack.length - 1])) {
rpn.push(stack.pop());
}
stack.push(token);
break;
}
}
}
while (stack.length > 0) {
const op = stack.pop();
if (op !== '(') {
rpn.push(op);
}
}
return rpn;
}
/**
*
* @param {String[]} rpn
*/
function _evaluate(rpn) {
const r = Object.create({});
let i = rpn.length;
while (i >= 0) {
const p = precedence(rpn[i])
if (p) {
r.value = convertToCorrectType(rpn[i])
r.children = [convertToCorrectType(evaluate(rpn.slice(0, i-2))), convertToCorrectType(evaluate(rpn.slice(0, i-1)))];
i -= 3;
continue;
} else {
r.value = convertToCorrectType(rpn[i]);
i -= 1;
continue;
}
}
return r
}
function evaluate(rpn) {
const operands = [];
while (rpn.length > 0) {
const token = rpn.shift();
const prec = precedence(token);
if (prec == -1) {
if (typeof token === "object") {
operands.push(token);
} else {
operands.push({
value: token
});
}
} else {
rpn.unshift({
value: token,
children: [operands.pop(), operands.pop()].reverse(),
});
}
}
return operands[0];
}
/**
*
* @param {String[]} arr
*/
function findParenPair(arr) {
const r = [];
const lastOpen = [];
for (let i = 0; i < arr.length; i++) {
if (arr[i] === "(") {
lastOpen.push([i, r.length])
r.push([i])
continue;
} else if (arr[i] === ')') {
if (lastOpen.length > 0) {
const last = lastOpen[lastOpen.length-1];
r[last[1]].push(i);
lastOpen.pop();
continue;
}
}
}
return r
}
/**
*
* @param {String} i
* @returns {String|Number|Boolean}
*/
function convertToCorrectType(i) {
if (/[0-9]+/.test(i)) {
return Number(i);
} else if (/(true|false)/.test(i)) {
return i === 'true' ? true : false;
} else if (/^\'(.+)\'$/.test(i)) {
return i.slice(1, i.length - 1);
}
return i
}
/**
*
* @param {String} filter
* @returns {String[]}
*/
function tokenize(filter) {
const tokens = [];
for (let i = 0; i < filter.length; i++) {
const char = filter[i];
switch (char) {
case "(":
case ")":
tokens.push(char);
break;
default:
let j;
for (j = i + 1; j < filter.length; j++) {
if (" ()".indexOf(filter[j]) !== -1) {
break;
}
}
tokens.push(filter.substring(i, j).trim());
i = j - 1;
break;
}
}
return tokens;
}
function precedence(op) {
switch (op) {
case "eq":
case "ne":
case "gt":
case "ge":
case "lt":
case "le":
return 2;
case "and":
case "or":
case "not":
return 1;
case "(":
case ")":
return 0;
default:
return -1;
}
}
function main(input) {
const t = tokenize(input)
const p = parse(t)
console.log(p);
return evaluate(p)
}
module.exports = { main, tokenize, parse, findParenPair }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment