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>