import { drop, skip, literal, bind, seq, many, parse, parseSome, ParseError, EofError, map, alt, many1, optional } from '../src/parser.js' import { Err } from '../src/result.js' import { describe, it } from 'node:test' import assert from 'node:assert/strict' const eq = (a, b) => assert.deepStrictEqual(a, b) const parseErr = (i, desc) => new Err(new ParseError(i, desc)) const eof = (i = 0) => new Err(new EofError(i)) const streamState = stream => { return [stream.index, Array.from(stream.clone())] } describe('Combinators', () => { it('bind', () => { const a = literal('a') const b = literal('b') // simple parser const p = bind(a, () => b) let res = parseSome(p, 'ab') assert(res.isOk()) const [v1, r1] = res.unwrap() eq(v1, 'b') eq(streamState(r1), [2, []]) // complex parser const pab = bind(a, x => map(b, y => `${x}${y}`)) res = parseSome(pab, 'abc') const [v2, r2] = res.unwrap() assert(res.isOk()) eq(v2, 'ab') eq(streamState(r2), [2, ['c']]) // fail on first parser res = parseSome(p, 'xb') eq(res, parseErr(0, 'Value did not match predicate: x')) // fail on second parser res = parseSome(p, 'ax') eq(res, parseErr(1, 'Value did not match predicate: x')) // eof on first parser res = parseSome(p, '') eq(res, eof()) // eof on second parser res = parseSome(p, 'a') eq(res, eof()) }) it('map', () => { const p = map(literal('a'), char => char.toUpperCase()) let res = parseSome(p, 'abc') assert(res.isOk()) const [value, rest] = res.unwrap() eq(value, 'A') eq(streamState(rest), [1, ['b', 'c']]) res = parseSome(p, 'xyz') eq(res, parseErr(0, 'Value did not match predicate: x')) }) it('seq', () => { const p = seq(literal('a'), literal('b')) let res = parseSome(p, 'abc') assert(res.isOk()) const [value, rest] = res.unwrap() eq(value, ['a', 'b']) eq(streamState(rest), [2, ['c']]) res = parseSome(p, 'xbc') eq(res, parseErr(0, 'Value did not match predicate: x')) res = parseSome(p, 'axc') eq(res, parseErr(1, 'Value did not match predicate: x')) }) it('alt', () => { const p = alt(literal('a'), literal('b')) let res = parseSome(p, 'abc') assert(res.isOk()) const [v1, r1] = res.unwrap() eq(v1, 'a') eq(streamState(r1), [1, ['b', 'c']]) res = parseSome(p, 'bac') assert(res.isOk()) const [v2, r2] = res.unwrap() eq(v2, 'b') eq(streamState(r2), [1, ['a', 'c']]) res = parseSome(p, 'xyz') eq(res, parseErr(1, 'No parsers matched alt')) res = parseSome(alt(), 'abc') eq(res, parseErr(0, 'No parsers matched alt')) }) it('many', () => { const p = many(literal('a')) let res = parseSome(p, 'xyz') assert(res.isOk()) const [v1, r1] = res.unwrap() eq(v1, []) eq(streamState(r1), [0, ['x', 'y', 'z']]) res = parseSome(p, 'abc') assert(res.isOk()) const [v2, r2] = res.unwrap() eq(v2, ['a']) eq(streamState(r2), [1, ['b', 'c']]) res = parseSome(p, 'aaabc') assert(res.isOk()) const [v3, r3] = res.unwrap() eq(v3, ['a', 'a', 'a']) eq(streamState(r3), [3, ['b', 'c']]) res = parse(p, 'aaa') assert(res.isOk()) eq(res.unwrap(), ['a', 'a', 'a']) res = parseSome(p, '') assert(res.isOk()) const [v4, r4] = res.unwrap() eq(v4, []) eq(streamState(r4), [0, []]) }) it('many1', () => { const p = many1(literal('a')) let res = parseSome(p, 'abc') assert(res.isOk()) const [v1, r1] = res.unwrap() eq(v1, ['a']) eq(streamState(r1), [1, ['b', 'c']]) res = parseSome(p, 'aaab') assert(res.isOk()) const [v2, r2] = res.unwrap() eq(v2, ['a', 'a', 'a']) eq(streamState(r2), [3, ['b']]) res = parseSome(p, 'xyz') eq(res, parseErr(0, 'Value did not match predicate: x')) res = parseSome(p, '') eq(res, eof()) }) it('optional', () => { const p = optional(literal('a')) let res = parseSome(p, 'abc') assert(res.isOk()) const [v1, r1] = res.unwrap() eq(v1, 'a') eq(streamState(r1), [1, ['b', 'c']]) res = parseSome(p, 'xyz') assert(res.isOk()) const [v2, r2] = res.unwrap() eq(v2, undefined) eq(streamState(r2), [0, ['x', 'y', 'z']]) res = parseSome(p, '') assert(res.isOk()) const [v3, r3] = res.unwrap() eq(v3, undefined) eq(streamState(r3), [0, []]) }) it('drop', () => { const p = drop(1) let res = parseSome(p, 'abc') assert(res.isOk()) const [v1, r1] = res.unwrap() eq(v1, undefined) eq(streamState(r1), [1, ['b', 'c']]) res = parseSome(p, '') eq(res, eof()) }) it('skip', () => { const p = skip(literal('a')) let res = parseSome(p, 'abc') assert(res.isOk()) const [v1, r1] = res.unwrap() eq(v1, undefined) eq(streamState(r1), [1, ['b', 'c']]) res = parseSome(p, 'xyz') eq(res, parseErr(0, 'Value did not match predicate: x')) res = parseSome(p, '') eq(res, eof()) }) })