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