Merge pull request #356 from gagern/callingConvention

New calling convention for functions and environments
This commit is contained in:
Kevin Barabash 2015-10-01 08:42:52 -07:00
commit 95e2f1c8d7
3 changed files with 116 additions and 68 deletions

View File

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

View File

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

View File

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