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

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: 
        !            21: /**
        !            22:  * Equation parser
        !            23:  * @constructor
        !            24:  * @param {boolean} [implicit_operators] - assume hidden multiplication and unit operators in some cases (unlike maxima)
        !            25:  * @param {boolean} [unit_mode] - handle only numerical expressions with units (no variable)
        !            26:  * @param {Array.<string>} [constants] - array of constant names for unit mode
        !            27:  */
        !            28: function Parser(implicit_operators, unit_mode, constants) {
        !            29:     if (typeof implicit_operators == "undefined")
        !            30:         this.implicit_operators = false;
        !            31:     else
        !            32:         this.implicit_operators = implicit_operators;
        !            33:     if (typeof unit_mode == "undefined")
        !            34:         this.unit_mode = false;
        !            35:     else
        !            36:         this.unit_mode = unit_mode;
        !            37:     if (typeof constants == "undefined")
        !            38:         this.constants = [];
        !            39:     else
        !            40:         this.constants = constants;
        !            41:     this.defs = new Definitions();
        !            42:     this.defs.define();
        !            43:     this.operators = this.defs.operators;
        !            44:     this.oph = {}; // operator hash table
        !            45:     for (var i=0; i<this.operators.length; i++)
        !            46:         this.oph[this.operators[i].id] = this.operators[i];
        !            47: }
        !            48: 
        !            49: /**
        !            50:  * Returns the right node at the current token, based on top-down operator precedence.
        !            51:  * @param {number} rbp - Right binding power
        !            52:  * @returns {ENode}
        !            53:  */
        !            54: Parser.prototype.expression = function(rbp) {
        !            55:     var left; // ENode
        !            56:     var t = this.current_token;
        !            57:     if (t == null)
        !            58:         throw new ParseException("Expected something at the end",
        !            59:             this.tokens[this.tokens.length - 1].to + 1);
        !            60:     this.advance();
        !            61:     if (t.op == null)
        !            62:         left = new ENode(t.type, null, t.value, null);
        !            63:     else if (t.op.nud == null)
        !            64:         throw new ParseException("Unexpected operator '" + t.op.id + "'", t.from);
        !            65:     else
        !            66:         left = t.op.nud(this);
        !            67:     while (this.current_token != null && this.current_token.op != null &&
        !            68:             rbp < this.current_token.op.lbp) {
        !            69:         t = this.current_token;
        !            70:         this.advance();
        !            71:         left = t.op.led(this, left);
        !            72:     }
        !            73:     return left;
        !            74: };
        !            75: 
        !            76: /**
        !            77:  * Advance to the next token,
        !            78:  * expecting the given operator id if it is provided.
        !            79:  * Throws a ParseException if a given operator id is not found.
        !            80:  * @param {string} [id] - Operator id
        !            81:  */
        !            82: Parser.prototype.advance = function(id) {
        !            83:     if (id && (this.current_token == null || this.current_token.op == null ||
        !            84:             this.current_token.op.id !== id)) {
        !            85:         if (this.current_token == null)
        !            86:             throw new ParseException("Expected '" + id + "' at the end",
        !            87:                 this.tokens[this.tokens.length - 1].to + 1);
        !            88:         else
        !            89:             throw new ParseException("Expected '" + id + "'", this.current_token.from);
        !            90:     }
        !            91:     if (this.token_nr >= this.tokens.length) {
        !            92:         this.current_token = null;
        !            93:         return;
        !            94:     }
        !            95:     this.current_token = this.tokens[this.token_nr];
        !            96:     this.token_nr += 1;
        !            97: };
        !            98: 
        !            99: /**
        !           100:  * Adds hidden multiplication and unit operators to the token stream
        !           101:  */
        !           102: Parser.prototype.addHiddenOperators = function() {
        !           103:     var multiplication = this.defs.findOperator("*");
        !           104:     var unit_operator = this.defs.findOperator("`");
        !           105:     var in_units = false; // we check if we are already in the units to avoid adding two ` operators inside
        !           106:     var in_exp = false;
        !           107:     for (var i=0; i<this.tokens.length - 1; i++) {
        !           108:         var token = this.tokens[i];
        !           109:         var next_token = this.tokens[i + 1];
        !           110:         if (this.unit_mode) {
        !           111:             if (token.value == "`")
        !           112:                 in_units = true;
        !           113:             else if (in_units) {
        !           114:                 if (token.value == "^")
        !           115:                     in_exp = true;
        !           116:                 else if (in_exp && token.type == Token.NUMBER)
        !           117:                     in_exp = false;
        !           118:                 else if (!in_exp && token.type == Token.NUMBER)
        !           119:                     in_units = false;
        !           120:                 else if (token.type == Token.OPERATOR && "*/^()".indexOf(token.value) == -1)
        !           121:                     in_units = false;
        !           122:                 else if (token.type == Token.NAME && next_token.value == "(")
        !           123:                     in_units = false;
        !           124:             }
        !           125:         }
        !           126:         if (
        !           127:                 (token.type == Token.NAME && next_token.type == Token.NAME) ||
        !           128:                 (token.type == Token.NUMBER && next_token.type == Token.NAME) ||
        !           129:                 (token.type == Token.NUMBER && next_token.type == Token.NUMBER) ||
        !           130:                 (token.type == Token.NUMBER && (next_token.value == "(" || next_token.value == "[")) ||
        !           131:                 /*(token.type == Token.NAME && next_token.value == "(") ||*/
        !           132:                 /* name ( could be a function call */
        !           133:                 ((token.value == ")" || token.value == "]") && next_token.type == Token.NAME) ||
        !           134:                 ((token.value == ")" || token.value == "]") && next_token.type == Token.NUMBER) ||
        !           135:                 ((token.value == ")" || token.value == "]") && next_token.value == "(")
        !           136:            ) {
        !           137:             // support for things like "(1/2) (m/s)" is complex...
        !           138:             var units = (this.unit_mode && !in_units && (token.type == Token.NUMBER ||
        !           139:                 (token.value == ")" || token.value == "]")) &&
        !           140:                 (next_token.type == Token.NAME ||
        !           141:                     ((next_token.value == "(" || next_token.value == "[") && this.tokens.length > i + 2 &&
        !           142:                     this.tokens[i + 2].type == Token.NAME)));
        !           143:             if (units) {
        !           144:                 var test_token, index_test;
        !           145:                 if (next_token.type == Token.NAME) {
        !           146:                     test_token = next_token;
        !           147:                     index_test = i + 1;
        !           148:                 } else {
        !           149:                     // for instance for "2 (m/s)"
        !           150:                     index_test = i + 2;
        !           151:                     test_token = this.tokens[index_test];
        !           152:                 }
        !           153:                 for (var j=0; j<this.constants.length; j++) {
        !           154:                     if (test_token.value == this.constants[j]) {
        !           155:                         units = false;
        !           156:                         break;
        !           157:                     }
        !           158:                 }
        !           159:                 if (this.tokens.length > index_test + 1 && this.tokens[index_test + 1].value == "(") {
        !           160:                     var known_functions = ["pow", "sqrt", "abs", "exp", "factorial", "diff",
        !           161:                         "integrate", "sum", "product", "limit", "binomial", "matrix",
        !           162:                         "ln", "log", "log10", "mod", "signum", "ceiling", "floor",
        !           163:                         "sin", "cos", "tan", "asin", "acos", "atan", "atan2",
        !           164:                         "sinh", "cosh", "tanh", "asinh", "acosh", "atanh"];
        !           165:                     for (var j=0; j<known_functions.length; j++) {
        !           166:                         if (test_token.value == known_functions[j]) {
        !           167:                             units = false;
        !           168:                             break;
        !           169:                         }
        !           170:                     }
        !           171:                 }
        !           172:             }
        !           173:             var new_token;
        !           174:             if (units) {
        !           175:                 new_token = new Token(Token.OPERATOR, next_token.from,
        !           176:                     next_token.from, unit_operator.id, unit_operator);
        !           177:                 in_units = true;
        !           178:             } else {
        !           179:                 new_token = new Token(Token.OPERATOR, next_token.from,
        !           180:                     next_token.from, multiplication.id, multiplication);
        !           181:             }
        !           182:             this.tokens.splice(i+1, 0, new_token);
        !           183:         }
        !           184:     }
        !           185: }
        !           186: 
        !           187: /**
        !           188:  * Parse the string, returning an ENode tree.
        !           189:  * @param {string} text - The text to parse.
        !           190:  * @returns {ENode}
        !           191:  */
        !           192: Parser.prototype.parse = function(text) {
        !           193:     var tokenizer = new Tokenizer(this.defs, text);
        !           194:     this.tokens = tokenizer.tokenize();
        !           195:     if (this.tokens.length == 0) {
        !           196:         return null;
        !           197:     }
        !           198:     if (this.implicit_operators) {
        !           199:         this.addHiddenOperators();
        !           200:     }
        !           201:     this.token_nr = 0;
        !           202:     this.current_token = this.tokens[this.token_nr];
        !           203:     this.advance();
        !           204:     var root = this.expression(0);
        !           205:     if (this.current_token != null) {
        !           206:         throw new ParseException("Expected the end", this.current_token.from);
        !           207:     }
        !           208:     return root;
        !           209: };

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