diff --git a/docs/options.md b/docs/options.md index 3533a819..8533f0cc 100644 --- a/docs/options.md +++ b/docs/options.md @@ -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. diff --git a/katex.js b/katex.js index 9824c2db..7dff3238 100644 --- a/katex.js +++ b/katex.js @@ -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; diff --git a/src/MacroExpander.js b/src/MacroExpander.js index 94a809a8..a91f2795 100644 --- a/src/MacroExpander.js +++ b/src/MacroExpander.js @@ -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 } diff --git a/src/Settings.js b/src/Settings.js index f555c21a..93ef5d52 100644 --- a/src/Settings.js +++ b/src/Settings.js @@ -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) => diff --git a/src/defineMacro.js b/src/defineMacro.js new file mode 100644 index 00000000..20d9a67d --- /dev/null +++ b/src/defineMacro.js @@ -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; + + /** + * 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; +} diff --git a/src/functions/operatorname.js b/src/functions/operatorname.js index d348d490..fa401e78 100644 --- a/src/functions/operatorname.js +++ b/src/functions/operatorname.js @@ -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"; diff --git a/src/macros.js b/src/macros.js index 0d8ec59c..f7c7a1f6 100644 --- a/src/macros.js +++ b/src/macros.js @@ -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; - - /** - * 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