diff --git a/src/char.js b/src/char.js index 50b23fc..504ce29 100644 --- a/src/char.js +++ b/src/char.js @@ -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) diff --git a/src/cond.js b/src/cond.js index 9b32d8d..9d7b76b 100644 --- a/src/cond.js +++ b/src/cond.js @@ -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 +}) + diff --git a/src/fn.js b/src/fn.js index 82dbc88..9603067 100644 --- a/src/fn.js +++ b/src/fn.js @@ -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'* / diff --git a/src/index.js b/src/index.js index a797e01..0c0c84a 100644 --- a/src/index.js +++ b/src/index.js @@ -4,3 +4,7 @@ export * from './cond.js' export * from './seq.js' export * from './state.js' +console.log( + parse(until(not(char('!'))), '!!!!!!!a!!!!') +) + diff --git a/src/seq.js b/src/seq.js index 1a8c998..de71fbe 100644 --- a/src/seq.js +++ b/src/seq.js @@ -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} 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 + +}) diff --git a/src/state.js b/src/state.js index 071fcbe..5e8840b 100644 --- a/src/state.js +++ b/src/state.js @@ -1,4 +1,5 @@ import { Iter } from './iter.js' +import { curry } from '../vendor/izuna/src/curry.js' /** * @typedef {Readonly<[any[], Iterator]>} 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))) +