diff --git a/src/Parser.js b/src/Parser.js index 9ba9079db..01566cc71 100644 --- a/src/Parser.js +++ b/src/Parser.js @@ -173,7 +173,7 @@ Parser.prototype.handleInfixNodes = function (body, mode) { } overIndex = i; funcName = node.value.replaceWith; - func = functions.funcs[funcName]; + func = functions[funcName]; } } @@ -225,7 +225,7 @@ Parser.prototype.handleSupSubscript = function(pos, mode, symbol, name) { } else if (group.isFunction) { // ^ and _ have a greediness, so handle interactions with functions' // greediness - var funcGreediness = functions.funcs[group.result.result].greediness; + var funcGreediness = functions[group.result.result].greediness; if (funcGreediness > SUPSUB_GREEDINESS) { return this.parseFunction(pos, mode); } else { @@ -484,7 +484,7 @@ Parser.prototype.parseFunction = function(pos, mode) { if (baseGroup) { if (baseGroup.isFunction) { var func = baseGroup.result.result; - var funcData = functions.funcs[func]; + var funcData = functions[func]; if (mode === "text" && !funcData.allowedInText) { throw new ParseError( "Can't use function '" + func + "' in text mode", @@ -494,7 +494,7 @@ Parser.prototype.parseFunction = function(pos, mode) { var args = [func]; var newPos = this.parseArguments( baseGroup.result.position, mode, func, funcData, args); - var result = functions.funcs[func].handler.apply(this, args); + var result = functions[func].handler.apply(this, args); return new ParseResult( new ParseNode(result.type, result, mode), newPos); @@ -563,7 +563,7 @@ Parser.prototype.parseArguments = function(pos, mode, func, funcData, args) { var argNode; if (arg.isFunction) { var argGreediness = - functions.funcs[arg.result.result].greediness; + functions[arg.result.result].greediness; if (argGreediness > baseGreediness) { argNode = this.parseFunction(newPos, mode); } else { @@ -695,7 +695,7 @@ Parser.prototype.parseOptionalGroup = function(pos, mode) { Parser.prototype.parseSymbol = function(pos, mode) { var nucleus = this.lexer.lex(pos, mode); - if (functions.funcs[nucleus.text]) { + if (functions[nucleus.text]) { // If there exists a function with this name, we return the function and // say that it is a function. return new ParseFuncOrArgument( diff --git a/src/functions.js b/src/functions.js index 1c2b2b2c0..aa9f0e971 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,135 +55,150 @@ 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: - * - 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 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 - * should be called to build this node into a DOM node - * Any other data can be added to the object, which will be passed - * in to the function in buildHTML/buildMathML as `group.value`. + * + * 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 + * should be called to build this node into a DOM node + * Any other data can be added to the object, which will be passed + * in to the function in buildHTML/buildMathML as `group.value`. */ -var functions = { - // A normal square root - "\\sqrt": { - numArgs: 1, - numOptionalArgs: 1, - handler: function(func, index, body, positions) { - return { - type: "sqrt", - body: body, - index: index - }; - } - }, - - // Some non-mathy text - "\\text": { - numArgs: 1, - argTypes: ["text"], - greediness: 2, - handler: 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 - var inner; - if (body.type === "ordgroup") { - inner = body.value; - } else { - inner = [body]; - } - - return { - type: "text", - body: inner - }; - } - }, - - // A two-argument custom color - "\\color": { - numArgs: 2, - allowedInText: true, - greediness: 3, - argTypes: ["color", "original"], - handler: function(func, color, body) { - // Normalize the different kinds of bodies (see \text above) - var inner; - if (body.type === "ordgroup") { - inner = body.value; - } else { - inner = [body]; - } - - return { - type: "color", - color: color.value, - value: inner - }; - } - }, - - // An overline - "\\overline": { - numArgs: 1, - handler: function(func, body) { - return { - type: "overline", - body: body - }; - } - }, - - // A box of the width and height - "\\rule": { - numArgs: 2, - numOptionalArgs: 1, - argTypes: ["size", "size", "size"], - handler: 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) { - return { - type: "katex" - }; - } - }, - - "\\phantom": { - numArgs: 1, - handler: function(func, body) { - var inner; - if (body.type === "ordgroup") { - inner = body.value; - } else { - inner = [body]; - } - - return { - type: "phantom", - value: inner - }; - } +function defineFunction(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 +defineFunction("\\sqrt", { + numArgs: 1, + numOptionalArgs: 1 +}, function(func, index, body, positions) { + return { + type: "sqrt", + body: body, + index: index + }; +}); + +// Some non-mathy text +defineFunction("\\text", { + numArgs: 1, + argTypes: ["text"], + 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 + var inner; + if (body.type === "ordgroup") { + inner = body.value; + } else { + inner = [body]; + } + + return { + type: "text", + body: inner + }; +}); + +// A two-argument custom color +defineFunction("\\color", { + numArgs: 2, + allowedInText: true, + greediness: 3, + argTypes: ["color", "original"] +}, function(func, color, body) { + // Normalize the different kinds of bodies (see \text above) + var inner; + if (body.type === "ordgroup") { + inner = body.value; + } else { + inner = [body]; + } + + return { + type: "color", + color: color.value, + value: inner + }; +}); + +// An overline +defineFunction("\\overline", { + numArgs: 1 +}, function(func, body) { + return { + type: "overline", + body: body + }; +}); + +// A box of the width and height +defineFunction("\\rule", { + numArgs: 2, + numOptionalArgs: 1, + 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 +defineFunction("\\KaTeX", { + numArgs: 0 +}, function(func) { + return { + type: "katex" + }; +}); + +defineFunction("\\phantom", { + numArgs: 1 +}, function(func, body) { + var inner; + if (body.type === "ordgroup") { + inner = body.value; + } else { + inner = [body]; + } + + return { + type: "phantom", + value: inner + }; +}); // Extra data needed for the delimiter handler down below var delimiterSizes = { @@ -221,409 +241,305 @@ 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: [ - "\\blue", "\\orange", "\\pink", "\\red", - "\\green", "\\gray", "\\purple", - "\\blueA", "\\blueB", "\\blueC", "\\blueD", "\\blueE", - "\\tealA", "\\tealB", "\\tealC", "\\tealD", "\\tealE", - "\\greenA", "\\greenB", "\\greenC", "\\greenD", "\\greenE", - "\\goldA", "\\goldB", "\\goldC", "\\goldD", "\\goldE", - "\\redA", "\\redB", "\\redC", "\\redD", "\\redE", - "\\maroonA", "\\maroonB", "\\maroonC", "\\maroonD", "\\maroonE", - "\\purpleA", "\\purpleB", "\\purpleC", "\\purpleD", "\\purpleE", - "\\mintA", "\\mintB", "\\mintC", - "\\grayA", "\\grayB", "\\grayC", "\\grayD", "\\grayE", - "\\grayF", "\\grayG", "\\grayH", "\\grayI", - "\\kaBlue", "\\kaGreen" - ], - data: { - numArgs: 1, - allowedInText: true, - greediness: 3, - handler: function(func, body) { - var atoms; - if (body.type === "ordgroup") { - atoms = body.value; - } else { - atoms = [body]; - } - - return { - type: "color", - 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: [ - "\\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) { - return { - type: "op", - limits: false, - symbol: false, - body: func - }; - } - } - }, - - // Limits, not symbols - { - funcs: [ - "\\det", "\\gcd", "\\inf", "\\lim", "\\liminf", "\\limsup", "\\max", - "\\min", "\\Pr", "\\sup" - ], - data: { - numArgs: 0, - handler: function(func) { - return { - type: "op", - limits: true, - symbol: false, - body: func - }; - } - } - }, - - // No limits, symbols - { - funcs: [ - "\\int", "\\iint", "\\iiint", "\\oint" - ], - data: { - numArgs: 0, - handler: function(func) { - return { - type: "op", - limits: false, - symbol: true, - body: func - }; - } - } - }, - - // Limits, symbols - { - funcs: [ - "\\coprod", "\\bigvee", "\\bigwedge", "\\biguplus", "\\bigcap", - "\\bigcup", "\\intop", "\\prod", "\\sum", "\\bigotimes", - "\\bigoplus", "\\bigodot", "\\bigsqcup", "\\smallint" - ], - data: { - numArgs: 0, - handler: function(func) { - return { - type: "op", - limits: true, - symbol: true, - body: func - }; - } - } - }, - - // Fractions - { - funcs: [ - "\\dfrac", "\\frac", "\\tfrac", - "\\dbinom", "\\binom", "\\tbinom" - ], - data: { - numArgs: 2, - greediness: 2, - handler: function(func, numer, denom) { - var hasBarLine; - var leftDelim = null; - var rightDelim = null; - var size = "auto"; - - switch (func) { - case "\\dfrac": - case "\\frac": - case "\\tfrac": - hasBarLine = true; - break; - case "\\dbinom": - case "\\binom": - case "\\tbinom": - hasBarLine = false; - leftDelim = "("; - rightDelim = ")"; - break; - default: - throw new Error("Unrecognized genfrac command"); - } - - switch (func) { - case "\\dfrac": - case "\\dbinom": - size = "display"; - break; - case "\\tfrac": - case "\\tbinom": - size = "text"; - break; - } - - return { - type: "genfrac", - numer: numer, - denom: denom, - hasBarLine: hasBarLine, - leftDelim: leftDelim, - rightDelim: rightDelim, - size: size - }; - } - } - }, - - // Left and right overlap functions - { - funcs: ["\\llap", "\\rlap"], - data: { - numArgs: 1, - allowedInText: true, - handler: function(func, body) { - return { - type: func.slice(1), - body: body - }; - } - } - }, - - // Delimiter functions - { - funcs: [ - "\\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) { - if (!utils.contains(delimiters, delim.value)) { - throw new ParseError( - "Invalid delimiter: '" + delim.value + "' after '" + - func + "'", - this.lexer, 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") { - return { - type: "leftright", - value: delim.value - }; - } else { - return { - type: "delimsizing", - size: delimiterSizes[func].size, - delimType: delimiterSizes[func].type, - value: delim.value - }; - } - } - } - }, - - // Sizing functions (handled in Parser.js explicitly, hence no handler) - { - funcs: [ - "\\tiny", "\\scriptsize", "\\footnotesize", "\\small", - "\\normalsize", "\\large", "\\Large", "\\LARGE", "\\huge", "\\Huge" - ], - data: { - numArgs: 0 - } - }, - - // Style changing functions (handled in Parser.js explicitly, hence no - // handler) - { - funcs: [ - "\\displaystyle", "\\textstyle", "\\scriptstyle", - "\\scriptscriptstyle" - ], - data: { - numArgs: 0 - } - }, - - { - funcs: [ - // styles - "\\mathrm", "\\mathit", "\\mathbf", - - // families - "\\mathbb", "\\mathcal", "\\mathfrak", "\\mathscr", "\\mathsf", - "\\mathtt", - - // aliases - "\\Bbb", "\\bold", "\\frak" - ], - data: { - numArgs: 1, - handler: function (func, body) { - if (func in fontAliases) { - func = fontAliases[func]; - } - return { - type: "font", - font: func.slice(1), - body: body - }; - } - } - }, - - // Accents - { - funcs: [ - "\\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) { - return { - type: "accent", - accent: func, - base: base - }; - } - } - }, - - // Infix generalized fractions - { - funcs: ["\\over", "\\choose"], - data: { - numArgs: 0, - handler: function (func) { - var replaceWith; - switch (func) { - case "\\over": - replaceWith = "\\frac"; - break; - case "\\choose": - replaceWith = "\\binom"; - break; - default: - throw new Error("Unrecognized infix genfrac command"); - } - return { - type: "infix", - replaceWith: replaceWith - }; - } - } - }, - - // Row breaks for aligned data - { - funcs: ["\\\\", "\\cr"], - data: { - numArgs: 0, - numOptionalArgs: 1, - argTypes: ["size"], - handler: function(func, size) { - return { - type: "cr", - size: size - }; - } - } - }, - - // Environment delimiters - { - funcs: ["\\begin", "\\end"], - data: { - numArgs: 1, - argTypes: ["text"], - handler: function(func, nameGroup, positions) { - if (nameGroup.type !== "ordgroup") { - throw new ParseError( - "Invalid environment name", - this.lexer, positions[1]); - } - var name = ""; - for (var i = 0; i < nameGroup.value.length; ++i) { - name += nameGroup.value[i].value; - } - return { - type: "environment", - name: name, - namepos: positions[1] - }; - } - } +// Single-argument color functions +defineFunction([ + "\\blue", "\\orange", "\\pink", "\\red", + "\\green", "\\gray", "\\purple", + "\\blueA", "\\blueB", "\\blueC", "\\blueD", "\\blueE", + "\\tealA", "\\tealB", "\\tealC", "\\tealD", "\\tealE", + "\\greenA", "\\greenB", "\\greenC", "\\greenD", "\\greenE", + "\\goldA", "\\goldB", "\\goldC", "\\goldD", "\\goldE", + "\\redA", "\\redB", "\\redC", "\\redD", "\\redE", + "\\maroonA", "\\maroonB", "\\maroonC", "\\maroonD", "\\maroonE", + "\\purpleA", "\\purpleB", "\\purpleC", "\\purpleD", "\\purpleE", + "\\mintA", "\\mintB", "\\mintC", + "\\grayA", "\\grayB", "\\grayC", "\\grayD", "\\grayE", + "\\grayF", "\\grayG", "\\grayH", "\\grayI", + "\\kaBlue", "\\kaGreen" +], { + numArgs: 1, + allowedInText: true, + greediness: 3 +}, function(func, body) { + var atoms; + if (body.type === "ordgroup") { + atoms = body.value; + } else { + atoms = [body]; } -]; -var addFuncsWithData = function(funcs, data) { - for (var i = 0; i < funcs.length; i++) { - functions[funcs[i]] = data; + return { + type: "color", + 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 +defineFunction([ + "\\arcsin", "\\arccos", "\\arctan", "\\arg", "\\cos", "\\cosh", + "\\cot", "\\coth", "\\csc", "\\deg", "\\dim", "\\exp", "\\hom", + "\\ker", "\\lg", "\\ln", "\\log", "\\sec", "\\sin", "\\sinh", + "\\tan","\\tanh" +], { + numArgs: 0 +}, function(func) { + return { + type: "op", + limits: false, + symbol: false, + body: func + }; +}); + +// Limits, not symbols +defineFunction([ + "\\det", "\\gcd", "\\inf", "\\lim", "\\liminf", "\\limsup", "\\max", + "\\min", "\\Pr", "\\sup" +], { + numArgs: 0 +}, function(func) { + return { + type: "op", + limits: true, + symbol: false, + body: func + }; +}); + +// No limits, symbols +defineFunction([ + "\\int", "\\iint", "\\iiint", "\\oint" +], { + numArgs: 0 +}, function(func) { + return { + type: "op", + limits: false, + symbol: true, + body: func + }; +}); + +// Limits, symbols +defineFunction([ + "\\coprod", "\\bigvee", "\\bigwedge", "\\biguplus", "\\bigcap", + "\\bigcup", "\\intop", "\\prod", "\\sum", "\\bigotimes", + "\\bigoplus", "\\bigodot", "\\bigsqcup", "\\smallint" +], { + numArgs: 0 +}, function(func) { + return { + type: "op", + limits: true, + symbol: true, + body: func + }; +}); + +// Fractions +defineFunction([ + "\\dfrac", "\\frac", "\\tfrac", + "\\dbinom", "\\binom", "\\tbinom" +], { + numArgs: 2, + greediness: 2 +}, function(func, numer, denom) { + var hasBarLine; + var leftDelim = null; + var rightDelim = null; + var size = "auto"; + + switch (func) { + case "\\dfrac": + case "\\frac": + case "\\tfrac": + hasBarLine = true; + break; + case "\\dbinom": + case "\\binom": + case "\\tbinom": + hasBarLine = false; + leftDelim = "("; + rightDelim = ")"; + break; + default: + throw new Error("Unrecognized genfrac command"); } -}; -// 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); -} + switch (func) { + case "\\dfrac": + case "\\dbinom": + size = "display"; + break; + case "\\tfrac": + case "\\tbinom": + size = "text"; + break; + } -// Set default values of functions -for (var f in functions) { - if (functions.hasOwnProperty(f)) { - var func = functions[f]; + return { + type: "genfrac", + numer: numer, + denom: denom, + hasBarLine: hasBarLine, + leftDelim: leftDelim, + rightDelim: rightDelim, + size: size + }; +}); - 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 +// Left and right overlap functions +defineFunction(["\\llap", "\\rlap"], { + numArgs: 1, + allowedInText: true +}, function(func, body) { + return { + type: func.slice(1), + body: body + }; +}); + +// Delimiter functions +defineFunction([ + "\\bigl", "\\Bigl", "\\biggl", "\\Biggl", + "\\bigr", "\\Bigr", "\\biggr", "\\Biggr", + "\\bigm", "\\Bigm", "\\biggm", "\\Biggm", + "\\big", "\\Big", "\\bigg", "\\Bigg", + "\\left", "\\right" +], { + numArgs: 1 +}, function(func, delim, positions) { + if (!utils.contains(delimiters, delim.value)) { + throw new ParseError( + "Invalid delimiter: '" + delim.value + "' after '" + + func + "'", + this.lexer, 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") { + return { + type: "leftright", + value: delim.value + }; + } else { + return { + type: "delimsizing", + size: delimiterSizes[func].size, + delimType: delimiterSizes[func].type, + value: delim.value }; } -} +}); -module.exports = { - funcs: functions -}; +// Sizing functions (handled in Parser.js explicitly, hence no handler) +defineFunction([ + "\\tiny", "\\scriptsize", "\\footnotesize", "\\small", + "\\normalsize", "\\large", "\\Large", "\\LARGE", "\\huge", "\\Huge" +], 0, null); + +// Style changing functions (handled in Parser.js explicitly, hence no +// handler) +defineFunction([ + "\\displaystyle", "\\textstyle", "\\scriptstyle", + "\\scriptscriptstyle" +], 0, null); + +defineFunction([ + // styles + "\\mathrm", "\\mathit", "\\mathbf", + + // families + "\\mathbb", "\\mathcal", "\\mathfrak", "\\mathscr", "\\mathsf", + "\\mathtt", + + // aliases + "\\Bbb", "\\bold", "\\frak" +], { + numArgs: 1 +}, function (func, body) { + if (func in fontAliases) { + func = fontAliases[func]; + } + return { + type: "font", + font: func.slice(1), + body: body + }; +}); + +// Accents +defineFunction([ + "\\acute", "\\grave", "\\ddot", "\\tilde", "\\bar", "\\breve", + "\\check", "\\hat", "\\vec", "\\dot" + // We don't support expanding accents yet + // "\\widetilde", "\\widehat" +], { + numArgs: 1 +}, function(func, base) { + return { + type: "accent", + accent: func, + base: base + }; +}); + +// Infix generalized fractions +defineFunction(["\\over", "\\choose"], { + numArgs: 0 +}, function (func) { + var replaceWith; + switch (func) { + case "\\over": + replaceWith = "\\frac"; + break; + case "\\choose": + replaceWith = "\\binom"; + break; + default: + throw new Error("Unrecognized infix genfrac command"); + } + return { + type: "infix", + replaceWith: replaceWith + }; +}); + +// Row breaks for aligned data +defineFunction(["\\\\", "\\cr"], { + numArgs: 0, + numOptionalArgs: 1, + argTypes: ["size"] +}, function(func, size) { + return { + type: "cr", + size: size + }; +}); + +// Environment delimiters +defineFunction(["\\begin", "\\end"], { + numArgs: 1, + argTypes: ["text"] +}, function(func, nameGroup, positions) { + if (nameGroup.type !== "ordgroup") { + throw new ParseError( + "Invalid environment name", + this.lexer, positions[1]); + } + var name = ""; + for (var i = 0; i < nameGroup.value.length; ++i) { + name += nameGroup.value[i].value; + } + return { + type: "environment", + name: name, + namepos: positions[1] + }; +});