//@flow import defineFunction from "../defineFunction"; import ParseError from "../ParseError"; import {assertNodeType} from "../parseNode"; const globalMap = { "\\global": "\\global", "\\long": "\\\\globallong", "\\\\globallong": "\\\\globallong", "\\def": "\\gdef", "\\gdef": "\\gdef", "\\edef": "\\xdef", "\\xdef": "\\xdef", "\\let": "\\\\globallet", "\\futurelet": "\\\\globalfuture", }; const checkControlSequence = (tok) => { const name = tok.text; if (/^(?:[\\{}$&#^_]|EOF)$/.test(name)) { throw new ParseError("Expected a control sequence", tok); } return name; }; const getRHS = (parser) => { let tok = parser.gullet.popToken(); if (tok.text === "=") { // consume optional equals tok = parser.gullet.popToken(); if (tok.text === " ") { // consume one optional space tok = parser.gullet.popToken(); } } return tok; }; const letCommand = (parser, name, tok, global) => { let macro = parser.gullet.macros.get(tok.text); if (macro == null) { // don't expand it later even if a macro with the same name is defined // e.g., \let\foo=\frac \def\frac{\relax} \frac12 tok.noexpand = true; macro = { tokens: [tok], numArgs: 0, // reproduce the same behavior in expansion unexpandable: !parser.gullet.isExpandable(tok.text), }; } parser.gullet.macros.set(name, macro, global); }; // -> | // -> |\global // -> | // -> \global|\long|\outer defineFunction({ type: "internal", names: [ "\\global", "\\long", "\\\\globallong", // can’t be entered directly ], props: { numArgs: 0, allowedInText: true, }, handler({parser, funcName}) { parser.consumeSpaces(); const token = parser.fetch(); if (globalMap[token.text]) { // KaTeX doesn't have \par, so ignore \long if (funcName === "\\global" || funcName === "\\\\globallong") { token.text = globalMap[token.text]; } return assertNodeType(parser.parseFunction(), "internal"); } throw new ParseError(`Invalid token after macro prefix`, token); }, }); // Basic support for macro definitions: \def, \gdef, \edef, \xdef // -> // -> \def|\gdef|\edef|\xdef // -> defineFunction({ type: "internal", names: ["\\def", "\\gdef", "\\edef", "\\xdef"], props: { numArgs: 0, allowedInText: true, primitive: true, }, handler({parser, funcName}) { let tok = parser.gullet.popToken(); const name = tok.text; if (/^(?:[\\{}$&#^_]|EOF)$/.test(name)) { throw new ParseError("Expected a control sequence", tok); } let numArgs = 0; let insert; const delimiters = [[]]; // contains no braces while (parser.gullet.future().text !== "{") { tok = parser.gullet.popToken(); if (tok.text === "#") { // If the very last character of the is #, so that // this # is immediately followed by {, TeX will behave as if the { // had been inserted at the right end of both the parameter text // and the replacement text. if (parser.gullet.future().text === "{") { insert = parser.gullet.future(); delimiters[numArgs].push("{"); break; } // A parameter, the first appearance of # must be followed by 1, // the next by 2, and so on; up to nine #’s are allowed tok = parser.gullet.popToken(); if (!(/^[1-9]$/.test(tok.text))) { throw new ParseError(`Invalid argument number "${tok.text}"`); } if (parseInt(tok.text) !== numArgs + 1) { throw new ParseError( `Argument number "${tok.text}" out of order`); } numArgs++; delimiters.push([]); } else if (tok.text === "EOF") { throw new ParseError("Expected a macro definition"); } else { delimiters[numArgs].push(tok.text); } } // replacement text, enclosed in '{' and '}' and properly nested let {tokens} = parser.gullet.consumeArg(); if (insert) { tokens.unshift(insert); } if (funcName === "\\edef" || funcName === "\\xdef") { tokens = parser.gullet.expandTokens(tokens); tokens.reverse(); // to fit in with stack order } // Final arg is the expansion of the macro parser.gullet.macros.set(name, { tokens, numArgs, delimiters, }, funcName === globalMap[funcName]); return { type: "internal", mode: parser.mode, }; }, }); // -> // -> \futurelet // | \let // -> |= defineFunction({ type: "internal", names: [ "\\let", "\\\\globallet", // can’t be entered directly ], props: { numArgs: 0, allowedInText: true, primitive: true, }, handler({parser, funcName}) { const name = checkControlSequence(parser.gullet.popToken()); parser.gullet.consumeSpaces(); const tok = getRHS(parser); letCommand(parser, name, tok, funcName === "\\\\globallet"); return { type: "internal", mode: parser.mode, }; }, }); // ref: https://www.tug.org/TUGboat/tb09-3/tb22bechtolsheim.pdf defineFunction({ type: "internal", names: [ "\\futurelet", "\\\\globalfuture", // can’t be entered directly ], props: { numArgs: 0, allowedInText: true, primitive: true, }, handler({parser, funcName}) { const name = checkControlSequence(parser.gullet.popToken()); const middle = parser.gullet.popToken(); const tok = parser.gullet.popToken(); letCommand(parser, name, tok, funcName === "\\\\globalfuture"); parser.gullet.pushToken(tok); parser.gullet.pushToken(middle); return { type: "internal", mode: parser.mode, }; }, });