diff --git a/src/functions.js b/src/functions.js index a34effb5b..2b2bfbe6b 100644 --- a/src/functions.js +++ b/src/functions.js @@ -1,13 +1,18 @@ var utils = require("./utils"); var ParseError = require("./ParseError"); -// This file contains a list of functions that we parse. The functions map -// contains the following data: - -/* - * Keys are the name of the functions to parse - * The data contains the following keys: +/* This file contains a list of functions that we parse, identified by + * the calls to declareFunction. + * + * The first argument to declareFunction 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 + * include the following: + * * - numArgs: The number of arguments the function takes. + * If this is the only property, it can be passed as a number + * instead of an element of a properties object. * - argTypes: (optional) An array corresponding to each argument of the * function, giving the type of argument that should be parsed. Its * length should be equal to `numArgs + numOptionalArgs`. Valid @@ -50,14 +55,17 @@ var ParseError = require("./ParseError"); * should parse. If the optional arguments aren't found, * `null` will be passed to the handler in their place. * (default 0) - * - handler: The function that is called to handle this function and its - * arguments. The arguments are: + * + * 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 * - 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. * 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 @@ -66,26 +74,45 @@ var ParseError = require("./ParseError"); * in to the function in buildHTML/buildMathML as `group.value`. */ -var functions = { +function declareFunction(names, props, handler) { + if (typeof names === "string") { + names = [names]; + } + if (typeof props === "number") { + props = { numArgs: props }; + } + // Set default values of functions + var data = { + numArgs: props.numArgs, + argTypes: props.argTypes, + greediness: (props.greediness === undefined) ? 1 : props.greediness, + allowedInText: !!props.allowedInText, + numOptionalArgs: props.numOptionalArgs || 0, + handler: handler + }; + for (var i = 0; i < names.length; ++i) { + module.exports[names[i]] = data; + } +} + // A normal square root - "\\sqrt": { + declareFunction("\\sqrt", { numArgs: 1, - numOptionalArgs: 1, - handler: function(func, index, body, positions) { + numOptionalArgs: 1 + }, function(func, index, body, positions) { return { type: "sqrt", body: body, index: index }; - } - }, + }); // Some non-mathy text - "\\text": { + declareFunction("\\text", { numArgs: 1, argTypes: ["text"], - greediness: 2, - handler: function(func, body) { + greediness: 2 + }, function(func, body) { // 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 @@ -100,16 +127,15 @@ var functions = { type: "text", body: inner }; - } - }, + }); // A two-argument custom color - "\\color": { + declareFunction("\\color", { numArgs: 2, allowedInText: true, greediness: 3, - argTypes: ["color", "original"], - handler: function(func, color, body) { + argTypes: ["color", "original"] + }, function(func, color, body) { // Normalize the different kinds of bodies (see \text above) var inner; if (body.type === "ordgroup") { @@ -123,48 +149,44 @@ var functions = { color: color.value, value: inner }; - } - }, + }); // An overline - "\\overline": { - numArgs: 1, - handler: function(func, body) { + declareFunction("\\overline", { + numArgs: 1 + }, function(func, body) { return { type: "overline", body: body }; - } - }, + }); // A box of the width and height - "\\rule": { + declareFunction("\\rule", { numArgs: 2, numOptionalArgs: 1, - argTypes: ["size", "size", "size"], - handler: function(func, shift, width, height) { + argTypes: ["size", "size", "size"] + }, function(func, shift, width, height) { return { type: "rule", shift: shift && shift.value, width: width.value, height: height.value }; - } - }, + }); // A KaTeX logo - "\\KaTeX": { - numArgs: 0, - handler: function(func) { + declareFunction("\\KaTeX", { + numArgs: 0 + }, function(func) { return { type: "katex" }; - } - }, + }); - "\\phantom": { - numArgs: 1, - handler: function(func, body) { + declareFunction("\\phantom", { + numArgs: 1 + }, function(func, body) { var inner; if (body.type === "ordgroup") { inner = body.value; @@ -176,9 +198,7 @@ var functions = { type: "phantom", value: inner }; - } - } -}; + }); // Extra data needed for the delimiter handler down below var delimiterSizes = { @@ -221,18 +241,8 @@ var fontAliases = { "\\frak": "\\mathfrak" }; -/* - * This is a list of functions which each have the same function but have - * different names so that we don't have to duplicate the data a bunch of times. - * Each element in the list is an object with the following keys: - * - funcs: A list of function names to be associated with the data - * - data: An objecty with the same data as in each value of the `function` - * table above - */ -var duplicatedFunctions = [ // Single-argument color functions - { - funcs: [ + declareFunction([ "\\blue", "\\orange", "\\pink", "\\red", "\\green", "\\gray", "\\purple", "\\blueA", "\\blueB", "\\blueC", "\\blueD", "\\blueE", @@ -246,12 +256,11 @@ var duplicatedFunctions = [ "\\grayA", "\\grayB", "\\grayC", "\\grayD", "\\grayE", "\\grayF", "\\grayG", "\\grayH", "\\grayI", "\\kaBlue", "\\kaGreen" - ], - data: { + ], { numArgs: 1, allowedInText: true, - greediness: 3, - handler: function(func, body) { + greediness: 3 + }, function(func, body) { var atoms; if (body.type === "ordgroup") { atoms = body.value; @@ -264,102 +273,82 @@ var duplicatedFunctions = [ color: "katex-" + func.slice(1), value: atoms }; - } - } - }, + }); // There are 2 flags for operators; whether they produce limits in // displaystyle, and whether they are symbols and should grow in // displaystyle. These four groups cover the four possible choices. // No limits, not symbols - { - funcs: [ + declareFunction([ "\\arcsin", "\\arccos", "\\arctan", "\\arg", "\\cos", "\\cosh", "\\cot", "\\coth", "\\csc", "\\deg", "\\dim", "\\exp", "\\hom", "\\ker", "\\lg", "\\ln", "\\log", "\\sec", "\\sin", "\\sinh", "\\tan","\\tanh" - ], - data: { - numArgs: 0, - handler: function(func) { + ], { + numArgs: 0 + }, function(func) { return { type: "op", limits: false, symbol: false, body: func }; - } - } - }, + }); // Limits, not symbols - { - funcs: [ + declareFunction([ "\\det", "\\gcd", "\\inf", "\\lim", "\\liminf", "\\limsup", "\\max", "\\min", "\\Pr", "\\sup" - ], - data: { - numArgs: 0, - handler: function(func) { + ], { + numArgs: 0 + }, function(func) { return { type: "op", limits: true, symbol: false, body: func }; - } - } - }, + }); // No limits, symbols - { - funcs: [ + declareFunction([ "\\int", "\\iint", "\\iiint", "\\oint" - ], - data: { - numArgs: 0, - handler: function(func) { + ], { + numArgs: 0 + }, function(func) { return { type: "op", limits: false, symbol: true, body: func }; - } - } - }, + }); // Limits, symbols - { - funcs: [ + declareFunction([ "\\coprod", "\\bigvee", "\\bigwedge", "\\biguplus", "\\bigcap", "\\bigcup", "\\intop", "\\prod", "\\sum", "\\bigotimes", "\\bigoplus", "\\bigodot", "\\bigsqcup", "\\smallint" - ], - data: { - numArgs: 0, - handler: function(func) { + ], { + numArgs: 0 + }, function(func) { return { type: "op", limits: true, symbol: true, body: func }; - } - } - }, + }); // Fractions - { - funcs: [ + declareFunction([ "\\dfrac", "\\frac", "\\tfrac", "\\dbinom", "\\binom", "\\tbinom" - ], - data: { + ], { numArgs: 2, - greediness: 2, - handler: function(func, numer, denom) { + greediness: 2 + }, function(func, numer, denom) { var hasBarLine; var leftDelim = null; var rightDelim = null; @@ -402,37 +391,29 @@ var duplicatedFunctions = [ rightDelim: rightDelim, size: size }; - } - } - }, + }); // Left and right overlap functions - { - funcs: ["\\llap", "\\rlap"], - data: { + declareFunction(["\\llap", "\\rlap"], { numArgs: 1, - allowedInText: true, - handler: function(func, body) { + allowedInText: true + }, function(func, body) { return { type: func.slice(1), body: body }; - } - } - }, + }); // Delimiter functions - { - funcs: [ + declareFunction([ "\\bigl", "\\Bigl", "\\biggl", "\\Biggl", "\\bigr", "\\Bigr", "\\biggr", "\\Biggr", "\\bigm", "\\Bigm", "\\biggm", "\\Biggm", "\\big", "\\Big", "\\bigg", "\\Bigg", "\\left", "\\right" - ], - data: { - numArgs: 1, - handler: function(func, delim, positions) { + ], { + numArgs: 1 + }, function(func, delim, positions) { if (!utils.contains(delimiters, delim.value)) { throw new ParseError( "Invalid delimiter: '" + delim.value + "' after '" + @@ -455,35 +436,22 @@ var duplicatedFunctions = [ value: delim.value }; } - } - } - }, + }); // Sizing functions (handled in Parser.js explicitly, hence no handler) - { - funcs: [ + declareFunction([ "\\tiny", "\\scriptsize", "\\footnotesize", "\\small", "\\normalsize", "\\large", "\\Large", "\\LARGE", "\\huge", "\\Huge" - ], - data: { - numArgs: 0 - } - }, + ], 0, null); // Style changing functions (handled in Parser.js explicitly, hence no // handler) - { - funcs: [ + declareFunction([ "\\displaystyle", "\\textstyle", "\\scriptstyle", "\\scriptscriptstyle" - ], - data: { - numArgs: 0 - } - }, + ], 0, null); - { - funcs: [ + declareFunction([ // styles "\\mathrm", "\\mathit", "\\mathbf", @@ -493,10 +461,9 @@ var duplicatedFunctions = [ // aliases "\\Bbb", "\\bold", "\\frak" - ], - data: { - numArgs: 1, - handler: function (func, body) { + ], { + numArgs: 1 + }, function (func, body) { if (func in fontAliases) { func = fontAliases[func]; } @@ -505,36 +472,28 @@ var duplicatedFunctions = [ font: func.slice(1), body: body }; - } - } - }, + }); // Accents - { - funcs: [ + declareFunction([ "\\acute", "\\grave", "\\ddot", "\\tilde", "\\bar", "\\breve", "\\check", "\\hat", "\\vec", "\\dot" // We don't support expanding accents yet // "\\widetilde", "\\widehat" - ], - data: { - numArgs: 1, - handler: function(func, base) { + ], { + numArgs: 1 + }, function(func, base) { return { type: "accent", accent: func, base: base }; - } - } - }, + }); // Infix generalized fractions - { - funcs: ["\\over", "\\choose"], - data: { - numArgs: 0, - handler: function (func) { + declareFunction(["\\over", "\\choose"], { + numArgs: 0 + }, function (func) { var replaceWith; switch (func) { case "\\over": @@ -550,33 +509,25 @@ var duplicatedFunctions = [ type: "infix", replaceWith: replaceWith }; - } - } - }, + }); // Row breaks for aligned data - { - funcs: ["\\\\", "\\cr"], - data: { + declareFunction(["\\\\", "\\cr"], { numArgs: 0, numOptionalArgs: 1, - argTypes: ["size"], - handler: function(func, size) { + argTypes: ["size"] + }, function(func, size) { return { type: "cr", size: size }; - } - } - }, + }); // Environment delimiters - { - funcs: ["\\begin", "\\end"], - data: { + declareFunction(["\\begin", "\\end"], { numArgs: 1, - argTypes: ["text"], - handler: function(func, nameGroup, positions) { + argTypes: ["text"] + }, function(func, nameGroup, positions) { if (nameGroup.type !== "ordgroup") { throw new ParseError( "Invalid environment name", @@ -591,37 +542,4 @@ var duplicatedFunctions = [ name: name, namepos: positions[1] }; - } - } - } -]; - -var addFuncsWithData = function(funcs, data) { - for (var i = 0; i < funcs.length; i++) { - functions[funcs[i]] = data; - } -}; - -// Add all of the functions in duplicatedFunctions to the functions map -for (var i = 0; i < duplicatedFunctions.length; i++) { - addFuncsWithData(duplicatedFunctions[i].funcs, duplicatedFunctions[i].data); -} - -// Set default values of functions -for (var f in functions) { - if (functions.hasOwnProperty(f)) { - var func = functions[f]; - - functions[f] = { - numArgs: func.numArgs, - argTypes: func.argTypes, - greediness: (func.greediness === undefined) ? 1 : func.greediness, - allowedInText: func.allowedInText ? func.allowedInText : false, - numOptionalArgs: (func.numOptionalArgs === undefined) ? 0 : - func.numOptionalArgs, - handler: func.handler - }; - } -} - -module.exports = functions; + });