
Also, the MathBb-chrome test changed, to what I believe is the correct result? Not sure why it looked wrong before. Test plan: - `make test` - take screenshots, see nothing changed.
721 lines
24 KiB
JavaScript
721 lines
24 KiB
JavaScript
var functions = require("./functions");
|
|
var environments = require("./environments");
|
|
var Lexer = require("./Lexer");
|
|
var symbols = require("./symbols");
|
|
var utils = require("./utils");
|
|
|
|
var parseData = require("./parseData");
|
|
var ParseError = require("./ParseError");
|
|
|
|
/**
|
|
* This file contains the parser used to parse out a TeX expression from the
|
|
* input. Since TeX isn't context-free, standard parsers don't work particularly
|
|
* well.
|
|
*
|
|
* The strategy of this parser is as such:
|
|
*
|
|
* The main functions (the `.parse...` ones) take a position in the current
|
|
* parse string to parse tokens from. The lexer (found in Lexer.js, stored at
|
|
* this.lexer) also supports pulling out tokens at arbitrary places. When
|
|
* individual tokens are needed at a position, the lexer is called to pull out a
|
|
* token, which is then used.
|
|
*
|
|
* The main functions also take a mode that the parser is currently in
|
|
* (currently "math" or "text"), which denotes whether the current environment
|
|
* is a math-y one or a text-y one (e.g. inside \text). Currently, this serves
|
|
* to limit the functions which can be used in text mode.
|
|
*
|
|
* The main functions then return an object which contains the useful data that
|
|
* was parsed at its given point, and a new position at the end of the parsed
|
|
* data. The main functions can call each other and continue the parsing by
|
|
* using the returned position as a new starting point.
|
|
*
|
|
* There are also extra `.handle...` functions, which pull out some reused
|
|
* functionality into self-contained functions.
|
|
*
|
|
* The earlier functions return `ParseResult`s, which contain a ParseNode and a
|
|
* new position.
|
|
*
|
|
* The later functions (which are called deeper in the parse) sometimes return
|
|
* ParseFuncOrArgument, which contain a ParseResult as well as some data about
|
|
* 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.
|
|
*/
|
|
|
|
/**
|
|
* Main Parser class
|
|
*/
|
|
function Parser(input, settings) {
|
|
// Make a new lexer
|
|
this.lexer = new Lexer(input);
|
|
// Store the settings for use in parsing
|
|
this.settings = settings;
|
|
}
|
|
|
|
var ParseNode = parseData.ParseNode;
|
|
var ParseResult = parseData.ParseResult;
|
|
|
|
/**
|
|
* An initial function (without its arguments), or an argument to a function.
|
|
* The `result` argument should be a ParseResult.
|
|
*/
|
|
function ParseFuncOrArgument(result, isFunction) {
|
|
this.result = result;
|
|
// Is this a function (i.e. is it something defined in functions.js)?
|
|
this.isFunction = isFunction;
|
|
}
|
|
|
|
/**
|
|
* Checks a result to make sure it has the right type, and throws an
|
|
* appropriate error otherwise.
|
|
*/
|
|
Parser.prototype.expect = function(result, text) {
|
|
if (result.text !== text) {
|
|
throw new ParseError(
|
|
"Expected '" + text + "', got '" + result.text + "'",
|
|
this.lexer, result.position
|
|
);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Main parsing function, which parses an entire input.
|
|
*
|
|
* @return {?Array.<ParseNode>}
|
|
*/
|
|
Parser.prototype.parse = function(input) {
|
|
// Try to parse the input
|
|
var parse = this.parseInput(0, "math");
|
|
return parse.result;
|
|
};
|
|
|
|
/**
|
|
* Parses an entire input tree.
|
|
*/
|
|
Parser.prototype.parseInput = function(pos, mode) {
|
|
// Parse an expression
|
|
var expression = this.parseExpression(pos, mode, false);
|
|
// If we succeeded, make sure there's an EOF at the end
|
|
this.expect(expression.peek, "EOF");
|
|
return expression;
|
|
};
|
|
|
|
var endOfExpression = ["}", "\\end", "\\right", "&", "\\\\", "\\cr"];
|
|
|
|
/**
|
|
* Parses an "expression", which is a list of atoms.
|
|
*
|
|
* @param {boolean} breakOnInfix Should the parsing stop when we hit infix
|
|
* nodes? This happens when functions have higher precendence
|
|
* than infix nodes in implicit parses.
|
|
*
|
|
* @param {?string} breakOnToken The token that the expression should end with,
|
|
* or `null` if something else should end the expression.
|
|
*
|
|
* @return {ParseResult}
|
|
*/
|
|
Parser.prototype.parseExpression = function(pos, mode, breakOnInfix, breakOnToken) {
|
|
var body = [];
|
|
var lex = null;
|
|
// Keep adding atoms to the body until we can't parse any more atoms (either
|
|
// we reached the end, a }, or a \right)
|
|
while (true) {
|
|
lex = this.lexer.lex(pos, mode);
|
|
if (endOfExpression.indexOf(lex.text) !== -1) {
|
|
break;
|
|
}
|
|
if (breakOnToken && lex.text === breakOnToken) {
|
|
break;
|
|
}
|
|
var atom = this.parseAtom(pos, mode);
|
|
if (!atom) {
|
|
if (!this.settings.throwOnError && lex.text[0] === "\\") {
|
|
var errorNode = this.handleUnsupportedCmd(lex.text, mode);
|
|
body.push(errorNode);
|
|
|
|
pos = lex.position;
|
|
continue;
|
|
}
|
|
|
|
break;
|
|
}
|
|
if (breakOnInfix && atom.result.type === "infix") {
|
|
break;
|
|
}
|
|
body.push(atom.result);
|
|
pos = atom.position;
|
|
}
|
|
var res = new ParseResult(this.handleInfixNodes(body, mode), pos);
|
|
res.peek = lex;
|
|
return res;
|
|
};
|
|
|
|
/**
|
|
* Rewrites infix operators such as \over with corresponding commands such
|
|
* as \frac.
|
|
*
|
|
* There can only be one infix operator per group. If there's more than one
|
|
* then the expression is ambiguous. This can be resolved by adding {}.
|
|
*
|
|
* @returns {Array}
|
|
*/
|
|
Parser.prototype.handleInfixNodes = function (body, mode) {
|
|
var overIndex = -1;
|
|
var func;
|
|
var funcName;
|
|
|
|
for (var i = 0; i < body.length; i++) {
|
|
var node = body[i];
|
|
if (node.type === "infix") {
|
|
if (overIndex !== -1) {
|
|
throw new ParseError("only one infix operator per group",
|
|
this.lexer, -1);
|
|
}
|
|
overIndex = i;
|
|
funcName = node.value.replaceWith;
|
|
func = functions.funcs[funcName];
|
|
}
|
|
}
|
|
|
|
if (overIndex !== -1) {
|
|
var numerNode, denomNode;
|
|
|
|
var numerBody = body.slice(0, overIndex);
|
|
var denomBody = body.slice(overIndex + 1);
|
|
|
|
if (numerBody.length === 1 && numerBody[0].type === "ordgroup") {
|
|
numerNode = numerBody[0];
|
|
} else {
|
|
numerNode = new ParseNode("ordgroup", numerBody, mode);
|
|
}
|
|
|
|
if (denomBody.length === 1 && denomBody[0].type === "ordgroup") {
|
|
denomNode = denomBody[0];
|
|
} else {
|
|
denomNode = new ParseNode("ordgroup", denomBody, mode);
|
|
}
|
|
|
|
var value = func.handler(funcName, numerNode, denomNode);
|
|
return [new ParseNode(value.type, value, mode)];
|
|
} else {
|
|
return body;
|
|
}
|
|
};
|
|
|
|
// The greediness of a superscript or subscript
|
|
var SUPSUB_GREEDINESS = 1;
|
|
|
|
/**
|
|
* Handle a subscript or superscript with nice errors.
|
|
*/
|
|
Parser.prototype.handleSupSubscript = function(pos, mode, symbol, name) {
|
|
var group = this.parseGroup(pos, mode);
|
|
|
|
if (!group) {
|
|
var lex = this.lexer.lex(pos, mode);
|
|
|
|
if (!this.settings.throwOnError && lex.text[0] === "\\") {
|
|
return new ParseResult(
|
|
this.handleUnsupportedCmd(lex.text, mode),
|
|
lex.position);
|
|
} else {
|
|
throw new ParseError(
|
|
"Expected group after '" + symbol + "'", this.lexer, pos);
|
|
}
|
|
} else if (group.isFunction) {
|
|
// ^ and _ have a greediness, so handle interactions with functions'
|
|
// greediness
|
|
var funcGreediness = functions.funcs[group.result.result].greediness;
|
|
if (funcGreediness > SUPSUB_GREEDINESS) {
|
|
return this.parseFunction(pos, mode);
|
|
} else {
|
|
throw new ParseError(
|
|
"Got function '" + group.result.result + "' with no arguments " +
|
|
"as " + name,
|
|
this.lexer, pos);
|
|
}
|
|
} else {
|
|
return group.result;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Converts the textual input of an unsupported command into a text node
|
|
* contained within a color node whose color is determined by errorColor
|
|
*/
|
|
Parser.prototype.handleUnsupportedCmd = function(text, mode) {
|
|
var textordArray = [];
|
|
|
|
for (var i = 0; i < text.length; i++) {
|
|
textordArray.push(new ParseNode("textord", text[i], "text"));
|
|
}
|
|
|
|
var textNode = new ParseNode(
|
|
"text",
|
|
{
|
|
body: textordArray,
|
|
type: "text"
|
|
},
|
|
mode);
|
|
|
|
var colorNode = new ParseNode(
|
|
"color",
|
|
{
|
|
color: this.settings.errorColor,
|
|
value: [textNode],
|
|
type: "color"
|
|
},
|
|
mode);
|
|
|
|
return colorNode;
|
|
};
|
|
|
|
/**
|
|
* Parses a group with optional super/subscripts.
|
|
*
|
|
* @return {?ParseResult}
|
|
*/
|
|
Parser.prototype.parseAtom = function(pos, mode) {
|
|
// The body of an atom is an implicit group, so that things like
|
|
// \left(x\right)^2 work correctly.
|
|
var base = this.parseImplicitGroup(pos, mode);
|
|
|
|
// In text mode, we don't have superscripts or subscripts
|
|
if (mode === "text") {
|
|
return base;
|
|
}
|
|
|
|
// Handle an empty base
|
|
var currPos;
|
|
if (!base) {
|
|
currPos = pos;
|
|
base = undefined;
|
|
} else {
|
|
currPos = base.position;
|
|
}
|
|
|
|
var superscript;
|
|
var subscript;
|
|
var result;
|
|
while (true) {
|
|
// Lex the first token
|
|
var lex = this.lexer.lex(currPos, mode);
|
|
|
|
if (lex.text === "\\limits" || lex.text === "\\nolimits") {
|
|
// We got a limit control
|
|
if (!base || base.result.type !== "op") {
|
|
throw new ParseError("Limit controls must follow a math operator",
|
|
this.lexer, currPos);
|
|
}
|
|
else {
|
|
var limits = lex.text === "\\limits";
|
|
base.result.value.limits = limits;
|
|
base.result.value.alwaysHandleSupSub = true;
|
|
currPos = lex.position;
|
|
}
|
|
} else if (lex.text === "^") {
|
|
// We got a superscript start
|
|
if (superscript) {
|
|
throw new ParseError(
|
|
"Double superscript", this.lexer, currPos);
|
|
}
|
|
result = this.handleSupSubscript(
|
|
lex.position, mode, lex.text, "superscript");
|
|
currPos = result.position;
|
|
superscript = result.result;
|
|
} else if (lex.text === "_") {
|
|
// We got a subscript start
|
|
if (subscript) {
|
|
throw new ParseError(
|
|
"Double subscript", this.lexer, currPos);
|
|
}
|
|
result = this.handleSupSubscript(
|
|
lex.position, mode, lex.text, "subscript");
|
|
currPos = result.position;
|
|
subscript = result.result;
|
|
} else if (lex.text === "'") {
|
|
// We got a prime
|
|
var prime = new ParseNode("textord", "\\prime", mode);
|
|
|
|
// Many primes can be grouped together, so we handle this here
|
|
var primes = [prime];
|
|
currPos = lex.position;
|
|
// Keep lexing tokens until we get something that's not a prime
|
|
while ((lex = this.lexer.lex(currPos, mode)).text === "'") {
|
|
// For each one, add another prime to the list
|
|
primes.push(prime);
|
|
currPos = lex.position;
|
|
}
|
|
// Put them into an ordgroup as the superscript
|
|
superscript = new ParseNode("ordgroup", primes, mode);
|
|
} else {
|
|
// If it wasn't ^, _, or ', stop parsing super/subscripts
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (superscript || subscript) {
|
|
// If we got either a superscript or subscript, create a supsub
|
|
return new ParseResult(
|
|
new ParseNode("supsub", {
|
|
base: base && base.result,
|
|
sup: superscript,
|
|
sub: subscript
|
|
}, mode),
|
|
currPos);
|
|
} else {
|
|
// Otherwise return the original body
|
|
return base;
|
|
}
|
|
};
|
|
|
|
// A list of the size-changing functions, for use in parseImplicitGroup
|
|
var sizeFuncs = [
|
|
"\\tiny", "\\scriptsize", "\\footnotesize", "\\small", "\\normalsize",
|
|
"\\large", "\\Large", "\\LARGE", "\\huge", "\\Huge"
|
|
];
|
|
|
|
// A list of the style-changing functions, for use in parseImplicitGroup
|
|
var styleFuncs = [
|
|
"\\displaystyle", "\\textstyle", "\\scriptstyle", "\\scriptscriptstyle"
|
|
];
|
|
|
|
/**
|
|
* Parses an implicit group, which is a group that starts at the end of a
|
|
* specified, and ends right before a higher explicit group ends, or at EOL. It
|
|
* is used for functions that appear to affect the current style, like \Large or
|
|
* \textrm, where instead of keeping a style we just pretend that there is an
|
|
* implicit grouping after it until the end of the group. E.g.
|
|
* small text {\Large large text} small text again
|
|
* It is also used for \left and \right to get the correct grouping.
|
|
*
|
|
* @return {?ParseResult}
|
|
*/
|
|
Parser.prototype.parseImplicitGroup = function(pos, mode) {
|
|
var start = this.parseSymbol(pos, mode);
|
|
|
|
if (!start || !start.result) {
|
|
// If we didn't get anything we handle, fall back to parseFunction
|
|
return this.parseFunction(pos, mode);
|
|
}
|
|
|
|
var func = start.result.result;
|
|
var body;
|
|
|
|
if (func === "\\left") {
|
|
// If we see a left:
|
|
// Parse the entire left function (including the delimiter)
|
|
var left = this.parseFunction(pos, mode);
|
|
// Parse out the implicit body
|
|
body = this.parseExpression(left.position, mode, false);
|
|
// Check the next token
|
|
this.expect(body.peek, "\\right");
|
|
var right = this.parseFunction(body.position, mode);
|
|
return new ParseResult(
|
|
new ParseNode("leftright", {
|
|
body: body.result,
|
|
left: left.result.value.value,
|
|
right: right.result.value.value
|
|
}, mode),
|
|
right.position);
|
|
} else if (func === "\\begin") {
|
|
// begin...end is similar to left...right
|
|
var begin = this.parseFunction(pos, mode);
|
|
var envName = begin.result.value.name;
|
|
if (!environments.hasOwnProperty(envName)) {
|
|
throw new ParseError(
|
|
"No such environment: " + envName,
|
|
this.lexer, begin.result.value.namepos);
|
|
}
|
|
// Build the environment object. Arguments and other information will
|
|
// be made available to the begin and end methods using properties.
|
|
var env = environments[envName];
|
|
var args = [null, mode, envName];
|
|
var newPos = this.parseArguments(
|
|
begin.position, mode, "\\begin{" + envName + "}", env, args);
|
|
args[0] = newPos;
|
|
var result = env.handler.apply(this, args);
|
|
var endLex = this.lexer.lex(result.position, mode);
|
|
this.expect(endLex, "\\end");
|
|
var end = this.parseFunction(result.position, mode);
|
|
if (end.result.value.name !== envName) {
|
|
throw new ParseError(
|
|
"Mismatch: \\begin{" + envName + "} matched " +
|
|
"by \\end{" + end.result.value.name + "}",
|
|
this.lexer, end.namepos);
|
|
}
|
|
result.position = end.position;
|
|
return result;
|
|
} else if (utils.contains(sizeFuncs, func)) {
|
|
// If we see a sizing function, parse out the implict body
|
|
body = this.parseExpression(start.result.position, mode, false);
|
|
return new ParseResult(
|
|
new ParseNode("sizing", {
|
|
// Figure out what size to use based on the list of functions above
|
|
size: "size" + (utils.indexOf(sizeFuncs, func) + 1),
|
|
value: body.result
|
|
}, mode),
|
|
body.position);
|
|
} else if (utils.contains(styleFuncs, func)) {
|
|
// If we see a styling function, parse out the implict body
|
|
body = this.parseExpression(start.result.position, mode, true);
|
|
return new ParseResult(
|
|
new ParseNode("styling", {
|
|
// Figure out what style to use by pulling out the style from
|
|
// the function name
|
|
style: func.slice(1, func.length - 5),
|
|
value: body.result
|
|
}, mode),
|
|
body.position);
|
|
} else {
|
|
// Defer to parseFunction if it's not a function we handle
|
|
return this.parseFunction(pos, mode);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Parses an entire function, including its base and all of its arguments
|
|
*
|
|
* @return {?ParseResult}
|
|
*/
|
|
Parser.prototype.parseFunction = function(pos, mode) {
|
|
var baseGroup = this.parseGroup(pos, mode);
|
|
|
|
if (baseGroup) {
|
|
if (baseGroup.isFunction) {
|
|
var func = baseGroup.result.result;
|
|
var funcData = functions.funcs[func];
|
|
if (mode === "text" && !funcData.allowedInText) {
|
|
throw new ParseError(
|
|
"Can't use function '" + func + "' in text mode",
|
|
this.lexer, baseGroup.position);
|
|
}
|
|
|
|
var args = [func];
|
|
var newPos = this.parseArguments(
|
|
baseGroup.result.position, mode, func, funcData, args);
|
|
var result = functions.funcs[func].handler.apply(this, args);
|
|
return new ParseResult(
|
|
new ParseNode(result.type, result, mode),
|
|
newPos);
|
|
} else {
|
|
return baseGroup.result;
|
|
}
|
|
} else {
|
|
return null;
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Parses the arguments of a function or environment
|
|
*
|
|
* @param {string} func "\name" or "\begin{name}"
|
|
* @param {{numArgs:number,numOptionalArgs:number|undefined}} funcData
|
|
* @param {Array} args list of arguments to which new ones will be pushed
|
|
* @return the position after all arguments have been parsed
|
|
*/
|
|
Parser.prototype.parseArguments = function(pos, mode, func, funcData, args) {
|
|
var totalArgs = funcData.numArgs + funcData.numOptionalArgs;
|
|
if (totalArgs === 0) {
|
|
return pos;
|
|
}
|
|
|
|
var newPos = pos;
|
|
var baseGreediness = funcData.greediness;
|
|
var positions = [newPos];
|
|
|
|
for (var i = 0; i < totalArgs; i++) {
|
|
var argType = funcData.argTypes && funcData.argTypes[i];
|
|
var arg;
|
|
if (i < funcData.numOptionalArgs) {
|
|
if (argType) {
|
|
arg = this.parseSpecialGroup(newPos, argType, mode, true);
|
|
} else {
|
|
arg = this.parseOptionalGroup(newPos, mode);
|
|
}
|
|
if (!arg) {
|
|
args.push(null);
|
|
positions.push(newPos);
|
|
continue;
|
|
}
|
|
} else {
|
|
if (argType) {
|
|
arg = this.parseSpecialGroup(newPos, argType, mode);
|
|
} else {
|
|
arg = this.parseGroup(newPos, mode);
|
|
}
|
|
if (!arg) {
|
|
var lex = this.lexer.lex(newPos, mode);
|
|
|
|
if (!this.settings.throwOnError && lex.text[0] === "\\") {
|
|
arg = new ParseFuncOrArgument(
|
|
new ParseResult(
|
|
this.handleUnsupportedCmd(lex.text, mode),
|
|
lex.position),
|
|
false);
|
|
} else {
|
|
throw new ParseError(
|
|
"Expected group after '" + func + "'", this.lexer, pos);
|
|
}
|
|
}
|
|
}
|
|
var argNode;
|
|
if (arg.isFunction) {
|
|
var argGreediness =
|
|
functions.funcs[arg.result.result].greediness;
|
|
if (argGreediness > baseGreediness) {
|
|
argNode = this.parseFunction(newPos, mode);
|
|
} else {
|
|
throw new ParseError(
|
|
"Got function '" + arg.result.result + "' as " +
|
|
"argument to '" + func + "'",
|
|
this.lexer, arg.result.position - 1);
|
|
}
|
|
} else {
|
|
argNode = arg.result;
|
|
}
|
|
args.push(argNode.result);
|
|
positions.push(argNode.position);
|
|
newPos = argNode.position;
|
|
}
|
|
|
|
args.push(positions);
|
|
|
|
return newPos;
|
|
};
|
|
|
|
|
|
/**
|
|
* Parses a group when the mode is changing. Takes a position, a new mode, and
|
|
* an outer mode that is used to parse the outside.
|
|
*
|
|
* @return {?ParseFuncOrArgument}
|
|
*/
|
|
Parser.prototype.parseSpecialGroup = function(pos, mode, outerMode, optional) {
|
|
// Handle `original` argTypes
|
|
if (mode === "original") {
|
|
mode = outerMode;
|
|
}
|
|
|
|
if (mode === "color" || mode === "size") {
|
|
// color and size modes are special because they should have braces and
|
|
// should only lex a single symbol inside
|
|
var openBrace = this.lexer.lex(pos, outerMode);
|
|
if (optional && openBrace.text !== "[") {
|
|
// optional arguments should return null if they don't exist
|
|
return null;
|
|
}
|
|
this.expect(openBrace, optional ? "[" : "{");
|
|
var inner = this.lexer.lex(openBrace.position, mode);
|
|
var data;
|
|
if (mode === "color") {
|
|
data = inner.text;
|
|
} else {
|
|
data = inner.data;
|
|
}
|
|
var closeBrace = this.lexer.lex(inner.position, outerMode);
|
|
this.expect(closeBrace, optional ? "]" : "}");
|
|
return new ParseFuncOrArgument(
|
|
new ParseResult(
|
|
new ParseNode(mode, data, outerMode),
|
|
closeBrace.position),
|
|
false);
|
|
} else if (mode === "text") {
|
|
// text mode is special because it should ignore the whitespace before
|
|
// it
|
|
var whitespace = this.lexer.lex(pos, "whitespace");
|
|
pos = whitespace.position;
|
|
}
|
|
|
|
if (optional) {
|
|
return this.parseOptionalGroup(pos, mode);
|
|
} else {
|
|
return this.parseGroup(pos, mode);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Parses a group, which is either a single nucleus (like "x") or an expression
|
|
* in braces (like "{x+y}")
|
|
*
|
|
* @return {?ParseFuncOrArgument}
|
|
*/
|
|
Parser.prototype.parseGroup = function(pos, mode) {
|
|
var start = this.lexer.lex(pos, mode);
|
|
// Try to parse an open brace
|
|
if (start.text === "{") {
|
|
// If we get a brace, parse an expression
|
|
var expression = this.parseExpression(start.position, mode, false);
|
|
// Make sure we get a close brace
|
|
var closeBrace = this.lexer.lex(expression.position, mode);
|
|
this.expect(closeBrace, "}");
|
|
return new ParseFuncOrArgument(
|
|
new ParseResult(
|
|
new ParseNode("ordgroup", expression.result, mode),
|
|
closeBrace.position),
|
|
false);
|
|
} else {
|
|
// Otherwise, just return a nucleus
|
|
return this.parseSymbol(pos, mode);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Parses a group, which is an expression in brackets (like "[x+y]")
|
|
*
|
|
* @return {?ParseFuncOrArgument}
|
|
*/
|
|
Parser.prototype.parseOptionalGroup = function(pos, mode) {
|
|
var start = this.lexer.lex(pos, mode);
|
|
// Try to parse an open bracket
|
|
if (start.text === "[") {
|
|
// If we get a brace, parse an expression
|
|
var expression = this.parseExpression(start.position, mode, false, "]");
|
|
// Make sure we get a close bracket
|
|
var closeBracket = this.lexer.lex(expression.position, mode);
|
|
this.expect(closeBracket, "]");
|
|
return new ParseFuncOrArgument(
|
|
new ParseResult(
|
|
new ParseNode("ordgroup", expression.result, mode),
|
|
closeBracket.position),
|
|
false);
|
|
} else {
|
|
// Otherwise, return null,
|
|
return null;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Parse a single symbol out of the string. Here, we handle both the functions
|
|
* we have defined, as well as the single character symbols
|
|
*
|
|
* @return {?ParseFuncOrArgument}
|
|
*/
|
|
Parser.prototype.parseSymbol = function(pos, mode) {
|
|
var nucleus = this.lexer.lex(pos, mode);
|
|
|
|
if (functions.funcs[nucleus.text]) {
|
|
// If there exists a function with this name, we return the function and
|
|
// say that it is a function.
|
|
return new ParseFuncOrArgument(
|
|
new ParseResult(nucleus.text, nucleus.position),
|
|
true);
|
|
} else if (symbols[mode][nucleus.text]) {
|
|
// Otherwise if this is a no-argument function, find the type it
|
|
// corresponds to in the symbols map
|
|
return new ParseFuncOrArgument(
|
|
new ParseResult(
|
|
new ParseNode(symbols[mode][nucleus.text].group,
|
|
nucleus.text, mode),
|
|
nucleus.position),
|
|
false);
|
|
} else {
|
|
return null;
|
|
}
|
|
};
|
|
|
|
Parser.prototype.ParseNode = ParseNode;
|
|
|
|
module.exports = Parser;
|