From ffbb58b95e18adeb849f976f78695a09c0e42048 Mon Sep 17 00:00:00 2001 From: rowan Date: Thu, 17 Apr 2025 02:54:10 -0500 Subject: [PATCH] improving combinators --- src/char.js | 6 +++--- src/combinator.js | 11 +++++++---- src/cond.js | 22 ++++++++++------------ src/fn.js | 5 +++++ src/iter.js | 15 +++++++++++++-- src/seq.js | 9 +++++---- src/state.js | 12 +++++++++++- 7 files changed, 54 insertions(+), 26 deletions(-) diff --git a/src/char.js b/src/char.js index e3acab3..89f1f6c 100644 --- a/src/char.js +++ b/src/char.js @@ -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) diff --git a/src/combinator.js b/src/combinator.js index afae4b7..1e519d3 100644 --- a/src/combinator.js +++ b/src/combinator.js @@ -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) +} + diff --git a/src/cond.js b/src/cond.js index a425bab..ac6436a 100644 --- a/src/cond.js +++ b/src/cond.js @@ -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) } } diff --git a/src/fn.js b/src/fn.js index 417c986..edd8bdd 100644 --- a/src/fn.js +++ b/src/fn.js @@ -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 diff --git a/src/iter.js b/src/iter.js index ce6aa7e..0ad05ce 100644 --- a/src/iter.js +++ b/src/iter.js @@ -5,12 +5,15 @@ */ export class Iter { _iterator + _source /** * @param {Iterator} iterator + * @param {Iterabl} [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] */ diff --git a/src/seq.js b/src/seq.js index f04fb43..04ef349 100644 --- a/src/seq.js +++ b/src/seq.js @@ -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)) + diff --git a/src/state.js b/src/state.js index 5e8840b..04557b6 100644 --- a/src/state.js +++ b/src/state.js @@ -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]>} 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))