Newer
Older
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>RPN calculator</title>
<style type="text/css">
#container {
display: flex;
flex-flow: row;
width: 700px;
margin: 10px auto;
/* border: 1px grey dotted; */
#stack-content {
margin-left: 10px;
font-size: 1.2em;
height: 400px;
overflow: auto;
}
#expand-stack {
display: none;
}
height: 100%;
border-left: 1px skyblue dotted;
border-right: 1px skyblue dotted;
}
#trail-content {
margin-left: 10px;
font-family: monospace;
height: 400px;
overflow: auto;
.keyboard {
font-size: 1.2em;
text-align: center;
}
.keyboard table {
margin: 2px auto;
}
.keyboard td {
width: 60px;
height: 60px;
max-width: 150px; /*just set to something small and it works in chrome.*/
overflow: auto;
}
#error-msg {
height: 30px;
min-width: 10px;
text-align: left;
}
.trail-active td.trail-op {
border-left: 2px lightblue solid;
}
.overlay {
display: none;
}
#help-overlay-container {
width: 700px;
}
#help-overlay-content {
}
#help-overlay-content h4 {
}
.button-group {
width: 100%;
display: flex;
justify-content: center;
}
/* title with buttons on the right. see ./test-rhs-link-on-title-row.html */
.title {
display: flex;
/* border: 1px black solid; */
}
.title h3 {
margin: 8px 16px;
font-size: 1em;
}
.title-button-group {
flex-grow: 1;
display: flex;
justify-content: flex-end;
margin: 8px 1em;
}
.title-button-group a, .title-button-group button {
padding: 0 5px;
}
#help-title {
text-align: center;
}
#copyright {
text-align: center;
}
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
@media only screen and (max-device-width: 480px) {
#container {
width: 300px;
flex-flow: column;
}
#stack {
height: 100%;
width: 300px;
order: 200;
}
#stack-content {
height: 100%;
max-height: 100px;
}
#expand-stack {
display: inline;
}
#keyboard {
height: 100%;
order: 100;
}
#trail {
height: 100%;
width: 300px;
order: 300;
}
#trail-content {
height: 100%;
max-height: 100px;
}
#expand-trail {
display: inline;
}
#error-msg {
height: 20px;
}
.keyboard td {
width: 60px;
height: 50px;
}
}
#help-overlay-content {
</style>
</head>
<body>
<div id="container">
<div id="stack">
<div class="title">
<h3>Stack</h3>
<div class="title-button-group">
<a title="clear stack" id="clear-stack" href="#">Clear</a>
<a title="expand stack" id="expand-stack" href="#">Expand</a>
<div id="stack-content"></div>
</div>
<div id="keyboard">
<div class="title">
<h3>Keyboard</h3>
<div class="title-button-group">
<a title="reset RPN calculator" id="reset" href="#">Reset All</a>
<a title="help (?)" id="help-link" href="#">Help</a>
</div>
</div>
<div class="keyboard">
<table>
<tr>
<td colspan="4" id="error-msg"></td>
</tr>
<tr>
<td colspan="4" id="number-display"></td>
<td data-bt-name="num7">7</td>
<td data-bt-name="num8">8</td>
<td data-bt-name="num9">9</td>
<td title="delete pending digit or last number in stack (q)" data-bt-name="backspace">←</td>
<td data-bt-name="num4">4</td>
<td data-bt-name="num5">5</td>
<td data-bt-name="num6">6</td>
<td title="times (*)" data-bt-name="times">×</td>
<td data-bt-name="num1">1</td>
<td data-bt-name="num2">2</td>
<td data-bt-name="num3">3</td>
<td title="push pending number to stack or duplicate top number on stack (Enter)"
data-bt-name="return">⏎</td>
<td title="change sign (n)" data-bt-name="change-sign">±</td>
<td title="dot" data-bt-name="dot">.</td>
<td title="plus" data-bt-name="plus">+</td>
<td title="swap top two numbers on stack" data-bt-name="swap">SWAP</td>
<td title="undo last operator (U)" data-bt-name="undo">UNDO</td>
<td title="divide (/)" data-bt-name="divide">÷</td>
<td title="minus" data-bt-name="minus">-</td>
<tr>
<td title="sum all numbers in stack" data-bt-name="sum-all">Σ</td>
<td title="square root (Q)" data-bt-name="square-root">√</td>
<td title="power (^)" data-bt-name="power">^</td>
</table>
</div>
</div>
<div id="trail">
<div class="title">
<h3>Trail</h3>
<div class="title-button-group">
<a title="clear trail" id="clear-trail" href="#">Clear</a>
<a title="expand trail" id="expand-trail" href="#">Expand</a>
<div id="trail-content"></div>
</div>
</div>
<div id="help-overlay-container" class="overlay">
<div id="help-overlay-content">
<h3 id="help-title" style="text-align: center">RPN calculator v1.1.0</h3>
<p>RPN calculator is a web implementation of a <a href="https://en.wikipedia.org/wiki/Reverse_Polish_notation">reverse polish notation calculator</a>. You push numbers to stack first, then press operator keys which will pop number from stack and run the calculation. For example, you may press num1, return, num2, num3, return, plus, and you will see result 24 in top of stack. Number is pushed to stack using return key.</p>
<p>Supported operators are: +, -, *, /, square-root, power, sum-all. Utility features include delete number, duplicate number, swap top 2 number on stack, undo, clear stack. Hover over button to see what that button does and the hotkey for that button.</p>
<p>This program should work on desktop and mobile browser. Tested in chrome and firefox.</p>
<h4>Credit</h4>
<p>This project is inspired by <a href="https://www.gnu.org/software/emacs/manual/html_mono/calc.html">emacs calc mode</a>. It tries to mimic the behavior when possible.</p>
<h4>Debug Features</h4>
<p>This website use service worker to make it work offline. If the local cache does not work for some reason, you may <a title="refresh page" id="refresh-page" href="#">force refresh page</a> to get latest version. You may also <a title="purge service worker cache" id="purge-service-worker-cache" href="#">purge all service worker cache</a>.</p>
<p id="copyright">Copyright (C) 2017 Yuanle Song <sylecn@gmail.com></p>
</div>
</div>
</div>
<script type="text/javascript" src="vendor/localforage.min.js"></script>
<script type="text/javascript" src="vendor/jquery.min.js"></script>
<script type="text/javascript" src="fifo.js"></script>
<script type="text/javascript" src="fsm.js"></script> <!-- fifo.js is required by fsm.js -->
const testing = false;
const uiTesting = false;
if ('serviceWorker' in navigator && enableServiceWorker && window.location.protocol !== "file:") {
navigator.serviceWorker
.register('sw.js', {scope: './'})
.then(function() {
console.log("Service Worker Registered");
});
}
/**
* show dumb data for UI testing.
*/
const showDumbData = function (rpnCalculator) {
rpnCalculator.sendKey("num1");
rpnCalculator.sendKey("return");
rpnCalculator.sendKey("num2");
rpnCalculator.sendKey("return");
rpnCalculator.sendKey("num3");
rpnCalculator.sendKey("return");
rpnCalculator.sendKey("return");
rpnCalculator.sendKey("plus");
}
rpnCalculator.sendKey("return");
}
rpnCalculator.sendKey("num7");
rpnCalculator.sendKey("num8");
rpnCalculator.sendKey("num9");
// for (i = 0; i < 10; ++i) {
// rpnCalculator.sendKey("num" + i);
// rpnCalculator.sendKey("num" + i);
// }
// $("#stack-content").append($('<pre>').text('3: 1'));
// $("#stack-content").append($('<pre>').text('2: 2'));
// $("#stack-content").append($('<pre>').text('1: 3'));
// $("#stack-content").append($('<pre>').text(' .'));
// $("#trail-content").append($('<pre>').text(' 1'));
// $("#trail-content").append($('<pre>').text(' 2'));
// $("#trail-content").append($('<pre>').text('> 3'));
const rpnTestBasic = function () {
const m = new fsm.RPNCalculator();
console.assert(m.numberStack.length === 0);
m.sendKey("num1");
m.sendKey("return");
m.sendKey("num2");
m.sendKey("return");
console.assert(m.numberStack.length === 2);
m.sendKey("plus");
console.assert(m.numberStack.length === 1);
console.assert(m.numberStack[0] === 3);
const trail = m.trail.data; // the underlying array.
console.assert(trail.length === 3);
console.assert(trail[0][0] === null);
console.assert(trail[0][1] === 1);
console.assert(trail[1][0] === null);
console.assert(trail[1][1] === 2);
console.assert(trail[2][0] === '+');
console.assert(trail[2][1] === 3);
};
const rpnTestNumberHandling = function () {
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
console.assert(m.numberStack.length === 0);
m.sendKey("num1");
m.sendKey("num2");
m.sendKey("return");
console.assert(m.numberStack.length === 1);
console.assert(m.numberStack[0] === 12);
console.assert(m.currentNumber === "");
m = new fsm.RPNCalculator();
console.assert(m.numberStack.length === 0);
m.sendKey("num0");
m.sendKey("dot");
m.sendKey("num1");
m.sendKey("num2");
m.sendKey("return");
console.assert(m.numberStack.length === 1);
console.assert(m.numberStack[0] === 0.12);
console.assert(m.currentNumber === "");
m = new fsm.RPNCalculator();
console.assert(m.numberStack.length === 0);
m.sendKey("num0");
m.sendKey("dot");
m.sendKey("num1");
m.sendKey("num2");
m.sendKey("backspace");
console.assert(m.currentNumber === "0.1");
m.sendKey("return");
console.assert(m.numberStack.length === 1);
console.assert(m.numberStack[0] === 0.1);
console.assert(m.currentNumber === "");
};
const rpnTestAutoCommitNumber = function () {
const m = new fsm.RPNCalculator();
console.assert(m.numberStack.length === 0);
m.sendKey("num1");
m.sendKey("return");
m.sendKey("num2");
m.sendKey("plus");
console.assert(m.numberStack.length === 1);
console.assert(m.numberStack[0] === 3);
console.assert(m.currentNumber === "");
console.assert(m.currentState === "idle");
};
const rpnTestOperator = function () {
const m = new fsm.RPNCalculator();
console.assert(m.numberStack.length === 0);
m.sendKey("num1");
m.sendKey("return");
m.sendKey("num2");
m.sendKey("return");
console.assert(m.numberStack.length === 2);
m.sendKey("minus");
console.assert(m.numberStack.length === 1);
console.assert(m.numberStack[0] === -1);
m.sendKey("num3");
m.sendKey("num0");
m.sendKey("return");
console.assert(m.numberStack.length === 2);
m.sendKey("plus");
console.assert(m.numberStack.length === 1);
console.assert(m.numberStack[0] === 29);
m.sendKey("return"); // duplicate
m.sendKey("plus");
console.assert(m.numberStack.length === 1);
console.assert(m.numberStack[0] === 29 * 2);
m.sendKey("num9");
m.sendKey("change-sign");
m.sendKey("swap");
console.assert(m.numberStack.length === 2);
console.assert(m.numberStack[0] === -9);
console.assert(m.numberStack[1] === 29 * 2);
m.sendKey("backspace");
m.sendKey("backspace"); // no number on stack now.
// test round, sqrt, power
m.sendKey("num1");
m.sendKey("dot");
m.sendKey("num2");
m.sendKey("return");
m.sendKey("round");
console.assert(m.numberStack.length === 1);
console.assert(m.numberStack[0] === 1);
m.sendKey("num9");
m.sendKey("times");
m.sendKey("square-root");
console.assert(m.numberStack.length === 1);
console.assert(m.numberStack[0] === 3);
m.sendKey("num2");
m.sendKey("power");
console.assert(m.numberStack.length === 1);
console.assert(m.numberStack[0] === 9);
const rpnTestBackspaceOperator = function () {
const m = new fsm.RPNCalculator();
m.sendKey("num1");
m.sendKey("num2");
console.assert(m.currentNumber === "12");
m.sendKey("backspace"); // remove last digit in current number.
console.assert(m.currentNumber === "1");
m.sendKey("num3");
m.sendKey("return");
console.assert(m.currentNumber === "");
console.assert(m.numberStack.length === 1);
console.assert(m.numberStack[0] === 13);
m.sendKey("backspace"); // delete last number in stack.
console.assert(m.numberStack.length === 0);
};
const rpnTestBackspaceOperatorDeleteAllDigits = function () {
const m = new fsm.RPNCalculator();
m.sendKey("num1");
console.assert(m.currentNumber === "1");
m.sendKey("backspace"); // remove last digit in current number.
console.assert(m.currentNumber === "0");
m.sendKey("num3");
m.sendKey("return");
console.assert(m.currentNumber === "");
console.assert(m.numberStack.length === 1);
console.assert(m.numberStack[0] === 3);
m.sendKey("backspace"); // delete last number in stack.
console.assert(m.numberStack.length === 0);
};
const rpnTestSumAllOperator = function () {
const m = new fsm.RPNCalculator();
m.sendKey("sum-all");
console.assert(m.lastError !== null);
m.sendKey("num1");
m.sendKey("sum-all");
console.assert(m.numberStack.length === 1);
console.assert(m.numberStack[0] === 1);
m.sendKey("num1");
m.sendKey("return");
m.sendKey("num2");
m.sendKey("return");
m.sendKey("num3");
m.sendKey("return");
m.sendKey("num1");
m.sendKey("change-sign");
m.sendKey("return");
m.sendKey("sum-all");
console.assert(m.numberStack.length === 1);
console.assert(m.numberStack[0] === 5);
};
const rpnTestNumberErrorHandling = function () {
m.sendKey("num0");
m.sendKey("num0");
console.assert(m.currentNumber === "0");
m = new fsm.RPNCalculator();
m.sendKey("dot");
console.assert(m.currentNumber === "0.");
m.sendKey("backspace");
console.assert(m.currentNumber === "0");
m.sendKey("backspace");
console.assert(m.currentNumber === "0");
m = new fsm.RPNCalculator();
m.sendKey("num1");
m.sendKey("dot");
m.sendKey("num2");
console.assert(m.currentNumber === "1.2");
m.sendKey("dot"); // future dot is ignored and show error.
console.assert(m.currentNumber === "1.2");
m.sendKey("num3");
console.assert(m.currentNumber === "1.23");
m.sendKey("dot");
console.assert(m.currentNumber === "1.23");
m.sendKey("return");
console.assert(m.numberStack[0] === 1.23);
};
const rpnTestNotEnoughElementOnStack = function () {
const m = new fsm.RPNCalculator();
console.assert(m.numberStack.length === 0);
m.sendKey("num1");
m.sendKey("return");
console.assert(m.numberStack.length === 1);
console.assert(m.numberStack[0] === 1);
m.sendKey("plus");
console.assert(m.numberStack.length === 1);
console.assert(m.numberStack[0] === 1);
console.assert(m.lastError !== null);
m.sendKey("backspace");
console.assert(m.numberStack.length === 0);
console.assert(m.lastError === null);
m.sendKey("change-sign");
console.assert(m.lastError !== null);
m.sendKey("num1");
console.assert(m.lastError === null);
m.sendKey("change-sign");
console.assert(m.numberStack.length === 1);
console.assert(m.numberStack[0] === -1);
console.assert(m.lastError === null);
};
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
const rpnTestUndo = function () {
const m = new fsm.RPNCalculator();
console.assert(m.numberStack.length === 0);
m.sendKey("num1");
m.sendKey("return");
console.assert(m.snapshots.data.length === 1);
console.assert(m.snapshots.data[0].numberStack.length === 0);
m.sendKey("num2");
m.sendKey("return");
console.assert(m.snapshots.data.length === 2);
console.assert(m.snapshots.data[0].numberStack.length === 0);
console.assert(m.snapshots.data[1].numberStack.length === 1);
console.assert(m.snapshots.data[1].numberStack[0] === 1);
console.assert(m.numberStack.length === 2);
console.assert(m.numberStack[0] === 1);
console.assert(m.numberStack[1] === 2);
m.sendKey("undo");
console.assert(m.numberStack.length === 1);
console.assert(m.numberStack[0] === 1);
m.sendKey("num3");
m.sendKey("return");
m.sendKey("plus");
m.sendKey("undo");
m.sendKey("times");
console.assert(m.numberStack.length === 1);
console.assert(m.numberStack[0] === 3);
m.sendKey("undo");
m.sendKey("minus");
console.assert(m.numberStack.length === 1);
console.assert(m.numberStack[0] === -2);
};
const rpnTestUndoWithErrors = function () {
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
console.assert(m.numberStack.length === 0);
m.sendKey("num1");
m.sendKey("return");
m.sendKey("plus");
console.assert(m.numberStack.length === 1);
m.sendKey("undo");
console.assert(m.numberStack.length === 0);
m = new fsm.RPNCalculator();
console.assert(m.numberStack.length === 0);
m.sendKey("num8");
m.sendKey("return");
m.sendKey("num1");
m.sendKey("return");
m.sendKey("num0");
m.sendKey("divide");
m.sendKey("undo");
console.assert(m.numberStack.length === 3);
console.assert(m.numberStack[0] === 8);
console.assert(m.numberStack[1] === 1);
console.assert(m.numberStack[2] === 0);
m.sendKey("undo");
console.assert(m.numberStack.length === 2);
console.assert(m.numberStack[0] === 8);
console.assert(m.numberStack[1] === 1);
};
/**
* run all tests.
*/
const runTests = function () {
console.assert(fsm.matchesSubstringAbb("123abb123") === true);
console.assert(fsm.matchesSubstringAbb("123ab123") === false);
console.assert(fsm.matchesSubstringAbb("123abaabb123") === true);
console.assert(fsm.matchesSubstringAbb("123abbb123") === true);
console.assert(fsm.matchesSubstringAbb("123ababab123") === false);
console.assert(fsm.matchesSubstringAbb("") === false);
console.assert(fsm.matchesSubstringAbb("123") === false);
console.assert(fsm.matchesSubstringAbb("123ab") === false);
console.assert(fsm.matchesSubstringAbb("123abb") === true);
console.assert(fsm.matchesSubstringAbb("abb") === true);
rpnTestBasic();
rpnTestNumberHandling();
rpnTestAutoCommitNumber();
rpnTestOperator();
rpnTestNotEnoughElementOnStack();
rpnTestBackspaceOperator();
rpnTestBackspaceOperatorDeleteAllDigits();
rpnTestSumAllOperator();
/**
* update web UI to match the state of th rpnCalculator.
*
* currently it syncs rpnCalculator.currentNumber and rpnCalculator.numberStack.
*/
const updateUI = function (rpnCalculator) {
$('#number-display').text(rpnCalculator.currentNumber);
$('#error-msg').text(rpnCalculator.lastError);
const stack = rpnCalculator.numberStack;
const stackSize = stack.length;
$("#stack-content").children().remove();
for (let i = 0; i < stackSize; ++i) {
$("#stack-content").append($('<pre>').text((stackSize - i) + ': ' + stack[i]));
}
$("#stack-content").append($('<pre>').text(' .')).scrollTop(9000); // TODO how to scroll to bottom?
const trail = rpnCalculator.trail.data;
const trailSize = trail.length;
const trailTable = $('<table>');
for (let i = 0; i < trailSize; ++i) {
trailOp = trail[i][0];
trailNum = trail[i][1];
trNode = $('<tr>');
if (i === trailSize - 1) {
trNode.addClass("trail-active");
}
trNode.append($('<td>').text(trailOp).addClass("trail-op"));
trNode.append($('<td>').text(trailNum).addClass("trail-num"));
trailTable.append(trNode);
}
$("#trail-content").children().remove();
$("#trail-content").append(trailTable).scrollTop(9000); // TODO how to scroll to bottom?
const rpnCalculator = new fsm.RPNCalculator();
if (uiTesting) {
showDumbData(rpnCalculator);
}
// init web UI
updateUI(rpnCalculator);
// setup event handler
$('.keyboard td').click(function (evt) {
const target = evt.target;
const keyName = $(target).attr('data-bt-name');
if (keyName !== undefined) {
console.log("user clicked on button: " + keyName);
rpnCalculator.sendKey(keyName);
// update UI
updateUI(rpnCalculator);
}
$('#clear-trail').click(function (evt) {
rpnCalculator.clearTrail();
updateUI(rpnCalculator);
return false;
});
$('#clear-stack').click(function (evt) {
rpnCalculator.clearStack();
updateUI(rpnCalculator);
return false;
});
$('#reset').click(function (evt) {
rpnCalculator.reset();
updateUI(rpnCalculator);
return false;
});
const showHelpOverlay = function () {
$('#container').hide();
$('#help-overlay-container').slideDown();
};
const hideHelpOverlay = function () {
$('#help-overlay-container').hide();
$('#container').show();
};
$('#help-link').click(function (evt) {
return false;
});
$('.close-help-overlay').click(function (evt) {
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
$('#refresh-page').click(function (evt) {
const urlJoin = function (baseUrl, path) {
const lastSlash = baseUrl.lastIndexOf("/");
return baseUrl.substring(0, lastSlash + 1) + path;
};
console.assert(urlJoin("http://localhost:8000/calc.html", "fifo.js") ===
"http://localhost:8000/fifo.js");
console.assert(urlJoin("http://localhost:8000/", "fifo.js") ===
"http://localhost:8000/fifo.js");
console.assert(urlJoin("http://localhost:8000/abc/t1.html", "fifo.js") ===
"http://localhost:8000/abc/fifo.js");
console.assert(urlJoin("https://www.example.com/calc.html", "fifo.js") ===
"https://www.example.com/fifo.js");
console.assert(urlJoin("https://www.example.com/", "fifo.js") ===
"https://www.example.com/fifo.js");
// set service worker cache to expire, thus fetching latest page on next refresh.
const htmlUrl = window.location.href;
const fifoJsUrl = urlJoin(htmlUrl, 'fifo.js');
const fsmJsUrl = urlJoin(htmlUrl, 'fsm.js');
// clear cache in page load order. the basic dependence should
// be cleared first before trigger a page reload.
localforage.setItem('timestamp:' + fsmJsUrl, new Date(2016,0,1).toJSON()).then(function (_) {
localforage.setItem('timestamp:' + fifoJsUrl, new Date(2016,0,1).toJSON()).then(function (_) {
localforage.setItem('timestamp:' + htmlUrl, new Date(2016,0,1).toJSON()).then(function (_) {
window.location.href = window.location.href;
});
});
});
return false;
});
$('#purge-service-worker-cache').click(function (evt) {
localforage.clear().then(function (_) {
caches.delete('calc').then(function (_) {
const msg = "clear all done";
console.log(msg);
$('#error-msg').text(msg);
$('#expand-stack').click(function (evt) {
const oldMaxHeight = $('#stack-content').css("max-height");
if (oldMaxHeight === "100px") {
$('#stack-content').css("max-height", "300px");
} else {
$('#stack-content').css("max-height", "100px");
});
$('#expand-trail').click(function (evt) {
const oldMaxHeight = $('#trail-content').css("max-height");
if (oldMaxHeight === "300px") {
$('#trail-content').css("max-height", "100px");
} else {
$('#trail-content').css("max-height", "300px");
$(document).keypress(function (evt) {
// map evt.key to RPNCalculator keyName.
const keyMap = {
"1": "num1",
"2": "num2",
"3": "num3",
"4": "num4",
"5": "num5",
"6": "num6",
"7": "num7",
"8": "num8",
"9": "num9",
"0": "num0",
".": "dot",
"Enter": "return",
"+": "plus",
"-": "minus",
"*": "times",
"/": "divide",
"n": "change-sign",
"^": "power",
"Q": "square-root",
"U": "undo",
// below is not from emacs calc
"q": "backspace",
};
console.log("charCode=" + evt.charCode + ", key=" + evt.key);
console.log(typeof(evt.key));
const keyName = keyMap[evt.key];
if (keyName !== undefined) {
rpnCalculator.sendKey(keyName);
updateUI(rpnCalculator);
} else {
if (evt.key === "?") {
const helpVisible = $('#help-overlay-container').css("display") === "block";
if (helpVisible) {
hideHelpOverlay();
} else {
showHelpOverlay();
}
</script>
</body>
</html>