export parser combinators

This commit is contained in:
Rowan 2025-04-16 08:56:05 -05:00
parent 2baf53640d
commit 28e213d5c2
2 changed files with 19 additions and 16 deletions

View file

@ -0,0 +1,2 @@
export * from './parser.js'

View file

@ -1,6 +1,6 @@
import { Ok, Err, curry } from '../vendor/kojima/src/index.js' import { Ok, Err, curry } from '../vendor/kojima/src/index.js'
class ParseError extends Error { export class ParseError extends Error {
constructor(message, state, source) { constructor(message, state, source) {
super(message) super(message)
this.state = state this.state = state
@ -44,15 +44,15 @@ const fork = ([tokens, state]) => {
) )
} }
const succeed = (v, [x, y]) => Ok(Tuple(x.concat(v), y)) export const succeed = (v, [x, y]) => Ok(Tuple(x.concat(v), y))
const fail = (msg, state, err = undefined) => Err(new ParseError(msg, state, err)) export const fail = (msg, state, err = undefined) => Err(new ParseError(msg, state, err))
const nth = (n, iter) => iter[n] const nth = (n, iter) => iter[n]
const next = state => nth(1, state).next().value const next = state => nth(1, state).next().value
const diff = (a, b) => b.slice(-Math.max(0, b.length - a.length)) const diff = (a, b) => b.slice(-Math.max(0, b.length - a.length))
const join = curry((delim, val) => val.join(delim)) const join = curry((delim, val) => val.join(delim))
const mapStr = curry((fn, str) => Array.from(str).map(v => fn(v))) const mapStr = curry((fn, str) => Array.from(str).map(v => fn(v)))
const any = (...parsers) => state => { export const any = (...parsers) => state => {
for (const parser of parsers) { for (const parser of parsers) {
const [original, clone] = fork(state) const [original, clone] = fork(state)
const result = parser(clone) const result = parser(clone)
@ -64,11 +64,11 @@ const any = (...parsers) => state => {
return fail('no matching parsers', state) return fail('no matching parsers', state)
} }
const anyOf = curry((str, state) => ( export const anyOf = curry((str, state) => (
any(...mapStr(char, str))(state) any(...mapStr(char, str))(state)
)) ))
const seq = (...parsers) => state => { export const seq = (...parsers) => state => {
let acc = Ok(state) let acc = Ok(state)
for (const parser of parsers) { for (const parser of parsers) {
@ -82,7 +82,7 @@ const seq = (...parsers) => state => {
return acc return acc
} }
const map = curry((fn, parser, state) => { export const map = curry((fn, parser, state) => {
return parser(state).bind(result => { return parser(state).bind(result => {
try { try {
const parsed = diff(state[0], result[0]) const parsed = diff(state[0], result[0])
@ -94,32 +94,33 @@ const map = curry((fn, parser, state) => {
}) })
}) })
const char = curry((ch, state) => ( export const char = curry((ch, state) => (
next(state) === ch ? succeed(ch, state) : fail(`could not parse ${ch} `, state) next(state) === ch ? succeed(ch, state) : fail(`could not parse ${ch} `, state)
)) ))
const anyChar = state => { export const anyChar = state => {
const ch = next(state) const ch = next(state)
return !!ch ? succeed(ch, state) : fail(`could not parse ${ch}`, state) return !!ch ? succeed(ch, state) : fail(`could not parse ${ch}`, state)
} }
const string = curry((str, state) => ( export const str = curry((str, state) => (
map( map(
join(''), join(''),
seq(...mapStr(char, str)) seq(...mapStr(char, str))
)(state) )(state)
)) ))
const digit = anyOf(Digits) export const digit = anyOf(Digits)
const lowerAlpha = anyOf(LowerAlpha) export const lowerAlpha = anyOf(LowerAlpha)
const upperAlpha = anyOf(UpperAlpha) export const upperAlpha = anyOf(UpperAlpha)
const alpha = anyOf(Alpha) export const alpha = anyOf(Alpha)
const alphanumeric = anyOf(Alphanumeric) export const alphanumeric = anyOf(Alphanumeric)
const maybe = curry((parser, state) => { export const maybe = curry((parser, state) => {
const [original, clone] = fork(state) const [original, clone] = fork(state)
const result = parser(clone) const result = parser(clone)
return result.isOk ? result : succeed([], original) return result.isOk ? result : succeed([], original)
}) })
export const parse = curry((parser, input) => parser(State(input)))