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 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;
});