Basic monadic expression evaluator
🧩 Syntax:
// u = Unit = [parsed thing, remaining string]
// p = Parser = String => Unit
// A parser that consumes the match of a regex
const patt = patt => s => {
const m = s.match(patt)
return m && [m[0], s.substr(m[0].length)] }
// A parser that consume a fixed string
const str = str => s =>
s.startsWith(str) && [str, s.substr(str.length)]
// Tries an array of parsers in order until one works
const any = ps => s => {
for (let p of ps) {
const u = p(s)
if (u) { return u } } }
// Uses an array of parsers in sequence, all must work for truthy result
const all = ps => s => {
let u1 = ['', s]
for (let p of ps) {
const u2 = p(u1[1])
if (!u2) { return }
u1 = [u1[0] + u2[0], u2[1]] }
return u1 }
// Makes a parser optional: provides a null unit in case of failure
const optional = p => s => p(s) || ['', s]
// Apply a function to the left side of a unit
const uMap = (f, u) => [f(u[0]), u[1]]
// Apply a parser and then a function to the left side of the resulting unit
const pMap = (f, p) => s => uMap(f, p(s))
// Use a parser and then apply f if the parser is successful, used to chain
// parsers with complex rules
const bind = (p, f) => s => {
const u = p(s)
if (u) {
return f(u[0])(u[1]) } }
// Supply x to create something that looks like a parser where one is expected
const unit = x => s => [x, s]
// An expression evaluator built out of a variety of small parsers:
const pDigits = patt(/^\d+/)
const pInteger = all([optional(str('-')), pDigits])
const pDecimal = all([pInteger, str('.'), pDigits])
const pNum = bind(any([pDecimal, pInteger]), s => unit(parseFloat(s)))
const pWS = patt(/^\s+/) // whitespace parser
const pOWS = optional(pWS)
const operatorPs = ['+', '-', '*', '/'].map(str)
const pOperator = s =>
bind(pNum, l =>
bind(pOWS, _ =>
bind(any(operatorPs), op =>
bind(pOWS, _ =>
bind(pExpr, r => unit({op, l, r}))))))(s)
const pExpr = any([pOperator, pNum])
const pProg = s =>
bind(pOWS, _ =>
bind(pExpr, e =>
bind(pOWS, _ => unit(e))))(s)
const operator = {
'+': (l, r) => l + r,
'-': (l, r) => l - r,
'*': (l, r) => l * r,
'/': (l, r) => l / r }
const evaluate = x => {
switch (typeof x) {
case 'number': return x
case 'string': return operator[x]
case 'object': return operator[x.op](evaluate(x.l), evaluate(x.r)) } }
for (;;) {
let prog = prompt('Expression:')
if (!prog) { break }
let res = pProg(prog)
console.log(res && evaluate(res[0])) }