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 { 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 { Alpha, Alphanumeric, Digits, LowerAlpha, UpperAlpha } from './const.js'
import { fail, join, mapStr, next, succeed } from './fn.js' import { fail, join, mapStr, next, succeed } from './fn.js'
import { seq } from './seq.js' import { seq } from './seq.js'
@ -36,6 +36,6 @@ export const anyChar = state => {
export const digit = anyOf(Digits) export const digit = anyOf(Digits)
export const lowerAlpha = anyOf(LowerAlpha) export const lowerAlpha = anyOf(LowerAlpha)
export const upperAlpha = anyOf(UpperAlpha) export const upperAlpha = anyOf(UpperAlpha)
export const alpha = anyOf(Alpha) export const alpha = any(lowerAlpha, upperAlpha)
export const alphanumeric = anyOf(Alphanumeric) export const alphanumeric = any(alpha, digit)

View file

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

View file

@ -5,12 +5,15 @@
*/ */
export class Iter { export class Iter {
_iterator _iterator
_source
/** /**
* @param {Iterator<T>} iterator * @param {Iterator<T>} iterator
* @param {Iterabl<T>} [source]
*/ */
constructor(iterator) { constructor(iterator, source) {
this._iterator = iterator this._iterator = iterator
this._source = source
} }
/** /**
@ -38,12 +41,20 @@ export class Iter {
return iterator return iterator
} }
return new Iter(iterator) return new Iter(iterator, value)
} }
throw new TypeError('object is not an iterator') 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] * @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 { ok } from '../vendor/kojima/src/index.js'
import { curry } from '../vendor/izuna/src/curry.js' import { curry } from '../vendor/izuna/src/curry.js'
import { anyChar } from './char.js' import { anyChar } from './char.js'
@ -56,8 +56,7 @@ export const many = curry((parser, state) => {
let result = ok(state) let result = ok(state)
while (true) { while (true) {
const [original, clone] = result.chain(fork) const res = parser(clone(state))
const res = parser(clone)
if (res.isOk()) { if (res.isOk()) {
result = res result = res
} else { } else {
@ -66,5 +65,7 @@ export const many = curry((parser, state) => {
} }
return result return result
}) })
export const many1 = parser => seq(parser, many(parser))

View file

@ -1,5 +1,8 @@
import { Iter } from './iter.js' 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 * @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 State = value => Object.freeze([[], Iter.from(value)])
export const parse = curry((parser, input) => parser(State(input))) export const parse = curry((parser, input) => parser(State(input)))
export const parseAll = curry((parser, input) => pipe(
State,
seq(
parser,
until(eof)
)
)(input))