Skip to content

Instantly share code, notes, and snippets.

@taras-d
Created March 25, 2025 10:20
Show Gist options
  • Save taras-d/ae7e47c538023a78973782110569f8e8 to your computer and use it in GitHub Desktop.
Save taras-d/ae7e47c538023a78973782110569f8e8 to your computer and use it in GitHub Desktop.
/*
Arithmetic expression calculator
*/
// Constats
const opPriority = [
["*", "/"],
["+", "-"],
];
const maxLoopCount = 100;
const exp = "(123+423)/234+(3242.543-234)*(234.423+234+(24*(234+5345)/56))";
console.log("Calc result", caclExp(exp));
console.log("Eval result", eval(exp));
// Calculate expression
// "2+5*(3*(5-4))+1" -> "18"
function caclExp(exp) {
const loopGuard = infiniteLoopGuard();
let curExp = exp;
while (true) {
loopGuard();
// Find sub expression
const match = curExp.match(/\([^()]+\)/);
if (!match) {
break;
}
// Calc sub expression
const subExp = match[0].slice(1, -1);
const subExpRes = calcPlainExp(subExp);
// Replace sub expression with result
const before = curExp.slice(0, match.index);
const after = curExp.slice(match.index + match[0].length);
curExp = `${before}${subExpRes}${after}`;
}
return calcPlainExp(curExp);
}
// Calculate plain expression (without parentheses)
// "2+5*3-2" -> "15"
function calcPlainExp(exp) {
if (/[()]/.test(exp)) {
throw new Error("Expression should not have parentheses");
}
const expItems = splitPlainExp(exp);
const loopGuard = infiniteLoopGuard();
let opPriorityIndex = 0;
let res = exp;
while (true) {
loopGuard();
// Operators
const curOperators = opPriority[opPriorityIndex];
if (!curOperators) {
// All operators checked, exit loop
break;
}
// Operator index
const opIndex = expItems.findIndex(item => curOperators.includes(item));
if (opIndex === -1) {
// Move to next operator
opPriorityIndex++;
continue;
}
const expOperator = expItems[opIndex];
const leftOperand = expItems[opIndex - 1];
const rightOperand = expItems[opIndex + 1];
const result = calcOperands(leftOperand, rightOperand, expOperator);
// Set result into left operand
expItems[opIndex - 1] = result.toString();
// Delete operator and right operand
expItems.splice(opIndex, 2);
}
return expItems[0];
}
// Split plain expression to array
// "100/2+56.7*44" -> ["100", "/", "56.7", "*", "44"]
function splitPlainExp(exp) {
const parts = exp.split(/\s*/);
const result = [];
for (const char of parts) {
const lastResultItem = result[result.length - 1] || "";
if (isNumChar(char)) {
if (lastResultItem && !isNaN(parseFloat(lastResultItem))) {
result[result.length - 1] = `${lastResultItem}${char}`;
} else {
result.push(char);
}
continue;
}
if (isOperator(char)) {
result.push(char);
}
}
return result;
}
function isNumChar(value) {
return /^\d+$/.test(value) || value === ".";
}
function isOperator(value) {
return opPriority.some(item => item.includes(value));
}
// Calculate left and right operands
function calcOperands(left, right, operator) {
const leftVal = parseFloat(left);
const rightVal = parseFloat(right);
switch (operator) {
case "*":
return leftVal * rightVal;
case "/":
return leftVal / rightVal;
case "+":
return leftVal + rightVal;
case "-":
return leftVal - rightVal;
default:
throw new Error(`Unknown operator "${operator}"`);
}
}
function infiniteLoopGuard(max = 100) {
let count = 0;
return function() {
if (count++ >= max) {
throw new Error("Max loop count reached");
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment