From ef59915addbcdf518d0fd8594575aa181a027633 Mon Sep 17 00:00:00 2001 From: Tangent Wantwight Date: Fri, 4 Aug 2023 18:31:21 -0400 Subject: [PATCH] Refactor error reporting to allow ignored errors to be reported on a successful pattern match --- notcl.js | 9 ++--- peg.js | 110 +++++++++++++++++++++++++++++++------------------------ 2 files changed, 66 insertions(+), 53 deletions(-) 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);