Brunobkr/llama.cpp_AlgMor24_github
ΩFFFΣLLIa • llama.cpp • AlgMor24 ██████╗ ███████╗███████╗███████╗██╗ ██╗ ██╗ █████╗ ██╔═══██╗██╔════╝██╔════╝██╔════╝██║ ██║ ██║██╔══██╗ ██║ ██║█████╗ █████╗ █████╗ ██║ ██║ ██║███████║ ██║ ██║██╔══╝ ██╔══╝ ██╔══╝ ██║ ██║ ██║██╔══██║ ╚██████╔╝██║ ██║ ███████╗███████╗███████╗██║██║ ██║ ╚═════╝ ╚═╝ ╚═╝ ╚══════╝╚══════╝╚══════╝╚═╝╚═╝ ╚═╝ High-Performance LLM / VLM Inference & Autonomous Agentic Ecosystem… See the full description on the dataset page: https://huggingface.co/datasets/Brunobkr/llama.cpp_AlgMor24_github.
03.1k
1import {makeFragment, makeLineSpan, makeSpan, makeVList} from "../buildCommon";2import Style from "../Style";3import defineEnvironment from "../defineEnvironment";4import {parseCD} from "./cd";5import defineFunction from "../defineFunction";6import defineMacro from "../defineMacro";7import {MathNode} from "../mathMLTree";8import ParseError from "../ParseError";9import {assertNodeType, assertSymbolNodeType} from "../parseNode";10import {checkSymbolNodeType} from "../parseNode";11import {Token} from "../Token";12import {calculateSize, makeEm} from "../units";13 14import * as html from "../buildHTML";15import * as mml from "../buildMathML";16 17import type Parser from "../Parser";18import type {ParseNode, AnyParseNode} from "../parseNode";19import type {StyleStr, Mode} from "../types";20import type {HtmlBuilder, MathMLBuilder} from "../defineFunction";21import type {HtmlDomNode} from "../domTree";22 23type EnvContextLike = {24 parser: Parser;25 envName: string;26 mode: Mode;27};28 29// Data stored in the ParseNode associated with the environment.30export type AlignSpec = {type: "separator", separator: string} | {31 type: "align";32 align: string;33 pregap?: number;34 postgap?: number;35};36 37// Type to indicate column separation in MathML38export type ColSeparationType = "align" | "alignat" | "gather" | "small" | "CD";39 40// Helper functions41function getHLines(parser: Parser): boolean[] {42 // Return an array. The array length = number of hlines.43 // Each element in the array tells if the line is dashed.44 const hlineInfo = [];45 parser.consumeSpaces();46 let nxt = parser.fetch().text;47 if (nxt === "\\relax") { // \relax is an artifact of the \cr macro below48 parser.consume();49 parser.consumeSpaces();50 nxt = parser.fetch().text;51 }52 while (nxt === "\\hline" || nxt === "\\hdashline") {53 parser.consume();54 hlineInfo.push(nxt === "\\hdashline");55 parser.consumeSpaces();56 nxt = parser.fetch().text;57 }58 return hlineInfo;59}60 61const validateAmsEnvironmentContext = (context: EnvContextLike) => {62 const settings = context.parser.settings;63 if (!settings.displayMode) {64 throw new ParseError(`{${context.envName}} can be used only in` +65 ` display mode.`);66 }67};68 69const gatherEnvironments = new Set(["gather", "gather*"]);70 71// autoTag (an argument to parseArray) can be one of three values:72// * undefined: Regular (not-top-level) array; no tags on each row73// * true: Automatic equation numbering, overridable by \tag74// * false: Tags allowed on each row, but no automatic numbering75// This function *doesn't* work with the "split" environment name.76function getAutoTag(name: string): boolean | null | undefined {77 if (!name.includes("ed")) {78 return !name.includes("*");79 }80 // return undefined;81}82 83/**84 * Parse the body of the environment, with rows delimited by \\ and85 * columns delimited by &, and create a nested list in row-major order86 * with one group per cell. If given an optional argument style87 * ("text", "display", etc.), then each cell is cast into that style.88 */89function parseArray(90 parser: Parser,91 {92 hskipBeforeAndAfter,93 addJot,94 cols,95 arraystretch,96 colSeparationType,97 autoTag,98 singleRow,99 emptySingleRow,100 maxNumCols,101 leqno,102 }: {103 hskipBeforeAndAfter?: boolean;104 addJot?: boolean;105 cols?: AlignSpec[];106 arraystretch?: number;107 colSeparationType?: ColSeparationType;108 autoTag?: boolean | null | undefined;109 singleRow?: boolean;110 emptySingleRow?: boolean;111 maxNumCols?: number;112 leqno?: boolean;113 },114 style: StyleStr,115): ParseNode<"array"> {116 parser.gullet.beginGroup();117 if (!singleRow) {118 // \cr is equivalent to \\ without the optional size argument (see below)119 // TODO: provide helpful error when \cr is used outside array environment120 parser.gullet.macros.set("\\cr", "\\\\\\relax");121 }122 123 // Get current arraystretch if it's not set by the environment124 if (!arraystretch) {125 const stretch = parser.gullet.expandMacroAsText("\\arraystretch");126 if (stretch == null) {127 // Default \arraystretch from lttab.dtx128 arraystretch = 1;129 } else {130 arraystretch = parseFloat(stretch);131 if (!arraystretch || arraystretch < 0) {132 throw new ParseError(`Invalid \\arraystretch: ${stretch}`);133 }134 }135 }136 137 // Start group for first cell138 parser.gullet.beginGroup();139 140 let row: AnyParseNode[] = [];141 const body: AnyParseNode[][] = [row];142 const rowGaps = [];143 const hLinesBeforeRow = [];144 const tags: Array<AnyParseNode[] | boolean> | undefined =145 (autoTag != null ? [] : undefined);146 147 // amsmath uses \global\@eqnswtrue and \global\@eqnswfalse to represent148 // whether this row should have an equation number. Simulate this with149 // a \@eqnsw macro set to 1 or 0.150 function beginRow() {151 if (autoTag) {152 parser.gullet.macros.set("\\@eqnsw", "1", true);153 }154 }155 function endRow() {156 if (tags) {157 if (parser.gullet.macros.get("\\df@tag")) {158 tags.push(parser.subparse([new Token("\\df@tag")]));159 parser.gullet.macros.set("\\df@tag", undefined, true);160 } else {161 tags.push(Boolean(autoTag) &&162 parser.gullet.macros.get("\\@eqnsw") === "1");163 }164 }165 }166 beginRow();167 168 // Test for \hline at the top of the array.169 hLinesBeforeRow.push(getHLines(parser));170 171 while (true) {172 // Parse each cell in its own group (namespace)173 const cellBody = parser.parseExpression(false, singleRow ? "\\end" : "\\\\");174 parser.gullet.endGroup();175 parser.gullet.beginGroup();176 let cell: AnyParseNode = {177 type: "ordgroup",178 mode: parser.mode,179 body: cellBody,180 };181 if (style) {182 cell = {183 type: "styling",184 mode: parser.mode,185 style,186 resetFont: true,187 body: [cell],188 };189 }190 row.push(cell);191 const next = parser.fetch().text;192 if (next === "&") {193 if (maxNumCols && row.length === maxNumCols) {194 if (singleRow || colSeparationType) {195 // {equation} or {split}196 throw new ParseError("Too many tab characters: &",197 parser.nextToken);198 } else {199 // {array} environment200 parser.settings.reportNonstrict("textEnv", "Too few columns " +201 "specified in the {array} column argument.");202 }203 }204 parser.consume();205 } else if (next === "\\end") {206 endRow();207 // Arrays terminate newlines with `\crcr` which consumes a `\cr` if208 // the last line is empty. However, AMS environments keep the209 // empty row if it's the only one.210 // NOTE: Currently, `cell` is the last item added into `row`.211 if (row.length === 1 && cell.type === "styling" &&212 cell.body.length === 1 && cell.body[0].type === "ordgroup" &&213 cell.body[0].body.length === 0 &&214 (body.length > 1 || !emptySingleRow)) {215 body.pop();216 }217 if (hLinesBeforeRow.length < body.length + 1) {218 hLinesBeforeRow.push([]);219 }220 break;221 } else if (next === "\\\\") {222 parser.consume();223 let size;224 // \def\Let@{\let\\\math@cr}225 // \def\math@cr{...\math@cr@}226 // \def\math@cr@{\new@ifnextchar[\math@cr@@{\math@cr@@[\z@]}}227 // \def\math@cr@@[#1]{...\math@cr@@@...}228 // \def\math@cr@@@{\cr}229 if (parser.gullet.future().text !== " ") {230 size = parser.parseSizeGroup(true);231 }232 rowGaps.push(size ? size.value : null);233 endRow();234 235 // check for \hline(s) following the row separator236 hLinesBeforeRow.push(getHLines(parser));237 238 row = [];239 body.push(row);240 beginRow();241 } else {242 throw new ParseError("Expected & or \\\\ or \\cr or \\end",243 parser.nextToken);244 }245 }246 247 // End cell group248 parser.gullet.endGroup();249 // End array group defining \cr250 parser.gullet.endGroup();251 252 return {253 type: "array",254 mode: parser.mode,255 addJot,256 arraystretch,257 body,258 cols,259 rowGaps,260 hskipBeforeAndAfter,261 hLinesBeforeRow,262 colSeparationType,263 tags,264 leqno,265 };266}267 268// Decides on a style for cells in an array according to whether the given269// environment name starts with the letter 'd'.270function dCellStyle(envName: string): StyleStr {271 if (envName.slice(0, 1) === "d") {272 return "display";273 } else {274 return "text";275 }276}277 278type Outrow = {279 cells: HtmlDomNode[];280 height: number;281 depth: number;282 pos: number;283};284 285const htmlBuilder: HtmlBuilder<"array"> = function(group, options) {286 let r;287 let c;288 const nr = group.body.length;289 const hLinesBeforeRow = group.hLinesBeforeRow;290 let nc = 0;291 const body: Outrow[] = new Array(nr);292 const hlines: Array<{pos: number; isDashed: boolean}> = [];293 const ruleThickness = Math.max(294 // From LaTeX \showthe\arrayrulewidth. Equals 0.04 em.295 options.fontMetrics().arrayRuleWidth,296 options.minRuleThickness, // User override.297 );298 299 // Horizontal spacing300 const pt = 1 / options.fontMetrics().ptPerEm;301 let arraycolsep = 5 * pt; // default value, i.e. \arraycolsep in article.cls302 if (group.colSeparationType && group.colSeparationType === "small") {303 // We're in a {smallmatrix}. Default column space is \thickspace,304 // i.e. 5/18em = 0.2778em, per amsmath.dtx for {smallmatrix}.305 // But that needs adjustment because LaTeX applies \scriptstyle to the306 // entire array, including the colspace, but this function applies307 // \scriptstyle only inside each element.308 const localMultiplier = options.havingStyle(Style.SCRIPT).sizeMultiplier;309 arraycolsep = 0.2778 * (localMultiplier / options.sizeMultiplier);310 }311 312 // Vertical spacing313 const baselineskip = group.colSeparationType === "CD"314 ? calculateSize({number: 3, unit: "ex"}, options)315 : 12 * pt; // see size10.clo316 // Default \jot from ltmath.dtx317 // TODO(edemaine): allow overriding \jot via \setlength (#687)318 const jot = 3 * pt;319 const arrayskip = group.arraystretch * baselineskip;320 const arstrutHeight = 0.7 * arrayskip; // \strutbox in ltfsstrc.dtx and321 const arstrutDepth = 0.3 * arrayskip; // \@arstrutbox in lttab.dtx322 323 let totalHeight = 0;324 325 // Set a position for \hline(s) at the top of the array, if any.326 function setHLinePos(hlinesInGap: boolean[]) {327 for (let i = 0; i < hlinesInGap.length; ++i) {328 if (i > 0) {329 totalHeight += 0.25;330 }331 hlines.push({pos: totalHeight, isDashed: hlinesInGap[i]});332 }333 }334 setHLinePos(hLinesBeforeRow[0]);335 336 for (r = 0; r < group.body.length; ++r) {337 const inrow = group.body[r];338 let height = arstrutHeight; // \@array adds an \@arstrut339 let depth = arstrutDepth; // to each tow (via the template)340 341 if (nc < inrow.length) {342 nc = inrow.length;343 }344 345 const outrow: Outrow = {346 cells: new Array<HtmlDomNode>(inrow.length),347 height: 0,348 depth: 0,349 pos: 0,350 };351 for (c = 0; c < inrow.length; ++c) {352 const elt = html.buildGroup(inrow[c], options);353 if (depth < elt.depth) {354 depth = elt.depth;355 }356 if (height < elt.height) {357 height = elt.height;358 }359 outrow.cells[c] = elt;360 }361 362 const rowGap = group.rowGaps[r];363 let gap = 0;364 if (rowGap) {365 gap = calculateSize(rowGap, options);366 if (gap > 0) { // \@argarraycr367 gap += arstrutDepth;368 if (depth < gap) {369 depth = gap; // \@xargarraycr370 }371 gap = 0;372 }373 }374 // In AMS multiline environments such as aligned and gathered, rows375 // correspond to lines that have additional \jot added between lines376 // via \openup.377 // We simulate this by adding \jot depth to each row except the last.378 if (group.addJot && r < group.body.length - 1) {379 depth += jot;380 }381 382 outrow.height = height;383 outrow.depth = depth;384 totalHeight += height;385 outrow.pos = totalHeight;386 totalHeight += depth + gap; // \@yargarraycr387 body[r] = outrow;388 389 // Set a position for \hline(s), if any.390 setHLinePos(hLinesBeforeRow[r + 1]);391 }392 393 const offset = totalHeight / 2 + options.fontMetrics().axisHeight;394 const colDescriptions = group.cols || [];395 const cols: HtmlDomNode[] = [];396 let colSep;397 let colDescrNum;398 const tagSpans: Array<{399 type: "elem";400 elem: HtmlDomNode;401 shift: number;402 }> = [];403 404 if (group.tags && group.tags.some(tag => tag)) {405 // An environment with manual tags and/or automatic equation numbers.406 // Create node(s), the latter of which trigger CSS counter increment.407 for (r = 0; r < nr; ++r) {408 const rw = body[r];409 const shift = rw.pos - offset;410 const tag = group.tags[r];411 let tagSpan;412 if (tag === true) { // automatic numbering413 tagSpan = makeSpan(["eqn-num"], [], options);414 } else if (tag === false) {415 // \nonumber/\notag or starred environment416 tagSpan = makeSpan([], [], options);417 } else { // manual \tag418 tagSpan = makeSpan([],419 html.buildExpression(tag, options, true), options);420 }421 tagSpan.depth = rw.depth;422 tagSpan.height = rw.height;423 tagSpans.push({type: "elem", elem: tagSpan, shift});424 }425 }426 427 for (c = 0, colDescrNum = 0;428 // Continue while either there are more columns or more column429 // descriptions, so trailing separators don't get lost.430 c < nc || colDescrNum < colDescriptions.length;431 ++c, ++colDescrNum) {432 let colDescr: AlignSpec | undefined = colDescriptions[colDescrNum];433 434 let firstSeparator = true;435 while (colDescr?.type === "separator") {436 // If there is more than one separator in a row, add a space437 // between them.438 if (!firstSeparator) {439 colSep = makeSpan(["arraycolsep"], []);440 colSep.style.width =441 makeEm(options.fontMetrics().doubleRuleSep);442 cols.push(colSep);443 }444 445 if (colDescr.separator === "|" || colDescr.separator === ":") {446 const lineType = colDescr.separator === "|" ? "solid" : "dashed";447 const separator = makeSpan(["vertical-separator"], [], options);448 separator.style.height = makeEm(totalHeight);449 separator.style.borderRightWidth = makeEm(ruleThickness);450 separator.style.borderRightStyle = lineType;451 separator.style.margin = `0 ${makeEm(-ruleThickness / 2)}`;452 const shift = totalHeight - offset;453 if (shift) {454 separator.style.verticalAlign = makeEm(-shift);455 }456 457 cols.push(separator);458 } else {459 throw new ParseError(460 "Invalid separator type: " + colDescr.separator);461 }462 463 colDescrNum++;464 colDescr = colDescriptions[colDescrNum];465 firstSeparator = false;466 }467 468 if (c >= nc) {469 continue;470 }471 472 let sepwidth;473 if (c > 0 || group.hskipBeforeAndAfter) {474 sepwidth = colDescr?.pregap ?? arraycolsep;475 if (sepwidth !== 0) {476 colSep = makeSpan(["arraycolsep"], []);477 colSep.style.width = makeEm(sepwidth);478 cols.push(colSep);479 }480 }481 482 const colElems: Array<{483 type: "elem";484 elem: HtmlDomNode;485 shift: number;486 }> = [];487 for (r = 0; r < nr; ++r) {488 const row = body[r];489 const elem = row.cells[c];490 if (!elem) {491 continue;492 }493 const shift = row.pos - offset;494 elem.depth = row.depth;495 elem.height = row.height;496 colElems.push({type: "elem", elem: elem, shift: shift});497 }498 499 const colVList = makeVList({500 positionType: "individualShift",501 children: colElems,502 }, options);503 const colSpan = makeSpan(504 ["col-align-" + (colDescr?.align || "c")],505 [colVList],506 );507 cols.push(colSpan);508 509 if (c < nc - 1 || group.hskipBeforeAndAfter) {510 sepwidth = colDescr?.postgap ?? arraycolsep;511 if (sepwidth !== 0) {512 colSep = makeSpan(["arraycolsep"], []);513 colSep.style.width = makeEm(sepwidth);514 cols.push(colSep);515 }516 }517 }518 519 let tableBody: HtmlDomNode = makeSpan(["mtable"], cols);520 521 // Add \hline(s), if any.522 if (hlines.length > 0) {523 const line = makeLineSpan("hline", options, ruleThickness);524 const dashes = makeLineSpan("hdashline", options, ruleThickness);525 const vListElems = [{type: "elem" as const, elem: tableBody, shift: 0}];526 while (hlines.length > 0) {527 const hline = hlines.pop()!;528 const lineShift = hline.pos - offset;529 if (hline.isDashed) {530 vListElems.push({type: "elem" as const, elem: dashes, shift: lineShift});531 } else {532 vListElems.push({type: "elem" as const, elem: line, shift: lineShift});533 }534 }535 536 tableBody = makeVList({537 positionType: "individualShift",538 children: vListElems,539 }, options);540 }541 542 if (tagSpans.length === 0) {543 return makeSpan(["mord"], [tableBody], options);544 } else {545 const eqnNumCol = makeVList({546 positionType: "individualShift",547 children: tagSpans,548 }, options);549 const tagCol = makeSpan(["tag"], [eqnNumCol], options);550 return makeFragment([tableBody, tagCol]);551 }552};553 554const alignMap: Record<string, string> = {555 c: "center ",556 l: "left ",557 r: "right ",558};559 560const mathmlBuilder: MathMLBuilder<"array"> = function(group, options) {561 const tbl = [];562 const glue = new MathNode("mtd", [], ["mtr-glue"]);563 const tag = new MathNode("mtd", [], ["mml-eqn-num"]);564 for (let i = 0; i < group.body.length; i++) {565 const rw = group.body[i];566 const row = [];567 for (let j = 0; j < rw.length; j++) {568 row.push(new MathNode("mtd",569 [mml.buildGroup(rw[j], options)]));570 }571 if (group.tags && group.tags[i]) {572 row.unshift(glue);573 row.push(glue);574 if (group.leqno) {575 row.unshift(tag);576 } else {577 row.push(tag);578 }579 }580 tbl.push(new MathNode("mtr", row));581 }582 let table = new MathNode("mtable", tbl);583 584 // Set column alignment, row spacing, column spacing, and585 // array lines by setting attributes on the table element.586 587 // Set the row spacing. In MathML, we specify a gap distance.588 // We do not use rowGap[] because MathML automatically increases589 // cell height with the height/depth of the element content.590 591 // LaTeX \arraystretch multiplies the row baseline-to-baseline distance.592 // We simulate this by adding (arraystretch - 1)em to the gap. This593 // does a reasonable job of adjusting arrays containing 1 em tall content.594 595 // The 0.16 and 0.09 values are found empirically. They produce an array596 // similar to LaTeX and in which content does not interfere with \hlines.597 const gap = (group.arraystretch === 0.5)598 ? 0.1 // {smallmatrix}, {subarray}599 : 0.16 + group.arraystretch - 1 + (group.addJot ? 0.09 : 0);600 table.setAttribute("rowspacing", makeEm(gap));601 602 // MathML table lines go only between cells.603 // To place a line on an edge we'll use <menclose>, if necessary.604 let menclose = "";605 let align = "";606 607 if (group.cols && group.cols.length > 0) {608 // Find column alignment, column spacing, and vertical lines.609 const cols = group.cols;610 let columnLines = "";611 let prevTypeWasAlign = false;612 let iStart = 0;613 let iEnd = cols.length;614 615 if (cols[0].type === "separator") {616 menclose += "top ";617 iStart = 1;618 }619 if (cols[cols.length - 1].type === "separator") {620 menclose += "bottom ";621 iEnd -= 1;622 }623 624 for (let i = iStart; i < iEnd; i++) {625 const col = cols[i];626 if (col.type === "align") {627 align += alignMap[col.align];628 629 if (prevTypeWasAlign) {630 columnLines += "none ";631 }632 prevTypeWasAlign = true;633 } else if (col.type === "separator") {634 // MathML accepts only single lines between cells.635 // So we read only the first of consecutive separators.636 if (prevTypeWasAlign) {637 columnLines += col.separator === "|" ? "solid " : "dashed ";638 prevTypeWasAlign = false;639 }640 }641 }642 643 table.setAttribute("columnalign", align.trim());644 645 if (/[sd]/.test(columnLines)) {646 table.setAttribute("columnlines", columnLines.trim());647 }648 }649 650 // Set column spacing.651 if (group.colSeparationType === "align") {652 const cols = group.cols || [];653 let spacing = "";654 for (let i = 1; i < cols.length; i++) {655 spacing += i % 2 ? "0em " : "1em ";656 }657 table.setAttribute("columnspacing", spacing.trim());658 } else if (group.colSeparationType === "alignat" ||659 group.colSeparationType === "gather") {660 table.setAttribute("columnspacing", "0em");661 } else if (group.colSeparationType === "small") {662 table.setAttribute("columnspacing", "0.2778em");663 } else if (group.colSeparationType === "CD") {664 table.setAttribute("columnspacing", "0.5em");665 } else {666 table.setAttribute("columnspacing", "1em");667 }668 669 // Address \hline and \hdashline670 let rowLines = "";671 const hlines = group.hLinesBeforeRow;672 673 menclose += hlines[0].length > 0 ? "left " : "";674 menclose += hlines[hlines.length - 1].length > 0 ? "right " : "";675 676 for (let i = 1; i < hlines.length - 1; i++) {677 rowLines += (hlines[i].length === 0)678 ? "none "679 // MathML accepts only a single line between rows. Read one element.680 : hlines[i][0] ? "dashed " : "solid ";681 }682 if (/[sd]/.test(rowLines)) {683 table.setAttribute("rowlines", rowLines.trim());684 }685 686 if (menclose !== "") {687 table = new MathNode("menclose", [table]);688 table.setAttribute("notation", menclose.trim());689 }690 691 if (group.arraystretch && group.arraystretch < 1) {692 // A small array. Wrap in scriptstyle so row gap is not too large.693 table = new MathNode("mstyle", [table]);694 table.setAttribute("scriptlevel", "1");695 }696 697 return table;698};699 700// Convenience function for align, align*, aligned, alignat, alignat*, alignedat.701const alignedHandler = function(context: EnvContextLike, args: AnyParseNode[]) {702 if (!context.envName.includes("ed")) {703 validateAmsEnvironmentContext(context);704 }705 const cols: AlignSpec[] = [];706 const separationType: ColSeparationType = context.envName.includes("at") ? "alignat" : "align";707 const isSplit = context.envName === "split";708 const res = parseArray(context.parser,709 {710 cols,711 addJot: true,712 autoTag: isSplit ? undefined : getAutoTag(context.envName),713 emptySingleRow: true,714 colSeparationType: separationType,715 maxNumCols: isSplit ? 2 : undefined,716 leqno: context.parser.settings.leqno,717 },718 "display"719 );720 721 // Determining number of columns.722 // 1. If the first argument is given, we use it as a number of columns,723 // and makes sure that each row doesn't exceed that number.724 // 2. Otherwise, just count number of columns = maximum number725 // of cells in each row ("aligned" mode -- isAligned will be true).726 //727 // At the same time, prepend empty group {} at beginning of every second728 // cell in each row (starting with second cell) so that operators become729 // binary. This behavior is implemented in amsmath's \start@aligned.730 let numMaths = 0;731 let numCols = 0;732 const emptyGroup: ParseNode<"ordgroup"> = {733 type: "ordgroup",734 mode: context.mode,735 body: [],736 };737 if (args[0] && args[0].type === "ordgroup") {738 let arg0 = "";739 for (let i = 0; i < args[0].body.length; i++) {740 const textord = assertNodeType(args[0].body[i], "textord");741 arg0 += textord.text;742 }743 numMaths = Number(arg0);744 numCols = numMaths * 2;745 }746 const isAligned = !numCols;747 res.body.forEach(function(row) {748 for (let i = 1; i < row.length; i += 2) {749 // Modify ordgroup node within styling node750 const styling = assertNodeType(row[i], "styling");751 const ordgroup = assertNodeType(styling.body[0], "ordgroup");752 ordgroup.body.unshift(emptyGroup);753 }754 if (!isAligned) { // Case 1755 const curMaths = row.length / 2;756 if (numMaths < curMaths) {757 throw new ParseError(758 "Too many math in a row: " +759 `expected ${numMaths}, but got ${curMaths}`,760 row[0]);761 }762 } else if (numCols < row.length) { // Case 2763 numCols = row.length;764 }765 });766 767 // Adjusting alignment.768 // In aligned mode, we add one \qquad between columns;769 // otherwise we add nothing.770 for (let i = 0; i < numCols; ++i) {771 let align = "r";772 let pregap = 0;773 if (i % 2 === 1) {774 align = "l";775 } else if (i > 0 && isAligned) { // "aligned" mode.776 pregap = 1; // add one \quad777 }778 cols[i] = {779 type: "align",780 align: align,781 pregap: pregap,782 postgap: 0,783 };784 }785 res.colSeparationType = isAligned ? "align" : "alignat";786 return res;787};788 789// Arrays are part of LaTeX, defined in lttab.dtx so its documentation790// is part of the source2e.pdf file of LaTeX2e source documentation.791// {darray} is an {array} environment where cells are set in \displaystyle,792// as defined in nccmath.sty.793defineEnvironment({794 type: "array",795 names: ["array", "darray"],796 props: {797 numArgs: 1,798 },799 handler(context, args) {800 // Since no types are specified above, the two possibilities are801 // - The argument is wrapped in {} or [], in which case Parser's802 // parseGroup() returns an "ordgroup" wrapping some symbol node.803 // - The argument is a bare symbol node.804 const symNode = checkSymbolNodeType(args[0]);805 const colalign: AnyParseNode[] =806 symNode ? [args[0]] : assertNodeType(args[0], "ordgroup").body;807 const cols: AlignSpec[] = colalign.map(function(nde) {808 const node = assertSymbolNodeType(nde);809 const ca = node.text;810 if ("lcr".includes(ca)) {811 return {812 type: "align",813 align: ca,814 };815 } else if (ca === "|") {816 return {817 type: "separator",818 separator: "|",819 };820 } else if (ca === ":") {821 return {822 type: "separator",823 separator: ":",824 };825 }826 throw new ParseError("Unknown column alignment: " + ca, nde);827 });828 const res: Parameters<typeof parseArray>[1] = {829 cols,830 hskipBeforeAndAfter: true, // \@preamble in lttab.dtx831 maxNumCols: cols.length,832 };833 return parseArray(context.parser, res, dCellStyle(context.envName));834 },835 htmlBuilder,836 mathmlBuilder,837});838 839// The matrix environments of amsmath builds on the array environment840// of LaTeX, which is discussed above.841// The mathtools package adds starred versions of the same environments.842// These have an optional argument to choose left|center|right justification.843defineEnvironment({844 type: "array",845 names: [846 "matrix",847 "pmatrix",848 "bmatrix",849 "Bmatrix",850 "vmatrix",851 "Vmatrix",852 "matrix*",853 "pmatrix*",854 "bmatrix*",855 "Bmatrix*",856 "vmatrix*",857 "Vmatrix*",858 ],859 props: {860 numArgs: 0,861 },862 handler(context) {863 const delimiters = {864 "matrix": null,865 "pmatrix": ["(", ")"],866 "bmatrix": ["[", "]"],867 "Bmatrix": ["\\{", "\\}"],868 "vmatrix": ["|", "|"],869 "Vmatrix": ["\\Vert", "\\Vert"],870 }[context.envName.replace("*", "")];871 // \hskip -\arraycolsep in amsmath872 let colAlign = "c";873 const payload: Parameters<typeof parseArray>[1] = {874 hskipBeforeAndAfter: false,875 cols: [{type: "align", align: colAlign}],876 };877 if (context.envName.charAt(context.envName.length - 1) === "*") {878 // It's one of the mathtools starred functions.879 // Parse the optional alignment argument.880 const parser = context.parser;881 parser.consumeSpaces();882 if (parser.fetch().text === "[") {883 parser.consume();884 parser.consumeSpaces();885 colAlign = parser.fetch().text;886 if (!"lcr".includes(colAlign)) {887 throw new ParseError("Expected l or c or r", parser.nextToken);888 }889 parser.consume();890 parser.consumeSpaces();891 parser.expect("]");892 parser.consume();893 payload.cols = [{type: "align", align: colAlign}];894 }895 }896 const res: ParseNode<"array"> =897 parseArray(context.parser, payload, dCellStyle(context.envName));898 // Populate cols with the correct number of column alignment specs.899 const numCols = Math.max(0, ...res.body.map(row => row.length));900 res.cols = new Array(numCols).fill(901 {type: "align", align: colAlign}902 );903 return delimiters ? {904 type: "leftright",905 mode: context.mode,906 body: [res],907 left: delimiters[0],908 right: delimiters[1],909 rightColor: undefined, // \right uninfluenced by \color in array910 } : res;911 },912 htmlBuilder,913 mathmlBuilder,914});915 916defineEnvironment({917 type: "array",918 names: ["smallmatrix"],919 props: {920 numArgs: 0,921 },922 handler(context) {923 const payload: Parameters<typeof parseArray>[1] = {arraystretch: 0.5};924 const res = parseArray(context.parser, payload, "script");925 res.colSeparationType = "small";926 return res;927 },928 htmlBuilder,929 mathmlBuilder,930});931 932defineEnvironment({933 type: "array",934 names: ["subarray"],935 props: {936 numArgs: 1,937 },938 handler(context, args) {939 // Parsing of {subarray} is similar to {array}940 const symNode = checkSymbolNodeType(args[0]);941 const colalign: AnyParseNode[] =942 symNode ? [args[0]] : assertNodeType(args[0], "ordgroup").body;943 const cols: AlignSpec[] = colalign.map(function(nde) {944 const node = assertSymbolNodeType(nde);945 const ca = node.text;946 // {subarray} only recognizes "l" & "c"947 if ("lc".includes(ca)) {948 return {949 type: "align",950 align: ca,951 };952 }953 throw new ParseError("Unknown column alignment: " + ca, nde);954 });955 if (cols.length > 1) {956 throw new ParseError("{subarray} can contain only one column");957 }958 const payload: Parameters<typeof parseArray>[1] = {959 cols,960 hskipBeforeAndAfter: false,961 arraystretch: 0.5,962 };963 const res = parseArray(context.parser, payload, "script");964 if (res.body.length > 0 && res.body[0].length > 1) {965 throw new ParseError("{subarray} can contain only one column");966 }967 return res;968 },969 htmlBuilder,970 mathmlBuilder,971});972 973// A cases environment (in amsmath.sty) is almost equivalent to974// \def\arraystretch{1.2}%975// \left\{\begin{array}{@{}l@{\quad}l@{}} … \end{array}\right.976// {dcases} is a {cases} environment where cells are set in \displaystyle,977// as defined in mathtools.sty.978// {rcases} is another mathtools environment. It's brace is on the right side.979defineEnvironment({980 type: "array",981 names: [982 "cases",983 "dcases",984 "rcases",985 "drcases",986 ],987 props: {988 numArgs: 0,989 },990 handler(context) {991 const payload: Parameters<typeof parseArray>[1] = {992 arraystretch: 1.2,993 cols: [{994 type: "align",995 align: "l",996 pregap: 0,997 // TODO(kevinb) get the current style.998 // For now we use the metrics for TEXT style which is what we were999 // doing before. Before attempting to get the current style we1000 // should look at TeX's behavior especially for \over and matrices.1001 postgap: 1.0, /* 1em quad */1002 }, {1003 type: "align",1004 align: "l",1005 pregap: 0,1006 postgap: 0,1007 }],1008 };1009 const res: ParseNode<"array"> =1010 parseArray(context.parser, payload, dCellStyle(context.envName));1011 return {1012 type: "leftright",1013 mode: context.mode,1014 body: [res],1015 left: context.envName.includes("r") ? "." : "\\{",1016 right: context.envName.includes("r") ? "\\}" : ".",1017 rightColor: undefined,1018 };1019 },1020 htmlBuilder,1021 mathmlBuilder,1022});1023 1024// In the align environment, one uses ampersands, &, to specify number of1025// columns in each row, and to locate spacing between each column.1026// align gets automatic numbering. align* and aligned do not.1027// The alignedat environment can be used in math mode.1028// Note that we assume \nomallineskiplimit to be zero,1029// so that \strut@ is the same as \strut.1030defineEnvironment({1031 type: "array",1032 names: ["align", "align*", "aligned", "split"],1033 props: {1034 numArgs: 0,1035 },1036 handler: alignedHandler,1037 htmlBuilder,1038 mathmlBuilder,1039});1040 1041// A gathered environment is like an array environment with one centered1042// column, but where rows are considered lines so get \jot line spacing1043// and contents are set in \displaystyle.1044defineEnvironment({1045 type: "array",1046 names: ["gathered", "gather", "gather*"],1047 props: {1048 numArgs: 0,1049 },1050 handler(context) {1051 if (gatherEnvironments.has(context.envName)) {1052 validateAmsEnvironmentContext(context);1053 }1054 const res: Parameters<typeof parseArray>[1] = {1055 cols: [{1056 type: "align",1057 align: "c",1058 }],1059 addJot: true,1060 colSeparationType: "gather",1061 autoTag: getAutoTag(context.envName),1062 emptySingleRow: true,1063 leqno: context.parser.settings.leqno,1064 };1065 return parseArray(context.parser, res, "display");1066 },1067 htmlBuilder,1068 mathmlBuilder,1069});1070 1071// alignat environment is like an align environment, but one must explicitly1072// specify maximum number of columns in each row, and can adjust spacing between1073// each columns.1074defineEnvironment({1075 type: "array",1076 names: ["alignat", "alignat*", "alignedat"],1077 props: {1078 numArgs: 1,1079 },1080 handler: alignedHandler,1081 htmlBuilder,1082 mathmlBuilder,1083});1084 1085defineEnvironment({1086 type: "array",1087 names: ["equation", "equation*"],1088 props: {1089 numArgs: 0,1090 },1091 handler(context) {1092 validateAmsEnvironmentContext(context);1093 const res: Parameters<typeof parseArray>[1] = {1094 autoTag: getAutoTag(context.envName),1095 emptySingleRow: true,1096 singleRow: true,1097 maxNumCols: 1,1098 leqno: context.parser.settings.leqno,1099 };1100 return parseArray(context.parser, res, "display");1101 },1102 htmlBuilder,1103 mathmlBuilder,1104});1105 1106defineEnvironment({1107 type: "array",1108 names: ["CD"],1109 props: {1110 numArgs: 0,1111 },1112 handler(context) {1113 validateAmsEnvironmentContext(context);1114 return parseCD(context.parser);1115 },1116 htmlBuilder,1117 mathmlBuilder,1118});1119 1120defineMacro("\\nonumber", "\\gdef\\@eqnsw{0}");1121defineMacro("\\notag", "\\nonumber");1122 1123// Catch \hline outside array environment1124defineFunction({1125 type: "text", // Doesn't matter what this is.1126 names: ["\\hline", "\\hdashline"],1127 props: {1128 numArgs: 0,1129 allowedInText: true,1130 allowedInMath: true,1131 },1132 handler(context, args) {1133 throw new ParseError(1134 `${context.funcName} valid only within array environment`);1135 },1136});1137 