import buildCommon from "../buildCommon";
import defineFunction from "../defineFunction";
import mathMLTree from "../mathMLTree";
import * as html from "../buildHTML";
import * as mml from "../buildMathML";
import {assertSymbolNodeType} from "../parseNode";
import ParseError from "../ParseError";
import {makeEm} from "../units";
import type Parser from "../Parser";
import type {ParseNode, AnyParseNode} from "../parseNode";
const cdArrowFunctionName = {
">": "\\\\cdrightarrow",
"<": "\\\\cdleftarrow",
"=": "\\\\cdlongequal",
"A": "\\uparrow",
"V": "\\downarrow",
"|": "\\Vert",
".": "no arrow",
};
const newCell = () => {
return {type: "styling", body: [], mode: "math", style: "display"};
};
const isStartOfArrow = (node: AnyParseNode) => {
return (node.type === "textord" && node.text === "@");
};
const isLabelEnd = (node: AnyParseNode, endChar: string): boolean => {
return ((node.type === "mathord" || node.type === "atom") &&
node.text === endChar);
};
function cdArrow(
arrowChar: string,
labels: ParseNode<"ordgroup">[],
parser: Parser
): AnyParseNode {
const funcName = cdArrowFunctionName[arrowChar];
switch (funcName) {
case "\\\\cdrightarrow":
case "\\\\cdleftarrow":
return parser.callFunction(
funcName, [labels[0]], [labels[1]]
);
case "\\uparrow":
case "\\downarrow": {
const leftLabel = parser.callFunction(
"\\\\cdleft", [labels[0]], []
);
const bareArrow = {
type: "atom",
text: funcName,
mode: "math",
family: "rel",
};
const sizedArrow = parser.callFunction("\\Big", [bareArrow], []);
const rightLabel = parser.callFunction(
"\\\\cdright", [labels[1]], []
);
const arrowGroup = {
type: "ordgroup",
mode: "math",
body: [leftLabel, sizedArrow, rightLabel],
};
return parser.callFunction("\\\\cdparent", [arrowGroup], []);
}
case "\\\\cdlongequal":
return parser.callFunction("\\\\cdlongequal", [], []);
case "\\Vert": {
const arrow = {type: "textord", text: "\\Vert", mode: "math"};
return parser.callFunction("\\Big", [arrow], []);
}
default:
return {type: "textord", text: " ", mode: "math"};
}
}
export function parseCD(parser: Parser): ParseNode<"array"> {
const parsedRows: AnyParseNode[][] = [];
parser.gullet.beginGroup();
parser.gullet.macros.set("\\cr", "\\\\\\relax");
parser.gullet.beginGroup();
while (true) { parsedRows.push(parser.parseExpression(false, "\\\\"));
parser.gullet.endGroup();
parser.gullet.beginGroup();
const next = parser.fetch().text;
if (next === "&" || next === "\\\\") {
parser.consume();
} else if (next === "\\end") {
if (parsedRows[parsedRows.length - 1].length === 0) {
parsedRows.pop(); }
break;
} else {
throw new ParseError("Expected \\\\ or \\cr or \\end",
parser.nextToken);
}
}
let row = [];
const body = [row];
for (let i = 0; i < parsedRows.length; i++) {
const rowNodes = parsedRows[i];
let cell = newCell();
for (let j = 0; j < rowNodes.length; j++) {
if (!isStartOfArrow(rowNodes[j])) {
cell.body.push(rowNodes[j]);
} else {
row.push(cell);
j += 1;
const arrowChar = assertSymbolNodeType(rowNodes[j]).text;
const labels: ParseNode<"ordgroup">[] = new Array(2);
labels[0] = {type: "ordgroup", mode: "math", body: []};
labels[1] = {type: "ordgroup", mode: "math", body: []};
if ("=|.".indexOf(arrowChar) > -1) {
} else if ("<>AV".indexOf(arrowChar) > -1) {
for (let labelNum = 0; labelNum < 2; labelNum++) {
let inLabel = true;
for (let k = j + 1; k < rowNodes.length; k++) {
if (isLabelEnd(rowNodes[k], arrowChar)) {
inLabel = false;
j = k;
break;
}
if (isStartOfArrow(rowNodes[k])) {
throw new ParseError("Missing a " + arrowChar +
" character to complete a CD arrow.", rowNodes[k]);
}
labels[labelNum].body.push(rowNodes[k]);
}
if (inLabel) {
throw new ParseError("Missing a " + arrowChar +
" character to complete a CD arrow.", rowNodes[j]);
}
}
} else {
throw new ParseError(`Expected one of "<>AV=|." after @`,
rowNodes[j]);
}
const arrow: AnyParseNode = cdArrow(arrowChar, labels, parser);
const wrappedArrow = {
type: "styling",
body: [arrow],
mode: "math",
style: "display", };
row.push(wrappedArrow);
cell = newCell();
}
}
if (i % 2 === 0) {
row.push(cell);
} else {
row.shift();
}
row = [];
body.push(row);
}
parser.gullet.endGroup();
parser.gullet.endGroup();
const cols = new Array(body[0].length).fill({
type: "align",
align: "c",
pregap: 0.25, postgap: 0.25, });
return {
type: "array",
mode: "math",
body,
arraystretch: 1,
addJot: true,
rowGaps: [null],
cols,
colSeparationType: "CD",
hLinesBeforeRow: new Array(body.length + 1).fill([]),
};
}
defineFunction({
type: "cdlabel",
names: ["\\\\cdleft", "\\\\cdright"],
props: {
numArgs: 1,
},
handler({parser, funcName}, args) {
return {
type: "cdlabel",
mode: parser.mode,
side: funcName.slice(4),
label: args[0],
};
},
htmlBuilder(group, options) {
const newOptions = options.havingStyle(options.style.sup());
const label = buildCommon.wrapFragment(
html.buildGroup(group.label, newOptions, options), options);
label.classes.push("cd-label-" + group.side);
label.style.bottom = makeEm(0.8 - label.depth);
label.height = 0;
label.depth = 0;
return label;
},
mathmlBuilder(group, options) {
let label = new mathMLTree.MathNode("mrow",
[mml.buildGroup(group.label, options)]);
label = new mathMLTree.MathNode("mpadded", [label]);
label.setAttribute("width", "0");
if (group.side === "left") {
label.setAttribute("lspace", "-1width");
}
label.setAttribute("voffset", "0.7em");
label = new mathMLTree.MathNode("mstyle", [label]);
label.setAttribute("displaystyle", "false");
label.setAttribute("scriptlevel", "1");
return label;
},
});
defineFunction({
type: "cdlabelparent",
names: ["\\\\cdparent"],
props: {
numArgs: 1,
},
handler({parser}, args) {
return {
type: "cdlabelparent",
mode: parser.mode,
fragment: args[0],
};
},
htmlBuilder(group, options) {
const parent = buildCommon.wrapFragment(
html.buildGroup(group.fragment, options), options
);
parent.classes.push("cd-vert-arrow");
return parent;
},
mathmlBuilder(group, options) {
return new mathMLTree.MathNode("mrow",
[mml.buildGroup(group.fragment, options)]);
},
});