const operator = document.querySelectorAll('.operator');
const equalsKey = document.getElementById('equalsKey');
const display = document.getElementById('result');
const numbers = document.querySelectorAll('.number');
const operators = document.querySelectorAll('.operator');
const clear = document.getElementById('clear');
const deci = document.getElementById('deci');
// to view debug info, change class 'hideDebug' display-style in CSS
const debug = document.getElementById('debug');
deci.addEventListener('click', () => alert('decimals not yet added...'));
// a temp array to hold input characters
let cache = [];
// a closure to handle creating, manipulating the parse-tree
function parser() {
// a tree-structure to use for storing & parsing operators, operands.
const myTree = { root: null, left: null, right: null };
// reset tree to original state
const reset = (mt) => {
mt.root = null; mt.left = null; mt.right = null;
};
// add a new operand or operator into the proper location
// uses BODMAS (also called PEDMAS, PEMDAS, BOMDAS, etc)
// basically division-multiplication takes priority over addition-subtraction
const add = (el, type, nt) => {
if (type === 'operand') {
if (nt.left === null) nt.left = +el;
else if (nt.right === null) nt.right = +el;
else add(el, type, nt.right);
} else {
if (nt.root === null) nt.root = el;
else if ('+-'.includes(nt.root) && '/*'.includes(el)) {
// if current operator is / or * and previous is + or -
// structure tree so that the / or * takes precedence
const t = structuredClone(nt.right);
nt.right = { root: el, left: t, right: null };
} else {
// for all other cases left side calculation takes precedence
const t = structuredClone(nt);
nt.left = t;
nt.root = el;
nt.right = null;
};
};
};
// helper method to quickly compute simple operations
const compute = (op1, op, op2) => {
switch (op) {
case '+': return op1 + op2;
case '-': return op1 - op2;
case '*': return op1 * op2;
case '/': return op1 / op2;
}
};
// recursive method to evaluate the parse-tree
const evaluate = (et) => (
compute(
Number.isFinite(et.left) ? et.left : evaluate(et.left),
et.root,
Number.isFinite(et.right) ? et.right : evaluate(et.right)
)
);
// return object with relevant methods
return {
isEmpty: () => myTree.root === null,
addToTree: (el, type = 'operand') => {
add(el, type, myTree);
},
showTree: () => (JSON.stringify(myTree)),
clearTree: () => {reset(myTree)},
calculate: () => evaluate(myTree)
};
};
const myParser = parser();
// handle clear button click
clear.addEventListener('click', function () {
display.textContent = '0';
myParser.clearTree();
debug.innerHTML = myParser.showTree();
cache = [];
});
// for each number clicked,
// cache the value, and update display
numbers.forEach(el => {
el.addEventListener('click', (ev) => {
if (cache.length === 0 && myParser.isEmpty()) display.innerHTML = ev.target.value;
else display.innerHTML += ev.target.value;
cache.push(ev.target.value);
});
});
// for each operator clicked,
// process existing cache and operator into parse-tree
operator.forEach(el => {
el.addEventListener('click', (ev) => {
if (cache.length > 0 || ev.target.value === '-') {
if (cache.length === 0) { // negative number as operand-1
display.innerHTML = '0-';
cache.push(0);
} else if (!('+-*/'.includes(cache.slice(-1)))) display.innerHTML += ev.target.value;
myParser.addToTree(cache.join(''));
cache = [];
myParser.addToTree(ev.target.value, 'operator');
debug.innerHTML = myParser.showTree();
}
});
});
// calculate method
// add last operand to parse-tree, clear cache
// obtain the calculated-result, display & cache it
// finally, clear the parse-tree
const calculate = () => {
myParser.addToTree(cache.join(''));
cache = [];
debug.innerHTML = myParser.showTree();
const calcResult = myParser.calculate();
display.innerHTML = calcResult;
cache.push(calcResult);
myParser.clearTree();
};
equalsKey.addEventListener('click', calculate);
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html {
font-size: 10px;
}
body {
font-family: "Open Sans", sans-serif;
}
p {
font-size: 16px;
line-height: 1.5;
}
img {
width: 100%;
}
.container {
max-width: 900px;
margin: 0 auto;
padding: 0 20px;
}
.box {
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
.calc {
width: 300px;
height: 350px;
margin: 0 auto;
background-color: #ccc;
padding: 15px;
border-radius: 3px;
position: relative;
}
.result {
color: rgb(68, 66, 66);
background-color: #fff;
height: 45px;
width: 70%;
border-radius: 3px;
margin-bottom: 5px;
border: 2px solid grey;
padding: 10px;
overflow: hidden;
}
.clear {
width: 20%;
height: 45px;
padding: 10px;
position: absolute;
right: 20px;
top: 5px;
border-radius: 3px;
cursor: pointer;
font-size: 16px;
}
.keys {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-gap: 5px;
font-size: 16px;
}
.keys button {
list-style: none;
width: 50px;
background-color: #fff;
margin: 10px 5px;
padding: 10px;
/* border-bottom: 1px solid #dadedc; */
border-radius: 3px;
color: rgb(68, 66, 66);
text-align: center;
cursor: pointer;
transition: all 0.1s;
}
.keys button:hover {
background-color: #dadedc;
color: #000;
}
.keys .equals {
background-color: blue;
color: #fff;
}
.hideDebug { display: none; }
<div class='hideDebug' id='debug'>aa</div>
<section>
<div class="container box">
<div class="calc">
<div class="result">
<p id="result">0</p>
</div>
<button class="clear" id="clear">C</button>
<ul class="keys">
<button class="number" value="1">1</button>
<button class="number" value="2">2</button>
<button class="number" value="3">3</button>
<button class="operator" value="+">+</button>
<button class="number" value="4">4</button>
<button class="number" value="5">5</button>
<button class="number" value="6">6</button>
<button class="operator" value="-">-</button>
<button class="number" value="7">7</button>
<button class="number" value="8">8</button>
<button class="number" value="9">9</button>
<button class="operator" value="*">*</button>
<button class="number" value="0">0</button>
<button id='deci' class="decimal" value=".">.</button>
<button class="equals" id="equalsKey" value="=">=</button>
<button class="operator" value="/">÷</button>
</ul>
</div>
</div>
</section>