diff --git a/notcl.js b/notcl.js index 56ccd92..195d3a7 100644 --- a/notcl.js +++ b/notcl.js @@ -82,19 +82,18 @@ var Notcl = (() => { code = code.replace(/(?Error at position ${commands[1]} + `
Error at position ${errorPos} ${escapeHtml(before + "" + after)} ${"-".repeat(before.length)}^ diff --git a/peg.js b/peg.js index d3c1171..13979aa 100644 --- a/peg.js +++ b/peg.js @@ -3,12 +3,17 @@ * * If it matches successfully, it returns some captured value, and the index following the match. * + * On success or failure, it returns the furthest point the pattern could make sense of, and a description of what was expected next at that point. + * + * For simple patterns, the "furthest point" may just be index; however, some more complex patterns might succeed, + * but consume much less input than they would have been able to if some other expected symbol was found. Reporting + * the furthest a pattern could hypothetically have gotten can help generate better error messages if no valid parse tree is found. + * * @template T * @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, 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. + * @returns {[[T, number] | null, number, string]} - [successValue, furthest symbol attempted, expected pattern] */ /** * @template T @@ -27,15 +32,15 @@ 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, string])} matchFunc + * @param {(source: string, index: number) => ([[T, number] | null, number, string])} matchFunc * @returns {Peg.Pattern} */ 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]] : match; + const [value, furthest, expected] = pattern(source, index); + return [value ? [map(value[0]), value[1]] : null, furthest, expected]; }).expects(pattern.expectLabel); }; @@ -73,9 +78,11 @@ Peg.Regex = function (regex) { const pattern = Peg.WrapPattern(function (source, index) { regex.lastIndex = index; const matches = regex.exec(source); - return matches - ? [true, matches, regex.lastIndex] - : [false, index, pattern.expectLabel]; + return [ + matches ? [matches, regex.lastIndex] : null, + index, + pattern.expectLabel, + ]; }).expects(regex.source); return pattern; }; @@ -87,21 +94,23 @@ Peg.Regex = function (regex) { * @return {Peg.Pattern } */ Peg.Choose = function (...patterns) { - const expected = patterns.map((pattern) => pattern.expectLabel).join(" | "); + const genericExpected = patterns + .map((pattern) => pattern.expectLabel) + .join(" | "); return Peg.WrapPattern(function (source, index) { - let furthest = index; - let furthestExpected = expected; + let furthestFound = index; + let furthestExpected = genericExpected; for (const pattern of patterns) { - const match = pattern(source, index); - if (match[0]) { - return match; - } else if (match[1] > furthest) { - furthest = match[1]; - furthestExpected = match[2]; + const [value, furthest, expected] = pattern(source, index); + if (value) { + return [value, furthest, expected]; + } else if (furthest > furthestFound) { + furthestFound = furthest; + furthestExpected = expected; } } - return [false, furthest, furthestExpected]; - }).expects(expected); + return [null, furthestFound, furthestExpected]; + }).expects(genericExpected); }; /** @@ -114,18 +123,23 @@ Peg.Choose = function (...patterns) { * @return {Peg.Pattern } */ Peg.Sequence = function (...patterns) { + const genericExpected = patterns[0]?.expectLabel ?? "(nothing)"; return Peg.WrapPattern(function (source, index) { const values = /** @type {T} */ (/** @type {unknown} */ ([])); + let furthestFound = index; + let furthestExpected = genericExpected; for (const pattern of patterns) { - const match = pattern(source, index); - if (match[0] == false) { - return match; + const [value, furthest, expected] = pattern(source, index); + if (value == null) { + return [value, furthest, expected]; } - values.push(match[1]); - index = match[2]; + values.push(value[0]); + index = value[1]; + furthestFound = furthest; + furthestExpected = expected; } - return [true, values, index]; - }).expects(patterns[0]?.expectLabel ?? "(nothing)"); + return [[values, index], furthestFound, furthestExpected]; + }).expects(genericExpected); }; /** @@ -144,26 +158,26 @@ Peg.Sequence = function (...patterns) { Peg.AtLeast = function (min, pattern) { return Peg.WrapPattern(function (source, index) { const values = /** @type {T[]} */ ([]); - let furthest = index; - let expected = pattern.expectLabel; + let furthestFound = index; + let furthestExpected = pattern.expectLabel; do { - const match = pattern(source, index); - if (match[0] == false) { - furthest = match[1]; - expected = match[2]; + const [value, furthest, expected] = pattern(source, index); + if (value == null) { + furthestFound = furthest; + furthestExpected = expected; break; } - values.push(match[1]); - if (index == match[2]) { - furthest = match[2]; + values.push(value[0]); + if (index == value[1]) { + furthestFound = value[1]; break; } - index = match[2]; + index = value[1]; } while (true); if (values.length >= min) { - return [true, values, index]; + return [[values, index], furthestFound, furthestExpected]; } else { - return [false, furthest, expected]; + return [null, furthestFound, furthestExpected]; } }).expects(pattern.expectLabel); }; @@ -175,11 +189,11 @@ Peg.AtLeast = function (min, pattern) { Peg.End = () => { /** @type {Peg.Pattern } */ const end = Peg.WrapPattern(function End(source, index) { - if (source.length == index) { - return [true, /** @type {true} */ (true), index]; - } else { - return [false, index, end.expectLabel]; - } + return [ + source.length == index ? [/** @type {true} */ (true), index] : null, + index, + end.expectLabel, + ]; }).expects(" "); return end; }; @@ -196,12 +210,12 @@ Peg.End = () => { Peg.Hint = function (pattern) { return /** @type {Peg.Pattern } */ ( Peg.WrapPattern(function (source, index) { - const match = pattern(source, index); - if (match[0]) { - console.log("oops match", match); - return [false, index, pattern.expectLabel]; + const [value, furthest, expected] = pattern(source, index); + if (value) { + console.log("oops match", value, furthest, expected); + return [null, index, pattern.expectLabel]; } else { - return match; + return [value, furthest, expected]; } }) ).expects(pattern.expectLabel);