Refactor error reporting to allow ignored errors to be reported on a successful pattern match

This commit is contained in:
Tangent Wantwight 2023-08-04 18:31:21 -04:00
parent 4b982a64ef
commit ef59915add
2 changed files with 66 additions and 53 deletions

View file

@ -82,19 +82,18 @@ var Notcl = (() => {
code = code.replace(/(?<!\\)((\\\\)*)\\\n/g, "$1");
/* Parse */
const commands = Script(code, 0);
const [commands, errorPos, expected] = Script(code, 0);
if (commands[0]) {
return [true, commands[1]];
if (commands) {
return [true, commands[0]];
} else {
const [, errorPos, expected] = commands;
ERROR_CONTEXT.lastIndex = errorPos;
const [, before, after] = /** @type {RegExpExecArray} */ (
ERROR_CONTEXT.exec(code)
);
return [
false,
`<pre>Error at position ${commands[1]}
`<pre>Error at position ${errorPos}
${escapeHtml(before + "" + after)}
${"-".repeat(before.length)}^

110
peg.js
View file

@ -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<T>}
*/
Peg.WrapPattern = function (matchFunc) {
const pattern = /** @type {Peg.Pattern<T>} */ (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<T>}
*/
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<T>}
*/
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<true>} */
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("<eof>");
return end;
};
@ -196,12 +210,12 @@ Peg.End = () => {
Peg.Hint = function (pattern) {
return /** @type {Peg.Pattern<never>} */ (
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);