Handle \middle.

This commit is contained in:
Eddie Kohler 2016-12-08 11:28:48 -05:00
parent 7433638fda
commit e449b2d61a
8 changed files with 106 additions and 20 deletions

View File

@ -52,6 +52,8 @@ function Parser(input, settings) {
this.gullet = new MacroExpander(input, settings.macros);
// Store the settings for use in parsing
this.settings = settings;
// Count leftright depth (for \middle errors)
this.leftrightDepth = 0;
}
var ParseNode = parseData.ParseNode;
@ -411,7 +413,9 @@ Parser.prototype.parseImplicitGroup = function() {
// Parse the entire left function (including the delimiter)
var left = this.parseFunction(start);
// Parse out the implicit body
++this.leftrightDepth;
body = this.parseExpression(false);
--this.leftrightDepth;
// Check the next token
this.expect("\\right", false);
var right = this.parseFunction();

View File

@ -1186,11 +1186,16 @@ groupTypes.leftright = function(group, options) {
var innerHeight = 0;
var innerDepth = 0;
var hadMiddle = false;
// Calculate its height and depth
for (var i = 0; i < inner.length; i++) {
innerHeight = Math.max(inner[i].height, innerHeight);
innerDepth = Math.max(inner[i].depth, innerDepth);
if (inner[i].isMiddle) {
hadMiddle = true;
} else {
innerHeight = Math.max(inner[i].height, innerHeight);
innerDepth = Math.max(inner[i].depth, innerDepth);
}
}
var style = options.style;
@ -1215,6 +1220,18 @@ groupTypes.leftright = function(group, options) {
// Add it to the beginning of the expression
inner.unshift(leftDelim);
// Handle middle delimiters
if (hadMiddle) {
for (i = 1; i < inner.length; i++) {
if (inner[i].isMiddle) {
// Apply the options that were active when \middle was called
inner[i] = delimiter.leftRightDelim(
inner[i].isMiddle.value, innerHeight, innerDepth,
inner[i].isMiddle.options, group.mode, []);
}
}
}
var rightDelim;
// Same for the right delimiter
if (group.value.right === ".") {
@ -1231,6 +1248,19 @@ groupTypes.leftright = function(group, options) {
["minner", style.cls()], inner, options);
};
groupTypes.middle = function(group, options) {
var middleDelim;
if (group.value.value === ".") {
middleDelim = makeNullDelimiter(options, []);
} else {
middleDelim = delimiter.sizedDelim(
group.value.value, 1, options,
group.mode, []);
middleDelim.isMiddle = {value: group.value.value, options: options};
}
return middleDelim;
};
groupTypes.rule = function(group, options) {
// Make an empty span for the rule
var rule = makeSpan(["mord", "rule"], [], options);

View File

@ -285,6 +285,13 @@ groupTypes.leftright = function(group, options) {
return outerNode;
};
groupTypes.middle = function(group, options) {
var middleNode = new mathMLTree.MathNode(
"mo", [makeText(group.value.middle, group.mode)]);
middleNode.setAttribute("fence", "true");
return middleNode;
};
groupTypes.accent = function(group, options) {
var accentNode = new mathMLTree.MathNode(
"mo", [makeText(group.value.accent, group.mode)]);

View File

@ -496,37 +496,61 @@ defineFunction(["\\llap", "\\rlap"], {
});
// Delimiter functions
var checkDelimiter = function(delim, context) {
if (utils.contains(delimiters, delim.value)) {
return delim;
} else {
throw new ParseError(
"Invalid delimiter: '" + delim.value + "' after '" +
context.funcName + "'", delim);
}
};
defineFunction([
"\\bigl", "\\Bigl", "\\biggl", "\\Biggl",
"\\bigr", "\\Bigr", "\\biggr", "\\Biggr",
"\\bigm", "\\Bigm", "\\biggm", "\\Biggm",
"\\big", "\\Big", "\\bigg", "\\Bigg",
], {
numArgs: 1,
}, function(context, args) {
var delim = checkDelimiter(args[0], context);
return {
type: "delimsizing",
size: delimiterSizes[context.funcName].size,
mclass: delimiterSizes[context.funcName].mclass,
value: delim.value,
};
});
defineFunction([
"\\left", "\\right",
], {
numArgs: 1,
}, function(context, args) {
var delim = args[0];
if (!utils.contains(delimiters, delim.value)) {
throw new ParseError(
"Invalid delimiter: '" + delim.value + "' after '" +
context.funcName + "'", delim);
}
var delim = checkDelimiter(args[0], context);
// \left and \right are caught somewhere in Parser.js, which is
// why this data doesn't match what is in buildHTML.
if (context.funcName === "\\left" || context.funcName === "\\right") {
return {
type: "leftright",
value: delim.value,
};
} else {
return {
type: "delimsizing",
size: delimiterSizes[context.funcName].size,
mclass: delimiterSizes[context.funcName].mclass,
value: delim.value,
};
return {
type: "leftright",
value: delim.value,
};
});
defineFunction("\\middle", {
numArgs: 1,
}, function(context, args) {
var delim = checkDelimiter(args[0], context);
if (!context.parser.leftrightDepth) {
throw new ParseError("\\middle without preceding \\left", delim);
}
return {
type: "middle",
value: delim.value,
};
});
// Sizing functions (handled in Parser.js explicitly, hence no handler)

View File

@ -1042,6 +1042,26 @@ describe("A left/right parser", function() {
var normalEmpty = "\\Bigl .";
expect(normalEmpty).toParse();
});
it("should handle \\middle", function() {
var normalMiddle = "\\left( \\dfrac{x}{y} \\middle| \\dfrac{y}{z} \\right)";
expect(normalMiddle).toParse();
});
it("should handle multiple \\middles", function() {
var multiMiddle = "\\left( \\dfrac{x}{y} \\middle| \\dfrac{y}{z} \\middle/ \\dfrac{z}{q} \\right)";
expect(multiMiddle).toParse();
});
it("should handle nested \\middles", function() {
var nestedMiddle = "\\left( a^2 \\middle| \\left( b \\middle/ c \\right) \\right)";
expect(nestedMiddle).toParse();
});
it("should error when \\middle is not in \\left...\\right", function() {
var unmatchedMiddle = "(\\middle|\\dfrac{x}{y})";
expect(unmatchedMiddle).toNotParse();
});
});
describe("A begin/end parser", function() {

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -72,6 +72,7 @@ Kern:
Lap: ab\llap{f}cd\rlap{g}h
LeftRight: \left( x^2 \right) \left\{ x^{x^{x^{x^x}}} \right.
LeftRightListStyling: a+\left(x+y\right)-x
LeftRightMiddle: \left( x^2 \middle/ \right) \left\{ x^{x^{x^{x^x}}} \middle/ y \right.
LeftRightStyleSizing: |
+\left\{\rule{0.1em}{1em}\right.
x^{+\left\{\rule{0.1em}{1em}\right.