import { Result } from './result.js' import { Iterator, Stream } from './stream.js' import { curry, join, of } from './fn.js' class ParseError extends Error { constructor(message, state, source) { super(message) this.state = state this.source = source } } /** * @typedef Iterator */ /** * @template T * @typedef {[T[], Stream]} ParserState */ /** * @typedef {Object} Peekable * @property {Iterator} iterator */ /** * @template T, E * @typedef {Object} Result * @property {T} value * @property {E} error */ /** * Create a ParserState from input * * @template T * @param {T[]} value - Any iterator or value which can become an iterator * @return {ParserState} - A new ParserState */ const ParserState = value => ([[], new Stream(value)]) const clone = ([a, b]) => ([a.slice(), b.clone()]) /** * Update ParserState with parsed tokens * * @template T * @param {T[]} - parsed tokens * @param {ParserState} * @return {Result} - Result.Ok with updated ParserState */ const succeed = curry((v, [p, rest]) => Result.Ok([p.concat(of(v)), rest])) /** * Indicate parser failure * * @template E * @param {string} - error message * @param {ParserState} * @param {E=} error * @return {Result} - Error with ParserState information and error source */ const fail = curry((msg, state, err = undefined) => Result.Err(new ParseError(msg, state, err))) const next = ([, rest]) => rest.next() // b.len - a.len // b.slice(-diff) //const difflen = result.value[0].length - parsed.length //console.log(difflen, result.value[0], result.value[0].slice(-difflen)) const diff = ([a], [b]) => b.slice(-Math.max(0, b.length - a.length)) const tokenize = str => str.split('') const tokenizeInto = curry((fn, str) => tokenize(str).map(v => fn(v))) const LowerAlpha = 'abcdefghijklmnopqrstuvwxyz' const UpperAlpha = LowerAlpha.toUpperCase() const Alpha = LowerAlpha + UpperAlpha const Digits = '1234567890' const Alphanumeric = Alpha + Digits export const seq = (...parsers) => state => { let acc = Result.Ok(state) for (const parser of parsers) { if (acc.isOk()) { acc = parser(acc.value) } else { break } } return acc } export const any = (...parsers) => (state) => { for (const parser of parsers) { const result = parser(clone(state)) if (result.isOk()) { return result } } return fail('no matching parsers', state) } export const many = curry((parser, state) => { let result = Result.Ok(state) while (true) { const res = parser(clone(result.value)) if (res.isOk()) { result = res } else { break } } return result }) export const skip = curry((parser, state) => { const [parsed] = state const result = parser(state) if (result.isOk()) { return Result.Ok([parsed, result.value[1]]) } else { return result } }) export const map = curry((fn, parser, state) => { const result = parser(state) if (result.isOk()) { const [parsed] = state const [, stream] = result.value const backtrack = [parsed, stream] try { const parsedValue = diff(state, result.value) return succeed(fn(parsedValue), backtrack) } catch (e) { return fail('map failed', state, e) } } return result }) export const char = curry((ch, state) => { return next(state) === ch ? succeed(ch, state) : fail(`could not parse ${ch}`, state) }) export const noCaseChar = curry((ch, state) => ( any(char(ch.toLowerCase()), char(ch.toUpperCase()))(state) )) export const string = curry((str, state) => map(join(''), seq(...tokenizeInto(char, str)))(state) ) export const anyChar = curry((str, state) => any(...tokenizeInto(char, str))(state) ) export const digit = anyChar(Digits) export const lowerAlpha = anyChar(LowerAlpha) export const upperAlpha = anyChar(UpperAlpha) export const alpha = anyChar(Alpha) export const alphanumeric = anyChar(Alphanumeric) export const noCaseString = curry((str, state) => ( seq(...tokenizeInto(noCaseChar, str))(state) )) export const maybe = curry((parser, state) => { const result = parser(state) return result.isOk() ? result : succeed([], state) }) export const eof = (state) => { return rest[1].done() ? succeed([], state) : fail('eof did not match', state) } export const parse = curry((parser, input) => parser(ParserState(input)))