Make errors more informative
Make error messages from the lexing and parsing stages be a bit more helpful. If provided with the input and a position, the error will display the error position, and the nearby input with the error position underlined (yay combining marks). Also, standardize the errors a bit (remove doubled "Error:" strings) Test plan: - Make sure the errors look totally sweet (before: {F15602}, after: {F15603}) - Trigger every error (that can be triggered) in Parser, Lexer, and buildTree using the inputs: `a^` `a_` `a^x^x` `a_x_x` `\color f` `\blue ` `\Huge` `\llap` `\text` `\dfrac` `\dfrac{x}` `\d` `\blue{` `\color{#f` `{\Huge{x}}` - See that the tests still work Auditors: alpert
This commit is contained in:
parent
c22d8644cc
commit
e68cc472c6
4
Lexer.js
4
Lexer.js
|
@ -80,7 +80,7 @@ Lexer.prototype._innerLex = function(pos, normals, ignoreWhitespace) {
|
|||
|
||||
// We didn't match any of the tokens, so throw an error.
|
||||
throw new ParseError("Unexpected character: '" + input[0] +
|
||||
"' at position " + pos);
|
||||
"'", this, pos);
|
||||
}
|
||||
|
||||
// A regex to match a CSS color (like #ffffff or BlueViolet)
|
||||
|
@ -101,7 +101,7 @@ Lexer.prototype._innerLexColor = function(pos) {
|
|||
}
|
||||
|
||||
// We didn't match a color, so throw an error.
|
||||
throw new ParseError("Invalid color at position " + pos);
|
||||
throw new ParseError("Invalid color", this, pos);
|
||||
};
|
||||
|
||||
// Lex a single token
|
||||
|
|
|
@ -1,5 +1,24 @@
|
|||
function ParseError(message) {
|
||||
var self = new Error("TeX parse error: " + message);
|
||||
function ParseError(message, lexer, position) {
|
||||
var error = "KaTeX parse error: " + message;
|
||||
|
||||
if (lexer !== undefined && position !== undefined) {
|
||||
// If we have the input and a position, make the error a bit fancier
|
||||
// Prepend some information
|
||||
error += " at position " + position + ": ";
|
||||
|
||||
// Get the input
|
||||
var input = lexer._input;
|
||||
// Insert a combining underscore at the correct position
|
||||
input = input.slice(0, position) + "\u0332" +
|
||||
input.slice(position);
|
||||
|
||||
// Extract some context from the input and add it to the error
|
||||
var begin = Math.max(0, position - 15);
|
||||
var end = position + 15;
|
||||
error += input.slice(begin, end);
|
||||
}
|
||||
|
||||
var self = new Error(error);
|
||||
self.name = "ParseError";
|
||||
self.__proto__ = ParseError.prototype;
|
||||
return self;
|
||||
|
|
68
Parser.js
68
Parser.js
|
@ -24,10 +24,12 @@ function ParseNode(type, value, mode) {
|
|||
|
||||
// Checks a result to make sure it has the right type, and throws an
|
||||
// appropriate error otherwise.
|
||||
var expect = function(result, type) {
|
||||
Parser.prototype.expect = function(result, type) {
|
||||
if (result.type !== type) {
|
||||
throw new ParseError(
|
||||
"Expected '" + type + "', got '" + result.type + "'");
|
||||
"Expected '" + type + "', got '" + result.type + "'",
|
||||
this.lexer, result.position
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -48,7 +50,7 @@ Parser.prototype.parseInput = function(pos, mode) {
|
|||
var expression = this.parseExpression(pos, mode);
|
||||
// If we succeeded, make sure there's an EOF at the end
|
||||
var EOF = this.lexer.lex(expression.position, mode);
|
||||
expect(EOF, "EOF");
|
||||
this.expect(EOF, "EOF");
|
||||
return expression;
|
||||
};
|
||||
|
||||
|
@ -73,7 +75,8 @@ Parser.prototype.parseExpression = function(pos, mode) {
|
|||
// Parses a superscript expression, like "^3"
|
||||
Parser.prototype.parseSuperscript = function(pos, mode) {
|
||||
if (mode !== "math") {
|
||||
throw new ParseError("Trying to parse superscript in non-math mode");
|
||||
throw new ParseError(
|
||||
"Trying to parse superscript in non-math mode", this.lexer, pos);
|
||||
}
|
||||
|
||||
// Try to parse a "^" character
|
||||
|
@ -85,7 +88,8 @@ Parser.prototype.parseSuperscript = function(pos, mode) {
|
|||
return group;
|
||||
} else {
|
||||
// Throw an error if we didn't find a group
|
||||
throw new ParseError("Couldn't find group after '^'");
|
||||
throw new ParseError(
|
||||
"Couldn't find group after '^'", this.lexer, sup.position);
|
||||
}
|
||||
} else if (sup.type === "'") {
|
||||
var pos = sup.position;
|
||||
|
@ -99,7 +103,8 @@ Parser.prototype.parseSuperscript = function(pos, mode) {
|
|||
// Parses a subscript expression, like "_3"
|
||||
Parser.prototype.parseSubscript = function(pos, mode) {
|
||||
if (mode !== "math") {
|
||||
throw new ParseError("Trying to parse subscript in non-math mode");
|
||||
throw new ParseError(
|
||||
"Trying to parse subscript in non-math mode", this.lexer, pos);
|
||||
}
|
||||
|
||||
// Try to parse a "_" character
|
||||
|
@ -111,7 +116,8 @@ Parser.prototype.parseSubscript = function(pos, mode) {
|
|||
return group;
|
||||
} else {
|
||||
// Throw an error if we didn't find a group
|
||||
throw new ParseError("Couldn't find group after '_'");
|
||||
throw new ParseError(
|
||||
"Couldn't find group after '_'", this.lexer, sub.position);
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
|
@ -146,7 +152,8 @@ Parser.prototype.parseAtom = function(pos, mode) {
|
|||
var node;
|
||||
if ((node = this.parseSuperscript(nextPos, mode))) {
|
||||
if (sup) {
|
||||
throw new ParseError("Parse error: Double superscript");
|
||||
throw new ParseError(
|
||||
"Double superscript", this.lexer, nextPos);
|
||||
}
|
||||
nextPos = node.position;
|
||||
sup = node.result;
|
||||
|
@ -154,7 +161,8 @@ Parser.prototype.parseAtom = function(pos, mode) {
|
|||
}
|
||||
if ((node = this.parseSubscript(nextPos, mode))) {
|
||||
if (sub) {
|
||||
throw new ParseError("Parse error: Double subscript");
|
||||
throw new ParseError(
|
||||
"Double subscript", this.lexer, nextPos);
|
||||
}
|
||||
nextPos = node.position;
|
||||
sub = node.result;
|
||||
|
@ -183,7 +191,7 @@ Parser.prototype.parseGroup = function(pos, mode) {
|
|||
var expression = this.parseExpression(start.position, mode);
|
||||
// Make sure we get a close brace
|
||||
var closeBrace = this.lexer.lex(expression.position, mode);
|
||||
expect(closeBrace, "}");
|
||||
this.expect(closeBrace, "}");
|
||||
return new ParseResult(
|
||||
new ParseNode("ordgroup", expression.result, mode),
|
||||
closeBrace.position);
|
||||
|
@ -202,14 +210,16 @@ Parser.prototype.parseColorGroup = function(pos, mode) {
|
|||
var color = this.lexer.lex(start.position, "color");
|
||||
// Make sure we get a close brace
|
||||
var closeBrace = this.lexer.lex(color.position, mode);
|
||||
expect(closeBrace, "}");
|
||||
this.expect(closeBrace, "}");
|
||||
return new ParseResult(
|
||||
new ParseNode("color", color.text),
|
||||
closeBrace.position);
|
||||
} else {
|
||||
// It has to have an open brace, so if it doesn't we throw
|
||||
throw new ParseError(
|
||||
"Parse error: There must be braces around colors");
|
||||
"There must be braces around colors",
|
||||
this.lexer, pos
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -254,7 +264,9 @@ Parser.prototype.parseNucleus = function(pos, mode) {
|
|||
group.position);
|
||||
} else {
|
||||
throw new ParseError(
|
||||
"Expected group after '" + nucleus.text + "'");
|
||||
"Expected group after '" + nucleus.text + "'",
|
||||
this.lexer, nucleus.position
|
||||
);
|
||||
}
|
||||
} else if (nucleus.type === "\\color") {
|
||||
// If this is a custom color function, parse its first argument as a
|
||||
|
@ -276,11 +288,15 @@ Parser.prototype.parseNucleus = function(pos, mode) {
|
|||
inner.position);
|
||||
} else {
|
||||
throw new ParseError(
|
||||
"Expected second group after '" + nucleus.text + "'");
|
||||
"Expected second group after '" + nucleus.text + "'",
|
||||
this.lexer, color.position
|
||||
);
|
||||
}
|
||||
} else {
|
||||
throw new ParseError(
|
||||
"Expected color after '" + nucleus.text + "'");
|
||||
"Expected color after '" + nucleus.text + "'",
|
||||
this.lexer, nucleus.position
|
||||
);
|
||||
}
|
||||
} else if (mode === "math" && utils.contains(sizeFuncs, nucleus.type)) {
|
||||
// If this is a size function, parse its argument and return
|
||||
|
@ -294,7 +310,9 @@ Parser.prototype.parseNucleus = function(pos, mode) {
|
|||
group.position);
|
||||
} else {
|
||||
throw new ParseError(
|
||||
"Expected group after '" + nucleus.text + "'");
|
||||
"Expected group after '" + nucleus.text + "'",
|
||||
this.lexer, nucleus.position
|
||||
);
|
||||
}
|
||||
} else if (mode === "math" && utils.contains(namedFns, nucleus.type)) {
|
||||
// If this is a named function, just return it plain
|
||||
|
@ -310,7 +328,9 @@ Parser.prototype.parseNucleus = function(pos, mode) {
|
|||
group.position);
|
||||
} else {
|
||||
throw new ParseError(
|
||||
"Expected group after '" + nucleus.text + "'");
|
||||
"Expected group after '" + nucleus.text + "'",
|
||||
this.lexer, nucleus.position
|
||||
);
|
||||
}
|
||||
} else if (mode === "math" && nucleus.type === "\\text") {
|
||||
var group = this.parseGroup(nucleus.position, "text");
|
||||
|
@ -320,7 +340,9 @@ Parser.prototype.parseNucleus = function(pos, mode) {
|
|||
group.position);
|
||||
} else {
|
||||
throw new ParseError(
|
||||
"Expected group after '" + nucleus.text + "'");
|
||||
"Expected group after '" + nucleus.text + "'",
|
||||
this.lexer, nucleus.position
|
||||
);
|
||||
}
|
||||
} else if (mode === "math" && (nucleus.type === "\\dfrac" ||
|
||||
nucleus.type === "\\frac" ||
|
||||
|
@ -339,11 +361,15 @@ Parser.prototype.parseNucleus = function(pos, mode) {
|
|||
denom.position);
|
||||
} else {
|
||||
throw new ParseError("Expected denominator after '" +
|
||||
nucleus.type + "'");
|
||||
nucleus.type + "'",
|
||||
this.lexer, numer.position
|
||||
);
|
||||
}
|
||||
} else {
|
||||
throw new ParseError("Parse error: Expected numerator after '" +
|
||||
nucleus.type + "'");
|
||||
throw new ParseError("Expected numerator after '" +
|
||||
nucleus.type + "'",
|
||||
this.lexer, nucleus.position
|
||||
);
|
||||
}
|
||||
} else if (mode === "math" && nucleus.type === "\\KaTeX") {
|
||||
// If this is a KaTeX node, return the special katex result
|
||||
|
|
|
@ -440,7 +440,7 @@ var buildGroup = function(group, options, prev) {
|
|||
|
||||
if (options.depth > 1) {
|
||||
throw new ParseError(
|
||||
"Error: Can't use sizing outside of the root node");
|
||||
"Can't use sizing outside of the root node");
|
||||
}
|
||||
|
||||
groupNode.height *= multiplier;
|
||||
|
@ -450,7 +450,7 @@ var buildGroup = function(group, options, prev) {
|
|||
return groupNode;
|
||||
} else {
|
||||
throw new ParseError(
|
||||
"Lex error: Got group of unknown type: '" + group.type + "'");
|
||||
"Got group of unknown type: '" + group.type + "'");
|
||||
}
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user