commit 95e2f1c8d732db3452a080abbdae2cb5b80301a5
parent a81c4fe78df1b19401883cff4f8af2b1c0f5b033
Author: Kevin Barabash <kevinb7@gmail.com>
Date: Thu, 1 Oct 2015 08:42:52 -0700
Merge pull request #356 from gagern/callingConvention
New calling convention for functions and environments
Diffstat:
3 files changed, 116 insertions(+), 68 deletions(-)
diff --git a/src/Parser.js b/src/Parser.js
@@ -195,7 +195,8 @@ Parser.prototype.handleInfixNodes = function (body, mode) {
denomNode = new ParseNode("ordgroup", denomBody, mode);
}
- var value = func.handler(funcName, numerNode, denomNode);
+ var value = this.callFunction(
+ funcName, [numerNode, denomNode], null);
return [new ParseNode(value.type, value, mode)];
} else {
return body;
@@ -430,11 +431,18 @@ Parser.prototype.parseImplicitGroup = function(pos, mode) {
// Build the environment object. Arguments and other information will
// be made available to the begin and end methods using properties.
var env = environments[envName];
- var args = [null, mode, envName];
+ var args = [];
var newPos = this.parseArguments(
begin.position, mode, "\\begin{" + envName + "}", env, args);
- args[0] = newPos;
- var result = env.handler.apply(this, args);
+ var context = {
+ pos: newPos,
+ mode: mode,
+ envName: envName,
+ parser: this,
+ lexer: this.lexer,
+ positions: args.pop()
+ };
+ var result = env.handler(context, args);
var endLex = this.lexer.lex(result.position, mode);
this.expect(endLex, "\\end");
var end = this.parseFunction(result.position, mode);
@@ -491,10 +499,10 @@ Parser.prototype.parseFunction = function(pos, mode) {
this.lexer, baseGroup.position);
}
- var args = [func];
+ var args = [];
var newPos = this.parseArguments(
baseGroup.result.position, mode, func, funcData, args);
- var result = functions[func].handler.apply(this, args);
+ var result = this.callFunction(func, args, args.pop());
return new ParseResult(
new ParseNode(result.type, result, mode),
newPos);
@@ -506,6 +514,18 @@ Parser.prototype.parseFunction = function(pos, mode) {
}
};
+/**
+ * Call a function handler with a suitable context and arguments.
+ */
+Parser.prototype.callFunction = function(name, args, positions) {
+ var context = {
+ funcName: name,
+ parser: this,
+ lexer: this.lexer,
+ positions: positions
+ };
+ return functions[name].handler(context, args);
+};
/**
* Parses the arguments of a function or environment
diff --git a/src/environments.js b/src/environments.js
@@ -50,14 +50,17 @@ function parseArray(parser, pos, mode, result) {
* - numOptionalArgs: (optional) Just like for a function
* A bare number instead of that object indicates the numArgs value.
*
- * The handler function will receive the following arguments:
+ * The handler function will receive two arguments
+ * - context: information and references provided by the parser
+ * - args: an array of arguments passed to \begin{name}
+ * The context contains the following properties:
* - pos: the current position of the parser.
* - mode: the current parsing mode.
* - envName: the name of the environment, one of the listed names.
- * - [args]: the arguments passed to \begin.
- * - positions: the positions associated with these arguments.
- * The handler is called with `this` referring to the parser.
- * It must return a ParseResult.
+ * - parser: the parser object
+ * - lexer: the lexer object
+ * - positions: the positions associated with these arguments from args.
+ * The handler must return a ParseResult.
*/
function defineEnvironment(names, props, handler) {
@@ -85,8 +88,10 @@ function defineEnvironment(names, props, handler) {
// is part of the source2e.pdf file of LaTeX2e source documentation.
defineEnvironment("array", {
numArgs: 1
-}, function(pos, mode, envName, colalign, positions) {
- var parser = this;
+}, function(context, args) {
+ var colalign = args[0];
+ var lexer = context.lexer;
+ var positions = context.positions;
colalign = colalign.value.map ? colalign.value : [colalign];
var cols = colalign.map(function(node) {
var ca = node.value;
@@ -103,14 +108,14 @@ defineEnvironment("array", {
}
throw new ParseError(
"Unknown column alignment: " + node.value,
- parser.lexer, positions[1]);
+ lexer, positions[1]);
});
var res = {
type: "array",
cols: cols,
hskipBeforeAndAfter: true // \@preamble in lttab.dtx
};
- res = parseArray(parser, pos, mode, res);
+ res = parseArray(context.parser, context.pos, context.mode, res);
return res;
});
@@ -124,7 +129,7 @@ defineEnvironment([
"vmatrix",
"Vmatrix"
], {
-}, function(pos, mode, envName) {
+}, function(context) {
var delimiters = {
"matrix": null,
"pmatrix": ["(", ")"],
@@ -132,18 +137,18 @@ defineEnvironment([
"Bmatrix": ["\\{", "\\}"],
"vmatrix": ["|", "|"],
"Vmatrix": ["\\Vert", "\\Vert"]
- }[envName];
+ }[context.envName];
var res = {
type: "array",
hskipBeforeAndAfter: false // \hskip -\arraycolsep in amsmath
};
- res = parseArray(this, pos, mode, res);
+ res = parseArray(context.parser, context.pos, context.mode, res);
if (delimiters) {
res.result = new ParseNode("leftright", {
body: [res.result],
left: delimiters[0],
right: delimiters[1]
- }, mode);
+ }, context.mode);
}
return res;
});
@@ -152,7 +157,7 @@ defineEnvironment([
// \def\arraystretch{1.2}%
// \left\{\begin{array}{@{}l@{\quad}l@{}} … \end{array}\right.
defineEnvironment("cases", {
-}, function(pos, mode, envName) {
+}, function(context) {
var res = {
type: "array",
arraystretch: 1.2,
@@ -168,11 +173,11 @@ defineEnvironment("cases", {
postgap: 0
}]
};
- res = parseArray(this, pos, mode, res);
+ res = parseArray(context.parser, context.pos, context.mode, res);
res.result = new ParseNode("leftright", {
body: [res.result],
left: "\\{",
right: "."
- }, mode);
+ }, context.mode);
return res;
});
diff --git a/src/functions.js b/src/functions.js
@@ -2,9 +2,9 @@ var utils = require("./utils");
var ParseError = require("./ParseError");
/* This file contains a list of functions that we parse, identified by
- * the calls to declareFunction.
+ * the calls to defineFunction.
*
- * The first argument to declareFunction is a single name or a list of names.
+ * The first argument to defineFunction 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
@@ -58,14 +58,17 @@ var ParseError = require("./ParseError");
*
* 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
+ * It receives two arguments:
+ * - context contains information and references provided by the parser
+ * - args is an array of arguments obtained from TeX input
+ * The context contains the following properties:
+ * - funcName: the text (i.e. name) of the function, including \
+ * - parser: the parser object
+ * - lexer: the lexer object
* - 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.
+ * and the arguments.
+ * The latter three 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
@@ -99,7 +102,9 @@ function defineFunction(names, props, handler) {
defineFunction("\\sqrt", {
numArgs: 1,
numOptionalArgs: 1
-}, function(func, index, body, positions) {
+}, function(context, args) {
+ var index = args[0];
+ var body = args[1];
return {
type: "sqrt",
body: body,
@@ -112,7 +117,8 @@ defineFunction("\\text", {
numArgs: 1,
argTypes: ["text"],
greediness: 2
-}, function(func, body) {
+}, function(context, args) {
+ var body = args[0];
// 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
@@ -135,7 +141,9 @@ defineFunction("\\color", {
allowedInText: true,
greediness: 3,
argTypes: ["color", "original"]
-}, function(func, color, body) {
+}, function(context, args) {
+ var color = args[0];
+ var body = args[1];
// Normalize the different kinds of bodies (see \text above)
var inner;
if (body.type === "ordgroup") {
@@ -154,7 +162,8 @@ defineFunction("\\color", {
// An overline
defineFunction("\\overline", {
numArgs: 1
-}, function(func, body) {
+}, function(context, args) {
+ var body = args[0];
return {
type: "overline",
body: body
@@ -166,7 +175,10 @@ defineFunction("\\rule", {
numArgs: 2,
numOptionalArgs: 1,
argTypes: ["size", "size", "size"]
-}, function(func, shift, width, height) {
+}, function(context, args) {
+ var shift = args[0];
+ var width = args[1];
+ var height = args[2];
return {
type: "rule",
shift: shift && shift.value,
@@ -178,7 +190,7 @@ defineFunction("\\rule", {
// A KaTeX logo
defineFunction("\\KaTeX", {
numArgs: 0
-}, function(func) {
+}, function(context) {
return {
type: "katex"
};
@@ -186,7 +198,8 @@ defineFunction("\\KaTeX", {
defineFunction("\\phantom", {
numArgs: 1
-}, function(func, body) {
+}, function(context, args) {
+ var body = args[0];
var inner;
if (body.type === "ordgroup") {
inner = body.value;
@@ -260,7 +273,8 @@ defineFunction([
numArgs: 1,
allowedInText: true,
greediness: 3
-}, function(func, body) {
+}, function(context, args) {
+ var body = args[0];
var atoms;
if (body.type === "ordgroup") {
atoms = body.value;
@@ -270,7 +284,7 @@ defineFunction([
return {
type: "color",
- color: "katex-" + func.slice(1),
+ color: "katex-" + context.funcName.slice(1),
value: atoms
};
});
@@ -287,12 +301,12 @@ defineFunction([
"\\tan","\\tanh"
], {
numArgs: 0
-}, function(func) {
+}, function(context) {
return {
type: "op",
limits: false,
symbol: false,
- body: func
+ body: context.funcName
};
});
@@ -302,12 +316,12 @@ defineFunction([
"\\min", "\\Pr", "\\sup"
], {
numArgs: 0
-}, function(func) {
+}, function(context) {
return {
type: "op",
limits: true,
symbol: false,
- body: func
+ body: context.funcName
};
});
@@ -316,12 +330,12 @@ defineFunction([
"\\int", "\\iint", "\\iiint", "\\oint"
], {
numArgs: 0
-}, function(func) {
+}, function(context) {
return {
type: "op",
limits: false,
symbol: true,
- body: func
+ body: context.funcName
};
});
@@ -332,12 +346,12 @@ defineFunction([
"\\bigoplus", "\\bigodot", "\\bigsqcup", "\\smallint"
], {
numArgs: 0
-}, function(func) {
+}, function(context) {
return {
type: "op",
limits: true,
symbol: true,
- body: func
+ body: context.funcName
};
});
@@ -348,13 +362,15 @@ defineFunction([
], {
numArgs: 2,
greediness: 2
-}, function(func, numer, denom) {
+}, function(context, args) {
+ var numer = args[0];
+ var denom = args[1];
var hasBarLine;
var leftDelim = null;
var rightDelim = null;
var size = "auto";
- switch (func) {
+ switch (context.funcName) {
case "\\dfrac":
case "\\frac":
case "\\tfrac":
@@ -371,7 +387,7 @@ defineFunction([
throw new Error("Unrecognized genfrac command");
}
- switch (func) {
+ switch (context.funcName) {
case "\\dfrac":
case "\\dbinom":
size = "display";
@@ -397,9 +413,10 @@ defineFunction([
defineFunction(["\\llap", "\\rlap"], {
numArgs: 1,
allowedInText: true
-}, function(func, body) {
+}, function(context, args) {
+ var body = args[0];
return {
- type: func.slice(1),
+ type: context.funcName.slice(1),
body: body
};
});
@@ -413,17 +430,18 @@ defineFunction([
"\\left", "\\right"
], {
numArgs: 1
-}, function(func, delim, positions) {
+}, function(context, args) {
+ var delim = args[0];
if (!utils.contains(delimiters, delim.value)) {
throw new ParseError(
"Invalid delimiter: '" + delim.value + "' after '" +
- func + "'",
- this.lexer, positions[1]);
+ context.funcName + "'",
+ context.lexer, context.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") {
+ if (context.funcName === "\\left" || context.funcName === "\\right") {
return {
type: "leftright",
value: delim.value
@@ -431,8 +449,8 @@ defineFunction([
} else {
return {
type: "delimsizing",
- size: delimiterSizes[func].size,
- delimType: delimiterSizes[func].type,
+ size: delimiterSizes[context.funcName].size,
+ delimType: delimiterSizes[context.funcName].type,
value: delim.value
};
}
@@ -464,7 +482,9 @@ defineFunction([
], {
numArgs: 1,
greediness: 2
-}, function (func, body) {
+}, function (context, args) {
+ var body = args[0];
+ var func = context.funcName;
if (func in fontAliases) {
func = fontAliases[func];
}
@@ -483,10 +503,11 @@ defineFunction([
// "\\widetilde", "\\widehat"
], {
numArgs: 1
-}, function(func, base) {
+}, function(context, args) {
+ var base = args[0];
return {
type: "accent",
- accent: func,
+ accent: context.funcName,
base: base
};
});
@@ -494,9 +515,9 @@ defineFunction([
// Infix generalized fractions
defineFunction(["\\over", "\\choose"], {
numArgs: 0
-}, function (func) {
+}, function (context) {
var replaceWith;
- switch (func) {
+ switch (context.funcName) {
case "\\over":
replaceWith = "\\frac";
break;
@@ -517,7 +538,8 @@ defineFunction(["\\\\", "\\cr"], {
numArgs: 0,
numOptionalArgs: 1,
argTypes: ["size"]
-}, function(func, size) {
+}, function(context, args) {
+ var size = args[0];
return {
type: "cr",
size: size
@@ -528,11 +550,12 @@ defineFunction(["\\\\", "\\cr"], {
defineFunction(["\\begin", "\\end"], {
numArgs: 1,
argTypes: ["text"]
-}, function(func, nameGroup, positions) {
+}, function(context, args) {
+ var nameGroup = args[0];
if (nameGroup.type !== "ordgroup") {
throw new ParseError(
"Invalid environment name",
- this.lexer, positions[1]);
+ context.lexer, context.positions[1]);
}
var name = "";
for (var i = 0; i < nameGroup.value.length; ++i) {
@@ -541,6 +564,6 @@ defineFunction(["\\begin", "\\end"], {
return {
type: "environment",
name: name,
- namepos: positions[1]
+ namepos: context.positions[1]
};
});