Merge pull request #345 from gagern/splitFunctions

Split up functions list into calls to defineFunction
This commit is contained in:
Kevin Barabash
2015-09-14 22:44:21 -06:00
2 changed files with 451 additions and 535 deletions

View File

@@ -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(

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 = {
// A normal square root
"\\sqrt": {
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,
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": {
// Some non-mathy text
defineFunction("\\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": {
// A two-argument custom color
defineFunction("\\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) {
// An overline
defineFunction("\\overline", {
numArgs: 1
}, function(func, body) {
return {
type: "overline",
body: body
};
}
},
});
// A box of the width and height
"\\rule": {
// A box of the width and height
defineFunction("\\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) {
// A KaTeX logo
defineFunction("\\KaTeX", {
numArgs: 0
}, function(func) {
return {
type: "katex"
};
}
},
});
"\\phantom": {
numArgs: 1,
handler: function(func, body) {
defineFunction("\\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: [
// Single-argument color functions
defineFunction([
"\\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.
// 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: [
// 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"
],
data: {
numArgs: 0,
handler: function(func) {
], {
numArgs: 0
}, function(func) {
return {
type: "op",
limits: false,
symbol: false,
body: func
};
}
}
},
});
// Limits, not symbols
{
funcs: [
// Limits, not symbols
defineFunction([
"\\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: [
// No limits, symbols
defineFunction([
"\\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: [
// Limits, symbols
defineFunction([
"\\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: [
// Fractions
defineFunction([
"\\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: {
// Left and right overlap functions
defineFunction(["\\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: [
// Delimiter functions
defineFunction([
"\\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: [
// Sizing functions (handled in Parser.js explicitly, hence no handler)
defineFunction([
"\\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: [
// Style changing functions (handled in Parser.js explicitly, hence no
// handler)
defineFunction([
"\\displaystyle", "\\textstyle", "\\scriptstyle",
"\\scriptscriptstyle"
],
data: {
numArgs: 0
}
},
], 0, null);
{
funcs: [
defineFunction([
// 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: [
// Accents
defineFunction([
"\\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) {
// Infix generalized fractions
defineFunction(["\\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: {
// Row breaks for aligned data
defineFunction(["\\\\", "\\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: {
// Environment delimiters
defineFunction(["\\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,39 +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 = {
funcs: functions
};
});