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])) }