From 4430580412af1d03006e87308a501063fc3246b1 Mon Sep 17 00:00:00 2001
From: Tangent Wantwight <tangent128@gmail.com>
Date: Sat, 29 Jul 2023 01:13:38 -0400
Subject: [PATCH] Add AtLeast to Peg helpers

---
 peg.js | 36 ++++++++++++++++++++++++++++++++++++
 1 file changed, 36 insertions(+)

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<T>} patterns
    * @return {Peg.Pattern<T>}
@@ -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<T[K]>}} patterns
    * @return {Peg.Pattern<T>}
@@ -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<T>} pattern
+   * @return {Peg.Pattern<T[]>}
+   */
+  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;
+      }
+    };
+  },
 };