flow typings for parser.js (#1041)

flow typings for parser.js
This commit is contained in:
Kevin Barabash
2017-12-29 11:26:22 -07:00
committed by Ashish Myles
parent 63733f796a
commit 5cc795eaaa
6 changed files with 136 additions and 163 deletions

View File

@@ -1,5 +1,4 @@
// @flow // @flow
import {Token} from "./Token";
import SourceLocation from "./SourceLocation"; import SourceLocation from "./SourceLocation";
import type {Mode} from "./types"; import type {Mode} from "./types";
@@ -21,14 +20,14 @@ export default class ParseNode {
type: string, // type of node, like e.g. "ordgroup" type: string, // type of node, like e.g. "ordgroup"
value: mixed, // type-specific representation of the node value: mixed, // type-specific representation of the node
mode: Mode, // parse mode in action for this node, "math" or "text" mode: Mode, // parse mode in action for this node, "math" or "text"
firstToken?: Token, // first token of the input for this node, first?: {loc: ?SourceLocation}, // first token or node of the input for
// will omit position information if unset // this node, will omit position information if unset
lastToken?: Token, // last token of the input for this node, last?: {loc: ?SourceLocation}, // last token or node of the input for this
// will default to firstToken if unset // node, will default to firstToken if unset
) { ) {
this.type = type; this.type = type;
this.value = value; this.value = value;
this.mode = mode; this.mode = mode;
this.loc = SourceLocation.range(firstToken, lastToken); this.loc = SourceLocation.range(first, last);
} }
} }

View File

