Replace ParseNode<*> with a more accurate AnyParseNode and fix flow errors. (#1387)

* Replace ParseNode<*> with a more accurate AnyParseNode and fix flow errors.

* Allow "array" environment type spec to use any all symbol type.

Before this commit, it was constrained to use "mathord" and "textord", but a
recent change in HEAD resulted in a "rel" node being used in the spec for, e.g.
\begin{array}{|l||c:r::}\end{array}

* Address reviewer comments: rename `lastRow` to `row` in array.js.
This commit is contained in:
Ashish Myles
2018-06-04 10:56:51 -04:00
committed by Kevin Barabash
parent 4492eedb68
commit 19d2aa63c3
20 changed files with 284 additions and 171 deletions

View File

@@ -18,7 +18,7 @@ import domTree from "./src/domTree";
import utils from "./src/utils";
import type {SettingsOptions} from "./src/Settings";
import type ParseNode from "./src/ParseNode";
import type {AnyParseNode} from "./src/ParseNode";
import {defineSymbol} from './src/symbols';
import {defineMacro} from './src/macros';
@@ -70,7 +70,7 @@ const renderToString = function(
const generateParseTree = function(
expression: string,
options: SettingsOptions,
): ParseNode<*>[] {
): AnyParseNode[] {
const settings = new Settings(options);
return parseTree(expression, settings);
};

View File

@@ -1,7 +1,8 @@
// @flow
import ParseNode from "./ParseNode";
import {Token} from "./Token";
import type {AnyParseNode} from "./ParseNode";
/**
* This is the ParseError class, which is the main error thrown by KaTeX
* functions when something has gone wrong. This is used to distinguish internal
@@ -16,7 +17,7 @@ class ParseError {
constructor(
message: string, // The error message
token?: Token | ParseNode<*>, // An object providing position information
token?: ?Token | AnyParseNode, // An object providing position information
) {
let error = "KaTeX parse error: " + message;
let start;

View File

@@ -1,9 +1,10 @@
// @flow
import SourceLocation from "./SourceLocation";
import type {ArrayEnvNodeData} from "./environments/array.js";
import {GROUPS} from "./symbols";
import type {ArrayEnvNodeData} from "./environments/array";
import type {Mode, StyleStr} from "./types";
import type {Token} from "./Token.js";
import type {Measurement} from "./units.js";
import type {Token} from "./Token";
import type {Measurement} from "./units";
/**
* The resulting parse tree nodes of the parse tree.
@@ -40,18 +41,83 @@ export type NodeValue<TYPE: NodeType> = $ElementType<ParseNodeTypes, TYPE>;
export type LeftRightDelimType = {|
type: "leftright",
body: ParseNode<*>[],
body: AnyParseNode[],
left: string,
right: string,
|};
// ParseNode's corresponding to Symbol `Group`s in symbols.js.
export type SymbolParseNode =
ParseNode<"accent-token"> |
ParseNode<"bin"> |
ParseNode<"close"> |
ParseNode<"inner"> |
ParseNode<"mathord"> |
ParseNode<"op-token"> |
ParseNode<"open"> |
ParseNode<"punct"> |
ParseNode<"rel"> |
ParseNode<"spacing"> |
ParseNode<"textord">;
// Union of all possible `ParseNode<>` types.
// Unable to derive this directly from `ParseNodeTypes` due to
// https://github.com/facebook/flow/issues/6369.
// Cannot use `ParseNode<NodeType>` since `ParseNode` is not strictly co-variant
// w.r.t. its type parameter due to the way the value type is computed.
export type AnyParseNode =
SymbolParseNode |
ParseNode<"array"> |
ParseNode<"color"> |
ParseNode<"color-token"> |
ParseNode<"op"> |
ParseNode<"ordgroup"> |
ParseNode<"size"> |
ParseNode<"styling"> |
ParseNode<"supsub"> |
ParseNode<"tag"> |
ParseNode<"text"> |
ParseNode<"url"> |
ParseNode<"verb"> |
ParseNode<"accent"> |
ParseNode<"accentUnder"> |
ParseNode<"cr"> |
ParseNode<"delimsizing"> |
ParseNode<"enclose"> |
ParseNode<"environment"> |
ParseNode<"font"> |
ParseNode<"genfrac"> |
ParseNode<"horizBrace"> |
ParseNode<"href"> |
ParseNode<"infix"> |
ParseNode<"kern"> |
ParseNode<"lap"> |
ParseNode<"leftright"> |
ParseNode<"leftright-right"> |
ParseNode<"mathchoice"> |
ParseNode<"middle"> |
ParseNode<"mclass"> |
ParseNode<"mod"> |
ParseNode<"operatorname"> |
ParseNode<"overline"> |
ParseNode<"phantom"> |
ParseNode<"hphantom"> |
ParseNode<"vphantom"> |
ParseNode<"raisebox"> |
ParseNode<"rule"> |
ParseNode<"sizing"> |
ParseNode<"smash"> |
ParseNode<"sqrt"> |
ParseNode<"underline"> |
ParseNode<"xArrow">;
// Map from `type` field value to corresponding `value` type.
export type ParseNodeTypes = {
"array": ArrayEnvNodeData,
"color": {|
type: "color",
color: string,
value: ParseNode<*>[],
value: AnyParseNode[],
|},
"color-token": string,
// To avoid requiring run-time type assertions, this more carefully captures
@@ -73,9 +139,9 @@ export type ParseNodeTypes = {
suppressBaseShift?: boolean,
symbol: false, // If 'symbol' is true, `body` *must* be set.
body?: void,
value: ParseNode<*>[],
value: AnyParseNode[],
|},
"ordgroup": ParseNode<*>[],
"ordgroup": AnyParseNode[],
"size": {|
type: "size",
value: Measurement,
@@ -83,22 +149,22 @@ export type ParseNodeTypes = {
"styling": {|
type: "styling",
style: StyleStr,
value: ParseNode<*>[],
value: AnyParseNode[],
|},
"supsub": {|
type: "supsub",
base: ?ParseNode<*>,
sup?: ?ParseNode<*>,
sub?: ?ParseNode<*>,
base: ?AnyParseNode,
sup?: ?AnyParseNode,
sub?: ?AnyParseNode,
|},
"tag": {|
type: "tag",
body: ParseNode<*>[],
tag: ParseNode<*>[],
body: AnyParseNode[],
tag: AnyParseNode[],
|},
"text": {|
type: "text",
body: ParseNode<*>[],
body: AnyParseNode[],
font?: string,
|},
"url": {|
@@ -131,14 +197,14 @@ export type ParseNodeTypes = {
label: string,
isStretchy?: boolean,
isShifty?: boolean,
base: ParseNode<*>,
base: AnyParseNode,
|},
"accentUnder": {|
type: "accentUnder",
label: string,
isStretchy?: boolean,
isShifty?: boolean,
base: ParseNode<*>,
base: AnyParseNode,
|},
"cr": {|
type: "cr",
@@ -157,23 +223,23 @@ export type ParseNodeTypes = {
label: string,
backgroundColor?: ParseNode<"color-token">,
borderColor?: ParseNode<"color-token">,
body: ParseNode<*>,
body: AnyParseNode,
|},
"environment": {|
type: "environment",
name: string,
nameGroup: ParseNode<*>,
nameGroup: AnyParseNode,
|},
"font": {|
type: "font",
font: string,
body: ParseNode<*>,
body: AnyParseNode,
|},
"genfrac": {|
type: "genfrac",
continued: boolean,
numer: ParseNode<*>,
denom: ParseNode<*>,
numer: AnyParseNode,
denom: AnyParseNode,
hasBarLine: boolean,
leftDelim: ?string,
rightDelim: ?string,
@@ -183,12 +249,12 @@ export type ParseNodeTypes = {
type: "horizBrace",
label: string,
isOver: boolean,
base: ParseNode<*>,
base: AnyParseNode,
|},
"href": {|
type: "href",
href: string,
body: ParseNode<*>[],
body: AnyParseNode[],
|},
"infix": {|
type: "infix",
@@ -202,7 +268,7 @@ export type ParseNodeTypes = {
"lap": {|
type: "lap",
alignment: string,
body: ParseNode<*>,
body: AnyParseNode,
|},
"leftright": LeftRightDelimType,
"leftright-right": {|
@@ -211,10 +277,10 @@ export type ParseNodeTypes = {
|},
"mathchoice": {|
type: "mathchoice",
display: ParseNode<*>[],
text: ParseNode<*>[],
script: ParseNode<*>[],
scriptscript: ParseNode<*>[],
display: AnyParseNode[],
text: AnyParseNode[],
script: AnyParseNode[],
scriptscript: AnyParseNode[],
|},
"middle": {|
type: "middle",
@@ -223,40 +289,40 @@ export type ParseNodeTypes = {
"mclass": {|
type: "mclass",
mclass: string,
value: ParseNode<*>[],
value: AnyParseNode[],
|},
"mod": {|
type: "mod",
modType: string,
value: ?ParseNode<*>[],
value: ?AnyParseNode[],
|},
"operatorname": {|
type: "operatorname",
value: ParseNode<*>[],
value: AnyParseNode[],
|},
"overline": {|
type: "overline",
body: ParseNode<*>,
body: AnyParseNode,
|},
"phantom": {|
type: "phantom",
value: ParseNode<*>[],
value: AnyParseNode[],
|},
"hphantom": {|
type: "hphantom",
body: ParseNode<*>,
value: ParseNode<*>[],
body: AnyParseNode,
value: AnyParseNode[],
|},
"vphantom": {|
type: "vphantom",
body: ParseNode<*>,
value: ParseNode<*>[],
body: AnyParseNode,
value: AnyParseNode[],
|},
"raisebox": {|
type: "raisebox",
dy: ParseNode<"size">,
body: ParseNode<*>,
value: ParseNode<*>[],
body: AnyParseNode,
value: AnyParseNode[],
|},
"rule": {|
type: "rule",
@@ -267,28 +333,28 @@ export type ParseNodeTypes = {
"sizing": {|
type: "sizing",
size: number,
value: ParseNode<*>[],
value: AnyParseNode[],
|},
"smash": {|
type: "smash",
body: ParseNode<*>,
body: AnyParseNode,
smashHeight: boolean,
smashDepth: boolean,
|},
"sqrt": {|
type: "sqrt",
body: ParseNode<*>,
index: ?ParseNode<*>,
body: AnyParseNode,
index: ?AnyParseNode,
|},
"underline": {|
type: "underline",
body: ParseNode<*>,
body: AnyParseNode,
|},
"xArrow": {|
type: "xArrow",
label: string,
body: ParseNode<*>,
below: ?ParseNode<*>,
body: AnyParseNode,
below: ?AnyParseNode,
|},
};
@@ -297,8 +363,7 @@ export type ParseNodeTypes = {
* typing. Throws if the node's type does not match.
*/
export function assertNodeType<NODETYPE: NodeType>(
// The union allows either ParseNode<*> or the union of two specific nodes.
node: ?ParseNode<*> | ParseNode<*>,
node: ?AnyParseNode,
type: NODETYPE,
): ParseNode<NODETYPE> {
const typedNode = checkNodeType(node, type);
@@ -315,11 +380,38 @@ export function assertNodeType<NODETYPE: NodeType>(
* returns null.
*/
export function checkNodeType<NODETYPE: NodeType>(
// The union allows either ParseNode<*> or the union of two specific nodes.
node: ?ParseNode<*> | ParseNode<*>,
node: ?AnyParseNode,
type: NODETYPE,
): ?ParseNode<NODETYPE> {
return node && node.type === type ?
(node: ParseNode<NODETYPE>) :
null;
if (node && node.type === type) {
// $FlowFixMe: Inference not sophisticated enough to figure this out.
return node;
}
return null;
}
/**
* Returns the node more strictly typed iff it is of the given type. Otherwise,
* returns null.
*/
export function assertSymbolNodeType(node: ?AnyParseNode): SymbolParseNode {
const typedNode = checkSymbolNodeType(node);
if (!typedNode) {
throw new Error(
`Expected node of symbol group type, but got ` +
(node ? `node of type ${node.type}` : String(node)));
}
return typedNode;
}
/**
* Returns the node more strictly typed iff it is of the given type. Otherwise,
* returns null.
*/
export function checkSymbolNodeType(node: ?AnyParseNode): ?SymbolParseNode {
if (node && GROUPS.hasOwnProperty(node.type)) {
// $FlowFixMe
return node;
}
return null;
}

View File

@@ -9,12 +9,12 @@ import {validUnit} from "./units";
import {supportedCodepoint} from "./unicodeScripts";
import unicodeAccents from "./unicodeAccents";
import unicodeSymbols from "./unicodeSymbols";
import ParseNode, {assertNodeType} from "./ParseNode";
import ParseNode, {assertNodeType, checkNodeType} from "./ParseNode";
import ParseError from "./ParseError";
import {combiningDiacriticalMarksEndRegex} from "./Lexer.js";
import Settings from "./Settings";
import {Token} from "./Token";
import type {AnyParseNode} from "./ParseNode";
import type {Mode, ArgType, BreakToken} from "./types";
import type {FunctionContext, FunctionSpec} from "./defineFunction";
import type {EnvSpec} from "./defineEnvironment";
@@ -60,12 +60,12 @@ type ParsedFunc = {|
|};
type ParsedArg = {|
type: "arg",
result: ParseNode<*>,
result: AnyParseNode,
token: Token,
|};
type ParsedFuncOrArg = ParsedFunc | ParsedArg;
function newArgument(result: ParseNode<*>, token: Token): ParsedArg {
function newArgument(result: AnyParseNode, token: Token): ParsedArg {
return {type: "arg", result, token};
}
@@ -127,7 +127,7 @@ export default class Parser {
/**
* Main parsing function, which parses an entire input.
*/
parse(): ParseNode<*>[] {
parse(): AnyParseNode[] {
// Create a group namespace for the math expression.
// (LaTeX creates a new group for every $...$, $$...$$, \[...\].)
this.gullet.beginGroup();
@@ -167,7 +167,7 @@ export default class Parser {
parseExpression(
breakOnInfix: boolean,
breakOnTokenText?: BreakToken,
): ParseNode<*>[] {
): AnyParseNode[] {
const body = [];
// Keep adding atoms to the body until we can't parse any more atoms (either
// we reached the end, a }, or a \right)
@@ -208,13 +208,13 @@ export default class Parser {
* There can only be one infix operator per group. If there's more than one
* then the expression is ambiguous. This can be resolved by adding {}.
*/
handleInfixNodes(body: ParseNode<*>[]): ParseNode<*>[] {
handleInfixNodes(body: AnyParseNode[]): AnyParseNode[] {
let overIndex = -1;
let funcName;
for (let i = 0; i < body.length; i++) {
const node = body[i];
if (node.type === "infix") {
const node = checkNodeType(body[i], "infix");
if (node) {
if (overIndex !== -1) {
throw new ParseError(
"only one infix operator per group",
@@ -259,7 +259,7 @@ export default class Parser {
*/
handleSupSubscript(
name: string, // For error reporting.
): ParseNode<*> {
): AnyParseNode {
const symbolToken = this.nextToken;
const symbol = symbolToken.text;
this.consume();
@@ -297,7 +297,7 @@ export default class Parser {
* Converts the textual input of an unsupported command into a text node
* contained within a color node whose color is determined by errorColor
*/
handleUnsupportedCmd(): ParseNode<*> {
handleUnsupportedCmd(): AnyParseNode {
const text = this.nextToken.text;
const textordArray = [];
@@ -329,7 +329,7 @@ export default class Parser {
/**
* Parses a group with optional super/subscripts.
*/
parseAtom(breakOnTokenText?: BreakToken): ?ParseNode<*> {
parseAtom(breakOnTokenText?: BreakToken): ?AnyParseNode {
// The body of an atom is an implicit group, so that things like
// \left(x\right)^2 work correctly.
const base = this.parseImplicitGroup(breakOnTokenText);
@@ -352,14 +352,15 @@ export default class Parser {
if (lex.text === "\\limits" || lex.text === "\\nolimits") {
// We got a limit control
if (!base || base.type !== "op") {
const opNode = checkNodeType(base, "op");
if (opNode) {
const limits = lex.text === "\\limits";
opNode.value.limits = limits;
opNode.value.alwaysHandleSupSub = true;
} else {
throw new ParseError(
"Limit controls must follow a math operator",
lex);
} else {
const limits = lex.text === "\\limits";
base.value.limits = limits;
base.value.alwaysHandleSupSub = true;
}
this.consume();
} else if (lex.text === "^") {
@@ -427,7 +428,7 @@ export default class Parser {
* implicit grouping after it until the end of the group. E.g.
* small text {\Large large text} small text again
*/
parseImplicitGroup(breakOnTokenText?: BreakToken): ?ParseNode<*> {
parseImplicitGroup(breakOnTokenText?: BreakToken): ?AnyParseNode {
const start = this.parseSymbol();
if (start == null) {
@@ -463,10 +464,12 @@ export default class Parser {
const result = env.handler(context, args, optArgs);
this.expect("\\end", false);
const endNameToken = this.nextToken;
const end = assertNodeType(this.parseFunction(), "environment");
let end = this.parseFunction();
if (!end) {
throw new ParseError("failed to parse function after \\end");
} else if (end.value.name !== envName) {
}
end = assertNodeType(end, "environment");
if (end.value.name !== envName) {
throw new ParseError(
"Mismatch: \\begin{" + envName + "} matched " +
"by \\end{" + end.value.name + "}",
@@ -483,7 +486,7 @@ export default class Parser {
* Parses an entire function, including its base and all of its arguments.
* It also handles the case where the parsed node is not a function.
*/
parseFunction(): ?ParseNode<*> {
parseFunction(): ?AnyParseNode {
const baseGroup = this.parseGroup();
return baseGroup ? this.parseGivenFunction(baseGroup) : null;
}
@@ -495,7 +498,7 @@ export default class Parser {
parseGivenFunction(
baseGroup: ParsedFuncOrArg,
breakOnTokenText?: BreakToken,
): ParseNode<*> {
): AnyParseNode {
if (baseGroup.type === "fn") {
const func = baseGroup.result;
const funcData = functions[func];
@@ -535,11 +538,11 @@ export default class Parser {
*/
callFunction(
name: string,
args: ParseNode<*>[],
optArgs: (?ParseNode<*>)[],
args: AnyParseNode[],
optArgs: (?AnyParseNode)[],
token?: Token,
breakOnTokenText?: BreakToken,
): ParseNode<*> {
): AnyParseNode {
const context: FunctionContext = {
funcName: name,
parser: this,
@@ -561,8 +564,8 @@ export default class Parser {
func: string, // Should look like "\name" or "\begin{name}".
funcData: FunctionSpec<*> | EnvSpec<*>,
): {
args: ParseNode<*>[],
optArgs: (?ParseNode<*>)[],
args: AnyParseNode[],
optArgs: (?AnyParseNode)[],
} {
const totalArgs = funcData.numArgs + funcData.numOptionalArgs;
if (totalArgs === 0) {
@@ -609,7 +612,7 @@ export default class Parser {
"Expected group after '" + func + "'", nextToken);
}
}
let argNode: ParseNode<*>;
let argNode: AnyParseNode;
if (arg.type === "fn") {
const argGreediness =
functions[arg.result].greediness;
@@ -895,7 +898,7 @@ export default class Parser {
* characters in its value. The representation is still ASCII source.
* The group will be modified in place.
*/
formLigatures(group: ParseNode<*>[]) {
formLigatures(group: AnyParseNode[]) {
let n = group.length - 1;
for (let i = 0; i < n; ++i) {
const a = group[i];

View File

@@ -7,13 +7,13 @@
import utils from "./utils";
import ParseError from "./ParseError.js";
import ParseNode from "./ParseNode";
import {Token} from "./Token";
import type {AnyParseNode} from "./ParseNode";
import type {MacroMap} from "./macros";
export type StrictFunction =
(errorCode: string, errorMsg: string, token?: Token | ParseNode<*>) =>
(errorCode: string, errorMsg: string, token?: Token | AnyParseNode) =>
?(boolean | string);
export type SettingsOptions = {
@@ -65,7 +65,7 @@ class Settings {
* Can safely not be called if `this.strict` is false in JavaScript.
*/
reportNonstrict(errorCode: string, errorMsg: string,
token?: Token | ParseNode<*>) {
token?: Token | AnyParseNode) {
let strict = this.strict;
if (typeof strict === "function") {
// Allow return value of strict function to be boolean or string
@@ -98,7 +98,7 @@ class Settings {
* This is for the second category of `errorCode`s listed in the README.
*/
useStrictBehavior(errorCode: string, errorMsg: string,
token?: Token | ParseNode<*>) {
token?: Token | AnyParseNode) {
let strict = this.strict;
if (typeof strict === "function") {
// Allow return value of strict function to be boolean or string

View File

@@ -6,7 +6,7 @@ import Options from "./Options";
import Settings from "./Settings";
import Style from "./Style";
import type ParseNode from "./ParseNode";
import type {AnyParseNode} from "./ParseNode";
import type {DomSpan} from "./domTree";
const optionsFromSettings = function(settings: Settings) {
@@ -17,7 +17,7 @@ const optionsFromSettings = function(settings: Settings) {
};
export const buildTree = function(
tree: ParseNode<*>[],
tree: AnyParseNode[],
expression: string,
settings: Settings,
): DomSpan {
@@ -39,7 +39,7 @@ export const buildTree = function(
};
export const buildHTMLTree = function(
tree: ParseNode<*>[],
tree: AnyParseNode[],
expression: string,
settings: Settings,
): DomSpan {

View File

@@ -1,14 +1,13 @@
// @flow
import {_htmlGroupBuilders, _mathmlGroupBuilders} from "./defineFunction";
import Options from "./Options";
import ParseNode from "./ParseNode";
import type Parser from "./Parser";
import type {AnyParseNode} from "./ParseNode";
import type {ArgType, Mode} from "./types";
import type {HtmlDomNode} from "./domTree";
import type {NodeType} from "./ParseNode";
import type {MathNode} from "./mathMLTree";
import type {HtmlBuilder, MathMLBuilder} from "./defineFunction";
/**
* The context contains the following properties:
@@ -29,8 +28,8 @@ type EnvContext = {|
*/
type EnvHandler<NODETYPE: NodeType> = (
context: EnvContext,
args: ParseNode<*>[],
optArgs: (?ParseNode<*>)[],
args: AnyParseNode[],
optArgs: (?AnyParseNode)[],
) => ParseNode<NODETYPE>;
/**
@@ -85,11 +84,11 @@ type EnvDefSpec<NODETYPE: NodeType> = {|
// This function returns an object representing the DOM structure to be
// created when rendering the defined LaTeX function.
htmlBuilder: (group: ParseNode<NODETYPE>, options: Options) => HtmlDomNode,
htmlBuilder: HtmlBuilder<NODETYPE>,
// This function returns an object representing the MathML structure to be
// created when rendering the defined LaTeX function.
mathmlBuilder: (group: ParseNode<NODETYPE>, options: Options) => MathNode,
mathmlBuilder: MathMLBuilder<NODETYPE>,
|};
export default function defineEnvironment<NODETYPE: NodeType>({

View File

@@ -3,7 +3,7 @@ import {checkNodeType} from "./ParseNode";
import domTree from "./domTree";
import type Parser from "./Parser";
import type ParseNode, {NodeType} from "./ParseNode";
import type ParseNode, {AnyParseNode, NodeType} from "./ParseNode";
import type Options from "./Options";
import type {ArgType, BreakToken, Mode} from "./types";
import type {HtmlDomNode} from "./domTree";
@@ -20,8 +20,8 @@ export type FunctionContext = {|
export type FunctionHandler<NODETYPE: NodeType> = (
context: FunctionContext,
args: ParseNode<*>[],
optArgs: (?ParseNode<*>)[],
args: AnyParseNode[],
optArgs: (?AnyParseNode)[],
) => ParseNode<NODETYPE>;
export type HtmlBuilder<NODETYPE> = (ParseNode<NODETYPE>, Options) => HtmlDomNode;
@@ -245,7 +245,7 @@ export function defineFunctionBuilders<NODETYPE: NodeType>({
// Since the corresponding buildHTML/buildMathML function expects a
// list of elements, we normalize for different kinds of arguments
export const ordargument = function(arg: ParseNode<*>): ParseNode<*>[] {
export const ordargument = function(arg: AnyParseNode): AnyParseNode[] {
const node = checkNodeType(arg, "ordgroup");
return node ? node.value : [arg];
};

View File

@@ -4,7 +4,8 @@ import defineEnvironment from "../defineEnvironment";
import mathMLTree from "../mathMLTree";
import ParseError from "../ParseError";
import ParseNode from "../ParseNode";
import {assertNodeType} from "../ParseNode";
import {assertNodeType, assertSymbolNodeType} from "../ParseNode";
import {checkNodeType, checkSymbolNodeType} from "../ParseNode";
import {calculateSize} from "../units";
import utils from "../utils";
@@ -12,7 +13,9 @@ import * as html from "../buildHTML";
import * as mml from "../buildMathML";
import type Parser from "../Parser";
import type {AnyParseNode} from "../ParseNode";
import type {StyleStr} from "../types";
import type {HtmlBuilder, MathMLBuilder} from "../defineFunction";
// Data stored in the ParseNode associated with the environment.
type AlignSpec = { type: "separator", separator: string } | {
@@ -28,8 +31,8 @@ export type ArrayEnvNodeData = {|
arraystretch: number,
addJot?: boolean,
cols?: AlignSpec[],
body: ParseNode<*>[][], // List of rows in the (2D) array.
rowGaps: (?ParseNode<*>)[],
body: AnyParseNode[][], // List of rows in the (2D) array.
rowGaps: (?ParseNode<"size">)[],
numHLinesBeforeRow: number[],
|};
// Same as above but with some fields not yet filled.
@@ -40,8 +43,8 @@ type ArrayEnvNodeDataIncomplete = {|
addJot?: boolean,
cols?: AlignSpec[],
// Before these fields are filled.
body?: ParseNode<*>[][],
rowGaps?: (?ParseNode<*>)[],
body?: AnyParseNode[][],
rowGaps?: (?ParseNode<"size">)[],
numHLinesBeforeRow?: number[],
|};
@@ -110,10 +113,8 @@ function parseArray(
} else if (next === "\\end") {
// Arrays terminate newlines with `\crcr` which consumes a `\cr` if
// the last line is empty.
const lastRow = body[body.length - 1];
if (body.length > 1
&& lastRow.length === 1
&& lastRow[0].value.value[0].value.length === 0) {
// NOTE: Currently, `cell` is the last item added into `row`.
if (row.length === 1 && cell.value.value[0].value.length === 0) {
body.pop();
}
break;
@@ -161,8 +162,7 @@ type Outrow = {
pos: number,
};
const htmlBuilder = function(group, options) {
const groupValue = assertNodeType(group, "array").value;
const htmlBuilder: HtmlBuilder<"array"> = function(group, options) {
let r;
let c;
const nr = group.value.body.length;
@@ -180,7 +180,7 @@ const htmlBuilder = function(group, options) {
// Default \jot from ltmath.dtx
// TODO(edemaine): allow overriding \jot via \setlength (#687)
const jot = 3 * pt;
const arrayskip = groupValue.arraystretch * baselineskip;
const arrayskip = group.value.arraystretch * baselineskip;
const arstrutHeight = 0.7 * arrayskip; // \strutbox in ltfsstrc.dtx and
const arstrutDepth = 0.3 * arrayskip; // \@arstrutbox in lttab.dtx
@@ -194,8 +194,8 @@ const htmlBuilder = function(group, options) {
hlinePos.push(totalHeight);
}
for (r = 0; r < groupValue.body.length; ++r) {
const inrow = groupValue.body[r];
for (r = 0; r < group.value.body.length; ++r) {
const inrow = group.value.body[r];
let height = arstrutHeight; // \@array adds an \@arstrut
let depth = arstrutDepth; // to each tow (via the template)
@@ -215,7 +215,7 @@ const htmlBuilder = function(group, options) {
outrow[c] = elt;
}
const rowGap = groupValue.rowGaps[r];
const rowGap = group.value.rowGaps[r];
let gap = 0;
if (rowGap) {
gap = calculateSize(rowGap.value.value, options);
@@ -230,7 +230,7 @@ const htmlBuilder = 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 (groupValue.addJot) {
if (group.value.addJot) {
depth += jot;
}
@@ -251,7 +251,7 @@ const htmlBuilder = function(group, options) {
}
const offset = totalHeight / 2 + options.fontMetrics().axisHeight;
const colDescriptions = groupValue.cols || [];
const colDescriptions = group.value.cols || [];
const cols = [];
let colSep;
let colDescrNum;
@@ -307,7 +307,7 @@ const htmlBuilder = function(group, options) {
}
let sepwidth;
if (c > 0 || groupValue.hskipBeforeAndAfter) {
if (c > 0 || group.value.hskipBeforeAndAfter) {
sepwidth = utils.deflt(colDescr.pregap, arraycolsep);
if (sepwidth !== 0) {
colSep = buildCommon.makeSpan(["arraycolsep"], []);
@@ -338,7 +338,7 @@ const htmlBuilder = function(group, options) {
[col]);
cols.push(col);
if (c < nc - 1 || groupValue.hskipBeforeAndAfter) {
if (c < nc - 1 || group.value.hskipBeforeAndAfter) {
sepwidth = utils.deflt(colDescr.postgap, arraycolsep);
if (sepwidth !== 0) {
colSep = buildCommon.makeSpan(["arraycolsep"], []);
@@ -366,10 +366,9 @@ const htmlBuilder = function(group, options) {
return buildCommon.makeSpan(["mord"], [body], options);
};
const mathmlBuilder = function(group, options) {
const groupValue = assertNodeType(group, "array").value;
const mathmlBuilder: MathMLBuilder<"array"> = function(group, options) {
return new mathMLTree.MathNode(
"mtable", groupValue.body.map(function(row) {
"mtable", group.value.body.map(function(row) {
return new mathMLTree.MathNode(
"mtr", row.map(function(cell) {
return new mathMLTree.MathNode(
@@ -400,10 +399,12 @@ const alignedHandler = function(context, args) {
let numMaths;
let numCols = 0;
const emptyGroup = new ParseNode("ordgroup", [], context.mode);
if (args[0] && args[0].value) {
const ordgroup = checkNodeType(args[0], "ordgroup");
if (ordgroup) {
let arg0 = "";
for (let i = 0; i < args[0].value.length; i++) {
arg0 += args[0].value[i].value;
for (let i = 0; i < ordgroup.value.length; i++) {
const textord = assertNodeType(ordgroup.value[i], "textord");
arg0 += textord.value;
}
numMaths = Number(arg0);
numCols = numMaths * 2;
@@ -412,7 +413,8 @@ const alignedHandler = function(context, args) {
res.value.body.forEach(function(row) {
for (let i = 1; i < row.length; i += 2) {
// Modify ordgroup node within styling node
const ordgroup = row[i].value.value[0];
const styling = assertNodeType(row[i], "styling");
const ordgroup = assertNodeType(styling.value.value[0], "ordgroup");
ordgroup.value.unshift(emptyGroup);
}
if (!isAligned) { // Case 1
@@ -459,10 +461,16 @@ defineEnvironment({
props: {
numArgs: 1,
},
handler: function(context, args) {
let colalign = args[0];
colalign = colalign.value.map ? colalign.value : [colalign];
const cols = colalign.map(function(node) {
handler(context, args) {
// Since no types are specified above, the two possibilities are
// - The argument is wrapped in {} or [], in which case Parser's
// parseGroup() returns an "ordgroup" wrapping some symbol node.
// - The argument is a bare symbol node.
const symNode = checkSymbolNodeType(args[0]);
const colalign: AnyParseNode[] =
symNode ? [args[0]] : assertNodeType(args[0], "ordgroup").value;
const cols = colalign.map(function(nde) {
const node = assertSymbolNodeType(nde);
const ca = node.value;
if ("lcr".indexOf(ca) !== -1) {
return {
@@ -480,9 +488,7 @@ defineEnvironment({
separator: ":",
};
}
throw new ParseError(
"Unknown column alignment: " + node.value,
node);
throw new ParseError("Unknown column alignment: " + ca, nde);
});
let res = {
type: "array",

View File

@@ -9,13 +9,14 @@ import ParseNode, {assertNodeType, checkNodeType} from "../ParseNode";
import * as html from "../buildHTML";
import * as mml from "../buildMathML";
import type {AnyParseNode} from "../ParseNode";
import type {HtmlBuilderSupSub, MathMLBuilder} from "../defineFunction";
// NOTE: Unlike most `htmlBuilder`s, this one handles not only "accent", but
// also "supsub" since an accent can affect super/subscripting.
export const htmlBuilder: HtmlBuilderSupSub<"accent"> = (grp, options) => {
// Accents are handled in the TeXbook pg. 443, rule 12.
let base: ParseNode<*>;
let base: AnyParseNode;
let group: ParseNode<"accent">;
const supSub = checkNodeType(grp, "supsub");

View File

@@ -2,7 +2,6 @@
import defineFunction, {ordargument} from "../defineFunction";
import buildCommon from "../buildCommon";
import mathMLTree from "../mathMLTree";
import ParseError from "../ParseError";
import ParseNode, {assertNodeType} from "../ParseNode";
import * as html from "../buildHTML";
@@ -99,10 +98,7 @@ defineFunction({
argTypes: ["color"],
},
handler({parser, breakOnTokenText}, args) {
const color = args[0];
if (!color) {
throw new ParseError("\\color not followed by color");
}
const color = assertNodeType(args[0], "color-token");
// If we see a styling function, parse out the implicit body
const body = parser.parseExpression(true, breakOnTokenText);

View File

@@ -5,11 +5,12 @@ import delimiter from "../delimiter";
import mathMLTree from "../mathMLTree";
import ParseError from "../ParseError";
import utils from "../utils";
import ParseNode, {assertNodeType} from "../ParseNode";
import ParseNode, {assertNodeType, checkSymbolNodeType} from "../ParseNode";
import * as html from "../buildHTML";
import * as mml from "../buildMathML";
import type {AnyParseNode, SymbolParseNode} from "../ParseNode";
import type {LeftRightDelimType} from "../ParseNode";
import type {FunctionContext} from "../defineFunction";
@@ -52,14 +53,15 @@ const delimiters = [
// Delimiter functions
function checkDelimiter(
delim: ParseNode<*>,
delim: AnyParseNode,
context: FunctionContext,
): ParseNode<*> {
if (utils.contains(delimiters, delim.value)) {
return delim;
): SymbolParseNode {
const symDelim = checkSymbolNodeType(delim);
if (symDelim && utils.contains(delimiters, symDelim.value)) {
return symDelim;
} else {
throw new ParseError(
"Invalid delimiter: '" + delim.value + "' after '" +
"Invalid delimiter: '" + String(delim.value) + "' after '" +
context.funcName + "'", delim);
}
}

View File

@@ -1,7 +1,7 @@
// @flow
import defineFunction from "../defineFunction";
import ParseError from "../ParseError";
import ParseNode from "../ParseNode";
import ParseNode, {assertNodeType} from "../ParseNode";
// Environment delimiters. HTML/MathML rendering is defined in the corresponding
// defineEnvironment definitions.
@@ -19,7 +19,7 @@ defineFunction({
}
let name = "";
for (let i = 0; i < nameGroup.value.length; ++i) {
name += nameGroup.value[i].value;
name += assertNodeType(nameGroup.value[i], "textord").value;
}
return new ParseNode("environment", {
type: "environment",

View File

@@ -19,7 +19,7 @@ defineFunction({
const height = assertNodeType(args[1], "size");
return new ParseNode("rule", {
type: "rule",
shift: shift && shift.value,
shift: shift && assertNodeType(shift, "size").value.value,
width: width.value.value,
height: height.value.value,
}, parser.mode);

View File

@@ -3,7 +3,7 @@
import defineFunction from "../defineFunction";
import buildCommon from "../buildCommon";
import mathMLTree from "../mathMLTree";
import ParseNode from "../ParseNode";
import ParseNode, {assertNodeType} from "../ParseNode";
import * as html from "../buildHTML";
import * as mml from "../buildMathML";
@@ -19,7 +19,7 @@ defineFunction({
handler: ({parser}, args, optArgs) => {
let smashHeight = false;
let smashDepth = false;
const tbArg = optArgs[0];
const tbArg = optArgs[0] && assertNodeType(optArgs[0], "ordgroup");
if (tbArg) {
// Optional [tb] argument is engaged.
// ref: amsmath: \renewcommand{\smash}[1][tb]{%

View File

@@ -5,6 +5,7 @@ import domTree from "../domTree";
import mathMLTree from "../mathMLTree";
import utils from "../utils";
import Style from "../Style";
import {checkNodeType} from "../ParseNode";
import * as html from "../buildHTML";
import * as mml from "../buildMathML";
@@ -184,13 +185,13 @@ defineFunctionBuilders({
let isBrace = false;
let isOver;
let isSup;
if (group.value.base) {
if (group.value.base.value.type === "horizBrace") {
isSup = (group.value.sup ? true : false);
if (isSup === group.value.base.value.isOver) {
const horizBrace = checkNodeType(group.value.base, "horizBrace");
if (horizBrace) {
isSup = !!group.value.sup;
if (isSup === horizBrace.value.isOver) {
isBrace = true;
isOver = group.value.base.value.isOver;
}
isOver = horizBrace.value.isOver;
}
}

View File

@@ -9,11 +9,12 @@ import ParseError from "./ParseError";
import ParseNode from "./ParseNode";
import type Settings from "./Settings";
import type {AnyParseNode} from "./ParseNode";
/**
* Parses an expression using a Parser, then returns the parsed result.
*/
const parseTree = function(toParse: string, settings: Settings): ParseNode<*>[] {
const parseTree = function(toParse: string, settings: Settings): AnyParseNode[] {
if (!(typeof toParse === 'string' || toParse instanceof String)) {
throw new TypeError('KaTeX can only parse string typed expression');
}

View File

@@ -11,7 +11,7 @@ import mathMLTree from "./mathMLTree";
import utils from "./utils";
import type Options from "./Options";
import type ParseNode from "./ParseNode";
import type ParseNode, {AnyParseNode} from "./ParseNode";
import type {DomSpan, SvgSpan} from "./domTree";
const stretchyCodePoint: {[string]: string} = {
@@ -159,7 +159,7 @@ const katexImagesData: {
"shortrightharpoonabovebar"], 1.75, 716],
};
const groupLength = function(arg: ParseNode<*>): number {
const groupLength = function(arg: AnyParseNode): number {
if (arg.type === "ordgroup") {
return arg.value.length;
} else {

View File

@@ -24,9 +24,20 @@ type Font = "main" | "ams"
// types for raw text tokens, and we want to avoid conflicts with higher-level
// `ParseNode` types. These `ParseNode`s are constructed within `Parser` by
// looking up the `symbols` map.
export type Group =
"accent-token" | "bin" | "close" | "inner" | "mathord" |
"op-token" | "open" | "punct" | "rel" | "spacing" | "textord";
export const GROUPS = { // Set of all the groups.
"accent-token": 1,
"bin": 1,
"close": 1,
"inner": 1,
"mathord": 1,
"op-token": 1,
"open": 1,
"punct": 1,
"rel": 1,
"spacing": 1,
"textord": 1,
};
export type Group = $Keys<typeof GROUPS>;
type CharInfoMap = {[string]: {font: Font, group: Group, replace: ?string}};
const symbols: {[Mode]: CharInfoMap} = {

View File

@@ -4,7 +4,7 @@
* files.
*/
import type ParseNode from "./ParseNode";
import type {AnyParseNode} from "./ParseNode";
/**
* Provide an `indexOf` function which works in IE8, but defers to native if
@@ -96,7 +96,7 @@ function clearNode(node: Node) {
* cases, this will just be the group itself, but when ordgroups and colors have
* a single element, we want to pull that out.
*/
const getBaseElem = function(group: ParseNode<*>): ParseNode<*> {
const getBaseElem = function(group: AnyParseNode): AnyParseNode {
if (group.type === "ordgroup") {
if (group.value.length === 1) {
return getBaseElem(group.value[0]);
@@ -121,7 +121,7 @@ const getBaseElem = function(group: ParseNode<*>): ParseNode<*> {
* with a single character in them. To decide if something is a character box,
* we find its innermost group, and see if it is a single character.
*/
const isCharacterBox = function(group: ParseNode<*>): boolean {
const isCharacterBox = function(group: AnyParseNode): boolean {
const baseElem = getBaseElem(group);
// These are all they types of groups which hold single characters