NewcommandMethods.ts 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. /*************************************************************
  2. *
  3. * Copyright (c) 2018-2022 The MathJax Consortium
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License");
  6. * you may not use this file except in compliance with the License.
  7. * You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. /**
  18. * @fileoverview Mappings for TeX parsing for definitorial commands.
  19. *
  20. * @author v.sorge@mathjax.org (Volker Sorge)
  21. */
  22. import {ParseMethod} from '../Types.js';
  23. import TexError from '../TexError.js';
  24. import TexParser from '../TexParser.js';
  25. import * as sm from '../SymbolMap.js';
  26. import {Symbol, Macro} from '../Symbol.js';
  27. import BaseMethods from '../base/BaseMethods.js';
  28. import ParseUtil from '../ParseUtil.js';
  29. import {StackItem} from '../StackItem.js';
  30. import NewcommandUtil from './NewcommandUtil.js';
  31. // Namespace
  32. let NewcommandMethods: Record<string, ParseMethod> = {};
  33. /**
  34. * Implements \newcommand{\name}[n][default]{...}
  35. * @param {TexParser} parser The calling parser.
  36. * @param {string} name The name of the calling command.
  37. */
  38. NewcommandMethods.NewCommand = function(parser: TexParser, name: string) {
  39. // @test Newcommand Simple
  40. let cs = NewcommandUtil.GetCsNameArgument(parser, name);
  41. let n = NewcommandUtil.GetArgCount(parser, name);
  42. let opt = parser.GetBrackets(name);
  43. let def = parser.GetArgument(name);
  44. NewcommandUtil.addMacro(parser, cs, NewcommandMethods.Macro, [def, n, opt]);
  45. };
  46. /**
  47. * Implements \newenvironment{name}[n][default]{begincmd}{endcmd}
  48. * @param {TexParser} parser The calling parser.
  49. * @param {string} name The name of the calling command.
  50. */
  51. NewcommandMethods.NewEnvironment = function(parser: TexParser, name: string) {
  52. // @test Newenvironment Empty, Newenvironment Content
  53. let env = ParseUtil.trimSpaces(parser.GetArgument(name));
  54. let n = NewcommandUtil.GetArgCount(parser, name);
  55. let opt = parser.GetBrackets(name);
  56. let bdef = parser.GetArgument(name);
  57. let edef = parser.GetArgument(name);
  58. NewcommandUtil.addEnvironment(parser, env, NewcommandMethods.BeginEnv, [true, bdef, edef, n, opt]);
  59. };
  60. /**
  61. * Implements \def command.
  62. * @param {TexParser} parser The calling parser.
  63. * @param {string} name The name of the calling command.
  64. */
  65. NewcommandMethods.MacroDef = function(parser: TexParser, name: string) {
  66. // @test Def DoubleLet, DefReDef
  67. let cs = NewcommandUtil.GetCSname(parser, name);
  68. let params = NewcommandUtil.GetTemplate(parser, name, '\\' + cs);
  69. let def = parser.GetArgument(name);
  70. !(params instanceof Array) ?
  71. // @test Def DoubleLet, DefReDef
  72. NewcommandUtil.addMacro(parser, cs, NewcommandMethods.Macro, [def, params]) :
  73. // @test Def Let
  74. NewcommandUtil.addMacro(parser, cs, NewcommandMethods.MacroWithTemplate, [def].concat(params));
  75. };
  76. /**
  77. * Implements the \let command.
  78. *
  79. * All \let commands create either new delimiters or macros in the extension
  80. * maps. In the latter case if the let binds a symbol we have to generate a
  81. * macro with the appropriate parse methods from the SymbolMap. Otherwise we
  82. * simply copy the macro under a new name.
  83. *
  84. * Let does not always work on special characters as TeX does. For example
  85. * "\let\car^ a\car b" will yield a superscript, on the otherhand
  86. * \let\bgroup={ is possible and will work fine in \bgroup a } but will fail
  87. * in \sqrt\bgroup a}.
  88. *
  89. * @param {TexParser} parser The calling parser.
  90. * @param {string} name The name of the calling command.
  91. */
  92. NewcommandMethods.Let = function(parser: TexParser, name: string) {
  93. const cs = NewcommandUtil.GetCSname(parser, name);
  94. let c = parser.GetNext();
  95. // @test Let Bar, Let Caret
  96. if (c === '=') {
  97. // @test Let Brace Equal, Let Brace Equal Stretchy
  98. parser.i++;
  99. c = parser.GetNext();
  100. }
  101. const handlers = parser.configuration.handlers;
  102. if (c === '\\') {
  103. // @test Let Bar, Let Brace Equal Stretchy
  104. name = NewcommandUtil.GetCSname(parser, name);
  105. let macro = handlers.get('delimiter').lookup('\\' + name) as Symbol;
  106. if (macro) {
  107. // @test Let Bar, Let Brace Equal Stretchy
  108. NewcommandUtil.addDelimiter(parser, '\\' + cs, macro.char, macro.attributes);
  109. return;
  110. }
  111. const map = handlers.get('macro').applicable(name);
  112. if (!map) {
  113. // @test Let Undefined CS
  114. return;
  115. }
  116. if (map instanceof sm.MacroMap) {
  117. // @test Def Let, Newcommand Let
  118. const macro = (map as sm.CommandMap).lookup(name) as Macro;
  119. NewcommandUtil.addMacro(parser, cs, macro.func, macro.args, macro.symbol);
  120. return;
  121. }
  122. macro = (map as sm.CharacterMap).lookup(name) as Symbol;
  123. const newArgs = NewcommandUtil.disassembleSymbol(cs, macro);
  124. const method = (p: TexParser, _cs: string, ...rest: any[]) => {
  125. // @test Let Relet, Let Let, Let Circular Macro
  126. const symb = NewcommandUtil.assembleSymbol(rest);
  127. return map.parser(p, symb);
  128. };
  129. NewcommandUtil.addMacro(parser, cs, method, newArgs);
  130. return;
  131. }
  132. // @test Let Brace Equal, Let Caret
  133. parser.i++;
  134. const macro = handlers.get('delimiter').lookup(c) as Symbol;
  135. if (macro) {
  136. // @test Let Paren Delim, Let Paren Stretchy
  137. NewcommandUtil.addDelimiter(parser, '\\' + cs, macro.char, macro.attributes);
  138. return;
  139. }
  140. // @test Let Brace Equal, Let Caret
  141. NewcommandUtil.addMacro(parser, cs, NewcommandMethods.Macro, [c]);
  142. };
  143. /**
  144. * Process a macro with a parameter template by replacing parameters in the
  145. * parser's string.
  146. * @param {TexParser} parser The calling parser.
  147. * @param {string} name The name of the calling command.
  148. * @param {string} text The text template of the macro.
  149. * @param {string} n The number of parameters.
  150. * @param {string[]} ...params The parameter values.
  151. */
  152. NewcommandMethods.MacroWithTemplate = function (parser: TexParser, name: string,
  153. text: string, n: string,
  154. ...params: string[]) {
  155. const argCount = parseInt(n, 10);
  156. // @test Def Let
  157. if (argCount) {
  158. // @test Def Let
  159. let args = [];
  160. parser.GetNext();
  161. if (params[0] && !NewcommandUtil.MatchParam(parser, params[0])) {
  162. // @test Missing Arguments
  163. throw new TexError('MismatchUseDef',
  164. 'Use of %1 doesn\'t match its definition', name);
  165. }
  166. for (let i = 0; i < argCount; i++) {
  167. // @test Def Let
  168. args.push(NewcommandUtil.GetParameter(parser, name, params[i + 1]));
  169. }
  170. text = ParseUtil.substituteArgs(parser, args, text);
  171. }
  172. parser.string = ParseUtil.addArgs(parser, text,
  173. parser.string.slice(parser.i));
  174. parser.i = 0;
  175. ParseUtil.checkMaxMacros(parser);
  176. };
  177. /**
  178. * Process a user-defined environment.
  179. * @param {TexParser} parser The calling parser.
  180. * @param {StackItem} begin The begin stackitem.
  181. * @param {string} bdef The begin definition in the newenvironment macro.
  182. * @param {string} edef The end definition in the newenvironment macro.
  183. * @param {number} n The number of parameters.
  184. * @param {string} def Default for an optional parameter.
  185. */
  186. NewcommandMethods.BeginEnv = function(parser: TexParser, begin: StackItem,
  187. bdef: string, edef: string, n: number, def: string) {
  188. // @test Newenvironment Empty, Newenvironment Content
  189. // We have an end item, and we are supposed to close this environment.
  190. if (begin.getProperty('end') && parser.stack.env['closing'] === begin.getName()) {
  191. // @test Newenvironment Empty, Newenvironment Content
  192. delete parser.stack.env['closing'];
  193. // Parse the commands in the end environment definition.
  194. let rest = parser.string.slice(parser.i);
  195. parser.string = edef;
  196. parser.i = 0;
  197. parser.Parse();
  198. // Reset to parsing the remainder of the expression.
  199. parser.string = rest;
  200. parser.i = 0;
  201. // Close this environment.
  202. return parser.itemFactory.create('end').setProperty('name', begin.getName());
  203. }
  204. if (n) {
  205. // @test Newenvironment Optional, Newenvironment Arg Optional
  206. let args: string[] = [];
  207. if (def != null) {
  208. // @test Newenvironment Optional, Newenvironment Arg Optional
  209. let optional = parser.GetBrackets('\\begin{' + begin.getName() + '}');
  210. args.push(optional == null ? def : optional);
  211. }
  212. for (let i = args.length; i < n; i++) {
  213. // @test Newenvironment Arg Optional
  214. args.push(parser.GetArgument('\\begin{' + begin.getName() + '}'));
  215. }
  216. bdef = ParseUtil.substituteArgs(parser, args, bdef);
  217. edef = ParseUtil.substituteArgs(parser, [], edef); // no args, but get errors for #n in edef
  218. }
  219. parser.string = ParseUtil.addArgs(parser, bdef,
  220. parser.string.slice(parser.i));
  221. parser.i = 0;
  222. return parser.itemFactory.create('beginEnv').setProperty('name', begin.getName());
  223. };
  224. NewcommandMethods.Macro = BaseMethods.Macro;
  225. export default NewcommandMethods;