1 /* 2 Copyright 2008-2010, 3 Matthias Ehmann, 4 Michael Gerhaeuser, 5 Carsten Miller, 6 Bianca Valentin, 7 Alfred Wassermann, 8 Peter Wilfahrt 9 10 This file is part of JSXGraph. 11 12 JSXGraph is free software: you can redistribute it and/or modify 13 it under the terms of the GNU Lesser General Public License as published by 14 the Free Software Foundation, either version 3 of the License, or 15 (at your option) any later version. 16 17 JSXGraph is distributed in the hope that it will be useful, 18 but WITHOUT ANY WARRANTY; without even the implied warranty of 19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 GNU Lesser General Public License for more details. 21 22 You should have received a copy of the GNU Lesser General Public License 23 along with JSXGraph. If not, see <http://www.gnu.org/licenses/>. 24 25 */ 26 27 28 /** 29 * Parser helper routines. The methods in here are for parsing expressions in Geonext Syntax. 30 */ 31 32 JXG.GeonextParser = {}; 33 34 /** 35 * Converts expression of the form <i>leftop^rightop</i> into <i>Math.pow(leftop,rightop)</i>. 36 * @param {String} te Expression of the form <i>leftop^rightop</i> 37 * @type String 38 * @return Converted expression. 39 */ 40 JXG.GeonextParser.replacePow = function(te) { 41 var count, pos, c, 42 leftop, rightop, pre, p, left, i, right, expr; 43 //te = te.replace(/\s+/g,''); // Loesche allen whitespace 44 // Achtung: koennte bei Variablennamen mit Leerzeichen 45 // zu Problemen fuehren. 46 47 te = te.replace(/(\s*)\^(\s*)/g,'\^'); // delete all whitespace immediately before and after all ^ operators 48 49 // Loop over all ^ operators 50 i = te.indexOf('^'); 51 while (i>=0) { 52 // left and right are the substrings before, resp. after the ^ character 53 left = te.slice(0,i); 54 right = te.slice(i+1); 55 56 // If there is a ")" immediately before the ^ operator, it can be the end of a 57 // (i) term in parenthesis 58 // (ii) function call 59 // (iii) method callthrow new Error("JSXGraph: Can't create Sector with parent types 60 // In either case, first the corresponding opening parenthesis is searched. 61 // This is the case, when count==0 62 if (left.charAt(left.length-1)==')') { 63 count = 1; 64 pos = left.length-2; 65 while (pos>=0 && count>0) { 66 c = left.charAt(pos); 67 if (c==')') { count++; } 68 else if (c=='(') { count--; } 69 pos--; 70 } 71 if (count==0) { 72 // Now, we have found the opning parenthesis and we have to look 73 // if it is (i), or (ii), (iii). 74 leftop = ''; 75 pre = left.substring(0,pos+1); // Search for F or p.M before (...)^ 76 p = pos; 77 while (p>=0 && pre.substr(p,1).match(/([\w\.]+)/)) { 78 leftop = RegExp.$1+leftop; 79 p--; 80 } 81 leftop += left.substring(pos+1,left.length); 82 leftop = leftop.replace(/([\(\)\+\*\%\^\-\/\]\[])/g,"\\$1"); 83 } else { 84 throw new Error("JSXGraph: Missing '(' in expression"); 85 } 86 } else { 87 //leftop = '[\\w\\.\\(\\)\\+\\*\\%\\^\\-\\/\\[\\]]+'; // former: \\w\\. . Doesn't work for sin(x^2) 88 // Otherwise, the operand has to be a constant (or variable). 89 leftop = '[\\w\\.]+'; // former: \\w\\. 90 } 91 // To the right of the ^ operator there also may be a function or method call 92 // or a term in parenthesis. Alos, ere we search for the closing 93 // parenthesis. 94 if (right.match(/^([\w\.]*\()/)) { 95 count = 1; 96 pos = RegExp.$1.length; 97 while (pos<right.length && count>0) { 98 c = right.charAt(pos); 99 if (c==')') { count--; } 100 else if (c=='(') { count++; } 101 pos++; 102 } 103 if (count==0) { 104 rightop = right.substring(0,pos); 105 rightop = rightop.replace(/([\(\)\+\*\%\^\-\/\[\]])/g,"\\$1"); 106 } else { 107 throw new Error("JSXGraph: Missing ')' in expression"); 108 } 109 } else { 110 //rightop = '[\\w\\.\\(\\)\\+\\*\\%\\^\\-\\/\\[\\]]+'; // ^b , see leftop. Doesn't work for sin(x^2) 111 // Otherwise, the operand has to be a constant (or variable). 112 rightop = '[\\w\\.]+'; 113 } 114 // Now, we have the two operands and replace ^ by JXG.Math.pow 115 expr = new RegExp('(' + leftop + ')\\^(' + rightop + ')'); 116 te = te.replace(expr,"JXG.Math.pow($1,$2)"); 117 i = te.indexOf('^'); 118 } 119 return te; 120 }; 121 122 /** 123 * Converts expression of the form <i>If(a,b,c)</i> into <i>(a)?(b):(c)/i>. 124 * @param {String} te Expression of the form <i>If(a,b,c)</i> 125 * @type String 126 * @return Converted expression. 127 */ 128 JXG.GeonextParser.replaceIf = function(te) { 129 var s = '', 130 left, right, 131 first = null, 132 second = null, 133 third = null, 134 i, pos, count, k1, k2, c, meat; 135 136 i = te.indexOf('If('); 137 if (i<0) { return te; } 138 139 te = te.replace(/""/g,'0'); // "" means not defined. Here, we replace it by 0 140 while (i>=0) { 141 left = te.slice(0,i); 142 right = te.slice(i+3); 143 144 // Search the end of the If() command and take out the meat 145 count = 1; 146 pos = 0; 147 k1 = -1; 148 k2 = -1; 149 while (pos<right.length && count>0) { 150 c = right.charAt(pos); 151 if (c==')') { 152 count--; 153 } else if (c=='(') { 154 count++; 155 } else if (c==',' && count==1) { 156 if (k1<0) { 157 k1 = pos; // first komma 158 } else { 159 k2 = pos; // second komma 160 } 161 } 162 pos++; 163 } 164 meat = right.slice(0,pos-1); 165 right = right.slice(pos); 166 167 // Test the two kommas 168 if (k1<0) { return ''; } // , missing 169 if (k2<0) { return ''; } // , missing 170 171 first = meat.slice(0,k1); 172 second = meat.slice(k1+1,k2); 173 third = meat.slice(k2+1); 174 first = this.replaceIf(first); // Recurse 175 second = this.replaceIf(second); // Recurse 176 third = this.replaceIf(third); // Recurse 177 178 s += left + '((' + first + ')?' + '('+second+'):('+third+'))'; 179 te = right; 180 first = null; 181 second = null; 182 i = te.indexOf('If('); 183 } 184 s += right; 185 return s; 186 }; 187 188 /** 189 * Replace _{} by <sub> 190 * @param {String} te String containing _{}. 191 * @type String 192 * @return Given string with _{} replaced by <sub>. 193 */ 194 JXG.GeonextParser.replaceSub = function(te) { 195 if(te['indexOf']) {} else return te; 196 197 var i = te.indexOf('_{'), 198 j; 199 while (i>=0) { 200 te = te.substr(0,i)+te.substr(i).replace(/_\{/,'<sub>'); 201 j = te.substr(i).indexOf('}'); 202 if (j>=0) { 203 te = te.substr(0,j)+te.substr(j).replace(/\}/,'</sub>'); 204 } 205 i = te.indexOf('_{'); 206 } 207 208 i = te.indexOf('_'); 209 while (i>=0) { 210 te = te.substr(0,i)+te.substr(i).replace(/_(.?)/,'<sub>$1</sub>'); 211 i = te.indexOf('_'); 212 } 213 return te; 214 }; 215 216 /** 217 * Replace ^{} by <sup> 218 * @param {String} te String containing ^{}. 219 * @type String 220 * @return Given string with ^{} replaced by <sup>. 221 */ 222 JXG.GeonextParser.replaceSup = function(te) { 223 if(te['indexOf']) {} else return te; 224 225 var i = te.indexOf('^{'), 226 j; 227 while (i>=0) { 228 te = te.substr(0,i)+te.substr(i).replace(/\^\{/,'<sup>'); 229 j = te.substr(i).indexOf('}'); 230 if (j>=0) { 231 te = te.substr(0,j)+te.substr(j).replace(/\}/,'</sup>'); 232 } 233 i = te.indexOf('^{'); 234 } 235 236 i = te.indexOf('^'); 237 while (i>=0) { 238 te = te.substr(0,i)+te.substr(i).replace(/\^(.?)/,'<sup>$1</sup>'); 239 i = te.indexOf('^'); 240 } 241 242 return te; 243 }; 244 245 /** 246 * Replace an element's name in terms by an element's id. 247 * @param term Term containing names of elements. 248 * @param board Reference to the board the elements are on. 249 * @return The same string with names replaced by ids. 250 **/ 251 JXG.GeonextParser.replaceNameById = function(/** string */ term, /** JXG.Board */ board) /** string */ { 252 var pos = 0, end, elName, el, i, 253 funcs = ['X','Y','L','V']; 254 255 for (i=0;i<funcs.length;i++) { 256 pos = term.indexOf(funcs[i]+'('); 257 while (pos>=0) { 258 if (pos>=0) { 259 end = term.indexOf(')',pos+2); 260 if (end>=0) { 261 elName = term.slice(pos+2,end); 262 elName = elName.replace(/\\(['"])?/g,"$1"); 263 el = board.elementsByName[elName]; 264 term = term.slice(0,pos+2) + el.id + term.slice(end); 265 } 266 } 267 end = term.indexOf(')',pos+2); 268 pos = term.indexOf(funcs[i]+'(',end); 269 } 270 } 271 272 pos = term.indexOf('Dist('); 273 while (pos>=0) { 274 if (pos>=0) { 275 end = term.indexOf(',',pos+5); 276 if (end>=0) { 277 elName = term.slice(pos+5,end); 278 elName = elName.replace(/\\(['"])?/g,"$1"); 279 el = board.elementsByName[elName]; 280 term = term.slice(0,pos+5) + el.id + term.slice(end); 281 } 282 } 283 end = term.indexOf(',',pos+5); 284 pos = term.indexOf(',',end); 285 end = term.indexOf(')',pos+1); 286 if (end>=0) { 287 elName = term.slice(pos+1,end); 288 elName = elName.replace(/\\(['"])?/g,"$1"); 289 el = board.elementsByName[elName]; 290 term = term.slice(0,pos+1) + el.id + term.slice(end); 291 } 292 end = term.indexOf(')',pos+1); 293 pos = term.indexOf('Dist(',end); 294 } 295 296 funcs = ['Deg','Rad']; 297 for (i=0;i<funcs.length;i++) { 298 pos = term.indexOf(funcs[i]+'('); 299 while (pos>=0) { 300 if (pos>=0) { 301 end = term.indexOf(',',pos+4); 302 if (end>=0) { 303 elName = term.slice(pos+4,end); 304 elName = elName.replace(/\\(['"])?/g,"$1"); 305 el = board.elementsByName[elName]; 306 term = term.slice(0,pos+4) + el.id + term.slice(end); 307 } 308 } 309 end = term.indexOf(',',pos+4); 310 pos = term.indexOf(',',end); 311 end = term.indexOf(',',pos+1); 312 if (end>=0) { 313 elName = term.slice(pos+1,end); 314 elName = elName.replace(/\\(['"])?/g,"$1"); 315 el = board.elementsByName[elName]; 316 term = term.slice(0,pos+1) + el.id + term.slice(end); 317 } 318 end = term.indexOf(',',pos+1); 319 pos = term.indexOf(',',end); 320 end = term.indexOf(')',pos+1); 321 if (end>=0) { 322 elName = term.slice(pos+1,end); 323 elName = elName.replace(/\\(['"])?/g,"$1"); 324 el = board.elementsByName[elName]; 325 term = term.slice(0,pos+1) + el.id + term.slice(end); 326 } 327 end = term.indexOf(')',pos+1); 328 pos = term.indexOf(funcs[i]+'(',end); 329 } 330 } 331 return term; 332 }; 333 334 /** 335 * Replaces element ids in terms by element this.board.objects['id']. 336 * @param term A GEONE<sub>x</sub>T function string with JSXGraph ids in it. 337 * @return The input string with element ids replaced by this.board.objects["id"]. 338 **/ 339 JXG.GeonextParser.replaceIdByObj = function(/** string */ term) /** string */ { 340 var expr = /(X|Y|L)\(([\w_]+)\)/g; // Suche "X(gi23)" oder "Y(gi23A)" und wandle in objects['gi23'].X() um. 341 term = term.replace(expr,"this.board.objects[\"$2\"].$1()"); 342 343 expr = /(V)\(([\w_]+)\)/g; // Suche "X(gi23)" oder "Y(gi23A)" und wandle in objects['gi23'].X() um. 344 term = term.replace(expr,"this.board.objects[\"$2\"].Value()"); 345 346 expr = /(Dist)\(([\w_]+),([\w_]+)\)/g; // 347 term = term.replace(expr,'this.board.objects[\"$2\"].Dist(this.board.objects[\"$3\"])'); 348 349 expr = /(Deg)\(([\w_]+),([ \w\[\w_]+),([\w_]+)\)/g; // 350 term = term.replace(expr,'JXG.Math.Geometry.trueAngle(this.board.objects[\"$2\"],this.board.objects[\"$3\"],this.board.objects[\"$4\"])'); 351 352 expr = /Rad\(([\w_]+),([\w_]+),([\w_]+)\)/g; // Suche Rad('gi23','gi24','gi25') 353 term = term.replace(expr,'JXG.Math.Geometry.rad(this.board.objects[\"$1\"],this.board.objects[\"$2\"],this.board.objects[\"$3\"])'); 354 return term; 355 }; 356 357 /** 358 * Converts the given algebraic expression in GEONE<sub>x</sub>T syntax into an equivalent expression in JavaScript syntax. 359 * @param {String} term Expression in GEONExT syntax 360 * @type String 361 * @return Given expression translated to JavaScript. 362 */ 363 JXG.GeonextParser.geonext2JS = function(term, board) { 364 var expr, newterm, i, 365 from = ['Abs', 'ACos', 'ASin', 'ATan','Ceil','Cos','Exp','Floor','Log','Max','Min','Random','Round','Sin','Sqrt','Tan','Trunc'], 366 to = ['Math.abs', 'Math.acos', 'Math.asin', 'Math.atan', 'Math.ceil', 'Math.cos', 'Math.exp', 'Math.floor', 'Math.log', 'Math.max', 'Math.min', 'Math.random', 'this.board.round', 'Math.sin', 'Math.sqrt', 'Math.tan', 'Math.ceil']; 367 // removed: 'Pow' -> Math.pow 368 369 //term = JXG.unescapeHTML(term); // This replaces > by >, < by < and & by &. But it is to strict. 370 term = term.replace(/</g,'<'); // Hacks, to enable not well formed XML, @see JXG.GeonextReader#replaceLessThan 371 term = term.replace(/>/g,'>'); 372 term = term.replace(/&/g,'&'); 373 374 // Umwandeln der GEONExT-Syntax in JavaScript-Syntax 375 newterm = term; 376 newterm = this.replaceNameById(newterm, board); 377 newterm = this.replaceIf(newterm); 378 // Exponentiations-Problem x^y -> Math(exp(x,y). 379 newterm = this.replacePow(newterm); 380 newterm = this.replaceIdByObj(newterm); 381 for (i=0; i<from.length; i++) { 382 expr = new RegExp(['(\\W|^)(',from[i],')'].join(''),"ig"); // sin -> Math.sin and asin -> Math.asin 383 newterm = newterm.replace(expr,['$1',to[i]].join('')); 384 } 385 newterm = newterm.replace(/True/g,'true'); 386 newterm = newterm.replace(/False/g,'false'); 387 newterm = newterm.replace(/fasle/g,'false'); 388 389 newterm = newterm.replace(/Pi/g,'Math.PI'); 390 return newterm; 391 }; 392 393 /** 394 * Finds dependencies in a given term and resolves them by adding the 395 * dependent object to the found objects child elements. 396 * @param {JXG.GeometryElement} me Object depending on objects in given term. 397 * @param {String} term String containing dependencies for the given object. 398 * @param {JXG.Board} [board=me.board] Reference to a board 399 */ 400 JXG.GeonextParser.findDependencies = function(me, term, board) { 401 if(typeof board=='undefined') 402 board = me.board; 403 404 var elements = board.elementsByName, 405 el, expr, elmask; 406 407 for (el in elements) { 408 if (el != me.name) { 409 if(elements[el].type == JXG.OBJECT_TYPE_TEXT) { 410 if(!elements[el].isLabel) { 411 elmask = el.replace(/\[/g,'\\['); 412 elmask = elmask.replace(/\]/g,'\\]'); 413 expr = new RegExp("\\(\(\[\\w\\[\\]'_ \]+,\)*\("+elmask+"\)\(,\[\\w\\[\\]'_ \]+\)*\\)","g"); // Searches (A), (A,B),(A,B,C) 414 if (term.search(expr)>=0) { 415 elements[el].addChild(me); 416 } 417 } 418 } 419 else { 420 elmask = el.replace(/\[/g,'\\['); 421 elmask = elmask.replace(/\]/g,'\\]'); 422 expr = new RegExp("\\(\(\[\\w\\[\\]'_ \]+,\)*\("+elmask+"\)\(,\[\\w\\[\\]'_ \]+\)*\\)","g"); // Searches (A), (A,B),(A,B,C) 423 if (term.search(expr)>=0) { 424 elements[el].addChild(me); 425 } 426 } 427 } 428 } 429 }; 430 431