initial commit
This commit is contained in:
commit
195777b1af
57
.eslintrc
Normal file
57
.eslintrc
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
{
|
||||||
|
"env": {
|
||||||
|
"es6": true,
|
||||||
|
"browser": true
|
||||||
|
},
|
||||||
|
"ecmaFeatures": {
|
||||||
|
"modules": true
|
||||||
|
},
|
||||||
|
"globals": {
|
||||||
|
"exports": true,
|
||||||
|
"require": true,
|
||||||
|
"ViewHelpers": true,
|
||||||
|
"is": true,
|
||||||
|
"Components": true,
|
||||||
|
"XPCOMUtils": true,
|
||||||
|
"EventEmitter": true,
|
||||||
|
"add_task": true,
|
||||||
|
"info": true,
|
||||||
|
"createHost": true,
|
||||||
|
"promiseTab": true,
|
||||||
|
"ok": true,
|
||||||
|
"TEST_URI_ROOT": true,
|
||||||
|
"TargetFactory": true,
|
||||||
|
"gBrowser": true,
|
||||||
|
"gDevTools": true
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"comma-dangle": [2, "never"],
|
||||||
|
"no-underscore-dangle": 0,
|
||||||
|
"no-cond-assign": 0,
|
||||||
|
"no-undef": 0,
|
||||||
|
"no-console": 0,
|
||||||
|
"no-reserved-keys": 2,
|
||||||
|
"valid-jsdoc": [2, {
|
||||||
|
"requireReturn": false,
|
||||||
|
"requireParamDescription": false,
|
||||||
|
"requireReturnDescription": false
|
||||||
|
}],
|
||||||
|
"max-len": [1, 80],
|
||||||
|
"no-use-before-define": 0,
|
||||||
|
"no-self-compare": 1,
|
||||||
|
"no-sequences": 0,
|
||||||
|
"radix": 2,
|
||||||
|
"wrap-iife": 2,
|
||||||
|
"indent": [2, 2],
|
||||||
|
"brace-style": [2, "1tbs"],
|
||||||
|
"comma-style": [2, "last"],
|
||||||
|
"no-lonely-if": 2,
|
||||||
|
"no-multiple-empty-lines": [2, {"max": 2}],
|
||||||
|
"quotes": 0,
|
||||||
|
"space-after-keywords": [2, "always"],
|
||||||
|
"space-before-blocks": [2, "always"],
|
||||||
|
"space-infix-ops": [2, { "int32Hint": false }],
|
||||||
|
"strict": 0,
|
||||||
|
"global-strict": 0
|
||||||
|
}
|
||||||
|
}
|
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
node_modules
|
||||||
|
.DS_Store
|
||||||
|
dist
|
33
Gruntfile.js
Normal file
33
Gruntfile.js
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
module.exports = function(grunt) {
|
||||||
|
grunt.initConfig({
|
||||||
|
eslint: {
|
||||||
|
target: ['*.js', 'tests/*.js']
|
||||||
|
},
|
||||||
|
babel: {
|
||||||
|
dist: {
|
||||||
|
files: [{
|
||||||
|
expand: true,
|
||||||
|
src: ['*.js', 'tests/*.js'],
|
||||||
|
dest: 'dist/'
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mochaTest: {
|
||||||
|
files: ['dist/tests/*.js']
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
scripts: {
|
||||||
|
files: '**/*.js',
|
||||||
|
tasks: ['eslint', 'babel']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
grunt.loadNpmTasks('grunt-eslint');
|
||||||
|
grunt.loadNpmTasks('grunt-contrib-watch');
|
||||||
|
grunt.loadNpmTasks('grunt-babel');
|
||||||
|
grunt.loadNpmTasks('grunt-mocha-test');
|
||||||
|
|
||||||
|
grunt.registerTask('default', [ 'babel']);
|
||||||
|
grunt.registerTask('test', ['babel', 'mochaTest']);
|
||||||
|
};
|
11
constats.js
Normal file
11
constats.js
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
/*
|
||||||
|
* Constats
|
||||||
|
* Keys must be UPPERCASE
|
||||||
|
* Values Can be a constant value or a function returning a value
|
||||||
|
* this function doesn't take any arguments (use case: random constats)
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
|
'PI': Math.PI,
|
||||||
|
'E': Math.E,
|
||||||
|
'RAND': Math.random
|
||||||
|
};
|
3
dist/.gitignore
vendored
Normal file
3
dist/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
node_modules
|
||||||
|
.DS_Store
|
||||||
|
dist
|
35
dist/Gruntfile.js
vendored
Normal file
35
dist/Gruntfile.js
vendored
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
module.exports = function (grunt) {
|
||||||
|
grunt.initConfig({
|
||||||
|
eslint: {
|
||||||
|
target: ['*.js', 'tests/*.js']
|
||||||
|
},
|
||||||
|
babel: {
|
||||||
|
dist: {
|
||||||
|
files: [{
|
||||||
|
expand: true,
|
||||||
|
src: ['*.js', 'tests/*.js'],
|
||||||
|
dest: 'dist/'
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mochaTest: {
|
||||||
|
files: ['dist/tests/*.js']
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
scripts: {
|
||||||
|
files: '**/*.js',
|
||||||
|
tasks: ['eslint', 'babel']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
grunt.loadNpmTasks('grunt-eslint');
|
||||||
|
grunt.loadNpmTasks('grunt-contrib-watch');
|
||||||
|
grunt.loadNpmTasks('grunt-babel');
|
||||||
|
grunt.loadNpmTasks('grunt-mocha-test');
|
||||||
|
|
||||||
|
grunt.registerTask('default', ['babel']);
|
||||||
|
grunt.registerTask('test', ['babel', 'mochaTest']);
|
||||||
|
};
|
17
dist/constats.js
vendored
Normal file
17
dist/constats.js
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
Object.defineProperty(exports, '__esModule', {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
/*
|
||||||
|
* Constats
|
||||||
|
* Keys must be UPPERCASE
|
||||||
|
* Values Can be a constant value or a function returning a value
|
||||||
|
* this function doesn't take any arguments (use case: random constats)
|
||||||
|
*/
|
||||||
|
exports['default'] = {
|
||||||
|
PI: Math.PI,
|
||||||
|
E: Math.E,
|
||||||
|
RAND: Math.random
|
||||||
|
};
|
||||||
|
module.exports = exports['default'];
|
157
dist/helpers.js
vendored
Normal file
157
dist/helpers.js
vendored
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
Object.defineProperty(exports, '__esModule', {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
var parseFormat = function parseFormat(a) {
|
||||||
|
var split = a.split('1');
|
||||||
|
return {
|
||||||
|
left: split[0].length,
|
||||||
|
right: split[1].length
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.parseFormat = parseFormat;
|
||||||
|
var isNumber = function isNumber(a) {
|
||||||
|
return !isNaN(+a);
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.isNumber = isNumber;
|
||||||
|
var parseNumbers = (function (_parseNumbers) {
|
||||||
|
function parseNumbers(_x) {
|
||||||
|
return _parseNumbers.apply(this, arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
parseNumbers.toString = function () {
|
||||||
|
return _parseNumbers.toString();
|
||||||
|
};
|
||||||
|
|
||||||
|
return parseNumbers;
|
||||||
|
})(function (a) {
|
||||||
|
return a.map(function (b) {
|
||||||
|
if (isNumber(b)) {
|
||||||
|
return parseFloat(b);
|
||||||
|
}
|
||||||
|
if (Array.isArray(b)) {
|
||||||
|
return parseNumbers(b);
|
||||||
|
}
|
||||||
|
return b;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
exports.parseNumbers = parseNumbers;
|
||||||
|
var dive = function dive(arr, n) {
|
||||||
|
var result = arr;
|
||||||
|
for (var i = 0; i < n; ++i) {
|
||||||
|
result = result[result.length - 1];
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.dive = dive;
|
||||||
|
var deep = (function (_deep) {
|
||||||
|
function deep(_x2, _x3) {
|
||||||
|
return _deep.apply(this, arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
deep.toString = function () {
|
||||||
|
return _deep.toString();
|
||||||
|
};
|
||||||
|
|
||||||
|
return deep;
|
||||||
|
})(function (arr, n) {
|
||||||
|
var index = arguments[2] === undefined ? 0 : arguments[2];
|
||||||
|
|
||||||
|
if (n < 2) {
|
||||||
|
return { arr: arr, index: index };
|
||||||
|
}
|
||||||
|
|
||||||
|
var d = arr.reduce(function (a, b, i) {
|
||||||
|
if (Array.isArray(b)) {
|
||||||
|
var _deep2 = deep(b, n - 1, i);
|
||||||
|
|
||||||
|
var _arr = _deep2.arr;
|
||||||
|
var x = _deep2.index;
|
||||||
|
var merged = a.concat(_arr);
|
||||||
|
|
||||||
|
index = x;
|
||||||
|
return merged;
|
||||||
|
}
|
||||||
|
return a;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return { arr: d, index: index };
|
||||||
|
});
|
||||||
|
|
||||||
|
exports.deep = deep;
|
||||||
|
var diveTo = (function (_diveTo) {
|
||||||
|
function diveTo(_x4, _x5, _x6) {
|
||||||
|
return _diveTo.apply(this, arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
diveTo.toString = function () {
|
||||||
|
return _diveTo.toString();
|
||||||
|
};
|
||||||
|
|
||||||
|
return diveTo;
|
||||||
|
})(function (arr, indexes, replace) {
|
||||||
|
var answer = [];
|
||||||
|
if (indexes.some(Array.isArray)) {
|
||||||
|
var _iteratorNormalCompletion = true;
|
||||||
|
var _didIteratorError = false;
|
||||||
|
var _iteratorError = undefined;
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (var _iterator = indexes[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
|
||||||
|
var index = _step.value;
|
||||||
|
|
||||||
|
answer.push(diveTo(arr, index, replace));
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
_didIteratorError = true;
|
||||||
|
_iteratorError = err;
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
if (!_iteratorNormalCompletion && _iterator['return']) {
|
||||||
|
_iterator['return']();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (_didIteratorError) {
|
||||||
|
throw _iteratorError;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
arr[indexes[0]] = replace;
|
||||||
|
return replace;
|
||||||
|
}
|
||||||
|
|
||||||
|
return answer;
|
||||||
|
});
|
||||||
|
|
||||||
|
exports.diveTo = diveTo;
|
||||||
|
var flatten = (function (_flatten) {
|
||||||
|
function flatten(_x7) {
|
||||||
|
return _flatten.apply(this, arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
flatten.toString = function () {
|
||||||
|
return _flatten.toString();
|
||||||
|
};
|
||||||
|
|
||||||
|
return flatten;
|
||||||
|
})(function (arr) {
|
||||||
|
if (!Array.isArray(arr) || !arr.some(Array.isArray)) {
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return arr.reduce(function (a, b) {
|
||||||
|
return a.concat(flatten(b));
|
||||||
|
}, []);
|
||||||
|
});
|
||||||
|
|
||||||
|
exports.flatten = flatten;
|
||||||
|
var removeSymbols = function removeSymbols(string) {
|
||||||
|
return string.replace(/\W/g, '');
|
||||||
|
};
|
||||||
|
exports.removeSymbols = removeSymbols;
|
396
dist/index.js
vendored
Normal file
396
dist/index.js
vendored
Normal file
@ -0,0 +1,396 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
var _interopRequireWildcard = function (obj) { return obj && obj.__esModule ? obj : { 'default': obj }; };
|
||||||
|
|
||||||
|
var _slicedToArray = function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i['return']) _i['return'](); } finally { if (_d) throw _e; } } return _arr; } else { throw new TypeError('Invalid attempt to destructure non-iterable instance'); } };
|
||||||
|
|
||||||
|
var _toConsumableArray = function (arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) arr2[i] = arr[i]; return arr2; } else { return Array.from(arr); } };
|
||||||
|
|
||||||
|
Object.defineProperty(exports, '__esModule', {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
|
||||||
|
var _ReadStream = require('./readstream');
|
||||||
|
|
||||||
|
var _ReadStream2 = _interopRequireWildcard(_ReadStream);
|
||||||
|
|
||||||
|
var _operators = require('./operators');
|
||||||
|
|
||||||
|
var _operators2 = _interopRequireWildcard(_operators);
|
||||||
|
|
||||||
|
var _constants = require('./constats');
|
||||||
|
|
||||||
|
var _constants2 = _interopRequireWildcard(_constants);
|
||||||
|
|
||||||
|
var _import = require('./helpers');
|
||||||
|
|
||||||
|
var _ = _interopRequireWildcard(_import);
|
||||||
|
|
||||||
|
var Mathstring = {
|
||||||
|
/**
|
||||||
|
* Solves the given math expression, following these steps:
|
||||||
|
* 1. Replace constants in the expression
|
||||||
|
* 2. parse the expression, separating numbers and operators into
|
||||||
|
* an array
|
||||||
|
* 3. Sort the stack by operators' precedence, it actually groups the
|
||||||
|
* operator with it's arguments n-level deep
|
||||||
|
* 4. Apply parseFloat to numbers of the array
|
||||||
|
* 5. Solve groups recursively
|
||||||
|
*
|
||||||
|
* @param {String} expression
|
||||||
|
* The math expression to solve
|
||||||
|
* @return {Number}
|
||||||
|
* Result of the expression
|
||||||
|
*/
|
||||||
|
solve: function solve(expression) {
|
||||||
|
// replace constants with their values
|
||||||
|
expression = replaceConstants(expression);
|
||||||
|
|
||||||
|
var stack = parseExpression(expression);
|
||||||
|
stack = sortStack(stack);
|
||||||
|
stack = _.parseNumbers(stack);
|
||||||
|
stack = solveStack(stack);
|
||||||
|
|
||||||
|
return stack;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Creates an equation function which replaces variables
|
||||||
|
* in the given expression with the values specified in order,
|
||||||
|
* and solves the new expression
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* equation('x+y+z')(2, 5, 6) => solve('2+5+6')
|
||||||
|
*
|
||||||
|
* @param {String} expression
|
||||||
|
* The expression to create an equation for (containing variables)
|
||||||
|
* @return {Function}
|
||||||
|
* The function which replaces variables with values in order
|
||||||
|
* and solves the expression
|
||||||
|
*/
|
||||||
|
equation: function equation(expression) {
|
||||||
|
var stack = parseExpression(expression);
|
||||||
|
var variables = [];
|
||||||
|
|
||||||
|
stack.forEach(function (a) {
|
||||||
|
if (!_.isNumber(a) && !_operators2['default'][a] && a === a.toLowerCase()) {
|
||||||
|
// grouped variables like (y) need to have their parantheses removed
|
||||||
|
variables.push(_.removeSymbols(a));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return function () {
|
||||||
|
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
|
||||||
|
args[_key] = arguments[_key];
|
||||||
|
}
|
||||||
|
|
||||||
|
expression = expression.replace(/[a-z]*/g, function (a) {
|
||||||
|
var index = variables.indexOf(a);
|
||||||
|
if (index > -1) {
|
||||||
|
return args[index] || 0;
|
||||||
|
}
|
||||||
|
return a;
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(variables, expression);
|
||||||
|
return Mathstring.solve(expression);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var solveStack = (function (_solveStack) {
|
||||||
|
function solveStack(_x) {
|
||||||
|
return _solveStack.apply(this, arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
solveStack.toString = function () {
|
||||||
|
return _solveStack.toString();
|
||||||
|
};
|
||||||
|
|
||||||
|
return solveStack;
|
||||||
|
})(function (stack) {
|
||||||
|
// $0(stack);
|
||||||
|
if (stack.some(Array.isArray)) {
|
||||||
|
stack = stack.map(function (group) {
|
||||||
|
if (!Array.isArray(group)) {
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
return solveStack(group);
|
||||||
|
});
|
||||||
|
|
||||||
|
return solveStack(stack);
|
||||||
|
} else {
|
||||||
|
return evaluate(stack);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var PRECEDENCES = Object.keys(_operators2['default']).map(function (key) {
|
||||||
|
return _operators2['default'][key].precedence;
|
||||||
|
});
|
||||||
|
|
||||||
|
var MAX_PRECEDENCE = Math.max.apply(Math, _toConsumableArray(PRECEDENCES));
|
||||||
|
var MIN_PRECEDENCE = Math.min.apply(Math, _toConsumableArray(PRECEDENCES));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the given expression into an array of separated
|
||||||
|
* numbers and operators/functions.
|
||||||
|
* The result is passed to parseGroups
|
||||||
|
*
|
||||||
|
* @param {String} expression
|
||||||
|
* The expression to parse
|
||||||
|
* @return {Array}
|
||||||
|
* The parsed array
|
||||||
|
*/
|
||||||
|
var parseExpression = function parseExpression(expression) {
|
||||||
|
var stream = new _ReadStream2['default'](expression),
|
||||||
|
stack = [],
|
||||||
|
record = '';
|
||||||
|
|
||||||
|
// Create an array of separated numbers & operators
|
||||||
|
while (stream.next()) {
|
||||||
|
var cur = stream.current();
|
||||||
|
if (cur === ' ') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// it's probably a function with a length more than one
|
||||||
|
if (!_.isNumber(cur) && !_operators2['default'][cur] && cur !== '.') {
|
||||||
|
record += cur;
|
||||||
|
} else if (record.length) {
|
||||||
|
stack.push(record, cur);
|
||||||
|
record = '';
|
||||||
|
} else if (_.isNumber(stack[stack.length - 1]) && (_.isNumber(cur) || cur === '.')) {
|
||||||
|
|
||||||
|
stack[stack.length - 1] += cur;
|
||||||
|
} else {
|
||||||
|
stack.push(cur);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (record.length) {
|
||||||
|
stack.push(record);
|
||||||
|
}
|
||||||
|
|
||||||
|
// $0(stack);
|
||||||
|
return parseGroups(stack);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes the parsed array from parseExpression and
|
||||||
|
* groups up expressions in parantheses in deep arrays
|
||||||
|
*
|
||||||
|
* Example: 2+(5+4) becomes [2, [5, '+', 4]]
|
||||||
|
*
|
||||||
|
* @param {Array} stack
|
||||||
|
* The parsed expression
|
||||||
|
* @return {Array}
|
||||||
|
* Grouped up expression
|
||||||
|
*/
|
||||||
|
var parseGroups = function parseGroups(stack) {
|
||||||
|
// Parantheses become inner arrays which will then be processed first
|
||||||
|
var sub = 0;
|
||||||
|
return stack.reduce(function (a, b) {
|
||||||
|
if (b[b.length - 1] === '(') {
|
||||||
|
if (b.length > 1) {
|
||||||
|
_.dive(a, sub).push(b.slice(0, -1), []);
|
||||||
|
} else {
|
||||||
|
_.dive(a, sub).push([]);
|
||||||
|
}
|
||||||
|
sub++;
|
||||||
|
} else if (b === ')') {
|
||||||
|
sub--;
|
||||||
|
} else {
|
||||||
|
_.dive(a, sub).push(b);
|
||||||
|
}
|
||||||
|
return a;
|
||||||
|
}, []);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gives information about an operator's format
|
||||||
|
* including number of left and right arguments
|
||||||
|
*
|
||||||
|
* @param {String/Object} operator
|
||||||
|
* The operator object or operator name (e.g. +, -)
|
||||||
|
* @return {Object}
|
||||||
|
* An object including the count of left and right arguments
|
||||||
|
*/
|
||||||
|
var formatInfo = function formatInfo(operator) {
|
||||||
|
var op = typeof operator === 'string' ? _operators2['default'][operator] : operator;
|
||||||
|
|
||||||
|
if (!op) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var format = op.format.split('1'),
|
||||||
|
left = format[0].length,
|
||||||
|
right = format[1].length;
|
||||||
|
|
||||||
|
return { left: left, right: right };
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Groups up operators and their arguments based on their precedence
|
||||||
|
* in deep arrays, the higher the priority, the deeper the group.
|
||||||
|
* This simplifies the evaluating process, the only thing to do is to
|
||||||
|
* evaluate from bottom up, evaluating deep groups first
|
||||||
|
*
|
||||||
|
* @param {Array} stack
|
||||||
|
* The parsed and grouped expression
|
||||||
|
* @return {Array}
|
||||||
|
* Grouped expression based on precedences
|
||||||
|
*/
|
||||||
|
var sortStack = (function (_sortStack) {
|
||||||
|
function sortStack(_x2) {
|
||||||
|
return _sortStack.apply(this, arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
sortStack.toString = function () {
|
||||||
|
return _sortStack.toString();
|
||||||
|
};
|
||||||
|
|
||||||
|
return sortStack;
|
||||||
|
})(function (stack) {
|
||||||
|
var _iteratorNormalCompletion = true;
|
||||||
|
var _didIteratorError = false;
|
||||||
|
var _iteratorError = undefined;
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (var _iterator = stack.entries()[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
|
||||||
|
var _step$value = _slicedToArray(_step.value, 2);
|
||||||
|
|
||||||
|
var index = _step$value[0];
|
||||||
|
var item = _step$value[1];
|
||||||
|
|
||||||
|
if (Array.isArray(item)) {
|
||||||
|
stack.splice(index, 1, sortStack(item));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
_didIteratorError = true;
|
||||||
|
_iteratorError = err;
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
if (!_iteratorNormalCompletion && _iterator['return']) {
|
||||||
|
_iterator['return']();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (_didIteratorError) {
|
||||||
|
throw _iteratorError;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = MIN_PRECEDENCE; i <= MAX_PRECEDENCE; i++) {
|
||||||
|
for (var index = 0; index < stack.length; ++index) {
|
||||||
|
var item = stack[index];
|
||||||
|
var op = _operators2['default'][item];
|
||||||
|
|
||||||
|
if (!op || op.precedence !== i) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var _formatInfo = formatInfo(op);
|
||||||
|
|
||||||
|
var left = _formatInfo.left;
|
||||||
|
var right = _formatInfo.right;
|
||||||
|
|
||||||
|
var group = stack.splice(index - left, left + right + 1, []);
|
||||||
|
stack[index - left] = group;
|
||||||
|
|
||||||
|
for (var y = 0; y < i; y++) {
|
||||||
|
group = [group];
|
||||||
|
}
|
||||||
|
|
||||||
|
index -= right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return stack;
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Evaluates the given math expression.
|
||||||
|
* The expression is an array with an operator and arguments
|
||||||
|
*
|
||||||
|
* Example: evaluate([2, '+', 4]) == 6
|
||||||
|
*
|
||||||
|
* @param {Array} stack
|
||||||
|
* A single math expression
|
||||||
|
* @return {Number}
|
||||||
|
* Result of the expression
|
||||||
|
*/
|
||||||
|
var evaluate = function evaluate(stack) {
|
||||||
|
var _operators$op;
|
||||||
|
|
||||||
|
var op = findOperator(stack);
|
||||||
|
if (!op) {
|
||||||
|
return stack[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
var _formatInfo2 = formatInfo(op);
|
||||||
|
|
||||||
|
var left = _formatInfo2.left;
|
||||||
|
|
||||||
|
var leftArguments = stack.slice(0, left),
|
||||||
|
rightArguments = stack.slice(left + 1);
|
||||||
|
|
||||||
|
return (_operators$op = _operators2['default'][op]).fn.apply(_operators$op, _toConsumableArray(leftArguments).concat(_toConsumableArray(rightArguments)));
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds the first operator in an array and returns it
|
||||||
|
*
|
||||||
|
* @param {Array} arr
|
||||||
|
* The array to look for an operator in
|
||||||
|
* @return {Object}
|
||||||
|
* The operator object or null if no operator is found
|
||||||
|
*/
|
||||||
|
var findOperator = function findOperator(arr) {
|
||||||
|
var _iteratorNormalCompletion2 = true;
|
||||||
|
var _didIteratorError2 = false;
|
||||||
|
var _iteratorError2 = undefined;
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (var _iterator2 = arr[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
|
||||||
|
var o = _step2.value;
|
||||||
|
|
||||||
|
if (typeof o === 'string') {
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
_didIteratorError2 = true;
|
||||||
|
_iteratorError2 = err;
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
if (!_iteratorNormalCompletion2 && _iterator2['return']) {
|
||||||
|
_iterator2['return']();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (_didIteratorError2) {
|
||||||
|
throw _iteratorError2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replaces constants in a string with their values
|
||||||
|
*
|
||||||
|
* @param {String} expression
|
||||||
|
* The expression to replace constants in
|
||||||
|
* @return {String}
|
||||||
|
* The expression with constants replaced
|
||||||
|
*/
|
||||||
|
var replaceConstants = function replaceConstants(expression) {
|
||||||
|
return expression.replace(/[A-Z]*/g, function (a) {
|
||||||
|
var c = _constants2['default'][a];
|
||||||
|
if (!c) {
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
return typeof c === 'function' ? c() : c;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports['default'] = Mathstring;
|
||||||
|
module.exports = exports['default'];
|
114
dist/operators.js
vendored
Normal file
114
dist/operators.js
vendored
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
Object.defineProperty(exports, '__esModule', {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
/*
|
||||||
|
* Operators and Functions
|
||||||
|
* fn: function used to evaluate value
|
||||||
|
* format: the format using which arguments are parsed:
|
||||||
|
* 0 indicates an argument and 1 indicates the operator
|
||||||
|
* e.g: factorial is 01, add is 010, like 2!, 2+2
|
||||||
|
* precedence: determines which operators should be evaluated first
|
||||||
|
* the lower the value, the higher the precedence
|
||||||
|
*/
|
||||||
|
exports['default'] = {
|
||||||
|
'^': {
|
||||||
|
fn: function fn(a, b) {
|
||||||
|
return Math.pow(a, b);
|
||||||
|
},
|
||||||
|
format: '010',
|
||||||
|
precedence: 0
|
||||||
|
},
|
||||||
|
'*': {
|
||||||
|
fn: function fn(a, b) {
|
||||||
|
return a * b;
|
||||||
|
},
|
||||||
|
format: '010',
|
||||||
|
precedence: 1
|
||||||
|
},
|
||||||
|
'/': {
|
||||||
|
fn: function fn(a, b) {
|
||||||
|
return a / b;
|
||||||
|
},
|
||||||
|
format: '010',
|
||||||
|
precedence: 1
|
||||||
|
},
|
||||||
|
'%': {
|
||||||
|
fn: function fn(a, b) {
|
||||||
|
return a % b;
|
||||||
|
},
|
||||||
|
format: '010',
|
||||||
|
precedence: 1
|
||||||
|
},
|
||||||
|
'\\': {
|
||||||
|
fn: function fn(a, b) {
|
||||||
|
return Math.floor(a / b);
|
||||||
|
},
|
||||||
|
format: '010',
|
||||||
|
precedence: 1
|
||||||
|
},
|
||||||
|
'+': {
|
||||||
|
fn: function fn(a, b) {
|
||||||
|
return a + b;
|
||||||
|
},
|
||||||
|
format: '010',
|
||||||
|
precedence: 2
|
||||||
|
},
|
||||||
|
'-': {
|
||||||
|
fn: function fn(a, b) {
|
||||||
|
return a - b;
|
||||||
|
},
|
||||||
|
format: '010',
|
||||||
|
precedence: 2
|
||||||
|
},
|
||||||
|
'!': {
|
||||||
|
fn: function fn(a) {
|
||||||
|
var sum = 1;
|
||||||
|
for (var i = 0; i < a; ++i) {
|
||||||
|
sum *= i;
|
||||||
|
}
|
||||||
|
return sum;
|
||||||
|
},
|
||||||
|
format: '01',
|
||||||
|
precedence: 2
|
||||||
|
},
|
||||||
|
log: {
|
||||||
|
fn: Math.log,
|
||||||
|
format: '10',
|
||||||
|
precedence: -1
|
||||||
|
},
|
||||||
|
ln: {
|
||||||
|
fn: Math.log,
|
||||||
|
format: '10',
|
||||||
|
precedence: -1
|
||||||
|
},
|
||||||
|
lg: {
|
||||||
|
fn: function fn(a) {
|
||||||
|
return Math.log(a) / Math.log(2);
|
||||||
|
},
|
||||||
|
format: '10',
|
||||||
|
precedence: -1
|
||||||
|
},
|
||||||
|
sin: {
|
||||||
|
fn: Math.sin,
|
||||||
|
format: '10',
|
||||||
|
precedence: -1
|
||||||
|
},
|
||||||
|
cos: {
|
||||||
|
fn: Math.cos,
|
||||||
|
format: '10',
|
||||||
|
precedence: -1
|
||||||
|
},
|
||||||
|
tan: {
|
||||||
|
fn: Math.tan,
|
||||||
|
format: '10',
|
||||||
|
precedence: -1
|
||||||
|
},
|
||||||
|
cot: {
|
||||||
|
fn: Math.cot,
|
||||||
|
format: '10',
|
||||||
|
precedence: -1
|
||||||
|
}
|
||||||
|
};
|
||||||
|
module.exports = exports['default'];
|
61
dist/readstream.js
vendored
Normal file
61
dist/readstream.js
vendored
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
Object.defineProperty(exports, '__esModule', {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
|
||||||
|
exports['default'] = function (string) {
|
||||||
|
var i = 0,
|
||||||
|
buffer = [];
|
||||||
|
return {
|
||||||
|
next: function next() {
|
||||||
|
buffer.push(string[i]);
|
||||||
|
|
||||||
|
if (i >= string.length) {
|
||||||
|
return null;
|
||||||
|
}return string[i++];
|
||||||
|
},
|
||||||
|
current: function current() {
|
||||||
|
return string[i - 1];
|
||||||
|
},
|
||||||
|
index: function index() {
|
||||||
|
return i - 1;
|
||||||
|
},
|
||||||
|
to: function to(n) {
|
||||||
|
var temp = '';
|
||||||
|
var dest = i + n;
|
||||||
|
for (i = i; i < dest; ++i) {
|
||||||
|
temp += [string[i]];
|
||||||
|
}
|
||||||
|
return temp;
|
||||||
|
},
|
||||||
|
drain: function drain() {
|
||||||
|
return buffer.splice(0, buffer.length);
|
||||||
|
},
|
||||||
|
replace: (function (_replace) {
|
||||||
|
function replace(_x, _x2, _x3) {
|
||||||
|
return _replace.apply(this, arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
replace.toString = function () {
|
||||||
|
return _replace.toString();
|
||||||
|
};
|
||||||
|
|
||||||
|
return replace;
|
||||||
|
})(function (start, end, replace) {
|
||||||
|
var temp = string.split('');
|
||||||
|
temp.splice(start, end, replace);
|
||||||
|
string = temp.join('');
|
||||||
|
|
||||||
|
i = i - (end - start);
|
||||||
|
}),
|
||||||
|
go: function go(n) {
|
||||||
|
i += n;
|
||||||
|
},
|
||||||
|
all: function all() {
|
||||||
|
return string;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = exports['default'];
|
73
dist/tests/basic.js
vendored
Normal file
73
dist/tests/basic.js
vendored
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
var _interopRequireWildcard = function (obj) { return obj && obj.__esModule ? obj : { 'default': obj }; };
|
||||||
|
|
||||||
|
var _expect = require('chai');
|
||||||
|
|
||||||
|
var _M = require('../index.js');
|
||||||
|
|
||||||
|
var _M2 = _interopRequireWildcard(_M);
|
||||||
|
|
||||||
|
describe('Basic math operators', function () {
|
||||||
|
it('should work for add +', function () {
|
||||||
|
_expect.expect(_M2['default'].solve('2+2')).to.equal(4);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work for minus -', function () {
|
||||||
|
_expect.expect(_M2['default'].solve('15-3')).to.equal(12);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work for divison /', function () {
|
||||||
|
_expect.expect(_M2['default'].solve('20/2')).to.equal(10);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work for multiplication *', function () {
|
||||||
|
_expect.expect(_M2['default'].solve('6*3')).to.equal(18);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work for power ^', function () {
|
||||||
|
_expect.expect(_M2['default'].solve('5^2')).to.equal(25);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work for multi-digit numbers', function () {
|
||||||
|
_expect.expect(_M2['default'].solve('12+15')).to.equal(27);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Precedence', function () {
|
||||||
|
it('Test case 1', function () {
|
||||||
|
_expect.expect(_M2['default'].solve('2+(2+1)*(1+1)^2')).to.equal(14);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Test case 2', function () {
|
||||||
|
_expect.expect(_M2['default'].solve('2+5*4/2-2')).to.equal(10);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Test case 3', function () {
|
||||||
|
_expect.expect(_M2['default'].solve('2+(5*4/2)-2')).to.equal(10);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Test case 4', function () {
|
||||||
|
_expect.expect(_M2['default'].solve('(2+2)^2+(5+1)*4+(2+(4/2)/2)')).to.equal(16 + 24 + 3);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Functions', function () {
|
||||||
|
it('should work for with parantheses', function () {
|
||||||
|
_expect.expect(_M2['default'].solve('lg(4) * 5')).to.equal(10);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work for without parantheses', function () {
|
||||||
|
_expect.expect(_M2['default'].solve('lg4 * 5')).to.equal(10);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Constats', function () {
|
||||||
|
it('should work for constant values', function () {
|
||||||
|
_expect.expect(_M2['default'].solve('sin(PI/2)')).to.equal(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work for functions as constants', function () {
|
||||||
|
_expect.expect(_M2['default'].solve('RAND')).to.not.equal(_M2['default'].solve('RAND'));
|
||||||
|
});
|
||||||
|
});
|
37
dist/tests/equation.js
vendored
Normal file
37
dist/tests/equation.js
vendored
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
var _interopRequireWildcard = function (obj) { return obj && obj.__esModule ? obj : { 'default': obj }; };
|
||||||
|
|
||||||
|
var _expect = require('chai');
|
||||||
|
|
||||||
|
var _M = require('../index.js');
|
||||||
|
|
||||||
|
var _M2 = _interopRequireWildcard(_M);
|
||||||
|
|
||||||
|
describe('Equations', function () {
|
||||||
|
it('should work with one variable', function () {
|
||||||
|
var equation = _M2['default'].equation('x+2');
|
||||||
|
|
||||||
|
_expect.expect(equation(2)).to.equal(4);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work with multiple variables', function () {
|
||||||
|
var equation = _M2['default'].equation('x+y');
|
||||||
|
_expect.expect(equation(2, 4)).to.equal(6);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work with multiple instances of the same variable', function () {
|
||||||
|
var equation = _M2['default'].equation('x*x');
|
||||||
|
_expect.expect(equation(4)).to.equal(16);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should only accept lowercase letters', function () {
|
||||||
|
var equation = _M2['default'].equation('X+2');
|
||||||
|
_expect.expect(equation).to['throw']();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Test case', function () {
|
||||||
|
var equation = _M2['default'].equation('2+x*(y+4)+z^2');
|
||||||
|
_expect.expect(equation(2, 4, 3)).to.equal(27);
|
||||||
|
});
|
||||||
|
});
|
73
dist/tests/solve.js
vendored
Normal file
73
dist/tests/solve.js
vendored
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
var _interopRequireWildcard = function (obj) { return obj && obj.__esModule ? obj : { 'default': obj }; };
|
||||||
|
|
||||||
|
var _expect = require('chai');
|
||||||
|
|
||||||
|
var _M = require('../index.js');
|
||||||
|
|
||||||
|
var _M2 = _interopRequireWildcard(_M);
|
||||||
|
|
||||||
|
describe('Basic math operators', function () {
|
||||||
|
it('should work for add +', function () {
|
||||||
|
_expect.expect(_M2['default'].solve('2+2')).to.equal(4);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work for minus -', function () {
|
||||||
|
_expect.expect(_M2['default'].solve('15-3')).to.equal(12);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work for divison /', function () {
|
||||||
|
_expect.expect(_M2['default'].solve('20/2')).to.equal(10);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work for multiplication *', function () {
|
||||||
|
_expect.expect(_M2['default'].solve('6*3')).to.equal(18);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work for power ^', function () {
|
||||||
|
_expect.expect(_M2['default'].solve('5^2')).to.equal(25);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work for multi-digit numbers', function () {
|
||||||
|
_expect.expect(_M2['default'].solve('12+15')).to.equal(27);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Precedence', function () {
|
||||||
|
it('Test case 1', function () {
|
||||||
|
_expect.expect(_M2['default'].solve('2+(2+1)*(1+1)^2')).to.equal(14);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Test case 2', function () {
|
||||||
|
_expect.expect(_M2['default'].solve('2+5*4/2-2')).to.equal(10);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Test case 3', function () {
|
||||||
|
_expect.expect(_M2['default'].solve('2+(5*4/2)-2')).to.equal(10);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Test case 4', function () {
|
||||||
|
_expect.expect(_M2['default'].solve('(2+2)^2+(5+1)*4+(2+(4/2)/2)')).to.equal(16 + 24 + 3);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Functions', function () {
|
||||||
|
it('should work for with parantheses', function () {
|
||||||
|
_expect.expect(_M2['default'].solve('lg(4) * 5')).to.equal(10);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work for without parantheses', function () {
|
||||||
|
_expect.expect(_M2['default'].solve('lg4 * 5')).to.equal(10);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Constats', function () {
|
||||||
|
it('should work for constant values', function () {
|
||||||
|
_expect.expect(_M2['default'].solve('sin(PI/2)')).to.equal(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work for functions as constants', function () {
|
||||||
|
_expect.expect(_M2['default'].solve('RAND')).to.not.equal(_M2['default'].solve('RAND'));
|
||||||
|
});
|
||||||
|
});
|
78
helpers.js
Normal file
78
helpers.js
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
export const parseFormat = function(a) {
|
||||||
|
const split = a.split('1');
|
||||||
|
return {
|
||||||
|
left: split[0].length,
|
||||||
|
right: split[1].length
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isNumber = a => {
|
||||||
|
return !isNaN(+a);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const parseNumbers = (a) => {
|
||||||
|
return a.map(b => {
|
||||||
|
if (isNumber(b)) {
|
||||||
|
return parseFloat(b);
|
||||||
|
}
|
||||||
|
if (Array.isArray(b)) {
|
||||||
|
return parseNumbers(b);
|
||||||
|
}
|
||||||
|
return b;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const dive = (arr, n) => {
|
||||||
|
let result = arr;
|
||||||
|
for (let i = 0; i < n; ++i) {
|
||||||
|
result = result[result.length - 1];
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const deep = (arr, n, index = 0) => {
|
||||||
|
if (n < 2) {
|
||||||
|
return {arr, index};
|
||||||
|
}
|
||||||
|
|
||||||
|
let d = arr.reduce((a, b, i) => {
|
||||||
|
if (Array.isArray(b)) {
|
||||||
|
let {arr, index: x} = deep(b, n - 1, i),
|
||||||
|
merged = a.concat(arr);
|
||||||
|
|
||||||
|
index = x;
|
||||||
|
return merged;
|
||||||
|
}
|
||||||
|
return a;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return {arr: d, index};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const diveTo = (arr, indexes, replace) => {
|
||||||
|
let answer = [];
|
||||||
|
if (indexes.some(Array.isArray)) {
|
||||||
|
for (let index of indexes) {
|
||||||
|
answer.push(diveTo(arr, index, replace));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
arr[indexes[0]] = replace;
|
||||||
|
return replace;
|
||||||
|
}
|
||||||
|
|
||||||
|
return answer;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const flatten = (arr) => {
|
||||||
|
if (!Array.isArray(arr) || !arr.some(Array.isArray)) {
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return arr.reduce((a, b) => {
|
||||||
|
return a.concat(flatten(b));
|
||||||
|
}, []);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const removeSymbols = string => {
|
||||||
|
return string.replace(/\W/g, '');
|
||||||
|
};
|
296
index.js
Normal file
296
index.js
Normal file
@ -0,0 +1,296 @@
|
|||||||
|
import ReadStream from './readstream';
|
||||||
|
import operators from './operators';
|
||||||
|
import constants from './constats';
|
||||||
|
import * as _ from './helpers';
|
||||||
|
|
||||||
|
let Mathstring = {
|
||||||
|
/**
|
||||||
|
* Solves the given math expression, following these steps:
|
||||||
|
* 1. Replace constants in the expression
|
||||||
|
* 2. parse the expression, separating numbers and operators into
|
||||||
|
* an array
|
||||||
|
* 3. Sort the stack by operators' precedence, it actually groups the
|
||||||
|
* operator with it's arguments n-level deep
|
||||||
|
* 4. Apply parseFloat to numbers of the array
|
||||||
|
* 5. Solve groups recursively
|
||||||
|
*
|
||||||
|
* @param {String} expression
|
||||||
|
* The math expression to solve
|
||||||
|
* @return {Number}
|
||||||
|
* Result of the expression
|
||||||
|
*/
|
||||||
|
solve(expression) {
|
||||||
|
// replace constants with their values
|
||||||
|
expression = replaceConstants(expression);
|
||||||
|
|
||||||
|
let stack = parseExpression(expression);
|
||||||
|
stack = sortStack(stack);
|
||||||
|
stack = _.parseNumbers(stack);
|
||||||
|
stack = solveStack(stack);
|
||||||
|
|
||||||
|
return stack;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Creates an equation function which replaces variables
|
||||||
|
* in the given expression with the values specified in order,
|
||||||
|
* and solves the new expression
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* equation('x+y+z')(2, 5, 6) => solve('2+5+6')
|
||||||
|
*
|
||||||
|
* @param {String} expression
|
||||||
|
* The expression to create an equation for (containing variables)
|
||||||
|
* @return {Function}
|
||||||
|
* The function which replaces variables with values in order
|
||||||
|
* and solves the expression
|
||||||
|
*/
|
||||||
|
equation(expression) {
|
||||||
|
let stack = parseExpression(expression);
|
||||||
|
let variables = [];
|
||||||
|
|
||||||
|
stack.forEach(a => {
|
||||||
|
if (!_.isNumber(a) && !operators[a] && a === a.toLowerCase()) {
|
||||||
|
// grouped variables like (y) need to have their parantheses removed
|
||||||
|
variables.push(_.removeSymbols(a));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return function(...args) {
|
||||||
|
expression = expression.replace(/[a-z]*/g, a => {
|
||||||
|
let index = variables.indexOf(a);
|
||||||
|
if (index > -1) {
|
||||||
|
return args[index] || 0;
|
||||||
|
}
|
||||||
|
return a;
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(variables, expression);
|
||||||
|
return Mathstring.solve(expression);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const solveStack = stack => {
|
||||||
|
// $0(stack);
|
||||||
|
if (stack.some(Array.isArray)) {
|
||||||
|
stack = stack.map(group => {
|
||||||
|
if (!Array.isArray(group)) {
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
return solveStack(group);
|
||||||
|
});
|
||||||
|
|
||||||
|
return solveStack(stack);
|
||||||
|
} else {
|
||||||
|
return evaluate(stack);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const PRECEDENCES = Object.keys(operators).map(key => {
|
||||||
|
return operators[key].precedence;
|
||||||
|
});
|
||||||
|
|
||||||
|
const MAX_PRECEDENCE = Math.max(...PRECEDENCES);
|
||||||
|
const MIN_PRECEDENCE = Math.min(...PRECEDENCES);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the given expression into an array of separated
|
||||||
|
* numbers and operators/functions.
|
||||||
|
* The result is passed to parseGroups
|
||||||
|
*
|
||||||
|
* @param {String} expression
|
||||||
|
* The expression to parse
|
||||||
|
* @return {Array}
|
||||||
|
* The parsed array
|
||||||
|
*/
|
||||||
|
const parseExpression = expression => {
|
||||||
|
let stream = new ReadStream(expression),
|
||||||
|
stack = [],
|
||||||
|
record = '';
|
||||||
|
|
||||||
|
// Create an array of separated numbers & operators
|
||||||
|
while (stream.next()) {
|
||||||
|
const cur = stream.current();
|
||||||
|
if (cur === ' ') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// it's probably a function with a length more than one
|
||||||
|
if (!_.isNumber(cur) && !operators[cur] && cur !== '.') {
|
||||||
|
record += cur;
|
||||||
|
} else if (record.length) {
|
||||||
|
stack.push(record, cur);
|
||||||
|
record = '';
|
||||||
|
} else if (_.isNumber(stack[stack.length - 1]) &&
|
||||||
|
(_.isNumber(cur) || cur === '.')) {
|
||||||
|
|
||||||
|
stack[stack.length - 1] += cur;
|
||||||
|
} else {
|
||||||
|
stack.push(cur);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (record.length) {
|
||||||
|
stack.push(record);
|
||||||
|
}
|
||||||
|
|
||||||
|
// $0(stack);
|
||||||
|
return parseGroups(stack);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes the parsed array from parseExpression and
|
||||||
|
* groups up expressions in parantheses in deep arrays
|
||||||
|
*
|
||||||
|
* Example: 2+(5+4) becomes [2, [5, '+', 4]]
|
||||||
|
*
|
||||||
|
* @param {Array} stack
|
||||||
|
* The parsed expression
|
||||||
|
* @return {Array}
|
||||||
|
* Grouped up expression
|
||||||
|
*/
|
||||||
|
const parseGroups = stack => {
|
||||||
|
// Parantheses become inner arrays which will then be processed first
|
||||||
|
let sub = 0;
|
||||||
|
return stack.reduce((a, b) => {
|
||||||
|
if (b[b.length - 1] === '(') {
|
||||||
|
if (b.length > 1) {
|
||||||
|
_.dive(a, sub).push(b.slice(0, -1), []);
|
||||||
|
} else {
|
||||||
|
_.dive(a, sub).push([]);
|
||||||
|
}
|
||||||
|
sub++;
|
||||||
|
} else if (b === ')') {
|
||||||
|
sub--;
|
||||||
|
} else {
|
||||||
|
_.dive(a, sub).push(b);
|
||||||
|
}
|
||||||
|
return a;
|
||||||
|
}, []);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gives information about an operator's format
|
||||||
|
* including number of left and right arguments
|
||||||
|
*
|
||||||
|
* @param {String/Object} operator
|
||||||
|
* The operator object or operator name (e.g. +, -)
|
||||||
|
* @return {Object}
|
||||||
|
* An object including the count of left and right arguments
|
||||||
|
*/
|
||||||
|
const formatInfo = operator => {
|
||||||
|
let op = typeof operator === 'string' ? operators[operator]
|
||||||
|
: operator;
|
||||||
|
|
||||||
|
if (!op) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const format = op.format.split('1'),
|
||||||
|
left = format[0].length,
|
||||||
|
right = format[1].length;
|
||||||
|
|
||||||
|
return { left, right };
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Groups up operators and their arguments based on their precedence
|
||||||
|
* in deep arrays, the higher the priority, the deeper the group.
|
||||||
|
* This simplifies the evaluating process, the only thing to do is to
|
||||||
|
* evaluate from bottom up, evaluating deep groups first
|
||||||
|
*
|
||||||
|
* @param {Array} stack
|
||||||
|
* The parsed and grouped expression
|
||||||
|
* @return {Array}
|
||||||
|
* Grouped expression based on precedences
|
||||||
|
*/
|
||||||
|
const sortStack = stack => {
|
||||||
|
for (let [index, item] of stack.entries()) {
|
||||||
|
if (Array.isArray(item)) {
|
||||||
|
stack.splice(index, 1, sortStack(item));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = MIN_PRECEDENCE; i <= MAX_PRECEDENCE; i++) {
|
||||||
|
for (let index = 0; index < stack.length; ++index) {
|
||||||
|
let item = stack[index];
|
||||||
|
let op = operators[item];
|
||||||
|
|
||||||
|
if (!op || op.precedence !== i) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { left, right } = formatInfo(op);
|
||||||
|
let group = stack.splice(index - left, left + right + 1, []);
|
||||||
|
stack[index - left] = group;
|
||||||
|
|
||||||
|
for (let y = 0; y < i; y++) {
|
||||||
|
group = [group];
|
||||||
|
}
|
||||||
|
|
||||||
|
index -= right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return stack;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Evaluates the given math expression.
|
||||||
|
* The expression is an array with an operator and arguments
|
||||||
|
*
|
||||||
|
* Example: evaluate([2, '+', 4]) == 6
|
||||||
|
*
|
||||||
|
* @param {Array} stack
|
||||||
|
* A single math expression
|
||||||
|
* @return {Number}
|
||||||
|
* Result of the expression
|
||||||
|
*/
|
||||||
|
const evaluate = stack => {
|
||||||
|
const op = findOperator(stack);
|
||||||
|
if (!op) {
|
||||||
|
return stack[0];
|
||||||
|
}
|
||||||
|
const { left } = formatInfo(op);
|
||||||
|
|
||||||
|
let leftArguments = stack.slice(0, left),
|
||||||
|
rightArguments = stack.slice(left + 1);
|
||||||
|
|
||||||
|
return operators[op].fn(...leftArguments, ...rightArguments);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds the first operator in an array and returns it
|
||||||
|
*
|
||||||
|
* @param {Array} arr
|
||||||
|
* The array to look for an operator in
|
||||||
|
* @return {Object}
|
||||||
|
* The operator object or null if no operator is found
|
||||||
|
*/
|
||||||
|
const findOperator = arr => {
|
||||||
|
for (let o of arr) {
|
||||||
|
if (typeof o === 'string') {
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replaces constants in a string with their values
|
||||||
|
*
|
||||||
|
* @param {String} expression
|
||||||
|
* The expression to replace constants in
|
||||||
|
* @return {String}
|
||||||
|
* The expression with constants replaced
|
||||||
|
*/
|
||||||
|
const replaceConstants = expression => {
|
||||||
|
return expression.replace(/[A-Z]*/g, (a) => {
|
||||||
|
let c = constants[a];
|
||||||
|
if (!c) {
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
return typeof c === 'function' ? c() : c;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Mathstring;
|
92
operators.js
Normal file
92
operators.js
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
/*
|
||||||
|
* Operators and Functions
|
||||||
|
* fn: function used to evaluate value
|
||||||
|
* format: the format using which arguments are parsed:
|
||||||
|
* 0 indicates an argument and 1 indicates the operator
|
||||||
|
* e.g: factorial is 01, add is 010, like 2!, 2+2
|
||||||
|
* precedence: determines which operators should be evaluated first
|
||||||
|
* the lower the value, the higher the precedence
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
|
'^': {
|
||||||
|
fn: (a, b) => Math.pow(a, b),
|
||||||
|
format: '010',
|
||||||
|
precedence: 0
|
||||||
|
},
|
||||||
|
'*': {
|
||||||
|
fn: (a, b) => a * b,
|
||||||
|
format: '010',
|
||||||
|
precedence: 1
|
||||||
|
},
|
||||||
|
'/': {
|
||||||
|
fn: (a, b) => a / b,
|
||||||
|
format: '010',
|
||||||
|
precedence: 1
|
||||||
|
},
|
||||||
|
'%': {
|
||||||
|
fn: (a, b) => a % b,
|
||||||
|
format: '010',
|
||||||
|
precedence: 1
|
||||||
|
},
|
||||||
|
'\\': {
|
||||||
|
fn: (a, b) => Math.floor(a / b),
|
||||||
|
format: '010',
|
||||||
|
precedence: 1
|
||||||
|
},
|
||||||
|
'+': {
|
||||||
|
fn: (a, b) => a + b,
|
||||||
|
format: '010',
|
||||||
|
precedence: 2
|
||||||
|
},
|
||||||
|
'-': {
|
||||||
|
fn: (a, b) => a - b,
|
||||||
|
format: '010',
|
||||||
|
precedence: 2
|
||||||
|
},
|
||||||
|
'!': {
|
||||||
|
fn: (a) => {
|
||||||
|
let sum = 1;
|
||||||
|
for (var i = 0; i < a; ++i) {
|
||||||
|
sum *= i;
|
||||||
|
}
|
||||||
|
return sum;
|
||||||
|
},
|
||||||
|
format: '01',
|
||||||
|
precedence: 2
|
||||||
|
},
|
||||||
|
'log': {
|
||||||
|
fn: Math.log,
|
||||||
|
format: '10',
|
||||||
|
precedence: -1
|
||||||
|
},
|
||||||
|
'ln': {
|
||||||
|
fn: Math.log,
|
||||||
|
format: '10',
|
||||||
|
precedence: -1
|
||||||
|
},
|
||||||
|
'lg': {
|
||||||
|
fn: (a) => Math.log(a) / Math.log(2),
|
||||||
|
format: '10',
|
||||||
|
precedence: -1
|
||||||
|
},
|
||||||
|
'sin': {
|
||||||
|
fn: Math.sin,
|
||||||
|
format: '10',
|
||||||
|
precedence: -1
|
||||||
|
},
|
||||||
|
'cos': {
|
||||||
|
fn: Math.cos,
|
||||||
|
format: '10',
|
||||||
|
precedence: -1
|
||||||
|
},
|
||||||
|
'tan': {
|
||||||
|
fn: Math.tan,
|
||||||
|
format: '10',
|
||||||
|
precedence: -1
|
||||||
|
},
|
||||||
|
'cot': {
|
||||||
|
fn: Math.cot,
|
||||||
|
format: '10',
|
||||||
|
precedence: -1
|
||||||
|
}
|
||||||
|
};
|
26
package.json
Normal file
26
package.json
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"name": "mathstring",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "dist/index.js",
|
||||||
|
"directories": {
|
||||||
|
"test": "tests"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"test": "grunt test"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"Math",
|
||||||
|
"JavaScript"
|
||||||
|
],
|
||||||
|
"author": "Mahdi Dibaiee",
|
||||||
|
"license": "MIT",
|
||||||
|
"devDependencies": {
|
||||||
|
"grunt": "^0.4.5",
|
||||||
|
"grunt-babel": "^5.0.0",
|
||||||
|
"grunt-contrib-watch": "^0.6.1",
|
||||||
|
"grunt-eslint": "^11.0.0",
|
||||||
|
"grunt-mocha-test": "^0.12.7",
|
||||||
|
"mocha": "^2.2.4"
|
||||||
|
}
|
||||||
|
}
|
41
readstream.js
Normal file
41
readstream.js
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
export default function(string) {
|
||||||
|
let i = 0, buffer = [];
|
||||||
|
return {
|
||||||
|
next() {
|
||||||
|
buffer.push(string[i]);
|
||||||
|
|
||||||
|
if (i >= string.length) return null;
|
||||||
|
return string[i++];
|
||||||
|
},
|
||||||
|
current() {
|
||||||
|
return string[i - 1];
|
||||||
|
},
|
||||||
|
index() {
|
||||||
|
return i - 1;
|
||||||
|
},
|
||||||
|
to(n) {
|
||||||
|
let temp = '';
|
||||||
|
const dest = i + n;
|
||||||
|
for (i = i; i < dest; ++i) {
|
||||||
|
temp += [string[i]];
|
||||||
|
}
|
||||||
|
return temp;
|
||||||
|
},
|
||||||
|
drain() {
|
||||||
|
return buffer.splice(0, buffer.length);
|
||||||
|
},
|
||||||
|
replace(start, end, replace) {
|
||||||
|
let temp = string.split('');
|
||||||
|
temp.splice(start, end, replace);
|
||||||
|
string = temp.join('');
|
||||||
|
|
||||||
|
i = i - (end - start);
|
||||||
|
},
|
||||||
|
go(n) {
|
||||||
|
i += n;
|
||||||
|
},
|
||||||
|
all() {
|
||||||
|
return string;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
30
tests/equation.js
Normal file
30
tests/equation.js
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import {expect} from 'chai';
|
||||||
|
import M from '../index.js';
|
||||||
|
|
||||||
|
describe('Equations', () => {
|
||||||
|
it('should work with one variable', () => {
|
||||||
|
let equation = M.equation('x+2');
|
||||||
|
|
||||||
|
expect(equation(2)).to.equal(4);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work with multiple variables', () => {
|
||||||
|
let equation = M.equation('x+y');
|
||||||
|
expect(equation(2, 4)).to.equal(6);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work with multiple instances of the same variable', () => {
|
||||||
|
let equation = M.equation('x*x');
|
||||||
|
expect(equation(4)).to.equal(16);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should only accept lowercase letters', () => {
|
||||||
|
let equation = M.equation('X+2');
|
||||||
|
expect(equation).to.throw();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Test case', () => {
|
||||||
|
let equation = M.equation('2+x*(y+4)+z^2');
|
||||||
|
expect(equation(2, 4, 3)).to.equal(27);
|
||||||
|
});
|
||||||
|
});
|
66
tests/solve.js
Normal file
66
tests/solve.js
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
import {expect} from 'chai';
|
||||||
|
import M from '../index.js';
|
||||||
|
|
||||||
|
describe('Basic math operators', () => {
|
||||||
|
it('should work for add +', () => {
|
||||||
|
expect(M.solve('2+2')).to.equal(4);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work for minus -', () => {
|
||||||
|
expect(M.solve('15-3')).to.equal(12);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work for divison /', () => {
|
||||||
|
expect(M.solve('20/2')).to.equal(10);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work for multiplication *', () => {
|
||||||
|
expect(M.solve('6*3')).to.equal(18);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work for power ^', () => {
|
||||||
|
expect(M.solve('5^2')).to.equal(25);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work for multi-digit numbers', () => {
|
||||||
|
expect(M.solve('12+15')).to.equal(27);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Precedence', () => {
|
||||||
|
it('Test case 1', () => {
|
||||||
|
expect(M.solve('2+(2+1)*(1+1)^2')).to.equal(14);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Test case 2', () => {
|
||||||
|
expect(M.solve('2+5*4/2-2')).to.equal(10);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Test case 3', () => {
|
||||||
|
expect(M.solve('2+(5*4/2)-2')).to.equal(10);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Test case 4', () => {
|
||||||
|
expect(M.solve('(2+2)^2+(5+1)*4+(2+(4/2)/2)')).to.equal(16 + 24 + 3);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Functions', () => {
|
||||||
|
it('should work for with parantheses', () => {
|
||||||
|
expect(M.solve('lg(4) * 5')).to.equal(10);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work for without parantheses', () => {
|
||||||
|
expect(M.solve('lg4 * 5')).to.equal(10);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Constats', () => {
|
||||||
|
it('should work for constant values', () => {
|
||||||
|
expect(M.solve('sin(PI/2)')).to.equal(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work for functions as constants', () => {
|
||||||
|
expect(M.solve('RAND')).to.not.equal(M.solve('RAND'));
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user