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