Avoid re-lexing, move position to internal state
Instead of passing around the current position as an argument, we now have a parser property called pos to keep track of that. Instead of repeatedly re-lexing at the current position we now have a property called nextToken which contains the token beginning at the current position. We may need to re-lex if we switch mode. Since the position is kept in the parser state, we don't need to return it from parsing methods, which obsoletes the ParseResult class.
This commit is contained in:
parent
5f275aa9c1
commit
4debcb34af
393
src/Parser.js
393
src/Parser.js
|
@ -34,11 +34,9 @@ var ParseError = require("./ParseError");
|
||||||
* There are also extra `.handle...` functions, which pull out some reused
|
* There are also extra `.handle...` functions, which pull out some reused
|
||||||
* functionality into self-contained functions.
|
* functionality into self-contained functions.
|
||||||
*
|
*
|
||||||
* The earlier functions return `ParseResult`s, which contain a ParseNode and a
|
* The earlier functions return ParseNodes.
|
||||||
* new position.
|
|
||||||
*
|
|
||||||
* The later functions (which are called deeper in the parse) sometimes return
|
* The later functions (which are called deeper in the parse) sometimes return
|
||||||
* ParseFuncOrArgument, which contain a ParseResult as well as some data about
|
* ParseFuncOrArgument, which contain a ParseNode as well as some data about
|
||||||
* whether the parsed object is a function which is missing some arguments, or a
|
* whether the parsed object is a function which is missing some arguments, or a
|
||||||
* standalone object which can be used as an argument to another function.
|
* standalone object which can be used as an argument to another function.
|
||||||
*/
|
*/
|
||||||
|
@ -54,11 +52,10 @@ function Parser(input, settings) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var ParseNode = parseData.ParseNode;
|
var ParseNode = parseData.ParseNode;
|
||||||
var ParseResult = parseData.ParseResult;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An initial function (without its arguments), or an argument to a function.
|
* An initial function (without its arguments), or an argument to a function.
|
||||||
* The `result` argument should be a ParseResult.
|
* The `result` argument should be a ParseNode.
|
||||||
*/
|
*/
|
||||||
function ParseFuncOrArgument(result, isFunction) {
|
function ParseFuncOrArgument(result, isFunction) {
|
||||||
this.result = result;
|
this.result = result;
|
||||||
|
@ -69,14 +66,29 @@ function ParseFuncOrArgument(result, isFunction) {
|
||||||
/**
|
/**
|
||||||
* Checks a result to make sure it has the right type, and throws an
|
* Checks a result to make sure it has the right type, and throws an
|
||||||
* appropriate error otherwise.
|
* appropriate error otherwise.
|
||||||
|
*
|
||||||
|
* @param {boolean=} consume whether to consume the expected token,
|
||||||
|
* defaults to true
|
||||||
*/
|
*/
|
||||||
Parser.prototype.expect = function(result, text) {
|
Parser.prototype.expect = function(text, consume) {
|
||||||
if (result.text !== text) {
|
if (this.nextToken.text !== text) {
|
||||||
throw new ParseError(
|
throw new ParseError(
|
||||||
"Expected '" + text + "', got '" + result.text + "'",
|
"Expected '" + text + "', got '" + this.nextToken.text + "'",
|
||||||
this.lexer, result.position
|
this.lexer, this.nextToken.position
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (consume !== false) {
|
||||||
|
this.consume();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Considers the current look ahead token as consumed,
|
||||||
|
* and fetches the one after that as the new look ahead.
|
||||||
|
*/
|
||||||
|
Parser.prototype.consume = function() {
|
||||||
|
this.pos = this.nextToken.position;
|
||||||
|
this.nextToken = this.lexer.lex(this.pos, this.mode);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -84,21 +96,23 @@ Parser.prototype.expect = function(result, text) {
|
||||||
*
|
*
|
||||||
* @return {?Array.<ParseNode>}
|
* @return {?Array.<ParseNode>}
|
||||||
*/
|
*/
|
||||||
Parser.prototype.parse = function(input) {
|
Parser.prototype.parse = function() {
|
||||||
// Try to parse the input
|
// Try to parse the input
|
||||||
this.mode = "math";
|
this.mode = "math";
|
||||||
var parse = this.parseInput(0);
|
this.pos = 0;
|
||||||
return parse.result;
|
this.nextToken = this.lexer.lex(this.pos, this.mode);
|
||||||
|
var parse = this.parseInput();
|
||||||
|
return parse;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses an entire input tree.
|
* Parses an entire input tree.
|
||||||
*/
|
*/
|
||||||
Parser.prototype.parseInput = function(pos) {
|
Parser.prototype.parseInput = function() {
|
||||||
// Parse an expression
|
// Parse an expression
|
||||||
var expression = this.parseExpression(pos, false);
|
var expression = this.parseExpression(false);
|
||||||
// If we succeeded, make sure there's an EOF at the end
|
// If we succeeded, make sure there's an EOF at the end
|
||||||
this.expect(expression.peek, "EOF");
|
this.expect("EOF", false);
|
||||||
return expression;
|
return expression;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -114,25 +128,25 @@ var endOfExpression = ["}", "\\end", "\\right", "&", "\\\\", "\\cr"];
|
||||||
* @param {?string} breakOnToken The token that the expression should end with,
|
* @param {?string} breakOnToken The token that the expression should end with,
|
||||||
* or `null` if something else should end the expression.
|
* or `null` if something else should end the expression.
|
||||||
*
|
*
|
||||||
* @return {ParseResult}
|
* @return {ParseNode}
|
||||||
*/
|
*/
|
||||||
Parser.prototype.parseExpression = function(pos, breakOnInfix, breakOnToken) {
|
Parser.prototype.parseExpression = function(breakOnInfix, breakOnToken) {
|
||||||
var body = [];
|
var body = [];
|
||||||
var lex = null;
|
|
||||||
// Keep adding atoms to the body until we can't parse any more atoms (either
|
// Keep adding atoms to the body until we can't parse any more atoms (either
|
||||||
// we reached the end, a }, or a \right)
|
// we reached the end, a }, or a \right)
|
||||||
while (true) {
|
while (true) {
|
||||||
lex = this.lexer.lex(pos, this.mode);
|
var lex = this.nextToken;
|
||||||
|
var pos = this.pos;
|
||||||
if (endOfExpression.indexOf(lex.text) !== -1) {
|
if (endOfExpression.indexOf(lex.text) !== -1) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (breakOnToken && lex.text === breakOnToken) {
|
if (breakOnToken && lex.text === breakOnToken) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
var atom = this.parseAtom(pos);
|
var atom = this.parseAtom();
|
||||||
if (!atom) {
|
if (!atom) {
|
||||||
if (!this.settings.throwOnError && lex.text[0] === "\\") {
|
if (!this.settings.throwOnError && lex.text[0] === "\\") {
|
||||||
var errorNode = this.handleUnsupportedCmd(lex.text);
|
var errorNode = this.handleUnsupportedCmd();
|
||||||
body.push(errorNode);
|
body.push(errorNode);
|
||||||
|
|
||||||
pos = lex.position;
|
pos = lex.position;
|
||||||
|
@ -141,15 +155,15 @@ Parser.prototype.parseExpression = function(pos, breakOnInfix, breakOnToken) {
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (breakOnInfix && atom.result.type === "infix") {
|
if (breakOnInfix && atom.type === "infix") {
|
||||||
|
// rewind so we can parse the infix atom again
|
||||||
|
this.pos = pos;
|
||||||
|
this.nextToken = lex;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
body.push(atom.result);
|
body.push(atom);
|
||||||
pos = atom.position;
|
|
||||||
}
|
}
|
||||||
var res = new ParseResult(this.handleInfixNodes(body), pos);
|
return this.handleInfixNodes(body);
|
||||||
res.peek = lex;
|
|
||||||
return res;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -211,31 +225,30 @@ var SUPSUB_GREEDINESS = 1;
|
||||||
/**
|
/**
|
||||||
* Handle a subscript or superscript with nice errors.
|
* Handle a subscript or superscript with nice errors.
|
||||||
*/
|
*/
|
||||||
Parser.prototype.handleSupSubscript = function(pos, symbol, name) {
|
Parser.prototype.handleSupSubscript = function(name) {
|
||||||
var group = this.parseGroup(pos);
|
var symbol = this.nextToken.text;
|
||||||
|
var symPos = this.pos;
|
||||||
|
this.consume();
|
||||||
|
var group = this.parseGroup();
|
||||||
|
|
||||||
if (!group) {
|
if (!group) {
|
||||||
var lex = this.lexer.lex(pos, this.mode);
|
if (!this.settings.throwOnError && this.nextToken.text[0] === "\\") {
|
||||||
|
return this.handleUnsupportedCmd();
|
||||||
if (!this.settings.throwOnError && lex.text[0] === "\\") {
|
|
||||||
return new ParseResult(
|
|
||||||
this.handleUnsupportedCmd(lex.text),
|
|
||||||
lex.position);
|
|
||||||
} else {
|
} else {
|
||||||
throw new ParseError(
|
throw new ParseError(
|
||||||
"Expected group after '" + symbol + "'", this.lexer, pos);
|
"Expected group after '" + symbol + "'", this.lexer, symPos + 1);
|
||||||
}
|
}
|
||||||
} else if (group.isFunction) {
|
} else if (group.isFunction) {
|
||||||
// ^ and _ have a greediness, so handle interactions with functions'
|
// ^ and _ have a greediness, so handle interactions with functions'
|
||||||
// greediness
|
// greediness
|
||||||
var funcGreediness = functions[group.result.result].greediness;
|
var funcGreediness = functions[group.result].greediness;
|
||||||
if (funcGreediness > SUPSUB_GREEDINESS) {
|
if (funcGreediness > SUPSUB_GREEDINESS) {
|
||||||
return this.parseFunction(pos);
|
return this.parseFunction(group);
|
||||||
} else {
|
} else {
|
||||||
throw new ParseError(
|
throw new ParseError(
|
||||||
"Got function '" + group.result.result + "' with no arguments " +
|
"Got function '" + group.result + "' with no arguments " +
|
||||||
"as " + name,
|
"as " + name,
|
||||||
this.lexer, pos);
|
this.lexer, symPos + 1);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return group.result;
|
return group.result;
|
||||||
|
@ -246,7 +259,8 @@ Parser.prototype.handleSupSubscript = function(pos, symbol, name) {
|
||||||
* Converts the textual input of an unsupported command into a text node
|
* Converts the textual input of an unsupported command into a text node
|
||||||
* contained within a color node whose color is determined by errorColor
|
* contained within a color node whose color is determined by errorColor
|
||||||
*/
|
*/
|
||||||
Parser.prototype.handleUnsupportedCmd = function(text) {
|
Parser.prototype.handleUnsupportedCmd = function() {
|
||||||
|
var text = this.nextToken.text;
|
||||||
var textordArray = [];
|
var textordArray = [];
|
||||||
|
|
||||||
for (var i = 0; i < text.length; i++) {
|
for (var i = 0; i < text.length; i++) {
|
||||||
|
@ -270,84 +284,71 @@ Parser.prototype.handleSupSubscript = function(pos, symbol, name) {
|
||||||
},
|
},
|
||||||
this.mode);
|
this.mode);
|
||||||
|
|
||||||
|
this.consume();
|
||||||
return colorNode;
|
return colorNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses a group with optional super/subscripts.
|
* Parses a group with optional super/subscripts.
|
||||||
*
|
*
|
||||||
* @return {?ParseResult}
|
* @return {?ParseNode}
|
||||||
*/
|
*/
|
||||||
Parser.prototype.parseAtom = function(pos) {
|
Parser.prototype.parseAtom = function() {
|
||||||
// The body of an atom is an implicit group, so that things like
|
// The body of an atom is an implicit group, so that things like
|
||||||
// \left(x\right)^2 work correctly.
|
// \left(x\right)^2 work correctly.
|
||||||
var base = this.parseImplicitGroup(pos);
|
var base = this.parseImplicitGroup();
|
||||||
|
|
||||||
// In text mode, we don't have superscripts or subscripts
|
// In text mode, we don't have superscripts or subscripts
|
||||||
if (this.mode === "text") {
|
if (this.mode === "text") {
|
||||||
return base;
|
return base;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle an empty base
|
// Note that base may be empty (i.e. null) at this point.
|
||||||
var currPos;
|
|
||||||
if (!base) {
|
|
||||||
currPos = pos;
|
|
||||||
base = undefined;
|
|
||||||
} else {
|
|
||||||
currPos = base.position;
|
|
||||||
}
|
|
||||||
|
|
||||||
var superscript;
|
var superscript;
|
||||||
var subscript;
|
var subscript;
|
||||||
var result;
|
|
||||||
while (true) {
|
while (true) {
|
||||||
// Lex the first token
|
// Lex the first token
|
||||||
var lex = this.lexer.lex(currPos, this.mode);
|
var lex = this.nextToken;
|
||||||
|
|
||||||
if (lex.text === "\\limits" || lex.text === "\\nolimits") {
|
if (lex.text === "\\limits" || lex.text === "\\nolimits") {
|
||||||
// We got a limit control
|
// We got a limit control
|
||||||
if (!base || base.result.type !== "op") {
|
if (!base || base.type !== "op") {
|
||||||
throw new ParseError("Limit controls must follow a math operator",
|
throw new ParseError("Limit controls must follow a math operator",
|
||||||
this.lexer, currPos);
|
this.lexer, this.pos);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
var limits = lex.text === "\\limits";
|
var limits = lex.text === "\\limits";
|
||||||
base.result.value.limits = limits;
|
base.value.limits = limits;
|
||||||
base.result.value.alwaysHandleSupSub = true;
|
base.value.alwaysHandleSupSub = true;
|
||||||
currPos = lex.position;
|
|
||||||
}
|
}
|
||||||
|
this.consume();
|
||||||
} else if (lex.text === "^") {
|
} else if (lex.text === "^") {
|
||||||
// We got a superscript start
|
// We got a superscript start
|
||||||
if (superscript) {
|
if (superscript) {
|
||||||
throw new ParseError(
|
throw new ParseError(
|
||||||
"Double superscript", this.lexer, currPos);
|
"Double superscript", this.lexer, this.pos);
|
||||||
}
|
}
|
||||||
result = this.handleSupSubscript(
|
superscript = this.handleSupSubscript("superscript");
|
||||||
lex.position, lex.text, "superscript");
|
|
||||||
currPos = result.position;
|
|
||||||
superscript = result.result;
|
|
||||||
} else if (lex.text === "_") {
|
} else if (lex.text === "_") {
|
||||||
// We got a subscript start
|
// We got a subscript start
|
||||||
if (subscript) {
|
if (subscript) {
|
||||||
throw new ParseError(
|
throw new ParseError(
|
||||||
"Double subscript", this.lexer, currPos);
|
"Double subscript", this.lexer, this.pos);
|
||||||
}
|
}
|
||||||
result = this.handleSupSubscript(
|
subscript = this.handleSupSubscript("subscript");
|
||||||
lex.position, lex.text, "subscript");
|
|
||||||
currPos = result.position;
|
|
||||||
subscript = result.result;
|
|
||||||
} else if (lex.text === "'") {
|
} else if (lex.text === "'") {
|
||||||
// We got a prime
|
// We got a prime
|
||||||
var prime = new ParseNode("textord", "\\prime", this.mode);
|
var prime = new ParseNode("textord", "\\prime", this.mode);
|
||||||
|
|
||||||
// Many primes can be grouped together, so we handle this here
|
// Many primes can be grouped together, so we handle this here
|
||||||
var primes = [prime];
|
var primes = [prime];
|
||||||
currPos = lex.position;
|
this.consume();
|
||||||
// Keep lexing tokens until we get something that's not a prime
|
// Keep lexing tokens until we get something that's not a prime
|
||||||
while ((lex = this.lexer.lex(currPos, this.mode)).text === "'") {
|
while (this.nextToken.text === "'") {
|
||||||
// For each one, add another prime to the list
|
// For each one, add another prime to the list
|
||||||
primes.push(prime);
|
primes.push(prime);
|
||||||
currPos = lex.position;
|
this.consume();
|
||||||
}
|
}
|
||||||
// Put them into an ordgroup as the superscript
|
// Put them into an ordgroup as the superscript
|
||||||
superscript = new ParseNode("ordgroup", primes, this.mode);
|
superscript = new ParseNode("ordgroup", primes, this.mode);
|
||||||
|
@ -359,13 +360,11 @@ Parser.prototype.parseAtom = function(pos) {
|
||||||
|
|
||||||
if (superscript || subscript) {
|
if (superscript || subscript) {
|
||||||
// If we got either a superscript or subscript, create a supsub
|
// If we got either a superscript or subscript, create a supsub
|
||||||
return new ParseResult(
|
return new ParseNode("supsub", {
|
||||||
new ParseNode("supsub", {
|
base: base,
|
||||||
base: base && base.result,
|
sup: superscript,
|
||||||
sup: superscript,
|
sub: subscript
|
||||||
sub: subscript
|
}, this.mode);
|
||||||
}, this.mode),
|
|
||||||
currPos);
|
|
||||||
} else {
|
} else {
|
||||||
// Otherwise return the original body
|
// Otherwise return the original body
|
||||||
return base;
|
return base;
|
||||||
|
@ -392,52 +391,47 @@ var styleFuncs = [
|
||||||
* small text {\Large large text} small text again
|
* small text {\Large large text} small text again
|
||||||
* It is also used for \left and \right to get the correct grouping.
|
* It is also used for \left and \right to get the correct grouping.
|
||||||
*
|
*
|
||||||
* @return {?ParseResult}
|
* @return {?ParseNode}
|
||||||
*/
|
*/
|
||||||
Parser.prototype.parseImplicitGroup = function(pos) {
|
Parser.prototype.parseImplicitGroup = function() {
|
||||||
var start = this.parseSymbol(pos);
|
var start = this.parseSymbol();
|
||||||
|
|
||||||
if (!start || !start.result) {
|
if (start == null) {
|
||||||
// If we didn't get anything we handle, fall back to parseFunction
|
// If we didn't get anything we handle, fall back to parseFunction
|
||||||
return this.parseFunction(pos);
|
return this.parseFunction();
|
||||||
}
|
}
|
||||||
|
|
||||||
var func = start.result.result;
|
var func = start.result;
|
||||||
var body;
|
var body;
|
||||||
|
|
||||||
if (func === "\\left") {
|
if (func === "\\left") {
|
||||||
// If we see a left:
|
// If we see a left:
|
||||||
// Parse the entire left function (including the delimiter)
|
// Parse the entire left function (including the delimiter)
|
||||||
var left = this.parseFunction(pos);
|
var left = this.parseFunction(start);
|
||||||
// Parse out the implicit body
|
// Parse out the implicit body
|
||||||
body = this.parseExpression(left.position, false);
|
body = this.parseExpression(false);
|
||||||
// Check the next token
|
// Check the next token
|
||||||
this.expect(body.peek, "\\right");
|
this.expect("\\right", false);
|
||||||
var right = this.parseFunction(body.position);
|
var right = this.parseFunction();
|
||||||
return new ParseResult(
|
return new ParseNode("leftright", {
|
||||||
new ParseNode("leftright", {
|
body: body,
|
||||||
body: body.result,
|
left: left.value.value,
|
||||||
left: left.result.value.value,
|
right: right.value.value
|
||||||
right: right.result.value.value
|
}, this.mode);
|
||||||
}, this.mode),
|
|
||||||
right.position);
|
|
||||||
} else if (func === "\\begin") {
|
} else if (func === "\\begin") {
|
||||||
// begin...end is similar to left...right
|
// begin...end is similar to left...right
|
||||||
var begin = this.parseFunction(pos);
|
var begin = this.parseFunction(start);
|
||||||
var envName = begin.result.value.name;
|
var envName = begin.value.name;
|
||||||
if (!environments.hasOwnProperty(envName)) {
|
if (!environments.hasOwnProperty(envName)) {
|
||||||
throw new ParseError(
|
throw new ParseError(
|
||||||
"No such environment: " + envName,
|
"No such environment: " + envName,
|
||||||
this.lexer, begin.result.value.namepos);
|
this.lexer, begin.value.namepos);
|
||||||
}
|
}
|
||||||
// Build the environment object. Arguments and other information will
|
// Build the environment object. Arguments and other information will
|
||||||
// be made available to the begin and end methods using properties.
|
// be made available to the begin and end methods using properties.
|
||||||
var env = environments[envName];
|
var env = environments[envName];
|
||||||
var args = [];
|
var args = this.parseArguments("\\begin{" + envName + "}", env);
|
||||||
var newPos = this.parseArguments(
|
|
||||||
begin.position, "\\begin{" + envName + "}", env, args);
|
|
||||||
var context = {
|
var context = {
|
||||||
pos: newPos,
|
|
||||||
mode: this.mode,
|
mode: this.mode,
|
||||||
envName: envName,
|
envName: envName,
|
||||||
parser: this,
|
parser: this,
|
||||||
|
@ -445,55 +439,57 @@ Parser.prototype.parseImplicitGroup = function(pos) {
|
||||||
positions: args.pop()
|
positions: args.pop()
|
||||||
};
|
};
|
||||||
var result = env.handler(context, args);
|
var result = env.handler(context, args);
|
||||||
var endLex = this.lexer.lex(result.position, this.mode);
|
this.expect("\\end", false);
|
||||||
this.expect(endLex, "\\end");
|
var end = this.parseFunction();
|
||||||
var end = this.parseFunction(result.position);
|
if (end.value.name !== envName) {
|
||||||
if (end.result.value.name !== envName) {
|
|
||||||
throw new ParseError(
|
throw new ParseError(
|
||||||
"Mismatch: \\begin{" + envName + "} matched " +
|
"Mismatch: \\begin{" + envName + "} matched " +
|
||||||
"by \\end{" + end.result.value.name + "}",
|
"by \\end{" + end.value.name + "}",
|
||||||
this.lexer, end.namepos);
|
this.lexer /* , end.value.namepos */);
|
||||||
|
// TODO: Add position to the above line and adjust test case,
|
||||||
|
// requires #385 to get merged first
|
||||||
}
|
}
|
||||||
result.position = end.position;
|
result.position = end.position;
|
||||||
return result;
|
return result;
|
||||||
} else if (utils.contains(sizeFuncs, func)) {
|
} else if (utils.contains(sizeFuncs, func)) {
|
||||||
// If we see a sizing function, parse out the implict body
|
// If we see a sizing function, parse out the implict body
|
||||||
body = this.parseExpression(start.result.position, false);
|
body = this.parseExpression(false);
|
||||||
return new ParseResult(
|
return new ParseNode("sizing", {
|
||||||
new ParseNode("sizing", {
|
// Figure out what size to use based on the list of functions above
|
||||||
// Figure out what size to use based on the list of functions above
|
size: "size" + (utils.indexOf(sizeFuncs, func) + 1),
|
||||||
size: "size" + (utils.indexOf(sizeFuncs, func) + 1),
|
value: body
|
||||||
value: body.result
|
}, this.mode);
|
||||||
}, this.mode),
|
|
||||||
body.position);
|
|
||||||
} else if (utils.contains(styleFuncs, func)) {
|
} else if (utils.contains(styleFuncs, func)) {
|
||||||
// If we see a styling function, parse out the implict body
|
// If we see a styling function, parse out the implict body
|
||||||
body = this.parseExpression(start.result.position, true);
|
body = this.parseExpression(true);
|
||||||
return new ParseResult(
|
return new ParseNode("styling", {
|
||||||
new ParseNode("styling", {
|
// Figure out what style to use by pulling out the style from
|
||||||
// Figure out what style to use by pulling out the style from
|
// the function name
|
||||||
// the function name
|
style: func.slice(1, func.length - 5),
|
||||||
style: func.slice(1, func.length - 5),
|
value: body
|
||||||
value: body.result
|
}, this.mode);
|
||||||
}, this.mode),
|
|
||||||
body.position);
|
|
||||||
} else {
|
} else {
|
||||||
// Defer to parseFunction if it's not a function we handle
|
// Defer to parseFunction if it's not a function we handle
|
||||||
return this.parseFunction(pos);
|
return this.parseFunction(start);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses an entire function, including its base and all of its arguments
|
* Parses an entire function, including its base and all of its arguments.
|
||||||
|
* The base might either have been parsed already, in which case
|
||||||
|
* it is provided as an argument, or it's the next group in the input.
|
||||||
*
|
*
|
||||||
* @return {?ParseResult}
|
* @param {ParseFuncOrArgument=} baseGroup optional as described above
|
||||||
|
* @return {?ParseNode}
|
||||||
*/
|
*/
|
||||||
Parser.prototype.parseFunction = function(pos) {
|
Parser.prototype.parseFunction = function(baseGroup) {
|
||||||
var baseGroup = this.parseGroup(pos);
|
if (!baseGroup) {
|
||||||
|
baseGroup = this.parseGroup();
|
||||||
|
}
|
||||||
|
|
||||||
if (baseGroup) {
|
if (baseGroup) {
|
||||||
if (baseGroup.isFunction) {
|
if (baseGroup.isFunction) {
|
||||||
var func = baseGroup.result.result;
|
var func = baseGroup.result;
|
||||||
var funcData = functions[func];
|
var funcData = functions[func];
|
||||||
if (this.mode === "text" && !funcData.allowedInText) {
|
if (this.mode === "text" && !funcData.allowedInText) {
|
||||||
throw new ParseError(
|
throw new ParseError(
|
||||||
|
@ -501,13 +497,9 @@ Parser.prototype.parseFunction = function(pos) {
|
||||||
this.lexer, baseGroup.position);
|
this.lexer, baseGroup.position);
|
||||||
}
|
}
|
||||||
|
|
||||||
var args = [];
|
var args = this.parseArguments(func, funcData);
|
||||||
var newPos = this.parseArguments(
|
|
||||||
baseGroup.result.position, func, funcData, args);
|
|
||||||
var result = this.callFunction(func, args, args.pop());
|
var result = this.callFunction(func, args, args.pop());
|
||||||
return new ParseResult(
|
return new ParseNode(result.type, result, this.mode);
|
||||||
new ParseNode(result.type, result, this.mode),
|
|
||||||
newPos);
|
|
||||||
} else {
|
} else {
|
||||||
return baseGroup.result;
|
return baseGroup.result;
|
||||||
}
|
}
|
||||||
|
@ -534,77 +526,73 @@ Parser.prototype.callFunction = function(name, args, positions) {
|
||||||
*
|
*
|
||||||
* @param {string} func "\name" or "\begin{name}"
|
* @param {string} func "\name" or "\begin{name}"
|
||||||
* @param {{numArgs:number,numOptionalArgs:number|undefined}} funcData
|
* @param {{numArgs:number,numOptionalArgs:number|undefined}} funcData
|
||||||
* @param {Array} args list of arguments to which new ones will be pushed
|
* @return the array of arguments, with the list of positions as last element
|
||||||
* @return the position after all arguments have been parsed
|
|
||||||
*/
|
*/
|
||||||
Parser.prototype.parseArguments = function(pos, func, funcData, args) {
|
Parser.prototype.parseArguments = function(func, funcData) {
|
||||||
var totalArgs = funcData.numArgs + funcData.numOptionalArgs;
|
var totalArgs = funcData.numArgs + funcData.numOptionalArgs;
|
||||||
if (totalArgs === 0) {
|
if (totalArgs === 0) {
|
||||||
return pos;
|
return [[this.pos]];
|
||||||
}
|
}
|
||||||
|
|
||||||
var newPos = pos;
|
|
||||||
var baseGreediness = funcData.greediness;
|
var baseGreediness = funcData.greediness;
|
||||||
var positions = [newPos];
|
var positions = [this.pos];
|
||||||
|
var args = [];
|
||||||
|
|
||||||
for (var i = 0; i < totalArgs; i++) {
|
for (var i = 0; i < totalArgs; i++) {
|
||||||
var argType = funcData.argTypes && funcData.argTypes[i];
|
var argType = funcData.argTypes && funcData.argTypes[i];
|
||||||
var arg;
|
var arg;
|
||||||
if (i < funcData.numOptionalArgs) {
|
if (i < funcData.numOptionalArgs) {
|
||||||
if (argType) {
|
if (argType) {
|
||||||
arg = this.parseSpecialGroup(newPos, argType, true);
|
arg = this.parseSpecialGroup(argType, true);
|
||||||
} else {
|
} else {
|
||||||
arg = this.parseOptionalGroup(newPos);
|
arg = this.parseOptionalGroup();
|
||||||
}
|
}
|
||||||
if (!arg) {
|
if (!arg) {
|
||||||
args.push(null);
|
args.push(null);
|
||||||
positions.push(newPos);
|
positions.push(this.pos);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (argType) {
|
if (argType) {
|
||||||
arg = this.parseSpecialGroup(newPos, argType);
|
arg = this.parseSpecialGroup(argType);
|
||||||
} else {
|
} else {
|
||||||
arg = this.parseGroup(newPos);
|
arg = this.parseGroup();
|
||||||
}
|
}
|
||||||
if (!arg) {
|
if (!arg) {
|
||||||
var lex = this.lexer.lex(newPos, this.mode);
|
if (!this.settings.throwOnError &&
|
||||||
|
this.nextToken.text[0] === "\\") {
|
||||||
if (!this.settings.throwOnError && lex.text[0] === "\\") {
|
|
||||||
arg = new ParseFuncOrArgument(
|
arg = new ParseFuncOrArgument(
|
||||||
new ParseResult(
|
this.handleUnsupportedCmd(this.nextToken.text),
|
||||||
this.handleUnsupportedCmd(lex.text),
|
|
||||||
lex.position),
|
|
||||||
false);
|
false);
|
||||||
} else {
|
} else {
|
||||||
throw new ParseError(
|
throw new ParseError(
|
||||||
"Expected group after '" + func + "'", this.lexer, pos);
|
"Expected group after '" + func + "'",
|
||||||
|
this.lexer, this.pos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var argNode;
|
var argNode;
|
||||||
if (arg.isFunction) {
|
if (arg.isFunction) {
|
||||||
var argGreediness =
|
var argGreediness =
|
||||||
functions[arg.result.result].greediness;
|
functions[arg.result].greediness;
|
||||||
if (argGreediness > baseGreediness) {
|
if (argGreediness > baseGreediness) {
|
||||||
argNode = this.parseFunction(newPos);
|
argNode = this.parseFunction(arg);
|
||||||
} else {
|
} else {
|
||||||
throw new ParseError(
|
throw new ParseError(
|
||||||
"Got function '" + arg.result.result + "' as " +
|
"Got function '" + arg.result + "' as " +
|
||||||
"argument to '" + func + "'",
|
"argument to '" + func + "'",
|
||||||
this.lexer, arg.result.position - 1);
|
this.lexer, this.pos - 1);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
argNode = arg.result;
|
argNode = arg.result;
|
||||||
}
|
}
|
||||||
args.push(argNode.result);
|
args.push(argNode);
|
||||||
positions.push(argNode.position);
|
positions.push(this.pos);
|
||||||
newPos = argNode.position;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
args.push(positions);
|
args.push(positions);
|
||||||
|
|
||||||
return newPos;
|
return args;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -614,7 +602,7 @@ Parser.prototype.parseArguments = function(pos, func, funcData, args) {
|
||||||
*
|
*
|
||||||
* @return {?ParseFuncOrArgument}
|
* @return {?ParseFuncOrArgument}
|
||||||
*/
|
*/
|
||||||
Parser.prototype.parseSpecialGroup = function(pos, innerMode, optional) {
|
Parser.prototype.parseSpecialGroup = function(innerMode, optional) {
|
||||||
var outerMode = this.mode;
|
var outerMode = this.mode;
|
||||||
// Handle `original` argTypes
|
// Handle `original` argTypes
|
||||||
if (innerMode === "original") {
|
if (innerMode === "original") {
|
||||||
|
@ -624,43 +612,46 @@ Parser.prototype.parseSpecialGroup = function(pos, innerMode, optional) {
|
||||||
if (innerMode === "color" || innerMode === "size") {
|
if (innerMode === "color" || innerMode === "size") {
|
||||||
// color and size modes are special because they should have braces and
|
// color and size modes are special because they should have braces and
|
||||||
// should only lex a single symbol inside
|
// should only lex a single symbol inside
|
||||||
var openBrace = this.lexer.lex(pos, outerMode);
|
var openBrace = this.nextToken;
|
||||||
if (optional && openBrace.text !== "[") {
|
if (optional && openBrace.text !== "[") {
|
||||||
// optional arguments should return null if they don't exist
|
// optional arguments should return null if they don't exist
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
this.expect(openBrace, optional ? "[" : "{");
|
// The call to expect will lex the token after the '{' in inner mode
|
||||||
var inner = this.lexer.lex(openBrace.position, innerMode);
|
this.mode = innerMode;
|
||||||
|
this.expect(optional ? "[" : "{");
|
||||||
|
var inner = this.nextToken;
|
||||||
|
this.mode = outerMode;
|
||||||
var data;
|
var data;
|
||||||
if (innerMode === "color") {
|
if (innerMode === "color") {
|
||||||
data = inner.text;
|
data = inner.text;
|
||||||
} else {
|
} else {
|
||||||
data = inner.data;
|
data = inner.data;
|
||||||
}
|
}
|
||||||
var closeBrace = this.lexer.lex(inner.position, outerMode);
|
this.consume(); // consume the token stored in inner
|
||||||
this.expect(closeBrace, optional ? "]" : "}");
|
this.expect(optional ? "]" : "}");
|
||||||
return new ParseFuncOrArgument(
|
return new ParseFuncOrArgument(
|
||||||
new ParseResult(
|
new ParseNode(innerMode, data, outerMode),
|
||||||
new ParseNode(innerMode, data, outerMode),
|
|
||||||
closeBrace.position),
|
|
||||||
false);
|
false);
|
||||||
} else if (innerMode === "text") {
|
} else if (innerMode === "text") {
|
||||||
// text mode is special because it should ignore the whitespace before
|
// text mode is special because it should ignore the whitespace before
|
||||||
// it
|
// it
|
||||||
var whitespace = this.lexer.lex(pos, "whitespace");
|
var whitespace = this.lexer.lex(this.pos, "whitespace");
|
||||||
pos = whitespace.position;
|
this.pos = whitespace.position;
|
||||||
}
|
}
|
||||||
|
|
||||||
// By the time we get here, innerMode is one of "text" or "math".
|
// By the time we get here, innerMode is one of "text" or "math".
|
||||||
// We switch the mode of the parser, recurse, then restore the old mode.
|
// We switch the mode of the parser, recurse, then restore the old mode.
|
||||||
this.mode = innerMode;
|
this.mode = innerMode;
|
||||||
|
this.nextToken = this.lexer.lex(this.pos, innerMode);
|
||||||
var res;
|
var res;
|
||||||
if (optional) {
|
if (optional) {
|
||||||
res = this.parseOptionalGroup(pos);
|
res = this.parseOptionalGroup();
|
||||||
} else {
|
} else {
|
||||||
res = this.parseGroup(pos);
|
res = this.parseGroup();
|
||||||
}
|
}
|
||||||
this.mode = outerMode;
|
this.mode = outerMode;
|
||||||
|
this.nextToken = this.lexer.lex(this.pos, outerMode);
|
||||||
return res;
|
return res;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -670,23 +661,20 @@ Parser.prototype.parseSpecialGroup = function(pos, innerMode, optional) {
|
||||||
*
|
*
|
||||||
* @return {?ParseFuncOrArgument}
|
* @return {?ParseFuncOrArgument}
|
||||||
*/
|
*/
|
||||||
Parser.prototype.parseGroup = function(pos) {
|
Parser.prototype.parseGroup = function() {
|
||||||
var start = this.lexer.lex(pos, this.mode);
|
|
||||||
// Try to parse an open brace
|
// Try to parse an open brace
|
||||||
if (start.text === "{") {
|
if (this.nextToken.text === "{") {
|
||||||
// If we get a brace, parse an expression
|
// If we get a brace, parse an expression
|
||||||
var expression = this.parseExpression(start.position, false);
|
this.consume();
|
||||||
|
var expression = this.parseExpression(false);
|
||||||
// Make sure we get a close brace
|
// Make sure we get a close brace
|
||||||
var closeBrace = this.lexer.lex(expression.position, this.mode);
|
this.expect("}");
|
||||||
this.expect(closeBrace, "}");
|
|
||||||
return new ParseFuncOrArgument(
|
return new ParseFuncOrArgument(
|
||||||
new ParseResult(
|
new ParseNode("ordgroup", expression, this.mode),
|
||||||
new ParseNode("ordgroup", expression.result, this.mode),
|
|
||||||
closeBrace.position),
|
|
||||||
false);
|
false);
|
||||||
} else {
|
} else {
|
||||||
// Otherwise, just return a nucleus
|
// Otherwise, just return a nucleus
|
||||||
return this.parseSymbol(pos);
|
return this.parseSymbol();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -695,19 +683,16 @@ Parser.prototype.parseGroup = function(pos) {
|
||||||
*
|
*
|
||||||
* @return {?ParseFuncOrArgument}
|
* @return {?ParseFuncOrArgument}
|
||||||
*/
|
*/
|
||||||
Parser.prototype.parseOptionalGroup = function(pos) {
|
Parser.prototype.parseOptionalGroup = function() {
|
||||||
var start = this.lexer.lex(pos, this.mode);
|
|
||||||
// Try to parse an open bracket
|
// Try to parse an open bracket
|
||||||
if (start.text === "[") {
|
if (this.nextToken.text === "[") {
|
||||||
// If we get a brace, parse an expression
|
// If we get a brace, parse an expression
|
||||||
var expression = this.parseExpression(start.position, false, "]");
|
this.consume();
|
||||||
|
var expression = this.parseExpression(false, "]");
|
||||||
// Make sure we get a close bracket
|
// Make sure we get a close bracket
|
||||||
var closeBracket = this.lexer.lex(expression.position, this.mode);
|
this.expect("]");
|
||||||
this.expect(closeBracket, "]");
|
|
||||||
return new ParseFuncOrArgument(
|
return new ParseFuncOrArgument(
|
||||||
new ParseResult(
|
new ParseNode("ordgroup", expression, this.mode),
|
||||||
new ParseNode("ordgroup", expression.result, this.mode),
|
|
||||||
closeBracket.position),
|
|
||||||
false);
|
false);
|
||||||
} else {
|
} else {
|
||||||
// Otherwise, return null,
|
// Otherwise, return null,
|
||||||
|
@ -721,23 +706,23 @@ Parser.prototype.parseOptionalGroup = function(pos) {
|
||||||
*
|
*
|
||||||
* @return {?ParseFuncOrArgument}
|
* @return {?ParseFuncOrArgument}
|
||||||
*/
|
*/
|
||||||
Parser.prototype.parseSymbol = function(pos) {
|
Parser.prototype.parseSymbol = function() {
|
||||||
var nucleus = this.lexer.lex(pos, this.mode);
|
var nucleus = this.nextToken;
|
||||||
|
|
||||||
if (functions[nucleus.text]) {
|
if (functions[nucleus.text]) {
|
||||||
|
this.consume();
|
||||||
// If there exists a function with this name, we return the function and
|
// If there exists a function with this name, we return the function and
|
||||||
// say that it is a function.
|
// say that it is a function.
|
||||||
return new ParseFuncOrArgument(
|
return new ParseFuncOrArgument(
|
||||||
new ParseResult(nucleus.text, nucleus.position),
|
nucleus.text,
|
||||||
true);
|
true);
|
||||||
} else if (symbols[this.mode][nucleus.text]) {
|
} else if (symbols[this.mode][nucleus.text]) {
|
||||||
|
this.consume();
|
||||||
// Otherwise if this is a no-argument function, find the type it
|
// Otherwise if this is a no-argument function, find the type it
|
||||||
// corresponds to in the symbols map
|
// corresponds to in the symbols map
|
||||||
return new ParseFuncOrArgument(
|
return new ParseFuncOrArgument(
|
||||||
new ParseResult(
|
new ParseNode(symbols[this.mode][nucleus.text].group,
|
||||||
new ParseNode(symbols[this.mode][nucleus.text].group,
|
nucleus.text, this.mode),
|
||||||
nucleus.text, this.mode),
|
|
||||||
nucleus.position),
|
|
||||||
false);
|
false);
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -3,39 +3,37 @@ var parseData = require("./parseData");
|
||||||
var ParseError = require("./ParseError");
|
var ParseError = require("./ParseError");
|
||||||
|
|
||||||
var ParseNode = parseData.ParseNode;
|
var ParseNode = parseData.ParseNode;
|
||||||
var ParseResult = parseData.ParseResult;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse the body of the environment, with rows delimited by \\ and
|
* Parse the body of the environment, with rows delimited by \\ and
|
||||||
* columns delimited by &, and create a nested list in row-major order
|
* columns delimited by &, and create a nested list in row-major order
|
||||||
* with one group per cell.
|
* with one group per cell.
|
||||||
*/
|
*/
|
||||||
function parseArray(parser, pos, result) {
|
function parseArray(parser, result) {
|
||||||
var row = [], body = [row], rowGaps = [];
|
var row = [], body = [row], rowGaps = [];
|
||||||
while (true) {
|
while (true) {
|
||||||
var cell = parser.parseExpression(pos, false, null);
|
var cell = parser.parseExpression(false, null);
|
||||||
row.push(new ParseNode("ordgroup", cell.result, parser.mode));
|
row.push(new ParseNode("ordgroup", cell, parser.mode));
|
||||||
pos = cell.position;
|
var next = parser.nextToken.text;
|
||||||
var next = cell.peek.text;
|
|
||||||
if (next === "&") {
|
if (next === "&") {
|
||||||
pos = cell.peek.position;
|
parser.consume();
|
||||||
} else if (next === "\\end") {
|
} else if (next === "\\end") {
|
||||||
break;
|
break;
|
||||||
} else if (next === "\\\\" || next === "\\cr") {
|
} else if (next === "\\\\" || next === "\\cr") {
|
||||||
var cr = parser.parseFunction(pos);
|
var cr = parser.parseFunction();
|
||||||
rowGaps.push(cr.result.value.size);
|
rowGaps.push(cr.value.size);
|
||||||
pos = cr.position;
|
|
||||||
row = [];
|
row = [];
|
||||||
body.push(row);
|
body.push(row);
|
||||||
} else {
|
} else {
|
||||||
|
// TODO: Clean up the following hack once #385 got merged
|
||||||
|
var pos = Math.min(parser.pos + 1, parser.lexer._input.length);
|
||||||
throw new ParseError("Expected & or \\\\ or \\end",
|
throw new ParseError("Expected & or \\\\ or \\end",
|
||||||
parser.lexer, cell.peek.position);
|
parser.lexer, pos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
result.body = body;
|
result.body = body;
|
||||||
result.rowGaps = rowGaps;
|
result.rowGaps = rowGaps;
|
||||||
return new ParseResult(
|
return new ParseNode(result.type, result, parser.mode);
|
||||||
new ParseNode(result.type, result, parser.mode), pos);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -55,7 +53,6 @@ function parseArray(parser, pos, result) {
|
||||||
* - context: information and references provided by the parser
|
* - context: information and references provided by the parser
|
||||||
* - args: an array of arguments passed to \begin{name}
|
* - args: an array of arguments passed to \begin{name}
|
||||||
* The context contains the following properties:
|
* The context contains the following properties:
|
||||||
* - pos: the current position of the parser.
|
|
||||||
* - envName: the name of the environment, one of the listed names.
|
* - envName: the name of the environment, one of the listed names.
|
||||||
* - parser: the parser object
|
* - parser: the parser object
|
||||||
* - lexer: the lexer object
|
* - lexer: the lexer object
|
||||||
|
@ -90,8 +87,6 @@ defineEnvironment("array", {
|
||||||
numArgs: 1
|
numArgs: 1
|
||||||
}, function(context, args) {
|
}, function(context, args) {
|
||||||
var colalign = args[0];
|
var colalign = args[0];
|
||||||
var lexer = context.lexer;
|
|
||||||
var positions = context.positions;
|
|
||||||
colalign = colalign.value.map ? colalign.value : [colalign];
|
colalign = colalign.value.map ? colalign.value : [colalign];
|
||||||
var cols = colalign.map(function(node) {
|
var cols = colalign.map(function(node) {
|
||||||
var ca = node.value;
|
var ca = node.value;
|
||||||
|
@ -108,14 +103,14 @@ defineEnvironment("array", {
|
||||||
}
|
}
|
||||||
throw new ParseError(
|
throw new ParseError(
|
||||||
"Unknown column alignment: " + node.value,
|
"Unknown column alignment: " + node.value,
|
||||||
lexer, positions[1]);
|
context.lexer, context.positions[1]);
|
||||||
});
|
});
|
||||||
var res = {
|
var res = {
|
||||||
type: "array",
|
type: "array",
|
||||||
cols: cols,
|
cols: cols,
|
||||||
hskipBeforeAndAfter: true // \@preamble in lttab.dtx
|
hskipBeforeAndAfter: true // \@preamble in lttab.dtx
|
||||||
};
|
};
|
||||||
res = parseArray(context.parser, context.pos, res);
|
res = parseArray(context.parser, res);
|
||||||
return res;
|
return res;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -142,10 +137,10 @@ defineEnvironment([
|
||||||
type: "array",
|
type: "array",
|
||||||
hskipBeforeAndAfter: false // \hskip -\arraycolsep in amsmath
|
hskipBeforeAndAfter: false // \hskip -\arraycolsep in amsmath
|
||||||
};
|
};
|
||||||
res = parseArray(context.parser, context.pos, res);
|
res = parseArray(context.parser, res);
|
||||||
if (delimiters) {
|
if (delimiters) {
|
||||||
res.result = new ParseNode("leftright", {
|
res = new ParseNode("leftright", {
|
||||||
body: [res.result],
|
body: [res],
|
||||||
left: delimiters[0],
|
left: delimiters[0],
|
||||||
right: delimiters[1]
|
right: delimiters[1]
|
||||||
}, context.mode);
|
}, context.mode);
|
||||||
|
@ -173,9 +168,9 @@ defineEnvironment("cases", {
|
||||||
postgap: 0
|
postgap: 0
|
||||||
}]
|
}]
|
||||||
};
|
};
|
||||||
res = parseArray(context.parser, context.pos, res);
|
res = parseArray(context.parser, res);
|
||||||
res.result = new ParseNode("leftright", {
|
res = new ParseNode("leftright", {
|
||||||
body: [res.result],
|
body: [res],
|
||||||
left: "\\{",
|
left: "\\{",
|
||||||
right: "."
|
right: "."
|
||||||
}, context.mode);
|
}, context.mode);
|
||||||
|
|
|
@ -7,17 +7,7 @@ function ParseNode(type, value, mode) {
|
||||||
this.mode = mode;
|
this.mode = mode;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* A result and final position returned by the `.parse...` functions.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
function ParseResult(result, newPosition, peek) {
|
|
||||||
this.result = result;
|
|
||||||
this.position = newPosition;
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
ParseNode: ParseNode,
|
ParseNode: ParseNode
|
||||||
ParseResult: ParseResult
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user