begingroup.js 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  1. /* -*- Mode: Javascript; indent-tabs-mode:nil; js-indent-level: 2 -*- */
  2. /* vim: set ts=2 et sw=2 tw=80: */
  3. /*************************************************************
  4. *
  5. * MathJax/extensions/TeX/begingroup.js
  6. *
  7. * Implements \begingroup and \endgroup commands that make local
  8. * definitions possible and are removed when the \endgroup occurs.
  9. *
  10. * ---------------------------------------------------------------------
  11. *
  12. * Copyright (c) 2011-2013 The MathJax Consortium
  13. *
  14. * Licensed under the Apache License, Version 2.0 (the "License");
  15. * you may not use this file except in compliance with the License.
  16. * You may obtain a copy of the License at
  17. *
  18. * http://www.apache.org/licenses/LICENSE-2.0
  19. *
  20. * Unless required by applicable law or agreed to in writing, software
  21. * distributed under the License is distributed on an "AS IS" BASIS,
  22. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  23. * See the License for the specific language governing permissions and
  24. * limitations under the License.
  25. */
  26. MathJax.Extension["TeX/begingroup"] = {
  27. version: "2.2"
  28. };
  29. MathJax.Hub.Register.StartupHook("TeX Jax Ready",function () {
  30. var TEX = MathJax.InputJax.TeX,
  31. TEXDEF = TEX.Definitions;
  32. /****************************************************/
  33. //
  34. // A namespace for localizing macros and environments
  35. // (\begingroup and \endgroup create and destroy these)
  36. //
  37. var NSFRAME = MathJax.Object.Subclass({
  38. macros: null, // the local macro definitions
  39. environments: null, // the local environments
  40. Init: function (macros,environments) {
  41. this.macros = (macros || {});
  42. this.environments = (environments || {});
  43. },
  44. //
  45. // Find a macro or environment by name
  46. //
  47. Find: function (name,type) {if (this[type][name]) {return this[type][name]}},
  48. //
  49. // Define or remove a macro or environment
  50. //
  51. Def: function (name,value,type) {this[type][name] = value},
  52. Undef: function (name,type) {delete this[type][name]},
  53. //
  54. // Merge two namespaces (used when the equation namespace is combined with the root one)
  55. //
  56. Merge: function (frame) {
  57. MathJax.Hub.Insert(this.macros,frame.macros);
  58. MathJax.Hub.Insert(this.environments,frame.environments);
  59. },
  60. //
  61. // Move global macros to the stack (globally) and remove from the frame
  62. //
  63. MergeGlobals: function (stack) {
  64. var macros = this.macros;
  65. for (var cs in macros) {if (macros.hasOwnProperty(cs) && macros[cs].global) {
  66. stack.Def(cs,macros[cs],"macros",true);
  67. delete macros[cs].global; delete macros[cs];
  68. }}
  69. },
  70. //
  71. // Clear the macro and environment lists
  72. // (but not global macros unless "all" is true)
  73. //
  74. Clear: function (all) {
  75. this.environments = {};
  76. if (all) {this.macros = {}} else {
  77. var macros = this.macros;
  78. for (var cs in macros) {
  79. if (macros.hasOwnProperty(cs) && !macros[cs].global) {delete macros[cs]}
  80. }
  81. }
  82. return this;
  83. }
  84. });
  85. /****************************************************/
  86. //
  87. // A Stack of namespace frames
  88. //
  89. var NSSTACK = TEX.nsStack = MathJax.Object.Subclass({
  90. stack: null, // the namespace frames
  91. top: 0, // the current top one (we don't pop for real until the equation completes)
  92. isEqn: false, // true if this is the equation stack (not the global one)
  93. //
  94. // Set up the initial stack frame
  95. //
  96. Init: function (eqn) {
  97. this.isEqn = eqn; this.stack = [];
  98. if (!eqn) {this.Push(NSFRAME(TEXDEF.macros,TEXDEF.environment))}
  99. else {this.Push(NSFRAME())}
  100. },
  101. //
  102. // Define a macro or environment in the top frame
  103. //
  104. Def: function (name,value,type,global) {
  105. var n = this.top-1;
  106. if (global) {
  107. //
  108. // Define global macros in the base frame and remove that cs
  109. // from all other frames. Mark the global ones in equations
  110. // so they can be made global when merged with the root stack.
  111. //
  112. while (n > 0) {this.stack[n].Undef(name,type); n--}
  113. if (!(value instanceof Array)) {value = [value]}
  114. if (this.isEqn) {value.global = true}
  115. }
  116. this.stack[n].Def(name,value,type);
  117. },
  118. //
  119. // Push a new namespace frame on the stack
  120. //
  121. Push: function (frame) {
  122. this.stack.push(frame);
  123. this.top = this.stack.length;
  124. },
  125. //
  126. // Pop the top stack frame
  127. // (if it is the root, just keep track of the pop so we can
  128. // reset it if the equation is reprocessed)
  129. //
  130. Pop: function () {
  131. var top;
  132. if (this.top > 1) {
  133. top = this.stack[--this.top];
  134. if (this.isEqn) {this.stack.pop()}
  135. } else if (this.isEqn) {
  136. this.Clear();
  137. }
  138. return top;
  139. },
  140. //
  141. // Search the stack from top to bottom for the first
  142. // definition of the given control sequence in the given type
  143. //
  144. Find: function (name,type) {
  145. for (var i = this.top-1; i >= 0; i--) {
  146. var def = this.stack[i].Find(name,type);
  147. if (def) {return def}
  148. }
  149. return null;
  150. },
  151. //
  152. // Combine the equation stack with the global one
  153. // (The bottom frame of the equation goes with the top frame of the global one,
  154. // and the remainder are pushed on the global stack, truncated to the
  155. // position where items were poped from it.)
  156. //
  157. Merge: function (stack) {
  158. stack.stack[0].MergeGlobals(this);
  159. this.stack[this.top-1].Merge(stack.stack[0]);
  160. var data = [this.top,this.stack.length-this.top].concat(stack.stack.slice(1));
  161. this.stack.splice.apply(this.stack,data);
  162. this.top = this.stack.length;
  163. },
  164. //
  165. // Put back the temporarily poped items
  166. //
  167. Reset: function () {this.top = this.stack.length},
  168. //
  169. // Clear the stack and start with a blank frame
  170. //
  171. Clear: function (all) {
  172. this.stack = [this.stack[0].Clear()];
  173. this.top = this.stack.length;
  174. }
  175. },{
  176. nsFrame: NSFRAME
  177. });
  178. /****************************************************/
  179. //
  180. // Define the new macros
  181. //
  182. TEXDEF.Add({
  183. macros: {
  184. begingroup: "BeginGroup",
  185. endgroup: "EndGroup",
  186. global: ["Extension","newcommand"],
  187. gdef: ["Extension","newcommand"]
  188. }
  189. },null,true);
  190. TEX.Parse.Augment({
  191. //
  192. // Implement \begingroup
  193. //
  194. BeginGroup: function (name) {
  195. TEX.eqnStack.Push(NSFRAME());
  196. },
  197. //
  198. // Implements \endgroup
  199. //
  200. EndGroup: function (name) {
  201. //
  202. // If the equation has pushed frames, pop one,
  203. // Otherwise clear the equation stack and pop the top global one
  204. //
  205. if (TEX.eqnStack.top > 1) {
  206. TEX.eqnStack.Pop();
  207. } else if (TEX.rootStack.top === 1) {
  208. TEX.Error(["ExtraEndMissingBegin","Extra %1 or missing \\begingroup",name]);
  209. } else {
  210. TEX.eqnStack.Clear();
  211. TEX.rootStack.Pop();
  212. }
  213. },
  214. //
  215. // Replace the original routines with ones that looks through the
  216. // equation and root stacks for the given name
  217. //
  218. csFindMacro: function (name) {
  219. return (TEX.eqnStack.Find(name,"macros") || TEX.rootStack.Find(name,"macros"));
  220. },
  221. envFindName: function (name) {
  222. return (TEX.eqnStack.Find(name,"environments") || TEX.rootStack.Find(name,"environments"));
  223. }
  224. });
  225. /****************************************************/
  226. TEX.rootStack = NSSTACK(); // the global namespace stack
  227. TEX.eqnStack = NSSTACK(true); // the equation stack
  228. //
  229. // Reset the global stack and clear the equation stack
  230. // (this gets us back to the initial stack state as it was
  231. // before the equation was first processed, in case the equation
  232. // get restarted due to an autoloaded file)
  233. //
  234. TEX.prefilterHooks.Add(function () {TEX.rootStack.Reset(); TEX.eqnStack.Clear(true)});
  235. //
  236. // We only get here if there were no errors and the equation is fully
  237. // processed (all restarts are complete). So we merge the equation
  238. // stack into the global stack, thus making the changes from this
  239. // equation permanent.
  240. //
  241. TEX.postfilterHooks.Add(function () {TEX.rootStack.Merge(TEX.eqnStack)});
  242. /*********************************************************/
  243. MathJax.Hub.Register.StartupHook("TeX newcommand Ready",function () {
  244. //
  245. // Add the commands that depend on the newcommand code
  246. //
  247. TEXDEF.Add({
  248. macros: {
  249. global: "Global",
  250. gdef: ["Macro","\\global\\def"]
  251. }
  252. },null,true);
  253. TEX.Parse.Augment({
  254. //
  255. // Modify the way macros and environments are defined
  256. // to make them go into the equation namespace stack
  257. //
  258. setDef: function (name,value) {
  259. value.isUser = true;
  260. TEX.eqnStack.Def(name,value,"macros",this.stack.env.isGlobal);
  261. delete this.stack.env.isGlobal;
  262. },
  263. setEnv: function (name,value) {
  264. value.isUser = true;
  265. TEX.eqnStack.Def(name,value,"environments")
  266. },
  267. //
  268. // Implement \global (for \global\let, \global\def and \global\newcommand)
  269. //
  270. Global: function (name) {
  271. var i = this.i; var cs = this.GetCSname(name); this.i = i;
  272. if (cs !== "let" && cs !== "def" && cs !== "newcommand") {
  273. TEX.Error(["GlobalNotFollowedBy",
  274. "%1 not followed by \\let, \\def, or \\newcommand",name]);
  275. }
  276. this.stack.env.isGlobal = true;
  277. }
  278. });
  279. });
  280. MathJax.Hub.Startup.signal.Post("TeX begingroup Ready");
  281. });
  282. MathJax.Ajax.loadComplete("[MathJax]/extensions/TeX/begingroup.js");