# Calculator in Node.JS

I'm new to parsing, and there's only one way to learn anything, and that's to do it yourself. So I did. I wrote what is essentially a calculator. But instead you will send it a string and it will be evaluated.

It's syntax is as follows:

5 + 5
2 * sin(45 * 2)
2 * sin 90
2*sin(90)
2*sin90


As you can see, it does not have a strict syntax. The brackets only mean it's an expression inside an expression, so 2 * sin(2 * 45) becomes 2 * sin 90.

There is one problem with it, it does not respect the order of operations (BIDMAS). I'm not sure how to implement that yet. But by using parentheses you can correctly make calculations.

Here is the code:

var Calc = function() {
'use strict';

this.vars = {
pi: Math.PI
};
};

Calc.prototype.evaluate = function(expression) {
'use strict';

// Trigger final instruction to execute at end.
expression += ' ';

// Loop Variables

var length = expression.length,
iter,
char,
type;

// Flags

var flag_number = false,
flag_float = false,
flag_keyword = false,
flag_operator = false,
flag_operator_waiting = false,
flag_brackets = 0,
flag_var = false,
flag_var_name = '',
flag_math = false,
flag_math_op = '';

// Parser Variables

var buffer = '',
total = 0,
cur_operator = null,
buffer_parsed = 0;

// Math Functions

var stdOps = ['log', 'tan'],
customOps = {
fact: function(value) {
var iter,
multiplier;

for(multiplier = value - 1; multiplier > 0; --multiplier) {
value *= multiplier;
}

return value;
},

sin: function(value) {
return Math.sin(value * Math.PI / 180);
},

cos: function(value) {
return Math.cos(value * Math.PI / 180);
}
};

// Helper Functions

function isAlpha(char) {
return 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.indexOf(char) !== -1;
}

function isNumeric(char) {
return '1234567890.,'.indexOf(char) !== -1;
}

function isSpecial(char) {
return '+-*/^'.indexOf(char) !== -1;
}

function applyOperator(left, right, operator) {
if(operator === '+') {
return left + right;
}

// Subtraction
if(operator === '-') {
return left - right;
}

// Division
if(operator === '/') {
return left / right;
}

// Multiplication
if(operator === '*') {
return left * right;
}

if(operator === '^') {
return Math.pow(left, right);
}

return null;
}

for(iter = 0; iter < length; ++iter) {
char = expression.charAt(iter);

// Are we parsing a number
if(flag_number) {
// The number has finished
if(!isNumeric(char)) {
if(isSpecial(buffer) && !isSpecial(char)) {
flag_number = false;
flag_operator = false;
}

// Is it a float?
if(flag_float) {
buffer_parsed = parseFloat(buffer, 10);
} else {
buffer_parsed = parseInt(buffer, 10);
}

// Is there an operator waiting?
if(cur_operator !== '' && cur_operator !== null) {
total = applyOperator(total, buffer_parsed, cur_operator);

// Reset flags
flag_number = false;
flag_float = false;
flag_operator_waiting = false;

// Unkown operator
if(total === null) {
process.stdout.write('Unrecognised operator ' + cur_operator);
break;
}

// Reset current operator
cur_operator = '';
} else {
// Is a mathematical function waiting? (sin, cos, tan etc)
if(flag_math) {
flag_math = false;
buffer = flag_math_op(buffer_parsed).toString();

// Check if float
if(buffer.indexOf('.') !== -1) {
flag_float = true;
}

iter--;
continue;
}

// Else it's just a number, function call, or variable at the beginning of the expression
total = buffer_parsed;
flag_number = false;
}

buffer = '';
cur_operator = '';
} else {
// Check for float
if(char === '.') {
flag_float = true;
}

// Allow for commas
if(char === ',') {
continue;
}

buffer += char;
continue;
}
}

if(flag_keyword) {
if(!isAlpha(char)) {
flag_keyword = false;

// Case insensitivity
buffer = buffer.toLocaleLowerCase(buffer);

switch(buffer) {
case 'exit':
process.exit();

break;

case 'set':
// setting a variable
flag_var = true;

default:
// Standard Math.[sin|cos|tan|log] operations
if(stdOps.indexOf(buffer) !== -1) {
flag_math = true;
flag_math_op = Math[buffer];
}

// Custom operations: fact (factorial)
if(buffer in customOps) {
flag_math = true;
flag_math_op = customOps[buffer];
}

// If not mathematical function, it must be a variable
if(!flag_math) {
// Are we setting a variable?
if(flag_var) {
flag_var_name = buffer;
} else {
// We are referencing a variable, not setting it
if(buffer in this.vars) {
buffer = this.vars[buffer].toString();
flag_number = true;
// Check if variable is float
if(buffer.indexOf('.') !== -1) {
flag_float = true;
}
--iter;
continue;
}
}
}
}

buffer = '';
} else {
buffer += char;
continue;
}
}

// Are we parsing an operator?
if(flag_operator) {
// Are we ending an operator?
if(!isSpecial(char) || char === '-' || char === '+') {
// reset flags
flag_operator = false;
cur_operator = buffer;
// reset buffer
buffer = '';
flag_operator_waiting = true;

if(char === '-' || char === '+') {
flag_number = true;
buffer += char;
continue;
}
} else {
buffer += char;
continue;
}
}

// A number has started
if(isNumeric(char)) {
flag_number = true;
buffer += char;
continue;
}

// A keyword, variable or function has started
if(isAlpha(char)) {
flag_keyword = true;
buffer += char;
continue;
}

// An operator has started
if(isSpecial(char)) {
if((char === '-' || char === '+') && (flag_operator_waiting || flag_math)) {
buffer += char;
flag_number = true;
continue;
} else {
flag_operator = true;
buffer += char;
continue;
}
}

// Parse parentheses
if(char === '(') {
var bIndex = expression.indexOf(')', iter + 1);

if(bIndex !== -1) {
// extract and independtly evaluate this sub-expression, in a recursive fashion
buffer = this.evaluate(expression.substring(iter + 1, bIndex)).toString();
flag_number = true;
flag_float = buffer.indexOf('.') !== -1;
iter = bIndex;

continue;
} else {
console.log("Invalid bracket syntax");
break;
}
}
}

// We are setting a variable
if(flag_var) {
this.vars[flag_var_name] = total;
}

};

var calc = new Calc();

process.stdin.resume();
process.stdin.setEncoding('utf8');
process.stdin.on('data', function(expression) {
console.log(calc.evaluate(expression))
process.stdout.write("> ");
});
console.log("Welcome to calc.js:");
process.stdout.write("> ");


I have updated the code, see the older version in the edit history.

• 42(42) doesn't work. 5(^2) returns 0. Negative powers don't work. Jan 31, 2016 at 0:12
• It never used to parse negative numbers, can't believe I forgot about negative numbers haha. Anyway, I've implemented them now. Jan 31, 2016 at 11:01