diff --git a/peg.js b/peg.js index 0b82fe7..55d8a8a 100644 --- a/peg.js +++ b/peg.js @@ -35,6 +35,7 @@ var Peg = { }, /** + * Creates a pattern that tries the given patterns, in order, until it finds one that matches at the current index. * @template T * @param {...Peg.Pattern} patterns * @return {Peg.Pattern} @@ -52,6 +53,10 @@ var Peg = { }, /** + * Creates a pattern that concatenates the given patterns, returning a tuple of their captured values. + * + * For example, if A matches "a" and captures 1, while B matches "b" and captures null, + * then `Sequence(A,B)` will match "ab" and capture [1, null] * @template {unknown[]} T * @param {{[K in keyof T]: Peg.Pattern}} patterns * @return {Peg.Pattern} @@ -70,4 +75,35 @@ var Peg = { return [values, index]; }; }, + + /** + * Creates a pattern that matches consecutive runs of the given pattern, returning an array of all captures. + * + * The match only succeeds if the run is at least {min} instances long. + * + * If the given pattern does not consume input, the matching will be terminated to prevent an eternal loop. + * + * Note that if the minimum run is zero, this pattern will always succeed, but might not consume any input. + * @template {unknown[]} T + * @param {number} min + * @param {Peg.Pattern} pattern + * @return {Peg.Pattern} + */ + AtLeast(min, pattern) { + return function (source, index) { + const values = /** @type {T[]} */ ([]); + do { + const match = pattern(source, index); + if (match == null) break; + values.push(match[0]); + if (index == match[1]) break; + index = match[1]; + } while (true); + if (values.length >= min) { + return [values, index]; + } else { + return null; + } + }; + }, };