Created
October 11, 2021 17:14
-
-
Save aldy505/7fd862e506a5b6b2984e32ec4af7c832 to your computer and use it in GitHub Desktop.
go-jason!
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"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" | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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