Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- var ExpressionParser = (function() {
- 'use strict';
- var NUMERIC_CHARSET = '01234567890.',
- ALPHA_CHARSET = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_',
- OPERATOR_CHARSET = '+-/*^%',
- WHITE_SPACE_REGEX = /\s/;
- var MathFunctions = {
- sin: function(radians) { return Math.sin(radians); },
- cos: function(radians) { return Math.cos(radians); },
- tan: function(radians) { return Math.tan(radians); },
- fact: function (value) {
- var iter,
- multiplier,
- returnValue = value;
- for(multiplier = value - 1; multiplier > 0; --multiplier) {
- returnValue *= multiplier;
- }
- return returnValue;
- },
- exp: function (value) { return Math.exp(value); },
- sqrt: function (value) { return Math.sqrt(value); },
- ceil: function (value) { return Math.ceil(value); },
- floor: function (value) { return Math.floor(value); },
- abs: function (value) { return Math.abs(value); },
- acos: function (value) { return Math.acos(value); },
- asin: function (value) { return Math.asin(value); },
- atan: function (value) { return Math.atan(value); },
- round: function (value) { return Math.round(value); }
- };
- var Helpers = {
- isNumeric: function(char) { return NUMERIC_CHARSET.indexOf(char) !== -1; },
- isAlpha: function(char) { return ALPHA_CHARSET.indexOf(char) !== -1; },
- isOperator: function(char) { return OPERATOR_CHARSET.indexOf(char) !== -1; },
- isMathFunction: function(keyword) { return typeof MathFunctions[keyword] === 'function'; },
- isWhitespace: function(char) { return WHITE_SPACE_REGEX.test(char); },
- radians: function(angle) { return angle * Math.PI / 180; }
- };
- var OperatorFunctions = {
- '+': function(left, right) { return left + right },
- '-': function (left, right) { return left - right },
- '/': function (left, right) { return left / right },
- '*': function (left, right) { return left * right },
- '%': function (left, right) { return left % right },
- '^': function (left, right) { return Math.pow(left, right); }
- };
- function EP() {
- 'use strict';
- this.variables = {
- pi: Math.PI,
- PI: Math.PI,
- e: Math.E,
- E: Math.E,
- rand: function() { return Math.random() }
- };
- this.readOnlyVariables = Object.keys(this.variables).map(function(varName) { return varName; });
- };
- /* Sets a variable */
- EP.prototype.setVariable = function(name, value) {
- 'use strict';
- if(this.readOnlyVariables.indexOf(name) !== -1) {
- throw new Error('Cannot set read only variable "' + name + '"');
- }
- this.variables[name] = value;
- };
- /* Gets a variable */
- EP.prototype.getVariable = function(name) {
- 'use strict';
- if(this.isVariable(name)) {
- var variable = this.variables[name];
- if(typeof variable === 'function') {
- return variable();
- }
- return variable;
- }
- return undefined;
- };
- /* Checks if a variable exists */
- EP.prototype.isVariable = function(name) {
- 'use strict';
- return this.variables.hasOwnProperty(name);
- };
- /* Parse an expression */
- EP.prototype.parse = function(expression) {
- 'use strict';
- var tokens = this.tokenize(expression);
- tokens = this.parseTokens(tokens);
- var tokensLength = tokens.length,
- iter,
- value = null,
- last_number = null,
- flag_assignment = false;
- for(iter = 0; iter < tokensLength; ++iter) {
- // Get the value
- if(tokens[iter][0] === 'number') {
- value = tokens[iter][1];
- }
- if(tokens[iter][0] === 'assignment') {
- if(
- iter - 1 === 0 && // Check there is a keyword previous
- iter + 1 < tokensLength && // Check there is a value to set next
- tokens[iter - 1][0] === 'keyword'
- ) {
- flag_assignment = true;
- } else {
- throw new Error('Unexpected assignment');
- }
- }
- }
- if(flag_assignment) {
- this.setVariable(tokens[0][1], value);
- }
- return value;
- };
- /* Parse tokens */
- EP.prototype.parseTokens = function(tkns) {
- 'use strict';
- var tokens = tkns;
- tokens = this.parseVariables(tokens);
- tokens = this.parseBrackets(tokens);
- tokens = this.parseNegatives(tokens);
- tokens = this.parseFunctions(tokens);
- tokens = this.parseOperations(tokens);
- return tokens;
- };
- EP.prototype.parseBrackets = function(tkns) {
- 'use strict';
- var tokens = tkns,
- tokensLength = tokens.length,
- bracketDepth = 0,
- bracketIndex = 0,
- iter;
- for(iter = 0; iter < tokensLength; ++iter) {
- if(tokens[iter][0] === 'bracket') {
- if(tokens[iter][1] === ')' && bracketDepth === 0) {
- throw new Error('Invalid bracket syntax');
- }
- if(bracketDepth > 0) {
- if(tokens[iter][1] === ')') {
- --bracketDepth;
- }
- if(bracketDepth === 0) {
- var leftSide = tokens.slice(0, bracketIndex),
- parsed = this.parseTokens(tokens.slice(bracketIndex + 1, iter)),
- rightSide = tokens.slice(iter + 1);
- tokens = leftSide.concat(parsed, rightSide);
- iter += tokens.length - tokensLength;
- tokensLength = tokens.length;
- }
- }
- if(tokens[iter][1] === '(') {
- if(bracketDepth === 0) {
- bracketIndex = iter;
- }
- ++bracketDepth;
- }
- }
- }
- if(bracketDepth > 0) {
- throw new Error('Invalid bracket syntax');
- }
- return tokens;
- };
- EP.prototype.parseNegatives = function(tkns) {
- 'use strict';
- var tokens = tkns,
- tokensLength = tokens.length,
- iter;
- for(iter = 0; iter < tokensLength; ++iter) {
- // Logic for a negative number
- if(
- tokens[iter][0] === 'operator' &&
- (
- tokens[iter][1] === '-' || // Check it's a minus symbol
- tokens[iter][1] === '+' // Or a plus symbol
- ) &&
- (
- iter - 1 < 0 || // Either there is no previous token...
- tokens[iter - 1][0] !== 'number' // Or it's not a number
- ) &&
- iter + 1 < tokensLength && // Check there is a proceeding token
- tokens[iter + 1][0] === 'number' // And it's a number
- ) {
- // Make the next number a negative
- tokens[iter + 1][1] = tokens[iter][1] === '-' ? -tokens[iter + 1][1] : tokens[iter + 1][1];
- // Remove this token from stack
- tokens.splice(iter, 1);
- --tokensLength;
- --iter;
- continue;
- }
- }
- return tokens;
- };
- EP.prototype.parseVariables = function(tkns) {
- 'use strict';
- var tokens = tkns,
- tokensLength = tokens.length,
- iter;
- for(iter = 0; iter < tokensLength; ++iter) {
- if(tokens[iter][0] === 'keyword') {
- if(
- !Helpers.isMathFunction(tokens[iter][1]) && // Check it's not a function
- (
- iter === tokensLength - 1 || // Either this is the last token
- tokens[iter + 1][0] !== 'assignment' // Or the next token is not an assignment
- )
- ) {
- // Check variable exists
- if(this.isVariable(tokens[iter][1])) {
- if(
- iter - 1 >= 0 &&
- tokens[iter - 1][0] === 'number'
- ) {
- tokens[iter][0] = 'number';
- tokens[iter][1] = this.getVariable(tokens[iter][1]) * tokens[iter - 1][1];
- var leftSide = tokens.slice(0, iter - 1),
- rightSide = tokens.slice(iter);
- tokens = leftSide.concat(rightSide);
- --iter;
- --tokensLength;
- } else {
- tokens[iter][0] = 'number';
- tokens[iter][1] = this.getVariable(tokens[iter][1]);
- }
- continue;
- } else {
- throw new Error('Undefined variable "' + tokens[iter][1] + '"');
- }
- }
- }
- }
- return tokens;
- };
- EP.prototype.parseFunctions = function(tkns) {
- 'use strict';
- var tokens = tkns,
- tokensLength = tokens.length,
- iter;
- for(iter = 0; iter < tokensLength; ++iter) {
- if(tokens[iter][0] === 'keyword' && tokens[iter][1] in MathFunctions) {
- if(
- iter + 1 < tokensLength && // Check this is not the last token
- tokens[iter + 1][0] === 'number' // And the last next token is a number
- ) {
- // Apply math function
- tokens[iter + 1][1] = MathFunctions[tokens[iter][1]](tokens[iter + 1][1]);
- // Remove this token from stack
- tokens.splice(iter, 1);
- --tokensLength;
- --iter;
- } else {
- throw new Error('unexpected function "' + tokens[iter][1] + '"');
- }
- }
- }
- return tokens;
- };
- EP.prototype.parseOperations = function(tkns) {
- 'use strict';
- // Order of operations
- var operators = ['^', '/', '*', '+', '-'],
- tokens = tkns,
- self = this;
- operators.forEach(function(operator) {
- tokens = self.parseOperator(tokens, operator);
- });
- return tokens;
- };
- EP.prototype.parseOperator = function(tkns, oprtr) {
- 'use strict';
- var tokens = tkns,
- operator = oprtr,
- tokensLength = tokens.length,
- iter;
- for(iter = 0; iter < tokensLength; ++iter) {
- var token = tokens[iter],
- token_type = token[0],
- token_value = token[1];
- if(token_type === 'operator' && token_value === operator) {
- if(
- iter - 1 >= 0 && // Check there is a previous token
- iter + 1 < tokensLength && // Check there is a next token
- tokens[iter - 1][0] === 'number' && // Check the previous token is a number
- tokens[iter + 1][0] === 'number' // Check the next token is a number
- ) {
- tokens[iter + 1][1] = OperatorFunctions[token_value](tokens[iter - 1][1], tokens[iter + 1][1]);
- var leftSide = tokens.slice(0, iter - 1),
- rightSide = tokens.slice(iter + 1);
- // Replace sub-expression with the result value
- tokens = leftSide.concat(rightSide);
- iter += tokens.length - tokensLength;
- tokensLength = tokens.length;
- continue;
- } else {
- throw new Error('unexpected operator "' + tokens[iter][1] + '"');
- }
- }
- }
- return tokens;
- };
- /**
- * Split expression into tokens
- */
- EP.prototype.tokenize = function(expr) {
- 'use strict';
- // TOKENIZER VARS
- var expression = expr + ' ', // Append space so that the last character before that space is tokenised
- expressionLength = expression.length,
- iter,
- tokens = [ ],
- buffer = '';
- // FLAGS
- var flag_numeric = false,
- flag_keyword = false,
- flag_operator = false,
- flag_sciNotation = false;
- // Iterate through expression
- for(iter = 0; iter < expressionLength; ++iter) {
- var char = expression.charAt(iter),
- char_isNumeric = Helpers.isNumeric(char),
- char_isOperator = Helpers.isOperator(char),
- char_isAlpha = Helpers.isAlpha(char);
- if(flag_keyword) {
- // We've reached the end of the keyword
- if(!char_isAlpha) {
- flag_keyword = false;
- tokens.push(['keyword', buffer]);
- buffer = '';
- }
- }
- if(flag_numeric) {
- // We've reached the end of the number
- if(!char_isNumeric) {
- if(char === 'e') {
- flag_sciNotation = true;
- buffer += char;
- continue;
- }
- if(flag_sciNotation && (char === '+' || char === '-')) {
- flag_sciNotation = false;
- buffer += char;
- continue;
- }
- // Skip char if comma, so we can allow for formatted numbers
- if(char === ',' && iter + 1 < expressionLength && Helpers.isNumeric(expression[iter + 1])) {
- continue;
- }
- var parsingFunction = parseInt;
- if(buffer.indexOf('.') !== -1) { // Check for float
- parsingFunction = parseFloat;
- }
- tokens.push(['number', new Number(buffer)]);
- flag_numeric = false;
- flag_sciNotation = false;
- buffer = '';
- } else {
- flag_sciNotation = false;
- }
- }
- if(char_isNumeric) { // Check for a number
- flag_numeric = true;
- buffer += char;
- } else if(char_isAlpha) { // Check for a keyword
- flag_keyword = true;
- buffer += char;
- } else if(char_isOperator) { // Check for an operator
- tokens.push(['operator', char]);
- } else if(char === '(') { // Check for parentheses
- tokens.push(['bracket', '(']);
- } else if(char === ')') { // Check for closing parentheses
- tokens.push(['bracket', ')']);
- } else if(char === '=') { // Check for assignment
- tokens.push(['assignment', char]);
- } else if(!Helpers.isWhitespace(char)) { // Check for whitespace
- throw new Error('Unexpected char "' + char + '"');
- }
- }
- return tokens;
- };
- return EP;
- })();
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement