Flatten "styling", "text", and "array" ParseNodes. (#1559)

* Flatten "styling" ParseNode.

* Flatten "text" ParseNode.

* Flatten "array" ParseNode.
This commit is contained in:
Ashish Myles
2018-08-07 01:26:40 -04:00
committed by ylemkimon
parent f0b9a344ed
commit f3a9bd4b3d
11 changed files with 215 additions and 287 deletions

View File

@@ -319,10 +319,7 @@ export default class Parser {
const textNode = {
type: "text",
mode: this.mode,
value: {
type: "text",
body: textordArray,
},
};
const colorNode = {

View File

@@ -171,8 +171,7 @@ export const buildExpression = function(
} else if (node.type === "sizing") {
glueOptions = options.havingSize(node.size);
} else if (node.type === "styling") {
glueOptions = options.havingStyle(
styleMap[node.value.style]);
glueOptions = options.havingStyle(styleMap[node.style]);
}
}

View File

@@ -16,39 +16,15 @@ import type Parser from "../Parser";
import type {ParseNode, AnyParseNode} from "../parseNode";
import type {StyleStr} from "../types";
import type {HtmlBuilder, MathMLBuilder} from "../defineFunction";
import type {Measurement} from "../units";
// Data stored in the ParseNode associated with the environment.
type AlignSpec = { type: "separator", separator: string } | {
export type AlignSpec = { type: "separator", separator: string } | {
type: "align",
align: string,
pregap?: number,
postgap?: number,
};
export type ArrayEnvNodeData = {|
type: "array",
hskipBeforeAndAfter?: boolean,
addJot?: boolean,
cols?: AlignSpec[],
arraystretch: number,
body: AnyParseNode[][], // List of rows in the (2D) array.
rowGaps: (?Measurement)[],
hLinesBeforeRow: Array<boolean[]>,
|};
// Same as above but with some fields not yet filled.
type ArrayEnvNodeDataIncomplete = {|
type: "array",
hskipBeforeAndAfter?: boolean,
addJot?: boolean,
cols?: AlignSpec[],
// Before these fields are filled.
arraystretch?: number,
body?: AnyParseNode[][],
rowGaps?: (?Measurement)[],
hLinesBeforeRow?: Array<boolean[]>,
|};
function getHLines(parser: Parser): boolean[] {
// Return an array. The array length = number of hlines.
// Each element in the array tells if the line is dashed.
@@ -72,7 +48,12 @@ function getHLines(parser: Parser): boolean[] {
*/
function parseArray(
parser: Parser,
result: ArrayEnvNodeDataIncomplete,
{hskipBeforeAndAfter, addJot, cols, arraystretch}: {|
hskipBeforeAndAfter?: boolean,
addJot?: boolean,
cols?: AlignSpec[],
arraystretch?: number,
|},
style: StyleStr,
): ParseNode<"array"> {
// Parse body of array with \\ temporarily mapped to \cr
@@ -80,15 +61,15 @@ function parseArray(
parser.gullet.macros.set("\\\\", "\\cr");
// Get current arraystretch if it's not set by the environment
if (!result.arraystretch) {
const arraystretch = parser.gullet.expandMacroAsText("\\arraystretch");
if (arraystretch == null) {
if (!arraystretch) {
const stretch = parser.gullet.expandMacroAsText("\\arraystretch");
if (stretch == null) {
// Default \arraystretch from lttab.dtx
result.arraystretch = 1;
arraystretch = 1;
} else {
result.arraystretch = parseFloat(arraystretch);
if (!result.arraystretch || result.arraystretch < 0) {
throw new ParseError(`Invalid \\arraystretch: ${arraystretch}`);
arraystretch = parseFloat(stretch);
if (!arraystretch || arraystretch < 0) {
throw new ParseError(`Invalid \\arraystretch: ${stretch}`);
}
}
}
@@ -112,11 +93,8 @@ function parseArray(
cell = {
type: "styling",
mode: parser.mode,
value: {
type: "styling",
style: style,
value: [cell],
},
style,
body: [cell],
};
}
row.push(cell);
@@ -128,7 +106,7 @@ function parseArray(
// the last line is empty.
// NOTE: Currently, `cell` is the last item added into `row`.
if (row.length === 1 && cell.type === "styling" &&
cell.value.value[0].value.length === 0) {
cell.body[0].value.length === 0) {
body.pop();
}
if (hLinesBeforeRow.length < body.length + 1) {
@@ -152,16 +130,17 @@ function parseArray(
parser.nextToken);
}
}
result.body = body;
result.rowGaps = rowGaps;
result.hLinesBeforeRow = hLinesBeforeRow;
// $FlowFixMe: The required fields were added immediately above.
const res: ArrayEnvNodeData = result;
parser.gullet.endGroup();
return {
type: "array",
mode: parser.mode,
value: res,
addJot,
arraystretch,
body,
cols,
rowGaps,
hskipBeforeAndAfter,
hLinesBeforeRow,
};
}
@@ -186,8 +165,8 @@ type Outrow = {
const htmlBuilder: HtmlBuilder<"array"> = function(group, options) {
let r;
let c;
const nr = group.value.body.length;
const hLinesBeforeRow = group.value.hLinesBeforeRow;
const nr = group.body.length;
const hLinesBeforeRow = group.hLinesBeforeRow;
let nc = 0;
let body = new Array(nr);
const hlines = [];
@@ -201,7 +180,7 @@ const htmlBuilder: HtmlBuilder<"array"> = function(group, options) {
// Default \jot from ltmath.dtx
// TODO(edemaine): allow overriding \jot via \setlength (#687)
const jot = 3 * pt;
const arrayskip = group.value.arraystretch * baselineskip;
const arrayskip = group.arraystretch * baselineskip;
const arstrutHeight = 0.7 * arrayskip; // \strutbox in ltfsstrc.dtx and
const arstrutDepth = 0.3 * arrayskip; // \@arstrutbox in lttab.dtx
@@ -218,8 +197,8 @@ const htmlBuilder: HtmlBuilder<"array"> = function(group, options) {
}
setHLinePos(hLinesBeforeRow[0]);
for (r = 0; r < group.value.body.length; ++r) {
const inrow = group.value.body[r];
for (r = 0; r < group.body.length; ++r) {
const inrow = group.body[r];
let height = arstrutHeight; // \@array adds an \@arstrut
let depth = arstrutDepth; // to each tow (via the template)
@@ -239,7 +218,7 @@ const htmlBuilder: HtmlBuilder<"array"> = function(group, options) {
outrow[c] = elt;
}
const rowGap = group.value.rowGaps[r];
const rowGap = group.rowGaps[r];
let gap = 0;
if (rowGap) {
gap = calculateSize(rowGap, options);
@@ -254,7 +233,7 @@ const htmlBuilder: HtmlBuilder<"array"> = function(group, options) {
// In AMS multiline environments such as aligned and gathered, rows
// correspond to lines that have additional \jot added to the
// \baselineskip via \openup.
if (group.value.addJot) {
if (group.addJot) {
depth += jot;
}
@@ -270,7 +249,7 @@ const htmlBuilder: HtmlBuilder<"array"> = function(group, options) {
}
const offset = totalHeight / 2 + options.fontMetrics().axisHeight;
const colDescriptions = group.value.cols || [];
const colDescriptions = group.cols || [];
const cols = [];
let colSep;
let colDescrNum;
@@ -326,7 +305,7 @@ const htmlBuilder: HtmlBuilder<"array"> = function(group, options) {
}
let sepwidth;
if (c > 0 || group.value.hskipBeforeAndAfter) {
if (c > 0 || group.hskipBeforeAndAfter) {
sepwidth = utils.deflt(colDescr.pregap, arraycolsep);
if (sepwidth !== 0) {
colSep = buildCommon.makeSpan(["arraycolsep"], []);
@@ -357,7 +336,7 @@ const htmlBuilder: HtmlBuilder<"array"> = function(group, options) {
[col]);
cols.push(col);
if (c < nc - 1 || group.value.hskipBeforeAndAfter) {
if (c < nc - 1 || group.hskipBeforeAndAfter) {
sepwidth = utils.deflt(colDescr.postgap, arraycolsep);
if (sepwidth !== 0) {
colSep = buildCommon.makeSpan(["arraycolsep"], []);
@@ -393,7 +372,7 @@ const htmlBuilder: HtmlBuilder<"array"> = function(group, options) {
const mathmlBuilder: MathMLBuilder<"array"> = function(group, options) {
return new mathMLTree.MathNode(
"mtable", group.value.body.map(function(row) {
"mtable", group.body.map(function(row) {
return new mathMLTree.MathNode(
"mtr", row.map(function(cell) {
return new mathMLTree.MathNode(
@@ -405,12 +384,7 @@ const mathmlBuilder: MathMLBuilder<"array"> = function(group, options) {
// Convenience function for aligned and alignedat environments.
const alignedHandler = function(context, args) {
const cols = [];
let res = {
type: "array",
cols,
addJot: true,
};
res = parseArray(context.parser, res, "display");
const res = parseArray(context.parser, {cols, addJot: true}, "display");
// Determining number of columns.
// 1. If the first argument is given, we use it as a number of columns,
@@ -439,11 +413,11 @@ const alignedHandler = function(context, args) {
numCols = numMaths * 2;
}
const isAligned = !numCols;
res.value.body.forEach(function(row) {
res.body.forEach(function(row) {
for (let i = 1; i < row.length; i += 2) {
// Modify ordgroup node within styling node
const styling = assertNodeType(row[i], "styling");
const ordgroup = assertNodeType(styling.value.value[0], "ordgroup");
const ordgroup = assertNodeType(styling.body[0], "ordgroup");
ordgroup.value.unshift(emptyGroup);
}
if (!isAligned) { // Case 1
@@ -519,13 +493,11 @@ defineEnvironment({
}
throw new ParseError("Unknown column alignment: " + ca, nde);
});
let res = {
type: "array",
cols: cols,
const res = {
cols,
hskipBeforeAndAfter: true, // \@preamble in lttab.dtx
};
res = parseArray(context.parser, res, dCellStyle(context.envName));
return res;
return parseArray(context.parser, res, dCellStyle(context.envName));
},
htmlBuilder,
mathmlBuilder,
@@ -555,10 +527,8 @@ defineEnvironment({
"vmatrix": ["|", "|"],
"Vmatrix": ["\\Vert", "\\Vert"],
}[context.envName];
const payload = {
type: "array",
hskipBeforeAndAfter: false, // \hskip -\arraycolsep in amsmath
};
// \hskip -\arraycolsep in amsmath
const payload = {hskipBeforeAndAfter: false};
const res: ParseNode<"array"> =
parseArray(context.parser, payload, dCellStyle(context.envName));
return delimiters ? {
@@ -589,7 +559,6 @@ defineEnvironment({
},
handler(context) {
const payload = {
type: "array",
arraystretch: 1.2,
cols: [{
type: "align",
@@ -645,17 +614,15 @@ defineEnvironment({
props: {
numArgs: 0,
},
handler: function(context) {
let res = {
type: "array",
handler(context) {
const res = {
cols: [{
type: "align",
align: "c",
}],
addJot: true,
};
res = parseArray(context.parser, res, "display");
return res;
return parseArray(context.parser, res, "display");
},
htmlBuilder,
mathmlBuilder,

View File

@@ -62,11 +62,8 @@ defineFunction({
const body = {
type: "text",
mode: parser.mode,
value: {
type: "text",
font: "\\texttt",
body: chars,
},
};
return {
type: "href",

View File

@@ -25,11 +25,8 @@ defineFunction({
return {
type: "styling",
mode: parser.mode,
value: {
type: "styling",
style: "text",
value: body,
},
body,
};
},
});

View File

@@ -31,11 +31,8 @@ defineFunction({
const text = {
type: "text",
mode: group.mode,
value: {
type: "text",
body: ordargument(group.body),
font: "mathrm", // simulate \textrm
},
};
const sizedText = {
type: "sizing",

View File

@@ -23,7 +23,7 @@ defineFunction({
numArgs: 0,
allowedInText: true,
},
handler: ({breakOnTokenText, funcName, parser}, args) => {
handler({breakOnTokenText, funcName, parser}, args) {
// parse out the implicit body
parser.consumeSpaces();
const body = parser.parseExpression(true, breakOnTokenText);
@@ -35,22 +35,19 @@ defineFunction({
return {
type: "styling",
mode: parser.mode,
value: {
type: "styling",
// Figure out what style to use by pulling out the style from
// the function name
style,
value: body,
},
body,
};
},
htmlBuilder: (group, options) => {
htmlBuilder(group, options) {
// Style changes are handled in the TeXbook on pg. 442, Rule 3.
const newStyle = styleMap[group.value.style];
const newStyle = styleMap[group.style];
const newOptions = options.havingStyle(newStyle).withFont('');
return sizingGroup(group.value.value, newOptions, options);
return sizingGroup(group.body, newOptions, options);
},
mathmlBuilder: (group, options) => {
mathmlBuilder(group, options) {
// Figure out what style we're changing to.
// TODO(kevinb): dedupe this with buildHTML.js
// This will be easier of handling of styling nodes is in the same file.
@@ -61,10 +58,10 @@ defineFunction({
"scriptscript": Style.SCRIPTSCRIPT,
};
const newStyle = styleMap[group.value.style];
const newStyle = styleMap[group.style];
const newOptions = options.havingStyle(newStyle);
const inner = mml.buildExpression(group.value.value, newOptions);
const inner = mml.buildExpression(group.body, newOptions);
const node = new mathMLTree.MathNode("mstyle", inner);
@@ -75,7 +72,7 @@ defineFunction({
"scriptscript": ["2", "false"],
};
const attr = styleAttributes[group.value.style];
const attr = styleAttributes[group.style];
node.setAttribute("scriptlevel", attr[0]);
node.setAttribute("displaystyle", attr[1]);

View File

@@ -20,7 +20,7 @@ const textFontShapes = {
};
const optionsWithFont = (group, options) => {
const font = group.value.font;
const font = group.font;
// Checks if the argument is a font family or a font style.
if (!font) {
return options;
@@ -55,21 +55,18 @@ defineFunction({
return {
type: "text",
mode: parser.mode,
value: {
type: "text",
body: ordargument(body),
font: funcName,
},
};
},
htmlBuilder(group, options) {
const newOptions = optionsWithFont(group, options);
const inner = html.buildExpression(group.value.body, newOptions, true);
const inner = html.buildExpression(group.body, newOptions, true);
buildCommon.tryCombineChars(inner);
return buildCommon.makeSpan(["mord", "text"], inner, newOptions);
},
mathmlBuilder(group, options) {
const newOptions = optionsWithFont(group, options);
return mml.buildExpressionRow(group.value.body, newOptions);
return mml.buildExpressionRow(group.body, newOptions);
},
});

View File

@@ -1,7 +1,7 @@
// @flow
import {NON_ATOMS} from "./symbols";
import type SourceLocation from "./SourceLocation";
import type {ArrayEnvNodeData} from "./environments/array";
import type {AlignSpec} from "./environments/array";
import type {Atom} from "./symbols";
import type {Mode, StyleStr} from "./types";
import type {Token} from "./Token";
@@ -28,7 +28,13 @@ type ParseNodeTypes = {
type: "array",
mode: Mode,
loc?: ?SourceLocation,
value: ArrayEnvNodeData,
hskipBeforeAndAfter?: boolean,
addJot?: boolean,
cols?: AlignSpec[],
arraystretch: number,
body: AnyParseNode[][], // List of rows in the (2D) array.
rowGaps: (?Measurement)[],
hLinesBeforeRow: Array<boolean[]>,
|},
"color": {|
type: "color",
@@ -85,11 +91,8 @@ type ParseNodeTypes = {
type: "styling",
mode: Mode,
loc?: ?SourceLocation,
value: {|
type: "styling",
style: StyleStr,
value: AnyParseNode[],
|},
body: AnyParseNode[],
|},
"supsub": {|
type: "supsub",
@@ -110,12 +113,9 @@ type ParseNodeTypes = {
type: "text",
mode: Mode,
loc?: ?SourceLocation,
value: {|
type: "text",
body: AnyParseNode[],
font?: string,
|},
|},
"url": {|
type: "url",
mode: Mode,

View File

@@ -3,20 +3,13 @@
exports[`A begin/end parser should grab \\arraystretch 1`] = `
[
{
"type": "array",
"mode": "math",
"value": {
"type": "array",
"arraystretch": 1.5,
"body": [
[
{
"type": "styling",
"mode": "math",
"value": {
"type": "styling",
"style": "text",
"value": [
"body": [
{
"type": "ordgroup",
"mode": "math",
@@ -36,16 +29,13 @@ exports[`A begin/end parser should grab \\arraystretch 1`] = `
}
]
}
]
}
],
"mode": "math",
"style": "text"
},
{
"type": "styling",
"mode": "math",
"value": {
"type": "styling",
"style": "text",
"value": [
"body": [
{
"type": "ordgroup",
"mode": "math",
@@ -65,18 +55,15 @@ exports[`A begin/end parser should grab \\arraystretch 1`] = `
}
]
}
]
}
],
"mode": "math",
"style": "text"
}
],
[
{
"type": "styling",
"mode": "math",
"value": {
"type": "styling",
"style": "text",
"value": [
"body": [
{
"type": "ordgroup",
"mode": "math",
@@ -96,16 +83,13 @@ exports[`A begin/end parser should grab \\arraystretch 1`] = `
}
]
}
]
}
],
"mode": "math",
"style": "text"
},
{
"type": "styling",
"mode": "math",
"value": {
"type": "styling",
"style": "text",
"value": [
"body": [
{
"type": "ordgroup",
"mode": "math",
@@ -125,8 +109,9 @@ exports[`A begin/end parser should grab \\arraystretch 1`] = `
}
]
}
]
}
],
"mode": "math",
"style": "text"
}
]
],
@@ -139,11 +124,11 @@ exports[`A begin/end parser should grab \\arraystretch 1`] = `
]
],
"hskipBeforeAndAfter": false,
"mode": "math",
"rowGaps": [
null
]
}
}
]
`;
@@ -660,18 +645,15 @@ exports[`An implicit group parser within optional groups should work style comma
"value": [
{
"type": "styling",
"mode": "math",
"value": {
"type": "styling",
"style": "text",
"value": [
"body": [
{
"type": "textord",
"mode": "math",
"value": "3"
}
]
}
],
"mode": "math",
"style": "text"
}
]
},

View File

@@ -666,12 +666,12 @@ describe("A text parser", function() {
const parse = getParsed(textExpression)[0];
expect(parse.type).toEqual("text");
expect(parse.value).toBeDefined();
expect(parse.body).toBeDefined();
});
it("should produce textords instead of mathords", function() {
const parse = getParsed(textExpression)[0];
const group = parse.value.body;
const group = parse.body;
expect(group[0].type).toEqual("textord");
});
@@ -694,7 +694,7 @@ describe("A text parser", function() {
it("should contract spaces", function() {
const parse = getParsed(spaceTextExpression)[0];
const group = parse.value.body;
const group = parse.body;
expect(group[0].type).toEqual("spacing");
expect(group[1].type).toEqual("textord");
@@ -709,10 +709,8 @@ describe("A text parser", function() {
it("should ignore a space before the text group", function() {
const parse = getParsed(leadingSpaceTextExpression)[0];
// [m, o, o]
expect(parse.value.body).toHaveLength(3);
expect(
parse.value.body.map(function(n) { return n.value; }).join("")
).toBe("moo");
expect(parse.body).toHaveLength(3);
expect(parse.body.map(n => n.value).join("")).toBe("moo");
});
it("should parse math within text group", function() {
@@ -860,14 +858,14 @@ describe("A tie parser", function() {
it("should produce spacing in text mode", function() {
const text = getParsed(textTie)[0];
const parse = text.value.body;
const parse = text.body;
expect(parse[1].type).toEqual("spacing");
});
it("should not contract with spaces in text mode", function() {
const text = getParsed(textTie)[0];
const parse = text.value.body;
const parse = text.body;
expect(parse[2].type).toEqual("spacing");
});
@@ -1241,7 +1239,7 @@ describe("A begin/end parser", function() {
it("should eat a final newline", function() {
const m3 = getParsed`\begin{matrix}a&b\\ c&d \\ \end{matrix}`[0];
expect(m3.value.body).toHaveLength(2);
expect(m3.body).toHaveLength(2);
});
it("should grab \\arraystretch", function() {
@@ -1440,10 +1438,10 @@ describe("A style change parser", function() {
it("should produce the correct style", function() {
const displayParse = getParsed`\displaystyle x`[0];
expect(displayParse.value.style).toEqual("display");
expect(displayParse.style).toEqual("display");
const scriptscriptParse = getParsed`\scriptscriptstyle x`[0];
expect(scriptscriptParse.value.style).toEqual("scriptscript");
expect(scriptscriptParse.style).toEqual("scriptscript");
});
it("should only change the style within its group", function() {
@@ -1454,7 +1452,7 @@ describe("A style change parser", function() {
expect(displayNode.type).toEqual("styling");
const displayBody = displayNode.value.value;
const displayBody = displayNode.body;
expect(displayBody).toHaveLength(2);
expect(displayBody[0].value).toEqual("e");
@@ -2407,7 +2405,7 @@ describe("An array environment", function() {
it("should accept a single alignment character", function() {
const parse = getParsed`\begin{array}r1\\20\end{array}`;
expect(parse[0].type).toBe("array");
expect(parse[0].value.cols).toEqual([
expect(parse[0].cols).toEqual([
{type: "align", align: "r"},
]);
});
@@ -2415,7 +2413,7 @@ describe("An array environment", function() {
it("should accept vertical separators", function() {
const parse = getParsed`\begin{array}{|l||c:r::}\end{array}`;
expect(parse[0].type).toBe("array");
expect(parse[0].value.cols).toEqual([
expect(parse[0].cols).toEqual([
{type: "separator", separator: "|"},
{type: "align", align: "l"},
{type: "separator", separator: "|"},
@@ -2455,7 +2453,7 @@ describe("An aligned environment", function() {
it("should not eat the last row when its first cell is empty", function() {
const ae = getParsed`\begin{aligned}&E_1 & (1)\\&E_2 & (2)\\&E_3 & (3)\end{aligned}`[0];
expect(ae.value.body).toHaveLength(3);
expect(ae.body).toHaveLength(3);
});
});