179 lines
5.2 KiB
JavaScript
179 lines
5.2 KiB
JavaScript
const repl = require('./repl.js');
|
|
// My implementation of the algorithm from https://en.wikipedia.org/wiki/Shunting-yard_algorithm.
|
|
function shuntingYardSolve(exp) {
|
|
exp = exp.split('');
|
|
// Remove whitespace.
|
|
exp = exp.filter(e => /\S+/.test(e));
|
|
let output = [];
|
|
let opStack = [];
|
|
let inNumber = false;
|
|
let numStack = '';
|
|
for (let i = 0; i < exp.length; i++) {
|
|
if (/\d|\./.test(exp[i])) {
|
|
if (inNumber)
|
|
numStack += exp[i];
|
|
else {
|
|
inNumber = true;
|
|
numStack = exp[i];
|
|
}
|
|
} else if (/\w/.test(exp[i])) {
|
|
if (inNumber) {
|
|
inNumber = false;
|
|
output.push((numStack));
|
|
numStack = '';
|
|
}
|
|
output.push(exp[i]);
|
|
} else if (/\+|-|\*|\/|=|\^/.test(exp[i])) {
|
|
if (inNumber) {
|
|
inNumber = false;
|
|
output.push((numStack));
|
|
numStack = '';
|
|
}
|
|
if (exp[i] == '-') {
|
|
if (i == 0)
|
|
exp[i] = ':';
|
|
else if (exp[i - 1] == '(' || /\+|-|\*|\/|=|\^/.test(exp[i - 1]))
|
|
exp[i] = ':';
|
|
}
|
|
let op = {
|
|
value: exp[i]
|
|
};
|
|
if (exp[i] == ':' || exp[i] == '^')
|
|
op.precedence = 4;
|
|
else if (exp[i] == '*' || exp[i] == '/')
|
|
op.precedence = 3;
|
|
else if (exp[i] == '+' || exp[i] == '-')
|
|
op.precedence = 2;
|
|
else if (exp[i] == '=')
|
|
op.precedence = 1;
|
|
if (typeof opStack.slice(-1)[0] != 'undefined')
|
|
while (opStack.slice(-1)[0].precedence > op.precedence && opStack.slice(-1)[0].value != '(')
|
|
output.push(opStack.pop().value);
|
|
opStack.push(op);
|
|
} else if (exp[i] == '(') {
|
|
if (inNumber) {
|
|
inNumber = false;
|
|
output.push((numStack));
|
|
numStack = '';
|
|
}
|
|
opStack.push({
|
|
value: exp[i],
|
|
precedence: 5
|
|
});
|
|
} else if (exp[i] == ')') {
|
|
if (inNumber) {
|
|
inNumber = false;
|
|
output.push((numStack));
|
|
numStack = '';
|
|
}
|
|
if (typeof opStack.slice(-1)[0] != 'undefined') {
|
|
while (opStack.slice(-1)[0].value != '(')
|
|
output.push(opStack.pop().value);
|
|
if (opStack.slice(-1)[0].value == '(')
|
|
opStack.pop();
|
|
} else throw new SyntaxError('Mismatched parentheses')
|
|
}
|
|
}
|
|
if (numStack.length > 0)
|
|
output.push((numStack));
|
|
while (opStack.length > 0)
|
|
output.push(opStack.pop().value);
|
|
return output;
|
|
}
|
|
// Reverse Polish notation implemented in JavaScript.
|
|
function rpnSolve(exp, data) {
|
|
let stack = [];
|
|
for (let i = 0; i < exp.length; i++) {
|
|
let key = exp[i];
|
|
if (key.match(/\d|[A-Za-z]/))
|
|
stack.push((key));
|
|
let opItems;
|
|
let result;
|
|
switch (key) {
|
|
case '+': // Add.
|
|
opItems = stack.splice(stack.length - 2, 2);
|
|
opItems = opItems.map(item => {
|
|
if (typeof data[item] != 'undefined')
|
|
return parseFloat(data[item]);
|
|
else return parseFloat(item);
|
|
});
|
|
result = opItems[0] + opItems[1];
|
|
stack.push(result);
|
|
break;
|
|
case '-': // Subtract.
|
|
opItems = stack.splice(stack.length - 2, 2);
|
|
opItems = opItems.map(item => {
|
|
if (typeof data[item] != 'undefined')
|
|
return parseFloat(data[item]);
|
|
else return parseFloat(item);
|
|
});
|
|
result = opItems[0] - opItems[1];
|
|
stack.push(result);
|
|
break;
|
|
case '*': // Multiply
|
|
opItems = stack.splice(stack.length - 2, 2);
|
|
opItems = opItems.map(item => {
|
|
if (typeof data[item] != 'undefined')
|
|
return parseFloat(data[item]);
|
|
else return parseFloat(item);
|
|
});
|
|
result = opItems[0] * opItems[1];
|
|
stack.push(result);
|
|
break;
|
|
case '/': // Divide
|
|
opItems = stack.splice(stack.length - 2, 2);
|
|
opItems = opItems.map(item => {
|
|
if (typeof data[item] != 'undefined')
|
|
return parseFloat(data[item]);
|
|
else return parseFloat(item);
|
|
});
|
|
result = opItems[0] / opItems[1];
|
|
stack.push(result);
|
|
break;
|
|
case '^': // Exponentiation
|
|
opItems = stack.splice(stack.length - 2, 2);
|
|
opItems = opItems.map(item => {
|
|
if (typeof data[item] != 'undefined')
|
|
return parseFloat(data[item]);
|
|
else return parseFloat(item);
|
|
});
|
|
result = opItems[0] ** opItems[1];
|
|
stack.push(result);
|
|
break;
|
|
case ':': // Unary negation
|
|
opItems = stack.splice(stack.length - 1, 1);
|
|
opItems = opItems.map(item => {
|
|
if (typeof data[item] != 'undefined')
|
|
return parseFloat(data[item]);
|
|
else return parseFloat(item);
|
|
});
|
|
result = -opItems[0];
|
|
stack.push(result);
|
|
break;
|
|
case '=': // Assignment
|
|
opItems = stack.splice(stack.length - 2, 2);
|
|
data[opItems[0]] = parseFloat(opItems[1]);
|
|
stack.push(parseFloat(opItems[1]));
|
|
break;
|
|
}
|
|
}
|
|
return {
|
|
result: stack[0],
|
|
data: data
|
|
};
|
|
};
|
|
|
|
function solve(exp) {
|
|
let data = {};
|
|
exp = exp.split(';');
|
|
exp.forEach(e => {
|
|
console.log(shuntingYardSolve(e).join(' '));
|
|
let result = rpnSolve(shuntingYardSolve(e), data);
|
|
data = result.data;
|
|
console.log(result);
|
|
});
|
|
};
|
|
|
|
console.log('Math Solver by ElementG9:');
|
|
repl('> ', solve);
|