From 50df27c50eab7206c38b7c7f388c529cb1c91f6a Mon Sep 17 00:00:00 2001 From: Tangent Wantwight Date: Fri, 4 Aug 2023 01:24:02 -0400 Subject: [PATCH] Add more ability to communicate what was expected in the error --- notcl.js | 6 ++++-- peg.js | 51 +++++++++++++++++++++++++++++++++------------------ 2 files changed, 37 insertions(+), 20 deletions(-) diff --git a/notcl.js b/notcl.js index 7c1b17a..5888cd7 100644 --- a/notcl.js +++ b/notcl.js @@ -78,7 +78,7 @@ var Notcl = (() => { if (commands[0]) { return [true, commands[1]]; } else { - const errorPos = commands[1]; + const [, errorPos, expected] = commands; ERROR_CONTEXT.lastIndex = errorPos; const [, before, after] = /** @type {RegExpExecArray} */ ( ERROR_CONTEXT.exec(code) @@ -87,7 +87,9 @@ var Notcl = (() => { false, `
Error at position ${commands[1]}
 ${escapeHtml(before + "" + after)}
-${"-".repeat(before.length)}^
`, +${"-".repeat(before.length)}^ + +Expected: ${escapeHtml(expected)}`, ]; } }, diff --git a/peg.js b/peg.js index e5677c0..a9b765f 100644 --- a/peg.js +++ b/peg.js @@ -7,12 +7,15 @@ * @callback Peg.PatternCall * @param {string} source - the string being parsed * @param {number} index - the index in the string to begin matching from - * @returns {[true, T, number] | [false, number]} - if successful, true, the captured value, and the index to start parsing following symbols from. Else, false, and the furthest index that could be understood. + * @returns {[true, T, number] | [false, number, string]} - if successful, true, the captured value, and the index to start parsing following symbols from. + * Else, false, the furthest index that could be understood, and a description of what was expected but not matchable. */ /** * @template T * @typedef {object} Peg.PatternExt * @property {(map: (value: T) => U) => Peg.Pattern} map Creates a pattern that wraps another pattern, transforming the returned value on a match + * @property {string} expectLabel A human-readable annotation describing the pattern for error messages + * @property {(label: string) => Peg.Pattern} expects Adds a human-readable annotation describing the pattern */ /** * @template T @@ -24,18 +27,25 @@ var Peg = window.Peg ?? {}; * Makes a pattern from a function, adding helper methods. * * @template T - * @param {(source: string, index: number) => ([true, T, number] | [false, number])} pattern + * @param {(source: string, index: number) => ([true, T, number] | [false, number, string])} matchFunc * @returns {Peg.Pattern} */ -Peg.WrapPattern = function (pattern) { - /** @type {Peg.Pattern} */ (pattern).map = function (map) { +Peg.WrapPattern = function (matchFunc) { + const pattern = /** @type {Peg.Pattern} */ (matchFunc); + pattern.map = function (map) { return Peg.WrapPattern(function (source, index) { const match = pattern(source, index); - return match[0] ? [true, map(match[1]), match[2]] : [false, match[1]]; - }); + return match[0] ? [true, map(match[1]), match[2]] : match; + }).expects(pattern.expectLabel); }; - return /** @type {Peg.Pattern} */ (pattern); + pattern.expectLabel = pattern.name; + pattern.expects = (label) => { + pattern.expectLabel = label; + return pattern; + }; + + return pattern; }; /** @@ -50,7 +60,7 @@ Peg.WrapPattern = function (pattern) { Peg.Use = function (getPattern) { return Peg.WrapPattern(function (source, index) { return getPattern()(source, index); - }); + }).expects(String(getPattern)); }; /** @@ -62,8 +72,10 @@ Peg.Regex = function (regex) { return Peg.WrapPattern(function (source, index) { regex.lastIndex = index; const matches = regex.exec(source); - return matches ? [true, matches, regex.lastIndex] : [false, index]; - }); + return matches + ? [true, matches, regex.lastIndex] + : [false, index, regex.source]; + }).expects(regex.source); }; /** @@ -73,6 +85,7 @@ Peg.Regex = function (regex) { * @return {Peg.Pattern} */ Peg.Choose = function (...patterns) { + const expected = patterns.map((pattern) => pattern.expectLabel).join(" | "); return Peg.WrapPattern(function (source, index) { let furthest = index; for (const pattern of patterns) { @@ -83,8 +96,8 @@ Peg.Choose = function (...patterns) { furthest = match[1]; } } - return [false, furthest]; - }); + return [false, furthest, expected]; + }).expects(expected); }; /** @@ -108,7 +121,7 @@ Peg.Sequence = function (...patterns) { index = match[2]; } return [true, values, index]; - }); + }).expects(patterns[0]?.expectLabel ?? "(nothing)"); }; /** @@ -128,10 +141,12 @@ Peg.AtLeast = function (min, pattern) { return Peg.WrapPattern(function (source, index) { const values = /** @type {T[]} */ ([]); let furthest = index; + let expected = pattern.expectLabel; do { const match = pattern(source, index); if (match[0] == false) { furthest = match[1]; + expected = match[2]; break; } values.push(match[1]); @@ -144,9 +159,9 @@ Peg.AtLeast = function (min, pattern) { if (values.length >= min) { return [true, values, index]; } else { - return [false, furthest]; + return [false, furthest, expected]; } - }); + }).expects(pattern.expectLabel); }; /** @@ -155,8 +170,8 @@ Peg.AtLeast = function (min, pattern) { */ Peg.End = Peg.WrapPattern(function End(source, index) { if (source.length == index) { - return [true, true, index]; + return [true, /** @type {true} */ (true), index]; } else { - return [false, index]; + return [false, index, ""]; } -}); +}).expects("");