diff --git a/src/combinators.ts b/src/combinators.ts index 8b6bc0d..f8d1caf 100644 --- a/src/combinators.ts +++ b/src/combinators.ts @@ -8,3 +8,4 @@ export * from './combinators/optional' export * from './combinators/sepBy' export * from './combinators/sequence' export * from './combinators/take' +export * from './combinators/until' diff --git a/src/combinators/until.ts b/src/combinators/until.ts new file mode 100644 index 0000000..14db643 --- /dev/null +++ b/src/combinators/until.ts @@ -0,0 +1,76 @@ +import { type Parser } from '../state' + +export function takeUntil(parser: Parser, terminator: Parser): Parser<[Array, S]> { + return { + parse(input, pos) { + const values: Array = [] + let nextPos = pos + + while (true) { + const resultT = terminator.parse(input, nextPos) + + switch (resultT.isOk) { + // If ok, then we stumbled upon a terminating parser, so push final matches, and then + // return accumulated values. + case true: { + return { + isOk: true, + pos: resultT.pos, + value: [values, resultT.value] + } + } + + // Otherwise try to run source parser and push results into `values`. + // If it fails, then return early and stop parsing. + case false: { + const resultP = parser.parse(input, nextPos) + + if (resultP.isOk) { + values.push(resultP.value) + nextPos = resultP.pos + continue + } + + return resultP + } + } + } + } + } +} + +export function skipUntil(parser: Parser, terminator: Parser): Parser { + return { + parse(input, pos) { + let nextPos = pos + + while (true) { + const resultT = terminator.parse(input, nextPos) + + switch (resultT.isOk) { + // If ok, then we stumbled upon a terminating parser, so return its value. + case true: { + return { + isOk: true, + pos: resultT.pos, + value: resultT.value + } + } + + // Otherwise try to run source parser *ignoring* its results. + // If it fails, then return early and stop parsing. + case false: { + const resultP = parser.parse(input, nextPos) + + if (resultP.isOk) { + nextPos = resultP.pos + continue + } + + return resultP + } + } + } + } + } +}