diff --git a/src/Parser.js b/src/Parser.js index 01566cc71..47fa25038 100644 --- a/src/Parser.js +++ b/src/Parser.js @@ -195,7 +195,8 @@ Parser.prototype.handleInfixNodes = function (body, mode) { denomNode = new ParseNode("ordgroup", denomBody, mode); } - var value = func.handler(funcName, numerNode, denomNode); + var value = this.callFunction( + funcName, [numerNode, denomNode], null); return [new ParseNode(value.type, value, mode)]; } else { return body; @@ -430,11 +431,18 @@ Parser.prototype.parseImplicitGroup = function(pos, mode) { // 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 args = []; var newPos = this.parseArguments( begin.position, mode, "\\begin{" + envName + "}", env, args); - args[0] = newPos; - var result = env.handler.apply(this, args); + var context = { + pos: newPos, + mode: mode, + envName: envName, + parser: this, + lexer: this.lexer, + positions: args.pop() + }; + var result = env.handler(context, args); var endLex = this.lexer.lex(result.position, mode); this.expect(endLex, "\\end"); var end = this.parseFunction(result.position, mode); @@ -491,10 +499,10 @@ Parser.prototype.parseFunction = function(pos, mode) { this.lexer, baseGroup.position); } - var args = [func]; + var args = []; var newPos = this.parseArguments( baseGroup.result.position, mode, func, funcData, args); - var result = functions[func].handler.apply(this, args); + var result = this.callFunction(func, args, args.pop()); return new ParseResult( new ParseNode(result.type, result, mode), newPos); @@ -506,6 +514,18 @@ Parser.prototype.parseFunction = function(pos, mode) { } }; +/** + * Call a function handler with a suitable context and arguments. + */ +Parser.prototype.callFunction = function(name, args, positions) { + var context = { + funcName: name, + parser: this, + lexer: this.lexer, + positions: positions + }; + return functions[name].handler(context, args); +}; /** * Parses the arguments of a function or environment diff --git a/src/environments.js b/src/environments.js index c323d433e..71a866c9d 100644 --- a/src/environments.js +++ b/src/environments.js @@ -50,14 +50,17 @@ function parseArray(parser, pos, mode, result) { * - numOptionalArgs: (optional) Just like for a function * A bare number instead of that object indicates the numArgs value. * - * The handler function will receive the following arguments: + * The handler function will receive two arguments + * - context: information and references provided by the parser + * - args: an array of arguments passed to \begin{name} + * The context contains the following properties: * - pos: the current position of the parser. * - mode: the current parsing mode. * - envName: the name of the environment, one of the listed names. - * - [args]: the arguments passed to \begin. - * - positions: the positions associated with these arguments. - * The handler is called with `this` referring to the parser. - * It must return a ParseResult. + * - parser: the parser object + * - lexer: the lexer object + * - positions: the positions associated with these arguments from args. + * The handler must return a ParseResult. */ function defineEnvironment(names, props, handler) { @@ -85,8 +88,10 @@ function defineEnvironment(names, props, handler) { // is part of the source2e.pdf file of LaTeX2e source documentation. defineEnvironment("array", { numArgs: 1 -}, function(pos, mode, envName, colalign, positions) { - var parser = this; +}, function(context, args) { + var colalign = args[0]; + var lexer = context.lexer; + var positions = context.positions; colalign = colalign.value.map ? colalign.value : [colalign]; var cols = colalign.map(function(node) { var ca = node.value; @@ -103,14 +108,14 @@ defineEnvironment("array", { } throw new ParseError( "Unknown column alignment: " + node.value, - parser.lexer, positions[1]); + lexer, positions[1]); }); var res = { type: "array", cols: cols, hskipBeforeAndAfter: true // \@preamble in lttab.dtx }; - res = parseArray(parser, pos, mode, res); + res = parseArray(context.parser, context.pos, context.mode, res); return res; }); @@ -124,7 +129,7 @@ defineEnvironment([ "vmatrix", "Vmatrix" ], { -}, function(pos, mode, envName) { +}, function(context) { var delimiters = { "matrix": null, "pmatrix": ["(", ")"], @@ -132,18 +137,18 @@ defineEnvironment([ "Bmatrix": ["\\{", "\\}"], "vmatrix": ["|", "|"], "Vmatrix": ["\\Vert", "\\Vert"] - }[envName]; + }[context.envName]; var res = { type: "array", hskipBeforeAndAfter: false // \hskip -\arraycolsep in amsmath }; - res = parseArray(this, pos, mode, res); + res = parseArray(context.parser, context.pos, context.mode, res); if (delimiters) { res.result = new ParseNode("leftright", { body: [res.result], left: delimiters[0], right: delimiters[1] - }, mode); + }, context.mode); } return res; }); @@ -152,7 +157,7 @@ defineEnvironment([ // \def\arraystretch{1.2}% // \left\{\begin{array}{@{}l@{\quad}l@{}} … \end{array}\right. defineEnvironment("cases", { -}, function(pos, mode, envName) { +}, function(context) { var res = { type: "array", arraystretch: 1.2, @@ -168,11 +173,11 @@ defineEnvironment("cases", { postgap: 0 }] }; - res = parseArray(this, pos, mode, res); + res = parseArray(context.parser, context.pos, context.mode, res); res.result = new ParseNode("leftright", { body: [res.result], left: "\\{", right: "." - }, mode); + }, context.mode); return res; }); diff --git a/src/functions.js b/src/functions.js index 777b1096b..ee676591b 100644 --- a/src/functions.js +++ b/src/functions.js @@ -2,9 +2,9 @@ var utils = require("./utils"); var ParseError = require("./ParseError"); /* This file contains a list of functions that we parse, identified by - * the calls to declareFunction. + * the calls to defineFunction. * - * The first argument to declareFunction is a single name or a list of names. + * The first argument to defineFunction is a single name or a list of names. * All functions named in such a list will share a single implementation. * * Each declared function can have associated properties, which @@ -58,14 +58,17 @@ var ParseError = require("./ParseError"); * * The last argument is that implementation, the handler for the function(s). * It is called to handle these functions and their arguments. - * Its own arguments are: - * - func: the text of the function - * - [args]: the next arguments are the arguments to the function, - * of which there are numArgs of them + * It receives two arguments: + * - context contains information and references provided by the parser + * - args is an array of arguments obtained from TeX input + * The context contains the following properties: + * - funcName: the text (i.e. name) of the function, including \ + * - parser: the parser object + * - lexer: the lexer object * - positions: the positions in the overall string of the function - * and the arguments. Should only be used to produce - * error messages - * The handler is called with `this` referring to the parser. + * and the arguments. + * The latter three should only be used to produce error messages. + * * The function should return an object with the following keys: * - type: The type of element that this is. This is then used in * buildHTML/buildMathML to determine which function @@ -99,7 +102,9 @@ function defineFunction(names, props, handler) { defineFunction("\\sqrt", { numArgs: 1, numOptionalArgs: 1 -}, function(func, index, body, positions) { +}, function(context, args) { + var index = args[0]; + var body = args[1]; return { type: "sqrt", body: body, @@ -112,7 +117,8 @@ defineFunction("\\text", { numArgs: 1, argTypes: ["text"], greediness: 2 -}, function(func, body) { +}, function(context, args) { + var body = args[0]; // Since the corresponding buildHTML/buildMathML function expects a // list of elements, we normalize for different kinds of arguments // TODO(emily): maybe this should be done somewhere else @@ -135,7 +141,9 @@ defineFunction("\\color", { allowedInText: true, greediness: 3, argTypes: ["color", "original"] -}, function(func, color, body) { +}, function(context, args) { + var color = args[0]; + var body = args[1]; // Normalize the different kinds of bodies (see \text above) var inner; if (body.type === "ordgroup") { @@ -154,7 +162,8 @@ defineFunction("\\color", { // An overline defineFunction("\\overline", { numArgs: 1 -}, function(func, body) { +}, function(context, args) { + var body = args[0]; return { type: "overline", body: body @@ -166,7 +175,10 @@ defineFunction("\\rule", { numArgs: 2, numOptionalArgs: 1, argTypes: ["size", "size", "size"] -}, function(func, shift, width, height) { +}, function(context, args) { + var shift = args[0]; + var width = args[1]; + var height = args[2]; return { type: "rule", shift: shift && shift.value, @@ -178,7 +190,7 @@ defineFunction("\\rule", { // A KaTeX logo defineFunction("\\KaTeX", { numArgs: 0 -}, function(func) { +}, function(context) { return { type: "katex" }; @@ -186,7 +198,8 @@ defineFunction("\\KaTeX", { defineFunction("\\phantom", { numArgs: 1 -}, function(func, body) { +}, function(context, args) { + var body = args[0]; var inner; if (body.type === "ordgroup") { inner = body.value; @@ -260,7 +273,8 @@ defineFunction([ numArgs: 1, allowedInText: true, greediness: 3 -}, function(func, body) { +}, function(context, args) { + var body = args[0]; var atoms; if (body.type === "ordgroup") { atoms = body.value; @@ -270,7 +284,7 @@ defineFunction([ return { type: "color", - color: "katex-" + func.slice(1), + color: "katex-" + context.funcName.slice(1), value: atoms }; }); @@ -287,12 +301,12 @@ defineFunction([ "\\tan","\\tanh" ], { numArgs: 0 -}, function(func) { +}, function(context) { return { type: "op", limits: false, symbol: false, - body: func + body: context.funcName }; }); @@ -302,12 +316,12 @@ defineFunction([ "\\min", "\\Pr", "\\sup" ], { numArgs: 0 -}, function(func) { +}, function(context) { return { type: "op", limits: true, symbol: false, - body: func + body: context.funcName }; }); @@ -316,12 +330,12 @@ defineFunction([ "\\int", "\\iint", "\\iiint", "\\oint" ], { numArgs: 0 -}, function(func) { +}, function(context) { return { type: "op", limits: false, symbol: true, - body: func + body: context.funcName }; }); @@ -332,12 +346,12 @@ defineFunction([ "\\bigoplus", "\\bigodot", "\\bigsqcup", "\\smallint" ], { numArgs: 0 -}, function(func) { +}, function(context) { return { type: "op", limits: true, symbol: true, - body: func + body: context.funcName }; }); @@ -348,13 +362,15 @@ defineFunction([ ], { numArgs: 2, greediness: 2 -}, function(func, numer, denom) { +}, function(context, args) { + var numer = args[0]; + var denom = args[1]; var hasBarLine; var leftDelim = null; var rightDelim = null; var size = "auto"; - switch (func) { + switch (context.funcName) { case "\\dfrac": case "\\frac": case "\\tfrac": @@ -371,7 +387,7 @@ defineFunction([ throw new Error("Unrecognized genfrac command"); } - switch (func) { + switch (context.funcName) { case "\\dfrac": case "\\dbinom": size = "display"; @@ -397,9 +413,10 @@ defineFunction([ defineFunction(["\\llap", "\\rlap"], { numArgs: 1, allowedInText: true -}, function(func, body) { +}, function(context, args) { + var body = args[0]; return { - type: func.slice(1), + type: context.funcName.slice(1), body: body }; }); @@ -413,17 +430,18 @@ defineFunction([ "\\left", "\\right" ], { numArgs: 1 -}, function(func, delim, positions) { +}, function(context, args) { + var delim = args[0]; if (!utils.contains(delimiters, delim.value)) { throw new ParseError( "Invalid delimiter: '" + delim.value + "' after '" + - func + "'", - this.lexer, positions[1]); + context.funcName + "'", + context.lexer, context.positions[1]); } // \left and \right are caught somewhere in Parser.js, which is // why this data doesn't match what is in buildHTML. - if (func === "\\left" || func === "\\right") { + if (context.funcName === "\\left" || context.funcName === "\\right") { return { type: "leftright", value: delim.value @@ -431,8 +449,8 @@ defineFunction([ } else { return { type: "delimsizing", - size: delimiterSizes[func].size, - delimType: delimiterSizes[func].type, + size: delimiterSizes[context.funcName].size, + delimType: delimiterSizes[context.funcName].type, value: delim.value }; } @@ -464,7 +482,9 @@ defineFunction([ ], { numArgs: 1, greediness: 2 -}, function (func, body) { +}, function (context, args) { + var body = args[0]; + var func = context.funcName; if (func in fontAliases) { func = fontAliases[func]; } @@ -483,10 +503,11 @@ defineFunction([ // "\\widetilde", "\\widehat" ], { numArgs: 1 -}, function(func, base) { +}, function(context, args) { + var base = args[0]; return { type: "accent", - accent: func, + accent: context.funcName, base: base }; }); @@ -494,9 +515,9 @@ defineFunction([ // Infix generalized fractions defineFunction(["\\over", "\\choose"], { numArgs: 0 -}, function (func) { +}, function (context) { var replaceWith; - switch (func) { + switch (context.funcName) { case "\\over": replaceWith = "\\frac"; break; @@ -517,7 +538,8 @@ defineFunction(["\\\\", "\\cr"], { numArgs: 0, numOptionalArgs: 1, argTypes: ["size"] -}, function(func, size) { +}, function(context, args) { + var size = args[0]; return { type: "cr", size: size @@ -528,11 +550,12 @@ defineFunction(["\\\\", "\\cr"], { defineFunction(["\\begin", "\\end"], { numArgs: 1, argTypes: ["text"] -}, function(func, nameGroup, positions) { +}, function(context, args) { + var nameGroup = args[0]; if (nameGroup.type !== "ordgroup") { throw new ParseError( "Invalid environment name", - this.lexer, positions[1]); + context.lexer, context.positions[1]); } var name = ""; for (var i = 0; i < nameGroup.value.length; ++i) { @@ -541,6 +564,6 @@ defineFunction(["\\begin", "\\end"], { return { type: "environment", name: name, - namepos: positions[1] + namepos: context.positions[1] }; });