@@ -1,3 +1,4 @@
// @flow
/* eslint no-constant-condition:0 */ /* eslint no-constant-condition:0 */
/* eslint no-console:0 */ /* eslint no-console:0 */
import functions from "./functions"; import functions from "./functions";
@@ -12,6 +13,12 @@ import unicodeSymbols from "./unicodeSymbols";
import ParseNode from "./ParseNode"; import ParseNode from "./ParseNode";
import ParseError from "./ParseError"; import ParseError from "./ParseError";
import { combiningDiacriticalMarksEndRegex } from "./Lexer.js"; import { combiningDiacriticalMarksEndRegex } from "./Lexer.js";
import Settings from "./Settings";
import { Token } from "./Token";
import type { Mode, ArgType } from "./types";
import type { FunctionContext, FunctionSpec } from "./defineFunction" ;
import type { EnvSpec } from "./defineEnvironment";
/** /**
* This file contains the parser used to parse out a TeX expression from the * This file contains the parser used to parse out a TeX expression from the
@@ -47,8 +54,6 @@ import { combiningDiacriticalMarksEndRegex } from "./Lexer.js";
* standalone object which can be used as an argument to another function. * standalone object which can be used as an argument to another function.
*/ */
/* TODO: Uncomment when porting to flow.
type ParsedType = "fn" | "arg" | "$"
type ParsedFunc = {| type ParsedFunc = {|
type: "fn", type: "fn",
result: string, // Function name defined via defineFunction (e.g. "\\frac"). result: string, // Function name defined via defineFunction (e.g. "\\frac").
@@ -66,38 +71,21 @@ type ParsedDollar = {|
token: Token, token: Token,
|}; |};
type ParsedFuncOrArgOrDollar = ParsedFunc | ParsedArg | ParsedDollar; type ParsedFuncOrArgOrDollar = ParsedFunc | ParsedArg | ParsedDollar;
*/ type ParsedFuncOrArg = ParsedFunc | ParsedArg;
/** function newArgument(result: ParseNode, token: Token): ParsedArg {
* @param {ParseNode} result
* @param {Token} token
* @return {ParsedArg}
*/
function newArgument(result, token) {
return {type: "arg", result, token}; return {type: "arg", result, token};
} }
/** function newFunction(token: Token): ParsedFunc {
* @param {Token} token
* @return {ParsedFunc}
*/
function newFunction(token) {
return {type: "fn", result: token.text, token}; return {type: "fn", result: token.text, token};
} }
/** function newDollar(token: Token): ParsedDollar {
* @param {Token} token
* @return {ParsedDollar}
*/
function newDollar(token) {
return {type: "$", result: "$", token}; return {type: "$", result: "$", token};
} }
/** function assertFuncOrArg(parsed: ParsedFuncOrArgOrDollar): ParsedFuncOrArg {
* @param {ParsedFuncOrArgOrDollar} parsed
* @return {ParsedFuncOrArg}
*/
function assertFuncOrArg(parsed) {
if (parsed.type === "$") { if (parsed.type === "$") {
throw new ParseError("Unexpected $", parsed.token); throw new ParseError("Unexpected $", parsed.token);
} }
@@ -105,7 +93,13 @@ function assertFuncOrArg(parsed) {
} }
export default class Parser { export default class Parser {
constructor(input, settings) { mode: Mode;
gullet: MacroExpander;
settings: Settings;
leftrightDepth: number;
nextToken: Token;
constructor(input: string, settings: Settings) {
// Start in math mode // Start in math mode
this.mode = "math"; this.mode = "math";
// Create a new macro expander (gullet) and (indirectly via that) also a // Create a new macro expander (gullet) and (indirectly via that) also a
@@ -125,18 +119,15 @@ export default class Parser {
/** /**
* Checks a result to make sure it has the right type, and throws an * Checks a result to make sure it has the right type, and throws an
* appropriate error otherwise. * appropriate error otherwise.
*
* @param {boolean=} consume whether to consume the expected token,
* defaults to true
*/ */
expect(text, consume) { expect(text: string, consume?: boolean = true) {
if (this.nextToken.text !== text) { if (this.nextToken.text !== text) {
throw new ParseError( throw new ParseError(
"Expected '" + text + "', got '" + this.nextToken.text + "'", "Expected '" + text + "', got '" + this.nextToken.text + "'",
this.nextToken this.nextToken
); );
} }
if (consume !== false) { if (consume) {
this.consume(); this.consume();
} }
} }
@@ -152,17 +143,15 @@ export default class Parser {
/** /**
* Switches between "text" and "math" modes. * Switches between "text" and "math" modes.
*/ */
switchMode(newMode) { switchMode(newMode: Mode) {
this.mode = newMode; this.mode = newMode;
this.gullet.switchMode(newMode); this.gullet.switchMode(newMode);
} }
/** /**
* Main parsing function, which parses an entire input. * Main parsing function, which parses an entire input.
*
* @return {Array.<ParseNode>}
*/ */
parse() { parse(): ParseNode[] {
// Try to parse the input // Try to parse the input
this.consume(); this.consume();
const parse = this.parseInput(); const parse = this.parseInput();
@@ -172,7 +161,7 @@ export default class Parser {
/** /**
* Parses an entire input tree. * Parses an entire input tree.
*/ */
parseInput() { parseInput(): ParseNode[] {
// Parse an expression // Parse an expression
const expression = this.parseExpression(false); const expression = this.parseExpression(false);
// If we succeeded, make sure there's an EOF at the end // If we succeeded, make sure there's an EOF at the end
@@ -185,17 +174,18 @@ export default class Parser {
/** /**
* Parses an "expression", which is a list of atoms. * Parses an "expression", which is a list of atoms.
* *
* @param {boolean} breakOnInfix Should the parsing stop when we hit infix * `breakOnInfix`: Should the parsing stop when we hit infix nodes? This
* nodes? This happens when functions have higher precendence * happens when functions have higher precendence han infix
* than infix nodes in implicit parses. * nodes in implicit parses.
* *
* @param {?string} breakOnTokenText The text of the token that the expression * `breakOnTokenText`: The text of the token that the expression should end
* should end with, or `null` if something else should end the * with, or `null` if something else should end the
* expression. * expression.
*
* @return {Array<ParseNode>}
*/ */
parseExpression(breakOnInfix, breakOnTokenText) { parseExpression(
breakOnInfix: boolean,
breakOnTokenText?: "]" | "}" | "$",
): ParseNode[] {
const body = []; const body = [];
// Keep adding atoms to the body until we can't parse any more atoms (either // Keep adding atoms to the body until we can't parse any more atoms (either
// we reached the end, a }, or a \right) // we reached the end, a }, or a \right)
@@ -235,11 +225,8 @@ export default class Parser {
* *
* There can only be one infix operator per group. If there's more than one * 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 {}. * then the expression is ambiguous. This can be resolved by adding {}.
*
* @param {Array<ParseNode>} body
* @return {Array<ParseNode>}
*/ */
handleInfixNodes(body) { handleInfixNodes(body: ParseNode[]): ParseNode[] {
let overIndex = -1; let overIndex = -1;
let funcName; let funcName;
@@ -256,7 +243,7 @@ export default class Parser {
} }
} }
if (overIndex !== -1) { if (overIndex !== -1 && funcName) {
let numerNode; let numerNode;
let denomNode; let denomNode;
@@ -287,10 +274,10 @@ export default class Parser {
/** /**
* Handle a subscript or superscript with nice errors. * Handle a subscript or superscript with nice errors.
* @param {string} name For error reporting.
* @return {ParsedNode}
*/ */
handleSupSubscript(name) { handleSupSubscript(
name: string, // For error reporting.
): ParseNode {
const symbolToken = this.nextToken; const symbolToken = this.nextToken;
const symbol = symbolToken.text; const symbol = symbolToken.text;
this.consume(); this.consume();
@@ -312,16 +299,16 @@ export default class Parser {
if (arg.type === "fn") { if (arg.type === "fn") {
// ^ and _ have a greediness, so handle interactions with functions' // ^ and _ have a greediness, so handle interactions with functions'
// greediness // greediness
const funcGreediness = functions[group.result].greediness; const funcGreediness = functions[arg.result].greediness;
if (funcGreediness > Parser.SUPSUB_GREEDINESS) { if (funcGreediness > Parser.SUPSUB_GREEDINESS) {
return this.parseGivenFunction(group); return this.parseGivenFunction(group);
} else { } else {
throw new ParseError( throw new ParseError(
"Got function '" + group.result + "' with no arguments " + "Got function '" + arg.result + "' with no arguments " +
"as " + name, symbolToken); "as " + name, symbolToken);
} }
} else { } else {
return group.result; return arg.result;
} }
} }
@@ -329,7 +316,7 @@ export default class Parser {
* Converts the textual input of an unsupported command into a text node * Converts the textual input of an unsupported command into a text node
* contained within a color node whose color is determined by errorColor * contained within a color node whose color is determined by errorColor
*/ */
handleUnsupportedCmd() { handleUnsupportedCmd(): ParseNode {
const text = this.nextToken.text; const text = this.nextToken.text;
const textordArray = []; const textordArray = [];
@@ -360,11 +347,8 @@ export default class Parser {
/** /**
* Parses a group with optional super/subscripts. * Parses a group with optional super/subscripts.
*
* @param {"]" | "}"} breakOnTokenText - character to stop parsing the group on.
* @return {?ParseNode}
*/ */
parseAtom(breakOnTokenText) { parseAtom(breakOnTokenText?: "]" | "}" | "$"): ?ParseNode {
// The body of an atom is an implicit group, so that things like // The body of an atom is an implicit group, so that things like
// \left(x\right)^2 work correctly. // \left(x\right)^2 work correctly.
const base = this.parseImplicitGroup(breakOnTokenText); const base = this.parseImplicitGroup(breakOnTokenText);
@@ -481,21 +465,39 @@ export default class Parser {
* implicit grouping after it until the end of the group. E.g. * implicit grouping after it until the end of the group. E.g.
* small text {\Large large text} small text again * small text {\Large large text} small text again
* It is also used for \left and \right to get the correct grouping. * It is also used for \left and \right to get the correct grouping.
*
* @param {"]" | "}"} breakOnTokenText - character to stop parsing the group on.
* @return {?ParseNode}
*/ */
parseImplicitGroup(breakOnTokenText) { parseImplicitGroup(breakOnTokenText?: "]" | "}" | "$"): ?ParseNode {
const start = this.parseSymbol(); const start = this.parseSymbol();
if (start == null) { if (start == null) {
// If we didn't get anything we handle, fall back to parseFunction // If we didn't get anything we handle, fall back to parseFunction
return this.parseFunction(); return this.parseFunction();
} else if (start.type === "arg") {
// Defer to parseGivenFunction if it's not a function we handle
return this.parseGivenFunction(start);
} }
const func = start.result; const func = start.result;
if (func === "\\left") { if (func === "$") {
if (this.mode === "math") {
throw new ParseError("$ within math mode");
}
const outerMode = this.mode;
this.switchMode("math");
// Expand next symbol now that we're in math mode.
this.consume();
const body = this.parseExpression(false, "$");
// We can't expand the next symbol after the $ until after
// switching modes back. So don't consume within expect.
this.expect("$", false);
this.switchMode(outerMode);
this.consume();
return new ParseNode("styling", {
style: "text",
value: body,
}, "math");
} else if (func === "\\left") {
// If we see a left: // If we see a left:
// Parse the entire left function (including the delimiter) // Parse the entire left function (including the delimiter)
const left = this.parseGivenFunction(start); const left = this.parseGivenFunction(start);
@@ -506,6 +508,9 @@ export default class Parser {
// Check the next token // Check the next token
this.expect("\\right", false); this.expect("\\right", false);
const right = this.parseFunction(); const right = this.parseFunction();
if (!right) {
throw new ParseError('failed to parse function after \\right');
}
return new ParseNode("leftright", { return new ParseNode("leftright", {
body: body, body: body,
left: left.value.value, left: left.value.value,
@@ -515,13 +520,13 @@ export default class Parser {
// begin...end is similar to left...right // begin...end is similar to left...right
const begin = this.parseGivenFunction(start); const begin = this.parseGivenFunction(start);
const envName = begin.value.name; const envName = begin.value.name;
if (!environments.has(envName)) { if (!environments.hasOwnProperty(envName)) {
throw new ParseError( throw new ParseError(
"No such environment: " + envName, begin.value.nameGroup); "No such environment: " + envName, begin.value.nameGroup);
} }
// Build the environment object. Arguments and other information will // Build the environment object. Arguments and other information will
// be made available to the begin and end methods using properties. // be made available to the begin and end methods using properties.
const env = environments.get(envName); const env = environments[envName];
const {args, optArgs} = const {args, optArgs} =
this.parseArguments("\\begin{" + envName + "}", env); this.parseArguments("\\begin{" + envName + "}", env);
const context = { const context = {
@@ -533,13 +538,14 @@ export default class Parser {
this.expect("\\end", false); this.expect("\\end", false);
const endNameToken = this.nextToken; const endNameToken = this.nextToken;
const end = this.parseFunction(); const end = this.parseFunction();
if (end.value.name !== envName) { if (!end) {
throw new ParseError("failed to parse function after \\end");
} else if (end.value.name !== envName) {
throw new ParseError( throw new ParseError(
"Mismatch: \\begin{" + envName + "} matched " + "Mismatch: \\begin{" + envName + "} matched " +
"by \\end{" + end.value.name + "}", "by \\end{" + end.value.name + "}",
endNameToken); endNameToken);
} }
result.position = end.position;
return result; return result;
} else if (utils.contains(Parser.sizeFuncs, func)) { } else if (utils.contains(Parser.sizeFuncs, func)) {
// If we see a sizing function, parse out the implicit body // If we see a sizing function, parse out the implicit body
@@ -588,24 +594,6 @@ export default class Parser {
color: color.result.value, color: color.result.value,
value: body, value: body,
}, this.mode); }, this.mode);
} else if (func === "$") {
if (this.mode === "math") {
throw new ParseError("$ within math mode");
}
const outerMode = this.mode;
this.switchMode("math");
// Expand next symbol now that we're in math mode.
this.consume();
const body = this.parseExpression(false, "$");
// We can't expand the next symbol after the $ until after
// switching modes back. So don't consume within expect.
this.expect("$", false);
this.switchMode(outerMode);
this.consume();
return new ParseNode("styling", {
style: "text",
value: body,
}, "math");
} else { } else {
// Defer to parseGivenFunction if it's not a function we handle // Defer to parseGivenFunction if it's not a function we handle
return this.parseGivenFunction(start); return this.parseGivenFunction(start);
@@ -615,10 +603,8 @@ export default class Parser {
/** /**
* Parses an entire function, including its base and all of its arguments. * 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. * It also handles the case where the parsed node is not a function.
*
* @return {?ParseNode}
*/ */
parseFunction() { parseFunction(): ?ParseNode {
const baseGroup = this.parseGroup(); const baseGroup = this.parseGroup();
return baseGroup ? this.parseGivenFunction(baseGroup) : null; return baseGroup ? this.parseGivenFunction(baseGroup) : null;
} }
@@ -626,11 +612,8 @@ export default class Parser {
/** /**
* Same as parseFunction(), except that the base is provided, guaranteeing a * Same as parseFunction(), except that the base is provided, guaranteeing a
* non-nullable result. * non-nullable result.
*
* @param {ParsedFuncOrArgOrDollar} baseGroup
* @return {ParseNode}
*/ */
parseGivenFunction(baseGroup) { parseGivenFunction(baseGroup: ParsedFuncOrArgOrDollar): ParseNode {
baseGroup = assertFuncOrArg(baseGroup); baseGroup = assertFuncOrArg(baseGroup);
if (baseGroup.type === "fn") { if (baseGroup.type === "fn") {
const func = baseGroup.result; const func = baseGroup.result;
@@ -657,34 +640,36 @@ export default class Parser {
/** /**
* Call a function handler with a suitable context and arguments. * Call a function handler with a suitable context and arguments.
* @param {string} name
* @param {Array<ParseNode>} args
* @param {Array<?ParseNode>} optArgs
* @param {Token=} token
*/ */
callFunction(name, args, optArgs, token) { callFunction(
const context = { name: string,
args: ParseNode[],
optArgs: (?ParseNode)[],
token?: Token,
): * {
const context: FunctionContext = {
funcName: name, funcName: name,
parser: this, parser: this,
token, token,
}; };
return functions[name].handler(context, args, optArgs); const func = functions[name];
if (func && func.handler) {
return func.handler(context, args, optArgs);
} else {
throw new ParseError(`No function handler for ${name}`);
}
} }
/** /**
* Parses the arguments of a function or environment * Parses the arguments of a function or environment
*
* @param {string} func "\name" or "\begin{name}"
* @param {{
* numArgs: number,
* numOptionalArgs: (number|undefined),
* }} funcData
* @return {{
* args: Array<ParseNode>,
* optArgs: Array<?ParseNode>,
* }}
*/ */
parseArguments(func, funcData) { parseArguments(
func: string, // Should look like "\name" or "\begin{name}".
funcData: FunctionSpec | EnvSpec,
): {
args: ParseNode[],
optArgs: (?ParseNode)[],
} {
const totalArgs = funcData.numArgs + funcData.numOptionalArgs; const totalArgs = funcData.numArgs + funcData.numOptionalArgs;
if (totalArgs === 0) { if (totalArgs === 0) {
return {args: [], optArgs: []}; return {args: [], optArgs: []};
@@ -730,7 +715,7 @@ export default class Parser {
"Expected group after '" + func + "'", nextToken); "Expected group after '" + func + "'", nextToken);
} }
} }
let argNode; let argNode: ParseNode;
arg = assertFuncOrArg(arg); arg = assertFuncOrArg(arg);
if (arg.type === "fn") { if (arg.type === "fn") {
const argGreediness = const argGreediness =
@@ -753,10 +738,11 @@ export default class Parser {
/** /**
* Parses a group when the mode is changing. * Parses a group when the mode is changing.
*
* @return {?ParsedFuncOrArgOrDollar}
*/ */
parseGroupOfType(type, optional) { parseGroupOfType(
type: ArgType, // Used to describe the mode in error messages.
optional: boolean,
): ?ParsedFuncOrArgOrDollar {
// Handle `original` argTypes // Handle `original` argTypes
if (type === "original") { if (type === "original") {
type = this.mode; type = this.mode;
@@ -786,12 +772,11 @@ export default class Parser {
/** /**
* Parses a group, essentially returning the string formed by the * Parses a group, essentially returning the string formed by the
* brace-enclosed tokens plus some position information. * brace-enclosed tokens plus some position information.
*
* @param {string} modeName Used to describe the mode in error messages
* @param {boolean=} optional Whether the group is optional or required
* @return {?Token}
*/ */
parseStringGroup(modeName, optional) { parseStringGroup(
modeName: ArgType, // Used to describe the mode in error messages.
optional: boolean,
): ?Token {
if (optional && this.nextToken.text !== "[") { if (optional && this.nextToken.text !== "[") {
return null; return null;
} }
@@ -820,12 +805,11 @@ export default class Parser {
* Parses a group, essentially returning the string formed by the * Parses a group, essentially returning the string formed by the
* brace-enclosed tokens plus some position information, possibly * brace-enclosed tokens plus some position information, possibly
* with nested braces. * with nested braces.
*
* @param {string} modeName Used to describe the mode in error messages
* @param {boolean=} optional Whether the group is optional or required
* @return {?Token}
*/ */
parseStringGroupWithBalancedBraces(modeName, optional) { parseStringGroupWithBalancedBraces(
modeName: ArgType, // Used to describe the mode in error messages.
optional: boolean,
): ?Token {
if (optional && this.nextToken.text !== "[") { if (optional && this.nextToken.text !== "[") {
return null; return null;
} }
@@ -866,12 +850,11 @@ export default class Parser {
* Parses a regex-delimited group: the largest sequence of tokens * Parses a regex-delimited group: the largest sequence of tokens
* whose concatenated strings match `regex`. Returns the string * whose concatenated strings match `regex`. Returns the string
* formed by the tokens plus some position information. * formed by the tokens plus some position information.
*
* @param {RegExp} regex
* @param {string} modeName Used to describe the mode in error messages
* @return {Token}
*/ */
parseRegexGroup(regex, modeName) { parseRegexGroup(
regex: RegExp,
modeName: string, // Used to describe the mode in error messages.
): Token {
const outerMode = this.mode; const outerMode = this.mode;
this.mode = "text"; this.mode = "text";
const firstToken = this.nextToken; const firstToken = this.nextToken;
@@ -895,7 +878,7 @@ export default class Parser {
/** /**
* Parses a color description. * Parses a color description.
*/ */
parseColorGroup(optional) { parseColorGroup(optional: boolean): ?ParsedArg {
const res = this.parseStringGroup("color", optional); const res = this.parseStringGroup("color", optional);
if (!res) { if (!res) {
return null; return null;
@@ -910,7 +893,7 @@ export default class Parser {
/** /**
* Parses a url string. * Parses a url string.
*/ */
parseUrlGroup(optional) { parseUrlGroup(optional: boolean): ?ParsedArg {
const res = this.parseStringGroupWithBalancedBraces("url", optional); const res = this.parseStringGroupWithBalancedBraces("url", optional);
if (!res) { if (!res) {
return null; return null;
@@ -927,7 +910,7 @@ export default class Parser {
/** /**
* Parses a size specification, consisting of magnitude and unit. * Parses a size specification, consisting of magnitude and unit.
*/ */
parseSizeGroup(optional) { parseSizeGroup(optional: boolean): ?ParsedArg {
let res; let res;
if (!optional && this.nextToken.text !== "{") { if (!optional && this.nextToken.text !== "{") {
res = this.parseRegexGroup( res = this.parseRegexGroup(
@@ -961,11 +944,8 @@ export default class Parser {
* bracket-enclosed group. * bracket-enclosed group.
* If `mode` is present, switches to that mode while parsing the group, * If `mode` is present, switches to that mode while parsing the group,
* and switches back after. * and switches back after.
*
* @param {boolean=} optional Whether the group is optional or required
* @return {?ParsedFuncOrArgOrDollar}
*/ */
parseGroup(optional, mode) { parseGroup(optional?: boolean, mode?: Mode): ?ParsedFuncOrArgOrDollar {
const outerMode = this.mode; const outerMode = this.mode;
const firstToken = this.nextToken; const firstToken = this.nextToken;
// Try to parse an open brace // Try to parse an open brace
@@ -1010,11 +990,9 @@ export default class Parser {
* The result will simply replace multiple textord nodes with a single * The result will simply replace multiple textord nodes with a single
* character in each value by a single textord node having multiple * character in each value by a single textord node having multiple
* characters in its value. The representation is still ASCII source. * characters in its value. The representation is still ASCII source.
* * The group will be modified in place.
* @param {Array.<ParseNode>} group the nodes of this group,
* list will be moified in place
*/ */
formLigatures(group) { formLigatures(group: ParseNode[]) {
let n = group.length - 1; let n = group.length - 1;
for (let i = 0; i < n; ++i) { for (let i = 0; i < n; ++i) {
const a = group[i]; const a = group[i];
@@ -1041,10 +1019,8 @@ export default class Parser {
/** /**
* Parse a single symbol out of the string. Here, we handle both the functions * Parse a single symbol out of the string. Here, we handle both the functions
* we have defined, as well as the single character symbols * we have defined, as well as the single character symbols
*
* @return {?ParsedFuncOrArgOrDollar}
*/ */
parseSymbol() { parseSymbol(): ?ParsedFuncOrArgOrDollar {
const nucleus = this.nextToken; const nucleus = this.nextToken;
let text = nucleus.text; let text = nucleus.text;

View File

@@ -6,11 +6,13 @@
import utils from "./utils"; import utils from "./utils";
import type { MacroMap } from "./macros";
export type SettingsOptions = { export type SettingsOptions = {
displayMode?: boolean; displayMode?: boolean;
throwOnError?: boolean; throwOnError?: boolean;
errorColor?: string; errorColor?: string;
macros?: {[macroName: string]: string}; macros?: MacroMap;
colorIsTextColor?: boolean; colorIsTextColor?: boolean;
maxSize?: number; maxSize?: number;
}; };
@@ -29,7 +31,7 @@ class Settings {
displayMode: boolean; displayMode: boolean;
throwOnError: boolean; throwOnError: boolean;
errorColor: string; errorColor: string;
macros: {[macroName: string]: string}; macros: MacroMap;
colorIsTextColor: boolean; colorIsTextColor: boolean;
maxSize: number; maxSize: number;

View File

@@ -49,7 +49,7 @@ type EnvProps = {
* 2. requires all arguments except argType * 2. requires all arguments except argType
* It is generated by `defineEnvironment()` below. * It is generated by `defineEnvironment()` below.
*/ */
type EnvSpec = {| export type EnvSpec = {|
numArgs: number, numArgs: number,
argTypes?: ArgType[], argTypes?: ArgType[],
greediness: number, greediness: number,

View File

@@ -1,14 +1,7 @@
// @flow // @flow
import {_environments} from "./defineEnvironment"; import {_environments} from "./defineEnvironment";
const environments = { const environments = _environments;
has(envName: string) {
return _environments.hasOwnProperty(envName);
},
get(envName: string) {
return _environments[envName];
},
};
export default environments; export default environments;

View File

@@ -48,7 +48,7 @@ function parseArray(
const body = [row]; const body = [row];
const rowGaps = []; const rowGaps = [];
while (true) { // eslint-disable-line no-constant-condition while (true) { // eslint-disable-line no-constant-condition
let cell = parser.parseExpression(false, null); let cell = parser.parseExpression(false, undefined);
cell = new ParseNode("ordgroup", cell, parser.mode); cell = new ParseNode("ordgroup", cell, parser.mode);
if (style) { if (style) {
cell = new ParseNode("styling", { cell = new ParseNode("styling", {
@@ -72,6 +72,9 @@ function parseArray(
break; break;
} else if (next === "\\\\" || next === "\\cr") { } else if (next === "\\\\" || next === "\\cr") {
const cr = parser.parseFunction(); const cr = parser.parseFunction();
if (!cr) {
throw new ParseError(`Failed to parse function after ${next}`);
}
rowGaps.push(cr.value.size); rowGaps.push(cr.value.size);
row = []; row = [];
body.push(row); body.push(row);