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;
}
#container > div {
border: 1px grey dotted;
}
#container > div > p {
margin-left: 10px;
}
#stack {
width: 200px;
}
#stack-content {
margin-left: 10px;
}
#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;
}
#number-display {
border: 1px grey solid;
width: 100%;
text-align: right;
font-size: 1.5em;
}
#error-msg {
height: 30px;
min-width: 10px;
text-align: left;
}
.trail-active td.trail-op {
border-left: 2px lightblue solid;
}
</style>
</head>
<body>
<div id="container">
<div id="stack">
<p>Stack <a title="clear stack" id="clear-stack" href="#">Clear Stack</a></p>
<div id="stack-content"></div>
</div>
<div id="keyboard">
<p>Keyboard <a title="reset RPN calculator" id="reset" href="#">Reset All</a></p>
<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="backspace" 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="minus" data-bt-name="minus">-</td>
<td title="change sign" 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" data-bt-name="swap">SWAP</td>
<td title="undo" data-bt-name="undo">UNDO</td>
<td title="divide" data-bt-name="divide">÷</td>
<td title="confirm or duplicate number" data-bt-name="return">RETURN</td>
</tr>
</table>
</div>
</div>
<div id="trail">
<p>Trail <a title="clear trail" id="clear-trail" href="#">Clear Trail</a></p>
<div id="trail-content"></div>
</div>
</div>
<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 -->
(function () {
/**
* show dumb data for UI testing.
*/
const showDumbData = function (rpnCalculator) {
var i;
rpnCalculator.sendKey("num1");
rpnCalculator.sendKey("return");
rpnCalculator.sendKey("num2");
rpnCalculator.sendKey("return");
rpnCalculator.sendKey("num3");
rpnCalculator.sendKey("return");
for (i = 0; i < 30; ++i) {
rpnCalculator.sendKey("return");
rpnCalculator.sendKey("plus");
}
for (i = 0; i < 30; ++i) {
rpnCalculator.sendKey("return");
}
rpnCalculator.sendKey("num7");
rpnCalculator.sendKey("num8");
rpnCalculator.sendKey("num9");
// $("#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);
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
};
const rpnTestNumberHandling = function () {
var m = new fsm.RPNCalculator();
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);
};
const rpnBackspaceOperator = 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);
};
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
const rpnTestNumberErrorHandling = function () {
var m = new fsm.RPNCalculator();
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 === "");
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);
};
/**
* 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();
rpnBackspaceOperator();
/**
* 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 (i = 0; i < stackSize; ++i) {
$("#stack-content").append($('<pre>').text((stackSize - i) + ': ' + stack[i]));
}
$("#stack-content").append($('<pre>').text(' .')).scrollTop(900); // TODO how to scroll to bottom?
const trail = rpnCalculator.trail.data;
const trailSize = trail.length;
const trailTable = $('<table>');
for (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();
if (trailSize > 0) {
$("#trail-content").append(trailTable).scrollTop(900); // TODO how to scroll to bottom?
}
const rpnCalculator = new fsm.RPNCalculator();
// init web UI
updateUI(rpnCalculator);
// setup event handler
$('.keyboard td').click(function (evt) {
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;
});
</script>
</body>
</html>