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>