From c896eae319e5aaa6153f87ee76c1bb63506cb29c Mon Sep 17 00:00:00 2001 From: josdejong Date: Sat, 23 Feb 2013 23:45:51 +0100 Subject: [PATCH] Parser implemented --- .npmignore | 1 - Jakefile.js | 11 +- README.md | 10 +- math.js | 2266 ++++++++++++++++++++++++- math.min.js | 22 +- src/exports.js | 7 +- src/function/arithmetic/divide.js | 4 +- src/function/arithmetic/multiply.js | 4 +- src/function/arithmetic/pow.js | 6 +- src/function/arithmetic/round.js | 2 +- src/header.js | 15 +- src/options.js | 9 - src/parser/Parser.js | 1148 +++++++++++++ src/parser/Scope.js | 344 ++++ src/parser/node/Assignment.js | 74 + src/parser/node/Block.js | 59 + src/parser/node/Constant.js | 28 + src/parser/node/Function.js | 75 + src/parser/node/FunctionAssignment.js | 99 ++ src/parser/node/Node.js | 22 + src/type/Complex.js | 130 +- src/type/Number.js | 2 +- src/type/String.js | 2 +- src/type/Unit.js | 2 +- src/util.js | 2 +- test/test.html | 3 + test/type/complex.js | 52 +- tools/jake-utils.js | 21 +- 28 files changed, 4305 insertions(+), 115 deletions(-) delete mode 100644 src/options.js create mode 100644 src/parser/Parser.js create mode 100644 src/parser/Scope.js create mode 100644 src/parser/node/Assignment.js create mode 100644 src/parser/node/Block.js create mode 100644 src/parser/node/Constant.js create mode 100644 src/parser/node/Function.js create mode 100644 src/parser/node/FunctionAssignment.js create mode 100644 src/parser/node/Node.js diff --git a/.npmignore b/.npmignore index 8954266f4..db89438a4 100644 --- a/.npmignore +++ b/.npmignore @@ -3,6 +3,5 @@ test img node_modules Jakefile.js -math.min.js .idea .npmignore diff --git a/Jakefile.js b/Jakefile.js index 417a13f13..323e9b3f6 100644 --- a/Jakefile.js +++ b/Jakefile.js @@ -40,12 +40,19 @@ task('concat', function () { var result = util.concat({ src: [ './src/exports.js', - './src/options.js', './src/util.js', './src/type/**/*.js', './src/constants.js', './src/functions.js', - './src/function/**/*.js' + './src/function/**/*.js', + './src/parser/node/Node.js', + './src/parser/node/Function.js', + './src/parser/node/Constant.js', + './src/parser/node/Block.js', + './src/parser/node/Assignment.js', + './src/parser/node/FunctionAssignment.js', + './src/parser/Scope.js', + './src/parser/Parser.js' ], dest: MATHJS, header: util.read(HEADER) + '\n(function() {\n', diff --git a/README.md b/README.md index 84701f98b..95c288a27 100644 --- a/README.md +++ b/README.md @@ -3,16 +3,20 @@ [http://mathjs.org](http://mathjs.org) Math.js is an extensive math library for JavaScript and Node.js, -compatible with the built-in Math library of JavaScript. +compatible with JavaScript's built-in Math library. -Features: + +## Features - A flexible expression parser. -- Supports numbers, complex values, units, strings, arrays, and matrices. +- Supports numbers, complex values, units, strings, arrays**\***, and + matrices**\***. - A large set of built-in functions and constants. - Easily extensible with new functions and constants. - Powerful and easy to use. +**\*** Note: arrays, and matrices are to be implemented. + ## Install diff --git a/math.js b/math.js index 7292b1003..f8c63f6fa 100644 --- a/math.js +++ b/math.js @@ -1,11 +1,22 @@ /** * math.js - * An extended math library. Includes a parser, real and complex values, units, - * matrices, strings, and a large set of functions and constants. * https://github.com/josdejong/mathjs * - * @version @@version - * @date @@date + * Math.js is an extensive math library for JavaScript and Node.js, + * compatible with JavaScript's built-in Math library. + * + * Features: + * - A flexible expression parser + * - Support for numbers, complex values, units, strings, arrays*, + * and matrices* + * - A large set of built-in functions and constants + * - Easily extensible with new functions and constants + * - Powerful and easy to use + * + * * Note: arrays and matrices are to be implemented. + * + * @version 2013-02-23 + * @date 0.2.0-SNAPSHOT * * @license * Copyright (C) 2013 Jos de Jong @@ -30,7 +41,12 @@ */ var math = { type: {}, - parser: {} + parser: { + node: {} + }, + options: { + precision: 10 // number of decimals in formatted output + } }; /** @@ -59,15 +75,6 @@ if (typeof(window) != 'undefined') { window['math'] = math; } -/** - * Settings for math.js - */ - -var options = { - precision: 10 // number of decimals in formatted output -}; - -math.options = options; var util = {}; @@ -123,8 +130,8 @@ util.randomUUID = function () { ); }; -// Internet Explorer 8 and older does not support Array.indexOf, -// so we define it here in that case +// Internet Explorer 8 and older does not support Array.indexOf, so we define +// it here in that case. // http://soledadpenades.com/2007/05/17/arrayindexof-in-internet-explorer/ if(!Array.prototype.indexOf) { Array.prototype.indexOf = function(obj){ @@ -143,8 +150,8 @@ if(!Array.prototype.indexOf) { } } -// Internet Explorer 8 and older does not support Array.forEach, -// so we define it here in that case +// Internet Explorer 8 and older does not support Array.forEach, so we define +// it here in that case. // https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/forEach if (!Array.prototype.forEach) { Array.prototype.forEach = function(fn, scope) { @@ -154,8 +161,85 @@ if (!Array.prototype.forEach) { } } +// Internet Explorer 8 and older does not support Array.map, so we define it +// here in that case. +// https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/map +// Production steps of ECMA-262, Edition 5, 15.4.4.19 +// Reference: http://es5.github.com/#x15.4.4.19 +if (!Array.prototype.map) { + Array.prototype.map = function(callback, thisArg) { + + var T, A, k; + + if (this == null) { + throw new TypeError(" this is null or not defined"); + } + + // 1. Let O be the result of calling ToObject passing the |this| value as the argument. + var O = Object(this); + + // 2. Let lenValue be the result of calling the Get internal method of O with the argument "length". + // 3. Let len be ToUint32(lenValue). + var len = O.length >>> 0; + + // 4. If IsCallable(callback) is false, throw a TypeError exception. + // See: http://es5.github.com/#x9.11 + if (typeof callback !== "function") { + throw new TypeError(callback + " is not a function"); + } + + // 5. If thisArg was supplied, let T be thisArg; else let T be undefined. + if (thisArg) { + T = thisArg; + } + + // 6. Let A be a new array created as if by the expression new Array(len) where Array is + // the standard built-in constructor with that name and len is the value of len. + A = new Array(len); + + // 7. Let k be 0 + k = 0; + + // 8. Repeat, while k < len + while(k < len) { + + var kValue, mappedValue; + + // a. Let Pk be ToString(k). + // This is implicit for LHS operands of the in operator + // b. Let kPresent be the result of calling the HasProperty internal method of O with argument Pk. + // This step can be combined with c + // c. If kPresent is true, then + if (k in O) { + + // i. Let kValue be the result of calling the Get internal method of O with argument Pk. + kValue = O[ k ]; + + // ii. Let mappedValue be the result of calling the Call internal method of callback + // with T as the this value and argument list containing kValue, k, and O. + mappedValue = callback.call(T, kValue, k, O); + + // iii. Call the DefineOwnProperty internal method of A with arguments + // Pk, Property Descriptor {Value: mappedValue, : true, Enumerable: true, Configurable: true}, + // and false. + + // In browsers that support Object.defineProperty, use the following: + // Object.defineProperty(A, Pk, { value: mappedValue, writable: true, enumerable: true, configurable: true }); + + // For best browser support, use the following: + A[ k ] = mappedValue; + } + // d. Increase k by 1. + k++; + } + + // 9. return A + return A; + }; +} + /** - * @constructor math.type.Unit + * @constructor Unit * * @param {Number} [value] A value for the unit, like 5.2 * @param {String} [prefixUnit] A unit like "cm" or "inch" @@ -661,7 +745,7 @@ Unit.UNITS = [ ]; /** - * The build in String object of Javascript is used. + * Utility functions for Strings */ /** @@ -674,7 +758,7 @@ function isString(value) { } /** - * The build in Number object of Javascript is used. + * Utility functions for Numbers */ @@ -697,29 +781,135 @@ function isInteger(value) { } /** - * @constructor math.type.Complex + * @constructor Complex * - * @param {Number} [re] - * @param {Number} [im] + * A complex value can be constructed in three ways: + * var a = new Complex(re, im); + * var b = new Complex(str); + * var c = new Complex(); + * + * Example usage: + * var a = new Complex(3, -4); // 3 - 4i + * var b = new Complex('2 + 6i'); // 2 + 6i + * var c = new Complex(); // 0 + 0i + * var d = math.add(a, b); // 5 + 2i + * + * @param {Number | String} re A number with the real part of the complex + * value, or a string containing a complex number + * @param {Number} [im] The imaginary part of the complex value */ function Complex(re, im) { if (this.constructor != Complex) { - throw new Error('Complex constructor must be called with the new operator'); + throw new SyntaxError( + 'Complex constructor must be called with the new operator'); } - /** - * @type {Number} - */ - this.re = re || 0; + switch (arguments.length) { + case 2: + // re and im numbers provided + if (!isNumber(re) || !isNumber(im)) { + throw new TypeError( + 'Two numbers or a single string expected in Complex constructor'); + } + this.re = re; + this.im = im; + break; - /** - * @type {Number} - */ - this.im = im || 0; + case 1: + // parse string into a complex number + if (!isString(re)) { + throw new TypeError( + 'Two numbers or a single string expected in Complex constructor'); + } + + // TODO: replace by some nice regexp? + // TODO: also support a pattern like "-2.5e+3 - 7.6e-5i" + var parts = [], + part; + var separator = '+'; + var index = re.lastIndexOf(separator); + if (index == -1) { + separator = '-'; + index = re.lastIndexOf(separator); + } + + if (index != -1) { + part = trim(re.substring(0, index)); + if (part) { + parts.push(part); + } + part = trim(re.substring(index + 1)); + if (part) { + parts.push(separator + part); + } + } + else { + part = trim(re); + if (part) { + parts.push(part); + } + } + + var ok = false; + switch (parts.length) { + case 1: + part = parts[0]; + if (part[part.length - 1].toUpperCase() == 'I') { + // complex number + this.re = 0; + this.im = Number(part.substring(0, part.length - 1)); + ok = !isNaN(this.im); + } + else { + // real number + this.re = Number(part); + this.im = 0; + ok = !isNaN(this.re); + } + break; + + case 2: + part = parts[0]; + this.re = Number(parts[0]); + this.im = Number(parts[1].substring(0, parts[1].length - 1)); + ok = !isNaN(this.re) && !isNaN(this.im) && + (parts[1][parts[1].length - 1].toUpperCase() == 'I'); + break; + } + + // TODO: allow '+3-2' + + if (!ok) { + throw new SyntaxError('Invalid string "' + re + '"'); + } + + break; + + case 0: + // nul values + this.re = 0; + this.im = 0; + break; + + default: + throw new SyntaxError( + 'Wrong number of arguments in Complex constructor ' + + '(' + arguments.length + ' provided, 0, 1, or 2 expected)'); + } } math.type.Complex = Complex; +/** + * Trim a string + * http://stackoverflow.com/a/498995/1262753 + * @param str + * @return {*|void} + */ +function trim(str) { + return str.replace(/^\s+|\s+$/g, ''); +} + /** * Test whether value is a Complex value * @param {*} value @@ -838,37 +1028,56 @@ math.i = math.I; /** * Create a TypeError with message: * 'Function does not support a parameter of type '; - * @param {String} fn + * @param {String} name Function name * @param {*} value1 * @param {*} [value2] * @return {TypeError | Error} error */ -function newUnsupportedTypeError(fn, value1, value2) { +function newUnsupportedTypeError(name, value1, value2) { var msg = undefined; if (arguments.length == 2) { var t = _typeof(value1); - msg = 'Function ' + fn + ' does not support a parameter of type ' + t; + msg = 'Function ' + name + ' does not support a parameter of type ' + t; } else if (arguments.length > 2) { var types = []; for (var i = 1; i < arguments.length; i++) { types.push(_typeof(arguments[i])); } - msg = 'Function ' + fn + ' does not support a parameters of type ' + types.join(', '); + msg = 'Function ' + name + ' does not support a parameters of type ' + types.join(', '); } else { - msg = 'Unsupported parameter in function ' + fn; + msg = 'Unsupported parameter in function ' + name; } return new TypeError(msg); } +/** + * Create a syntax error with the message: + * 'Wrong number of arguments in function ( provided, - expected)' + * @param {String} name Function name + * @param {Number} count Actual argument count + * @param {Number} min Minimum required argument count + * @param {Number} [max] Maximum required argument count + */ +function newArgumentsError(name, count, min, max) { + var msg = 'Wrong number of arguments in function ' + name + + ' (' + count + ' provided, ' + + min + ((max != undefined) ? ('-' + max) : '') + ' expected)'; + return new SyntaxError(msg); +} + /** * Display documentation on a function or data type * @param {function | string | Object} subject * @return {String} documentation */ function help(subject) { + if (arguments.length != 1) { + throw newArgumentsError('help', arguments.length, 1); + } + if (subject.doc) { return generateDoc(subject.doc); } @@ -966,6 +1175,10 @@ help.doc = { * "array". */ function _typeof(x) { + if (arguments.length != 1) { + throw newArgumentsError('typeof', arguments.length, 1); + } + var type = typeof x; if (type == 'object') { @@ -1008,7 +1221,7 @@ _typeof.doc = { */ function min(args) { if (arguments.length == 0) { - throw new Error('Function sum requires one or multiple parameters (0 provided)'); + throw new Error('Function sum requires one or more parameters (0 provided)'); } // TODO: implement array support @@ -1060,7 +1273,7 @@ min.doc = { */ function max(args) { if (arguments.length == 0) { - throw new Error('Function sum requires one or multiple parameters (0 provided)'); + throw new Error('Function sum requires one or more parameters (0 provided)'); } // TODO: implement array support @@ -1112,6 +1325,10 @@ max.doc = { * @return {Unit} res */ function unit_in(x, unit) { + if (arguments.length != 2) { + throw newArgumentsError('in', arguments.length, 2); + } + if (x instanceof Unit) { // Test if unit has no value if (unit.hasValue) { @@ -1162,6 +1379,10 @@ unit_in.doc ={ * @return {Number | Complex} res */ function sin(x) { + if (arguments.length != 1) { + throw newArgumentsError('sin', arguments.length, 1); + } + if (isNumber(x)) { return Math.sin(x); } @@ -1219,6 +1440,10 @@ sin.doc = { * @return {Number | Complex} res */ function atan2(y, x) { + if (arguments.length != 2) { + throw newArgumentsError('atan2', arguments.length, 2); + } + if (isNumber(y)) { if (isNumber(x)) { return Math.atan2(y, x); @@ -1275,6 +1500,10 @@ atan2.doc = { * @return {Number | Complex} res */ function asin(x) { + if (arguments.length != 1) { + throw newArgumentsError('asin', arguments.length, 1); + } + if (isNumber(x)) { if (x >= -1 && x <= 1) { return Math.asin(x); @@ -1339,6 +1568,10 @@ asin.doc = { * @return {Number | Complex} res */ function atan(x) { + if (arguments.length != 1) { + throw newArgumentsError('atan', arguments.length, 1); + } + if (isNumber(x)) { return Math.atan(x); } @@ -1396,6 +1629,10 @@ atan.doc = { * @return {Number | Complex} res */ function cos(x) { + if (arguments.length != 1) { + throw newArgumentsError('cos', arguments.length, 1); + } + if (isNumber(x)) { return Math.cos(x); } @@ -1453,6 +1690,10 @@ cos.doc = { * @return {Number | Complex} res */ function tan(x) { + if (arguments.length != 1) { + throw newArgumentsError('tan', arguments.length, 1); + } + if (isNumber(x)) { return Math.tan(x); } @@ -1512,6 +1753,10 @@ tan.doc = { * @return {Number | Complex} res */ function acos(x) { + if (arguments.length != 1) { + throw newArgumentsError('acos', arguments.length, 1); + } + if (isNumber(x)) { if (x >= -1 && x <= 1) { return Math.acos(x); @@ -1577,6 +1822,10 @@ acos.doc = { * @return {Number | Complex | Unit} res */ function divide(x, y) { + if (arguments.length != 2) { + throw newArgumentsError('divide', arguments.length, 2); + } + if (isNumber(x)) { if (isNumber(y)) { // number / number @@ -1584,13 +1833,13 @@ function divide(x, y) { } else if (y instanceof Complex) { // number / complex - return divideComplex(new Complex(x), y); + return divideComplex(new Complex(x, 0), y); } } else if (x instanceof Complex) { if (isNumber(y)) { // complex / number - return divideComplex(x, new Complex(y)); + return divideComplex(x, new Complex(y, 0)); } else if (y instanceof Complex) { // complex / complex @@ -1659,6 +1908,10 @@ divide.doc = { * @return {Number | Complex} res */ function round(x, n) { + if (arguments.length != 1 && arguments.length != 2) { + throw newArgumentsError('round', arguments.length, 1, 2); + } + if (n == undefined) { // round (x) if (isNumber(x)) { @@ -1715,7 +1968,7 @@ math.round = round; * @return {Number} roundedValue */ function roundNumber (value, digits) { - var p = Math.pow(10, (digits != undefined) ? digits : options.precision); + var p = Math.pow(10, (digits != undefined) ? digits : math.options.precision); return Math.round(value * p) / p; } @@ -1751,6 +2004,10 @@ round.doc = { * @return {Number | Complex} res */ function fix(x) { + if (arguments.length != 1) { + throw newArgumentsError('fix', arguments.length, 1); + } + if (isNumber(x)) { return (value > 0) ? Math.floor(x) : Math.ceil(x); } @@ -1799,6 +2056,10 @@ fix.doc = { * @return {Number | Complex | Unit | String} res */ function add(x, y) { + if (arguments.length != 2) { + throw newArgumentsError('add', arguments.length, 2); + } + if (isNumber(x)) { if (isNumber(y)) { // number + number @@ -1890,6 +2151,10 @@ add.doc = { * @return {Number | Complex} res */ function exp (x) { + if (arguments.length != 1) { + throw newArgumentsError('exp', arguments.length, 1); + } + if (isNumber(x)) { return Math.exp(x); } @@ -1938,6 +2203,10 @@ exp.doc = { * @return {Number | Complex} res */ function sqrt (x) { + if (arguments.length != 1) { + throw newArgumentsError('sqrt', arguments.length, 1); + } + if (isNumber(x)) { if (x >= 0) { return Math.sqrt(x); @@ -2002,6 +2271,10 @@ sqrt.doc = { * @return {Boolean} res */ function larger(x, y) { + if (arguments.length != 2) { + throw newArgumentsError('larger', arguments.length, 2); + } + if (isNumber(x)) { if (isNumber(y)) { return x > y; @@ -2071,6 +2344,10 @@ larger.doc = { * @return {Number | Complex | Unit} res */ function unaryminus(x) { + if (arguments.length != 1) { + throw newArgumentsError('unaryminus', arguments.length, 1); + } + if (isNumber(x)) { return -x; } @@ -2122,6 +2399,10 @@ unaryminus.doc = { * @return {Boolean} res */ function smaller(x, y) { + if (arguments.length != 2) { + throw newArgumentsError('smaller', arguments.length, 2); + } + if (isNumber(x)) { if (isNumber(y)) { return x < y; @@ -2190,6 +2471,10 @@ smaller.doc = { * @return {Number | Complex} res */ function abs(x) { + if (arguments.length != 1) { + throw newArgumentsError('abs', arguments.length, 1); + } + if (isNumber(x)) { return Math.abs(x); } @@ -2229,6 +2514,10 @@ abs.doc = { * @return {Number | Complex} res */ function log(x) { + if (arguments.length != 1) { + throw newArgumentsError('log', arguments.length, 1); + } + if (isNumber(x)) { if (x >= 0) { return Math.log(x); @@ -2284,6 +2573,10 @@ log.doc = { * @return {Number | Complex} res */ function pow(x, y) { + if (arguments.length != 2) { + throw newArgumentsError('pow', arguments.length, 2); + } + if (isNumber(x)) { if (isNumber(y)) { if (isInteger(y) || x >= 0) { @@ -2291,16 +2584,16 @@ function pow(x, y) { return Math.pow(x, y); } else { - return powComplex(new Complex(x), new Complex(y)); + return powComplex(new Complex(x, 0), new Complex(y, 0)); } } else if (y instanceof Complex) { - return powComplex(new Complex(x), y); + return powComplex(new Complex(x, 0), y); } } else if (x instanceof Complex) { if (isNumber(y)) { - return powComplex(x, new Complex(y)); + return powComplex(x, new Complex(y, 0)); } else if (y instanceof Complex) { return powComplex(x, y); @@ -2358,6 +2651,10 @@ pow.doc = { * @return {Number | Complex} res */ function floor(x) { + if (arguments.length != 1) { + throw newArgumentsError('floor', arguments.length, 1); + } + if (isNumber(x)) { return Math.floor(x); } @@ -2404,6 +2701,10 @@ floor.doc = { * @return {Number | Complex} res */ function ceil(x) { + if (arguments.length != 1) { + throw newArgumentsError('ceil', arguments.length, 1); + } + if (isNumber(x)) { return Math.ceil(x); } @@ -2453,6 +2754,10 @@ ceil.doc = { function multiply(x, y) { var res; + if (arguments.length != 2) { + throw newArgumentsError('multiply', arguments.length, 2); + } + if (isNumber(x)) { if (isNumber(y)) { // number * number @@ -2460,7 +2765,7 @@ function multiply(x, y) { } else if (y instanceof Complex) { // number * complex - return multiplyComplex(new Complex(x), y); + return multiplyComplex(new Complex(x, 0), y); } else if (y instanceof Unit) { res = y.copy(); @@ -2471,7 +2776,7 @@ function multiply(x, y) { else if (x instanceof Complex) { if (isNumber(y)) { // complex * number - return multiplyComplex(x, new Complex(y)); + return multiplyComplex(x, new Complex(y, 0)); } else if (y instanceof Complex) { // complex * complex @@ -2538,6 +2843,10 @@ multiply.doc = { * @return {Number | Complex | Unit} res */ function subtract(x, y) { + if (arguments.length != 2) { + throw newArgumentsError('subtract', arguments.length, 2); + } + if (isNumber(x)) { if (isNumber(y)) { // number - number @@ -2624,6 +2933,10 @@ subtract.doc = { * @return {Number} res */ function random () { + if (arguments.length != 0) { + throw newArgumentsError('random', arguments.length, 0); + } + // TODO: implement parameter min and max return Math.random(); } @@ -2648,5 +2961,1862 @@ random.doc = { 'seealso': [] }; +/** + * Node + */ +function Node() {} + +math.parser.node.Node = Node; + +/** + * Evaluate the node + * @return {*} result + */ +Node.prototype.eval = function () { + throw new Error('Cannot evaluate a Node interface'); +}; + +/** + * Get string representation + * @return {String} + */ +Node.prototype.toString = function() { + return ''; +}; + +/** + * @constructor math.parser.node.Function + * @param {String} [name] + * @param {function} fn + * @param {Node[]} params + * @extends {Node} + */ +function Function(name, fn, params) { + this.name = name; + this.fn = fn; + this.params = params; +} + +Function.prototype = new Node(); + +math.parser.node.Function = Function; + +/** + * Check whether the Function has one or multiple parameters set. + * @return {Boolean} + */ +Function.prototype.hasParams = function () { + return (this.params != undefined && this.params.length > 0); +}; + +/** + * Evaluate the symbol + * @return {*} result + * @override + */ +Function.prototype.eval = function() { + var fn = this.fn; + if (fn === undefined) { + throw new Error('Undefined symbol ' + this.name); + } + + // evaluate the parameters + var results = this.params.map(function (param) { + return param.eval(); + }); + + // evaluate the function + return fn.apply(this, results); +}; + +/** + * Get string representation + * @return {String} str + * @override + */ +Function.prototype.toString = function() { + // variable. format the symbol like "myvar" + if (this.name && !this.params) { + return this.name; + } + + /* TODO: determine if the function is an operator + // operator. format the operation like "(2 + 3)" + if (this.fn && (this.fn instanceof mathnotepad.fn.Operator)) { + if (this.params && this.params.length == 2) { + return '(' + + this.params[0].toString() + ' ' + + this.name + ' ' + + this.params[1].toString() + ')'; + } + } + */ + + // function. format the operation like "f(2, 4.2)" + var str = this.name; + if (this.params && this.params.length) { + str += '(' + this.params.join(', ') + ')'; + } + return str; +}; + +/** + * @constructor math.parser.node.Constant + * @param {*} value + * @extends {Node} + */ +function Constant(value) { + this.value = value; +} + +Constant.prototype = new Node(); + +math.parser.node.Constant = Constant; + +/** + * Evaluate the constant + * @return {*} value + */ +Constant.prototype.eval = function () { + return this.value; +}; + +/** + * Get string representation + * @return {String} str + */ +Constant.prototype.toString = function() { + return this.value ? this.value.toString() : ''; +}; + +/** + * @constructor math.parser.node.Block + * Holds a set with nodes + * @extends {Node} + */ +function Block() { + this.params = []; + this.visible = []; +} + +Block.prototype = new Node(); + +math.parser.node.Block = Block; + +/** + * Add a parameter + * @param {Node} param + * @param {Boolean} [visible] true by default + */ +Block.prototype.add = function (param, visible) { + var index = this.params.length; + this.params[index] = param; + this.visible[index] = (visible != undefined) ? visible : true; +}; + +/** + * Evaluate the set + * @return {*[]} results + * @override + */ +Block.prototype.eval = function() { + // evaluate the parameters + var results = []; + for (var i = 0, iMax = this.params.length; i < iMax; i++) { + var result = this.params[i].eval(); + if (this.visible[i]) { + results.push(result); + } + } + + return results; +}; + +/** + * Get string representation + * @return {String} str + * @override + */ +Block.prototype.toString = function() { + var strings = []; + + for (var i = 0, iMax = this.params.length; i < iMax; i++) { + if (this.visible[i]) { + strings.push('\n ' + this.params[i].toString()); + } + } + + return '[' + strings.join(',') + '\n]'; +}; + +/** + * @constructor mathnotepad.tree.Assignment + * @param {String} name Symbol name + * @param {Node[] | undefined} params Zero or more parameters + * @param {Node} expr The expression defining the symbol + * @param {function} result placeholder for the result + */ +function Assignment(name, params, expr, result) { + this.name = name; + this.params = params; + this.expr = expr; + this.result = result; +} + +Assignment.prototype = new Node(); + +math.parser.node.Assignment = Assignment; + +/** + * Evaluate the assignment + * @return {*} result + */ +Assignment.prototype.eval = function() { + if (this.expr === undefined) { + throw new Error('Undefined symbol ' + this.name); + } + + var result; + var params = this.params; + + if (params && params.length) { + // change part of a matrix, for example "a=[]", "a(2,3)=4.5" + var paramResults = []; + this.params.forEach(function (param) { + paramResults.push(param.eval()); + }); + + var exprResult = this.expr.eval(); + + // test if definition is currently undefined + if (this.result.value == undefined) { + throw new Error('Undefined symbol ' + this.name); + } + + var prevResult = this.result.eval(); + result = prevResult.set(paramResults, exprResult); + + this.result.value = result; + } + else { + // variable definition, for example "a = 3/4" + result = this.expr.eval(); + this.result.value = result; + } + + return result; +}; + +/** + * Get string representation + * @return {String} + */ +Assignment.prototype.toString = function() { + var str = ''; + + str += this.name; + if (this.params && this.params.length) { + str += '(' + this.params.join(', ') + ')'; + } + str += ' = '; + str += this.expr.toString(); + + return str; +}; + +/** + * @constructor FunctionAssignment + * assigns a custom defined function + * + * @param {String} name Function name + * @param {String[]} variableNames Variable names + * @param {function[]} variables Links to the variables in a scope + * @param {Node} expr The function expression + * @param {function} result Link to store the result + */ +function FunctionAssignment(name, variableNames, variables, expr, result) { + this.name = name; + this.variables = variables; + + this.values = []; + for (var i = 0, iMax = this.variables.length; i < iMax; i++) { + this.values[i] = (function () { + var value = function () { + return value.value; + }; + value.value = undefined; + return value; + })(); + } + + this.def = this.createFunction(name, variableNames, variables, expr); + + this.result = result; +} + +FunctionAssignment.prototype = new Node(); + +math.parser.node.FunctionAssignment = FunctionAssignment; + +/** + * Create a function from the function assignment + * @param {String} name Function name + * @param {String[]} variableNames Variable names + * @param {function[]} values Zero or more functions returning a value + * Each function contains a parameter + * name of type String and value of + * type mathnotepad.fn.Link + * @param {Node} expr The function expression + * + */ +FunctionAssignment.prototype.createFunction = function (name, variableNames, + values, expr) { + var fn = function () { + // validate correct number of arguments + var valuesNum = values ? values.length : 0; + var argumentsNum = arguments ? arguments.length : 0; + if (valuesNum != argumentsNum) { + throw newArgumentsError(name, argumentsNum, valuesNum); + } + + // fill in all parameter values + if (valuesNum > 0) { + for (var i = 0; i < valuesNum; i++){ + values[i].value = arguments[i]; + } + } + + // evaluate the expression + return expr.eval(); + }; + + fn.toString = function() { + return name + '(' + variableNames.join(', ') + ')'; + }; + + return fn; +}; + +/** + * Evaluate the function assignment + * @return {function} result + */ +FunctionAssignment.prototype.eval = function() { + // link the variables to the values of this function assignment + var variables = this.variables, + values = this.values; + for (var i = 0, iMax = variables.length; i < iMax; i++) { + variables[i].value = values[i]; + } + + // put the definition in the result + this.result.value = this.def; + + // TODO: what to return? a neat "function y(x) defined"? + return this.def; +}; + +/** + * get string representation + * @return {String} str + */ +FunctionAssignment.prototype.toString = function() { + return this.def.toString(); +}; + +/** + * @License Apache 2.0 License + * + * @Author Jos de Jong + * @Date 2012-07-10 + */ + +/** + * Scope + * A scope stores functions. + * + * @constructor mathnotepad.Scope + * @param {Scope} [parentScope] + */ +function Scope(parentScope) { + this.parentScope = parentScope; + this.nestedScopes = undefined; + + this.symbols = {}; // the actual symbols + + // the following objects are just used to test existence. + this.defs = {}; // definitions by name (for example "a = [1, 2; 3, 4]") + this.updates = {}; // updates by name (for example "a(2, 1) = 5.2") + this.links = {}; // links by name (for example "2 * a") +} + +math.parser.node.Scope = Scope; + +/** + * Create a nested scope + * The variables in a nested scope are not accessible from the parent scope + * @return {Scope} nestedScope + */ +Scope.prototype.createNestedScope = function () { + var nestedScope = new Scope(this); + if (!this.nestedScopes) { + this.nestedScopes = []; + } + this.nestedScopes.push(nestedScope); + return nestedScope; +}; + +/** + * Clear all symbols in this scope and its nested scopes + * (parent scope will not be cleared) + */ +Scope.prototype.clear = function () { + this.symbols = {}; + this.defs = {}; + this.links = {}; + this.updates = {}; + + if (this.nestedScopes) { + var nestedScopes = this.nestedScopes; + for (var i = 0, iMax = nestedScopes.length; i < iMax; i++) { + nestedScopes[i].clear(); + } + } +}; + +/** + * create a symbol + * @param {String} name + * @return {function} symbol + * @private + */ +Scope.prototype.createSymbol = function (name) { + var symbol = this.symbols[name]; + if (!symbol) { + // get a link to the last definition + var lastDef = this.findDef(name); + + // create a new symbol + symbol = this.newSymbol(name, lastDef); + this.symbols[name] = symbol; + + } + return symbol; +}; + +/** + * Create a new symbol + * @param {String} name + * @param {*} [value] + * @return {function} symbol + * @private + */ +Scope.prototype.newSymbol = function (name, value) { + // create a new symbol + var symbol = function () { + if (!symbol.value) { + throw new Error('Undefined symbol ' + name); + } + if (typeof symbol.value == 'function') { + return symbol.value.apply(null, arguments); + } + else { + // TODO: implement subset for all types + return symbol.value; + } + }; + + symbol.value = value; + + symbol.toString = function () { + return symbol.value ? symbol.value.toString() : ''; + }; + + return symbol; +}; + +/** + * create a link to a value. + * @param {String} name + * @return {function} symbol + */ +Scope.prototype.createLink = function (name) { + var symbol = this.links[name]; + if (!symbol) { + symbol = this.createSymbol(name); + this.links[name] = symbol; + } + return symbol; +}; + +/** + * Create a variable definition + * Returns the created symbol + * @param {String} name + * @return {function} symbol + */ +Scope.prototype.createDef = function (name) { + var symbol = this.defs[name]; + if (!symbol) { + symbol = this.createSymbol(name); + this.defs[name] = symbol; + } + return symbol; +}; + +/** + * Create a variable update definition + * Returns the created symbol + * @param {String} name + * @return {function} symbol + */ +Scope.prototype.createUpdate = function (name) { + var symbol = this.updates[name]; + if (!symbol) { + symbol = this.createLink(name); + this.updates[name] = symbol; + } + return symbol; +}; + +/** + * get the link to a symbol definition or update. + * If the symbol is not found in this scope, it will be looked up in its parent + * scope. + * @param {String} name + * @return {function | undefined} symbol, or undefined when not found + */ +Scope.prototype.findDef = function (name) { + var symbol; + + // check scope + symbol = this.defs[name]; + if (symbol) { + return symbol; + } + symbol = this.updates[name]; + if (symbol) { + return symbol; + } + + // check parent scope + if (this.parentScope) { + return this.parentScope.findDef(name); + } + else { + // this is the root scope (has no parent) + + var newSymbol = this.newSymbol, + symbols = this.symbols, + defs = this.defs; + + /** + * Store a symbol in the root scope + * @param {String} name + * @param {*} value + * @return {function} symbol + */ + function put(name, value) { + var symbol = newSymbol(name, value); + symbols[name] = symbol; + defs[name] = symbol; + return symbol; + } + + // check constant (and load the constant) + if (name == 'pi') { + return put(name, math.PI); + } + if (name == 'e') { + return put(name, math.E); + } + if (name == 'i') { + return put(name, new Complex(0, 1)); + } + + // check function (and load the function), for example "sin" or "sqrt" + // search in the mathnotepad.math namespace for this symbol + var fn = math[name]; + if (fn) { + return put(name, fn); + } + + // Check if token is a unit + // Note: we do not check the upper case name, units are case sensitive! + if (Unit.isUnit(name)) { + var unit = new Unit(undefined, name); + return put(name, unit); + } + } + + return undefined; +}; + +/** + * Remove a link to a symbol + * @param {String} name + */ +Scope.prototype.removeLink = function (name) { + delete this.links[name]; +}; + +/** + * Remove a definition of a symbol + * @param {String} name + */ +Scope.prototype.removeDef = function (name) { + delete this.defs[name]; +}; + +/** + * Remove an update definition of a symbol + * @param {String} name + */ +Scope.prototype.removeUpdate = function (name) { + delete this.updates[name]; +}; + +/** + * initialize the scope and its nested scopes + * + * All functions are linked to their previous definition + * If there is no parentScope, or no definition of the func in the parent scope, + * the link will be set undefined + */ +Scope.prototype.init = function () { + var symbols = this.symbols; + var parentScope = this.parentScope; + + for (var name in symbols) { + if (symbols.hasOwnProperty(name)) { + var symbol = symbols[name]; + symbol.set(parentScope ? parentScope.findDef(name) : undefined); + } + } + + if (this.nestedScopes) { + this.nestedScopes.forEach(function (nestedScope) { + nestedScope.init(); + }); + } +}; + +/** + * Check whether this scope or any of its nested scopes contain a link to a + * symbol with given name + * @param {String} name + * @return {boolean} hasLink True if a link with given name is found + */ +Scope.prototype.hasLink = function (name) { + if (this.links[name]) { + return true; + } + + if (this.nestedScopes) { + var nestedScopes = this.nestedScopes; + for (var i = 0, iMax = nestedScopes.length; i < iMax; i++) { + if (nestedScopes[i].hasLink(name)) { + return true; + } + } + } + + return false; +}; + +/** + * Check whether this scope contains a definition of a symbol with given name + * @param {String} name + * @return {boolean} hasDef True if a definition with given name is found + */ +Scope.prototype.hasDef = function (name) { + return (this.defs[name] != undefined); +}; + +/** + * Check whether this scope contains an update definition of a symbol with + * given name + * @param {String} name + * @return {boolean} hasUpdate True if an update definition with given name is found + */ +Scope.prototype.hasUpdate = function (name) { + return (this.updates[name] != undefined); +}; + +/** + * Retrieve all undefined symbols + * @return {function[]} undefinedSymbols All symbols which are undefined + */ +Scope.prototype.getUndefinedSymbols = function () { + var symbols = this.symbols; + var undefinedSymbols = []; + for (var i in symbols) { + if (symbols.hasOwnProperty(i)) { + var symbol = symbols[i]; + if (symbol.value == undefined) { + undefinedSymbols.push(symbol); + } + } + } + + if (this.nestedScopes) { + this.nestedScopes.forEach(function (nestedScope) { + undefinedSymbols = + undefinedSymbols.concat(nestedScope.getUndefinedSymbols()); + }); + } + + return undefinedSymbols; +}; + +// TODO: do not use this.token, but a local variable var token for better speed? -> getToken() must return token. +// TODO: make all parse methods private + +/** + * @constructor math.parser.Parser + * TODO: add comments to the Parser constructor + */ +function Parser() { + // token types enumeration + this.TOKENTYPE = { + NULL : 0, + DELIMITER : 1, + NUMBER : 2, + SYMBOL : 3, + UNKNOWN : 4 + }; + + this.expr = ''; // current expression + this.index = 0; // current index in expr + this.c = ''; // current token character in expr + this.token = ''; // current token + this.token_type = this.TOKENTYPE.NULL; // type of the token + + this.scope = new Scope(); +} + +math.parser.Parser = Parser; + +/** + * Clear the scope with variables and functions + */ +Parser.prototype.clear = function () { + this.scope.clear(); +}; + +/** + * Parse an expression end return the parsed function node. + * The node can be evaluated via node.eval() + * @param {String} expr + * @param {Scope} [scope] + * @return {Node} node + * @throws {Error} + */ +Parser.prototype.parse = function (expr, scope) { + this.expr = expr || ''; + + if (!scope) { + scope = this.scope; + } + + return this.parse_start(scope); +}; + +/** + * Parse and evaluate the given expression + * @param {String} expr A string containing an expression, for example "2+3" + * @return {*} result The result, or undefined when the expression was + * empty + * @throws {Error} + */ +Parser.prototype.eval = function (expr) { + var result = undefined; + + try { + var node = this.parse(expr); + result = node.eval(); + } catch (err) { + result = err.toString ? err.toString() : err; + } + + return result; +}; + +/** + * Get the next character from the expression. + * The character is stored into the char t. + * If the end of the expression is reached, the function puts an empty + * string in t. + * @private + */ +Parser.prototype.getChar = function () { + this.index++; + this.c = this.expr.charAt(this.index); +}; + +/** + * Get the first character from the expression. + * The character is stored into the char t. + * If the end of the expression is reached, the function puts an empty + * string in t. + * @private + */ +Parser.prototype.getFirstChar = function () { + this.index = 0; + this.c = this.expr.charAt(0); +}; + +/** + * Get next token in the current string expr. + * Uses the Parser data expr, e, token, t, token_type and err + * The token and token type are available at this.token_type and this.token + * @private + */ +Parser.prototype.getToken = function () { + this.token_type = this.TOKENTYPE.NULL; + this.token = ''; + + // skip over whitespaces + while (this.c == ' ' || this.c == '\t') { // space or tab + this.getChar(); + } + + // skip comment + if (this.c == '#') { + while (this.c != '\n' && this.c != '') { + this.getChar(); + } + } + + // check for end of expression + if (this.c == '') { + // token is still empty + this.token_type = this.TOKENTYPE.DELIMITER; + return; + } + + // check for minus, comma, parentheses, quotes, newline, semicolon + if (this.c == '-' || this.c == ',' || + this.c == '(' || this.c == ')' || + this.c == '[' || this.c == ']' || + this.c == '\"' || this.c == '\n' || + this.c == ';' || this.c == ':') { + this.token_type = this.TOKENTYPE.DELIMITER; + this.token += this.c; + this.getChar(); + return; + } + + // check for operators (delimiters) + if (this.isDelimiter(this.c)) { + this.token_type = this.TOKENTYPE.DELIMITER; + while (this.isDelimiter(this.c)) { + this.token += this.c; + this.getChar(); + } + return; + } + + // check for a number + if (this.isDigitDot(this.c)) { + this.token_type = this.TOKENTYPE.NUMBER; + while (this.isDigitDot(this.c)) { + this.token += this.c; + this.getChar(); + } + + // check for scientific notation like "2.3e-4" or "1.23e50" + if (this.c == 'E' || this.c == 'e') { + this.token += this.c; + this.getChar(); + + if (this.c == '+' || this.c == '-') { + this.token += this.c; + this.getChar(); + } + + // Scientific notation MUST be followed by an exponent + if (!this.isDigit(this.c)) { + // this is no legal number, exponent is missing. + this.token_type = this.TOKENTYPE.UNKNOWN; + } + + while (this.isDigit(this.c)) { + this.token += this.c; + this.getChar(); + } + } + return; + } + // check for variables or functions + if (this.isAlpha(this.c)) { + this.token_type = this.TOKENTYPE.SYMBOL; + + while (this.isAlpha(this.c) || this.isDigit(this.c)) + { + this.token += this.c; + this.getChar(); + } + return; + } + + // something unknown is found, wrong characters -> a syntax error + this.token_type = this.TOKENTYPE.UNKNOWN; + while (this.c != '') { + this.token += this.c; + this.getChar(); + } + throw this.createSyntaxError('Syntax error in part "' + this.token + '"'); +}; + +/** + * checks if the given char c is a delimiter + * minus is not checked in this method (can be unary minus) + * @param {String} c a string with one character + * @return {Boolean} + * @private + */ +Parser.prototype.isDelimiter = function (c) { + return c == '&' || + c == '|' || + c == '<' || + c == '>' || + c == '=' || + c == '+' || + c == '/' || + c == '*' || + c == '%' || + c == '^' || + c == ',' || + c == ';' || + c == '\n' || + c == '!'; +}; + +/** + * Check if a given name is valid + * if not, an error is thrown + * @param {String} name + * @return {boolean} valid + * @private + */ +Parser.prototype.isValidSymbolName = function (name) { + for (var i = 0, iMax = name.length; i < iMax; i++) { + var c = name.charAt(i); + //var valid = (this.isAlpha(c) || (i > 0 && this.isDigit(c))); // TODO + var valid = (this.isAlpha(c)); + if (!valid) { + return false; + } + } + + return true; +}; + +/** + * checks if the given char c is a letter (upper or lower case) + * or underscore + * @param {String} c a string with one character + * @return {Boolean} + * @private + */ +Parser.prototype.isAlpha = function (c) { + return ((c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z') || + c == '_'); +}; + +/** + * checks if the given char c is a digit or dot + * @param {String} c a string with one character + * @return {Boolean} + * @private + */ +Parser.prototype.isDigitDot = function (c) { + return ((c >= '0' && c <= '9') || + c == '.'); +}; + +/** + * checks if the given char c is a digit + * @param {String} c a string with one character + * @return {Boolean} + * @private + */ +Parser.prototype.isDigit = function (c) { + return ((c >= '0' && c <= '9')); +}; + +/** + * Start of the parse levels below, in order of precedence + * @param {Scope} scope + * @return {Node} node + * @private + */ +Parser.prototype.parse_start = function (scope) { + // get the first character in expression + this.getFirstChar(); + + this.getToken(); + + var node; + if (this.token == '') { + // empty expression + node = new Constant(undefined); + } + else { + node = this.parse_block(scope); + } + + // check for garbage at the end of the expression + // an expression ends with a empty character '' and token_type DELIMITER + if (this.token != '') { + if (this.token_type == this.TOKENTYPE.DELIMITER) { + // user entered a not existing operator like "//" + + // TODO: give hints for aliases, for example with "<>" give as hint " did you mean != ?" + throw this.createError('Unknown operator ' + this.token); + } + else { + throw this.createSyntaxError('Unexpected part "' + this.token + '"'); + } + } + + return node; +}; + + +/** + * Parse assignment of ans. + * Ans is assigned when the expression itself is no variable or function + * assignment + * @param {Scope} scope + * @return {Node} node + * @private + */ +Parser.prototype.parse_ans = function (scope) { + var expression = this.parse_function_assignment(scope); + + // TODO: not so nice having to specify some special types here... + if (!(expression instanceof Assignment) + // !(expression instanceof FunctionAssignment) && // TODO + // !(expression instanceof plot) // TODO + ) { + // create a variable definition for ans + var name = 'ans'; + var params = undefined; + var link = scope.createDef(name); + return new Assignment(name, params, expression, link); + } + + return expression; +}; + + +/** + * Parse a block with expressions. Expressions can be separated by a newline + * character '\n', or by a semicolon ';'. In case of a semicolon, no output + * of the preceding line is returned. + * @param {Scope} scope + * @return {Node} node + * @private + */ +Parser.prototype.parse_block = function (scope) { + var node, block, visible; + + if (this.token != '\n' && this.token != ';' && this.token != '') { + node = this.parse_ans(scope); + } + + while (this.token == '\n' || this.token == ';') { + if (!block) { + // initialize the block + block = new Block(); + if (node) { + visible = (this.token != ';'); + block.add(node, visible); + } + } + + this.getToken(); + if (this.token != '\n' && this.token != ';' && this.token != '') { + node = this.parse_ans(scope); + + visible = (this.token != ';'); + block.add(node, visible); + } + } + + if (block) { + return block; + } + + if (!node) { + node = this.parse_ans(scope); + } + + return node; +}; + +/** + * Parse a function assignment like "function f(a,b) = a*b" + * @param {Scope} scope + * @return {Node} node + * @private + */ +Parser.prototype.parse_function_assignment = function (scope) { + // TODO: keyword 'function' must become a reserved keyword + if (this.token_type == this.TOKENTYPE.SYMBOL && this.token == 'function') { + // get function name + this.getToken(); + if (this.token_type != this.TOKENTYPE.SYMBOL) { + throw this.createSyntaxError('Function name expected'); + } + var name = this.token; + + // get parenthesis open + this.getToken(); + if (this.token != '(') { + throw this.createSyntaxError('Opening parenthesis ( expected'); + } + + // get function variables + var functionScope = scope.createNestedScope(); + var variableNames = []; + var variables = []; + while (true) { + this.getToken(); + if (this.token_type == this.TOKENTYPE.SYMBOL) { + // store parameter + var variableName = this.token; + var variable = functionScope.createDef(variableName); + variableNames.push(variableName); + variables.push(variable); + } + else { + throw this.createSyntaxError('Variable name expected'); + } + + this.getToken(); + if (this.token == ',') { + // ok, nothing to do, read next variable + } + else if (this.token == ')') { + // end of variable list encountered. break loop + break; + } + else { + throw this.createSyntaxError('Comma , or closing parenthesis ) expected"'); + } + } + + this.getToken(); + if (this.token != '=') { + throw this.createSyntaxError('Equal sign = expected'); + } + + // parse the expression, with the correct function scope + this.getToken(); + var expression = this.parse_range(functionScope); + var result = scope.createDef(name); + + return new FunctionAssignment(name, variableNames, variables, + expression, result); + } + + return this.parse_assignment(scope); +}; + +/** + * Assignment of a variable, can be a variable like "a=2.3" or a updating an + * existing variable like "matrix(2,3:5)=[6,7,8]" + * @param {Scope} scope + * @return {Node} node + * @private + */ +Parser.prototype.parse_assignment = function (scope) { + var linkExisted = false; + if (this.token_type == this.TOKENTYPE.SYMBOL) { + linkExisted = scope.hasLink(this.token); + } + + var node = this.parse_range(scope); + + if (this.token == '=') { + if (!(node instanceof Function)) { + throw this.createSyntaxError('Variable expected at the left hand side ' + + 'of assignment operator ='); + } + var name = node.name; + var params = node.params; + + if (!linkExisted) { + // we parsed the assignment as if it where an expression instead, + // therefore, a link was created to the symbol. This link must + // be cleaned up again, and only if it wasn't existing before + scope.removeLink(name); + } + + // parse the expression, with the correct function scope + this.getToken(); + var expression = this.parse_range(scope); + var link = node.hasParams() ? scope.createUpdate(name) : scope.createDef(name); + return new Assignment(name, params, expression, link); + } + + return node; +}; + +/** + * parse range, "start:end" or "start:step:end" + * @param {Scope} scope + * @return {Node} node + * @private + */ +Parser.prototype.parse_range = function (scope) { + var node = this.parse_conditions(scope); + + /* TODO: implement range + if (this.token == ':') { + var params = [node]; + + while (this.token == ':') { + this.getToken(); + params.push(this.parse_conditions(scope)); + } + + var fn = range; + var name = ':'; + node = new Function(name, fn, params); + } + */ + + return node; +}; + +/** + * conditions like and, or, in + * @param {Scope} scope + * @return {Node} node + * @private + */ +Parser.prototype.parse_conditions = function (scope) { + var node = this.parse_bitwise_conditions(scope); + + // TODO: precedence of And above Or? + var operators = { + 'in' : 'in' + /* TODO: implement conditions + 'and' : 'and', + '&&' : 'and', + 'or': 'or', + '||': 'or', + 'xor': 'xor' + */ + }; + while (operators[this.token] !== undefined) { + // TODO: with all operators: only load one instance of the operator, use the scope + var name = this.token; + var fn = math[operators[name]]; + + this.getToken(); + var params = [node, this.parse_bitwise_conditions(scope)]; + node = new Function(name, fn, params); + } + + return node; +}; + +/** + * conditional operators and bitshift + * @param {Scope} scope + * @return {Node} node + * @private + */ +Parser.prototype.parse_bitwise_conditions = function (scope) { + var node = this.parse_comparison(scope); + + /* TODO: implement bitwise conditions + var operators = { + '&' : 'bitwiseand', + '|' : 'bitwiseor', + // todo: bitwise xor? + '<<': 'bitshiftleft', + '>>': 'bitshiftright' + }; + while (operators[this.token] !== undefined) { + var name = this.token; + var fn = math[operators[name]]; + + this.getToken(); + var params = [node, this.parse_comparison()]; + node = new Function(name, fn, params); + } + */ + + return node; +}; + +/** + * comparison operators + * @param {Scope} scope + * @return {Node} node + * @private + */ +Parser.prototype.parse_comparison = function (scope) { + var node = this.parse_addsubtract(scope); + + var operators = { + '==': 'equal', + '!=': 'unequal', + '<': 'smaller', + '>': 'larger', + '<=': 'smallereq', + '>=': 'largereq' + }; + while (operators[this.token] !== undefined) { + var name = this.token; + var fn = math[operators[name]]; + + this.getToken(); + var params = [node, this.parse_addsubtract(scope)]; + node = new Function(name, fn, params); + } + + return node; +}; + +/** + * add or subtract + * @param {Scope} scope + * @return {Node} node + * @private + */ +Parser.prototype.parse_addsubtract = function (scope) { + var node = this.parse_multiplydivide(scope); + + var operators = { + '+': 'add', + '-': 'subtract' + }; + while (operators[this.token] !== undefined) { + var name = this.token; + var fn = math[operators[name]]; + + this.getToken(); + var params = [node, this.parse_multiplydivide(scope)]; + node = new Function(name, fn, params); + } + + return node; +}; + + +/** + * multiply, divide, modulus + * @param {Scope} scope + * @return {Node} node + * @private + */ +Parser.prototype.parse_multiplydivide = function (scope) { + var node = this.parse_pow(scope); + + var operators = { + '*': 'multiply', + '/': 'divide', + '%': 'mod', + 'mod': 'mod' + }; + while (operators[this.token] !== undefined) { + var name = this.token; + var fn = math[operators[name]]; + + this.getToken(); + var params = [node, this.parse_pow(scope)]; + node = new Function(name, fn, params); + } + + return node; +}; + +/** + * power + * @param {Scope} scope + * @return {Node} node + * @private + */ +Parser.prototype.parse_pow = function (scope) { + var node = this.parse_factorial(scope); + + while (this.token == '^') { + var name = this.token; + var fn = pow; + this.getToken(); + var params = [node, this.parse_factorial(scope)]; + + node = new Function(name, fn, params); + } + + return node; +}; + +/** + * Factorial + * @param {Scope} scope + * @return {Node} node + * @private + */ +Parser.prototype.parse_factorial = function (scope) { + var node = this.parse_unaryminus(scope); + + while (this.token == '!') { + var name = this.token; + var fn = factorial; + this.getToken(); + var params = [node]; + + node = new Function(name, fn, params); + } + + return node; +}; + +/** + * Unary minus + * @param {Scope} scope + * @return {Node} node + * @private + */ +Parser.prototype.parse_unaryminus = function (scope) { + if (this.token == '-') { + var name = this.token; + var fn = unaryminus; + this.getToken(); + var params = [this.parse_plot(scope)]; + + return new Function(name, fn, params); + } + + return this.parse_plot(scope); +}; + +/** + * parse plot + * @param {Scope} scope + * @return {Node} node + * @private + */ +Parser.prototype.parse_plot = function (scope) { + /* TODO: implement plot + if (this.token_type == this.TOKENTYPE.SYMBOL && + this.token == 'plot') { + this.getToken(); + + // parse the parentheses and parameters of the plot + // the parameters are something like: plot(sin(x), cos(x), x) + var functions = []; + if (this.token == '(') { + var plotScope = scope.createNestedScope(); + + this.getToken(); + functions.push(this.parse_range(plotScope)); + + // parse a list with parameters + while (this.token == ',') { + this.getToken(); + functions.push(this.parse_range(plotScope)); + } + + if (this.token != ')') { + throw this.createSyntaxError('Parenthesis ) missing'); + } + this.getToken(); + } + + // check what the variable of the functions is. + var variable = undefined; + var lastFunction = functions[functions.length - 1]; + if (lastFunction) { + // if the last function is a variable, remove it from the functions list + // and use its variable func + var lastIsSymbol = (lastFunction instanceof Function && + !lastFunction.hasParams()); + if (lastIsSymbol) { + functions.pop(); + variable = lastFunction.fn; + } + } + return new plot(functions, variable, plotScope); + } + */ + + return this.parse_symbol(scope); +}; + +/** + * parse symbols: functions, variables, constants, units + * @param {Scope} scope + * @return {Node} node + * @private + */ +Parser.prototype.parse_symbol = function (scope) { + if (this.token_type == this.TOKENTYPE.SYMBOL) { + var name = this.token; + + this.getToken(); + + var link = scope.createLink(name); + var arguments = this.parse_arguments(scope); // TODO: not so nice to "misuse" creating a Function + var symbol = new Function(name, link, arguments); + + /* TODO: parse arguments + // parse arguments + while (this.token == '(') { + symbol = this.parse_arguments(scope, symbol); + } + */ + return symbol; + } + + return this.parse_string(scope); +}; + +/** + * parse symbol parameters + * @param {Scope} scope + * @return {Node[]} arguments + * @private + */ +Parser.prototype.parse_arguments = function (scope) { + var arguments = []; + if (this.token == '(') { + // TODO: in case of Plot, create a new scope. + + this.getToken(); + arguments.push(this.parse_range(scope)); + + // parse a list with parameters + while (this.token == ',') { + this.getToken(); + arguments.push(this.parse_range(scope)); + } + + if (this.token != ')') { + throw this.createSyntaxError('Parenthesis ) missing'); + } + this.getToken(); + } + + return arguments; +}; + +/** + * parse a string. + * A string is enclosed by double quotes + * @param {Scope} scope + * @return {Node} node + * @private + */ +Parser.prototype.parse_string = function (scope) { + if (this.token == '"') { + // string "..." + var str = ''; + var tPrev = ''; + while (this.c != '' && (this.c != '\"' || tPrev == '\\')) { // also handle escape character + str += this.c; + tPrev = this.c; + this.getChar(); + } + + this.getToken(); + if (this.token != '"') { + throw this.createSyntaxError('End of string " missing'); + } + this.getToken(); + + var res = new Constant(str); + + /* TODO: implement string with arguments + // parse arguments + while (this.token == '(') { + res = this.parse_arguments(scope, res); + } + */ + + return res; + } + + return this.parse_matrix(scope); +}; + +/** + * parse the matrix + * @param {Scope} scope + * @return {Node} A MatrixNode + * @private + */ +Parser.prototype.parse_matrix = function (scope) { + /* TODO: implement matrix + if (this.token == '[') { + // matrix [...] + var matrix; + + // skip newlines + this.getToken(); + while (this.token == '\n') { + this.getToken(); + } + + // check if this is an empty matrix "[ ]" + if (this.token != ']') { + // this is a non-empty matrix + var params = []; + var r = 0, c = 0; + + params[0] = [this.parse_range(scope)]; + + // the columns in the matrix are separated by commas, and the rows by dot-comma's + while (this.token == ',' || this.token == ';') { + if (this.token == ',') { + c++; + } + else { + r++; + c = 0; + params[r] = []; + } + + // skip newlines + this.getToken(); + while (this.token == '\n') { + this.getToken(); + } + + params[r][c] = this.parse_range(scope); + + // skip newlines + while (this.token == '\n') { + this.getToken(); + } + } + + var rows = params.length; + var cols = (params.length > 0) ? params[0].length : 0; + + // check if the number of columns matches in all rows + for (r = 1; r < rows; r++) { + if (params[r].length != cols) { + throw this.createError('Number of columns must match ' + + '(' + params[r].length + ' != ' + cols + ')'); + } + } + + if (this.token != ']') { + throw this.createSyntaxError('End of matrix ] missing'); + } + + this.getToken(); + matrix = new MatrixNode(params); + } + else { + // this is an empty matrix "[ ]" + this.getToken(); + matrix = new MatrixNode(); + } + + // parse arguments + while (this.token == '(') { + matrix = this.parse_arguments(scope, matrix); + } + + return matrix; + } + */ + + return this.parse_number(scope); +}; + +/** + * parse a number + * @param {Scope} scope + * @return {Node} node + * @private + */ +Parser.prototype.parse_number = function (scope) { + if (this.token_type == this.TOKENTYPE.NUMBER) { + // this is a number + var number; + if (this.token == '.') { + number = 0.0; + } else { + number = Number(this.token); + } + this.getToken(); + + /* TODO: implicit multiplication? + // TODO: how to calculate a=3; 2/2a ? is this (2/2)*a or 2/(2*a) ? + // check for implicit multiplication + if (token_type == TOKENTYPE.VARIABLE) { + node = multiply(node, parse_pow()); + } + //*/ + + var value; + if (this.token_type == this.TOKENTYPE.SYMBOL) { + if (this.token == 'i' || this.token == 'I') { + value = new Complex(0, number); + this.getToken(); + return new Constant(value); + } + + if (Unit.isUnit(this.token)) { + value = new Unit(number, this.token); + this.getToken(); + return new Constant(value); + } + + throw this.createTypeError('Unknown unit "' + this.token + '"'); + } + + // just a regular number + var res = new Constant(number); + + /* TODO: implement number with arguments + // parse arguments + while (this.token == '(') { + res = this.parse_arguments(scope, res); + } + */ + + return res; + } + + return this.parse_parentheses(scope); +}; + +/** + * parentheses + * @param {Scope} scope + * @return {Node} res + * @private + */ +Parser.prototype.parse_parentheses = function (scope) { + // check if it is a parenthesized expression + if (this.token == '(') { + // parentheses (...) + this.getToken(); + var res = this.parse_range(scope); // start again + + if (this.token != ')') { + throw this.createSyntaxError('Parenthesis ) expected'); + } + this.getToken(); + + /* TODO: implicit multiplication? + // TODO: how to calculate a=3; 2/2a ? is this (2/2)*a or 2/(2*a) ? + // check for implicit multiplication + if (token_type == TOKENTYPE.VARIABLE) { + node = multiply(node, parse_pow()); + } + //*/ + + /* TODO: parse parentheses with arguments + // parse arguments + while (this.token == '(') { + res = this.parse_arguments(scope, res); + } + */ + + return res; + } + + return this.parse_end(scope); +}; + +/** + * Evaluated when the expression is not yet ended but expected to end + * @param {Scope} scope + * @return {Node} res + * @private + */ +Parser.prototype.parse_end = function (scope) { + if (this.token == '') { + // syntax error or unexpected end of expression + throw this.createSyntaxError('Unexpected end of expression'); + } else { + throw this.createSyntaxError('Value expected'); + } +}; + +/** + * Shortcut for getting the current row value (one based) + * Returns the line of the currently handled expression + * @private + */ +Parser.prototype.row = function () { + // TODO: also register row number during parsing + return undefined; +}; + +/** + * Shortcut for getting the current col value (one based) + * Returns the column (position) where the last token starts + * @private + */ +Parser.prototype.col = function () { + return this.index - this.token.length + 1; +}; + + +/** + * Build up an error message + * @param {String} message + * @return {String} message with row and column information + * @private + */ +Parser.prototype.createErrorMessage = function(message) { + var row = this.row(); + var col = this.col(); + if (row === undefined) { + if (col === undefined) { + return message; + } else { + return message + ' (col ' + col + ')'; + } + } else { + return message + ' (ln ' + row + ', col ' + col + ')'; + } +}; + +/** + * Create an error + * @param {String} message + * @return {SyntaxError} instantiated error + * @private + */ +Parser.prototype.createSyntaxError = function(message) { + return new SyntaxError(this.createErrorMessage(message)); +}; + +/** + * Create an error + * @param {String} message + * @return {TypeError} instantiated error + * @private + */ +Parser.prototype.createTypeError = function(message) { + return new TypeError(this.createErrorMessage(message)); +}; + +/** + * Create an error + * @param {String} message + * @return {Error} instantiated error + * @private + */ +Parser.prototype.createError = function(message) { + return new Error(this.createErrorMessage(message)); +}; + })(); diff --git a/math.min.js b/math.min.js index 341a88db7..d482a7998 100644 --- a/math.min.js +++ b/math.min.js @@ -1,11 +1,22 @@ /** * math.js - * An extended math library. Includes a parser, real and complex values, units, - * matrices, strings, and a large set of functions and constants. * https://github.com/josdejong/mathjs * - * @version @@version - * @date @@date + * Math.js is an extensive math library for JavaScript and Node.js, + * compatible with JavaScript's built-in Math library. + * + * Features: + * - A flexible expression parser + * - Support for numbers, complex values, units, strings, arrays*, + * and matrices* + * - A large set of built-in functions and constants + * - Easily extensible with new functions and constants + * - Powerful and easy to use + * + * * Note: arrays and matrices are to be implemented. + * + * @version 2013-02-23 + * @date 0.2.0-SNAPSHOT * * @license * Copyright (C) 2013 Jos de Jong @@ -22,4 +33,5 @@ * License for the specific language governing permissions and limitations under * the License. */ -undefined(function(){function e(a,i){if(this.constructor!=e)throw Error("Unit constructor must be called with the new operator");this.value=1,this.unit=e.UNIT_NONE,this.prefix=e.PREFIX_NONE,this.hasUnit=!1,this.hasValue=!1,this.fixPrefix=!1,this._init(a,i)}function a(e){return e instanceof String||"string"==typeof e}function i(e){return e instanceof Number||"number"==typeof e}function n(e){return e==Math.round(e)}function t(e,a){if(this.constructor!=t)throw Error("Complex constructor must be called with the new operator");this.re=e||0,this.im=a||0}function r(e,a){var i=void 0;if(2==arguments.length){var n=f(a);i="Function "+e+" does not support a parameter of type "+n}else if(arguments.length>2){for(var t=[],r=1;arguments.length>r;r++)t.push(f(arguments[r]));i="Function "+e+" does not support a parameters of type "+t.join(", ")}else i="Unsupported parameter in function "+e;return new TypeError(i)}function s(e){if(e.doc)return o(e.doc);if(e.constructor.doc)return o(e.constructor.doc);if(a(e)){var i=H[e];if(i&&i.doc)return o(i.doc);for(var n in H.type)if(H.type.hasOwnProperty(n)&&e.toLowerCase()==n.toLowerCase()&&H.type[n].doc)return o(H.type[n].doc)}return e instanceof Object&&e.name?'No documentation found on subject "'+e.name+'"':e instanceof Object&&e.constructor.name?'No documentation found on subject "'+e.constructor.name+'"':'No documentation found on subject "'+e+'"'}function o(e){var a="";if(e.name&&(a+="NAME\n"+e.name+"\n\n"),e.category&&(a+="CATEGORY\n"+e.category+"\n\n"),e.syntax&&(a+="SYNTAX\n"+e.syntax.join("\n")+"\n\n"),e.examples){a+="EXAMPLES\n";for(var i=0;e.examples.length>i;i++)a+=e.examples[i]+"\n";a+="\n"}return e.seealso&&(a+="SEE ALSO\n"+e.seealso.join(", ")+"\n"),a}function f(e){var a=typeof e;if("object"==a){if(null==e)return"null";if(e&&e.constructor&&e.constructor.name)return e.constructor.name.toLowerCase()}return a}function u(){if(0==arguments.length)throw Error("Function sum requires one or multiple parameters (0 provided)");for(var e=arguments[0],a=1,i=arguments.length;i>a;a++){var n=arguments[a];L(n,e)&&(e=n)}return c}function c(){if(0==arguments.length)throw Error("Function sum requires one or multiple parameters (0 provided)");for(var e=arguments[0],a=1,i=arguments.length;i>a;a++){var n=arguments[a];S(n,e)&&(e=n)}return e}function l(a,i){if(a instanceof e){if(i.hasValue)throw Error("Cannot convert to a unit with a value");if(!i.hasUnit)throw Error("Unit expected on the right hand side of function in");var n=i.copy();return n.value=a.value,n.fixPrefix=!0,n}throw r("in",a)}function m(a){if(i(a))return Math.sin(a);if(a instanceof t)return new t(.5*Math.sin(a.re)*(Math.exp(-a.im)+Math.exp(a.im)),.5*Math.cos(a.re)*(Math.exp(a.im)-Math.exp(-a.im)));if(a instanceof e){if(!a.hasBase(e.BASE_UNITS.ANGLE))throw new TypeError("Unit in function cos is no angle");return Math.sin(a.value)}throw r("sin",a)}function p(e,a){if(i(e)){if(i(a))return Math.atan2(e,a);if(a instanceof t)return Math.atan2(e,a.re)}else if(e instanceof t){if(i(a))return Math.atan2(e.re,a);if(a instanceof t)return Math.atan2(e.re,a.re)}throw r("atan2",e,a)}function h(e){if(i(e))return e>=-1&&1>=e?Math.asin(e):h(new t(e,0));if(e instanceof t){var a=e.re,n=e.im,s=new t(n*n-a*a+1,-2*a*n),o=T(s),f=new t(o.re-n,o.im+a),u=R(f);return new t(u.im,-u.re)}throw r("asin",e)}function v(e){if(i(e))return Math.atan(e);if(e instanceof t){var a=e.re,n=e.im,s=a*a+(1-n)*(1-n),o=new t((1-n*n-a*a)/s,-2*a/s),f=R(o);return new t(-.5*f.im,.5*f.re)}throw r("atan",e)}function x(a){if(i(a))return Math.cos(a);if(a instanceof t)return new t(.5*Math.cos(a.re)*(Math.exp(-a.im)+Math.exp(a.im)),.5*Math.sin(a.re)*(Math.exp(-a.im)-Math.exp(a.im)));if(a instanceof e){if(!a.hasBase(e.BASE_UNITS.ANGLE))throw new TypeError("Unit in function cos is no angle");return Math.cos(a.value)}throw r("cos",a)}function d(a){if(i(a))return Math.tan(a);if(a instanceof t){var n=Math.exp(-4*a.im)+2*Math.exp(-2*a.im)*Math.cos(2*a.re)+1;return new t(2*Math.exp(-2*a.im)*Math.sin(2*a.re)/n,(1-Math.exp(-4*a.im))/n)}if(a instanceof e){if(!a.hasBase(e.BASE_UNITS.ANGLE))throw new TypeError("Unit in function tan is no angle");return Math.tan(a.value)}throw r("tan",a)}function N(e){if(i(e))return e>=-1&&1>=e?Math.acos(e):N(new t(e,0));if(e instanceof t){var a=new t(e.im*e.im-e.re*e.re+1,-2*e.re*e.im),n=T(a),s=new t(n.re-e.im,n.im+e.re),o=R(s);return new t(1.5707963267948966-o.im,o.re)}throw r("acos",e)}function E(a,n){if(i(a)){if(i(n))return a/n;if(n instanceof t)return b(new t(a),n)}else if(a instanceof t){if(i(n))return b(a,new t(n));if(n instanceof t)return b(a,n)}else if(a instanceof e&&i(n)){var s=a.copy();return s.value/=n,s}throw r("divide",a,n)}function b(e,a){var i=a.re*a.re+a.im*a.im;return new t((e.re*a.re+e.im*a.im)/i,(e.im*a.re-e.re*a.im)/i)}function y(e,a){if(void 0==a){if(i(e))return Math.round(e);if(e instanceof t)return new t(Math.round(e.re),Math.round(e.im));throw r("round",e)}if(!i(a))throw new TypeError("Number of digits in function round must be an integer");if(a!==Math.round(a))throw new TypeError("Number of digits in function round must be integer");if(0>a||a>9)throw Error("Number of digits in function round must be in te range of 0-9");if(i(e))return M(e,a);if(e instanceof t)return new t(M(e.re,a),M(e.im,a));throw r("round",e,a)}function M(e,a){var i=Math.pow(10,void 0!=a?a:F.precision);return Math.round(e*i)/i}function O(e){if(i(e))return value>0?Math.floor(e):Math.ceil(e);if(e instanceof t)return new t(e.re>0?Math.floor(e.re):Math.ceil(e.re),e.im>0?Math.floor(e.im):Math.ceil(e.im));throw r("fix",e)}function g(n,s){if(i(n)){if(i(s))return n+s;if(s instanceof t)return new t(n+s.re,s.im)}else if(n instanceof t){if(i(s))return new t(n.re+s,n.im);if(s instanceof t)return new t(n.re+s.re,n.im+s.im)}else if(n instanceof e&&s instanceof e){if(!n.equalBase(s))throw Error("Units do not match");if(!n.hasValue)throw Error("Unit on left hand side of operator + has no value");if(!s.hasValue)throw Error("Unit on right hand side of operator + has no value");var o=n.copy();return o.value+=s.value,o.fixPrefix=!1,o}if(a(n)||a(s))return n+s;throw r("add",n,s)}function w(e){if(i(e))return Math.exp(e);if(e instanceof t){var a=Math.exp(e.re);return new t(a*Math.cos(e.im),a*Math.sin(e.im))}throw r("exp",e)}function T(e){if(i(e))return e>=0?Math.sqrt(e):T(new t(e,0));if(e instanceof t){var a=Math.sqrt(e.re*e.re+e.im*e.im);return e.im>=0?new t(.5*Math.sqrt(2*(a+e.re)),.5*Math.sqrt(2*(a-e.re))):new t(.5*Math.sqrt(2*(a+e.re)),-.5*Math.sqrt(2*(a-e.re)))}throw r("sqrt",e)}function S(n,s){if(i(n)){if(i(s))return n>s;if(s instanceof t)return n>A(s)}if(n instanceof t){if(i(s))return A(n)>s;if(s instanceof t)return A(n)>A(s)}if(n instanceof e&&s instanceof e){if(!n.equalBase(s))throw Error("Cannot compare units with different base");return n.value>s.value}if(a(n)||a(s))return n>s;throw r("larger",n,s)}function U(a){if(i(a))return-a;if(a instanceof t)return new t(-a.re,-a.im);if(a instanceof e){var n=a.copy();return n.value=-a.value,n}throw r("unaryminus",a)}function L(n,s){if(i(n)){if(i(s))return s>n;if(s instanceof t)return A(s)>n}if(n instanceof t){if(i(s))return s>A(n);if(s instanceof t)return A(n)n;throw r("smaller",n,s)}function A(e){if(i(e))return Math.abs(e);if(e instanceof t)return Math.sqrt(e.re*e.re+e.im*e.im);throw r("abs",e)}function R(e){if(i(e))return e>=0?Math.log(e):R(new t(e,0));if(e instanceof t)return new t(Math.log(Math.sqrt(e.re*e.re+e.im*e.im)),Math.atan2(e.im,e.re));throw r("log",e)}function I(e,a){if(i(e)){if(i(a))return n(a)||e>=0?Math.pow(e,a):q(new t(e),new t(a));if(a instanceof t)return q(new t(e),a)}else if(e instanceof t){if(i(a))return q(e,new t(a));if(a instanceof t)return q(e,a)}throw r("pow",e,a)}function q(e,a){var i=R(e),n=_(i,a);return w(n)}function G(e){if(i(e))return Math.floor(e);if(e instanceof t)return new t(Math.floor(e.re),Math.floor(e.im));throw r("floor",e)}function C(e){if(i(e))return Math.ceil(e);if(e instanceof t)return new t(Math.ceil(e.re),Math.ceil(e.im));throw r("ceil",e)}function _(a,n){var s;if(i(a)){if(i(n))return a*n;if(n instanceof t)return V(new t(a),n);if(n instanceof e)return s=n.copy(),s.value*=a,s}else if(a instanceof t){if(i(n))return V(a,new t(n));if(n instanceof t)return V(a,n)}else if(a instanceof e&&i(n))return s=a.copy(),s.value*=n,s;throw r("multiply",a,n)}function V(e,a){return new t(e.re*a.re-e.im*a.im,e.re*a.im+e.im*a.re)}function P(a,n){if(i(a)){if(i(n))return a-n;if(n instanceof t)return new t(a-n.re,n.im)}else if(a instanceof t){if(i(n))return new t(a.re-n,a.im);if(n instanceof t)return new t(a.re-n.re,a.im-n.im)}else if(a instanceof e&&n instanceof e){if(!a.equalBase(n))throw Error("Units do not match");if(!a.hasValue)throw Error("Unit on left hand side of operator - has no value");if(!n.hasValue)throw Error("Unit on right hand side of operator - has no value");var s=a.copy();return s.value-=n.value,s.fixPrefix=!1,s}throw r("subtract",a,n)}function B(){return Math.random()}var H={type:{},parser:{}};"undefined"!=typeof module&&module.exports!==void 0&&(module.exports=H),"undefined"!=typeof exports&&(exports=H),"undefined"!=typeof require&&"undefined"!=typeof define&&define(function(){return H}),"undefined"!=typeof window&&(window.math=H);var F={precision:10};H.options=F;var k={};if(k.format=function(e,a){if(1/0===e)return"Infinity";if(e===-1/0)return"-Infinity";if(0/0===e)return"NaN";var i=Math.abs(e);if(i>1e-4&&1e6>i||0==i)return M(e,a)+"";var n=Math.round(Math.log(i)/Math.LN10),t=e/Math.pow(10,n);return M(t,a)+"E"+n},k.randomUUID=function(){var e=function(){return Math.floor(65536*Math.random()).toString(16)};return e()+e()+"-"+e()+"-"+e()+"-"+e()+"-"+e()+e()+e()},!Array.prototype.indexOf){Array.prototype.indexOf=function(e){for(var a=0;this.length>a;a++)if(this[a]==e)return a;return-1};try{console.log("Warning: Ancient browser detected. Please update your browser")}catch(z){}}Array.prototype.forEach||(Array.prototype.forEach=function(e,a){for(var i=0,n=this.length;n>i;++i)e.call(a||this,this[i],i,this)}),H.type.Unit=e,e.prototype.copy=function(){var a=new e;for(var i in this)this.hasOwnProperty(i)&&(a[i]=this[i]);return a},e.endsWith=function(e,a){var i=e.length-a.length,n=e.length;return e.substring(i,n)===a},e.prototype._init=function(a,i){if(void 0!==i){for(var n=e.UNITS,t=!1,r=0,s=n.length;s>r;r++){var o=n[r];if(e.endsWith(i,o.name)){var f=i.length-o.name.length,u=i.substring(0,f),c=o.prefixes[u];if(void 0!==c){this.unit=o,this.prefix=c,this.hasUnit=!0,t=!0;break}}}if(!t)throw Error('String "'+i+'" is no unit')}void 0!==a?(this.value=this._normalize(a),this.hasValue=!0):this.value=this._normalize(1)},e.prototype._normalize=function(e){return(e+this.unit.offset)*this.unit.value*this.prefix.value},e.prototype._unnormalize=function(e,a){return void 0===a?e/this.unit.value/this.prefix.value-this.unit.offset:e/this.unit.value/a-this.unit.offset},e.isUnit=function(a){for(var i=e.UNITS,n=i.length,t=0;n>t;t++){var r=i[t];if(e.endsWith(a,r.name)){var s=a.length-r.name.length;if(0==s)return!0;var o=a.substring(0,s),f=r.prefixes[o];if(void 0!==f)return!0}}return!1},e.prototype.hasBase=function(e){return void 0===this.unit.base?void 0===e:this.unit.base===e},e.prototype.equalBase=function(e){return this.unit.base===e.unit.base},e.prototype.equals=function(e){return this.equalBase(e)&&this.value==e.value},e.prototype.toString=function(){var a;if(this.fixPrefix)return a=this._unnormalize(this.value),k.format(a)+" "+this.prefix.name+this.unit.name;var i=e.PREFIX_NONE,n=Math.abs(Math.log(this.value/i.value)/Math.LN10-1.5),t=this.unit.prefixes;for(var r in t)if(t.hasOwnProperty(r)){var s=t[r];if(s.scientific){var o=Math.abs(Math.log(this.value/s.value)/Math.LN10-1.5);n>o&&(i=s,n=o)}}return a=this._unnormalize(this.value,i.value),k.format(a)+" "+i.name+this.unit.name},e.PREFIXES={NONE:{"":{name:"",value:1,scientific:!0}},SHORT:{"":{name:"",value:1,scientific:!0},da:{name:"da",value:10,scientific:!1},h:{name:"h",value:100,scientific:!1},k:{name:"k",value:1e3,scientific:!0},M:{name:"M",value:1e6,scientific:!0},G:{name:"G",value:1e9,scientific:!0},T:{name:"T",value:1e12,scientific:!0},P:{name:"P",value:1e15,scientific:!0},E:{name:"E",value:1e18,scientific:!0},Z:{name:"Z",value:1e21,scientific:!0},Y:{name:"Y",value:1e24,scientific:!0},d:{name:"d",value:.1,scientific:!1},c:{name:"c",value:.01,scientific:!1},m:{name:"m",value:.001,scientific:!0},u:{name:"u",value:1e-6,scientific:!0},n:{name:"n",value:1e-9,scientific:!0},p:{name:"p",value:1e-12,scientific:!0},f:{name:"f",value:1e-15,scientific:!0},a:{name:"a",value:1e-18,scientific:!0},z:{name:"z",value:1e-21,scientific:!0},y:{name:"y",value:1e-24,scientific:!0}},LONG:{"":{name:"",value:1,scientific:!0},deca:{name:"deca",value:10,scientific:!1},hecto:{name:"hecto",value:100,scientific:!1},kilo:{name:"kilo",value:1e3,scientific:!0},mega:{name:"mega",value:1e6,scientific:!0},giga:{name:"giga",value:1e9,scientific:!0},tera:{name:"tera",value:1e12,scientific:!0},peta:{name:"peta",value:1e15,scientific:!0},exa:{name:"exa",value:1e18,scientific:!0},zetta:{name:"zetta",value:1e21,scientific:!0},yotta:{name:"yotta",value:1e24,scientific:!0},deci:{name:"deci",value:.1,scientific:!1},centi:{name:"centi",value:.01,scientific:!1},milli:{name:"milli",value:.001,scientific:!0},micro:{name:"micro",value:1e-6,scientific:!0},nano:{name:"nano",value:1e-9,scientific:!0},pico:{name:"pico",value:1e-12,scientific:!0},femto:{name:"femto",value:1e-15,scientific:!0},atto:{name:"atto",value:1e-18,scientific:!0},zepto:{name:"zepto",value:1e-21,scientific:!0},yocto:{name:"yocto",value:1e-24,scientific:!0}},BINARY_SHORT:{"":{name:"",value:1,scientific:!0},k:{name:"k",value:1024,scientific:!0},M:{name:"M",value:Math.pow(1024,2),scientific:!0},G:{name:"G",value:Math.pow(1024,3),scientific:!0},T:{name:"T",value:Math.pow(1024,4),scientific:!0},P:{name:"P",value:Math.pow(1024,5),scientific:!0},E:{name:"E",value:Math.pow(1024,6),scientific:!0},Z:{name:"Z",value:Math.pow(1024,7),scientific:!0},Y:{name:"Y",value:Math.pow(1024,8),scientific:!0},Ki:{name:"Ki",value:1024,scientific:!0},Mi:{name:"Mi",value:Math.pow(1024,2),scientific:!0},Gi:{name:"Gi",value:Math.pow(1024,3),scientific:!0},Ti:{name:"Ti",value:Math.pow(1024,4),scientific:!0},Pi:{name:"Pi",value:Math.pow(1024,5),scientific:!0},Ei:{name:"Ei",value:Math.pow(1024,6),scientific:!0},Zi:{name:"Zi",value:Math.pow(1024,7),scientific:!0},Yi:{name:"Yi",value:Math.pow(1024,8),scientific:!0}},BINARY_LONG:{"":{name:"",value:1,scientific:!0},kilo:{name:"kilo",value:1024,scientific:!0},mega:{name:"mega",value:Math.pow(1024,2),scientific:!0},giga:{name:"giga",value:Math.pow(1024,3),scientific:!0},tera:{name:"tera",value:Math.pow(1024,4),scientific:!0},peta:{name:"peta",value:Math.pow(1024,5),scientific:!0},exa:{name:"exa",value:Math.pow(1024,6),scientific:!0},zetta:{name:"zetta",value:Math.pow(1024,7),scientific:!0},yotta:{name:"yotta",value:Math.pow(1024,8),scientific:!0},kibi:{name:"kibi",value:1024,scientific:!0},mebi:{name:"mebi",value:Math.pow(1024,2),scientific:!0},gibi:{name:"gibi",value:Math.pow(1024,3),scientific:!0},tebi:{name:"tebi",value:Math.pow(1024,4),scientific:!0},pebi:{name:"pebi",value:Math.pow(1024,5),scientific:!0},exi:{name:"exi",value:Math.pow(1024,6),scientific:!0},zebi:{name:"zebi",value:Math.pow(1024,7),scientific:!0},yobi:{name:"yobi",value:Math.pow(1024,8),scientific:!0}}},e.PREFIX_NONE={name:"",value:1,scientific:!0},e.BASE_UNITS={NONE:{},LENGTH:{},MASS:{},TIME:{},CURRENT:{},TEMPERATURE:{},LUMINOUS_INTENSITY:{},AMOUNT_OF_SUBSTANCE:{},FORCE:{},SURFACE:{},VOLUME:{},ANGLE:{},BIT:{}};var Y=e.BASE_UNITS,j=e.PREFIXES;e.BASE_UNIT_NONE={},e.UNIT_NONE={name:"",base:e.BASE_UNIT_NONE,value:1,offset:0},e.UNITS=[{name:"meter",base:Y.LENGTH,prefixes:j.LONG,value:1,offset:0},{name:"inch",base:Y.LENGTH,prefixes:j.NONE,value:.0254,offset:0},{name:"foot",base:Y.LENGTH,prefixes:j.NONE,value:.3048,offset:0},{name:"yard",base:Y.LENGTH,prefixes:j.NONE,value:.9144,offset:0},{name:"mile",base:Y.LENGTH,prefixes:j.NONE,value:1609.344,offset:0},{name:"link",base:Y.LENGTH,prefixes:j.NONE,value:.201168,offset:0},{name:"rod",base:Y.LENGTH,prefixes:j.NONE,value:5.02921,offset:0},{name:"chain",base:Y.LENGTH,prefixes:j.NONE,value:20.1168,offset:0},{name:"angstrom",base:Y.LENGTH,prefixes:j.NONE,value:1e-10,offset:0},{name:"m",base:Y.LENGTH,prefixes:j.SHORT,value:1,offset:0},{name:"ft",base:Y.LENGTH,prefixes:j.NONE,value:.3048,offset:0},{name:"yd",base:Y.LENGTH,prefixes:j.NONE,value:.9144,offset:0},{name:"mi",base:Y.LENGTH,prefixes:j.NONE,value:1609.344,offset:0},{name:"li",base:Y.LENGTH,prefixes:j.NONE,value:.201168,offset:0},{name:"rd",base:Y.LENGTH,prefixes:j.NONE,value:5.02921,offset:0},{name:"ch",base:Y.LENGTH,prefixes:j.NONE,value:20.1168,offset:0},{name:"mil",base:Y.LENGTH,prefixes:j.NONE,value:254e-7,offset:0},{name:"m2",base:Y.SURFACE,prefixes:j.SHORT,value:1,offset:0},{name:"sqin",base:Y.SURFACE,prefixes:j.NONE,value:64516e-8,offset:0},{name:"sqft",base:Y.SURFACE,prefixes:j.NONE,value:.09290304,offset:0},{name:"sqyd",base:Y.SURFACE,prefixes:j.NONE,value:.83612736,offset:0},{name:"sqmi",base:Y.SURFACE,prefixes:j.NONE,value:2589988.110336,offset:0},{name:"sqrd",base:Y.SURFACE,prefixes:j.NONE,value:25.29295,offset:0},{name:"sqch",base:Y.SURFACE,prefixes:j.NONE,value:404.6873,offset:0},{name:"sqmil",base:Y.SURFACE,prefixes:j.NONE,value:6.4516e-10,offset:0},{name:"m3",base:Y.VOLUME,prefixes:j.SHORT,value:1,offset:0},{name:"L",base:Y.VOLUME,prefixes:j.SHORT,value:.001,offset:0},{name:"litre",base:Y.VOLUME,prefixes:j.LONG,value:.001,offset:0},{name:"cuin",base:Y.VOLUME,prefixes:j.NONE,value:16387064e-12,offset:0},{name:"cuft",base:Y.VOLUME,prefixes:j.NONE,value:.028316846592,offset:0},{name:"cuyd",base:Y.VOLUME,prefixes:j.NONE,value:.764554857984,offset:0},{name:"teaspoon",base:Y.VOLUME,prefixes:j.NONE,value:5e-6,offset:0},{name:"tablespoon",base:Y.VOLUME,prefixes:j.NONE,value:15e-6,offset:0},{name:"minim",base:Y.VOLUME,prefixes:j.NONE,value:6.161152e-8,offset:0},{name:"fluiddram",base:Y.VOLUME,prefixes:j.NONE,value:36966911e-13,offset:0},{name:"fluidounce",base:Y.VOLUME,prefixes:j.NONE,value:2957353e-11,offset:0},{name:"gill",base:Y.VOLUME,prefixes:j.NONE,value:.0001182941,offset:0},{name:"cup",base:Y.VOLUME,prefixes:j.NONE,value:.0002365882,offset:0},{name:"pint",base:Y.VOLUME,prefixes:j.NONE,value:.0004731765,offset:0},{name:"quart",base:Y.VOLUME,prefixes:j.NONE,value:.0009463529,offset:0},{name:"gallon",base:Y.VOLUME,prefixes:j.NONE,value:.003785412,offset:0},{name:"beerbarrel",base:Y.VOLUME,prefixes:j.NONE,value:.1173478,offset:0},{name:"oilbarrel",base:Y.VOLUME,prefixes:j.NONE,value:.1589873,offset:0},{name:"hogshead",base:Y.VOLUME,prefixes:j.NONE,value:.238481,offset:0},{name:"fldr",base:Y.VOLUME,prefixes:j.NONE,value:36966911e-13,offset:0},{name:"floz",base:Y.VOLUME,prefixes:j.NONE,value:2957353e-11,offset:0},{name:"gi",base:Y.VOLUME,prefixes:j.NONE,value:.0001182941,offset:0},{name:"cp",base:Y.VOLUME,prefixes:j.NONE,value:.0002365882,offset:0},{name:"pt",base:Y.VOLUME,prefixes:j.NONE,value:.0004731765,offset:0},{name:"qt",base:Y.VOLUME,prefixes:j.NONE,value:.0009463529,offset:0},{name:"gal",base:Y.VOLUME,prefixes:j.NONE,value:.003785412,offset:0},{name:"bbl",base:Y.VOLUME,prefixes:j.NONE,value:.1173478,offset:0},{name:"obl",base:Y.VOLUME,prefixes:j.NONE,value:.1589873,offset:0},{name:"g",base:Y.MASS,prefixes:j.SHORT,value:.001,offset:0},{name:"gram",base:Y.MASS,prefixes:j.LONG,value:.001,offset:0},{name:"ton",base:Y.MASS,prefixes:j.SHORT,value:907.18474,offset:0},{name:"tonne",base:Y.MASS,prefixes:j.SHORT,value:1e3,offset:0},{name:"grain",base:Y.MASS,prefixes:j.NONE,value:6479891e-11,offset:0},{name:"dram",base:Y.MASS,prefixes:j.NONE,value:.0017718451953125,offset:0},{name:"ounce",base:Y.MASS,prefixes:j.NONE,value:.028349523125,offset:0},{name:"poundmass",base:Y.MASS,prefixes:j.NONE,value:.45359237,offset:0},{name:"hundredweight",base:Y.MASS,prefixes:j.NONE,value:45.359237,offset:0},{name:"stick",base:Y.MASS,prefixes:j.NONE,value:.115,offset:0},{name:"gr",base:Y.MASS,prefixes:j.NONE,value:6479891e-11,offset:0},{name:"dr",base:Y.MASS,prefixes:j.NONE,value:.0017718451953125,offset:0},{name:"oz",base:Y.MASS,prefixes:j.NONE,value:.028349523125,offset:0},{name:"lbm",base:Y.MASS,prefixes:j.NONE,value:.45359237,offset:0},{name:"cwt",base:Y.MASS,prefixes:j.NONE,value:45.359237,offset:0},{name:"s",base:Y.TIME,prefixes:j.SHORT,value:1,offset:0},{name:"min",base:Y.TIME,prefixes:j.NONE,value:60,offset:0},{name:"h",base:Y.TIME,prefixes:j.NONE,value:3600,offset:0},{name:"seconds",base:Y.TIME,prefixes:j.LONG,value:1,offset:0},{name:"second",base:Y.TIME,prefixes:j.LONG,value:1,offset:0},{name:"sec",base:Y.TIME,prefixes:j.LONG,value:1,offset:0},{name:"minutes",base:Y.TIME,prefixes:j.NONE,value:60,offset:0},{name:"minute",base:Y.TIME,prefixes:j.NONE,value:60,offset:0},{name:"hours",base:Y.TIME,prefixes:j.NONE,value:3600,offset:0},{name:"hour",base:Y.TIME,prefixes:j.NONE,value:3600,offset:0},{name:"day",base:Y.TIME,prefixes:j.NONE,value:86400,offset:0},{name:"days",base:Y.TIME,prefixes:j.NONE,value:86400,offset:0},{name:"rad",base:Y.ANGLE,prefixes:j.NONE,value:1,offset:0},{name:"deg",base:Y.ANGLE,prefixes:j.NONE,value:.017453292519943295,offset:0},{name:"grad",base:Y.ANGLE,prefixes:j.NONE,value:.015707963267948967,offset:0},{name:"cycle",base:Y.ANGLE,prefixes:j.NONE,value:6.283185307179586,offset:0},{name:"A",base:Y.CURRENT,prefixes:j.SHORT,value:1,offset:0},{name:"ampere",base:Y.CURRENT,prefixes:j.LONG,value:1,offset:0},{name:"K",base:Y.TEMPERATURE,prefixes:j.NONE,value:1,offset:0},{name:"degC",base:Y.TEMPERATURE,prefixes:j.NONE,value:1,offset:273.15},{name:"degF",base:Y.TEMPERATURE,prefixes:j.NONE,value:1/1.8,offset:459.67},{name:"degR",base:Y.TEMPERATURE,prefixes:j.NONE,value:1/1.8,offset:0},{name:"kelvin",base:Y.TEMPERATURE,prefixes:j.NONE,value:1,offset:0},{name:"celsius",base:Y.TEMPERATURE,prefixes:j.NONE,value:1,offset:273.15},{name:"fahrenheit",base:Y.TEMPERATURE,prefixes:j.NONE,value:1/1.8,offset:459.67},{name:"rankine",base:Y.TEMPERATURE,prefixes:j.NONE,value:1/1.8,offset:0},{name:"mol",base:Y.AMOUNT_OF_SUBSTANCE,prefixes:j.NONE,value:1,offset:0},{name:"mole",base:Y.AMOUNT_OF_SUBSTANCE,prefixes:j.NONE,value:1,offset:0},{name:"cd",base:Y.LUMINOUS_INTENSITY,prefixes:j.NONE,value:1,offset:0},{name:"candela",base:Y.LUMINOUS_INTENSITY,prefixes:j.NONE,value:1,offset:0},{name:"N",base:Y.FORCE,prefixes:j.SHORT,value:1,offset:0},{name:"newton",base:Y.FORCE,prefixes:j.LONG,value:1,offset:0},{name:"lbf",base:Y.FORCE,prefixes:j.NONE,value:4.4482216152605,offset:0},{name:"poundforce",base:Y.FORCE,prefixes:j.NONE,value:4.4482216152605,offset:0},{name:"b",base:Y.BIT,prefixes:j.BINARY_SHORT,value:1,offset:0},{name:"bits",base:Y.BIT,prefixes:j.BINARY_LONG,value:1,offset:0},{name:"B",base:Y.BIT,prefixes:j.BINARY_SHORT,value:8,offset:0},{name:"bytes",base:Y.BIT,prefixes:j.BINARY_LONG,value:8,offset:0}],H.type.Complex=t,t.prototype.copy=function(){return new t(this.re,this.im)},t.prototype.toString=function(){var e="";return e=0===this.im?k.format(this.re):0===this.re?1===this.im?"i":-1===this.im?"-i":k.format(this.im)+"i":this.im>0?1==this.im?k.format(this.re)+" + i":k.format(this.re)+" + "+k.format(this.im)+"i":-1==this.im?k.format(this.re)+" - i":k.format(this.re)+" - "+k.format(Math.abs(this.im))+"i"},t.doc={name:"Complex",category:"type",syntax:["a + bi","a + b * i"],description:"A complex value a + bi, where a is the real part and b is the complex part, and i is the imaginary number defined as sqrt(-1).",examples:["2 + 3i","sqrt(-4)","(1.2 -5i) * 2"],seealso:["abs","arg","conj","im","re"]},H.E=Math.E,H.LN2=Math.LN2,H.LN10=Math.LN10,H.LOG2E=Math.LOG2E,H.LOG10E=Math.LOG10E,H.PI=Math.PI,H.SQRT1_2=Math.SQRT1_2,H.SQRT2=Math.SQRT2,H.I=new t(0,-1),H.pi=H.PI,H.e=H.E,H.i=H.I,H.help=s,H.abs=A,s.doc={name:"help",category:"Utils",syntax:["help(object)"],description:"Display documentation on a function or data type.",examples:['help("sqrt")','help("Complex")'],seealso:[]},H["typeof"]=f,f.doc={name:"typeof",category:"Utils",syntax:["typeof(x)"],description:"Get the type of a variable.",examples:["typeof(3.5)","typeof(2 - 4i)","typeof(45 deg)",'typeof("hello world")'],seealso:[]},H.min=u,u.doc={name:"min",category:"Statistics",syntax:["min(a, b, c, ...)"],description:"Compute the minimum value of a list of values.",examples:["max(2, 3, 4, 1)","max(2.7, 7.1, -4.5, 2.0, 4.1)","min(2.7, 7.1, -4.5, 2.0, 4.1)"],seealso:["sum","prod","avg","var","std","min","median"]},H.max=c,c.doc={name:"max",category:"Statistics",syntax:["max(a, b, c, ...)"],description:"Compute the maximum value of a list of values.",examples:["max(2, 3, 4, 1)","max(2.7, 7.1, -4.5, 2.0, 4.1)","min(2.7, 7.1, -4.5, 2.0, 4.1)"],seealso:["sum","prod","avg","var","std","min","median"]},H["in"]=l,l.doc={name:"in",category:"Units",syntax:["x in unit","in(x, unit)"],description:"Change the unit of a value.",examples:["5 inch in cm","3.2kg in g","16 bytes in bits"],seealso:[]},H.sin=m,m.doc={name:"sin",category:"Trigonometry",syntax:["sin(x)"],description:"Compute the sine of x in radians.",examples:["sin(2)","sin(pi / 4) ^ 2","sin(90 deg)","sin(30 deg)","sin(0.2)^2 + cos(0.2)^2"],seealso:["asin","cos","tan"]},H.atan2=p,p.doc={name:"atan2",category:"Trigonometry",syntax:["atan2(y, x)"],description:"Computes the principal value of the arc tangent of y/x in radians.",examples:["atan2(2, 2) / pi","angle = 60 deg in rad","x = cos(angle)","y = sin(angle)","atan2(y, x)"],seealso:["sin","cos","tan"]},H.asin=h,h.doc={name:"asin",category:"Trigonometry",syntax:["asin(x)"],description:"Compute the inverse sine of a value in radians.",examples:["asin(0.5)","asin(sin(2.3))"],seealso:["sin","acos","asin"]},H.atan=v,v.doc={name:"atan",category:"Trigonometry",syntax:["atan(x)"],description:"Compute the inverse tangent of a value in radians.",examples:["atan(0.5)","atan(tan(2.3))"],seealso:["tan","acos","asin"]},H.cos=x,x.doc={name:"cos",category:"Trigonometry",syntax:["cos(x)"],description:"Compute the cosine of x in radians.",examples:["cos(2)","cos(pi / 4) ^ 2","cos(180 deg)","cos(60 deg)","sin(0.2)^2 + cos(0.2)^2"],seealso:["acos","sin","tan"]},H.tan=d,d.doc={name:"tan",category:"Trigonometry",syntax:["tan(x)"],description:"Compute the tangent of x in radians.",examples:["tan(0.5)","sin(0.5) / cos(0.5)","tan(pi / 4)","tan(45 deg)"],seealso:["atan","sin","cos"]},H.acos=N,N.doc={name:"acos",category:"Trigonometry",syntax:["acos(x)"],description:"Compute the inverse cosine of a value in radians.",examples:["acos(0.5)","acos(cos(2.3))"],seealso:["cos","acos","asin"]},H.divide=E,E.doc={name:"divide",category:"Operators",syntax:["x / y","divide(x, y)"],description:"Divide two values.",examples:["2 / 3","ans * 3","4.5 / 2","3 + 4 / 2","(3 + 4) / 2","18 km / 4.5"],seealso:["multiply"]},H.round=y,y.doc={name:"round",category:"Arithmetic",syntax:["round(x)","round(x, n)"],description:"round a value towards the nearest integer.If x is complex, both real and imaginary part are rounded towards the nearest integer. When n is specified, the value is rounded to n decimals.",examples:["round(3.2)","round(3.8)","round(-4.2)","round(-4.8)","round(pi, 3)","round(123.45678, 2)"],seealso:["ceil","floor","fix"]},H.fix=O,O.doc={name:"fix",category:"Arithmetic",syntax:["fix(x)"],description:"Round a value towards zero.If x is complex, both real and imaginary part are rounded towards zero.",examples:["fix(3.2)","fix(3.8)","fix(-4.2)","fix(-4.8)"],seealso:["ceil","floor","round"]},H.add=g,g.doc={name:"add",category:"Operators",syntax:["x + y","add(x, y)"],description:"Add two values.",examples:["2.1 + 3.6","ans - 3.6","3 + 2i",'"hello" + " world"',"3 cm + 2 inch"],seealso:["subtract"]},H.exp=w,w.doc={name:"exp",category:"Arithmetic",syntax:["exp(x)"],description:"Calculate the exponent of a value.",examples:["exp(1.3)","e ^ 1.3","log(exp(1.3))","x = 2.4","(exp(i*x) == cos(x) + i*sin(x)) # Euler's formula"],seealso:["square","multiply","log"]},H.sqrt=T,T.doc={name:"sqrt",category:"Arithmetic",syntax:["sqrt(x)"],description:"Compute the square root value. If x = y * y, then y is the square root of x.",examples:["sqrt(25)","5 * 5","sqrt(-1)"],seealso:["square","multiply"]},H.larger=S,S.doc={name:"larger",category:"Operators",syntax:["x > y","larger(x, y)"],description:"Check if value x is larger y. Returns 1 if x is larger than y, and 0 if not.",examples:["2 > 3","5 > 2*2","a = 3.3","b = 6-2.8","(a > b)","(b < a)","5 cm > 2 inch"],seealso:["equal","unequal","smaller","smallereq","largereq"]},H.unaryminus=U,U.doc={name:"unaryminus",category:"Operators",syntax:["-x","unaryminus(x)"],description:"Inverse the sign of a value.",examples:["-4.5","-(-5.6)"],seealso:["add","subtract"]},H.smaller=L,L.doc={name:"smaller",category:"Operators",syntax:["x < y","smaller(x, y)"],description:"Check if value a is smaller value b. Returns 1 if x is smaller than y, and 0 if not.",examples:["2 < 3","5 < 2*2","a = 3.3","b = 6-2.8","(a < b)","5 cm < 2 inch"],seealso:["equal","unequal","larger","smallereq","largereq"]},H.abs=A,A.doc={name:"abs",category:"Arithmetic",syntax:["abs(x)"],description:"Compute the absolute value.",examples:["abs(3.5)","abs(-4.2)"],seealso:["sign"]},H.log=R,R.doc={name:"log",category:"Arithmetic",syntax:["log(x)"],description:"Compute the natural logarithm of a value.",examples:["log(3.5)","a = log(2.4)","exp(a)","log(1000) / log(10)"],seealso:["exp","logb","log10"]},H.pow=I,I.doc={name:"pow",category:"Operators",syntax:["x ^ y","pow(x, y)"],description:"Calculates the power of x to y, x^y.",examples:["2^3 = 8","2*2*2","1 + e ^ (pi * i)"],seealso:["unequal","smaller","larger","smallereq","largereq"]},H.floor=G,G.doc={name:"floor",category:"Arithmetic",syntax:["floor(x)"],description:"Round a value towards minus infinity.If x is complex, both real and imaginary part are rounded towards minus infinity.",examples:["floor(3.2)","floor(3.8)","floor(-4.2)"],seealso:["ceil","fix","round"]},H.ceil=C,C.doc={name:"ceil",category:"Arithmetic",syntax:["ceil(x)"],description:"Round a value towards plus infinity.If x is complex, both real and imaginary part are rounded towards plus infinity.",examples:["ceil(3.2)","ceil(3.8)","ceil(-4.2)"],seealso:["floor","fix","round"]},H.multiply=_,_.doc={name:"multiply",category:"Operators",syntax:["x * y","multiply(x, y)"],description:"multiply two values.",examples:["2.1 * 3.6","ans / 3.6","2 * 3 + 4","2 * (3 + 4)","3 * 2.1 km"],seealso:["divide"]},H.subtract=P,P.doc={name:"subtract",category:"Operators",syntax:["x - y","subtract(x, y)"],description:"subtract two values.",examples:["5.3 - 2","ans + 2","2/3 - 1/6","2 * 3 - 3","2.1 km - 500m"],seealso:["add"]},H.random=B,B.doc={name:"random",category:"Probability",syntax:["random()"],description:"Return a random number between 0 and 1.",examples:["random()","100 * random()"],seealso:[]}})(); \ No newline at end of file +undefined(function(){function e(t,n){if(this.constructor!=e)throw Error("Unit constructor must be called with the new operator");this.value=1,this.unit=e.UNIT_NONE,this.prefix=e.PREFIX_NONE,this.hasUnit=!1,this.hasValue=!1,this.fixPrefix=!1,this._init(t,n)}function t(e){return e instanceof String||"string"==typeof e}function n(e){return e instanceof Number||"number"==typeof e}function i(e){return e==Math.round(e)}function r(e,i){if(this.constructor!=r)throw new SyntaxError("Complex constructor must be called with the new operator");switch(arguments.length){case 2:if(!n(e)||!n(i))throw new TypeError("Two numbers or a single string expected in Complex constructor");this.re=e,this.im=i;break;case 1:if(!t(e))throw new TypeError("Two numbers or a single string expected in Complex constructor");var s,o=[],f="+",u=e.lastIndexOf(f);-1==u&&(f="-",u=e.lastIndexOf(f)),-1!=u?(s=a(e.substring(0,u)),s&&o.push(s),s=a(e.substring(u+1)),s&&o.push(f+s)):(s=a(e),s&&o.push(s));var h=!1;switch(o.length){case 1:s=o[0],"I"==s[s.length-1].toUpperCase()?(this.re=0,this.im=Number(s.substring(0,s.length-1)),h=!isNaN(this.im)):(this.re=Number(s),this.im=0,h=!isNaN(this.re));break;case 2:s=o[0],this.re=Number(o[0]),this.im=Number(o[1].substring(0,o[1].length-1)),h=!isNaN(this.re)&&!isNaN(this.im)&&"I"==o[1][o[1].length-1].toUpperCase()}if(!h)throw new SyntaxError('Invalid string "'+e+'"');break;case 0:this.re=0,this.im=0;break;default:throw new SyntaxError("Wrong number of arguments in Complex constructor ("+arguments.length+" provided, 0, 1, or 2 expected)")}}function a(e){return e.replace(/^\s+|\s+$/g,"")}function s(e,t){var n=void 0;if(2==arguments.length){var i=h(t);n="Function "+e+" does not support a parameter of type "+i}else if(arguments.length>2){for(var r=[],a=1;arguments.length>a;a++)r.push(h(arguments[a]));n="Function "+e+" does not support a parameters of type "+r.join(", ")}else n="Unsupported parameter in function "+e;return new TypeError(n)}function o(e,t,n,i){var r="Wrong number of arguments in function "+e+" ("+t+" provided, "+n+(void 0!=i?"-"+i:"")+" expected)";return new SyntaxError(r)}function f(e){if(1!=arguments.length)throw o("help",arguments.length,1);if(e.doc)return u(e.doc);if(e.constructor.doc)return u(e.constructor.doc);if(t(e)){var n=Z[e];if(n&&n.doc)return u(n.doc);for(var i in Z.type)if(Z.type.hasOwnProperty(i)&&e.toLowerCase()==i.toLowerCase()&&Z.type[i].doc)return u(Z.type[i].doc)}return e instanceof Object&&e.name?'No documentation found on subject "'+e.name+'"':e instanceof Object&&e.constructor.name?'No documentation found on subject "'+e.constructor.name+'"':'No documentation found on subject "'+e+'"'}function u(e){var t="";if(e.name&&(t+="NAME\n"+e.name+"\n\n"),e.category&&(t+="CATEGORY\n"+e.category+"\n\n"),e.syntax&&(t+="SYNTAX\n"+e.syntax.join("\n")+"\n\n"),e.examples){t+="EXAMPLES\n";for(var n=0;e.examples.length>n;n++)t+=e.examples[n]+"\n";t+="\n"}return e.seealso&&(t+="SEE ALSO\n"+e.seealso.join(", ")+"\n"),t}function h(e){if(1!=arguments.length)throw o("typeof",arguments.length,1);var t=typeof e;if("object"==t){if(null==e)return"null";if(e&&e.constructor&&e.constructor.name)return e.constructor.name.toLowerCase()}return t}function c(){if(0==arguments.length)throw Error("Function sum requires one or more parameters (0 provided)");for(var e=arguments[0],t=1,n=arguments.length;n>t;t++){var i=arguments[t];L(i,e)&&(e=i)}return l}function l(){if(0==arguments.length)throw Error("Function sum requires one or more parameters (0 provided)");for(var e=arguments[0],t=1,n=arguments.length;n>t;t++){var i=arguments[t];U(i,e)&&(e=i)}return e}function p(t,n){if(2!=arguments.length)throw o("in",arguments.length,2);if(t instanceof e){if(n.hasValue)throw Error("Cannot convert to a unit with a value");if(!n.hasUnit)throw Error("Unit expected on the right hand side of function in");var i=n.copy();return i.value=t.value,i.fixPrefix=!0,i}throw s("in",t)}function m(t){if(1!=arguments.length)throw o("sin",arguments.length,1);if(n(t))return Math.sin(t);if(t instanceof r)return new r(.5*Math.sin(t.re)*(Math.exp(-t.im)+Math.exp(t.im)),.5*Math.cos(t.re)*(Math.exp(t.im)-Math.exp(-t.im)));if(t instanceof e){if(!t.hasBase(e.BASE_UNITS.ANGLE))throw new TypeError("Unit in function cos is no angle");return Math.sin(t.value)}throw s("sin",t)}function v(e,t){if(2!=arguments.length)throw o("atan2",arguments.length,2);if(n(e)){if(n(t))return Math.atan2(e,t);if(t instanceof r)return Math.atan2(e,t.re)}else if(e instanceof r){if(n(t))return Math.atan2(e.re,t);if(t instanceof r)return Math.atan2(e.re,t.re)}throw s("atan2",e,t)}function d(e){if(1!=arguments.length)throw o("asin",arguments.length,1);if(n(e))return e>=-1&&1>=e?Math.asin(e):d(new r(e,0));if(e instanceof r){var t=e.re,i=e.im,a=new r(i*i-t*t+1,-2*t*i),f=k(a),u=new r(f.re-i,f.im+t),h=R(u);return new r(h.im,-h.re)}throw s("asin",e)}function g(e){if(1!=arguments.length)throw o("atan",arguments.length,1);if(n(e))return Math.atan(e);if(e instanceof r){var t=e.re,i=e.im,a=t*t+(1-i)*(1-i),f=new r((1-i*i-t*t)/a,-2*t/a),u=R(f);return new r(-.5*u.im,.5*u.re)}throw s("atan",e)}function x(t){if(1!=arguments.length)throw o("cos",arguments.length,1);if(n(t))return Math.cos(t);if(t instanceof r)return new r(.5*Math.cos(t.re)*(Math.exp(-t.im)+Math.exp(t.im)),.5*Math.sin(t.re)*(Math.exp(-t.im)-Math.exp(t.im)));if(t instanceof e){if(!t.hasBase(e.BASE_UNITS.ANGLE))throw new TypeError("Unit in function cos is no angle");return Math.cos(t.value)}throw s("cos",t)}function y(t){if(1!=arguments.length)throw o("tan",arguments.length,1);if(n(t))return Math.tan(t);if(t instanceof r){var i=Math.exp(-4*t.im)+2*Math.exp(-2*t.im)*Math.cos(2*t.re)+1;return new r(2*Math.exp(-2*t.im)*Math.sin(2*t.re)/i,(1-Math.exp(-4*t.im))/i)}if(t instanceof e){if(!t.hasBase(e.BASE_UNITS.ANGLE))throw new TypeError("Unit in function tan is no angle");return Math.tan(t.value)}throw s("tan",t)}function E(e){if(1!=arguments.length)throw o("acos",arguments.length,1);if(n(e))return e>=-1&&1>=e?Math.acos(e):E(new r(e,0));if(e instanceof r){var t=new r(e.im*e.im-e.re*e.re+1,-2*e.re*e.im),i=k(t),a=new r(i.re-e.im,i.im+e.re),f=R(a);return new r(1.5707963267948966-f.im,f.re)}throw s("acos",e)}function N(t,i){if(2!=arguments.length)throw o("divide",arguments.length,2);if(n(t)){if(n(i))return t/i;if(i instanceof r)return w(new r(t,0),i)}else if(t instanceof r){if(n(i))return w(t,new r(i,0));if(i instanceof r)return w(t,i)}else if(t instanceof e&&n(i)){var a=t.copy();return a.value/=i,a}throw s("divide",t,i)}function w(e,t){var n=t.re*t.re+t.im*t.im;return new r((e.re*t.re+e.im*t.im)/n,(e.im*t.re-e.re*t.im)/n)}function b(e,t){if(1!=arguments.length&&2!=arguments.length)throw o("round",arguments.length,1,2);if(void 0==t){if(n(e))return Math.round(e);if(e instanceof r)return new r(Math.round(e.re),Math.round(e.im));throw s("round",e)}if(!n(t))throw new TypeError("Number of digits in function round must be an integer");if(t!==Math.round(t))throw new TypeError("Number of digits in function round must be integer");if(0>t||t>9)throw Error("Number of digits in function round must be in te range of 0-9");if(n(e))return M(e,t);if(e instanceof r)return new r(M(e.re,t),M(e.im,t));throw s("round",e,t)}function M(e,t){var n=Math.pow(10,void 0!=t?t:Z.options.precision);return Math.round(e*n)/n}function O(e){if(1!=arguments.length)throw o("fix",arguments.length,1);if(n(e))return value>0?Math.floor(e):Math.ceil(e);if(e instanceof r)return new r(e.re>0?Math.floor(e.re):Math.ceil(e.re),e.im>0?Math.floor(e.im):Math.ceil(e.im));throw s("fix",e)}function T(i,a){if(2!=arguments.length)throw o("add",arguments.length,2);if(n(i)){if(n(a))return i+a;if(a instanceof r)return new r(i+a.re,a.im)}else if(i instanceof r){if(n(a))return new r(i.re+a,i.im);if(a instanceof r)return new r(i.re+a.re,i.im+a.im)}else if(i instanceof e&&a instanceof e){if(!i.equalBase(a))throw Error("Units do not match");if(!i.hasValue)throw Error("Unit on left hand side of operator + has no value");if(!a.hasValue)throw Error("Unit on right hand side of operator + has no value");var f=i.copy();return f.value+=a.value,f.fixPrefix=!1,f}if(t(i)||t(a))return i+a;throw s("add",i,a)}function S(e){if(1!=arguments.length)throw o("exp",arguments.length,1);if(n(e))return Math.exp(e);if(e instanceof r){var t=Math.exp(e.re);return new r(t*Math.cos(e.im),t*Math.sin(e.im))}throw s("exp",e)}function k(e){if(1!=arguments.length)throw o("sqrt",arguments.length,1);if(n(e))return e>=0?Math.sqrt(e):k(new r(e,0));if(e instanceof r){var t=Math.sqrt(e.re*e.re+e.im*e.im);return e.im>=0?new r(.5*Math.sqrt(2*(t+e.re)),.5*Math.sqrt(2*(t-e.re))):new r(.5*Math.sqrt(2*(t+e.re)),-.5*Math.sqrt(2*(t-e.re)))}throw s("sqrt",e)}function U(i,a){if(2!=arguments.length)throw o("larger",arguments.length,2);if(n(i)){if(n(a))return i>a;if(a instanceof r)return i>A(a)}if(i instanceof r){if(n(a))return A(i)>a;if(a instanceof r)return A(i)>A(a)}if(i instanceof e&&a instanceof e){if(!i.equalBase(a))throw Error("Cannot compare units with different base");return i.value>a.value}if(t(i)||t(a))return i>a;throw s("larger",i,a)}function _(t){if(1!=arguments.length)throw o("unaryminus",arguments.length,1);if(n(t))return-t;if(t instanceof r)return new r(-t.re,-t.im);if(t instanceof e){var i=t.copy();return i.value=-t.value,i}throw s("unaryminus",t)}function L(i,a){if(2!=arguments.length)throw o("smaller",arguments.length,2);if(n(i)){if(n(a))return a>i;if(a instanceof r)return A(a)>i}if(i instanceof r){if(n(a))return a>A(i);if(a instanceof r)return A(i)i;throw s("smaller",i,a)}function A(e){if(1!=arguments.length)throw o("abs",arguments.length,1);if(n(e))return Math.abs(e);if(e instanceof r)return Math.sqrt(e.re*e.re+e.im*e.im);throw s("abs",e)}function R(e){if(1!=arguments.length)throw o("log",arguments.length,1);if(n(e))return e>=0?Math.log(e):R(new r(e,0));if(e instanceof r)return new r(Math.log(Math.sqrt(e.re*e.re+e.im*e.im)),Math.atan2(e.im,e.re));throw s("log",e)}function I(e,t){if(2!=arguments.length)throw o("pow",arguments.length,2);if(n(e)){if(n(t))return i(t)||e>=0?Math.pow(e,t):C(new r(e,0),new r(t,0));if(t instanceof r)return C(new r(e,0),t)}else if(e instanceof r){if(n(t))return C(e,new r(t,0));if(t instanceof r)return C(e,t)}throw s("pow",e,t)}function C(e,t){var n=R(e),i=G(n,t);return S(i)}function P(e){if(1!=arguments.length)throw o("floor",arguments.length,1);if(n(e))return Math.floor(e);if(e instanceof r)return new r(Math.floor(e.re),Math.floor(e.im));throw s("floor",e)}function q(e){if(1!=arguments.length)throw o("ceil",arguments.length,1);if(n(e))return Math.ceil(e);if(e instanceof r)return new r(Math.ceil(e.re),Math.ceil(e.im));throw s("ceil",e)}function G(t,i){var a;if(2!=arguments.length)throw o("multiply",arguments.length,2);if(n(t)){if(n(i))return t*i;if(i instanceof r)return B(new r(t,0),i);if(i instanceof e)return a=i.copy(),a.value*=t,a}else if(t instanceof r){if(n(i))return B(t,new r(i,0));if(i instanceof r)return B(t,i)}else if(t instanceof e&&n(i))return a=t.copy(),a.value*=i,a;throw s("multiply",t,i)}function B(e,t){return new r(e.re*t.re-e.im*t.im,e.re*t.im+e.im*t.re)}function Y(t,i){if(2!=arguments.length)throw o("subtract",arguments.length,2);if(n(t)){if(n(i))return t-i;if(i instanceof r)return new r(t-i.re,i.im)}else if(t instanceof r){if(n(i))return new r(t.re-i,t.im);if(i instanceof r)return new r(t.re-i.re,t.im-i.im)}else if(t instanceof e&&i instanceof e){if(!t.equalBase(i))throw Error("Units do not match");if(!t.hasValue)throw Error("Unit on left hand side of operator - has no value");if(!i.hasValue)throw Error("Unit on right hand side of operator - has no value");var a=t.copy();return a.value-=i.value,a.fixPrefix=!1,a}throw s("subtract",t,i)}function V(){if(0!=arguments.length)throw o("random",arguments.length,0);return Math.random()}function F(){}function D(e,t,n){this.name=e,this.fn=t,this.params=n}function H(e){this.value=e}function K(){this.params=[],this.visible=[]}function z(e,t,n,i){this.name=e,this.params=t,this.expr=n,this.result=i}function j(e,t,n,i,r){this.name=e,this.variables=n,this.values=[];for(var a=0,s=this.variables.length;s>a;a++)this.values[a]=function(){var e=function(){return e.value};return e.value=void 0,e}();this.def=this.createFunction(e,t,n,i),this.result=r}function W(e){this.parentScope=e,this.nestedScopes=void 0,this.symbols={},this.defs={},this.updates={},this.links={}}function X(){this.TOKENTYPE={NULL:0,DELIMITER:1,NUMBER:2,SYMBOL:3,UNKNOWN:4},this.expr="",this.index=0,this.c="",this.token="",this.token_type=this.TOKENTYPE.NULL,this.scope=new W}var Z={type:{},parser:{node:{}},options:{precision:10}};"undefined"!=typeof module&&module.exports!==void 0&&(module.exports=Z),"undefined"!=typeof exports&&(exports=Z),"undefined"!=typeof require&&"undefined"!=typeof define&&define(function(){return Z}),"undefined"!=typeof window&&(window.math=Z);var Q={};if(Q.format=function(e,t){if(1/0===e)return"Infinity";if(e===-1/0)return"-Infinity";if(0/0===e)return"NaN";var n=Math.abs(e);if(n>1e-4&&1e6>n||0==n)return M(e,t)+"";var i=Math.round(Math.log(n)/Math.LN10),r=e/Math.pow(10,i);return M(r,t)+"E"+i},Q.randomUUID=function(){var e=function(){return Math.floor(65536*Math.random()).toString(16)};return e()+e()+"-"+e()+"-"+e()+"-"+e()+"-"+e()+e()+e()},!Array.prototype.indexOf){Array.prototype.indexOf=function(e){for(var t=0;this.length>t;t++)if(this[t]==e)return t;return-1};try{console.log("Warning: Ancient browser detected. Please update your browser")}catch($){}}Array.prototype.forEach||(Array.prototype.forEach=function(e,t){for(var n=0,i=this.length;i>n;++n)e.call(t||this,this[n],n,this)}),Array.prototype.map||(Array.prototype.map=function(e,t){var n,i,r;if(null==this)throw new TypeError(" this is null or not defined");var a=Object(this),s=a.length>>>0;if("function"!=typeof e)throw new TypeError(e+" is not a function");for(t&&(n=t),i=Array(s),r=0;s>r;){var o,f;r in a&&(o=a[r],f=e.call(n,o,r,a),i[r]=f),r++}return i}),Z.type.Unit=e,e.prototype.copy=function(){var t=new e;for(var n in this)this.hasOwnProperty(n)&&(t[n]=this[n]);return t},e.endsWith=function(e,t){var n=e.length-t.length,i=e.length;return e.substring(n,i)===t},e.prototype._init=function(t,n){if(void 0!==n){for(var i=e.UNITS,r=!1,a=0,s=i.length;s>a;a++){var o=i[a];if(e.endsWith(n,o.name)){var f=n.length-o.name.length,u=n.substring(0,f),h=o.prefixes[u];if(void 0!==h){this.unit=o,this.prefix=h,this.hasUnit=!0,r=!0;break}}}if(!r)throw Error('String "'+n+'" is no unit')}void 0!==t?(this.value=this._normalize(t),this.hasValue=!0):this.value=this._normalize(1)},e.prototype._normalize=function(e){return(e+this.unit.offset)*this.unit.value*this.prefix.value},e.prototype._unnormalize=function(e,t){return void 0===t?e/this.unit.value/this.prefix.value-this.unit.offset:e/this.unit.value/t-this.unit.offset},e.isUnit=function(t){for(var n=e.UNITS,i=n.length,r=0;i>r;r++){var a=n[r];if(e.endsWith(t,a.name)){var s=t.length-a.name.length;if(0==s)return!0;var o=t.substring(0,s),f=a.prefixes[o];if(void 0!==f)return!0}}return!1},e.prototype.hasBase=function(e){return void 0===this.unit.base?void 0===e:this.unit.base===e},e.prototype.equalBase=function(e){return this.unit.base===e.unit.base},e.prototype.equals=function(e){return this.equalBase(e)&&this.value==e.value},e.prototype.toString=function(){var t;if(this.fixPrefix)return t=this._unnormalize(this.value),Q.format(t)+" "+this.prefix.name+this.unit.name;var n=e.PREFIX_NONE,i=Math.abs(Math.log(this.value/n.value)/Math.LN10-1.5),r=this.unit.prefixes;for(var a in r)if(r.hasOwnProperty(a)){var s=r[a];if(s.scientific){var o=Math.abs(Math.log(this.value/s.value)/Math.LN10-1.5);i>o&&(n=s,i=o)}}return t=this._unnormalize(this.value,n.value),Q.format(t)+" "+n.name+this.unit.name},e.PREFIXES={NONE:{"":{name:"",value:1,scientific:!0}},SHORT:{"":{name:"",value:1,scientific:!0},da:{name:"da",value:10,scientific:!1},h:{name:"h",value:100,scientific:!1},k:{name:"k",value:1e3,scientific:!0},M:{name:"M",value:1e6,scientific:!0},G:{name:"G",value:1e9,scientific:!0},T:{name:"T",value:1e12,scientific:!0},P:{name:"P",value:1e15,scientific:!0},E:{name:"E",value:1e18,scientific:!0},Z:{name:"Z",value:1e21,scientific:!0},Y:{name:"Y",value:1e24,scientific:!0},d:{name:"d",value:.1,scientific:!1},c:{name:"c",value:.01,scientific:!1},m:{name:"m",value:.001,scientific:!0},u:{name:"u",value:1e-6,scientific:!0},n:{name:"n",value:1e-9,scientific:!0},p:{name:"p",value:1e-12,scientific:!0},f:{name:"f",value:1e-15,scientific:!0},a:{name:"a",value:1e-18,scientific:!0},z:{name:"z",value:1e-21,scientific:!0},y:{name:"y",value:1e-24,scientific:!0}},LONG:{"":{name:"",value:1,scientific:!0},deca:{name:"deca",value:10,scientific:!1},hecto:{name:"hecto",value:100,scientific:!1},kilo:{name:"kilo",value:1e3,scientific:!0},mega:{name:"mega",value:1e6,scientific:!0},giga:{name:"giga",value:1e9,scientific:!0},tera:{name:"tera",value:1e12,scientific:!0},peta:{name:"peta",value:1e15,scientific:!0},exa:{name:"exa",value:1e18,scientific:!0},zetta:{name:"zetta",value:1e21,scientific:!0},yotta:{name:"yotta",value:1e24,scientific:!0},deci:{name:"deci",value:.1,scientific:!1},centi:{name:"centi",value:.01,scientific:!1},milli:{name:"milli",value:.001,scientific:!0},micro:{name:"micro",value:1e-6,scientific:!0},nano:{name:"nano",value:1e-9,scientific:!0},pico:{name:"pico",value:1e-12,scientific:!0},femto:{name:"femto",value:1e-15,scientific:!0},atto:{name:"atto",value:1e-18,scientific:!0},zepto:{name:"zepto",value:1e-21,scientific:!0},yocto:{name:"yocto",value:1e-24,scientific:!0}},BINARY_SHORT:{"":{name:"",value:1,scientific:!0},k:{name:"k",value:1024,scientific:!0},M:{name:"M",value:Math.pow(1024,2),scientific:!0},G:{name:"G",value:Math.pow(1024,3),scientific:!0},T:{name:"T",value:Math.pow(1024,4),scientific:!0},P:{name:"P",value:Math.pow(1024,5),scientific:!0},E:{name:"E",value:Math.pow(1024,6),scientific:!0},Z:{name:"Z",value:Math.pow(1024,7),scientific:!0},Y:{name:"Y",value:Math.pow(1024,8),scientific:!0},Ki:{name:"Ki",value:1024,scientific:!0},Mi:{name:"Mi",value:Math.pow(1024,2),scientific:!0},Gi:{name:"Gi",value:Math.pow(1024,3),scientific:!0},Ti:{name:"Ti",value:Math.pow(1024,4),scientific:!0},Pi:{name:"Pi",value:Math.pow(1024,5),scientific:!0},Ei:{name:"Ei",value:Math.pow(1024,6),scientific:!0},Zi:{name:"Zi",value:Math.pow(1024,7),scientific:!0},Yi:{name:"Yi",value:Math.pow(1024,8),scientific:!0}},BINARY_LONG:{"":{name:"",value:1,scientific:!0},kilo:{name:"kilo",value:1024,scientific:!0},mega:{name:"mega",value:Math.pow(1024,2),scientific:!0},giga:{name:"giga",value:Math.pow(1024,3),scientific:!0},tera:{name:"tera",value:Math.pow(1024,4),scientific:!0},peta:{name:"peta",value:Math.pow(1024,5),scientific:!0},exa:{name:"exa",value:Math.pow(1024,6),scientific:!0},zetta:{name:"zetta",value:Math.pow(1024,7),scientific:!0},yotta:{name:"yotta",value:Math.pow(1024,8),scientific:!0},kibi:{name:"kibi",value:1024,scientific:!0},mebi:{name:"mebi",value:Math.pow(1024,2),scientific:!0},gibi:{name:"gibi",value:Math.pow(1024,3),scientific:!0},tebi:{name:"tebi",value:Math.pow(1024,4),scientific:!0},pebi:{name:"pebi",value:Math.pow(1024,5),scientific:!0},exi:{name:"exi",value:Math.pow(1024,6),scientific:!0},zebi:{name:"zebi",value:Math.pow(1024,7),scientific:!0},yobi:{name:"yobi",value:Math.pow(1024,8),scientific:!0}}},e.PREFIX_NONE={name:"",value:1,scientific:!0},e.BASE_UNITS={NONE:{},LENGTH:{},MASS:{},TIME:{},CURRENT:{},TEMPERATURE:{},LUMINOUS_INTENSITY:{},AMOUNT_OF_SUBSTANCE:{},FORCE:{},SURFACE:{},VOLUME:{},ANGLE:{},BIT:{}};var J=e.BASE_UNITS,et=e.PREFIXES;e.BASE_UNIT_NONE={},e.UNIT_NONE={name:"",base:e.BASE_UNIT_NONE,value:1,offset:0},e.UNITS=[{name:"meter",base:J.LENGTH,prefixes:et.LONG,value:1,offset:0},{name:"inch",base:J.LENGTH,prefixes:et.NONE,value:.0254,offset:0},{name:"foot",base:J.LENGTH,prefixes:et.NONE,value:.3048,offset:0},{name:"yard",base:J.LENGTH,prefixes:et.NONE,value:.9144,offset:0},{name:"mile",base:J.LENGTH,prefixes:et.NONE,value:1609.344,offset:0},{name:"link",base:J.LENGTH,prefixes:et.NONE,value:.201168,offset:0},{name:"rod",base:J.LENGTH,prefixes:et.NONE,value:5.02921,offset:0},{name:"chain",base:J.LENGTH,prefixes:et.NONE,value:20.1168,offset:0},{name:"angstrom",base:J.LENGTH,prefixes:et.NONE,value:1e-10,offset:0},{name:"m",base:J.LENGTH,prefixes:et.SHORT,value:1,offset:0},{name:"ft",base:J.LENGTH,prefixes:et.NONE,value:.3048,offset:0},{name:"yd",base:J.LENGTH,prefixes:et.NONE,value:.9144,offset:0},{name:"mi",base:J.LENGTH,prefixes:et.NONE,value:1609.344,offset:0},{name:"li",base:J.LENGTH,prefixes:et.NONE,value:.201168,offset:0},{name:"rd",base:J.LENGTH,prefixes:et.NONE,value:5.02921,offset:0},{name:"ch",base:J.LENGTH,prefixes:et.NONE,value:20.1168,offset:0},{name:"mil",base:J.LENGTH,prefixes:et.NONE,value:254e-7,offset:0},{name:"m2",base:J.SURFACE,prefixes:et.SHORT,value:1,offset:0},{name:"sqin",base:J.SURFACE,prefixes:et.NONE,value:64516e-8,offset:0},{name:"sqft",base:J.SURFACE,prefixes:et.NONE,value:.09290304,offset:0},{name:"sqyd",base:J.SURFACE,prefixes:et.NONE,value:.83612736,offset:0},{name:"sqmi",base:J.SURFACE,prefixes:et.NONE,value:2589988.110336,offset:0},{name:"sqrd",base:J.SURFACE,prefixes:et.NONE,value:25.29295,offset:0},{name:"sqch",base:J.SURFACE,prefixes:et.NONE,value:404.6873,offset:0},{name:"sqmil",base:J.SURFACE,prefixes:et.NONE,value:6.4516e-10,offset:0},{name:"m3",base:J.VOLUME,prefixes:et.SHORT,value:1,offset:0},{name:"L",base:J.VOLUME,prefixes:et.SHORT,value:.001,offset:0},{name:"litre",base:J.VOLUME,prefixes:et.LONG,value:.001,offset:0},{name:"cuin",base:J.VOLUME,prefixes:et.NONE,value:16387064e-12,offset:0},{name:"cuft",base:J.VOLUME,prefixes:et.NONE,value:.028316846592,offset:0},{name:"cuyd",base:J.VOLUME,prefixes:et.NONE,value:.764554857984,offset:0},{name:"teaspoon",base:J.VOLUME,prefixes:et.NONE,value:5e-6,offset:0},{name:"tablespoon",base:J.VOLUME,prefixes:et.NONE,value:15e-6,offset:0},{name:"minim",base:J.VOLUME,prefixes:et.NONE,value:6.161152e-8,offset:0},{name:"fluiddram",base:J.VOLUME,prefixes:et.NONE,value:36966911e-13,offset:0},{name:"fluidounce",base:J.VOLUME,prefixes:et.NONE,value:2957353e-11,offset:0},{name:"gill",base:J.VOLUME,prefixes:et.NONE,value:.0001182941,offset:0},{name:"cup",base:J.VOLUME,prefixes:et.NONE,value:.0002365882,offset:0},{name:"pint",base:J.VOLUME,prefixes:et.NONE,value:.0004731765,offset:0},{name:"quart",base:J.VOLUME,prefixes:et.NONE,value:.0009463529,offset:0},{name:"gallon",base:J.VOLUME,prefixes:et.NONE,value:.003785412,offset:0},{name:"beerbarrel",base:J.VOLUME,prefixes:et.NONE,value:.1173478,offset:0},{name:"oilbarrel",base:J.VOLUME,prefixes:et.NONE,value:.1589873,offset:0},{name:"hogshead",base:J.VOLUME,prefixes:et.NONE,value:.238481,offset:0},{name:"fldr",base:J.VOLUME,prefixes:et.NONE,value:36966911e-13,offset:0},{name:"floz",base:J.VOLUME,prefixes:et.NONE,value:2957353e-11,offset:0},{name:"gi",base:J.VOLUME,prefixes:et.NONE,value:.0001182941,offset:0},{name:"cp",base:J.VOLUME,prefixes:et.NONE,value:.0002365882,offset:0},{name:"pt",base:J.VOLUME,prefixes:et.NONE,value:.0004731765,offset:0},{name:"qt",base:J.VOLUME,prefixes:et.NONE,value:.0009463529,offset:0},{name:"gal",base:J.VOLUME,prefixes:et.NONE,value:.003785412,offset:0},{name:"bbl",base:J.VOLUME,prefixes:et.NONE,value:.1173478,offset:0},{name:"obl",base:J.VOLUME,prefixes:et.NONE,value:.1589873,offset:0},{name:"g",base:J.MASS,prefixes:et.SHORT,value:.001,offset:0},{name:"gram",base:J.MASS,prefixes:et.LONG,value:.001,offset:0},{name:"ton",base:J.MASS,prefixes:et.SHORT,value:907.18474,offset:0},{name:"tonne",base:J.MASS,prefixes:et.SHORT,value:1e3,offset:0},{name:"grain",base:J.MASS,prefixes:et.NONE,value:6479891e-11,offset:0},{name:"dram",base:J.MASS,prefixes:et.NONE,value:.0017718451953125,offset:0},{name:"ounce",base:J.MASS,prefixes:et.NONE,value:.028349523125,offset:0},{name:"poundmass",base:J.MASS,prefixes:et.NONE,value:.45359237,offset:0},{name:"hundredweight",base:J.MASS,prefixes:et.NONE,value:45.359237,offset:0},{name:"stick",base:J.MASS,prefixes:et.NONE,value:.115,offset:0},{name:"gr",base:J.MASS,prefixes:et.NONE,value:6479891e-11,offset:0},{name:"dr",base:J.MASS,prefixes:et.NONE,value:.0017718451953125,offset:0},{name:"oz",base:J.MASS,prefixes:et.NONE,value:.028349523125,offset:0},{name:"lbm",base:J.MASS,prefixes:et.NONE,value:.45359237,offset:0},{name:"cwt",base:J.MASS,prefixes:et.NONE,value:45.359237,offset:0},{name:"s",base:J.TIME,prefixes:et.SHORT,value:1,offset:0},{name:"min",base:J.TIME,prefixes:et.NONE,value:60,offset:0},{name:"h",base:J.TIME,prefixes:et.NONE,value:3600,offset:0},{name:"seconds",base:J.TIME,prefixes:et.LONG,value:1,offset:0},{name:"second",base:J.TIME,prefixes:et.LONG,value:1,offset:0},{name:"sec",base:J.TIME,prefixes:et.LONG,value:1,offset:0},{name:"minutes",base:J.TIME,prefixes:et.NONE,value:60,offset:0},{name:"minute",base:J.TIME,prefixes:et.NONE,value:60,offset:0},{name:"hours",base:J.TIME,prefixes:et.NONE,value:3600,offset:0},{name:"hour",base:J.TIME,prefixes:et.NONE,value:3600,offset:0},{name:"day",base:J.TIME,prefixes:et.NONE,value:86400,offset:0},{name:"days",base:J.TIME,prefixes:et.NONE,value:86400,offset:0},{name:"rad",base:J.ANGLE,prefixes:et.NONE,value:1,offset:0},{name:"deg",base:J.ANGLE,prefixes:et.NONE,value:.017453292519943295,offset:0},{name:"grad",base:J.ANGLE,prefixes:et.NONE,value:.015707963267948967,offset:0},{name:"cycle",base:J.ANGLE,prefixes:et.NONE,value:6.283185307179586,offset:0},{name:"A",base:J.CURRENT,prefixes:et.SHORT,value:1,offset:0},{name:"ampere",base:J.CURRENT,prefixes:et.LONG,value:1,offset:0},{name:"K",base:J.TEMPERATURE,prefixes:et.NONE,value:1,offset:0},{name:"degC",base:J.TEMPERATURE,prefixes:et.NONE,value:1,offset:273.15},{name:"degF",base:J.TEMPERATURE,prefixes:et.NONE,value:1/1.8,offset:459.67},{name:"degR",base:J.TEMPERATURE,prefixes:et.NONE,value:1/1.8,offset:0},{name:"kelvin",base:J.TEMPERATURE,prefixes:et.NONE,value:1,offset:0},{name:"celsius",base:J.TEMPERATURE,prefixes:et.NONE,value:1,offset:273.15},{name:"fahrenheit",base:J.TEMPERATURE,prefixes:et.NONE,value:1/1.8,offset:459.67},{name:"rankine",base:J.TEMPERATURE,prefixes:et.NONE,value:1/1.8,offset:0},{name:"mol",base:J.AMOUNT_OF_SUBSTANCE,prefixes:et.NONE,value:1,offset:0},{name:"mole",base:J.AMOUNT_OF_SUBSTANCE,prefixes:et.NONE,value:1,offset:0},{name:"cd",base:J.LUMINOUS_INTENSITY,prefixes:et.NONE,value:1,offset:0},{name:"candela",base:J.LUMINOUS_INTENSITY,prefixes:et.NONE,value:1,offset:0},{name:"N",base:J.FORCE,prefixes:et.SHORT,value:1,offset:0},{name:"newton",base:J.FORCE,prefixes:et.LONG,value:1,offset:0},{name:"lbf",base:J.FORCE,prefixes:et.NONE,value:4.4482216152605,offset:0},{name:"poundforce",base:J.FORCE,prefixes:et.NONE,value:4.4482216152605,offset:0},{name:"b",base:J.BIT,prefixes:et.BINARY_SHORT,value:1,offset:0},{name:"bits",base:J.BIT,prefixes:et.BINARY_LONG,value:1,offset:0},{name:"B",base:J.BIT,prefixes:et.BINARY_SHORT,value:8,offset:0},{name:"bytes",base:J.BIT,prefixes:et.BINARY_LONG,value:8,offset:0}],Z.type.Complex=r,r.prototype.copy=function(){return new r(this.re,this.im)},r.prototype.toString=function(){var e="";return e=0===this.im?Q.format(this.re):0===this.re?1===this.im?"i":-1===this.im?"-i":Q.format(this.im)+"i":this.im>0?1==this.im?Q.format(this.re)+" + i":Q.format(this.re)+" + "+Q.format(this.im)+"i":-1==this.im?Q.format(this.re)+" - i":Q.format(this.re)+" - "+Q.format(Math.abs(this.im))+"i"},r.doc={name:"Complex",category:"type",syntax:["a + bi","a + b * i"],description:"A complex value a + bi, where a is the real part and b is the complex part, and i is the imaginary number defined as sqrt(-1).",examples:["2 + 3i","sqrt(-4)","(1.2 -5i) * 2"],seealso:["abs","arg","conj","im","re"]},Z.E=Math.E,Z.LN2=Math.LN2,Z.LN10=Math.LN10,Z.LOG2E=Math.LOG2E,Z.LOG10E=Math.LOG10E,Z.PI=Math.PI,Z.SQRT1_2=Math.SQRT1_2,Z.SQRT2=Math.SQRT2,Z.I=new r(0,-1),Z.pi=Z.PI,Z.e=Z.E,Z.i=Z.I,Z.help=f,Z.abs=A,f.doc={name:"help",category:"Utils",syntax:["help(object)"],description:"Display documentation on a function or data type.",examples:['help("sqrt")','help("Complex")'],seealso:[]},Z["typeof"]=h,h.doc={name:"typeof",category:"Utils",syntax:["typeof(x)"],description:"Get the type of a variable.",examples:["typeof(3.5)","typeof(2 - 4i)","typeof(45 deg)",'typeof("hello world")'],seealso:[]},Z.min=c,c.doc={name:"min",category:"Statistics",syntax:["min(a, b, c, ...)"],description:"Compute the minimum value of a list of values.",examples:["max(2, 3, 4, 1)","max(2.7, 7.1, -4.5, 2.0, 4.1)","min(2.7, 7.1, -4.5, 2.0, 4.1)"],seealso:["sum","prod","avg","var","std","min","median"]},Z.max=l,l.doc={name:"max",category:"Statistics",syntax:["max(a, b, c, ...)"],description:"Compute the maximum value of a list of values.",examples:["max(2, 3, 4, 1)","max(2.7, 7.1, -4.5, 2.0, 4.1)","min(2.7, 7.1, -4.5, 2.0, 4.1)"],seealso:["sum","prod","avg","var","std","min","median"]},Z["in"]=p,p.doc={name:"in",category:"Units",syntax:["x in unit","in(x, unit)"],description:"Change the unit of a value.",examples:["5 inch in cm","3.2kg in g","16 bytes in bits"],seealso:[]},Z.sin=m,m.doc={name:"sin",category:"Trigonometry",syntax:["sin(x)"],description:"Compute the sine of x in radians.",examples:["sin(2)","sin(pi / 4) ^ 2","sin(90 deg)","sin(30 deg)","sin(0.2)^2 + cos(0.2)^2"],seealso:["asin","cos","tan"]},Z.atan2=v,v.doc={name:"atan2",category:"Trigonometry",syntax:["atan2(y, x)"],description:"Computes the principal value of the arc tangent of y/x in radians.",examples:["atan2(2, 2) / pi","angle = 60 deg in rad","x = cos(angle)","y = sin(angle)","atan2(y, x)"],seealso:["sin","cos","tan"]},Z.asin=d,d.doc={name:"asin",category:"Trigonometry",syntax:["asin(x)"],description:"Compute the inverse sine of a value in radians.",examples:["asin(0.5)","asin(sin(2.3))"],seealso:["sin","acos","asin"]},Z.atan=g,g.doc={name:"atan",category:"Trigonometry",syntax:["atan(x)"],description:"Compute the inverse tangent of a value in radians.",examples:["atan(0.5)","atan(tan(2.3))"],seealso:["tan","acos","asin"]},Z.cos=x,x.doc={name:"cos",category:"Trigonometry",syntax:["cos(x)"],description:"Compute the cosine of x in radians.",examples:["cos(2)","cos(pi / 4) ^ 2","cos(180 deg)","cos(60 deg)","sin(0.2)^2 + cos(0.2)^2"],seealso:["acos","sin","tan"]},Z.tan=y,y.doc={name:"tan",category:"Trigonometry",syntax:["tan(x)"],description:"Compute the tangent of x in radians.",examples:["tan(0.5)","sin(0.5) / cos(0.5)","tan(pi / 4)","tan(45 deg)"],seealso:["atan","sin","cos"]},Z.acos=E,E.doc={name:"acos",category:"Trigonometry",syntax:["acos(x)"],description:"Compute the inverse cosine of a value in radians.",examples:["acos(0.5)","acos(cos(2.3))"],seealso:["cos","acos","asin"]},Z.divide=N,N.doc={name:"divide",category:"Operators",syntax:["x / y","divide(x, y)"],description:"Divide two values.",examples:["2 / 3","ans * 3","4.5 / 2","3 + 4 / 2","(3 + 4) / 2","18 km / 4.5"],seealso:["multiply"]},Z.round=b,b.doc={name:"round",category:"Arithmetic",syntax:["round(x)","round(x, n)"],description:"round a value towards the nearest integer.If x is complex, both real and imaginary part are rounded towards the nearest integer. When n is specified, the value is rounded to n decimals.",examples:["round(3.2)","round(3.8)","round(-4.2)","round(-4.8)","round(pi, 3)","round(123.45678, 2)"],seealso:["ceil","floor","fix"]},Z.fix=O,O.doc={name:"fix",category:"Arithmetic",syntax:["fix(x)"],description:"Round a value towards zero.If x is complex, both real and imaginary part are rounded towards zero.",examples:["fix(3.2)","fix(3.8)","fix(-4.2)","fix(-4.8)"],seealso:["ceil","floor","round"]},Z.add=T,T.doc={name:"add",category:"Operators",syntax:["x + y","add(x, y)"],description:"Add two values.",examples:["2.1 + 3.6","ans - 3.6","3 + 2i",'"hello" + " world"',"3 cm + 2 inch"],seealso:["subtract"]},Z.exp=S,S.doc={name:"exp",category:"Arithmetic",syntax:["exp(x)"],description:"Calculate the exponent of a value.",examples:["exp(1.3)","e ^ 1.3","log(exp(1.3))","x = 2.4","(exp(i*x) == cos(x) + i*sin(x)) # Euler's formula"],seealso:["square","multiply","log"]},Z.sqrt=k,k.doc={name:"sqrt",category:"Arithmetic",syntax:["sqrt(x)"],description:"Compute the square root value. If x = y * y, then y is the square root of x.",examples:["sqrt(25)","5 * 5","sqrt(-1)"],seealso:["square","multiply"]},Z.larger=U,U.doc={name:"larger",category:"Operators",syntax:["x > y","larger(x, y)"],description:"Check if value x is larger y. Returns 1 if x is larger than y, and 0 if not.",examples:["2 > 3","5 > 2*2","a = 3.3","b = 6-2.8","(a > b)","(b < a)","5 cm > 2 inch"],seealso:["equal","unequal","smaller","smallereq","largereq"]},Z.unaryminus=_,_.doc={name:"unaryminus",category:"Operators",syntax:["-x","unaryminus(x)"],description:"Inverse the sign of a value.",examples:["-4.5","-(-5.6)"],seealso:["add","subtract"]},Z.smaller=L,L.doc={name:"smaller",category:"Operators",syntax:["x < y","smaller(x, y)"],description:"Check if value a is smaller value b. Returns 1 if x is smaller than y, and 0 if not.",examples:["2 < 3","5 < 2*2","a = 3.3","b = 6-2.8","(a < b)","5 cm < 2 inch"],seealso:["equal","unequal","larger","smallereq","largereq"]},Z.abs=A,A.doc={name:"abs",category:"Arithmetic",syntax:["abs(x)"],description:"Compute the absolute value.",examples:["abs(3.5)","abs(-4.2)"],seealso:["sign"]},Z.log=R,R.doc={name:"log",category:"Arithmetic",syntax:["log(x)"],description:"Compute the natural logarithm of a value.",examples:["log(3.5)","a = log(2.4)","exp(a)","log(1000) / log(10)"],seealso:["exp","logb","log10"]},Z.pow=I,I.doc={name:"pow",category:"Operators",syntax:["x ^ y","pow(x, y)"],description:"Calculates the power of x to y, x^y.",examples:["2^3 = 8","2*2*2","1 + e ^ (pi * i)"],seealso:["unequal","smaller","larger","smallereq","largereq"]},Z.floor=P,P.doc={name:"floor",category:"Arithmetic",syntax:["floor(x)"],description:"Round a value towards minus infinity.If x is complex, both real and imaginary part are rounded towards minus infinity.",examples:["floor(3.2)","floor(3.8)","floor(-4.2)"],seealso:["ceil","fix","round"]},Z.ceil=q,q.doc={name:"ceil",category:"Arithmetic",syntax:["ceil(x)"],description:"Round a value towards plus infinity.If x is complex, both real and imaginary part are rounded towards plus infinity.",examples:["ceil(3.2)","ceil(3.8)","ceil(-4.2)"],seealso:["floor","fix","round"]},Z.multiply=G,G.doc={name:"multiply",category:"Operators",syntax:["x * y","multiply(x, y)"],description:"multiply two values.",examples:["2.1 * 3.6","ans / 3.6","2 * 3 + 4","2 * (3 + 4)","3 * 2.1 km"],seealso:["divide"]},Z.subtract=Y,Y.doc={name:"subtract",category:"Operators",syntax:["x - y","subtract(x, y)"],description:"subtract two values.",examples:["5.3 - 2","ans + 2","2/3 - 1/6","2 * 3 - 3","2.1 km - 500m"],seealso:["add"]},Z.random=V,V.doc={name:"random",category:"Probability",syntax:["random()"],description:"Return a random number between 0 and 1.",examples:["random()","100 * random()"],seealso:[]},Z.parser.node.Node=F,F.prototype.eval=function(){throw Error("Cannot evaluate a Node interface") +},F.prototype.toString=function(){return""},D.prototype=new F,Z.parser.node.Function=D,D.prototype.hasParams=function(){return void 0!=this.params&&this.params.length>0},D.prototype.eval=function(){var e=this.fn;if(void 0===e)throw Error("Undefined symbol "+this.name);var t=this.params.map(function(e){return e.eval()});return e.apply(this,t)},D.prototype.toString=function(){if(this.name&&!this.params)return this.name;var e=this.name;return this.params&&this.params.length&&(e+="("+this.params.join(", ")+")"),e},H.prototype=new F,Z.parser.node.Constant=H,H.prototype.eval=function(){return this.value},H.prototype.toString=function(){return this.value?""+this.value:""},K.prototype=new F,Z.parser.node.Block=K,K.prototype.add=function(e,t){var n=this.params.length;this.params[n]=e,this.visible[n]=void 0!=t?t:!0},K.prototype.eval=function(){for(var e=[],t=0,n=this.params.length;n>t;t++){var i=this.params[t].eval();this.visible[t]&&e.push(i)}return e},K.prototype.toString=function(){for(var e=[],t=0,n=this.params.length;n>t;t++)this.visible[t]&&e.push("\n "+(""+this.params[t]));return"["+e.join(",")+"\n]"},z.prototype=new F,Z.parser.node.Assignment=z,z.prototype.eval=function(){if(void 0===this.expr)throw Error("Undefined symbol "+this.name);var e,t=this.params;if(t&&t.length){var n=[];this.params.forEach(function(e){n.push(e.eval())});var i=this.expr.eval();if(void 0==this.result.value)throw Error("Undefined symbol "+this.name);var r=this.result.eval();e=r.set(n,i),this.result.value=e}else e=this.expr.eval(),this.result.value=e;return e},z.prototype.toString=function(){var e="";return e+=this.name,this.params&&this.params.length&&(e+="("+this.params.join(", ")+")"),e+=" = ",e+=""+this.expr},j.prototype=new F,Z.parser.node.FunctionAssignment=j,j.prototype.createFunction=function(e,t,n,i){var r=function(){var t=n?n.length:0,r=arguments?arguments.length:0;if(t!=r)throw o(e,r,t);if(t>0)for(var a=0;t>a;a++)n[a].value=arguments[a];return i.eval()};return r.toString=function(){return e+"("+t.join(", ")+")"},r},j.prototype.eval=function(){for(var e=this.variables,t=this.values,n=0,i=e.length;i>n;n++)e[n].value=t[n];return this.result.value=this.def,this.def},j.prototype.toString=function(){return""+this.def},Z.parser.node.Scope=W,W.prototype.createNestedScope=function(){var e=new W(this);return this.nestedScopes||(this.nestedScopes=[]),this.nestedScopes.push(e),e},W.prototype.clear=function(){if(this.symbols={},this.defs={},this.links={},this.updates={},this.nestedScopes)for(var e=this.nestedScopes,t=0,n=e.length;n>t;t++)e[t].clear()},W.prototype.createSymbol=function(e){var t=this.symbols[e];if(!t){var n=this.findDef(e);t=this.newSymbol(e,n),this.symbols[e]=t}return t},W.prototype.newSymbol=function(e,t){var n=function(){if(!n.value)throw Error("Undefined symbol "+e);return"function"==typeof n.value?n.value.apply(null,arguments):n.value};return n.value=t,n.toString=function(){return n.value?""+n.value:""},n},W.prototype.createLink=function(e){var t=this.links[e];return t||(t=this.createSymbol(e),this.links[e]=t),t},W.prototype.createDef=function(e){var t=this.defs[e];return t||(t=this.createSymbol(e),this.defs[e]=t),t},W.prototype.createUpdate=function(e){var t=this.updates[e];return t||(t=this.createLink(e),this.updates[e]=t),t},W.prototype.findDef=function(t){function n(e,t){var n=a(e,t);return s[e]=n,o[e]=n,n}var i;if(i=this.defs[t])return i;if(i=this.updates[t])return i;if(this.parentScope)return this.parentScope.findDef(t);var a=this.newSymbol,s=this.symbols,o=this.defs;if("pi"==t)return n(t,Z.PI);if("e"==t)return n(t,Z.E);if("i"==t)return n(t,new r(0,1));var f=Z[t];if(f)return n(t,f);if(e.isUnit(t)){var u=new e(void 0,t);return n(t,u)}return void 0},W.prototype.removeLink=function(e){delete this.links[e]},W.prototype.removeDef=function(e){delete this.defs[e]},W.prototype.removeUpdate=function(e){delete this.updates[e]},W.prototype.init=function(){var e=this.symbols,t=this.parentScope;for(var n in e)if(e.hasOwnProperty(n)){var i=e[n];i.set(t?t.findDef(n):void 0)}this.nestedScopes&&this.nestedScopes.forEach(function(e){e.init()})},W.prototype.hasLink=function(e){if(this.links[e])return!0;if(this.nestedScopes)for(var t=this.nestedScopes,n=0,i=t.length;i>n;n++)if(t[n].hasLink(e))return!0;return!1},W.prototype.hasDef=function(e){return void 0!=this.defs[e]},W.prototype.hasUpdate=function(e){return void 0!=this.updates[e]},W.prototype.getUndefinedSymbols=function(){var e=this.symbols,t=[];for(var n in e)if(e.hasOwnProperty(n)){var i=e[n];void 0==i.value&&t.push(i)}return this.nestedScopes&&this.nestedScopes.forEach(function(e){t=t.concat(e.getUndefinedSymbols())}),t},Z.parser.Parser=X,X.prototype.clear=function(){this.scope.clear()},X.prototype.parse=function(e,t){return this.expr=e||"",t||(t=this.scope),this.parse_start(t)},X.prototype.eval=function(e){var t=void 0;try{var n=this.parse(e);t=n.eval()}catch(i){t=i.toString?""+i:i}return t},X.prototype.getChar=function(){this.index++,this.c=this.expr.charAt(this.index)},X.prototype.getFirstChar=function(){this.index=0,this.c=this.expr.charAt(0)},X.prototype.getToken=function(){for(this.token_type=this.TOKENTYPE.NULL,this.token="";" "==this.c||" "==this.c;)this.getChar();if("#"==this.c)for(;"\n"!=this.c&&""!=this.c;)this.getChar();if(""==this.c)return this.token_type=this.TOKENTYPE.DELIMITER,void 0;if("-"==this.c||","==this.c||"("==this.c||")"==this.c||"["==this.c||"]"==this.c||'"'==this.c||"\n"==this.c||";"==this.c||":"==this.c)return this.token_type=this.TOKENTYPE.DELIMITER,this.token+=this.c,this.getChar(),void 0;if(this.isDelimiter(this.c))for(this.token_type=this.TOKENTYPE.DELIMITER;this.isDelimiter(this.c);)this.token+=this.c,this.getChar();else if(this.isDigitDot(this.c)){for(this.token_type=this.TOKENTYPE.NUMBER;this.isDigitDot(this.c);)this.token+=this.c,this.getChar();if("E"==this.c||"e"==this.c)for(this.token+=this.c,this.getChar(),("+"==this.c||"-"==this.c)&&(this.token+=this.c,this.getChar()),this.isDigit(this.c)||(this.token_type=this.TOKENTYPE.UNKNOWN);this.isDigit(this.c);)this.token+=this.c,this.getChar()}else{if(!this.isAlpha(this.c)){for(this.token_type=this.TOKENTYPE.UNKNOWN;""!=this.c;)this.token+=this.c,this.getChar();throw this.createSyntaxError('Syntax error in part "'+this.token+'"')}for(this.token_type=this.TOKENTYPE.SYMBOL;this.isAlpha(this.c)||this.isDigit(this.c);)this.token+=this.c,this.getChar()}},X.prototype.isDelimiter=function(e){return"&"==e||"|"==e||"<"==e||">"==e||"="==e||"+"==e||"/"==e||"*"==e||"%"==e||"^"==e||","==e||";"==e||"\n"==e||"!"==e},X.prototype.isValidSymbolName=function(e){for(var t=0,n=e.length;n>t;t++){var i=e.charAt(t),r=this.isAlpha(i);if(!r)return!1}return!0},X.prototype.isAlpha=function(e){return e>="a"&&"z">=e||e>="A"&&"Z">=e||"_"==e},X.prototype.isDigitDot=function(e){return e>="0"&&"9">=e||"."==e},X.prototype.isDigit=function(e){return e>="0"&&"9">=e},X.prototype.parse_start=function(e){this.getFirstChar(),this.getToken();var t;if(t=""==this.token?new H(void 0):this.parse_block(e),""!=this.token)throw this.token_type==this.TOKENTYPE.DELIMITER?this.createError("Unknown operator "+this.token):this.createSyntaxError('Unexpected part "'+this.token+'"');return t},X.prototype.parse_ans=function(e){var t=this.parse_function_assignment(e);if(!(t instanceof z)){var n="ans",i=void 0,r=e.createDef(n);return new z(n,i,t,r)}return t},X.prototype.parse_block=function(e){var t,n,i;for("\n"!=this.token&&";"!=this.token&&""!=this.token&&(t=this.parse_ans(e));"\n"==this.token||";"==this.token;)n||(n=new K,t&&(i=";"!=this.token,n.add(t,i))),this.getToken(),"\n"!=this.token&&";"!=this.token&&""!=this.token&&(t=this.parse_ans(e),i=";"!=this.token,n.add(t,i));return n?n:(t||(t=this.parse_ans(e)),t)},X.prototype.parse_function_assignment=function(e){if(this.token_type==this.TOKENTYPE.SYMBOL&&"function"==this.token){if(this.getToken(),this.token_type!=this.TOKENTYPE.SYMBOL)throw this.createSyntaxError("Function name expected");var t=this.token;if(this.getToken(),"("!=this.token)throw this.createSyntaxError("Opening parenthesis ( expected");for(var n=e.createNestedScope(),i=[],r=[];;){if(this.getToken(),this.token_type!=this.TOKENTYPE.SYMBOL)throw this.createSyntaxError("Variable name expected");var a=this.token,s=n.createDef(a);if(i.push(a),r.push(s),this.getToken(),","!=this.token){if(")"==this.token)break;throw this.createSyntaxError('Comma , or closing parenthesis ) expected"')}}if(this.getToken(),"="!=this.token)throw this.createSyntaxError("Equal sign = expected");this.getToken();var o=this.parse_range(n),f=e.createDef(t);return new j(t,i,r,o,f)}return this.parse_assignment(e)},X.prototype.parse_assignment=function(e){var t=!1;this.token_type==this.TOKENTYPE.SYMBOL&&(t=e.hasLink(this.token));var n=this.parse_range(e);if("="==this.token){if(!(n instanceof D))throw this.createSyntaxError("Variable expected at the left hand side of assignment operator =");var i=n.name,r=n.params;t||e.removeLink(i),this.getToken();var a=this.parse_range(e),s=n.hasParams()?e.createUpdate(i):e.createDef(i);return new z(i,r,a,s)}return n},X.prototype.parse_range=function(e){var t=this.parse_conditions(e);return t},X.prototype.parse_conditions=function(e){for(var t=this.parse_bitwise_conditions(e),n={"in":"in"};void 0!==n[this.token];){var i=this.token,r=Z[n[i]];this.getToken();var a=[t,this.parse_bitwise_conditions(e)];t=new D(i,r,a)}return t},X.prototype.parse_bitwise_conditions=function(e){var t=this.parse_comparison(e);return t},X.prototype.parse_comparison=function(e){for(var t=this.parse_addsubtract(e),n={"==":"equal","!=":"unequal","<":"smaller",">":"larger","<=":"smallereq",">=":"largereq"};void 0!==n[this.token];){var i=this.token,r=Z[n[i]];this.getToken();var a=[t,this.parse_addsubtract(e)];t=new D(i,r,a)}return t},X.prototype.parse_addsubtract=function(e){for(var t=this.parse_multiplydivide(e),n={"+":"add","-":"subtract"};void 0!==n[this.token];){var i=this.token,r=Z[n[i]];this.getToken();var a=[t,this.parse_multiplydivide(e)];t=new D(i,r,a)}return t},X.prototype.parse_multiplydivide=function(e){for(var t=this.parse_pow(e),n={"*":"multiply","/":"divide","%":"mod",mod:"mod"};void 0!==n[this.token];){var i=this.token,r=Z[n[i]];this.getToken();var a=[t,this.parse_pow(e)];t=new D(i,r,a)}return t},X.prototype.parse_pow=function(e){for(var t=this.parse_factorial(e);"^"==this.token;){var n=this.token,i=I;this.getToken();var r=[t,this.parse_factorial(e)];t=new D(n,i,r)}return t},X.prototype.parse_factorial=function(e){for(var t=this.parse_unaryminus(e);"!"==this.token;){var n=this.token,i=factorial;this.getToken();var r=[t];t=new D(n,i,r)}return t},X.prototype.parse_unaryminus=function(e){if("-"==this.token){var t=this.token,n=_;this.getToken();var i=[this.parse_plot(e)];return new D(t,n,i)}return this.parse_plot(e)},X.prototype.parse_plot=function(e){return this.parse_symbol(e)},X.prototype.parse_symbol=function(e){if(this.token_type==this.TOKENTYPE.SYMBOL){var t=this.token;this.getToken();var n=e.createLink(t),i=this.parse_arguments(e),r=new D(t,n,i);return r}return this.parse_string(e)},X.prototype.parse_arguments=function(e){var t=[];if("("==this.token){for(this.getToken(),t.push(this.parse_range(e));","==this.token;)this.getToken(),t.push(this.parse_range(e));if(")"!=this.token)throw this.createSyntaxError("Parenthesis ) missing");this.getToken()}return t},X.prototype.parse_string=function(e){if('"'==this.token){for(var t="",n="";""!=this.c&&('"'!=this.c||"\\"==n);)t+=this.c,n=this.c,this.getChar();if(this.getToken(),'"'!=this.token)throw this.createSyntaxError('End of string " missing');this.getToken();var i=new H(t);return i}return this.parse_matrix(e)},X.prototype.parse_matrix=function(e){return this.parse_number(e)},X.prototype.parse_number=function(t){if(this.token_type==this.TOKENTYPE.NUMBER){var n;n="."==this.token?0:Number(this.token),this.getToken();var i;if(this.token_type==this.TOKENTYPE.SYMBOL){if("i"==this.token||"I"==this.token)return i=new r(0,n),this.getToken(),new H(i);if(e.isUnit(this.token))return i=new e(n,this.token),this.getToken(),new H(i);throw this.createTypeError('Unknown unit "'+this.token+'"')}var a=new H(n);return a}return this.parse_parentheses(t)},X.prototype.parse_parentheses=function(e){if("("==this.token){this.getToken();var t=this.parse_range(e);if(")"!=this.token)throw this.createSyntaxError("Parenthesis ) expected");return this.getToken(),t}return this.parse_end(e)},X.prototype.parse_end=function(){throw""==this.token?this.createSyntaxError("Unexpected end of expression"):this.createSyntaxError("Value expected")},X.prototype.row=function(){return void 0},X.prototype.col=function(){return this.index-this.token.length+1},X.prototype.createErrorMessage=function(e){var t=this.row(),n=this.col();return void 0===t?void 0===n?e:e+" (col "+n+")":e+" (ln "+t+", col "+n+")"},X.prototype.createSyntaxError=function(e){return new SyntaxError(this.createErrorMessage(e))},X.prototype.createTypeError=function(e){return new TypeError(this.createErrorMessage(e))},X.prototype.createError=function(e){return Error(this.createErrorMessage(e))}})(); \ No newline at end of file diff --git a/src/exports.js b/src/exports.js index c0cd90c77..5f53b8ca1 100644 --- a/src/exports.js +++ b/src/exports.js @@ -3,7 +3,12 @@ */ var math = { type: {}, - parser: {} + parser: { + node: {} + }, + options: { + precision: 10 // number of decimals in formatted output + } }; /** diff --git a/src/function/arithmetic/divide.js b/src/function/arithmetic/divide.js index 5926cb64a..5d512bc9b 100644 --- a/src/function/arithmetic/divide.js +++ b/src/function/arithmetic/divide.js @@ -16,13 +16,13 @@ function divide(x, y) { } else if (y instanceof Complex) { // number / complex - return divideComplex(new Complex(x), y); + return divideComplex(new Complex(x, 0), y); } } else if (x instanceof Complex) { if (isNumber(y)) { // complex / number - return divideComplex(x, new Complex(y)); + return divideComplex(x, new Complex(y, 0)); } else if (y instanceof Complex) { // complex / complex diff --git a/src/function/arithmetic/multiply.js b/src/function/arithmetic/multiply.js index b4cb7a585..e8609e098 100644 --- a/src/function/arithmetic/multiply.js +++ b/src/function/arithmetic/multiply.js @@ -18,7 +18,7 @@ function multiply(x, y) { } else if (y instanceof Complex) { // number * complex - return multiplyComplex(new Complex(x), y); + return multiplyComplex(new Complex(x, 0), y); } else if (y instanceof Unit) { res = y.copy(); @@ -29,7 +29,7 @@ function multiply(x, y) { else if (x instanceof Complex) { if (isNumber(y)) { // complex * number - return multiplyComplex(x, new Complex(y)); + return multiplyComplex(x, new Complex(y, 0)); } else if (y instanceof Complex) { // complex * complex diff --git a/src/function/arithmetic/pow.js b/src/function/arithmetic/pow.js index ee7508200..19b96ac04 100644 --- a/src/function/arithmetic/pow.js +++ b/src/function/arithmetic/pow.js @@ -16,16 +16,16 @@ function pow(x, y) { return Math.pow(x, y); } else { - return powComplex(new Complex(x), new Complex(y)); + return powComplex(new Complex(x, 0), new Complex(y, 0)); } } else if (y instanceof Complex) { - return powComplex(new Complex(x), y); + return powComplex(new Complex(x, 0), y); } } else if (x instanceof Complex) { if (isNumber(y)) { - return powComplex(x, new Complex(y)); + return powComplex(x, new Complex(y, 0)); } else if (y instanceof Complex) { return powComplex(x, y); diff --git a/src/function/arithmetic/round.js b/src/function/arithmetic/round.js index 28a89c8a8..841095c35 100644 --- a/src/function/arithmetic/round.js +++ b/src/function/arithmetic/round.js @@ -65,7 +65,7 @@ math.round = round; * @return {Number} roundedValue */ function roundNumber (value, digits) { - var p = Math.pow(10, (digits != undefined) ? digits : options.precision); + var p = Math.pow(10, (digits != undefined) ? digits : math.options.precision); return Math.round(value * p) / p; } diff --git a/src/header.js b/src/header.js index 20a44290c..153a6f019 100644 --- a/src/header.js +++ b/src/header.js @@ -1,9 +1,20 @@ /** * math.js - * An extended math library. Includes a parser, real and complex values, units, - * matrices, strings, and a large set of functions and constants. * https://github.com/josdejong/mathjs * + * Math.js is an extensive math library for JavaScript and Node.js, + * compatible with JavaScript's built-in Math library. + * + * Features: + * - A flexible expression parser + * - Support for numbers, complex values, units, strings, arrays*, + * and matrices* + * - A large set of built-in functions and constants + * - Easily extensible with new functions and constants + * - Powerful and easy to use + * + * * Note: arrays and matrices are to be implemented. + * * @version @@version * @date @@date * diff --git a/src/options.js b/src/options.js deleted file mode 100644 index d297ea8ce..000000000 --- a/src/options.js +++ /dev/null @@ -1,9 +0,0 @@ -/** - * Settings for math.js - */ - -var options = { - precision: 10 // number of decimals in formatted output -}; - -math.options = options; \ No newline at end of file diff --git a/src/parser/Parser.js b/src/parser/Parser.js new file mode 100644 index 000000000..c95983f1e --- /dev/null +++ b/src/parser/Parser.js @@ -0,0 +1,1148 @@ +// TODO: do not use this.token, but a local variable var token for better speed? -> getToken() must return token. +// TODO: make all parse methods private + +/** + * @constructor math.parser.Parser + * TODO: add comments to the Parser constructor + */ +function Parser() { + // token types enumeration + this.TOKENTYPE = { + NULL : 0, + DELIMITER : 1, + NUMBER : 2, + SYMBOL : 3, + UNKNOWN : 4 + }; + + this.expr = ''; // current expression + this.index = 0; // current index in expr + this.c = ''; // current token character in expr + this.token = ''; // current token + this.token_type = this.TOKENTYPE.NULL; // type of the token + + this.scope = new Scope(); +} + +math.parser.Parser = Parser; + +/** + * Clear the scope with variables and functions + */ +Parser.prototype.clear = function () { + this.scope.clear(); +}; + +/** + * Parse an expression end return the parsed function node. + * The node can be evaluated via node.eval() + * @param {String} expr + * @param {Scope} [scope] + * @return {Node} node + * @throws {Error} + */ +Parser.prototype.parse = function (expr, scope) { + this.expr = expr || ''; + + if (!scope) { + scope = this.scope; + } + + return this.parse_start(scope); +}; + +/** + * Parse and evaluate the given expression + * @param {String} expr A string containing an expression, for example "2+3" + * @return {*} result The result, or undefined when the expression was + * empty + * @throws {Error} + */ +Parser.prototype.eval = function (expr) { + var result = undefined; + + try { + var node = this.parse(expr); + result = node.eval(); + } catch (err) { + result = err.toString ? err.toString() : err; + } + + return result; +}; + +/** + * Get the next character from the expression. + * The character is stored into the char t. + * If the end of the expression is reached, the function puts an empty + * string in t. + * @private + */ +Parser.prototype.getChar = function () { + this.index++; + this.c = this.expr.charAt(this.index); +}; + +/** + * Get the first character from the expression. + * The character is stored into the char t. + * If the end of the expression is reached, the function puts an empty + * string in t. + * @private + */ +Parser.prototype.getFirstChar = function () { + this.index = 0; + this.c = this.expr.charAt(0); +}; + +/** + * Get next token in the current string expr. + * Uses the Parser data expr, e, token, t, token_type and err + * The token and token type are available at this.token_type and this.token + * @private + */ +Parser.prototype.getToken = function () { + this.token_type = this.TOKENTYPE.NULL; + this.token = ''; + + // skip over whitespaces + while (this.c == ' ' || this.c == '\t') { // space or tab + this.getChar(); + } + + // skip comment + if (this.c == '#') { + while (this.c != '\n' && this.c != '') { + this.getChar(); + } + } + + // check for end of expression + if (this.c == '') { + // token is still empty + this.token_type = this.TOKENTYPE.DELIMITER; + return; + } + + // check for minus, comma, parentheses, quotes, newline, semicolon + if (this.c == '-' || this.c == ',' || + this.c == '(' || this.c == ')' || + this.c == '[' || this.c == ']' || + this.c == '\"' || this.c == '\n' || + this.c == ';' || this.c == ':') { + this.token_type = this.TOKENTYPE.DELIMITER; + this.token += this.c; + this.getChar(); + return; + } + + // check for operators (delimiters) + if (this.isDelimiter(this.c)) { + this.token_type = this.TOKENTYPE.DELIMITER; + while (this.isDelimiter(this.c)) { + this.token += this.c; + this.getChar(); + } + return; + } + + // check for a number + if (this.isDigitDot(this.c)) { + this.token_type = this.TOKENTYPE.NUMBER; + while (this.isDigitDot(this.c)) { + this.token += this.c; + this.getChar(); + } + + // check for scientific notation like "2.3e-4" or "1.23e50" + if (this.c == 'E' || this.c == 'e') { + this.token += this.c; + this.getChar(); + + if (this.c == '+' || this.c == '-') { + this.token += this.c; + this.getChar(); + } + + // Scientific notation MUST be followed by an exponent + if (!this.isDigit(this.c)) { + // this is no legal number, exponent is missing. + this.token_type = this.TOKENTYPE.UNKNOWN; + } + + while (this.isDigit(this.c)) { + this.token += this.c; + this.getChar(); + } + } + return; + } + // check for variables or functions + if (this.isAlpha(this.c)) { + this.token_type = this.TOKENTYPE.SYMBOL; + + while (this.isAlpha(this.c) || this.isDigit(this.c)) + { + this.token += this.c; + this.getChar(); + } + return; + } + + // something unknown is found, wrong characters -> a syntax error + this.token_type = this.TOKENTYPE.UNKNOWN; + while (this.c != '') { + this.token += this.c; + this.getChar(); + } + throw this.createSyntaxError('Syntax error in part "' + this.token + '"'); +}; + +/** + * checks if the given char c is a delimiter + * minus is not checked in this method (can be unary minus) + * @param {String} c a string with one character + * @return {Boolean} + * @private + */ +Parser.prototype.isDelimiter = function (c) { + return c == '&' || + c == '|' || + c == '<' || + c == '>' || + c == '=' || + c == '+' || + c == '/' || + c == '*' || + c == '%' || + c == '^' || + c == ',' || + c == ';' || + c == '\n' || + c == '!'; +}; + +/** + * Check if a given name is valid + * if not, an error is thrown + * @param {String} name + * @return {boolean} valid + * @private + */ +Parser.prototype.isValidSymbolName = function (name) { + for (var i = 0, iMax = name.length; i < iMax; i++) { + var c = name.charAt(i); + //var valid = (this.isAlpha(c) || (i > 0 && this.isDigit(c))); // TODO + var valid = (this.isAlpha(c)); + if (!valid) { + return false; + } + } + + return true; +}; + +/** + * checks if the given char c is a letter (upper or lower case) + * or underscore + * @param {String} c a string with one character + * @return {Boolean} + * @private + */ +Parser.prototype.isAlpha = function (c) { + return ((c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z') || + c == '_'); +}; + +/** + * checks if the given char c is a digit or dot + * @param {String} c a string with one character + * @return {Boolean} + * @private + */ +Parser.prototype.isDigitDot = function (c) { + return ((c >= '0' && c <= '9') || + c == '.'); +}; + +/** + * checks if the given char c is a digit + * @param {String} c a string with one character + * @return {Boolean} + * @private + */ +Parser.prototype.isDigit = function (c) { + return ((c >= '0' && c <= '9')); +}; + +/** + * Start of the parse levels below, in order of precedence + * @param {Scope} scope + * @return {Node} node + * @private + */ +Parser.prototype.parse_start = function (scope) { + // get the first character in expression + this.getFirstChar(); + + this.getToken(); + + var node; + if (this.token == '') { + // empty expression + node = new Constant(undefined); + } + else { + node = this.parse_block(scope); + } + + // check for garbage at the end of the expression + // an expression ends with a empty character '' and token_type DELIMITER + if (this.token != '') { + if (this.token_type == this.TOKENTYPE.DELIMITER) { + // user entered a not existing operator like "//" + + // TODO: give hints for aliases, for example with "<>" give as hint " did you mean != ?" + throw this.createError('Unknown operator ' + this.token); + } + else { + throw this.createSyntaxError('Unexpected part "' + this.token + '"'); + } + } + + return node; +}; + + +/** + * Parse assignment of ans. + * Ans is assigned when the expression itself is no variable or function + * assignment + * @param {Scope} scope + * @return {Node} node + * @private + */ +Parser.prototype.parse_ans = function (scope) { + var expression = this.parse_function_assignment(scope); + + // TODO: not so nice having to specify some special types here... + if (!(expression instanceof Assignment) + // !(expression instanceof FunctionAssignment) && // TODO + // !(expression instanceof plot) // TODO + ) { + // create a variable definition for ans + var name = 'ans'; + var params = undefined; + var link = scope.createDef(name); + return new Assignment(name, params, expression, link); + } + + return expression; +}; + + +/** + * Parse a block with expressions. Expressions can be separated by a newline + * character '\n', or by a semicolon ';'. In case of a semicolon, no output + * of the preceding line is returned. + * @param {Scope} scope + * @return {Node} node + * @private + */ +Parser.prototype.parse_block = function (scope) { + var node, block, visible; + + if (this.token != '\n' && this.token != ';' && this.token != '') { + node = this.parse_ans(scope); + } + + while (this.token == '\n' || this.token == ';') { + if (!block) { + // initialize the block + block = new Block(); + if (node) { + visible = (this.token != ';'); + block.add(node, visible); + } + } + + this.getToken(); + if (this.token != '\n' && this.token != ';' && this.token != '') { + node = this.parse_ans(scope); + + visible = (this.token != ';'); + block.add(node, visible); + } + } + + if (block) { + return block; + } + + if (!node) { + node = this.parse_ans(scope); + } + + return node; +}; + +/** + * Parse a function assignment like "function f(a,b) = a*b" + * @param {Scope} scope + * @return {Node} node + * @private + */ +Parser.prototype.parse_function_assignment = function (scope) { + // TODO: keyword 'function' must become a reserved keyword + if (this.token_type == this.TOKENTYPE.SYMBOL && this.token == 'function') { + // get function name + this.getToken(); + if (this.token_type != this.TOKENTYPE.SYMBOL) { + throw this.createSyntaxError('Function name expected'); + } + var name = this.token; + + // get parenthesis open + this.getToken(); + if (this.token != '(') { + throw this.createSyntaxError('Opening parenthesis ( expected'); + } + + // get function variables + var functionScope = scope.createNestedScope(); + var variableNames = []; + var variables = []; + while (true) { + this.getToken(); + if (this.token_type == this.TOKENTYPE.SYMBOL) { + // store parameter + var variableName = this.token; + var variable = functionScope.createDef(variableName); + variableNames.push(variableName); + variables.push(variable); + } + else { + throw this.createSyntaxError('Variable name expected'); + } + + this.getToken(); + if (this.token == ',') { + // ok, nothing to do, read next variable + } + else if (this.token == ')') { + // end of variable list encountered. break loop + break; + } + else { + throw this.createSyntaxError('Comma , or closing parenthesis ) expected"'); + } + } + + this.getToken(); + if (this.token != '=') { + throw this.createSyntaxError('Equal sign = expected'); + } + + // parse the expression, with the correct function scope + this.getToken(); + var expression = this.parse_range(functionScope); + var result = scope.createDef(name); + + return new FunctionAssignment(name, variableNames, variables, + expression, result); + } + + return this.parse_assignment(scope); +}; + +/** + * Assignment of a variable, can be a variable like "a=2.3" or a updating an + * existing variable like "matrix(2,3:5)=[6,7,8]" + * @param {Scope} scope + * @return {Node} node + * @private + */ +Parser.prototype.parse_assignment = function (scope) { + var linkExisted = false; + if (this.token_type == this.TOKENTYPE.SYMBOL) { + linkExisted = scope.hasLink(this.token); + } + + var node = this.parse_range(scope); + + if (this.token == '=') { + if (!(node instanceof Function)) { + throw this.createSyntaxError('Variable expected at the left hand side ' + + 'of assignment operator ='); + } + var name = node.name; + var params = node.params; + + if (!linkExisted) { + // we parsed the assignment as if it where an expression instead, + // therefore, a link was created to the symbol. This link must + // be cleaned up again, and only if it wasn't existing before + scope.removeLink(name); + } + + // parse the expression, with the correct function scope + this.getToken(); + var expression = this.parse_range(scope); + var link = node.hasParams() ? scope.createUpdate(name) : scope.createDef(name); + return new Assignment(name, params, expression, link); + } + + return node; +}; + +/** + * parse range, "start:end" or "start:step:end" + * @param {Scope} scope + * @return {Node} node + * @private + */ +Parser.prototype.parse_range = function (scope) { + var node = this.parse_conditions(scope); + + /* TODO: implement range + if (this.token == ':') { + var params = [node]; + + while (this.token == ':') { + this.getToken(); + params.push(this.parse_conditions(scope)); + } + + var fn = range; + var name = ':'; + node = new Function(name, fn, params); + } + */ + + return node; +}; + +/** + * conditions like and, or, in + * @param {Scope} scope + * @return {Node} node + * @private + */ +Parser.prototype.parse_conditions = function (scope) { + var node = this.parse_bitwise_conditions(scope); + + // TODO: precedence of And above Or? + var operators = { + 'in' : 'in' + /* TODO: implement conditions + 'and' : 'and', + '&&' : 'and', + 'or': 'or', + '||': 'or', + 'xor': 'xor' + */ + }; + while (operators[this.token] !== undefined) { + // TODO: with all operators: only load one instance of the operator, use the scope + var name = this.token; + var fn = math[operators[name]]; + + this.getToken(); + var params = [node, this.parse_bitwise_conditions(scope)]; + node = new Function(name, fn, params); + } + + return node; +}; + +/** + * conditional operators and bitshift + * @param {Scope} scope + * @return {Node} node + * @private + */ +Parser.prototype.parse_bitwise_conditions = function (scope) { + var node = this.parse_comparison(scope); + + /* TODO: implement bitwise conditions + var operators = { + '&' : 'bitwiseand', + '|' : 'bitwiseor', + // todo: bitwise xor? + '<<': 'bitshiftleft', + '>>': 'bitshiftright' + }; + while (operators[this.token] !== undefined) { + var name = this.token; + var fn = math[operators[name]]; + + this.getToken(); + var params = [node, this.parse_comparison()]; + node = new Function(name, fn, params); + } + */ + + return node; +}; + +/** + * comparison operators + * @param {Scope} scope + * @return {Node} node + * @private + */ +Parser.prototype.parse_comparison = function (scope) { + var node = this.parse_addsubtract(scope); + + var operators = { + '==': 'equal', + '!=': 'unequal', + '<': 'smaller', + '>': 'larger', + '<=': 'smallereq', + '>=': 'largereq' + }; + while (operators[this.token] !== undefined) { + var name = this.token; + var fn = math[operators[name]]; + + this.getToken(); + var params = [node, this.parse_addsubtract(scope)]; + node = new Function(name, fn, params); + } + + return node; +}; + +/** + * add or subtract + * @param {Scope} scope + * @return {Node} node + * @private + */ +Parser.prototype.parse_addsubtract = function (scope) { + var node = this.parse_multiplydivide(scope); + + var operators = { + '+': 'add', + '-': 'subtract' + }; + while (operators[this.token] !== undefined) { + var name = this.token; + var fn = math[operators[name]]; + + this.getToken(); + var params = [node, this.parse_multiplydivide(scope)]; + node = new Function(name, fn, params); + } + + return node; +}; + + +/** + * multiply, divide, modulus + * @param {Scope} scope + * @return {Node} node + * @private + */ +Parser.prototype.parse_multiplydivide = function (scope) { + var node = this.parse_pow(scope); + + var operators = { + '*': 'multiply', + '/': 'divide', + '%': 'mod', + 'mod': 'mod' + }; + while (operators[this.token] !== undefined) { + var name = this.token; + var fn = math[operators[name]]; + + this.getToken(); + var params = [node, this.parse_pow(scope)]; + node = new Function(name, fn, params); + } + + return node; +}; + +/** + * power + * @param {Scope} scope + * @return {Node} node + * @private + */ +Parser.prototype.parse_pow = function (scope) { + var node = this.parse_factorial(scope); + + while (this.token == '^') { + var name = this.token; + var fn = pow; + this.getToken(); + var params = [node, this.parse_factorial(scope)]; + + node = new Function(name, fn, params); + } + + return node; +}; + +/** + * Factorial + * @param {Scope} scope + * @return {Node} node + * @private + */ +Parser.prototype.parse_factorial = function (scope) { + var node = this.parse_unaryminus(scope); + + while (this.token == '!') { + var name = this.token; + var fn = factorial; + this.getToken(); + var params = [node]; + + node = new Function(name, fn, params); + } + + return node; +}; + +/** + * Unary minus + * @param {Scope} scope + * @return {Node} node + * @private + */ +Parser.prototype.parse_unaryminus = function (scope) { + if (this.token == '-') { + var name = this.token; + var fn = unaryminus; + this.getToken(); + var params = [this.parse_plot(scope)]; + + return new Function(name, fn, params); + } + + return this.parse_plot(scope); +}; + +/** + * parse plot + * @param {Scope} scope + * @return {Node} node + * @private + */ +Parser.prototype.parse_plot = function (scope) { + /* TODO: implement plot + if (this.token_type == this.TOKENTYPE.SYMBOL && + this.token == 'plot') { + this.getToken(); + + // parse the parentheses and parameters of the plot + // the parameters are something like: plot(sin(x), cos(x), x) + var functions = []; + if (this.token == '(') { + var plotScope = scope.createNestedScope(); + + this.getToken(); + functions.push(this.parse_range(plotScope)); + + // parse a list with parameters + while (this.token == ',') { + this.getToken(); + functions.push(this.parse_range(plotScope)); + } + + if (this.token != ')') { + throw this.createSyntaxError('Parenthesis ) missing'); + } + this.getToken(); + } + + // check what the variable of the functions is. + var variable = undefined; + var lastFunction = functions[functions.length - 1]; + if (lastFunction) { + // if the last function is a variable, remove it from the functions list + // and use its variable func + var lastIsSymbol = (lastFunction instanceof Function && + !lastFunction.hasParams()); + if (lastIsSymbol) { + functions.pop(); + variable = lastFunction.fn; + } + } + return new plot(functions, variable, plotScope); + } + */ + + return this.parse_symbol(scope); +}; + +/** + * parse symbols: functions, variables, constants, units + * @param {Scope} scope + * @return {Node} node + * @private + */ +Parser.prototype.parse_symbol = function (scope) { + if (this.token_type == this.TOKENTYPE.SYMBOL) { + var name = this.token; + + this.getToken(); + + var link = scope.createLink(name); + var arguments = this.parse_arguments(scope); // TODO: not so nice to "misuse" creating a Function + var symbol = new Function(name, link, arguments); + + /* TODO: parse arguments + // parse arguments + while (this.token == '(') { + symbol = this.parse_arguments(scope, symbol); + } + */ + return symbol; + } + + return this.parse_string(scope); +}; + +/** + * parse symbol parameters + * @param {Scope} scope + * @return {Node[]} arguments + * @private + */ +Parser.prototype.parse_arguments = function (scope) { + var arguments = []; + if (this.token == '(') { + // TODO: in case of Plot, create a new scope. + + this.getToken(); + arguments.push(this.parse_range(scope)); + + // parse a list with parameters + while (this.token == ',') { + this.getToken(); + arguments.push(this.parse_range(scope)); + } + + if (this.token != ')') { + throw this.createSyntaxError('Parenthesis ) missing'); + } + this.getToken(); + } + + return arguments; +}; + +/** + * parse a string. + * A string is enclosed by double quotes + * @param {Scope} scope + * @return {Node} node + * @private + */ +Parser.prototype.parse_string = function (scope) { + if (this.token == '"') { + // string "..." + var str = ''; + var tPrev = ''; + while (this.c != '' && (this.c != '\"' || tPrev == '\\')) { // also handle escape character + str += this.c; + tPrev = this.c; + this.getChar(); + } + + this.getToken(); + if (this.token != '"') { + throw this.createSyntaxError('End of string " missing'); + } + this.getToken(); + + var res = new Constant(str); + + /* TODO: implement string with arguments + // parse arguments + while (this.token == '(') { + res = this.parse_arguments(scope, res); + } + */ + + return res; + } + + return this.parse_matrix(scope); +}; + +/** + * parse the matrix + * @param {Scope} scope + * @return {Node} A MatrixNode + * @private + */ +Parser.prototype.parse_matrix = function (scope) { + /* TODO: implement matrix + if (this.token == '[') { + // matrix [...] + var matrix; + + // skip newlines + this.getToken(); + while (this.token == '\n') { + this.getToken(); + } + + // check if this is an empty matrix "[ ]" + if (this.token != ']') { + // this is a non-empty matrix + var params = []; + var r = 0, c = 0; + + params[0] = [this.parse_range(scope)]; + + // the columns in the matrix are separated by commas, and the rows by dot-comma's + while (this.token == ',' || this.token == ';') { + if (this.token == ',') { + c++; + } + else { + r++; + c = 0; + params[r] = []; + } + + // skip newlines + this.getToken(); + while (this.token == '\n') { + this.getToken(); + } + + params[r][c] = this.parse_range(scope); + + // skip newlines + while (this.token == '\n') { + this.getToken(); + } + } + + var rows = params.length; + var cols = (params.length > 0) ? params[0].length : 0; + + // check if the number of columns matches in all rows + for (r = 1; r < rows; r++) { + if (params[r].length != cols) { + throw this.createError('Number of columns must match ' + + '(' + params[r].length + ' != ' + cols + ')'); + } + } + + if (this.token != ']') { + throw this.createSyntaxError('End of matrix ] missing'); + } + + this.getToken(); + matrix = new MatrixNode(params); + } + else { + // this is an empty matrix "[ ]" + this.getToken(); + matrix = new MatrixNode(); + } + + // parse arguments + while (this.token == '(') { + matrix = this.parse_arguments(scope, matrix); + } + + return matrix; + } + */ + + return this.parse_number(scope); +}; + +/** + * parse a number + * @param {Scope} scope + * @return {Node} node + * @private + */ +Parser.prototype.parse_number = function (scope) { + if (this.token_type == this.TOKENTYPE.NUMBER) { + // this is a number + var number; + if (this.token == '.') { + number = 0.0; + } else { + number = Number(this.token); + } + this.getToken(); + + /* TODO: implicit multiplication? + // TODO: how to calculate a=3; 2/2a ? is this (2/2)*a or 2/(2*a) ? + // check for implicit multiplication + if (token_type == TOKENTYPE.VARIABLE) { + node = multiply(node, parse_pow()); + } + //*/ + + var value; + if (this.token_type == this.TOKENTYPE.SYMBOL) { + if (this.token == 'i' || this.token == 'I') { + value = new Complex(0, number); + this.getToken(); + return new Constant(value); + } + + if (Unit.isUnit(this.token)) { + value = new Unit(number, this.token); + this.getToken(); + return new Constant(value); + } + + throw this.createTypeError('Unknown unit "' + this.token + '"'); + } + + // just a regular number + var res = new Constant(number); + + /* TODO: implement number with arguments + // parse arguments + while (this.token == '(') { + res = this.parse_arguments(scope, res); + } + */ + + return res; + } + + return this.parse_parentheses(scope); +}; + +/** + * parentheses + * @param {Scope} scope + * @return {Node} res + * @private + */ +Parser.prototype.parse_parentheses = function (scope) { + // check if it is a parenthesized expression + if (this.token == '(') { + // parentheses (...) + this.getToken(); + var res = this.parse_range(scope); // start again + + if (this.token != ')') { + throw this.createSyntaxError('Parenthesis ) expected'); + } + this.getToken(); + + /* TODO: implicit multiplication? + // TODO: how to calculate a=3; 2/2a ? is this (2/2)*a or 2/(2*a) ? + // check for implicit multiplication + if (token_type == TOKENTYPE.VARIABLE) { + node = multiply(node, parse_pow()); + } + //*/ + + /* TODO: parse parentheses with arguments + // parse arguments + while (this.token == '(') { + res = this.parse_arguments(scope, res); + } + */ + + return res; + } + + return this.parse_end(scope); +}; + +/** + * Evaluated when the expression is not yet ended but expected to end + * @param {Scope} scope + * @return {Node} res + * @private + */ +Parser.prototype.parse_end = function (scope) { + if (this.token == '') { + // syntax error or unexpected end of expression + throw this.createSyntaxError('Unexpected end of expression'); + } else { + throw this.createSyntaxError('Value expected'); + } +}; + +/** + * Shortcut for getting the current row value (one based) + * Returns the line of the currently handled expression + * @private + */ +Parser.prototype.row = function () { + // TODO: also register row number during parsing + return undefined; +}; + +/** + * Shortcut for getting the current col value (one based) + * Returns the column (position) where the last token starts + * @private + */ +Parser.prototype.col = function () { + return this.index - this.token.length + 1; +}; + + +/** + * Build up an error message + * @param {String} message + * @return {String} message with row and column information + * @private + */ +Parser.prototype.createErrorMessage = function(message) { + var row = this.row(); + var col = this.col(); + if (row === undefined) { + if (col === undefined) { + return message; + } else { + return message + ' (col ' + col + ')'; + } + } else { + return message + ' (ln ' + row + ', col ' + col + ')'; + } +}; + +/** + * Create an error + * @param {String} message + * @return {SyntaxError} instantiated error + * @private + */ +Parser.prototype.createSyntaxError = function(message) { + return new SyntaxError(this.createErrorMessage(message)); +}; + +/** + * Create an error + * @param {String} message + * @return {TypeError} instantiated error + * @private + */ +Parser.prototype.createTypeError = function(message) { + return new TypeError(this.createErrorMessage(message)); +}; + +/** + * Create an error + * @param {String} message + * @return {Error} instantiated error + * @private + */ +Parser.prototype.createError = function(message) { + return new Error(this.createErrorMessage(message)); +}; diff --git a/src/parser/Scope.js b/src/parser/Scope.js new file mode 100644 index 000000000..ec50df890 --- /dev/null +++ b/src/parser/Scope.js @@ -0,0 +1,344 @@ +/** + * @License Apache 2.0 License + * + * @Author Jos de Jong + * @Date 2012-07-10 + */ + +/** + * Scope + * A scope stores functions. + * + * @constructor mathnotepad.Scope + * @param {Scope} [parentScope] + */ +function Scope(parentScope) { + this.parentScope = parentScope; + this.nestedScopes = undefined; + + this.symbols = {}; // the actual symbols + + // the following objects are just used to test existence. + this.defs = {}; // definitions by name (for example "a = [1, 2; 3, 4]") + this.updates = {}; // updates by name (for example "a(2, 1) = 5.2") + this.links = {}; // links by name (for example "2 * a") +} + +math.parser.node.Scope = Scope; + +/** + * Create a nested scope + * The variables in a nested scope are not accessible from the parent scope + * @return {Scope} nestedScope + */ +Scope.prototype.createNestedScope = function () { + var nestedScope = new Scope(this); + if (!this.nestedScopes) { + this.nestedScopes = []; + } + this.nestedScopes.push(nestedScope); + return nestedScope; +}; + +/** + * Clear all symbols in this scope and its nested scopes + * (parent scope will not be cleared) + */ +Scope.prototype.clear = function () { + this.symbols = {}; + this.defs = {}; + this.links = {}; + this.updates = {}; + + if (this.nestedScopes) { + var nestedScopes = this.nestedScopes; + for (var i = 0, iMax = nestedScopes.length; i < iMax; i++) { + nestedScopes[i].clear(); + } + } +}; + +/** + * create a symbol + * @param {String} name + * @return {function} symbol + * @private + */ +Scope.prototype.createSymbol = function (name) { + var symbol = this.symbols[name]; + if (!symbol) { + // get a link to the last definition + var lastDef = this.findDef(name); + + // create a new symbol + symbol = this.newSymbol(name, lastDef); + this.symbols[name] = symbol; + + } + return symbol; +}; + +/** + * Create a new symbol + * @param {String} name + * @param {*} [value] + * @return {function} symbol + * @private + */ +Scope.prototype.newSymbol = function (name, value) { + // create a new symbol + var symbol = function () { + if (!symbol.value) { + throw new Error('Undefined symbol ' + name); + } + if (typeof symbol.value == 'function') { + return symbol.value.apply(null, arguments); + } + else { + // TODO: implement subset for all types + return symbol.value; + } + }; + + symbol.value = value; + + symbol.toString = function () { + return symbol.value ? symbol.value.toString() : ''; + }; + + return symbol; +}; + +/** + * create a link to a value. + * @param {String} name + * @return {function} symbol + */ +Scope.prototype.createLink = function (name) { + var symbol = this.links[name]; + if (!symbol) { + symbol = this.createSymbol(name); + this.links[name] = symbol; + } + return symbol; +}; + +/** + * Create a variable definition + * Returns the created symbol + * @param {String} name + * @return {function} symbol + */ +Scope.prototype.createDef = function (name) { + var symbol = this.defs[name]; + if (!symbol) { + symbol = this.createSymbol(name); + this.defs[name] = symbol; + } + return symbol; +}; + +/** + * Create a variable update definition + * Returns the created symbol + * @param {String} name + * @return {function} symbol + */ +Scope.prototype.createUpdate = function (name) { + var symbol = this.updates[name]; + if (!symbol) { + symbol = this.createLink(name); + this.updates[name] = symbol; + } + return symbol; +}; + +/** + * get the link to a symbol definition or update. + * If the symbol is not found in this scope, it will be looked up in its parent + * scope. + * @param {String} name + * @return {function | undefined} symbol, or undefined when not found + */ +Scope.prototype.findDef = function (name) { + var symbol; + + // check scope + symbol = this.defs[name]; + if (symbol) { + return symbol; + } + symbol = this.updates[name]; + if (symbol) { + return symbol; + } + + // check parent scope + if (this.parentScope) { + return this.parentScope.findDef(name); + } + else { + // this is the root scope (has no parent) + + var newSymbol = this.newSymbol, + symbols = this.symbols, + defs = this.defs; + + /** + * Store a symbol in the root scope + * @param {String} name + * @param {*} value + * @return {function} symbol + */ + function put(name, value) { + var symbol = newSymbol(name, value); + symbols[name] = symbol; + defs[name] = symbol; + return symbol; + } + + // check constant (and load the constant) + if (name == 'pi') { + return put(name, math.PI); + } + if (name == 'e') { + return put(name, math.E); + } + if (name == 'i') { + return put(name, new Complex(0, 1)); + } + + // check function (and load the function), for example "sin" or "sqrt" + // search in the mathnotepad.math namespace for this symbol + var fn = math[name]; + if (fn) { + return put(name, fn); + } + + // Check if token is a unit + // Note: we do not check the upper case name, units are case sensitive! + if (Unit.isUnit(name)) { + var unit = new Unit(undefined, name); + return put(name, unit); + } + } + + return undefined; +}; + +/** + * Remove a link to a symbol + * @param {String} name + */ +Scope.prototype.removeLink = function (name) { + delete this.links[name]; +}; + +/** + * Remove a definition of a symbol + * @param {String} name + */ +Scope.prototype.removeDef = function (name) { + delete this.defs[name]; +}; + +/** + * Remove an update definition of a symbol + * @param {String} name + */ +Scope.prototype.removeUpdate = function (name) { + delete this.updates[name]; +}; + +/** + * initialize the scope and its nested scopes + * + * All functions are linked to their previous definition + * If there is no parentScope, or no definition of the func in the parent scope, + * the link will be set undefined + */ +Scope.prototype.init = function () { + var symbols = this.symbols; + var parentScope = this.parentScope; + + for (var name in symbols) { + if (symbols.hasOwnProperty(name)) { + var symbol = symbols[name]; + symbol.set(parentScope ? parentScope.findDef(name) : undefined); + } + } + + if (this.nestedScopes) { + this.nestedScopes.forEach(function (nestedScope) { + nestedScope.init(); + }); + } +}; + +/** + * Check whether this scope or any of its nested scopes contain a link to a + * symbol with given name + * @param {String} name + * @return {boolean} hasLink True if a link with given name is found + */ +Scope.prototype.hasLink = function (name) { + if (this.links[name]) { + return true; + } + + if (this.nestedScopes) { + var nestedScopes = this.nestedScopes; + for (var i = 0, iMax = nestedScopes.length; i < iMax; i++) { + if (nestedScopes[i].hasLink(name)) { + return true; + } + } + } + + return false; +}; + +/** + * Check whether this scope contains a definition of a symbol with given name + * @param {String} name + * @return {boolean} hasDef True if a definition with given name is found + */ +Scope.prototype.hasDef = function (name) { + return (this.defs[name] != undefined); +}; + +/** + * Check whether this scope contains an update definition of a symbol with + * given name + * @param {String} name + * @return {boolean} hasUpdate True if an update definition with given name is found + */ +Scope.prototype.hasUpdate = function (name) { + return (this.updates[name] != undefined); +}; + +/** + * Retrieve all undefined symbols + * @return {function[]} undefinedSymbols All symbols which are undefined + */ +Scope.prototype.getUndefinedSymbols = function () { + var symbols = this.symbols; + var undefinedSymbols = []; + for (var i in symbols) { + if (symbols.hasOwnProperty(i)) { + var symbol = symbols[i]; + if (symbol.value == undefined) { + undefinedSymbols.push(symbol); + } + } + } + + if (this.nestedScopes) { + this.nestedScopes.forEach(function (nestedScope) { + undefinedSymbols = + undefinedSymbols.concat(nestedScope.getUndefinedSymbols()); + }); + } + + return undefinedSymbols; +}; diff --git a/src/parser/node/Assignment.js b/src/parser/node/Assignment.js new file mode 100644 index 000000000..3afb1cfb9 --- /dev/null +++ b/src/parser/node/Assignment.js @@ -0,0 +1,74 @@ +/** + * @constructor mathnotepad.tree.Assignment + * @param {String} name Symbol name + * @param {Node[] | undefined} params Zero or more parameters + * @param {Node} expr The expression defining the symbol + * @param {function} result placeholder for the result + */ +function Assignment(name, params, expr, result) { + this.name = name; + this.params = params; + this.expr = expr; + this.result = result; +} + +Assignment.prototype = new Node(); + +math.parser.node.Assignment = Assignment; + +/** + * Evaluate the assignment + * @return {*} result + */ +Assignment.prototype.eval = function() { + if (this.expr === undefined) { + throw new Error('Undefined symbol ' + this.name); + } + + var result; + var params = this.params; + + if (params && params.length) { + // change part of a matrix, for example "a=[]", "a(2,3)=4.5" + var paramResults = []; + this.params.forEach(function (param) { + paramResults.push(param.eval()); + }); + + var exprResult = this.expr.eval(); + + // test if definition is currently undefined + if (this.result.value == undefined) { + throw new Error('Undefined symbol ' + this.name); + } + + var prevResult = this.result.eval(); + result = prevResult.set(paramResults, exprResult); + + this.result.value = result; + } + else { + // variable definition, for example "a = 3/4" + result = this.expr.eval(); + this.result.value = result; + } + + return result; +}; + +/** + * Get string representation + * @return {String} + */ +Assignment.prototype.toString = function() { + var str = ''; + + str += this.name; + if (this.params && this.params.length) { + str += '(' + this.params.join(', ') + ')'; + } + str += ' = '; + str += this.expr.toString(); + + return str; +}; diff --git a/src/parser/node/Block.js b/src/parser/node/Block.js new file mode 100644 index 000000000..1d9b30066 --- /dev/null +++ b/src/parser/node/Block.js @@ -0,0 +1,59 @@ +/** + * @constructor math.parser.node.Block + * Holds a set with nodes + * @extends {Node} + */ +function Block() { + this.params = []; + this.visible = []; +} + +Block.prototype = new Node(); + +math.parser.node.Block = Block; + +/** + * Add a parameter + * @param {Node} param + * @param {Boolean} [visible] true by default + */ +Block.prototype.add = function (param, visible) { + var index = this.params.length; + this.params[index] = param; + this.visible[index] = (visible != undefined) ? visible : true; +}; + +/** + * Evaluate the set + * @return {*[]} results + * @override + */ +Block.prototype.eval = function() { + // evaluate the parameters + var results = []; + for (var i = 0, iMax = this.params.length; i < iMax; i++) { + var result = this.params[i].eval(); + if (this.visible[i]) { + results.push(result); + } + } + + return results; +}; + +/** + * Get string representation + * @return {String} str + * @override + */ +Block.prototype.toString = function() { + var strings = []; + + for (var i = 0, iMax = this.params.length; i < iMax; i++) { + if (this.visible[i]) { + strings.push('\n ' + this.params[i].toString()); + } + } + + return '[' + strings.join(',') + '\n]'; +}; diff --git a/src/parser/node/Constant.js b/src/parser/node/Constant.js new file mode 100644 index 000000000..dfdf2c36f --- /dev/null +++ b/src/parser/node/Constant.js @@ -0,0 +1,28 @@ +/** + * @constructor math.parser.node.Constant + * @param {*} value + * @extends {Node} + */ +function Constant(value) { + this.value = value; +} + +Constant.prototype = new Node(); + +math.parser.node.Constant = Constant; + +/** + * Evaluate the constant + * @return {*} value + */ +Constant.prototype.eval = function () { + return this.value; +}; + +/** + * Get string representation + * @return {String} str + */ +Constant.prototype.toString = function() { + return this.value ? this.value.toString() : ''; +}; diff --git a/src/parser/node/Function.js b/src/parser/node/Function.js new file mode 100644 index 000000000..39119a41a --- /dev/null +++ b/src/parser/node/Function.js @@ -0,0 +1,75 @@ +/** + * @constructor math.parser.node.Function + * @param {String} [name] + * @param {function} fn + * @param {Node[]} params + * @extends {Node} + */ +function Function(name, fn, params) { + this.name = name; + this.fn = fn; + this.params = params; +} + +Function.prototype = new Node(); + +math.parser.node.Function = Function; + +/** + * Check whether the Function has one or multiple parameters set. + * @return {Boolean} + */ +Function.prototype.hasParams = function () { + return (this.params != undefined && this.params.length > 0); +}; + +/** + * Evaluate the symbol + * @return {*} result + * @override + */ +Function.prototype.eval = function() { + var fn = this.fn; + if (fn === undefined) { + throw new Error('Undefined symbol ' + this.name); + } + + // evaluate the parameters + var results = this.params.map(function (param) { + return param.eval(); + }); + + // evaluate the function + return fn.apply(this, results); +}; + +/** + * Get string representation + * @return {String} str + * @override + */ +Function.prototype.toString = function() { + // variable. format the symbol like "myvar" + if (this.name && !this.params) { + return this.name; + } + + /* TODO: determine if the function is an operator + // operator. format the operation like "(2 + 3)" + if (this.fn && (this.fn instanceof mathnotepad.fn.Operator)) { + if (this.params && this.params.length == 2) { + return '(' + + this.params[0].toString() + ' ' + + this.name + ' ' + + this.params[1].toString() + ')'; + } + } + */ + + // function. format the operation like "f(2, 4.2)" + var str = this.name; + if (this.params && this.params.length) { + str += '(' + this.params.join(', ') + ')'; + } + return str; +}; diff --git a/src/parser/node/FunctionAssignment.js b/src/parser/node/FunctionAssignment.js new file mode 100644 index 000000000..854797c6c --- /dev/null +++ b/src/parser/node/FunctionAssignment.js @@ -0,0 +1,99 @@ +/** + * @constructor FunctionAssignment + * assigns a custom defined function + * + * @param {String} name Function name + * @param {String[]} variableNames Variable names + * @param {function[]} variables Links to the variables in a scope + * @param {Node} expr The function expression + * @param {function} result Link to store the result + */ +function FunctionAssignment(name, variableNames, variables, expr, result) { + this.name = name; + this.variables = variables; + + this.values = []; + for (var i = 0, iMax = this.variables.length; i < iMax; i++) { + this.values[i] = (function () { + var value = function () { + return value.value; + }; + value.value = undefined; + return value; + })(); + } + + this.def = this.createFunction(name, variableNames, variables, expr); + + this.result = result; +} + +FunctionAssignment.prototype = new Node(); + +math.parser.node.FunctionAssignment = FunctionAssignment; + +/** + * Create a function from the function assignment + * @param {String} name Function name + * @param {String[]} variableNames Variable names + * @param {function[]} values Zero or more functions returning a value + * Each function contains a parameter + * name of type String and value of + * type mathnotepad.fn.Link + * @param {Node} expr The function expression + * + */ +FunctionAssignment.prototype.createFunction = function (name, variableNames, + values, expr) { + var fn = function () { + // validate correct number of arguments + var valuesNum = values ? values.length : 0; + var argumentsNum = arguments ? arguments.length : 0; + if (valuesNum != argumentsNum) { + throw newArgumentsError(name, argumentsNum, valuesNum); + } + + // fill in all parameter values + if (valuesNum > 0) { + for (var i = 0; i < valuesNum; i++){ + values[i].value = arguments[i]; + } + } + + // evaluate the expression + return expr.eval(); + }; + + fn.toString = function() { + return name + '(' + variableNames.join(', ') + ')'; + }; + + return fn; +}; + +/** + * Evaluate the function assignment + * @return {function} result + */ +FunctionAssignment.prototype.eval = function() { + // link the variables to the values of this function assignment + var variables = this.variables, + values = this.values; + for (var i = 0, iMax = variables.length; i < iMax; i++) { + variables[i].value = values[i]; + } + + // put the definition in the result + this.result.value = this.def; + + // TODO: what to return? a neat "function y(x) defined"? + return this.def; +}; + +/** + * get string representation + * @return {String} str + */ +FunctionAssignment.prototype.toString = function() { + return this.def.toString(); +}; diff --git a/src/parser/node/Node.js b/src/parser/node/Node.js new file mode 100644 index 000000000..9201b3415 --- /dev/null +++ b/src/parser/node/Node.js @@ -0,0 +1,22 @@ +/** + * Node + */ +function Node() {} + +math.parser.node.Node = Node; + +/** + * Evaluate the node + * @return {*} result + */ +Node.prototype.eval = function () { + throw new Error('Cannot evaluate a Node interface'); +}; + +/** + * Get string representation + * @return {String} + */ +Node.prototype.toString = function() { + return ''; +}; diff --git a/src/type/Complex.js b/src/type/Complex.js index 5654cba3f..cba3432b6 100644 --- a/src/type/Complex.js +++ b/src/type/Complex.js @@ -1,27 +1,133 @@ /** - * @constructor math.type.Complex + * @constructor Complex * - * @param {Number} [re] - * @param {Number} [im] + * A complex value can be constructed in three ways: + * var a = new Complex(re, im); + * var b = new Complex(str); + * var c = new Complex(); + * + * Example usage: + * var a = new Complex(3, -4); // 3 - 4i + * var b = new Complex('2 + 6i'); // 2 + 6i + * var c = new Complex(); // 0 + 0i + * var d = math.add(a, b); // 5 + 2i + * + * @param {Number | String} re A number with the real part of the complex + * value, or a string containing a complex number + * @param {Number} [im] The imaginary part of the complex value */ function Complex(re, im) { if (this.constructor != Complex) { - throw new Error('Complex constructor must be called with the new operator'); + throw new SyntaxError( + 'Complex constructor must be called with the new operator'); } - /** - * @type {Number} - */ - this.re = re || 0; + switch (arguments.length) { + case 2: + // re and im numbers provided + if (!isNumber(re) || !isNumber(im)) { + throw new TypeError( + 'Two numbers or a single string expected in Complex constructor'); + } + this.re = re; + this.im = im; + break; - /** - * @type {Number} - */ - this.im = im || 0; + case 1: + // parse string into a complex number + if (!isString(re)) { + throw new TypeError( + 'Two numbers or a single string expected in Complex constructor'); + } + + // TODO: replace by some nice regexp? + // TODO: also support a pattern like "-2.5e+3 - 7.6e-5i" + var parts = [], + part; + var separator = '+'; + var index = re.lastIndexOf(separator); + if (index == -1) { + separator = '-'; + index = re.lastIndexOf(separator); + } + + if (index != -1) { + part = trim(re.substring(0, index)); + if (part) { + parts.push(part); + } + part = trim(re.substring(index + 1)); + if (part) { + parts.push(separator + part); + } + } + else { + part = trim(re); + if (part) { + parts.push(part); + } + } + + var ok = false; + switch (parts.length) { + case 1: + part = parts[0]; + if (part[part.length - 1].toUpperCase() == 'I') { + // complex number + this.re = 0; + this.im = Number(part.substring(0, part.length - 1)); + ok = !isNaN(this.im); + } + else { + // real number + this.re = Number(part); + this.im = 0; + ok = !isNaN(this.re); + } + break; + + case 2: + part = parts[0]; + this.re = Number(parts[0]); + this.im = Number(parts[1].substring(0, parts[1].length - 1)); + ok = !isNaN(this.re) && !isNaN(this.im) && + (parts[1][parts[1].length - 1].toUpperCase() == 'I'); + break; + } + + // TODO: allow '+3-2' + + if (!ok) { + throw new SyntaxError('Invalid string "' + re + '"'); + } + + break; + + case 0: + // nul values + this.re = 0; + this.im = 0; + break; + + default: + throw new SyntaxError( + 'Wrong number of arguments in Complex constructor ' + + '(' + arguments.length + ' provided, 0, 1, or 2 expected)'); + } } math.type.Complex = Complex; +/** + * Trim a string + * http://stackoverflow.com/a/498995/1262753 + * @param str + * @return {*|void} + */ +function trim(str) { + return str.replace(/^\s+|\s+$/g, ''); +} + /** * Test whether value is a Complex value * @param {*} value diff --git a/src/type/Number.js b/src/type/Number.js index 8c312e3ff..7748da0d9 100644 --- a/src/type/Number.js +++ b/src/type/Number.js @@ -1,5 +1,5 @@ /** - * The build in Number object of Javascript is used. + * Utility functions for Numbers */ diff --git a/src/type/String.js b/src/type/String.js index d5b24a815..6edcc851f 100644 --- a/src/type/String.js +++ b/src/type/String.js @@ -1,5 +1,5 @@ /** - * The build in String object of Javascript is used. + * Utility functions for Strings */ /** diff --git a/src/type/Unit.js b/src/type/Unit.js index cfc883228..92ba8f9aa 100644 --- a/src/type/Unit.js +++ b/src/type/Unit.js @@ -1,5 +1,5 @@ /** - * @constructor math.type.Unit + * @constructor Unit * * @param {Number} [value] A value for the unit, like 5.2 * @param {String} [prefixUnit] A unit like "cm" or "inch" diff --git a/src/util.js b/src/util.js index 5c52e92aa..e7328e357 100644 --- a/src/util.js +++ b/src/util.js @@ -159,4 +159,4 @@ if (!Array.prototype.map) { // 9. return A return A; }; -} \ No newline at end of file +} diff --git a/test/test.html b/test/test.html index fa7cdb386..7f0405119 100644 --- a/test/test.html +++ b/test/test.html @@ -19,6 +19,9 @@ console.log('sqrt(25) = ' + math.sqrt(25)); console.log('sqrt(' + complex1.toString() + ') = ' + math.sqrt(complex1)); console.log('sqrt(-4) = ' + math.sqrt(-4)); + + var p = new math.parser.Parser(); + \ No newline at end of file diff --git a/test/type/complex.js b/test/type/complex.js index 9fb7f357f..051159465 100644 --- a/test/type/complex.js +++ b/test/type/complex.js @@ -9,21 +9,49 @@ var complex1 = new Complex(3, -4); assert.equal(complex1.re, 3); assert.equal(complex1.im, -4); assert.equal(complex1, '3 - 4i'); +assert.throws(function () { Complex(3, -4); }); + +// test constructor +assert.equal(new Complex().toString(), '0'); +assert.equal(new Complex(2, 3).toString(), '2 + 3i'); +assert.equal(new Complex(2, 0).toString(), '2'); +assert.equal(new Complex(0, 3).toString(), '3i'); +assert.equal(new Complex('2 + 3i').toString(), '2 + 3i'); +assert.equal(new Complex('2 +3i').toString(), '2 + 3i'); +assert.equal(new Complex('2+3i').toString(), '2 + 3i'); +assert.equal(new Complex(' 2+3i ').toString(), '2 + 3i'); +assert.equal(new Complex('2-3i').toString(), '2 - 3i'); +assert.equal(new Complex('-2-3i').toString(), '-2 - 3i'); +assert.equal(new Complex('-2+3i').toString(), '-2 + 3i'); +assert.equal(new Complex('-2+3i').toString(), '-2 + 3i'); +//assert.equal(new Complex('-2+3e-1i').toString(), '-2 + 0.3i'); // TODO +//assert.equal(new Complex('-2+3e+1i').toString(), '-2 + 30i'); // TODO +//assert.equal(new Complex('2+3e2i').toString(), '2 + 300i'); // TODO +//assert.equal(new Complex('2.2e-1-3.2e-1i').toString(), '0.22 - 0.32i'); // TODO +assert.equal(new Complex('2').toString(), '2'); +assert.equal(new Complex('-2').toString(), '-2'); +assert.equal(new Complex('3i').toString(), '3i'); +assert.equal(new Complex('-3i').toString(), '-3i'); +assert.throws(function () { new Complex(1, 2, 3); }); +assert.throws(function () { new Complex("str", 2); }); +assert.throws(function () { new Complex(1, true); }); +assert.throws(function () { new Complex(2); }); +assert.throws(function () { new Complex(""); }); +assert.throws(function () { new Complex("2r"); }); +assert.throws(function () { new Complex("str"); }); +assert.throws(function () { new Complex("2i+3i"); }); -assert.throws(function () { - Complex(3, -4); -}); // test toString -assert.equal(new Complex(), '0'); -assert.equal(new Complex(0, 2), '2i'); -assert.equal(new Complex(1, 1), '1 + i'); -assert.equal(new Complex(1, 2), '1 + 2i'); -assert.equal(new Complex(1, -1), '1 - i'); -assert.equal(new Complex(1, -2), '1 - 2i'); -assert.equal(new Complex(1, 0), '1'); -assert.equal(new Complex(-1, 2), '-1 + 2i'); -assert.equal(new Complex(-1, 1), '-1 + i'); +assert.equal(new Complex().toString(), '0'); +assert.equal(new Complex(0, 2).toString(), '2i'); +assert.equal(new Complex(1, 1).toString(), '1 + i'); +assert.equal(new Complex(1, 2).toString(), '1 + 2i'); +assert.equal(new Complex(1, -1).toString(), '1 - i'); +assert.equal(new Complex(1, -2).toString(), '1 - 2i'); +assert.equal(new Complex(1, 0).toString(), '1'); +assert.equal(new Complex(-1, 2).toString(), '-1 + 2i'); +assert.equal(new Complex(-1, 1).toString(), '-1 + i'); // test copy var copy = complex1.copy(); diff --git a/tools/jake-utils.js b/tools/jake-utils.js index 4dabf3956..045f02a8c 100644 --- a/tools/jake-utils.js +++ b/tools/jake-utils.js @@ -4,7 +4,7 @@ * Dependencies: * jake * uglify-js - * date-utils + * dateable * * Usage: * var util = require('jake-utils'); @@ -64,8 +64,8 @@ function version() { * {pattern: '@@version', replacement: '1.4.0'} * }, * src: [ - * 'file1.js', - * 'file2.js' + * 'main.js', + * 'other/*.js' * ] * }); * @@ -137,8 +137,10 @@ function replace (params) { * './src/extra.js', * './src/functions/**', * ], - * dest: './lib/mylibrary.js', - * header: '// license information...' + * dest: './lib/mylibrary.js', // optional + * header: '// license information...', // optional + * separator: '\n', // optional + * footer: '// the end...' // optional * }); * * @param {Object} params Object containing: @@ -207,8 +209,11 @@ function concat (params) { * src: [ * './lib/mylibrary.js' * ], - * dest: './lib/mylibrary.min.js', - * header: '// license information...' + * dest: './lib/mylibrary.min.js', // optional + * options: {}, // uglify-js options. optional + * header: '// license information...', // optional + * separator: '\n', // optional + * footer: '// the end...' // optional * }); * * @param {Object} params Object containing: @@ -228,7 +233,7 @@ function concat (params) { * @return {Object} res Result information. The object contains: * {String[]} src List with the filenames of the * files which are minified - * {String} code The contents of the concatenated + * {String} code The contents of the minified * file. */ function minify (params) {