mirror of
https://github.com/Smaug123/KaTeX
synced 2025-10-05 03:08:40 +00:00
committed by
Ashish Myles
parent
63733f796a
commit
5cc795eaaa
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
266
src/Parser.js
266
src/Parser.js
@@ -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;
|
||||||
|
|
||||||
|
@@ -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;
|
||||||
|
|
||||||
|
@@ -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,
|
||||||
|
@@ -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;
|
||||||
|
|
||||||
|
@@ -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);
|
||||||
|
Reference in New Issue
Block a user