Files
KaTeX/src/macros.js
Erik Demaine 588f5a1ee6 eslint-plugin-flowtype & upgrade to eslint@4 (#849)
* Add eslint-plugin-flowtype. Fix #844

Using the recommended settings for plugin.
This involved adding some spaces to some existing union types.

* Upgrade to eslint@4, fix spotted bugs

Switched to indent-legacy to allow e.g. comments to have extra indents.
2017-09-06 22:06:26 -04:00

301 lines
11 KiB
JavaScript

// @flow
/**
* Predefined macros for KaTeX.
* This can be used to define some commands in terms of others.
*/
import symbols from "./symbols";
import utils from "./utils";
import {Token} from "./Token";
/**
* Provides context to macros defined by functions. Implemented by
* MacroExpander.
*/
export interface MacroContextInterface {
/**
* Returns the topmost token on the stack, without expanding it.
* Similar in behavior to TeX's `\futurelet`.
*/
future(): 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;
}
/** Macro tokens (in reverse order). */
export type MacroExpansion = {tokens: Token[], numArgs: number};
type MacroDefinition = string | (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.
function defineMacro(name: string, body: string | MacroContextInterface => string) {
builtinMacros[name] = body;
}
//////////////////////////////////////////////////////////////////////
// basics
defineMacro("\\bgroup", "{");
defineMacro("\\egroup", "}");
defineMacro("\\begingroup", "{");
defineMacro("\\endgroup", "}");
// We don't distinguish between math and nonmath kerns.
// (In TeX, the mu unit works only with \mkern.)
defineMacro("\\mkern", "\\kern");
// \llap and \rlap render their contents in text mode
defineMacro("\\llap", "\\mathllap{\\textrm{#1}}");
defineMacro("\\rlap", "\\mathrlap{\\textrm{#1}}");
defineMacro("\\clap", "\\mathclap{\\textrm{#1}}");
//////////////////////////////////////////////////////////////////////
// amsmath.sty
// http://mirrors.concertpass.com/tex-archive/macros/latex/required/amsmath/amsmath.pdf
// \def\overset#1#2{\binrel@{#2}\binrel@@{\mathop{\kern\z@#2}\limits^{#1}}}
defineMacro("\\overset", "\\mathop{#2}\\limits^{#1}");
defineMacro("\\underset", "\\mathop{#2}\\limits_{#1}");
// \newcommand{\boxed}[1]{\fbox{\m@th$\displaystyle#1$}}
defineMacro("\\boxed", "\\fbox{\\displaystyle{#1}}");
// \def\iff{\DOTSB\;\Longleftrightarrow\;}
// \def\implies{\DOTSB\;\Longrightarrow\;}
// \def\impliedby{\DOTSB\;\Longleftarrow\;}
defineMacro("\\iff", "\\DOTSB\\;\\Longleftrightarrow\\;");
defineMacro("\\implies", "\\DOTSB\\;\\Longrightarrow\\;");
defineMacro("\\impliedby", "\\DOTSB\\;\\Longleftarrow\\;");
// AMSMath's automatic \dots, based on \mdots@@ macro.
const dotsByToken = {
',': '\\dotsc',
'\\not': '\\dotsb',
// \keybin@ checks for the following:
'+': '\\dotsb',
'=': '\\dotsb',
'<': '\\dotsb',
'>': '\\dotsb',
'-': '\\dotsb',
'*': '\\dotsb',
':': '\\dotsb',
// Symbols whose definition starts with \DOTSB:
'\\DOTSB': '\\dotsb',
'\\coprod': '\\dotsb',
'\\bigvee': '\\dotsb',
'\\bigwedge': '\\dotsb',
'\\biguplus': '\\dotsb',
'\\bigcap': '\\dotsb',
'\\bigcup': '\\dotsb',
'\\prod': '\\dotsb',
'\\sum': '\\dotsb',
'\\bigotimes': '\\dotsb',
'\\bigoplus': '\\dotsb',
'\\bigodot': '\\dotsb',
'\\bigsqcup': '\\dotsb',
'\\implies': '\\dotsb',
'\\impliedby': '\\dotsb',
'\\And': '\\dotsb',
'\\longrightarrow': '\\dotsb',
'\\Longrightarrow': '\\dotsb',
'\\longleftarrow': '\\dotsb',
'\\Longleftarrow': '\\dotsb',
'\\longleftrightarrow': '\\dotsb',
'\\Longleftrightarrow': '\\dotsb',
'\\mapsto': '\\dotsb',
'\\longmapsto': '\\dotsb',
'\\hookrightarrow': '\\dotsb',
'\\iff': '\\dotsb',
'\\doteq': '\\dotsb',
// Symbols whose definition starts with \mathbin:
'\\mathbin': '\\dotsb',
'\\bmod': '\\dotsb',
// Symbols whose definition starts with \mathrel:
'\\mathrel': '\\dotsb',
'\\relbar': '\\dotsb',
'\\Relbar': '\\dotsb',
'\\xrightarrow': '\\dotsb',
'\\xleftarrow': '\\dotsb',
// Symbols whose definition starts with \DOTSI:
'\\DOTSI': '\\dotsi',
'\\int': '\\dotsi',
'\\oint': '\\dotsi',
'\\iint': '\\dotsi',
'\\iiint': '\\dotsi',
'\\iiiint': '\\dotsi',
'\\idotsint': '\\dotsi',
// Symbols whose definition starts with \DOTSX:
'\\DOTSX': '\\dotsx',
};
defineMacro("\\dots", function(context) {
// TODO: If used in text mode, should expand to \textellipsis.
// However, in KaTeX, \textellipsis and \ldots behave the same
// (in text mode), and it's unlikely we'd see any of the math commands
// that affect the behavior of \dots when in text mode. So fine for now
// (until we support \ifmmode ... \else ... \fi).
let thedots = '\\dotso';
const next = context.expandAfterFuture().text;
if (next in dotsByToken) {
thedots = dotsByToken[next];
} else if (next.substr(0, 4) === '\\not') {
thedots = '\\dotsb';
} else if (next in symbols.math) {
if (utils.contains(['bin', 'rel'], symbols.math[next].group)) {
thedots = '\\dotsb';
}
}
return thedots;
});
const spaceAfterDots = {
// \rightdelim@ checks for the following:
')': true,
']': true,
'\\rbrack': true,
'\\}': true,
'\\rbrace': true,
'\\rangle': true,
'\\rceil': true,
'\\rfloor': true,
'\\rgroup': true,
'\\rmoustache': true,
'\\right': true,
'\\bigr': true,
'\\biggr': true,
'\\Bigr': true,
'\\Biggr': true,
// \extra@ also tests for the following:
'$': true,
// \extrap@ checks for the following:
';': true,
'.': true,
',': true,
};
defineMacro("\\dotso", function(context) {
const next = context.future().text;
if (next in spaceAfterDots) {
return "\\ldots\\,";
} else {
return "\\ldots";
}
});
defineMacro("\\dotsc", function(context) {
const next = context.future().text;
// \dotsc uses \extra@ but not \extrap@, instead specially checking for
// ';' and '.', but doesn't check for ','.
if (next in spaceAfterDots && next !== ',') {
return "\\ldots\\,";
} else {
return "\\ldots";
}
});
defineMacro("\\cdots", function(context) {
const next = context.future().text;
if (next in spaceAfterDots) {
return "\\@cdots\\,";
} else {
return "\\@cdots";
}
});
defineMacro("\\dotsb", "\\cdots");
defineMacro("\\dotsm", "\\cdots");
defineMacro("\\dotsi", "\\!\\cdots");
// amsmath doesn't actually define \dotsx, but \dots followed by a macro
// starting with \DOTSX implies \dotso, and then \extra@ detects this case
// and forces the added `\,`.
defineMacro("\\dotsx", "\\ldots\\,");
// \let\DOTSI\relax
// \let\DOTSB\relax
// \let\DOTSX\relax
defineMacro("\\DOTSI", "\\relax");
defineMacro("\\DOTSB", "\\relax");
defineMacro("\\DOTSX", "\\relax");
// http://texdoc.net/texmf-dist/doc/latex/amsmath/amsmath.pdf
defineMacro("\\thinspace", "\\,"); // \let\thinspace\,
defineMacro("\\medspace", "\\:"); // \let\medspace\:
defineMacro("\\thickspace", "\\;"); // \let\thickspace\;
//////////////////////////////////////////////////////////////////////
// LaTeX source2e
// \DeclareRobustCommand\hspace{\@ifstar\@hspacer\@hspace}
// \def\@hspace#1{\hskip #1\relax}
// KaTeX doesn't do line breaks, so \hspace is the same as \kern
defineMacro("\\hspace", "\\kern{#1}");
//////////////////////////////////////////////////////////////////////
// mathtools.sty
//\providecommand\ordinarycolon{:}
defineMacro("\\ordinarycolon", ":");
//\def\vcentcolon{\mathrel{\mathop\ordinarycolon}}
//TODO(edemaine): Not yet centered. Fix via \raisebox or #726
defineMacro("\\vcentcolon", "\\mathrel{\\mathop\\ordinarycolon}");
// \providecommand*\dblcolon{\vcentcolon\mathrel{\mkern-.9mu}\vcentcolon}
defineMacro("\\dblcolon", "\\vcentcolon\\mathrel{\\mkern-.9mu}\\vcentcolon");
// \providecommand*\coloneqq{\vcentcolon\mathrel{\mkern-1.2mu}=}
defineMacro("\\coloneqq", "\\vcentcolon\\mathrel{\\mkern-1.2mu}=");
// \providecommand*\Coloneqq{\dblcolon\mathrel{\mkern-1.2mu}=}
defineMacro("\\Coloneqq", "\\dblcolon\\mathrel{\\mkern-1.2mu}=");
// \providecommand*\coloneq{\vcentcolon\mathrel{\mkern-1.2mu}\mathrel{-}}
defineMacro("\\coloneq", "\\vcentcolon\\mathrel{\\mkern-1.2mu}\\mathrel{-}");
// \providecommand*\Coloneq{\dblcolon\mathrel{\mkern-1.2mu}\mathrel{-}}
defineMacro("\\Coloneq", "\\dblcolon\\mathrel{\\mkern-1.2mu}\\mathrel{-}");
// \providecommand*\eqqcolon{=\mathrel{\mkern-1.2mu}\vcentcolon}
defineMacro("\\eqqcolon", "=\\mathrel{\\mkern-1.2mu}\\vcentcolon");
// \providecommand*\Eqqcolon{=\mathrel{\mkern-1.2mu}\dblcolon}
defineMacro("\\Eqqcolon", "=\\mathrel{\\mkern-1.2mu}\\dblcolon");
// \providecommand*\eqcolon{\mathrel{-}\mathrel{\mkern-1.2mu}\vcentcolon}
defineMacro("\\eqcolon", "\\mathrel{-}\\mathrel{\\mkern-1.2mu}\\vcentcolon");
// \providecommand*\Eqcolon{\mathrel{-}\mathrel{\mkern-1.2mu}\dblcolon}
defineMacro("\\Eqcolon", "\\mathrel{-}\\mathrel{\\mkern-1.2mu}\\dblcolon");
// \providecommand*\colonapprox{\vcentcolon\mathrel{\mkern-1.2mu}\approx}
defineMacro("\\colonapprox", "\\vcentcolon\\mathrel{\\mkern-1.2mu}\\approx");
// \providecommand*\Colonapprox{\dblcolon\mathrel{\mkern-1.2mu}\approx}
defineMacro("\\Colonapprox", "\\dblcolon\\mathrel{\\mkern-1.2mu}\\approx");
// \providecommand*\colonsim{\vcentcolon\mathrel{\mkern-1.2mu}\sim}
defineMacro("\\colonsim", "\\vcentcolon\\mathrel{\\mkern-1.2mu}\\sim");
// \providecommand*\Colonsim{\dblcolon\mathrel{\mkern-1.2mu}\sim}
defineMacro("\\Colonsim", "\\dblcolon\\mathrel{\\mkern-1.2mu}\\sim");
//////////////////////////////////////////////////////////////////////
// colonequals.sty
// Alternate names for mathtools's macros:
defineMacro("\\ratio", "\\vcentcolon");
defineMacro("\\coloncolon", "\\dblcolon");
defineMacro("\\colonequals", "\\coloneqq");
defineMacro("\\coloncolonequals", "\\Coloneqq");
defineMacro("\\equalscolon", "\\eqqcolon");
defineMacro("\\equalscoloncolon", "\\Eqqcolon");
defineMacro("\\colonminus", "\\coloneq");
defineMacro("\\coloncolonminus", "\\Coloneq");
defineMacro("\\minuscolon", "\\eqcolon");
defineMacro("\\minuscoloncolon", "\\Eqcolon");
// \colonapprox name is same in mathtools and colonequals.
defineMacro("\\coloncolonapprox", "\\Colonapprox");
// \colonsim name is same in mathtools and colonequals.
defineMacro("\\coloncolonsim", "\\Colonsim");
// Additional macros, implemented by analogy with mathtools definitions:
defineMacro("\\simcolon", "\\sim\\mathrel{\\mkern-1.2mu}\\vcentcolon");
defineMacro("\\simcoloncolon", "\\sim\\mathrel{\\mkern-1.2mu}\\dblcolon");
defineMacro("\\approxcolon", "\\approx\\mathrel{\\mkern-1.2mu}\\vcentcolon");
defineMacro("\\approxcoloncolon",
"\\approx\\mathrel{\\mkern-1.2mu}\\dblcolon");