added math templates
[lambda.git] / jsMath / extensions / newcommand.js
1 /*
2  *  extensions/newcommand.js
3  *  
4  *  Part of the jsMath package for mathematics on the web.
5  *
6  *  This file implements the \newcommand and \def macros.  It will be
7  *  loaded automatically when needed, or can be loaded by
8  *  
9  *    jsMath.Extension.Require('newcommand');
10  *
11  *  ---------------------------------------------------------------------
12  *
13  *  Copyright 2005-2006 by Davide P. Cervone
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 /********************************************************************/
29
30 jsMath.Package(jsMath.Parser,{
31   
32   macros: {
33     newcommand: 'NewCommand',
34     newenvironment: 'NewEnvironment',
35     def: 'MacroDef'
36   },
37   
38   /*
39    *  Implement \newcommand{\name}[n]{...}
40    */
41   NewCommand: function (name) {
42     var cs = this.trimSpaces(this.GetArgument(this.cmd+name)); if (this.error) return;
43     var n = this.trimSpaces(this.GetBrackets(this.cmd+name)); if (this.error) return;
44     var def = this.GetArgument(this.cmd+name); if (this.error) return;
45     if (n == '') {n = null}
46     if (cs.charAt(0) == this.cmd) {cs = cs.substr(1)}
47     if (!cs.match(/^(.|[a-z]+)$/i)) {this.Error("Illegal control sequence name for "+this.cmd+name); return}
48     if (n != null && !n.match(/^[0-9]+$/)) {this.Error("Illegal number of parameters specified in "+this.cmd+name); return}
49     jsMath.Parser.prototype.macros[cs] = ['Macro',def,n];
50   },
51   
52   /*
53    *  Implement \newenvironment{name}[n]{begincmd}{endcmd}
54    */
55   NewEnvironment: function (name) {
56     var env = this.trimSpaces(this.GetArgument(this.cmd+name)); if (this.error) return;
57     var n = this.trimSpaces(this.GetBrackets(this.cmd+name)); if (this.error) return;
58     var bdef = this.GetArgument(this.cmd+name); if (this.error) return;
59     var edef = this.GetArgument(this.cmd+name); if (this.error) return;
60     if (n == '') {n = null}
61     if (n != null && !n.match(/^[0-9]+$/)) {this.Error("Illegal number of parameters specified in "+this.cmd+name); return}
62     jsMath.Parser.prototype.environments[env] = ['Environment',bdef,edef,n];
63   },
64   
65   /*
66    *  Implement \def command
67    */
68   MacroDef: function (name) {
69     var cs = this.GetCSname(this.cmd+name); if (this.error) return;
70     var params = this.GetTemplate(this.cmd+name); if (this.error) return;
71     var def = this.GetArgument(this.cmd+name); if (this.error) return;
72     if (typeof(params) == 'number') {
73       jsMath.Parser.prototype.macros[cs] = ['Macro',def,params];
74     } else {
75       jsMath.Parser.prototype.macros[cs] = ['MacroWithTemplate',def,params[0],params[1]];
76     }
77   },
78
79   /*
80    *  Get a CS name or give an error
81    */
82   GetCSname: function (cmd) {
83     var c = this.GetNext();
84     if (c != this.cmd) {this.Error(cmd+" must be followed by a control sequence"); return null}
85     var cs = this.trimSpaces(this.GetArgument(cmd)); if (this.error) {return null};
86     return cs.substr(1);
87   },
88   
89   /*
90    *  Get a \def parameter template
91    */
92   GetTemplate: function (cmd) {
93     var c; var params = []; var n = 0;
94     c = this.GetNext(); var i = this.i;
95     while (this.i < this.string.length) {
96       c = this.GetNext();
97       if (c == '#') {
98         if (i != this.i) {params[n] = this.string.substr(i,this.i-i)}
99         c = this.string.charAt(++this.i);
100         if (!c.match(/[1-9]/)) {this.Error("Illegal use of # in "+cmd); return null}
101         if (1*c != ++n) {this.Error("Parameters must be numbered sequentially"); return null}
102         i = this.i+1;
103       } else if (c == '{') {
104         if (i != this.i) {params[n] = this.string.substr(i,this.i-i)}
105         if (params.length > 0) {return [n,params]} else {return n}
106       }
107       this.i++;
108     }
109     this.Error("Missing replacement string for definition of "+cmd);
110     return null;
111   },
112   
113   /*
114    *  Process a macro with a parameter template
115    */
116   MacroWithTemplate: function (name,data) {
117     var text = data[0];
118     var n = data[1]; var params = data[2];
119     if (n) {
120       var args = []; var c = this.GetNext();
121       if (params[0] && !this.MatchParam(params[0]))
122         {this.Error("Use of "+this.cmd+name+" doesn't match its definition"); return}
123       for (var i = 0; i < n; i++) {
124         args[args.length] = this.GetParameter(this.cmd+name,params[i+1]);
125         if (this.error) return;
126       }
127       text = this.SubstituteArgs(args,text);
128     }
129     this.string = this.AddArgs(text,this.string.slice(this.i));
130     this.i = 0;
131   },
132   
133   /*
134    *  Process a user-defined environment
135    */
136   Environment: function (name,data) {
137     var bdef = data[0]; var edef = data[1]; var n = data[2];
138     if (n) {
139       var args = [];
140       for (var i = 0; i < n; i++) {
141         args[args.length] = this.GetArgument(this.cmd+"begin{"+name+"}"); if (this.error) return;
142       }
143       bdef = this.SubstituteArgs(args,bdef);
144     }
145     var text = this.GetEnd(name); if (this.error) return;
146     text = this.AddArgs(this.AddArgs(bdef,text),edef);
147     this.string = this.AddArgs(text,this.string.slice(this.i));
148     this.i = 0;
149   },
150
151   /*
152    *  Find a single parameter delimited by a trailing template
153    */
154   GetParameter: function (name,param) {
155     if (param == null) {return this.GetArgument(name)}
156     var i = this.i; var j = 0; var hasBraces = 0;
157     while (this.i < this.string.length) {
158       if (this.string.charAt(this.i) == '{') {
159         if (this.i == i) {hasBraces = 1}
160         this.GetArgument(name); j = this.i - i;
161       } else if (this.MatchParam(param)) {
162         if (hasBraces) {i++; j -= 2}
163         return this.string.substr(i,j);
164       } else {
165         this.i++; j++; hasBraces = 0;
166       }
167     }
168     this.Error("Runaway argument for "+name+"?");
169     return null;
170   },
171
172   /*
173    *  Check if a template is at the current location.
174    *  (The match must be exact, with no spacing differences.  TeX is
175    *   a little more forgiving about spaces after macro names)
176    */
177   MatchParam: function (param) {
178     if (this.string.substr(this.i,param.length) != param) {return 0}
179     this.i += param.length;
180     return 1;
181   }
182   
183 });
184
185 /*
186  *  Define a jsMath.Environment() command similar to the
187  *  jsMath.Macro() command.
188  *  
189  *  Usage:  jsMath.Environment(name,begin,end[,n])
190  *  
191  *  where "name" is the name of the environment, "begin" is the
192  *  text that replaces the \begin{name} and "end" is the text that
193  *  replaces the \end{name}.  If "n" is provided, it is the number
194  *  of parameters that the \begin{name} accepts, and these are
195  *  used to replace #1, #2, etc within the "begin" text.
196  */
197
198 jsMath.Add(jsMath,{
199   Environment: function (name) {
200     var environments = jsMath.Parser.prototype.environments;
201     environments[name] = ['Environment'];
202     for (var i = 1; i < arguments.length; i++) 
203       {environments[name][environments[name].length] = arguments[i]}
204   }
205 });