refactor: separate defineMacro.js from macros.js (#3198)

Allows defineMacro to be called from other places, in particular
removing cycles from the dependency graph.
This commit is contained in:
Erik Demaine
2021-08-27 16:15:22 -04:00
committed by GitHub
parent 610feae647
commit 877e725620
7 changed files with 130 additions and 119 deletions

View File

@@ -15,7 +15,7 @@ You can provide an object of options as the last argument to [`katex.render` and
- `fleqn`: `boolean`. If `true`, display math renders flush left with a `2em` left margin, like `\documentclass[fleqn]` in LaTeX with the `amsmath` package.
- `throwOnError`: `boolean`. If `true` (the default), KaTeX will throw a `ParseError` when it encounters an unsupported command or invalid LaTeX. If `false`, KaTeX will render unsupported commands as text, and render invalid LaTeX as its source code with hover text giving the error, in the color given by `errorColor`.
- `errorColor`: `string`. A color string given in the format `"#XXX"` or `"#XXXXXX"`. This option determines the color that unsupported commands and invalid LaTeX are rendered in when `throwOnError` is set to `false`. (default: `#cc0000`)
- `macros`: `object`. A collection of custom macros. Each macro is a property with a name like `\name` (written `"\\name"` in JavaScript) which maps to a string that describes the expansion of the macro, or a function that accepts an instance of `MacroExpander` as first argument and returns the expansion as a string. `MacroExpander` is an internal API and subject to non-backwards compatible changes. See [`src/macros.js`](https://github.com/KaTeX/KaTeX/blob/master/src/macros.js) for its usage. Single-character keys can also be included in which case the character will be redefined as the given macro (similar to TeX active characters). *This object will be modified* if the LaTeX code defines its own macros via `\gdef`, which enables consecutive calls to KaTeX to share state.
- `macros`: `object`. A collection of custom macros. Each macro is a property with a name like `\name` (written `"\\name"` in JavaScript) which maps to a string that describes the expansion of the macro, or a function that accepts an instance of `MacroExpander` as first argument and returns the expansion as a string. `MacroExpander` is an internal API and subject to non-backwards compatible changes. See [`src/defineMacro.js`](https://github.com/KaTeX/KaTeX/blob/master/src/defineMacro.js) for its usage. Single-character keys can also be included in which case the character will be redefined as the given macro (similar to TeX active characters). *This object will be modified* if the LaTeX code defines its own macros via `\gdef`, which enables consecutive calls to KaTeX to share state.
- `minRuleThickness`: `number`. Specifies a minimum thickness, in ems, for fraction lines, `\sqrt` top lines, `{array}` vertical lines, `\hline`, `\hdashline`, `\underline`, `\overline`, and the borders of `\fbox`, `\boxed`, and `\fcolorbox`. The usual value for these items is `0.04`, so for `minRuleThickness` to be effective it should probably take a value slightly above `0.04`, say `0.05` or `0.06`. Negative values will be ignored.
- `colorIsTextColor`: `boolean`. In early versions of both KaTeX (<0.8.0) and MathJax, the `\color` function expected the content to be a function argument, as in `\color{blue}{hello}`. In current KaTeX, `\color` is a switch, as in `\color{blue} hello`. This matches LaTeX behavior. If you want the old `\color` behavior, set option `colorIsTextColor` to true.
- `maxSize`: `number`. All user-specified sizes, e.g. in `\rule{500em}{500em}`, will be capped to `maxSize` ems. If set to `Infinity` (the default), users can make elements and spaces arbitrarily large.

View File

@@ -28,7 +28,7 @@ import type {AnyParseNode} from "./src/parseNode";
import type {DomSpan} from "./src/domTree";
import {defineSymbol} from './src/symbols';
import {defineMacro} from './src/macros';
import defineMacro from './src/defineMacro';
import {setFontMetrics} from './src/fontMetrics';
declare var __VERSION__: string;

View File

@@ -11,10 +11,10 @@ import {Token} from "./Token";
import type {Mode} from "./types";
import ParseError from "./ParseError";
import Namespace from "./Namespace";
import builtinMacros from "./macros";
import macros from "./macros";
import type {MacroContextInterface, MacroDefinition, MacroExpansion, MacroArg}
from "./macros";
from "./defineMacro";
import type Settings from "./Settings";
// List of commands that act like macros but aren't defined as a macro,
@@ -40,7 +40,7 @@ export default class MacroExpander implements MacroContextInterface {
this.expansionCount = 0;
this.feed(input);
// Make new global namespace
this.macros = new Namespace(builtinMacros, settings.macros);
this.macros = new Namespace(macros, settings.macros);
this.mode = mode;
this.stack = []; // contains tokens in REVERSE order
}

View File

@@ -10,7 +10,7 @@ import ParseError from "./ParseError";
import {Token} from "./Token";
import type {AnyParseNode} from "./parseNode";
import type {MacroMap} from "./macros";
import type {MacroMap} from "./defineMacro";
export type StrictFunction =
(errorCode: string, errorMsg: string, token?: Token | AnyParseNode) =>

118
src/defineMacro.js Normal file
View File

@@ -0,0 +1,118 @@
// @flow
import {Token} from "./Token";
import type Namespace from "./Namespace";
import type {Mode} from "./types";
/**
* Provides context to macros defined by functions. Implemented by
* MacroExpander.
*/
export interface MacroContextInterface {
mode: Mode;
/**
* Object mapping macros to their expansions.
*/
macros: Namespace<MacroDefinition>;
/**
* Returns the topmost token on the stack, without expanding it.
* Similar in behavior to TeX's `\futurelet`.
*/
future(): Token;
/**
* Remove and return the next unexpanded token.
*/
popToken(): Token;
/**
* Consume all following space tokens, without expansion.
*/
consumeSpaces(): void;
/**
* Expand the next token only once if possible.
*/
expandOnce(expandableOnly?: boolean): Token | Token[];
/**
* Expand the next token only once (if possible), and return the resulting
* top token on the stack (without removing anything from the stack).
* Similar in behavior to TeX's `\expandafter\futurelet`.
*/
expandAfterFuture(): Token;
/**
* Recursively expand first token, then return first non-expandable token.
*/
expandNextToken(): Token;
/**
* Fully expand the given macro name and return the resulting list of
* tokens, or return `undefined` if no such macro is defined.
*/
expandMacro(name: string): Token[] | void;
/**
* Fully expand the given macro name and return the result as a string,
* or return `undefined` if no such macro is defined.
*/
expandMacroAsText(name: string): string | void;
/**
* Consume an argument from the token stream, and return the resulting array
* of tokens and start/end token.
*/
consumeArg(delims?: ?string[]): MacroArg;
/**
* Consume the specified number of arguments from the token stream,
* and return the resulting array of arguments.
*/
consumeArgs(numArgs: number): Token[][];
/**
* Determine whether a command is currently "defined" (has some
* functionality), meaning that it's a macro (in the current group),
* a function, a symbol, or one of the special commands listed in
* `implicitCommands`.
*/
isDefined(name: string): boolean;
/**
* Determine whether a command is expandable.
*/
isExpandable(name: string): boolean;
}
export type MacroArg = {
tokens: Token[],
start: Token,
end: Token
};
/** Macro tokens (in reverse order). */
export type MacroExpansion = {
tokens: Token[],
numArgs: number,
delimiters?: string[][],
unexpandable?: boolean, // used in \let
};
export type MacroDefinition = string | MacroExpansion |
(MacroContextInterface => (string | MacroExpansion));
export type MacroMap = {[string]: MacroDefinition};
/**
* All registered global/built-in macros.
* `macros.js` exports this same dictionary again and makes it public.
* `Parser.js` requires this dictionary via `macros.js`.
*/
export const _macros: MacroMap = {};
// This function might one day accept an additional argument and do more things.
export default function defineMacro(name: string, body: MacroDefinition) {
_macros[name] = body;
}

View File

@@ -1,6 +1,6 @@
// @flow
import defineFunction, {ordargument} from "../defineFunction";
import {defineMacro} from "../macros";
import defineMacro from "../defineMacro";
import buildCommon from "../buildCommon";
import mathMLTree from "../mathMLTree";
import {SymbolNode} from "../domTree";

View File

@@ -4,124 +4,17 @@
* This can be used to define some commands in terms of others.
*/
// Export global macros object from defineMacro
import defineMacro, {_macros} from "./defineMacro";
const macros = _macros;
export default macros;
import fontMetricsData from "./fontMetricsData";
import functions from "./functions";
import symbols from "./symbols";
import utils from "./utils";
import {Token} from "./Token";
import ParseError from "./ParseError";
import type Namespace from "./Namespace";
import type {Mode} from "./types";
/**
* Provides context to macros defined by functions. Implemented by
* MacroExpander.
*/
export interface MacroContextInterface {
mode: Mode;
/**
* Object mapping macros to their expansions.
*/
macros: Namespace<MacroDefinition>;
/**
* Returns the topmost token on the stack, without expanding it.
* Similar in behavior to TeX's `\futurelet`.
*/
future(): Token;
/**
* Remove and return the next unexpanded token.
*/
popToken(): Token;
/**
* Consume all following space tokens, without expansion.
*/
consumeSpaces(): void;
/**
* Expand the next token only once if possible.
*/
expandOnce(expandableOnly?: boolean): Token | Token[];
/**
* Expand the next token only once (if possible), and return the resulting
* top token on the stack (without removing anything from the stack).
* Similar in behavior to TeX's `\expandafter\futurelet`.
*/
expandAfterFuture(): Token;
/**
* Recursively expand first token, then return first non-expandable token.
*/
expandNextToken(): Token;
/**
* Fully expand the given macro name and return the resulting list of
* tokens, or return `undefined` if no such macro is defined.
*/
expandMacro(name: string): Token[] | void;
/**
* Fully expand the given macro name and return the result as a string,
* or return `undefined` if no such macro is defined.
*/
expandMacroAsText(name: string): string | void;
/**
* Consume an argument from the token stream, and return the resulting array
* of tokens and start/end token.
*/
consumeArg(delims?: ?string[]): MacroArg;
/**
* Consume the specified number of arguments from the token stream,
* and return the resulting array of arguments.
*/
consumeArgs(numArgs: number): Token[][];
/**
* Determine whether a command is currently "defined" (has some
* functionality), meaning that it's a macro (in the current group),
* a function, a symbol, or one of the special commands listed in
* `implicitCommands`.
*/
isDefined(name: string): boolean;
/**
* Determine whether a command is expandable.
*/
isExpandable(name: string): boolean;
}
export type MacroArg = {
tokens: Token[],
start: Token,
end: Token
};
/** Macro tokens (in reverse order). */
export type MacroExpansion = {
tokens: Token[],
numArgs: number,
delimiters?: string[][],
unexpandable?: boolean, // used in \let
};
export type MacroDefinition = string | MacroExpansion |
(MacroContextInterface => (string | MacroExpansion));
export type MacroMap = {[string]: MacroDefinition};
const builtinMacros: MacroMap = {};
export default builtinMacros;
// This function might one day accept an additional argument and do more things.
export function defineMacro(name: string, body: MacroDefinition) {
builtinMacros[name] = body;
}
//////////////////////////////////////////////////////////////////////
// macro tools