improving combinators

This commit is contained in:
Rowan 2025-04-17 02:54:10 -05:00
parent 170e4b2ae6
commit ffbb58b95e
7 changed files with 54 additions and 26 deletions

View file

@ -1,5 +1,5 @@
import { State } from './state.js'
import { anyOf, map } from './combinator.js'
import { any, anyOf, map } from './combinator.js'
import { Alpha, Alphanumeric, Digits, LowerAlpha, UpperAlpha } from './const.js'
import { fail, join, mapStr, next, succeed } from './fn.js'
import { seq } from './seq.js'
@ -36,6 +36,6 @@ export const anyChar = state => {
export const digit = anyOf(Digits)
export const lowerAlpha = anyOf(LowerAlpha)
export const upperAlpha = anyOf(UpperAlpha)
export const alpha = anyOf(Alpha)
export const alphanumeric = anyOf(Alphanumeric)
export const alpha = any(lowerAlpha, upperAlpha)
export const alphanumeric = any(alpha, digit)

View file

@ -1,5 +1,5 @@
import { char } from './char.js'
import { diff, fail, fork, mapStr, succeed, Tuple } from './fn.js'
import { clone, diff, fail, mapStr, succeed, Tuple } from './fn.js'
import { curry } from '../vendor/izuna/src/curry.js'
/** @import { ParserState } from './state.js' */
@ -13,9 +13,8 @@ export const any = (...parsers) =>
*/
state => {
for (const parser of parsers) {
const [original, clone] = fork(state)
const result = parser(clone)
if (result.isOk) {
const result = parser(clone(state))
if (result.isOk()) {
return result
}
}
@ -53,3 +52,7 @@ export const map = curry(
})
})
export const eof = state => {
return clone(state).next().done ? succeed([], state) : fail('not end of stream', state)
}

View file

@ -1,8 +1,8 @@
import { ParseError } from './state.js'
import { fail, fork, succeed } from './fn.js'
import { clone, fail, fork, succeed } from './fn.js'
import { anyChar } from './char.js'
import { ok } from '../vendor/kojima/src/index.js'
import { curry } from '../vendor/izuna/src/index.js'
import { curry, pipe } from '../vendor/izuna/src/index.js'
/** @import { Result } from '../vendor/kojima/src/index.js' */
/** @import { ParserState } from './state.js' */
@ -13,19 +13,17 @@ export const maybe = curry(
* @param {ParserState} state
*/
(parser, state) => {
const [original, clone] = fork(state)
const result = parser(clone)
return result.isOk() ? result : succeed([], original)
const result = parser(clone(state))
return result.isOk() ? result : succeed([], state)
})
export const not = curry((parser, state) => {
const [original, clone] = fork(state)
const result = parser(clone)
const result = parser(clone(state))
if (result.isOk()) {
return fail('"not" parser failed', original)
return fail(`'not' parser failed for ${parser.name}`, state)
} else {
return succeed([], original)
return succeed([], state)
}
})
@ -33,12 +31,12 @@ export const until = curry((parser, state) => {
let result = ok(state)
while (result.isOk()) {
const [original, clone] = result.chain(fork)
result = result.chain(x => parser(clone))
console.log(parser.name, state)
result = result.chain(pipe(clone, parser))
if (result.isOk()) {
break
} else {
result = anyChar(original)
result = anyChar(state)
}
}

View file

@ -48,6 +48,11 @@ export const fork = ([tokens, state]) => {
)
}
/**
* @param {ParserState} state
*/
export const clone = ([h, iter]) => [h, iter.clone()]
/**
* @template T
* @param {T | T[]} v

View file

@ -5,12 +5,15 @@
*/
export class Iter {
_iterator
_source
/**
* @param {Iterator<T>} iterator
* @param {Iterabl<T>} [source]
*/
constructor(iterator) {
constructor(iterator, source) {
this._iterator = iterator
this._source = source
}
/**
@ -38,12 +41,20 @@ export class Iter {
return iterator
}
return new Iter(iterator)
return new Iter(iterator, value)
}
throw new TypeError('object is not an iterator')
}
clone() {
if (this._source) {
return Iter.from(this._source)
}
throw new Error('Cannot clone Iterator: not created from an iterable')
}
/**
* @param {any} [value]
*/

View file

@ -1,4 +1,4 @@
import { diff, fail, fork, succeed, Tuple } from './fn.js'
import { clone, diff, fail, 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'
@ -56,8 +56,7 @@ export const many = curry((parser, state) => {
let result = ok(state)
while (true) {
const [original, clone] = result.chain(fork)
const res = parser(clone)
const res = parser(clone(state))
if (res.isOk()) {
result = res
} else {
@ -66,5 +65,7 @@ export const many = curry((parser, state) => {
}
return result
})
export const many1 = parser => seq(parser, many(parser))

View file

@ -1,5 +1,8 @@
import { Iter } from './iter.js'
import { curry } from '../vendor/izuna/src/curry.js'
import { curry, pipe } from '../vendor/izuna/src/index.js'
import { seq } from './seq.js'
import { until } from './cond.js'
import { eof } from './combinator.js'
/**
* @typedef {Readonly<[any[], Iterator<any>]>} ParserState
*/
@ -23,4 +26,11 @@ export class ParseError extends Error {
export const State = value => Object.freeze([[], Iter.from(value)])
export const parse = curry((parser, input) => parser(State(input)))
export const parseAll = curry((parser, input) => pipe(
State,
seq(
parser,
until(eof)
)
)(input))