import { flow, pipe } from 'fp-ts/function' import * as Either from 'fp-ts/Either' import * as Option from 'fp-ts/Option' import { fst, mapFst, mapSnd, snd } from 'fp-ts/Tuple' import { eq } from '../utils' import { flatten, prepend } from 'fp-ts/Array' export type char = string export type ParserState = [T, string] export type ParserError = [string, string] export type ParserResult = Either.Either> export type Parser = (input: string) => ParserResult export const constP = (v: T): Parser => (inp: string) => Either.right([v, inp]) export const sepBy1 = (sep: Parser, parser: Parser): Parser => flow( parser, Either.chain(([val, nextInput]) => pipe( nextInput, many0(prefixed(sep, parser)), Either.map(mapFst(prepend(val))), ), ), ) export const not = (parser: Parser): Parser => (input: string) => pipe( input, parser, Either.fold( ([_left, inp]) => Either.right([null as never, inp]), ([_right, inp]) => Either.left(['`not` parser failed. Match found', inp]) ), ) export const manyTill = (parser: Parser, end: Parser): Parser => { const p = pair(many0(suffixed(parser, not(end))), suffixed(parser, end)) return mapTo(p, ([xs, l]) => xs.concat([l])) } export const many0 = (parser: Parser): Parser> => flow( parser, Either.chain(([a, nextInput]) => pipe(nextInput, many0(parser), Either.map(mapFst(prepend(a)))), ), Either.orElse( flow( mapFst(_ => [] as T[]), Either.right, ), ), ) export const many1 = (parser: Parser): Parser> => recoverInput( flow( many0(parser), Either.chain(([res, inp]) => res.length > 0 ? Either.right([res, inp]) : Either.left([`many1 failed to parse at '${inp}'`, inp]), ), ), ) const recoverInput = (p: Parser): Parser => (input: string) => pipe( input, p, Either.orElseW( flow( mapSnd(_ => input), Either.left, ), ), ) export const prefixed = (a: Parser, b: Parser): Parser => recoverInput(mapTo(pair(a, b), snd)) export const suffixed = (a: Parser, b: Parser): Parser => recoverInput(mapTo(pair(a, b), fst)) export const delimited = ( p: Parser, a: Parser, s: Parser, ): Parser => suffixed(prefixed(p, a), s) export const satisfyChar = (f: (c: char) => boolean): Parser => (input: string) => { if (input.length === 0) return Either.left(['Unexpected end of input', input]) const c = input.charAt(0) if (f(c)) return Either.right([c, input.slice(1)]) return Either.left([`Expected to satisfy ${f}, got "${c}"`, input]) } export const digit = satisfyChar(c => /^[0-9]$/g.test(c)) export const digits: Parser = flow( many1(digit), Either.map(mapFst(ds => ds.join(''))), ) export const or = (parsers: Parser[]): Parser => { const run = ([p, ...ps]: Parser[]) => flow( p, Either.orElse(([_, inp]) => or(ps)(inp)), ) return parsers.length > 0 ? run(parsers) : (inp: string) => Either.left(['unable to match', inp]) } export const matchChar = (ch: char): Parser => satisfyChar(eq(ch)) export const matchString = (s: string): Parser => (input: string) => input.slice(0, s.length) === s ? Either.right([s, input.slice(s.length)]) : Either.left([`Expected ${s} but got ${input.slice(0, 1)}`, input]) export const oneOf = (xs: string[]): Parser => or(xs.map(matchString)) export const whitespace = oneOf([' ', '\n', '\t']) export const whitespaces0 = many0(whitespace) export const symbol = (s: string): Parser => delimited(whitespaces0, matchString(s), whitespaces0) export const mapTo = (p: Parser, f: (p: I) => R): Parser => flow(p, Either.map(mapFst(f))) export const andThen = (f: (p: I) => Parser) => (p: Parser): Parser => flow( p, Either.chain(([v, inp]) => f(v)(inp)), ) export const optional = (p: Parser): Parser> => flow( recoverInput(p), Either.fold( flow( mapFst(_ => Option.none), Either.right, ), flow(mapFst(Option.some), Either.right), ), ) export const pair = (a: Parser, b: Parser): Parser<[A, B]> => recoverInput( pipe( a, andThen(ra => mapTo(b, rb => [ra, rb])), ), ) export const tuple3 = ( a: Parser, b: Parser, c: Parser, ): Parser<[A, B, C]> => recoverInput(mapTo(pair(pair(a, b), c), ([[a, b], c]) => [a, b, c]))