Last active
November 15, 2016 00:07
-
-
Save alanrsoares/2306ac9105ad18e5cd70bde056488b92 to your computer and use it in GitHub Desktop.
A Tiny Polish Notation (Lisp-like) Arithmetic Parser Built in ES6 - live demo => http://jsbin.com/tuqoqaw/11/edit?js,console
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
/* | |
A Tiny Lisp Arithmetic Parser | |
---------------------------------- | |
Built with <3 by @alanrsoares | |
*/ | |
// functional helpers | |
const apply = f => (...xs) => xs.reverse().reduceRight(f) | |
const compose = (...fs) => (...args) => | |
(([g, ...gs]) => | |
gs.reduce((acc, h) => h(acc), g(...args)))(fs.reverse()) | |
// point-free functions | |
const keys = ::Object.keys | |
const map = f => xs => xs.map(f) | |
const join = sep => xs => xs.join(sep) | |
const split = sep => xs => xs.split(sep) | |
const OPERATIONS = { | |
'+': apply((a, b) => a + b), | |
'-': apply((a, b) => a - b), | |
'*': apply((a, b) => a * b), | |
'/': apply((a, b) => a / b), | |
'%': (a, b) => a % b, | |
'^': ::Math.pow, | |
// just to show that it's flexible enough to support custom operations | |
'sqrt': ::Math.sqrt | |
} | |
const escape = x => `/^+*`.includes(x) ? `\\${x}` : x | |
const buildOps = compose(join('|'), map(escape), keys) | |
const OPS = buildOps(OPERATIONS) | |
const EXPR = RegExp(`\\((${OPS})\\s+((\\s*(-)?(\\d+(\\.\\d+)?))+)\\)`) | |
const UNIT = /\((-?(\d+(\.\d+)?))\)/ | |
const WHITESPACE = /\s+/ | |
const toInt = x => +x | |
const evaluate = (x, ys) => OPERATIONS[x](...ys.split(WHITESPACE).map(toInt)) | |
const parseUnit = e => UNIT.test(e) ? e.match(UNIT).map(toInt)[1] : e | |
const parse = e => | |
((expr, operator, operands) => !expr | |
? parseUnit(e) | |
: toInt(parse(e.replace(expr, evaluate(operator, operands)))) | |
)(...(e.match(EXPR) || [])) | |
/* | |
--- LES TESTS --- | |
BONUS: micro test assertion and runner functions | |
*/ | |
const assert = (passed, message) => passed | |
? console.log('passed') || true | |
: console.error(message) || false | |
const assertEq = f => ([a, b]) => | |
(r => assert(r === b, `expected: ${b}, found: ${r}`))(f(a)) | |
const summary = xs => | |
xs.reduce((acc, x) => ({ | |
...acc, | |
passed: acc.passed + !!x, | |
failed: acc.failed + !!!x, | |
}), { passed: 0, failed: 0 }) | |
const runTests = (f, cases) => cases.map(assertEq(f)) | |
const testCases = [ | |
['(0)', 0], | |
['(-1)', -1], | |
['(-3.2)', -3.2], | |
['(0.0)', 0.0], | |
['(42)', 42], | |
['(sqrt 9)', 3], | |
['(sqrt 10.889999999999999)', 3.3], | |
['(sqrt 16)', 4], | |
['(sqrt 25)', 5], | |
['(^ 2 -1)', 0.5], | |
['(^ 2 2)', 4], | |
['(^ 3 3)', 27], | |
['(% 3 2)', 1], | |
['(% 4 2)', 0], | |
['(+ 1 1)', 2], | |
['(+ -1 1)', 0], | |
['(+ 10 0)', 10], | |
['(- 1 1)', 0], | |
['(* 2 2)', 4], | |
['(/ 2 2)', 1], | |
['(+ 5 (* (/ 2 2) 3))', 8], | |
['(+ 5 (* (/ 2 2) 3 4) 2)', 19], | |
['(/ (* 2 3 4) 2)', 12], | |
// forced assertion failure - big brother is watching you | |
['(+ 2 2)', 5], | |
] | |
function main () { | |
console.clear() | |
return compose(::console.log, summary, runTests)(parse, testCases) | |
} | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment