|
|
|
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"); |
|
|
|
global_str = "" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function Parser(input, settings) { |
|
|
|
this.lexer = new Lexer(input); |
|
|
|
this.settings = settings; |
|
} |
|
|
|
var ParseNode = parseData.ParseNode; |
|
|
|
|
|
|
|
|
|
|
|
function ParseFuncOrArgument(result, isFunction) { |
|
this.result = result; |
|
|
|
this.isFunction = isFunction; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Parser.prototype.expect = function(text, consume) { |
|
if (this.nextToken.text !== text) { |
|
throw new ParseError( |
|
"Expected '" + text + "', got '" + this.nextToken.text + "'", |
|
this.lexer, this.nextToken.position |
|
); |
|
} |
|
if (consume !== false) { |
|
this.consume(); |
|
} |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
Parser.prototype.consume = function() { |
|
this.pos = this.nextToken.position; |
|
|
|
global_str = global_str + " " + this.nextToken.text |
|
this.nextToken = this.lexer.lex(this.pos, this.mode); |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
Parser.prototype.parse = function() { |
|
|
|
this.mode = "math"; |
|
this.pos = 0; |
|
this.nextToken = this.lexer.lex(this.pos, this.mode); |
|
var parse = this.parseInput(); |
|
return parse; |
|
}; |
|
|
|
|
|
|
|
|
|
Parser.prototype.parseInput = function() { |
|
|
|
var expression = this.parseExpression(false); |
|
|
|
this.expect("EOF", false); |
|
return expression; |
|
}; |
|
|
|
var endOfExpression = ["}", "\\end", "\\right", "&", "\\\\", "\\cr"]; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Parser.prototype.parseExpression = function(breakOnInfix, breakOnToken) { |
|
var body = []; |
|
|
|
|
|
while (true) { |
|
var lex = this.nextToken; |
|
var pos = this.pos; |
|
if (endOfExpression.indexOf(lex.text) !== -1) { |
|
break; |
|
} |
|
if (breakOnToken && lex.text === breakOnToken) { |
|
break; |
|
} |
|
var atom = this.parseAtom(); |
|
if (!atom) { |
|
if (!this.settings.throwOnError && lex.text[0] === "\\") { |
|
var errorNode = this.handleUnsupportedCmd(); |
|
body.push(errorNode); |
|
|
|
pos = lex.position; |
|
continue; |
|
} |
|
|
|
break; |
|
} |
|
if (breakOnInfix && atom.type === "infix") { |
|
|
|
this.pos = pos; |
|
this.nextToken = lex; |
|
break; |
|
} |
|
body.push(atom); |
|
} |
|
return this.handleInfixNodes(body); |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Parser.prototype.handleInfixNodes = function(body) { |
|
var overIndex = -1; |
|
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; |
|
} |
|
} |
|
|
|
if (overIndex !== -1) { |
|
var numerNode; |
|
var 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, this.mode); |
|
} |
|
|
|
if (denomBody.length === 1 && denomBody[0].type === "ordgroup") { |
|
denomNode = denomBody[0]; |
|
} else { |
|
denomNode = new ParseNode("ordgroup", denomBody, this.mode); |
|
} |
|
|
|
var value = this.callFunction( |
|
funcName, [numerNode, denomNode], null); |
|
return [new ParseNode(value.type, value, this.mode)]; |
|
} else { |
|
return body; |
|
} |
|
}; |
|
|
|
|
|
var SUPSUB_GREEDINESS = 1; |
|
|
|
|
|
|
|
|
|
Parser.prototype.handleSupSubscript = function(name) { |
|
var symbol = this.nextToken.text; |
|
var symPos = this.pos; |
|
this.consume(); |
|
var group = this.parseGroup(); |
|
|
|
if (!group) { |
|
if (!this.settings.throwOnError && this.nextToken.text[0] === "\\") { |
|
return this.handleUnsupportedCmd(); |
|
} else { |
|
|
|
|
|
|
|
|
|
|
|
} |
|
} else if (group.isFunction) { |
|
|
|
|
|
var funcGreediness = functions[group.result].greediness; |
|
if (funcGreediness > SUPSUB_GREEDINESS) { |
|
return this.parseFunction(group); |
|
} else { |
|
throw new ParseError( |
|
"Got function '" + group.result + "' with no arguments " + |
|
"as " + name, |
|
this.lexer, symPos + 1); |
|
} |
|
} else { |
|
return group.result; |
|
} |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
Parser.prototype.handleUnsupportedCmd = function() { |
|
var text = this.nextToken.text; |
|
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", |
|
}, |
|
this.mode); |
|
|
|
var colorNode = new ParseNode( |
|
"color", |
|
{ |
|
color: this.settings.errorColor, |
|
value: [textNode], |
|
type: "color", |
|
}, |
|
this.mode); |
|
|
|
this.consume(); |
|
return colorNode; |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
Parser.prototype.parseAtom = function() { |
|
|
|
|
|
var base = this.parseImplicitGroup(); |
|
|
|
|
|
if (this.mode === "text") { |
|
return base; |
|
} |
|
|
|
|
|
|
|
var superscript; |
|
var subscript; |
|
while (true) { |
|
|
|
var lex = this.nextToken; |
|
|
|
if (lex.text === "\\limits" || lex.text === "\\nolimits") { |
|
|
|
if (!base || base.type !== "op") { |
|
throw new ParseError( |
|
"Limit controls must follow a math operator", |
|
this.lexer, this.pos); |
|
} else { |
|
var limits = lex.text === "\\limits"; |
|
base.value.limits = limits; |
|
base.value.alwaysHandleSupSub = true; |
|
} |
|
this.consume(); |
|
} else if (lex.text === "^") { |
|
|
|
|
|
|
|
|
|
|
|
superscript = this.handleSupSubscript("superscript"); |
|
} else if (lex.text === "_") { |
|
|
|
|
|
|
|
|
|
|
|
subscript = this.handleSupSubscript("subscript"); |
|
} else if (lex.text === "'") { |
|
|
|
var prime = new ParseNode("textord", "\\prime", this.mode); |
|
|
|
|
|
var primes = [prime]; |
|
this.consume(); |
|
|
|
while (this.nextToken.text === "'") { |
|
|
|
primes.push(prime); |
|
this.consume(); |
|
} |
|
|
|
superscript = new ParseNode("ordgroup", primes, this.mode); |
|
} else { |
|
|
|
break; |
|
} |
|
} |
|
|
|
if (superscript || subscript) { |
|
|
|
return new ParseNode("supsub", { |
|
base: base, |
|
sup: superscript, |
|
sub: subscript, |
|
}, this.mode); |
|
} else { |
|
|
|
return base; |
|
} |
|
}; |
|
|
|
|
|
var sizeFuncs = [ |
|
"\\tiny", "\\scriptsize", "\\footnotesize", "\\small", "\\normalsize", |
|
"\\large", "\\Large", "\\LARGE", "\\huge", "\\Huge", "\\textrm", "\\rm", "\\cal", |
|
"\\bf", "\\siptstyle", "\\boldmath", "\\it" |
|
]; |
|
|
|
|
|
var styleFuncs = [ |
|
"\\displaystyle", "\\textstyle", "\\scriptstyle", "\\scriptscriptstyle", |
|
]; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Parser.prototype.parseImplicitGroup = function() { |
|
var start = this.parseSymbol(); |
|
|
|
if (start == null) { |
|
|
|
return this.parseFunction(); |
|
} |
|
|
|
var func = start.result; |
|
var body; |
|
if (func === "\\left") { |
|
|
|
|
|
var left = this.parseFunction(start); |
|
|
|
body = this.parseExpression(false); |
|
|
|
this.expect("\\right", false); |
|
var right = this.parseFunction(); |
|
return new ParseNode("leftright", { |
|
body: body, |
|
left: left.value.value, |
|
right: right.value.value, |
|
}, this.mode); |
|
} else if (func === "\\begin") { |
|
|
|
var begin = this.parseFunction(start); |
|
var envName = begin.value.name; |
|
var name = (begin.value.name + "") |
|
|
|
global_str = global_str.substring(0, global_str.length - (name.length * 2 + 2)) + name + "}" |
|
|
|
if (!environments.hasOwnProperty(envName)) { |
|
throw new ParseError( |
|
"No such environment: " + envName, |
|
this.lexer, begin.value.namepos); |
|
} |
|
|
|
|
|
var env = environments[envName]; |
|
var args = this.parseArguments("\\begin{" + envName + "}", env); |
|
var context = { |
|
mode: this.mode, |
|
envName: envName, |
|
parser: this, |
|
lexer: this.lexer, |
|
positions: args.pop(), |
|
}; |
|
var result = env.handler(context, args); |
|
this.expect("\\end", false); |
|
var end = this.parseFunction(); |
|
|
|
var name = (begin.value.name + "") |
|
|
|
global_str = global_str.substring(0, global_str.length - (name.length * 2 + 2)) + name + "}" |
|
if (end.value.name !== envName) { |
|
throw new ParseError( |
|
"Mismatch: \\begin{" + envName + "} matched " + |
|
"by \\end{" + end.value.name + "}", |
|
this.lexer ); |
|
|
|
|
|
} |
|
result.position = end.position; |
|
|
|
return result; |
|
|
|
} else if (func.value == "\\matrix" || func.value == "\\pmatrix" || func.value == "\\cases") { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
envName = func.value.slice(1); |
|
var env = environments[envName]; |
|
|
|
this.expect("{", true); |
|
var context = { |
|
mode: this.mode, |
|
envName: envName, |
|
parser: this, |
|
lexer: this.lexer |
|
}; |
|
|
|
var result = env.handler(context, {} ); |
|
|
|
this.expect("}", true); |
|
|
|
var next = this.nextToken.text; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return result; |
|
|
|
} else if (utils.contains(sizeFuncs, func)) { |
|
|
|
body = this.parseExpression(false); |
|
|
|
return new ParseNode("sizing", { |
|
|
|
original: func, |
|
size: "size" + (utils.indexOf(sizeFuncs, func) + 1), |
|
value: body, |
|
}, this.mode); |
|
} else if (utils.contains(styleFuncs, func)) { |
|
|
|
body = this.parseExpression(true); |
|
return new ParseNode("styling", { |
|
|
|
|
|
original: func, |
|
style: func.slice(1, func.length - 5), |
|
value: body, |
|
}, this.mode); |
|
} else { |
|
|
|
return this.parseFunction(start); |
|
} |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Parser.prototype.parseFunction = function(baseGroup) { |
|
if (!baseGroup) { |
|
baseGroup = this.parseGroup(); |
|
} |
|
|
|
if (baseGroup) { |
|
if (baseGroup.isFunction) { |
|
var func = baseGroup.result; |
|
var funcData = functions[func]; |
|
if (this.mode === "text" && !funcData.allowedInText) { |
|
|
|
|
|
|
|
} |
|
|
|
var args = this.parseArguments(func, funcData); |
|
var result = this.callFunction(func, args, args.pop()); |
|
return new ParseNode(result.type, result, this.mode); |
|
} else { |
|
return baseGroup.result; |
|
} |
|
} else { |
|
return null; |
|
} |
|
}; |
|
|
|
|
|
|
|
|
|
Parser.prototype.callFunction = function(name, args, positions) { |
|
var context = { |
|
funcName: name, |
|
parser: this, |
|
lexer: this.lexer, |
|
positions: positions, |
|
}; |
|
return functions[name].handler(context, args); |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Parser.prototype.parseArguments = function(func, funcData) { |
|
var totalArgs = funcData.numArgs + funcData.numOptionalArgs; |
|
if (totalArgs === 0) { |
|
return [[this.pos]]; |
|
} |
|
|
|
var baseGreediness = funcData.greediness; |
|
var positions = [this.pos]; |
|
var args = []; |
|
|
|
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(argType, true); |
|
} else { |
|
arg = this.parseOptionalGroup(); |
|
} |
|
if (!arg) { |
|
args.push(null); |
|
positions.push(this.pos); |
|
continue; |
|
} |
|
} else { |
|
if (argType) { |
|
arg = this.parseSpecialGroup(argType); |
|
} else { |
|
arg = this.parseGroup(); |
|
} |
|
if (!arg) { |
|
if (!this.settings.throwOnError && |
|
this.nextToken.text[0] === "\\") { |
|
arg = new ParseFuncOrArgument( |
|
this.handleUnsupportedCmd(this.nextToken.text), |
|
false); |
|
} else { |
|
throw new ParseError( |
|
"Expected group after '" + func + "'", |
|
this.lexer, this.pos); |
|
} |
|
} |
|
} |
|
var argNode; |
|
if (arg.isFunction) { |
|
var argGreediness = |
|
functions[arg.result].greediness; |
|
if (argGreediness > baseGreediness) { |
|
argNode = this.parseFunction(arg); |
|
} else { |
|
|
|
|
|
|
|
|
|
} |
|
} else { |
|
argNode = arg.result; |
|
} |
|
args.push(argNode); |
|
positions.push(this.pos); |
|
} |
|
|
|
args.push(positions); |
|
|
|
return args; |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Parser.prototype.parseSpecialGroup = function(innerMode, optional) { |
|
var outerMode = this.mode; |
|
|
|
if (innerMode === "original") { |
|
innerMode = outerMode; |
|
} |
|
|
|
if (innerMode === "color" || innerMode === "size") { |
|
|
|
|
|
var openBrace = this.nextToken; |
|
if (optional && openBrace.text !== "[") { |
|
|
|
return null; |
|
} |
|
|
|
this.mode = innerMode; |
|
this.expect(optional ? "[" : "{"); |
|
var inner = this.nextToken; |
|
this.mode = outerMode; |
|
var data; |
|
if (innerMode === "color") { |
|
data = inner.text; |
|
} else { |
|
data = inner.data; |
|
} |
|
this.consume(); |
|
this.expect(optional ? "]" : "}"); |
|
return new ParseFuncOrArgument( |
|
new ParseNode(innerMode, data, outerMode), |
|
false); |
|
} else if (innerMode === "text") { |
|
|
|
|
|
var whitespace = this.lexer.lex(this.pos, "whitespace"); |
|
this.pos = whitespace.position; |
|
} |
|
|
|
|
|
|
|
this.mode = innerMode; |
|
this.nextToken = this.lexer.lex(this.pos, innerMode); |
|
var res; |
|
if (optional) { |
|
res = this.parseOptionalGroup(); |
|
} else { |
|
res = this.parseGroup(); |
|
} |
|
this.mode = outerMode; |
|
this.nextToken = this.lexer.lex(this.pos, outerMode); |
|
return res; |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Parser.prototype.parseGroup = function() { |
|
|
|
if (this.nextToken.text === "{") { |
|
|
|
this.consume(); |
|
var expression = this.parseExpression(false); |
|
|
|
this.expect("}"); |
|
return new ParseFuncOrArgument( |
|
new ParseNode("ordgroup", expression, this.mode), |
|
false); |
|
} else { |
|
|
|
return this.parseSymbol(); |
|
} |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
Parser.prototype.parseOptionalGroup = function() { |
|
|
|
if (this.nextToken.text === "[") { |
|
|
|
this.consume(); |
|
var expression = this.parseExpression(false, "]"); |
|
|
|
this.expect("]"); |
|
return new ParseFuncOrArgument( |
|
new ParseNode("ordgroup", expression, this.mode), |
|
false); |
|
} else { |
|
|
|
return null; |
|
} |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Parser.prototype.parseSymbol = function() { |
|
var nucleus = this.nextToken; |
|
|
|
if (functions[nucleus.text]) { |
|
this.consume(); |
|
|
|
|
|
return new ParseFuncOrArgument( |
|
nucleus.text, |
|
true); |
|
} else if (symbols[this.mode][nucleus.text]) { |
|
this.consume(); |
|
|
|
|
|
return new ParseFuncOrArgument( |
|
new ParseNode(symbols[this.mode][nucleus.text].group, |
|
nucleus.text, this.mode), |
|
false); |
|
} else if (nucleus.text == "EOF" || nucleus.text == "{") { |
|
return null; |
|
|
|
} else { |
|
this.consume(); |
|
|
|
return new ParseFuncOrArgument( |
|
new ParseNode(symbols["math"]["\\sigma"].group, |
|
nucleus.text, this.mode), |
|
false); |
|
|
|
|
|
} |
|
}; |
|
|
|
Parser.prototype.ParseNode = ParseNode; |
|
|
|
module.exports = Parser; |
|
|