@masala/parser
Version:
Masala Parser
604 lines (518 loc) • 17 kB
JavaScript
import { describe, it, expect } from 'vitest'
import stream from '../../lib/stream/index'
import { F, C, N } from '../../lib/parsec/index'
describe('Parser Core Tests', () => {
it('expect val to be a nice shortcut', () => {
const parser = C.string('xyz')
const val = parser.val('xyz')
expect(val).toBe('xyz')
})
it('expect (map) to be accepted', () => {
expect(
C.char('a')
.map(function (a) {
return a + 'b'
})
.parse(stream.ofString('a'), 0)
.isAccepted(),
).toBe(true)
})
it('expect (map) to be rejected', () => {
expect(
C.char('a')
.map(function (a) {
return a + 'b'
})
.parse(stream.ofString('b'), 0)
.isAccepted(),
).toBe(false)
})
it('expect (map) to be return ab', () => {
expect(
C.char('a')
.map(function (a) {
return a + 'b'
})
.parse(stream.ofString('a'), 0).value,
).toBe('ab')
})
it('expect (map) to be return 5x8', () => {
const st = stream.ofString('5x8')
const combinator = N.integer()
.then(C.char('x').drop())
.then(N.integer())
.array()
.map((values) => values[0] * values[1])
expect(combinator.parse(st).value).toBe(40)
})
it('expect (flatMap) to be accepted', () => {
expect(
C.char('a')
.flatMap(function () {
return F.returns('b')
})
.parse(stream.ofString('a'), 0)
.isAccepted(),
).toBe(true)
})
it('expect (flatMap) to be rejected', () => {
expect(
C.char('a')
.flatMap(function () {
return F.returns('b')
})
.parse(stream.ofString('b'), 0)
.isAccepted(),
).toBe(false)
})
it('expect (flatMap) to be return ab', () => {
expect(
C.char('a')
.flatMap(function () {
return C.char('b')
})
.parse(stream.ofString('ab'), 0).value,
).toBe('b')
})
it('expect (filter) to be accepted', () => {
expect(
C.char('a')
.filter(function (a) {
return a === 'a'
})
.parse(stream.ofString('a'), 0)
.isAccepted(),
).toBe(true)
})
it('expect (filter) to be rejected', () => {
expect(
C.char('a')
.filter(function (a) {
return a === 'b'
})
.parse(stream.ofString('a'), 0)
.isAccepted(),
).toBe(false)
})
it('expect (match) to be accepted', () => {
expect(
C.char('a').match('a').parse(stream.ofString('a'), 0).isAccepted(),
).toBe(true)
})
it('expect (match) to be rejected', () => {
expect(
C.char('a').match('b').parse(stream.ofString('a'), 0).isAccepted(),
).toBe(false)
})
it('expect (then) to be accepted', () => {
expect(
C.char('a')
.then(C.char('b'))
.parse(stream.ofString('ab'), 0)
.isAccepted(),
).toBe(true)
})
it('expect (then) left to be rejected', () => {
expect(
C.char('a')
.then(C.char('b'))
.parse(stream.ofString('cb'), 0)
.isAccepted(),
).toBe(false)
})
it('expect (then) right to be rejected', () => {
expect(
C.char('a')
.then(C.char('b'))
.parse(stream.ofString('ac'), 0)
.isAccepted(),
).toBe(false)
})
it('expect (then) to return [a,b]', () => {
expect(
C.char('a')
.then(C.char('b'))
.array()
.parse(stream.ofString('ab'), 0).value,
).toEqual(['a', 'b'])
})
it('expect (then) to return [a,b,d]', () => {
expect(
C.char('a')
.then(C.char('b').then(C.char('c').drop()).then(C.char('d')))
.array()
.parse(stream.ofString('abcd'), 0).value,
).toEqual(['a', 'b', 'd'])
})
it('expect (then) to be empty with two drops', () => {
const parser = C.char('a').drop().then(C.char('b').drop())
const value = parser.parse(stream.ofString('ab')).value.value
expect(Array.isArray(value)).toBe(true)
expect(value.length).toBe(0)
})
it('expect (then) to be associative', () => {
const first = C.char('a')
.then(C.char('b'))
.then(C.char('c').drop())
.then(C.char('d'))
.array()
const second = C.char('a')
.then(C.char('b'))
.then(C.char('c').drop().then(C.char('d')))
.array()
expect(first.parse(stream.ofString('abcd')).value).toEqual(
second.parse(stream.ofString('abcd')).value,
)
})
it('expect (then) to be replaced by concat', () => {
expect(
C.char('a')
.concat(C.char('b'))
.then(C.char('c').drop())
.concat(C.char('d'))
.array()
.parse(stream.ofString('abcd'), 0).value,
).toEqual(['a', 'b', 'd'])
})
it('expect (thenLeft) to be accepted', () => {
expect(
C.char('a')
.thenLeft(C.char('b'))
.parse(stream.ofString('ab'), 0)
.isAccepted(),
).toBe(true)
})
it('expect (thenLeft) to return a', () => {
expect(
C.char('a')
.thenLeft(C.char('b'))
.single()
.parse(stream.ofString('ab'), 0).value,
).toBe('a')
})
it('expect (thenLeft) to be rejected', () => {
expect(
C.char('a')
.thenLeft(C.char('b'))
.parse(stream.ofString('b'), 0)
.isAccepted(),
).toBe(false)
})
it('expect (thenRight) to be accepted', () => {
expect(
C.char('a')
.thenRight(C.char('b'))
.parse(stream.ofString('ab'), 0)
.isAccepted(),
).toBe(true)
})
it('expect (thenRight) to return a', () => {
expect(
C.char('a')
.thenRight(C.char('b'))
.single()
.parse(stream.ofString('ab'), 0).value,
).toBe('b')
})
it('expect (thenRight) to be rejected', () => {
expect(
C.char('a')
.thenRight(C.char('b'))
.parse(stream.ofString('b'), 0)
.isAccepted(),
).toBe(false)
})
it('expect (drop/then) to be accepted', () => {
expect(
C.char('a')
.drop()
.then(C.char('b'))
.parse(stream.ofString('ab'), 0)
.isAccepted(),
).toBe(true)
})
it('expect (drop/then) to be return b', () => {
expect(
C.char('a')
.drop()
.then(C.char('b'))
.single()
.parse(stream.ofString('ab'), 0).value,
).toBe('b')
})
it('expect (then/drop) to be accepted', () => {
expect(
C.char('a')
.then(C.char('b').drop())
.parse(stream.ofString('ab'), 0)
.isAccepted(),
).toBe(true)
})
it('expect (drop/then) to be return a', () => {
expect(
C.char('a')
.then(C.char('b').drop())
.single()
.parse(stream.ofString('ab'), 0).value,
).toBe('a')
})
it('expect (eos) to be accepted at the end', () => {
const parser = C.string('abc').eos()
const response = parser.parse(stream.ofString('abc'))
expect(response.isAccepted()).toBe(true)
expect(response.isEos()).toBe(true)
expect(response.value).toBe('abc')
})
it('expect (eos) to be rejected without eating char', () => {
const parser = C.char('a').eos()
const response = parser.parse(stream.ofString('ab'))
expect(response.isAccepted()).toBe(false)
expect(response.isEos()).toBe(false)
expect(response.offset).toBe(1)
expect(response.value).toBeUndefined()
})
it('expect rejected (eos) to be rejected keeping previous offset', () => {
const parser = C.char('a').then(C.char('a')).eos()
const response = parser.parse(stream.ofString('ab is ending at 1'))
expect(response.isAccepted()).toBe(false)
expect(response.isEos()).toBe(false)
expect(response.offset).toBe(1)
expect(response.value).toBeUndefined()
})
it('expect (thenEos) to be accepted at the end', () => {
expect(
C.char('a').thenEos().parse(stream.ofString('a')).isAccepted(),
).toBe(true)
})
it('expect (thenEos) to be rejected if not the end', () => {
expect(
C.char('a').thenEos().parse(stream.ofString('abc')).isAccepted(),
).toBe(false)
})
it('expect (returns) to be accepted', () => {
expect(
C.char('a')
.returns('b')
.parse(stream.ofString('ab'), 0)
.isAccepted(),
).toBe(true)
})
it('expect (returns) to return b', () => {
expect(
C.char('a').returns('b').parse(stream.ofString('ab'), 0).value,
).toBe('b')
})
it('expect (returns) not to eat char', () => {
expect(
C.char('a')
.returns('X')
.then(C.char('b'))
.array()
.parse(stream.ofString('ab'), 0).value,
).toEqual(['X', 'b'])
})
it('expect (returns) to be rejected', () => {
expect(
C.char('a')
.returns('b')
.parse(stream.ofString('b'), 0)
.isAccepted(),
).toBe(false)
})
it('expect (or) to be accepted', () => {
expect(
C.char('a')
.or(C.char('b'))
.parse(stream.ofString('a'), 0)
.isAccepted(),
).toBe(true)
})
it('expect (or) to be rejected', () => {
expect(
C.char('a')
.or(C.char('b'))
.parse(stream.ofString('c'), 0)
.isAccepted(),
).toBe(false)
})
it('expect (or) LL(1) to be rejected', () => {
expect(
C.char('a')
.then(C.char('b'))
.or(C.char('a'))
.parse(stream.ofString('ac'), 0)
.isAccepted(),
).toBe(false)
})
it('expect (or) to return a', () => {
expect(
C.char('a').or(C.char('b')).parse(stream.ofString('a'), 0).value,
).toBe('a')
})
it('expect (or) to return b', () => {
expect(
C.char('a').or(C.char('b')).parse(stream.ofString('b'), 0).value,
).toBe('b')
})
it('expect (then.or) left to be rejected', () => {
expect(
C.char('a')
.then(C.char('b').or(C.char('c')))
.parse(stream.ofString('ad'), 0)
.isAccepted(),
).toBe(false)
})
it('expect (then.or) left to be consumed', () => {
expect(
C.char('a')
.then(C.char('b').or(C.char('c')))
.parse(stream.ofString('ad'), 0).consumed,
).toBe(true)
})
it('expect (opt) some to accepted', () => {
expect(
C.char('a').opt().parse(stream.ofString('a'), 0).isAccepted(),
).toBe(true)
})
it('expect (opt) some to return some a', () => {
expect(
C.char('a').opt().parse(stream.ofString('a'), 0).value.get(),
).toBe('a')
})
it('expect (opt) none to accepted', () => {
expect(
C.char('a').opt().parse(stream.ofString('b'), 0).isAccepted(),
).toBe(true)
})
it('expect (opt) none to return none', () => {
expect(
C.char('a').opt().parse(stream.ofString('b'), 0).value.isPresent(),
).toBe(false)
})
it('expect (rep) to accepted', () => {
expect(
C.char('a').rep().parse(stream.ofString('a'), 0).isAccepted(),
).toBe(true)
})
it('expect (rep) to rejected', () => {
expect(
C.char('a').rep().parse(stream.ofString('b'), 0).isAccepted(),
).toBe(false)
})
it('expect (rep) mutiple to accepted', () => {
expect(
C.char('a').rep().parse(stream.ofString('aaaabbb'), 0).isAccepted(),
).toBe(true)
})
it('expect (rep) mutiple to return [a,a,a,a]', () => {
expect(
C.char('a')
.rep()
.parse(stream.ofString('aaaabbb'), 0)
.value.array(),
).toEqual(['a', 'a', 'a', 'a'])
})
it('expect (optrep) to accepted', () => {
expect(
C.char('a').optrep().parse(stream.ofString('a'), 0).isAccepted(),
).toBe(true)
})
it('expect (optrep) none to accepted', () => {
expect(
C.char('a').optrep().parse(stream.ofString('b'), 0).isAccepted(),
).toBe(true)
})
it('expect (optrep) mutiple to accepted', () => {
expect(
C.char('a')
.optrep()
.parse(stream.ofString('aaaabbb'), 0)
.isAccepted(),
).toBe(true)
})
it('expect (optrep) mutiple to return some [a,a,a,a]', () => {
expect(
C.char('a')
.optrep()
.parse(stream.ofString('aaaabbb'), 0)
.value.array(),
).toEqual(['a', 'a', 'a', 'a'])
})
it('expect (optrep) to return none', () => {
expect(
C.char('a').optrep().parse(stream.ofString('bbb'), 0).value.array(),
).toEqual([])
})
it('expect (optrep) to return [b,b,b]', () => {
expect(
C.notChar('a')
.optrep()
.parse(stream.ofString('bbba'), 0)
.value.array(),
).toEqual(['b', 'b', 'b'])
})
it('expect two (optrep) to be merged as [b,b,b,a]', () => {
expect(
C.char('b')
.optrep()
.then(C.char('a').optrep())
.array()
.parse(stream.ofString('bbba'), 0).value,
).toEqual(['b', 'b', 'b', 'a'])
})
it('expect two (optrep) to return [[b,b,b],[a]] using array()', () => {
expect(
C.char('b')
.optrep()
.array()
.then(C.char('a').optrep().array())
.array()
.parse(stream.ofString('bbba'), 0).value,
).toEqual([['b', 'b', 'b'], ['a']])
})
it('expect debug() to make side effect', () => {
const original = console.log
let sideEffect = false
console.log = function () {
sideEffect = true
}
C.char('a').debug('found').optrep().parse(stream.ofString('aaa'), 0)
console.log = original
expect(sideEffect).toBe(true)
})
it('expect debug(param, false) to make side effect', () => {
const original = console.log
let sideEffect = false
console.log = function () {
sideEffect = true
}
C.char('a')
.debug('found', false)
.optrep()
.parse(stream.ofString('aaa'), 0)
console.log = original
expect(sideEffect).toBe(true)
})
it('expect (debug) to not make side effect', () => {
const original = console.log
let sideEffect = false
console.log = function () {
sideEffect = true
}
C.char('a').debug('found').optrep().parse(stream.ofString('xxxx'), 0)
console.log = original
expect(sideEffect).toBe(false)
})
it('joins a TupleParser resulting in a string', () => {
const st = stream.ofString('5x8')
let combinator = F.any().rep().join('')
expect(combinator.parse(st).value).toBe('5x8')
combinator = F.any().rep().join('-')
expect(combinator.parse(st).value).toBe('5-x-8')
})
it('fails with join() if it is not a TupleParser', () => {
const st = stream.ofString('5x8')
let combinator = F.any().rep().first().join('')
expect(() => combinator.parse(st)).toThrow()
})
})