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) {
// Addition
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;
}
return 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)
returns0
. Negative powers don't work. \$\endgroup\$