      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].elementClass === Const.OBJECT_CLASS_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