add more combinators

This commit is contained in:
Rowan 2025-04-16 16:44:36 -05:00
parent 8323c9f6a1
commit 5152336bdc
6 changed files with 98 additions and 3 deletions

View file

@ -28,6 +28,11 @@ export const str = curry(
)(state)
))
export const anyChar = state => {
const ch = next(state)
return !!ch ? succeed(ch, state) : fail('end of input', state)
}
export const digit = anyOf(Digits)
export const lowerAlpha = anyOf(LowerAlpha)
export const upperAlpha = anyOf(UpperAlpha)

View file

@ -1,6 +1,7 @@
import { ParseError } from './state.js'
import { curry } from '../vendor/izuna/src/index.js'
import { fork, succeed } from './fn.js'
import { curry } from '../vendor/izuna/src/index.js'
import { anyChar } from './char.js'
/** @import { Result } from '../vendor/kojima/src/index.js' */
/** @import { ParserState } from './state.js' */
@ -16,3 +17,44 @@ export const maybe = curry(
return result.isOk() ? result : succeed([], original)
})
export const not = parser => state => {
const result = parser(state)
if (result.isOk()) {
return fail('"not" parser failed', state)
} else {
return succeed([], state)
}
}
export const until = parser => state => {
let acc = ok(state)
while (result.isOk()) {
const [original, clone] = fork(state)
const result = acc.chain(x => parser(clone))
if (result.isOk()) {
break
} else {
acc = anyChar(original)
}
}
return acc
}
export const many = curry((parser, state) => {
let result = ok(state)
while (true) {
const [original, clone] = fork(result)
const res = result.chain(x => parser(clone))
if (res.isOk()) {
result = res
} else {
break
}
}
return result
})

View file

@ -1,7 +1,7 @@
import { ParseError } from './state.js'
import { Iter } from './iter.js'
import { err, ok } from '../vendor/kojima/src/index.js'
import { curry } from '/vendor/izuna/src/index.js'
import { curry } from '../vendor/izuna/src/index.js'
/** @import { ParserState } from './state.js'* /

View file

@ -4,3 +4,7 @@ export * from './cond.js'
export * from './seq.js'
export * from './state.js'
console.log(
parse(until(not(char('!'))), '!!!!!!!a!!!!')
)

View file

@ -1,6 +1,7 @@
import { diff, fail, succeed, Tuple } from './fn.js'
import { diff, fail, fork, succeed, Tuple } from './fn.js'
import { ok } from '../vendor/kojima/src/index.js'
import { curry } from '../vendor/izuna/src/curry.js'
import { anyChar } from './char.js'
/** @import { ParseError, ParserState } from './state.js' */
/** @import { Result } from '../vendor/kojima/src/index.js' */
@ -9,6 +10,29 @@ import { curry } from '../vendor/izuna/src/curry.js'
* @typedef {(value: any) => Result<ParserState, ParseError>} Parser
*/
export const take = n => state => {
let result = anyChar(state)
for (let i = n; i > 0; i--) {
if (result.isErr()) {
return result.chain(e => fail(`"take(${n})" failed`, state, e))
}
result = result.chain(anyChar)
}
return result
}
export const skip = parser => state => {
const tokens = state[0]
const result = parser(state)
if (result.isOk()) {
return result.map(other => [tokens, other[1]])
} else {
return result
}
}
/**
* @param {...Parser} parsers
*/
@ -53,3 +77,20 @@ export const map = curry(
})
})
export const many = curry((parser, state) => {
let result = ok(state)
while (true) {
const [a, b] = fork(state)
const res = parser(clone(result.unwrap()))
if (res.isOk()) {
result = res
} else {
break
}
}
return result
})

View file

@ -1,4 +1,5 @@
import { Iter } from './iter.js'
import { curry } from '../vendor/izuna/src/curry.js'
/**
* @typedef {Readonly<[any[], Iterator<any>]>} ParserState
*/
@ -21,3 +22,5 @@ export class ParseError extends Error {
*/
export const State = value => Object.freeze([[], Iter.from(value)])
export const parse = curry((parser, input) => parser(State(input)))