/** * A Pattern is a function that matches against a string starting at a given index. * * If it matches successfully, it returns some captured value, and the index following the match. * * @template T * @typedef {{ * (source: string, index: number): ([T, number] | null) * }} Peg.Pattern */ var Peg = window.Peg ?? {}; /** * Makes a pattern from a function, adding helper methods. * * @template T * @param {(source: string, index: number) => ([T, number] | null)} pattern * @returns {Peg.Pattern} */ Peg.WrapPattern = function (pattern) { return /** @type {Peg.Pattern} */ (pattern); }; /** * Proxies to a pattern retrieved from an accessor function. * * Allows using a pattern recursively in its own definition, by returning the value of the const assigned to. * * @template T * @param {() => Peg.Pattern} getPattern * @returns {Peg.Pattern} */ Peg.Use = function (getPattern) { return Peg.WrapPattern(function (source, index) { return getPattern()(source, index); }); }; /** * Creates a pattern that wraps another pattern, transforming the returned value on a match * @template T, U * @param {Peg.Pattern} pattern * @param {(value: T)=> U} map * @return {Peg.Pattern} */ Peg.Map = function (pattern, map) { return Peg.WrapPattern(function (source, index) { const match = pattern(source, index); return match ? [map(match[0]), match[1]] : null; }); }; /** * Creates a pattern matching a regex & returning any captures. The regex needs to be sticky (using the //y modifier) * @param {RegExp} regex * @return {Peg.Pattern} */ Peg.Regex = function (regex) { return Peg.WrapPattern(function (source, index) { regex.lastIndex = index; const matches = regex.exec(source); return matches ? [matches, regex.lastIndex] : null; }); }; /** * 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} */ Peg.Choose = function (...patterns) { return Peg.WrapPattern(function (source, index) { for (const pattern of patterns) { const match = pattern(source, index); if (match) { return match; } } return null; }); }; /** * 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} */ Peg.Sequence = function (...patterns) { return Peg.WrapPattern(function (source, index) { const values = /** @type {T} */ (/** @type {unknown} */ ([])); for (const pattern of patterns) { const match = pattern(source, index); if (match == null) { return null; } values.push(match[0]); index = match[1]; } 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} */ Peg.AtLeast = function (min, pattern) { return Peg.WrapPattern(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; } }); }; /** * Pattern that matches the end of input * @type {Peg.Pattern} */ Peg.End = Peg.WrapPattern(function End(source, index) { if (source.length == index) { return [true, index]; } else { return null; } });