Annotation of loncom/html/adm/LC_math_editor/src/parser.js, revision 1.2

1.1       damieng     1: /*
                      2: 
                      3: Copyright (C) 2014  Michigan State University Board of Trustees
                      4: 
                      5: The JavaScript code in this page is free software: you can
                      6: redistribute it and/or modify it under the terms of the GNU
                      7: General Public License (GNU GPL) as published by the Free Software
                      8: Foundation, either version 3 of the License, or (at your option)
                      9: any later version.  The code is distributed WITHOUT ANY WARRANTY;
                     10: without even the implied warranty of MERCHANTABILITY or FITNESS
                     11: FOR A PARTICULAR PURPOSE.  See the GNU GPL for more details.
                     12: 
                     13: As additional permission under GNU GPL version 3 section 7, you
                     14: may distribute non-source (e.g., minimized or compacted) forms of
                     15: that code without the copy of the GNU GPL normally required by
                     16: section 4, provided you include this license notice and a URL
                     17: through which recipients can access the Corresponding Source.
                     18: 
                     19: */
                     20: 
1.2     ! damieng    21: "use strict";
        !            22: 
1.1       damieng    23: /**
                     24:  * Equation parser
                     25:  * @constructor
                     26:  * @param {boolean} [implicit_operators] - assume hidden multiplication and unit operators in some cases (unlike maxima)
                     27:  * @param {boolean} [unit_mode] - handle only numerical expressions with units (no variable)
                     28:  * @param {Array.<string>} [constants] - array of constant names for unit mode
                     29:  */
                     30: function Parser(implicit_operators, unit_mode, constants) {
                     31:     if (typeof implicit_operators == "undefined")
                     32:         this.implicit_operators = false;
                     33:     else
                     34:         this.implicit_operators = implicit_operators;
                     35:     if (typeof unit_mode == "undefined")
                     36:         this.unit_mode = false;
                     37:     else
                     38:         this.unit_mode = unit_mode;
1.2     ! damieng    39:     if (typeof constants == "undefined" || constants == null)
1.1       damieng    40:         this.constants = [];
                     41:     else
                     42:         this.constants = constants;
                     43:     this.defs = new Definitions();
                     44:     this.defs.define();
                     45:     this.operators = this.defs.operators;
                     46:     this.oph = {}; // operator hash table
                     47:     for (var i=0; i<this.operators.length; i++)
                     48:         this.oph[this.operators[i].id] = this.operators[i];
                     49: }
                     50: 
                     51: /**
                     52:  * Returns the right node at the current token, based on top-down operator precedence.
                     53:  * @param {number} rbp - Right binding power
                     54:  * @returns {ENode}
                     55:  */
                     56: Parser.prototype.expression = function(rbp) {
                     57:     var left; // ENode
                     58:     var t = this.current_token;
                     59:     if (t == null)
                     60:         throw new ParseException("Expected something at the end",
                     61:             this.tokens[this.tokens.length - 1].to + 1);
                     62:     this.advance();
                     63:     if (t.op == null)
                     64:         left = new ENode(t.type, null, t.value, null);
                     65:     else if (t.op.nud == null)
                     66:         throw new ParseException("Unexpected operator '" + t.op.id + "'", t.from);
                     67:     else
                     68:         left = t.op.nud(this);
                     69:     while (this.current_token != null && this.current_token.op != null &&
                     70:             rbp < this.current_token.op.lbp) {
                     71:         t = this.current_token;
                     72:         this.advance();
                     73:         left = t.op.led(this, left);
                     74:     }
                     75:     return left;
                     76: };
                     77: 
                     78: /**
                     79:  * Advance to the next token,
                     80:  * expecting the given operator id if it is provided.
                     81:  * Throws a ParseException if a given operator id is not found.
                     82:  * @param {string} [id] - Operator id
                     83:  */
                     84: Parser.prototype.advance = function(id) {
                     85:     if (id && (this.current_token == null || this.current_token.op == null ||
                     86:             this.current_token.op.id !== id)) {
                     87:         if (this.current_token == null)
                     88:             throw new ParseException("Expected '" + id + "' at the end",
                     89:                 this.tokens[this.tokens.length - 1].to + 1);
                     90:         else
                     91:             throw new ParseException("Expected '" + id + "'", this.current_token.from);
                     92:     }
                     93:     if (this.token_nr >= this.tokens.length) {
                     94:         this.current_token = null;
                     95:         return;
                     96:     }
                     97:     this.current_token = this.tokens[this.token_nr];
                     98:     this.token_nr += 1;
                     99: };
                    100: 
                    101: /**
                    102:  * Adds hidden multiplication and unit operators to the token stream
                    103:  */
                    104: Parser.prototype.addHiddenOperators = function() {
                    105:     var multiplication = this.defs.findOperator("*");
                    106:     var unit_operator = this.defs.findOperator("`");
                    107:     var in_units = false; // we check if we are already in the units to avoid adding two ` operators inside
                    108:     var in_exp = false;
                    109:     for (var i=0; i<this.tokens.length - 1; i++) {
                    110:         var token = this.tokens[i];
                    111:         var next_token = this.tokens[i + 1];
                    112:         if (this.unit_mode) {
                    113:             if (token.value == "`")
                    114:                 in_units = true;
                    115:             else if (in_units) {
                    116:                 if (token.value == "^")
                    117:                     in_exp = true;
                    118:                 else if (in_exp && token.type == Token.NUMBER)
                    119:                     in_exp = false;
                    120:                 else if (!in_exp && token.type == Token.NUMBER)
                    121:                     in_units = false;
1.2     ! damieng   122:                 else if (!in_exp && token.type == Token.OPERATOR && "*/^()".indexOf(token.value) == -1)
1.1       damieng   123:                     in_units = false;
                    124:                 else if (token.type == Token.NAME && next_token.value == "(")
                    125:                     in_units = false;
                    126:             }
                    127:         }
                    128:         if (
                    129:                 (token.type == Token.NAME && next_token.type == Token.NAME) ||
                    130:                 (token.type == Token.NUMBER && next_token.type == Token.NAME) ||
                    131:                 (token.type == Token.NUMBER && next_token.type == Token.NUMBER) ||
1.2     ! damieng   132:                 (token.type == Token.NUMBER && (next_token.value == "(" || next_token.value == "[" || next_token.value == "{")) ||
1.1       damieng   133:                 /*(token.type == Token.NAME && next_token.value == "(") ||*/
                    134:                 /* name ( could be a function call */
1.2     ! damieng   135:                 ((token.value == ")" || token.value == "]" || token.value == "}") && next_token.type == Token.NAME) ||
        !           136:                 ((token.value == ")" || token.value == "]" || token.value == "}") && next_token.type == Token.NUMBER) ||
        !           137:                 ((token.value == ")" || token.value == "]" || token.value == "}") && next_token.value == "(")
1.1       damieng   138:            ) {
                    139:             // support for things like "(1/2) (m/s)" is complex...
                    140:             var units = (this.unit_mode && !in_units && (token.type == Token.NUMBER ||
1.2     ! damieng   141:                 (token.value == ")" || token.value == "]" || token.value == "}")) &&
1.1       damieng   142:                 (next_token.type == Token.NAME ||
1.2     ! damieng   143:                     ((next_token.value == "(" || next_token.value == "[" || next_token.value == "{") && this.tokens.length > i + 2 &&
1.1       damieng   144:                     this.tokens[i + 2].type == Token.NAME)));
                    145:             if (units) {
                    146:                 var test_token, index_test;
                    147:                 if (next_token.type == Token.NAME) {
                    148:                     test_token = next_token;
                    149:                     index_test = i + 1;
                    150:                 } else {
                    151:                     // for instance for "2 (m/s)"
                    152:                     index_test = i + 2;
                    153:                     test_token = this.tokens[index_test];
                    154:                 }
                    155:                 for (var j=0; j<this.constants.length; j++) {
                    156:                     if (test_token.value == this.constants[j]) {
                    157:                         units = false;
                    158:                         break;
                    159:                     }
                    160:                 }
                    161:                 if (this.tokens.length > index_test + 1 && this.tokens[index_test + 1].value == "(") {
                    162:                     var known_functions = ["pow", "sqrt", "abs", "exp", "factorial", "diff",
                    163:                         "integrate", "sum", "product", "limit", "binomial", "matrix",
                    164:                         "ln", "log", "log10", "mod", "signum", "ceiling", "floor",
                    165:                         "sin", "cos", "tan", "asin", "acos", "atan", "atan2",
                    166:                         "sinh", "cosh", "tanh", "asinh", "acosh", "atanh"];
                    167:                     for (var j=0; j<known_functions.length; j++) {
                    168:                         if (test_token.value == known_functions[j]) {
                    169:                             units = false;
                    170:                             break;
                    171:                         }
                    172:                     }
                    173:                 }
                    174:             }
                    175:             var new_token;
                    176:             if (units) {
                    177:                 new_token = new Token(Token.OPERATOR, next_token.from,
                    178:                     next_token.from, unit_operator.id, unit_operator);
                    179:                 in_units = true;
                    180:             } else {
                    181:                 new_token = new Token(Token.OPERATOR, next_token.from,
                    182:                     next_token.from, multiplication.id, multiplication);
                    183:             }
                    184:             this.tokens.splice(i+1, 0, new_token);
                    185:         }
                    186:     }
                    187: }
                    188: 
                    189: /**
                    190:  * Parse the string, returning an ENode tree.
                    191:  * @param {string} text - The text to parse.
                    192:  * @returns {ENode}
                    193:  */
                    194: Parser.prototype.parse = function(text) {
                    195:     var tokenizer = new Tokenizer(this.defs, text);
                    196:     this.tokens = tokenizer.tokenize();
                    197:     if (this.tokens.length == 0) {
                    198:         return null;
                    199:     }
                    200:     if (this.implicit_operators) {
                    201:         this.addHiddenOperators();
                    202:     }
                    203:     this.token_nr = 0;
                    204:     this.current_token = this.tokens[this.token_nr];
                    205:     this.advance();
                    206:     var root = this.expression(0);
                    207:     if (this.current_token != null) {
                    208:         throw new ParseException("Expected the end", this.current_token.from);
                    209:     }
                    210:     return root;
                    211: };

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>