kuebiko/tests/combinators.test.js
2025-07-02 11:13:04 -04:00

205 lines
5 KiB
JavaScript

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())
})
})