Split up functions list into calls to declareFunction

Having one long array literal to contain the code of all function
implementations is problematic.  It makes it difficult to add auxiliary
functions or data close to the function inside the list where it is needed.

Now the functions are no longer defined using such a literal, but instead
using calls to a "declareFunction" function which receives all the relevant
data.  Since each function call is independent from the others, anything can
go in between.

This commit deliberately avoided reindenting existing code to match the new
surroundings.  That way it is easier to see where actual changes happen,
even when not performing a whitespace-ignoring diff.
This commit is contained in:
Martin von Gagern 2015-09-10 09:45:02 +02:00
parent 5539226f4b
commit d553353204

View File

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