mathjs
Version:
Math.js is an extensive math library for JavaScript and Node.js. It features a flexible expression parser and offers an integrated solution to work with numbers, big numbers, complex numbers, units, and matrices.
1,252 lines (1,056 loc) • 25.9 kB
JavaScript
var util = require('../util/index'),
isString = util.string.isString,
isArray = Array.isArray,
type = util.types.type,
// types
Complex = require('../type/Complex'),
Matrix = require('../type/Matrix'),
Unit = require('../type/Unit'),
collection = require('../type/collection'),
// scope and nodes
ArrayNode = require('./node/ArrayNode'),
AssignmentNode = require('./node/AssignmentNode'),
BlockNode = require('./node/BlockNode'),
ConstantNode = require('./node/ConstantNode'),
FunctionNode = require('./node/FunctionNode'),
IndexNode = require('./node/IndexNode'),
OperatorNode = require('./node/OperatorNode'),
ParamsNode = require('./node/ParamsNode'),
RangeNode = require('./node/RangeNode'),
SymbolNode = require('./node/SymbolNode'),
TernaryNode = require('./node/TernaryNode'),
UnitNode = require('./node/UnitNode'),
UpdateNode = require('./node/UpdateNode');
/**
* Parse an expression. Returns a node tree, which can be evaluated by
* invoking node.eval();
*
* Syntax:
*
* parse(expr)
* parse(expr, nodes)
* parse([expr1, expr2, expr3, ...])
* parse([expr1, expr2, expr3, ...], nodes)
*
* Example:
*
* var node = parse('sqrt(3^2 + 4^2)');
* node.compile(math).eval(); // 5
*
* var scope = {a:3, b:4}
* var node = parse('a * b'); // 12
* var code = node.compile(math);
* code.eval(scope); // 12
* scope.a = 5;
* code.eval(scope); // 20
*
* var nodes = math.parse(['a = 3', 'b = 4', 'a * b']);
* nodes[2].compile(math).eval(); // 12
*
* @param {String | String[] | Matrix} expr
* @param {Object<String, Node>} [nodes] A set of custom nodes
* @return {Node | Node[]} node
* @throws {Error}
*/
function parse (expr, nodes) {
if (arguments.length != 1 && arguments.length != 2) {
throw new SyntaxError('Wrong number of arguments: 1 or 2 expected');
}
// pass extra nodes
extra_nodes = (type(nodes) === 'object') ? nodes : {};
if (isString(expr)) {
// parse a single expression
expression = expr;
return parseStart();
}
else if (isArray(expr) || expr instanceof Matrix) {
// parse an array or matrix with expressions
return collection.deepMap(expr, function (elem) {
if (!isString(elem)) throw new TypeError('String expected');
expression = elem;
return parseStart();
});
}
else {
// oops
throw new TypeError('String or matrix expected');
}
}
// token types enumeration
var TOKENTYPE = {
NULL : 0,
DELIMITER : 1,
NUMBER : 2,
SYMBOL : 3,
UNKNOWN : 4
};
// map with all delimiters
var DELIMITERS = {
',': true,
'(': true,
')': true,
'[': true,
']': true,
'\"': true,
'\n': true,
';': true,
'+': true,
'-': true,
'*': true,
'.*': true,
'/': true,
'./': true,
'%': true,
'^': true,
'.^': true,
'!': true,
'\'': true,
'=': true,
':': true,
'?': true,
'==': true,
'!=': true,
'<': true,
'>': true,
'<=': true,
'>=': true
};
// map with all named delimiters
var NAMED_DELIMITERS = {
'mod': true,
'to': true,
'in': true
};
var extra_nodes = {}; // current extra nodes
var expression = ''; // current expression
var index = 0; // current index in expr
var c = ''; // current token character in expr
var token = ''; // current token
var token_type = TOKENTYPE.NULL; // type of the token
/**
* Get the first character from the expression.
* The character is stored into the char c. If the end of the expression is
* reached, the function puts an empty string in c.
* @private
*/
function first() {
index = 0;
c = expression.charAt(0);
}
/**
* Get the next character from the expression.
* The character is stored into the char c. If the end of the expression is
* reached, the function puts an empty string in c.
* @private
*/
function next() {
index++;
c = expression.charAt(index);
}
/**
* Preview the next character from the expression.
* @return {String} cNext
* @private
*/
function nextPreview() {
return expression.charAt(index + 1);
}
/**
* Get next token in the current string expr.
* The token and token type are available as token and token_type
* @private
*/
function getToken() {
token_type = TOKENTYPE.NULL;
token = '';
// skip over whitespaces
while (c == ' ' || c == '\t') { // space, tab
// TODO: also take '\r' carriage return as newline? Or does that give problems on mac?
next();
}
// skip comment
if (c == '#') {
while (c != '\n' && c != '') {
next();
}
}
// check for end of expression
if (c == '') {
// token is still empty
token_type = TOKENTYPE.DELIMITER;
return;
}
// check for delimiters consisting of 2 characters
var c2 = c + nextPreview();
if (DELIMITERS[c2]) {
token_type = TOKENTYPE.DELIMITER;
token = c2;
next();
next();
return;
}
// check for delimiters consisting of 1 character
if (DELIMITERS[c]) {
token_type = TOKENTYPE.DELIMITER;
token = c;
next();
return;
}
// check for a number
if (isDigitDot(c)) {
token_type = TOKENTYPE.NUMBER;
// get number, can have a single dot
if (c == '.') {
token += c;
next();
if (!isDigit(c)) {
// this is no legal number, it is just a dot
token_type = TOKENTYPE.UNKNOWN;
}
}
else {
while (isDigit(c)) {
token += c;
next();
}
if (c == '.') {
token += c;
next();
}
}
while (isDigit(c)) {
token += c;
next();
}
// check for exponential notation like "2.3e-4" or "1.23e50"
if (c == 'E' || c == 'e') {
token += c;
next();
if (c == '+' || c == '-') {
token += c;
next();
}
// Scientific notation MUST be followed by an exponent
if (!isDigit(c)) {
// this is no legal number, exponent is missing.
token_type = TOKENTYPE.UNKNOWN;
}
while (isDigit(c)) {
token += c;
next();
}
}
return;
}
// check for variables, functions, named operators
if (isAlpha(c)) {
while (isAlpha(c) || isDigit(c)) {
token += c;
next();
}
if (NAMED_DELIMITERS[token]) {
token_type = TOKENTYPE.DELIMITER;
}
else {
token_type = TOKENTYPE.SYMBOL;
}
return;
}
// something unknown is found, wrong characters -> a syntax error
token_type = TOKENTYPE.UNKNOWN;
while (c != '') {
token += c;
next();
}
throw createSyntaxError('Syntax error in part "' + token + '"');
}
/**
* Skip newline tokens
*/
function skipNewlines () {
while (token == '\n') {
getToken();
}
}
/**
* Check if a given name is valid
* if not, an error is thrown
* @param {String} name
* @return {boolean} valid
* @private
*/
/** TODO: check for valid symbol name
function isValidSymbolName (name) {
for (var i = 0, iMax = name.length; i < iMax; i++) {
var c = name.charAt(i);
//var valid = (isAlpha(c) || (i > 0 && isDigit(c))); // TODO: allow digits in symbol name
var valid = (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
*/
function isAlpha (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
*/
function isDigitDot (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
*/
function isDigit (c) {
return ((c >= '0' && c <= '9'));
}
/**
* Start of the parse levels below, in order of precedence
* @return {Node} node
* @private
*/
function parseStart () {
// get the first character in expression
first();
getToken();
var node = parseBlock();
// check for garbage at the end of the expression
// an expression ends with a empty character '' and token_type DELIMITER
if (token != '') {
if (token_type == TOKENTYPE.DELIMITER) {
// user entered a not existing operator like "//"
// TODO: give hints for aliases, for example with "<>" give as hint " did you mean != ?"
throw createError('Unknown operator ' + token);
}
else {
throw createSyntaxError('Unexpected part "' + token + '"');
}
}
return node;
}
/**
* 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.
* @return {Node} node
* @private
*/
function parseBlock () {
var node, block, visible;
if (token == '') {
// empty expression
return new ConstantNode('undefined', 'undefined');
}
if (token != '\n' && token != ';') {
node = parseAns();
}
while (token == '\n' || token == ';') {
if (!block) {
// initialize the block
block = new BlockNode();
if (node) {
visible = (token != ';');
block.add(node, visible);
}
}
getToken();
if (token != '\n' && token != ';' && token != '') {
node = parseAns();
visible = (token != ';');
block.add(node, visible);
}
}
if (block) {
return block;
}
return node;
}
/**
* Parse assignment of ans.
* Ans is assigned when the expression itself is no variable or function
* assignment
* @return {Node} node
* @private
*/
function parseAns () {
var expression = parseFunctionAssignment();
// create a variable definition for ans
var name = 'ans';
return new AssignmentNode(name, expression);
}
/**
* Parse a function assignment like "function f(a,b) = a*b"
* @return {Node} node
* @private
*/
function parseFunctionAssignment () {
// TODO: function assignment using keyword 'function' is deprecated since version 0.18.0, cleanup some day
if (token_type == TOKENTYPE.SYMBOL && token == 'function') {
throw new Error('Deprecated keyword "function". ' +
'Functions can now be assigned without it, like "f(x) = x^2".');
}
return parseAssignment();
}
/**
* 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]"
* @return {Node} node
* @private
*/
function parseAssignment () {
var name, args, expr;
var node = parseRange();
if (token == '=') {
if (node instanceof SymbolNode) {
// parse a variable assignment like 'a = 2/3'
name = node.name;
getToken();
expr = parseAssignment();
return new AssignmentNode(name, expr);
}
else if (node instanceof IndexNode) {
// parse a matrix subset assignment like 'A[1,2] = 4'
getToken();
expr = parseAssignment();
return new UpdateNode(node, expr);
}
else if (node instanceof ParamsNode) {
// parse function assignment like 'f(x) = x^2'
var valid = true;
args = [];
if (node.object instanceof SymbolNode) {
name = node.object.name;
node.params.forEach(function (param, index) {
if (param instanceof SymbolNode) {
args[index] = param.name;
}
else {
valid = false;
}
});
}
else {
valid = false;
}
if (valid) {
getToken();
expr = parseAssignment();
return new FunctionNode(name, args, expr);
}
}
throw createSyntaxError('Invalid left hand side of assignment operator =');
}
return node;
}
/**
* parse range, "start:end", "start:step:end", ":", "start:", ":end", etc
* @return {Node} node
* @private
*/
function parseRange () {
var node, params = [];
if (token == ':') {
// implicit start=1 (one-based)
node = new ConstantNode('number', '1');
}
else {
// explicit start
node = parseBitwiseConditions();
}
if (token == ':') {
params.push(node);
// parse step and end
while (token == ':') {
getToken();
if (token == ')' || token == ']' || token == ',' || token == '') {
// implicit end
params.push(new SymbolNode('end'));
}
else {
// explicit end
params.push(parseBitwiseConditions());
}
}
// swap step and end
if (params.length == 3) {
var step = params[2];
params[2] = params[1];
params[1] = step;
}
node = new RangeNode(params);
}
return node;
}
/**
* conditional operators and bitshift
* @return {Node} node
* @private
*/
function parseBitwiseConditions () {
var node = parseIfElse();
/* TODO: implement bitwise conditions
var operators = {
'&' : 'bitwiseand',
'|' : 'bitwiseor',
// todo: bitwise xor?
'<<': 'bitshiftleft',
'>>': 'bitshiftright'
};
while (token in operators) {
var name = token;
getToken();
var params = [node, parseComparison()];
node = new OperatorNode(name, fn, params);
}
*/
return node;
}
/**
* conditional operation
*
* condition ? truePart : falsePart
*
* Note: conditional operator is right-associative
*
* @return {Node} node
* @private
*/
function parseIfElse () {
var node = parseComparison();
while (token == '?') {
getToken();
var params = [node];
params.push(parseComparison());
if (token != ':') throw createSyntaxError('False part of conditional expression expected');
getToken();
params.push(parseIfElse());
node = new TernaryNode(['?', ':'], 'ifElse', params);
}
return node;
}
/**
* comparison operators
* @return {Node} node
* @private
*/
function parseComparison () {
var node, operators, name, fn, params;
node = parseConditions();
operators = {
'==': 'equal',
'!=': 'unequal',
'<': 'smaller',
'>': 'larger',
'<=': 'smallereq',
'>=': 'largereq'
};
while (token in operators) {
name = token;
fn = operators[name];
getToken();
params = [node, parseConditions()];
node = new OperatorNode(name, fn, params);
}
return node;
}
/**
* conditions like and, or, in
* @return {Node} node
* @private
*/
function parseConditions () {
var node, operators, name, fn, params;
node = parseAddSubtract();
// TODO: precedence of And above Or?
// TODO: implement a method for unit to number conversion
operators = {
'to' : 'to',
'in' : 'to' // alias of to
/* TODO: implement conditions
'and' : 'and',
'&&' : 'and',
'or': 'or',
'||': 'or',
'xor': 'xor'
*/
};
while (token in operators) {
name = token;
fn = operators[name];
getToken();
params = [node, parseAddSubtract()];
node = new OperatorNode(name, fn, params);
}
return node;
}
/**
* add or subtract
* @return {Node} node
* @private
*/
function parseAddSubtract () {
var node, operators, name, fn, params;
node = parseMultiplyDivide();
operators = {
'+': 'add',
'-': 'subtract'
};
while (token in operators) {
name = token;
fn = operators[name];
getToken();
params = [node, parseMultiplyDivide()];
node = new OperatorNode(name, fn, params);
}
return node;
}
/**
* multiply, divide, modulus
* @return {Node} node
* @private
*/
function parseMultiplyDivide () {
var node, operators, name, fn, params;
node = parseUnit();
operators = {
'*': 'multiply',
'.*': 'emultiply',
'/': 'divide',
'./': 'edivide',
'%': 'mod',
'mod': 'mod'
};
while (token in operators) {
name = token;
fn = operators[name];
getToken();
params = [node, parseUnit()];
node = new OperatorNode(name, fn, params);
}
return node;
}
/**
* parse units like in '2i', '2 cm'
* @return {Node} node
* @private
*/
function parseUnit() {
var node, symbol;
node = parseUnary();
if (token_type == TOKENTYPE.SYMBOL || token == 'in') {
// note unit 'in' (inch) is also a conversion operator
symbol = token;
getToken();
node = new UnitNode(node, symbol);
}
return node;
}
/**
* Unary minus
* @return {Node} node
* @private
*/
function parseUnary () {
var name, fn, params;
if (token == '-') {
name = token;
fn = 'unary';
getToken();
params = [parseUnary()];
return new OperatorNode(name, fn, params);
}
return parsePow();
}
/**
* power
* Note: power operator is right associative
* @return {Node} node
* @private
*/
function parsePow () {
var node, name, fn, params;
node = parseLeftHandOperators();
if (token == '^' || token == '.^') {
name = token;
fn = (name == '^') ? 'pow' : 'epow';
getToken();
params = [node, parsePow()];
node = new OperatorNode(name, fn, params);
}
return node;
}
/**
* Left hand operators: factorial x!, transpose x'
* @return {Node} node
* @private
*/
function parseLeftHandOperators () {
var node, operators, name, fn, params;
node = parseCustomNodes();
operators = {
'!': 'factorial',
'\'': 'transpose'
};
while (token in operators) {
name = token;
fn = operators[name];
getToken();
params = [node];
node = new OperatorNode(name, fn, params);
}
return node;
}
/**
* Parse a custom node handler. A node handler can be used to process
* nodes in a custom way, for example for handling a plot.
*
* A handler must be passed as second argument of the parse function.
* - must extend math.expression.node.Node
* - must contain a function _compile(defs: Object) : String
* - must contain a function find(filter: Object) : Node[]
* - must contain a function toString() : String
* - the constructor is called with a single argument containing all parameters
*
* For example:
*
* nodes = {
* 'plot': PlotHandler
* };
*
* The constructor of the handler is called as:
*
* node = new PlotHandler(params);
*
* The handler will be invoked when evaluating an expression like:
*
* node = math.parse('plot(sin(x), x)', nodes);
*
* @return {Node} node
* @private
*/
function parseCustomNodes () {
var params = [], handler;
if (token_type == TOKENTYPE.SYMBOL && extra_nodes[token]) {
handler = extra_nodes[token];
getToken();
// parse parameters
if (token == '(') {
params = [];
getToken();
if (token != ')') {
params.push(parseRange());
// parse a list with parameters
while (token == ',') {
getToken();
params.push(parseRange());
}
}
if (token != ')') {
throw createSyntaxError('Parenthesis ) expected');
}
getToken();
}
// create a new node handler
//noinspection JSValidateTypes
return new handler(params);
}
return parseSymbol();
}
/**
* parse symbols: functions, variables, constants, units
* @return {Node} node
* @private
*/
function parseSymbol () {
var node, name;
if (token_type == TOKENTYPE.SYMBOL ||
(token_type == TOKENTYPE.DELIMITER && token in NAMED_DELIMITERS)) {
name = token;
getToken();
// create a symbol
node = new SymbolNode(name);
// parse parameters
return parseParams(node);
}
return parseString();
}
/**
* parse parameters, enclosed in parenthesis. Can be two types:
* - round brackets (...) will return a ParamsNode
* - square brackets [...] will return an IndexNode
* @param {Node} node Node on which to apply the parameters. If there
* are no parameters in the expression, the node
* itself is returned
* @return {Node} node
* @private
*/
function parseParams (node) {
var bracket, params;
while (token == '(' || token == '[') {
bracket = token;
params = [];
getToken();
if (token != ')' && token != ']') {
params.push(parseRange());
// parse a list with parameters
while (token == ',') {
getToken();
params.push(parseRange());
}
}
if ((bracket == '(' && token != ')')) {
throw createSyntaxError('Parenthesis ) expected');
}
if ((bracket == '[' && token != ']')) {
throw createSyntaxError('Parenthesis ] expected');
}
getToken();
if (bracket == '(') {
node = new ParamsNode(node, params);
}
else {
node = new IndexNode(node, params);
}
}
return node;
}
/**
* parse a string.
* A string is enclosed by double quotes
* @return {Node} node
* @private
*/
function parseString () {
var node, str, tPrev;
if (token == '"') {
// string "..."
str = '';
tPrev = '';
while (c != '' && (c != '\"' || tPrev == '\\')) { // also handle escape character
str += c;
tPrev = c;
next();
}
getToken();
if (token != '"') {
throw createSyntaxError('End of string " expected');
}
getToken();
// create constant
node = new ConstantNode('string', str);
// parse parameters
node = parseParams(node);
return node;
}
return parseMatrix();
}
/**
* parse the matrix
* @return {Node} node
* @private
*/
function parseMatrix () {
var array, params, rows, cols;
if (token == '[') {
// matrix [...]
getToken();
skipNewlines();
if (token != ']') {
// this is a non-empty matrix
var row = parseRow();
if (token == ';') {
// 2 dimensional array
rows = 1;
params = [row];
// the rows of the matrix are separated by dot-comma's
while (token == ';') {
getToken();
skipNewlines();
params[rows] = parseRow();
rows++;
skipNewlines();
}
if (token != ']') {
throw createSyntaxError('End of matrix ] expected');
}
getToken();
// check if the number of columns matches in all rows
cols = params[0].nodes.length;
for (var r = 1; r < rows; r++) {
if (params[r].nodes.length != cols) {
throw createError('Column dimensions mismatch ' +
'(' + params[r].nodes.length + ' != ' + cols + ')');
}
}
array = new ArrayNode(params);
}
else {
// 1 dimensional vector
if (token != ']') {
throw createSyntaxError('End of matrix ] expected');
}
getToken();
array = row;
}
}
else {
// this is an empty matrix "[ ]"
getToken();
array = new ArrayNode([]);
}
// parse parameters
array = parseParams(array);
return array;
}
return parseNumber();
}
/**
* Parse a single comma-separated row from a matrix, like 'a, b, c'
* @return {ArrayNode} node
*/
function parseRow () {
var params = [parseAssignment()];
var len = 1;
while (token == ',') {
getToken();
skipNewlines();
// parse expression
params[len] = parseAssignment();
len++;
skipNewlines();
}
return new ArrayNode(params);
}
/**
* parse a number
* @return {Node} node
* @private
*/
function parseNumber () {
var node, complex, number;
if (token_type == TOKENTYPE.NUMBER) {
// this is a number
number = token;
getToken();
if (token == 'i' || token == 'I') {
// create a complex number
getToken();
node = new ConstantNode('complex', number);
}
else {
// a number
node = new ConstantNode('number', number);
}
// parse parameters
node = parseParams(node);
return node;
}
return parseParentheses();
}
/**
* parentheses
* @return {Node} node
* @private
*/
function parseParentheses () {
var node;
// check if it is a parenthesized expression
if (token == '(') {
// parentheses (...)
getToken();
node = parseAssignment(); // start again
if (token != ')') {
throw createSyntaxError('Parenthesis ) expected');
}
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.SYMBOL) {
node = multiply(node, parsePow());
}
//*/
// parse parameters
node = parseParams(node);
return node;
}
return parseEnd();
}
/**
* Evaluated when the expression is not yet ended but expected to end
* @return {Node} res
* @private
*/
function parseEnd () {
if (token == '') {
// syntax error or unexpected end of expression
throw createSyntaxError('Unexpected end of expression');
} else {
throw createSyntaxError('Value expected');
}
}
/**
* Shortcut for getting the current row value (one based)
* Returns the line of the currently handled expression
* @private
*/
/* TODO: implement keeping track on the row number
function row () {
return null;
}
*/
/**
* Shortcut for getting the current col value (one based)
* Returns the column (position) where the last token starts
* @private
*/
function col () {
return index - token.length + 1;
}
/**
* Build up an error message
* @param {String} message
* @return {String} message with char information
* @private
*/
function createErrorMessage (message) {
return message + ' (char ' + col() + ')';
}
/**
* Create an error
* @param {String} message
* @return {SyntaxError} instantiated error
* @private
*/
function createSyntaxError (message) {
return new SyntaxError(createErrorMessage(message));
}
/**
* Create an error
* @param {String} message
* @return {Error} instantiated error
* @private
*/
function createError (message) {
return new Error(createErrorMessage(message));
}
module.exports = parse;