1 /* 2 Copyright 2008-2013 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 dual licensed under the GNU LGPL or MIT License. 13 14 You can redistribute it and/or modify it under the terms of the 15 16 * GNU Lesser General Public License as published by 17 the Free Software Foundation, either version 3 of the License, or 18 (at your option) any later version 19 OR 20 * MIT License: https://github.com/jsxgraph/jsxgraph/blob/master/LICENSE.MIT 21 22 JSXGraph is distributed in the hope that it will be useful, 23 but WITHOUT ANY WARRANTY; without even the implied warranty of 24 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 25 GNU Lesser General Public License for more details. 26 27 You should have received a copy of the GNU Lesser General Public License and 28 the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/> 29 and <http://opensource.org/licenses/MIT/>. 30 */ 31 32 33 /*global JXG: true, define: true*/ 34 /*jslint nomen: true, plusplus: true*/ 35 36 /* depends: 37 jxg 38 base/constants 39 utils/type 40 math/math 41 math/geometry 42 */ 43 44 define([ 45 'jxg', 'base/constants', 'utils/type' 46 ], function (JXG, Const, Type) { 47 48 "use strict"; 49 50 /** 51 * Parser helper routines. The methods in here are for parsing expressions in Geonext Syntax. 52 * @namespace 53 */ 54 JXG.GeonextParser = { 55 /** 56 * Converts expression of the form <i>leftop^rightop</i> into <i>Math.pow(leftop,rightop)</i>. 57 * @param {String} te Expression of the form <i>leftop^rightop</i> 58 * @returns {String} Converted expression. 59 */ 60 replacePow: function (te) { 61 var count, pos, c, 62 leftop, rightop, pre, p, left, i, right, expr; 63 64 // delete all whitespace immediately before and after all ^ operators 65 te = te.replace(/(\s*)\^(\s*)/g, '^'); 66 67 // Loop over all ^ operators 68 i = te.indexOf('^'); 69 while (i >= 0) { 70 // left and right are the substrings before, resp. after the ^ character 71 left = te.slice(0, i); 72 right = te.slice(i + 1); 73 74 // If there is a ")" immediately before the ^ operator, it can be the end of a 75 // (i) term in parenthesis 76 // (ii) function call 77 // (iii) method call 78 // In either case, first the corresponding opening parenthesis is searched. 79 // This is the case, when count==0 80 if (left.charAt(left.length - 1) === ')') { 81 count = 1; 82 pos = left.length - 2; 83 84 while (pos >= 0 && count > 0) { 85 c = left.charAt(pos); 86 if (c === ')') { 87 count++; 88 } else if (c === '(') { 89 count -= 1; 90 } 91 pos -= 1; 92 } 93 94 if (count === 0) { 95 // Now, we have found the opning parenthesis and we have to look 96 // if it is (i), or (ii), (iii). 97 leftop = ''; 98 // Search for F or p.M before (...)^ 99 pre = left.substring(0, pos + 1); 100 p = pos; 101 while (p >= 0 && pre.substr(p, 1).match(/([\w\.]+)/)) { 102 leftop = RegExp.$1 + leftop; 103 p -= 1; 104 } 105 leftop += left.substring(pos + 1, left.length); 106 leftop = leftop.replace(/([\(\)\+\*\%\^\-\/\]\[])/g, '\\$1'); 107 } else { 108 throw new Error("JSXGraph: Missing '(' in expression"); 109 } 110 } else { 111 // Otherwise, the operand has to be a constant (or variable). 112 leftop = '[\\w\\.]+'; // former: \\w\\. 113 } 114 115 // To the right of the ^ operator there also may be a function or method call 116 // or a term in parenthesis. Alos, ere we search for the closing 117 // parenthesis. 118 if (right.match(/^([\w\.]*\()/)) { 119 count = 1; 120 pos = RegExp.$1.length; 121 122 while (pos < right.length && count > 0) { 123 c = right.charAt(pos); 124 125 if (c === ')') { 126 count -= 1; 127 } else if (c === '(') { 128 count += 1; 129 } 130 pos += 1; 131 } 132 133 if (count === 0) { 134 rightop = right.substring(0, pos); 135 rightop = rightop.replace(/([\(\)\+\*\%\^\-\/\[\]])/g, '\\$1'); 136 } else { 137 throw new Error("JSXGraph: Missing ')' in expression"); 138 } 139 } else { 140 // Otherwise, the operand has to be a constant (or variable). 141 rightop = '[\\w\\.]+'; 142 } 143 // Now, we have the two operands and replace ^ by JXG.Math.pow 144 expr = new RegExp('(' + leftop + ')\\^(' + rightop + ')'); 145 //te = te.replace(expr, 'JXG.Math.pow($1,$2)'); 146 te = te.replace(expr, 'pow($1,$2)'); 147 i = te.indexOf('^'); 148 } 149 150 return te; 151 }, 152 153 /** 154 * Converts expression of the form <i>If(a,b,c)</i> into <i>(a)?(b):(c)/i>. 155 * @param {String} te Expression of the form <i>If(a,b,c)</i> 156 * @returns {String} Converted expression. 157 */ 158 replaceIf: function (te) { 159 var left, right, 160 i, pos, count, k1, k2, c, meat, 161 s = '', 162 first = null, 163 second = null, 164 third = null; 165 166 i = te.indexOf('If('); 167 if (i < 0) { 168 return te; 169 } 170 171 // "" means not defined. Here, we replace it by 0 172 te = te.replace(/""/g, '0'); 173 while (i >= 0) { 174 left = te.slice(0, i); 175 right = te.slice(i + 3); 176 177 // Search the end of the If() command and take out the meat 178 count = 1; 179 pos = 0; 180 k1 = -1; 181 k2 = -1; 182 183 while (pos < right.length && count > 0) { 184 c = right.charAt(pos); 185 186 if (c === ')') { 187 count -= 1; 188 } else if (c === '(') { 189 count += 1; 190 } else if (c === ',' && count === 1) { 191 if (k1 < 0) { 192 // first komma 193 k1 = pos; 194 } else { 195 // second komma 196 k2 = pos; 197 } 198 } 199 pos += 1; 200 } 201 meat = right.slice(0, pos - 1); 202 right = right.slice(pos); 203 204 // Test the two kommas 205 if (k1 < 0) { 206 // , missing 207 return ''; 208 } 209 210 if (k2 < 0) { 211 // , missing 212 return ''; 213 } 214 215 first = meat.slice(0, k1); 216 second = meat.slice(k1 + 1, k2); 217 third = meat.slice(k2 + 1); 218 219 // Recurse 220 first = this.replaceIf(first); 221 second = this.replaceIf(second); 222 third = this.replaceIf(third); 223 224 s += left + '((' + first + ')?' + '(' + second + '):(' + third + '))'; 225 te = right; 226 first = null; 227 second = null; 228 i = te.indexOf('If('); 229 } 230 s += right; 231 return s; 232 }, 233 234 /** 235 * Replace an element's name in terms by an element's id. 236 * @param {String} term Term containing names of elements. 237 * @param {JXG.Board} board Reference to the board the elements are on. 238 * @param {Boolean} [jc=false] If true, all id's will be surrounded by <tt>$('</tt> and <tt>')</tt>. 239 * @returns {String} The same string with names replaced by ids. 240 **/ 241 replaceNameById: function (term, board, jc) { 242 var end, elName, el, i, 243 pos = 0, 244 funcs = ['X', 'Y', 'L', 'V'], 245 246 printId = function (id) { 247 if (jc) { 248 return '$(\'' + id + '\')'; 249 } 250 251 return id; 252 }; 253 254 // Find X(el), Y(el), ... 255 // All functions declared in funcs 256 for (i = 0; i < funcs.length; i++) { 257 pos = term.indexOf(funcs[i] + '('); 258 259 while (pos >= 0) { 260 if (pos >= 0) { 261 end = term.indexOf(')', pos + 2); 262 if (end >= 0) { 263 elName = term.slice(pos + 2, end); 264 elName = elName.replace(/\\(['"])?/g, '$1'); 265 el = board.elementsByName[elName]; 266 267 if (el) { 268 term = term.slice(0, pos + 2) + (jc ? '$(\'' : '') + printId(el.id) + term.slice(end); 269 } 270 } 271 } 272 end = term.indexOf(')', pos + 2); 273 pos = term.indexOf(funcs[i] + '(', end); 274 } 275 } 276 277 pos = term.indexOf('Dist('); 278 while (pos >= 0) { 279 if (pos >= 0) { 280 end = term.indexOf(',', pos + 5); 281 if (end >= 0) { 282 elName = term.slice(pos + 5, end); 283 elName = elName.replace(/\\(['"])?/g, '$1'); 284 el = board.elementsByName[elName]; 285 286 if (el) { 287 term = term.slice(0, pos + 5) + printId(el.id) + term.slice(end); 288 } 289 } 290 } 291 end = term.indexOf(',', pos + 5); 292 pos = term.indexOf(',', end); 293 end = term.indexOf(')', pos + 1); 294 295 if (end >= 0) { 296 elName = term.slice(pos + 1, end); 297 elName = elName.replace(/\\(['"])?/g, '$1'); 298 el = board.elementsByName[elName]; 299 300 if (el) { 301 term = term.slice(0, pos + 1) + printId(el.id) + term.slice(end); 302 } 303 } 304 end = term.indexOf(')', pos + 1); 305 pos = term.indexOf('Dist(', end); 306 } 307 308 funcs = ['Deg', 'Rad']; 309 for (i = 0; i < funcs.length; i++) { 310 pos = term.indexOf(funcs[i] + '('); 311 while (pos >= 0) { 312 if (pos >= 0) { 313 end = term.indexOf(',', pos + 4); 314 if (end >= 0) { 315 elName = term.slice(pos + 4, end); 316 elName = elName.replace(/\\(['"])?/g, '$1'); 317 el = board.elementsByName[elName]; 318 319 if (el) { 320 term = term.slice(0, pos + 4) + printId(el.id) + term.slice(end); 321 } 322 } 323 } 324 325 end = term.indexOf(',', pos + 4); 326 pos = term.indexOf(',', end); 327 end = term.indexOf(',', pos + 1); 328 329 if (end >= 0) { 330 elName = term.slice(pos + 1, end); 331 elName = elName.replace(/\\(['"])?/g, '$1'); 332 el = board.elementsByName[elName]; 333 334 if (el) { 335 term = term.slice(0, pos + 1) + printId(el.id) + term.slice(end); 336 } 337 } 338 339 end = term.indexOf(',', pos + 1); 340 pos = term.indexOf(',', end); 341 end = term.indexOf(')', pos + 1); 342 343 if (end >= 0) { 344 elName = term.slice(pos + 1, end); 345 elName = elName.replace(/\\(['"])?/g, '$1'); 346 el = board.elementsByName[elName]; 347 if (el) { 348 term = term.slice(0, pos + 1) + printId(el.id) + term.slice(end); 349 } 350 } 351 352 end = term.indexOf(')', pos + 1); 353 pos = term.indexOf(funcs[i] + '(', end); 354 } 355 } 356 357 return term; 358 }, 359 360 /** 361 * Replaces element ids in terms by element this.board.objects['id']. 362 * @param {String} term A GEONE<sub>x</sub>T function string with JSXGraph ids in it. 363 * @returns {String} The input string with element ids replaced by this.board.objects["id"]. 364 **/ 365 replaceIdByObj: function (term) { 366 // Search for expressions like "X(gi23)" or "Y(gi23A)" and convert them to objects['gi23'].X(). 367 var expr = /(X|Y|L)\(([\w_]+)\)/g; 368 term = term.replace(expr, '$(\'$2\').$1()'); 369 370 expr = /(V)\(([\w_]+)\)/g; 371 term = term.replace(expr, '$(\'$2\').Value()'); 372 373 expr = /(Dist)\(([\w_]+),([\w_]+)\)/g; 374 term = term.replace(expr, 'dist($(\'$2\'), $(\'$3\'))'); 375 376 expr = /(Deg)\(([\w_]+),([ \w\[\w_]+),([\w_]+)\)/g; 377 term = term.replace(expr, 'deg($(\'$2\'),$(\'$3\'),$(\'$4\'))'); 378 379 // Search for Rad('gi23','gi24','gi25') 380 expr = /Rad\(([\w_]+),([\w_]+),([\w_]+)\)/g; 381 term = term.replace(expr, 'rad($(\'$1\'),$(\'$2\'),$(\'$3\'))'); 382 383 // it's ok, it will run through the jessiecode parser afterwards... 384 /*jslint regexp: true*/ 385 expr = /N\((.+)\)/g; 386 term = term.replace(expr, '($1)'); 387 388 return term; 389 }, 390 391 /** 392 * Converts the given algebraic expression in GEONE<sub>x</sub>T syntax into an equivalent expression in JavaScript syntax. 393 * @param {String} term Expression in GEONExT syntax 394 * @param {JXG.Board} board 395 * @returns {String} Given expression translated to JavaScript. 396 */ 397 geonext2JS: function (term, board) { 398 var expr, newterm, i, 399 from = ['Abs', 'ACos', 'ASin', 'ATan', 'Ceil', 'Cos', 'Exp', 'Factorial', 'Floor', 400 'Log', 'Max', 'Min', 'Random', 'Round', 'Sin', 'Sqrt', 'Tan', 'Trunc'], 401 to = ['abs', 'acos', 'asin', 'atan', 'ceil', 'cos', 402 'exp', 'factorial', 'floor', 'log', 'max', 'min', 403 'random', 'round', 'sin', 'sqrt', 'tan', 'ceil']; 404 405 // Hacks, to enable not well formed XML, @see JXG.GeonextReader#replaceLessThan 406 term = term.replace(/</g, '<'); 407 term = term.replace(/>/g, '>'); 408 term = term.replace(/&/g, '&'); 409 410 // Umwandeln der GEONExT-Syntax in JavaScript-Syntax 411 newterm = term; 412 newterm = this.replaceNameById(newterm, board); 413 newterm = this.replaceIf(newterm); 414 // Exponentiations-Problem x^y -> Math(exp(x,y). 415 newterm = this.replacePow(newterm); 416 newterm = this.replaceIdByObj(newterm); 417 418 for (i = 0; i < from.length; i++) { 419 // sin -> Math.sin and asin -> Math.asin 420 expr = new RegExp(['(\\W|^)(', from[i], ')'].join(''), 'ig'); 421 newterm = newterm.replace(expr, ['$1', to[i]].join('')); 422 } 423 newterm = newterm.replace(/True/g, 'true'); 424 newterm = newterm.replace(/False/g, 'false'); 425 newterm = newterm.replace(/fasle/g, 'false'); 426 newterm = newterm.replace(/Pi/g, 'PI'); 427 newterm = newterm.replace(/"/g, '\''); 428 429 return newterm; 430 }, 431 432 /** 433 * Finds dependencies in a given term and resolves them by adding the 434 * dependent object to the found objects child elements. 435 * @param {JXG.GeometryElement} me Object depending on objects in given term. 436 * @param {String} term String containing dependencies for the given object. 437 * @param {JXG.Board} [board=me.board] Reference to a board 438 */ 439 findDependencies: function (me, term, board) { 440 var elements, el, expr, elmask; 441 442 if (!Type.exists(board)) { 443 board = me.board; 444 } 445 446 elements = board.elementsByName; 447 448 for (el in elements) { 449 if (elements.hasOwnProperty(el)) { 450 if (el !== me.name) { 451 if (elements[el].type === Const.OBJECT_TYPE_TEXT) { 452 if (!elements[el].visProp.islabel) { 453 elmask = el.replace(/\[/g, '\\['); 454 elmask = elmask.replace(/\]/g, '\\]'); 455 456 // Searches (A), (A,B),(A,B,C) 457 expr = new RegExp("\\(([\\w\\[\\]'_ ]+,)*(" + elmask + ")(,[\\w\\[\\]'_ ]+)*\\)", 'g'); 458 459 if (term.search(expr) >= 0) { 460 elements[el].addChild(me); 461 } 462 } 463 } else { 464 elmask = el.replace(/\[/g, '\\['); 465 elmask = elmask.replace(/\]/g, '\\]'); 466 467 // Searches (A), (A,B),(A,B,C) 468 expr = new RegExp("\\(([\\w\\[\\]'_ ]+,)*(" + elmask + ")(,[\\w\\[\\]'_ ]+)*\\)", 'g'); 469 470 if (term.search(expr) >= 0) { 471 elements[el].addChild(me); 472 } 473 } 474 } 475 } 476 } 477 }, 478 479 /** 480 * Converts the given algebraic expression in GEONE<sub>x</sub>T syntax into an equivalent expression in JessieCode syntax. 481 * @param {String} term Expression in GEONExT syntax 482 * @param {JXG.Board} board 483 * @returns {String} Given expression translated to JavaScript. 484 */ 485 gxt2jc: function (term, board) { 486 var newterm, 487 from = ['Sqrt'], 488 to = ['sqrt']; 489 490 // Hacks, to enable not well formed XML, @see JXG.GeonextReader#replaceLessThan 491 term = term.replace(/</g, '<'); 492 term = term.replace(/>/g, '>'); 493 term = term.replace(/&/g, '&'); 494 newterm = term; 495 newterm = this.replaceNameById(newterm, board, true); 496 newterm = newterm.replace(/True/g, 'true'); 497 newterm = newterm.replace(/False/g, 'false'); 498 newterm = newterm.replace(/fasle/g, 'false'); 499 500 return newterm; 501 } 502 }; 503 504 return JXG.GeonextParser; 505 }); 506