import { Stream } from './iterator.js' import { Result } from './result.js' import { curry, range } from './utils.js' import * as string from './string.js' export class ParseError extends Error { name index description constructor(index, description) { super() this.name = 'ParseError' this.index = index this.description = description } } export class EofError extends ParseError { name = 'EofError' description = 'End of stream reached' } // convenience factory functions and cached callbacks const eofErr = index => () => new EofError(index) const parseErr = (index, desc) => () => new ParseError(index, desc) const eq = a => b => a === b const toString = value => value.toString() export const pure = curry((value, input) => Result.ok([value, input])) export const fail = curry((error, input) => Result.err(new ParseError(input.index, error))) export const anyItem = () => input => { return input.peek().okOrElse(eofErr(input.index)) .map(value => [value, input.drop()]) } export const satisfy = curry((predicate, input) => { return input.peek() .okOrElse(eofErr(input.index)) .filterOrElse( predicate, value => parseErr(input.index, `Value did not match predicate: ${value}`) ) .map(value => [value, input.drop()]) }) export const literal = curry((value, input) => ( satisfy(eq(value), input) )) export const bind = curry((parser, transform, input) => parser(input).andThen(([value, rest]) => transform(value)(rest)) ) export const map = curry((parser, morphism, input) => ( parser(input).map(([value, rest]) => [morphism(value), rest]) )) export const seq = curry((a, b, input) => ( bind(a, x => map(b, y => [x, y]), input) )) export const alt = (...parsers) => input => { for (const p of parsers) { const result = p(input.clone()) if (result.isOk()) { return result } } return Result.err(new ParseError(input.index, "No parsers matched alt")) } export const many = curry((parser, input) => { const results = [] let stream = input while (true) { const result = parser(stream.clone()) if (result.isOk()) { const [value, rest] = result.unwrap() results.push(value) stream = rest } else { break } } return Result.ok([results, stream]) }) export const many1 = curry((parser, input) => ( parser(input).andThen(([first, rest]) => ( many(parser, rest).map(([others, rest]) => ( [[first, ...others], rest] )) )) )) export const optional = curry((parser, input) => parser(input.clone()).orElse(() => Result.ok([undefined, input])) ) export const eof = () => input => ( anyItem()(input) .andThen(([value, rest]) => Result.err( new ParseError(rest.index, `Expected EOF, found ${value}`) ) ) .orElse(err => { if (err instanceof EofError) { return Result.ok([null, input]) } else { return Result.err(err) } }) ) export const take = curry((limit, input) => { const result = [] const next = anyItem() let stream = input for (const _index of range(limit)) { const nextResult = next(stream) if (nextResult.isOk()) { const [value, rest] = nextResult.unwrap() result.push(value) stream = rest } else { return nextResult } } return Result.ok([result, stream]) }) export const drop = curry((limit, input) => skip(take(limit), input) ) export const skip = curry((parser, input) => parser(input).map(([_, rest]) => [undefined, rest]) ) const streamInfo = (stream, n = 3) => { const clone = stream.clone() const values = clone.take(n).map(toString).join(', ') const hasMore = clone.peek().isSome() const ellip = hasMore ? '...' : '' return `Stream { index = ${stream.index}, values = [${values}${ellip}]` } export const trace = curry((parser, label, input) => { const name = label || parser.name || 'anonymous parser' console.log(`trace(parser = ${name}, input = ${streamInfo(input)})`) const result = parser(input) if (result.isOk()) { const [value, rest] = result.unwrap() console.log(` success: value = ${toString(value)}, remaining = ${streamInfo(rest)}`) } else { const err = result.unwrapErr() console.log(` fail: error = ${err}`) } }) // cached parsers const LetterParser = alt(...Array.from(string.AsciiLetters).map(x => literal(x))) const DigitParser = alt(...Array.from(string.Digits).map(x => literal(x))) const WhitespaceParser = alt(...Array.from(string.Whitespace).map(x => literal(x))) export const letter = () => LetterParser export const digit = () => DigitParser export const whitespace = () => WhitespaceParser export const parseSome = curry((parser, value) => parser(new Stream(value)) ) export const parse = curry((parser, value) => { const result = parseSome(seq(parser, eof()), value) if (result.isOk()) { const [[value, _rest], _stream] = result.unwrap() return Result.ok(value) } else { return result } })