UNPKG

parser-ts

Version:

String parser combinators for TypeScript

635 lines (634 loc) 19 kB
var __assign = (this && this.__assign) || function () { __assign = Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; import * as A from 'fp-ts/es6/Array'; import { tailRec } from 'fp-ts/es6/ChainRec'; import * as E from 'fp-ts/es6/Either'; import { identity, pipe } from 'fp-ts/es6/function'; import * as NEA from 'fp-ts/es6/NonEmptyArray'; import * as O from 'fp-ts/es6/Option'; import { not } from 'fp-ts/es6/Predicate'; import * as RA from 'fp-ts/es6/ReadonlyArray'; import * as RNEA from 'fp-ts/es6/ReadonlyNonEmptyArray'; import { error, escalate, extend, success, withExpected } from './ParseResult'; import { atEnd, getAndNext } from './Stream'; // ------------------------------------------------------------------------------------- // constructors // ------------------------------------------------------------------------------------- /** * The `succeed` parser constructor creates a parser which will simply * return the value provided as its argument, without consuming any input. * * This is equivalent to the monadic `of`. * * @category constructors * @since 0.6.0 */ export var succeed = function (a) { return function (i) { return success(a, i, i); }; }; /** * The `fail` parser will just fail immediately without consuming any input * * @category constructors * @since 0.6.0 */ export var fail = function () { return function (i) { return error(i); }; }; /** * The `failAt` parser will fail immediately without consuming any input, * but will report the failure at the provided input position. * * @category constructors * @since 0.6.0 */ export var failAt = function (i) { return function () { return error(i); }; }; /** * The `sat` parser constructor takes a predicate function, and will consume * a single character if calling that predicate function with the character * as its argument returns `true`. If it returns `false`, the parser will * fail. * * @category constructors * @since 0.6.0 */ export var sat = function (predicate) { return pipe(withStart(item()), chain(function (_a) { var a = _a[0], start = _a[1]; return (predicate(a) ? of(a) : failAt(start)); })); }; // ------------------------------------------------------------------------------------- // combinators // ------------------------------------------------------------------------------------- /** * A parser combinator which returns the provided parser unchanged, except * that if it fails, the provided error message will be returned in the * ParseError`. * * @category combinators * @since 0.6.0 */ export var expected = function (p, message) { return function (i) { return pipe(p(i), E.mapLeft(function (err) { return withExpected(err, [message]); })); }; }; /** * The `item` parser consumes a single value, regardless of what it is, * and returns it as its result. * * @category combinators * @since 0.6.0 */ export var item = function () { return function (i) { return pipe(getAndNext(i), O.fold(function () { return error(i); }, function (_a) { var value = _a.value, next = _a.next; return success(value, next, i); })); }; }; /** * The `cut` parser combinator takes a parser and produces a new parser for * which all errors are fatal, causing either to stop trying further * parsers and return immediately with a fatal error. * * @category combinators * @since 0.6.0 */ export var cut = function (p) { return function (i) { return pipe(p(i), E.mapLeft(escalate)); }; }; /** * Takes two parsers `p1` and `p2`, returning a parser which will match * `p1` first, discard the result, then either match `p2` or produce a fatal * error. * * @category combinators * @since 0.6.0 */ export var cutWith = function (p1, p2) { return pipe(p1, apSecond(cut(p2))); }; /** * The `seq` combinator takes a parser, and a function which will receive * the result of that parser if it succeeds, and which should return another * parser, which will be run immediately after the initial parser. In this * way, you can join parsers together in a sequence, producing more complex * parsers. * * This is equivalent to the monadic `chain` operation. * * @category combinators * @since 0.6.0 */ export var seq = function (fa, f) { return function (i) { return pipe(fa(i), E.chain(function (s) { return pipe(f(s.value)(s.next), E.chain(function (next) { return success(next.value, next.next, i); })); })); }; }; /** * The `either` combinator takes two parsers, runs the first on the input * stream, and if that fails, it will backtrack and attempt the second * parser on the same input. Basically, try parser 1, then try parser 2. * * If the first parser fails with an error flagged as fatal (see `cut`), * the second parser will not be attempted. * * This is equivalent to the `alt` operation. * * @category combinators * @since 0.6.0 */ export var either = function (p, f) { return function (i) { var e = p(i); if (E.isRight(e)) { return e; } if (e.left.fatal) { return e; } return pipe(f()(i), E.mapLeft(function (err) { return extend(e.left, err); })); }; }; /** * Converts a parser into one which will return the point in the stream where * it started parsing in addition to its parsed value. * * Useful if you want to keep track of where in the input stream a parsed * token came from. * * @category combinators * @since 0.6.0 */ export var withStart = function (p) { return function (i) { return pipe(p(i), E.map(function (s) { return (__assign(__assign({}, s), { value: [s.value, i] })); })); }; }; /** * The `maybe` parser combinator creates a parser which will run the provided * parser on the input, and if it fails, it will returns the empty value (as * defined by `empty`) as a result, without consuming any input. * * @category combinators * @since 0.6.0 */ export var maybe = function (M) { return alt(function () { return of(M.empty); }); }; /** * Matches the end of the stream. * * @category combinators * @since 0.6.0 */ export var eof = function () { return expected(function (i) { return (atEnd(i) ? success(undefined, i, i) : error(i)); }, 'end of file'); }; /** * The `many` combinator takes a parser, and returns a new parser which will * run the parser repeatedly on the input stream until it fails, returning * a list of the result values of each parse operation as its result, or the * empty list if the parser never succeeded. * * Read that as "match this parser zero or more times and give me a list of * the results." * * @category combinators * @since 0.6.0 */ export var many = function (p) { return pipe(many1(p), alt(function () { return of([]); })); }; /** * The `many1` combinator is just like the `many` combinator, except it * requires its wrapped parser to match at least once. The resulting list is * thus guaranteed to contain at least one value. * * @category combinators * @since 0.6.0 */ export var many1 = function (parser) { return pipe(parser, chain(function (head) { return chainRec_(NEA.of(head), function (acc) { return pipe(parser, map(function (a) { return E.left(A.append(a)(acc)); }), alt(function () { return of(E.right(acc)); })); }); })); }; /** * Matches the provided parser `p` zero or more times, but requires the * parser `sep` to match once in between each match of `p`. In other words, * use `sep` to match separator characters in between matches of `p`. * * @category combinators * @since 0.6.0 */ export var sepBy = function (sep, p) { var nil = of([]); return pipe(sepBy1(sep, p), alt(function () { return nil; })); }; /** * Matches the provided parser `p` one or more times, but requires the * parser `sep` to match once in between each match of `p`. In other words, * use `sep` to match separator characters in between matches of `p`. * * @category combinators * @since 0.6.0 */ export var sepBy1 = function (sep, p) { return pipe(p, chain(function (head) { return pipe(many(pipe(sep, apSecond(p))), map(function (tail) { return A.prepend(head)(tail); })); })); }; /** * Like `sepBy1`, but cut on the separator, so that matching a `sep` not * followed by a `p` will cause a fatal error. * * @category combinators * @since 0.6.0 */ export var sepByCut = function (sep, p) { return pipe(p, chain(function (head) { return pipe(many(cutWith(sep, p)), map(function (tail) { return A.prepend(head)(tail); })); })); }; /** * Filters the result of a parser based upon a `Refinement` or a `Predicate`. * * @example * import { pipe } from 'fp-ts/function' * import { run } from 'parser-ts/code-frame' * import * as C from 'parser-ts/char' * import * as P from 'parser-ts/Parser' * * const parser = P.expected( * pipe( * P.item<C.Char>(), * P.filter((c) => c !== 'a') * ), * 'anything except "a"' * ) * * run(parser, 'a') * // { _tag: 'Left', left: '> 1 | a\n | ^ Expected: anything except "a"' } * * run(parser, 'b') * // { _tag: 'Right', right: 'b' } * * @category combinators * @since 0.6.10 */ export var filter = function (predicate) { return function (p) { return function (i) { return pipe(p(i), E.chain(function (next) { return (predicate(next.value) ? E.right(next) : error(i)); })); }; }; }; /** * Matches the provided parser `p` that occurs between the provided `left` and `right` parsers. * * `p` is polymorphic in its return type, because in general bounds and actual parser could return different types. * * @category combinators * @since 0.6.4 */ export var between = function (left, right) { return function (p) { return pipe(left, chain(function () { return p; }), chainFirst(function () { return right; })); }; }; /** * Matches the provided parser `p` that is surrounded by the `bound` parser. Shortcut for `between(bound, bound)`. * * @category combinators * @since 0.6.4 */ export var surroundedBy = function (bound) { return between(bound, bound); }; /** * Takes a `Parser` and tries to match it without consuming any input. * * @example * import { run } from 'parser-ts/code-frame' * import * as P from 'parser-ts/Parser' * import * as S from 'parser-ts/string' * * const parser = S.fold([ * S.string('hello '), * P.lookAhead(S.string('world')), * S.string('wor') * ]) * * run(parser, 'hello world') * // { _tag: 'Right', right: 'hello worldwor' } * * @category combinators * @since 0.6.6 */ export var lookAhead = function (p) { return function (i) { return pipe(p(i), E.chain(function (next) { return success(next.value, i, i); })); }; }; /** * Takes a `Predicate` and continues parsing until the given `Predicate` is satisfied. * * @example * import * as C from 'parser-ts/char' * import { run } from 'parser-ts/code-frame' * import * as P from 'parser-ts/Parser' * * const parser = P.takeUntil((c: C.Char) => c === 'w') * * run(parser, 'hello world') * // { _tag: 'Right', right: [ 'h', 'e', 'l', 'l', 'o', ' ' ] } * * @category combinators * @since 0.6.6 */ export var takeUntil = function (predicate) { return many(sat(not(predicate))); }; /** * Returns `Some<A>` if the specified parser succeeds, otherwise returns `None`. * * @example * import * as C from 'parser-ts/char' * import { run } from 'parser-ts/code-frame' * import * as P from 'parser-ts/Parser' * * const a = P.sat((c: C.Char) => c === 'a') * const parser = P.optional(a) * * run(parser, 'a') * // { _tag: 'Right', right: { _tag: 'Some', value: 'a' } } * * run(parser, 'b') * // { _tag: 'Left', left: { _tag: 'None' } } * * @category combinators * @since 0.6.10 */ export var optional = function (parser) { return pipe(parser, map(O.some), alt(function () { return succeed(O.none); })); }; /** * The `manyTill` combinator takes a value `parser` and a `terminator` parser, and * returns a new parser that will run the value `parser` repeatedly on the input * stream, returning a list of the result values of each parse operation as its * result, or the empty list if the parser never succeeded. * * @example * import * as C from 'parser-ts/char' * import { run } from 'parser-ts/code-frame' * import * as P from 'parser-ts/Parser' * * const parser = P.manyTill(C.letter, C.char('-')) * * run(parser, 'abc-') * // { _tag: 'Right', right: [ 'a', 'b', 'c' ] } * * run(parser, '-') * // { _tag: 'Right', right: [] } * * @category combinators * @since 0.6.11 */ export var manyTill = function (parser, terminator) { return pipe(terminator, map(function () { return RA.empty; }), alt(function () { return many1Till(parser, terminator); })); }; /** * The `many1Till` combinator is just like the `manyTill` combinator, except it * requires the value `parser` to match at least once before the `terminator` * parser. The resulting list is thus guaranteed to contain at least one value. * * @example * import * as C from 'parser-ts/char' * import { run } from 'parser-ts/code-frame' * import * as P from 'parser-ts/Parser' * * const parser = P.many1Till(C.letter, C.char('-')) * * run(parser, 'abc-') * // { _tag: 'Right', right: [ 'a', 'b', 'c' ] } * * run(parser, '-') * // { _tag: 'Left', left: '> 1 | -\n | ^ Expected: a letter' } * * @category combinators * @since 0.6.11 */ export var many1Till = function (parser, terminator) { return pipe(parser, chain(function (x) { return chainRec_(RNEA.of(x), function (acc) { return pipe(terminator, map(function () { return E.right(acc); }), alt(function () { return pipe(parser, map(function (a) { return E.left(RA.append(a)(acc)); })); })); }); })); }; var map_ = function (ma, f) { return function (i) { return pipe(ma(i), E.map(function (s) { return (__assign(__assign({}, s), { value: f(s.value) })); })); }; }; var ap_ = function (mab, ma) { return chain_(mab, function (f) { return map_(ma, f); }); }; var chain_ = function (ma, f) { return seq(ma, f); }; var chainRec_ = function (a, f) { var split = function (start) { return function (result) { return E.isLeft(result.value) ? E.left({ value: result.value.left, stream: result.next }) : E.right(success(result.value.right, result.next, start)); }; }; return function (start) { return tailRec({ value: a, stream: start }, function (state) { var result = f(state.value)(state.stream); if (E.isLeft(result)) { return E.right(error(state.stream, result.left.expected, result.left.fatal)); } return split(start)(result.right); }); }; }; var alt_ = function (fa, that) { return either(fa, that); }; // ------------------------------------------------------------------------------------- // pipeables // ------------------------------------------------------------------------------------- /** * @category Functor * @since 0.6.7 */ export var map = function (f) { return function (fa) { return map_(fa, f); }; }; /** * @category Apply * @since 0.6.7 */ export var ap = function (fa) { return function (fab) { return ap_(fab, fa); }; }; /** * @category Apply * @since 0.6.7 */ export var apFirst = function (fb) { return function (fa) { return ap_(map_(fa, function (a) { return function () { return a; }; }), fb); }; }; /** * @category Apply * @since 0.6.7 */ export var apSecond = function (fb) { return function (fa) { return ap_(map_(fa, function () { return function (b) { return b; }; }), fb); }; }; /** * @category Applicative * @since 0.6.7 */ export var of = succeed; /** * @category Monad * @since 0.6.7 */ export var chain = function (f) { return function (ma) { return chain_(ma, f); }; }; /** * @category Monad * @since 0.6.7 */ export var chainFirst = function (f) { return function (ma) { return chain_(ma, function (a) { return map_(f(a), function () { return a; }); }); }; }; /** * @category Alt * @since 0.6.7 */ export var alt = function (that) { return function (fa) { return alt_(fa, that); }; }; /** * @category Monad * @since 0.6.7 */ export var flatten = function (mma) { return chain_(mma, identity); }; /** * @category Alternative * @since 0.6.7 */ export var zero = fail; // ------------------------------------------------------------------------------------- // instances // ------------------------------------------------------------------------------------- /** * @category instances * @since 0.6.0 */ export var URI = 'Parser'; /** * @category instances * @since 0.6.7 */ export var getSemigroup = function (S) { return ({ concat: function (x, y) { return ap_(map_(x, function (x) { return function (y) { return S.concat(x, y); }; }), y); } }); }; /** * @category instances * @since 0.6.0 */ export var getMonoid = function (M) { return (__assign(__assign({}, getSemigroup(M)), { empty: succeed(M.empty) })); }; /** * @category instances * @since 0.6.7 */ export var Functor = { URI: URI, map: map_ }; /** * @category instances * @since 0.6.7 */ export var Applicative = { URI: URI, map: map_, ap: ap_, of: of }; /** * @category instances * @since 0.6.7 */ export var Monad = { URI: URI, map: map_, ap: ap_, of: of, chain: chain_ }; /** * @category instances * @since 0.6.11 */ export var ChainRec = { URI: URI, map: map_, ap: ap_, chain: chain_, chainRec: chainRec_ }; /** * @category instances * @since 0.6.7 */ export var Alt = { URI: URI, map: map_, alt: alt_ }; /** * @category instances * @since 0.6.7 */ export var Alternative = { URI: URI, map: map_, of: of, ap: ap_, alt: alt_, zero: fail }; /** * @category instances * @since 0.6.7 */ export var parser = { URI: URI, map: map_, of: of, ap: ap_, chain: chain_, alt: alt_, zero: fail }; // ------------------------------------------------------------------------------------- // do notation // ------------------------------------------------------------------------------------- /** * @internal */ var bind_ = function (a, name, b) { var _a; return Object.assign({}, a, (_a = {}, _a[name] = b, _a)); }; /** * @since 0.6.8 */ export var bindTo = function (name) { return function (fa) { return pipe(fa, map(function (a) { return bind_({}, name, a); })); }; }; /** * @since 0.6.8 */ export var bind = function (name, f) { return function (fa) { return pipe(fa, chain(function (a) { return pipe(f(a), map(function (b) { return bind_(a, name, b); })); })); }; };