From 9b87906c09adb9d9d4342fb90f1295592a00b47d Mon Sep 17 00:00:00 2001 From: Tangent Wantwight Date: Sat, 29 Jul 2023 14:39:11 -0400 Subject: [PATCH] Break out Peg namespace to permit self-reference --- peg.js | 221 ++++++++++++++++++++++++++++++--------------------------- 1 file changed, 117 insertions(+), 104 deletions(-) diff --git a/peg.js b/peg.js index c8545f3..5f2c76b 100644 --- a/peg.js +++ b/peg.js @@ -4,118 +4,131 @@ * 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 + * @typedef {{ + * (source: string, index: number): ([T, number] | null) + * }} Peg.Pattern */ -var Peg = { - /** - * 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} - */ - Map(pattern, map) { - return function (source, index) { +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); +}; + +/** + * 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); - 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} - */ - Regex(regex) { - return 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} - */ - Choose(...patterns) { - return function (source, index) { - for (const pattern of patterns) { - const match = pattern(source, index); - if (match) { - return match; - } + if (match) { + return match; } - return null; - }; - }, + } + 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} - */ - Sequence(...patterns) { - return 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} - */ - 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 { +/** + * 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]; + }); +}; - /** - * Pattern that matches the end of input - * @type {Peg.Pattern} - */ - End(source, index) { - if (source.length == index) { - return [true, 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; + } +});