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