Skip to content

Instantly share code, notes, and snippets.

@alanrsoares
Last active November 15, 2016 00:07
Show Gist options
  • Save alanrsoares/2306ac9105ad18e5cd70bde056488b92 to your computer and use it in GitHub Desktop.
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
/*
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