Refactor error reporting to allow ignored errors to be reported on a successful pattern match
This commit is contained in:
parent
4b982a64ef
commit
ef59915add
2 changed files with 66 additions and 53 deletions
9
notcl.js
9
notcl.js
|
@ -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
110
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<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);
|
||||
|
|
Loading…
Reference in a new issue