205 lines
5 KiB
JavaScript
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())
|
|
})
|
|
})
|
|
|