Loading calc.html +119 −17 Original line number Diff line number Diff line Loading @@ -75,30 +75,31 @@ <td data-bt-name="num7">7</td> <td data-bt-name="num8">8</td> <td data-bt-name="num9">9</td> <td data-bt-name="backspace"><</td> <td title="backspace" data-bt-name="backspace"><</td> </tr> <tr> <td data-bt-name="num4">4</td> <td data-bt-name="num5">5</td> <td data-bt-name="num6">6</td> <td data-bt-name="multiply">*</td> <td title="times" data-bt-name="times">*</td> </tr> <tr> <td data-bt-name="num1">1</td> <td data-bt-name="num2">2</td> <td data-bt-name="num3">3</td> <td data-bt-name="subtract">-</td> <td title="minus" data-bt-name="minus">-</td> </tr> <tr> <td data-bt-name="num0">0</td> <td data-bt-name="divide">÷</td> <td data-bt-name="dot">.</td> <td data-bt-name="plus">+</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> </tr> <tr> <td title="swap" data-bt-name="swap">SWAP</td> <td title="undo" data-bt-name="undo">UNDO</td> <td title="confirm or duplicate number" colspan="2" data-bt-name="return">RETURN</td> <td title="divide" data-bt-name="divide">÷</td> <td title="confirm or duplicate number" data-bt-name="return">RETURN</td> </tr> </table> </div> Loading Loading @@ -128,10 +129,100 @@ $("#trail-content").append($('<pre>').text('> 3')); }; // page init showDumbData(); 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 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); }; /** * run all tests. */ const runTests = function () { console.assert(fsm.matchesSubstringAbb("123abb123") === true); console.assert(fsm.matchesSubstringAbb("123ab123") === 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); Loading @@ -141,6 +232,17 @@ console.assert(fsm.matchesSubstringAbb("123abb") === true); console.assert(fsm.matchesSubstringAbb("abb") === true); rpnTestBasic(); rpnTestNumberHandling(); rpnTestAutoCommitNumber(); rpnTestOperator(); }; // page init showDumbData(); runTests(); // setup event handler $('.keyboard td').click(function (evt) { const target = evt.target; Loading fsm.js +202 −16 Original line number Diff line number Diff line Loading @@ -12,37 +12,37 @@ var fsm = function () { * matchesSubstringAbb. */ const matchAbbMachine = function () { this.stateWaitForA = "wait for a"; this.stateWaitForFirstB = "wait for 1st b"; this.stateWaitForSecondB = "wait for 2nd b"; this.stateSucceed = "succeed"; const stateWaitForA = "wait for a"; const stateWaitForFirstB = "wait for 1st b"; const stateWaitForSecondB = "wait for 2nd b"; const stateSucceed = "succeed"; this.currentState = "wait for a"; this.readch = function (ch) { switch (this.currentState) { case this.stateWaitForA: case stateWaitForA: if (ch === 'a') { this.currentState = this.stateWaitForFirstB; this.currentState = stateWaitForFirstB; } else { this.currentState = this.stateWaitForA; this.currentState = stateWaitForA; } break; case this.stateWaitForFirstB: case stateWaitForFirstB: if (ch === 'b') { this.currentState = this.stateWaitForSecondB; this.currentState = stateWaitForSecondB; } else if (ch === 'a') { // do nothing, keep current state. } else { this.currentState = this.stateWaitForA; this.currentState = stateWaitForA; } break; case this.stateWaitForSecondB: case stateWaitForSecondB: if (ch === 'b') { this.currentState = this.stateSucceed; this.currentState = stateSucceed; } else if (ch === 'a') { this.currentState = this.stateWaitForFirstB; this.currentState = stateWaitForFirstB; } else { this.currentState = this.stateWaitForA; this.currentState = stateWaitForA; } break; case this.succeed: Loading @@ -59,7 +59,7 @@ var fsm = function () { * implemented using fsm. */ const matchesSubstringAbb = function (str) { var machine = new matchAbbMachine(); const machine = new matchAbbMachine(); for (i = 0; i < str.length; ++i) { machine.readch(str[i]); if (machine.currentState === "succeed") { Loading @@ -70,7 +70,193 @@ var fsm = function () { return false; }; /** * a fsm that implement a RPN calculator. see document in ./operational */ const RPNCalculator = function () { // states const stateIdle = "idle"; const stateWaitingForNumberOrAction = "waiting for number or action"; // keynames const keyNames = { num0: "num0", num1: "num1", num2: "num2", num3: "num3", num4: "num4", num5: "num5", num6: "num6", num7: "num7", num8: "num8", num9: "num9", dot: "dot", backspace: "backspace", times: "times", divide: "divide", plus: "plus", minus: "minus", swap: "swap", undo: "undo", "return": "return", change_sign: "change-sign", }; this.currentState = stateIdle; this.numberStack = []; this.currentNumber = ""; this.lastError = null; /** * save current state of this FSM. You can use load to restore the FSM state. */ this.save = function () { return { "currentState": this.currentState, "numberStack": this.numberStack, "currentNumber": this.currentNumber, }; }; /** * load saved data back to this FSM. current FSM status will be lost. */ this.load = function (data) { this.currentState = data.currentState; this.numberStack = data.numberStack; this.currentNumber = data.currentNumber; }; /** * set error msg. only the last error msg can be fetched via this.lastError. */ this.setErrorMsg = function (msg) { console.log(msg); this.lastError = msg; }; /** * return true if given keyName is one of number keys. * number keys include num0-9 and dot. */ const isNumberKey = function (keyName) { return keyName.startsWith("num") || keyName === keyNames.dot; }; /** * operator key names. */ const operatorKeyList = [ keyNames.change_sign, keyNames.plus, keyNames.minus, keyNames.times, keyNames.divide, keyNames.swap, ]; const isOperator = function (keyName) { return operatorKeyList.indexOf(keyName) !== -1; }; /** * keys that is not a simple operator. */ const miscKeyList = [keyNames["return"], keyNames.undo, keyNames.backspace]; const isMiscKey = function (keyName) { return miscKeyList.index(keyName) !== -1; }; /** * convert number key to a number string. */ const numKeyToNumString = function (keyName) { console.assert(isNumberKey(keyName), "not a number key: " + keyName); if (keyName === keyNames.dot) { return "."; } else { return keyName.substring(3); } }; this.sendKey = function (keyName) { var num, num1, num2; switch (this.currentState) { case stateIdle: if (isNumberKey(keyName)) { // TODO handle errors and special cases this.currentNumber += numKeyToNumString(keyName); this.currentState = stateWaitingForNumberOrAction; } else { switch (keyName) { case keyNames.plus: this.numberStack.push(this.numberStack.pop() + this.numberStack.pop()); break; case keyNames.minus: num2 = this.numberStack.pop(); num1 = this.numberStack.pop(); this.numberStack.push(num1 - num2); break; case keyNames.change_sign: this.numberStack.push(-this.numberStack.pop()); break; case keyNames.times: this.numberStack.push(this.numberStack.pop() * this.numberStack.pop()); break; case keyNames.divide: num2 = this.numberStack.pop(); num1 = this.numberStack.pop(); this.numberStack.push(num1 / num2); break; case keyNames.swap: num2 = this.numberStack.pop(); num1 = this.numberStack.pop(); this.numberStack.push(num2); this.numberStack.push(num1); break; case keyNames['return']: // duplicate number num = this.numberStack.pop(); this.numberStack.push(num); this.numberStack.push(num); break; default: console.assert(false, "action not implemented, key is " + keyName); } } break; case stateWaitingForNumberOrAction: if (isNumberKey(keyName)) { // TODO handle errors and special cases this.currentNumber += numKeyToNumString(keyName); // keep current state } else if (keyName === keyNames['return']) { this.numberStack.push(parseFloat(this.currentNumber)); this.currentNumber = ""; this.currentState = stateIdle; } else if (keyName === keyNames.backspace) { if (this.currentNumber.length > 0) { this.currentNumber = this.currentNumber.substring(0, this.currentNumber.length - 1); } else { setErrorMsg("no digits to delete"); } } else if (isOperator(keyName)) { // commit number console.log("send return key automatically"); this.sendKey(keyNames['return']); console.assert(this.currentState === stateIdle, "auto commit number should set state to idle"); // consume and resend this key. notice the early return. console.log("resend key " + keyName); return this.sendKey(keyName); } else { switch (keyName) { default: console.assert(false, "action not implemented, key is " + keyName); } } break; default: console.assert(false, "unknown state: " + this.currentState); } debug("sendKey " + keyName + ", currentState is " + this.currentState); return this.currentState; }; }; return { matchesSubstringAbb: matchesSubstringAbb matchesSubstringAbb: matchesSubstringAbb, RPNCalculator: RPNCalculator, }; }(); operational +16 −6 Original line number Diff line number Diff line * COMMENT -*- mode: org -*- #+Date: 2017-01-26 Time-stamp: <2017-01-26> Time-stamp: <2017-01-27> #+STARTUP: content * notes :entry: * later :entry: Loading @@ -15,6 +15,10 @@ the parent DOM. * current :entry: ** ** 2017-01-26 when testing the fsm, always sync this.numberStack and this.currentNumber with the web UI. ** 2017-01-26 handle error for every this.numberStack.pop() ** 2017-01-26 design data structure for the calculator. - this can be written and tested independently from the web UI. - basic data: Loading @@ -24,17 +28,19 @@ the parent DOM. - how many states are there? init state is "waiting for number". init state is "idle". | this state | event | next state | |------------------------------+---------------------------+--------------------------------------------------| | waiting | press num keys or dot key | modify number, then waiting for number or action | | idle | press num keys or dot key | modify number, then waiting for number or action | | idle | press +-*/ swap | do op, then idle | | waiting for number or action | press num keys or dot key | modify number, then waiting for number or action | | waiting for number or action | press return key | commit number, then waiting | | waiting for number or action | press +-*/ | commit number, do op, then waiting | | waiting for number or action | press return key | commit number, then idle | | waiting for number or action | press +-*/ | commit number, do op, then idle | | waiting for number or action | press < key | modify number, then waiting for number or action | | idle | press return key | dup number, then idle | There is no "ending state", the machine can always accept new keyboard events. There is no ending state, the machine can always accept new keyboard events. - a few display and error handling. Loading Loading @@ -67,6 +73,10 @@ trail cache. it's not a problem. - make DUP and SWP work - make trail work * done :entry: ** 2017-01-26 what to do with negate operator? emacs use n (change sign) add this button. move divide button to somewhere else. ** 2017-01-26 how to write a fsm simulator in javascript. - try write an easier example before writing the real thing. - easy example: Loading Loading
calc.html +119 −17 Original line number Diff line number Diff line Loading @@ -75,30 +75,31 @@ <td data-bt-name="num7">7</td> <td data-bt-name="num8">8</td> <td data-bt-name="num9">9</td> <td data-bt-name="backspace"><</td> <td title="backspace" data-bt-name="backspace"><</td> </tr> <tr> <td data-bt-name="num4">4</td> <td data-bt-name="num5">5</td> <td data-bt-name="num6">6</td> <td data-bt-name="multiply">*</td> <td title="times" data-bt-name="times">*</td> </tr> <tr> <td data-bt-name="num1">1</td> <td data-bt-name="num2">2</td> <td data-bt-name="num3">3</td> <td data-bt-name="subtract">-</td> <td title="minus" data-bt-name="minus">-</td> </tr> <tr> <td data-bt-name="num0">0</td> <td data-bt-name="divide">÷</td> <td data-bt-name="dot">.</td> <td data-bt-name="plus">+</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> </tr> <tr> <td title="swap" data-bt-name="swap">SWAP</td> <td title="undo" data-bt-name="undo">UNDO</td> <td title="confirm or duplicate number" colspan="2" data-bt-name="return">RETURN</td> <td title="divide" data-bt-name="divide">÷</td> <td title="confirm or duplicate number" data-bt-name="return">RETURN</td> </tr> </table> </div> Loading Loading @@ -128,10 +129,100 @@ $("#trail-content").append($('<pre>').text('> 3')); }; // page init showDumbData(); 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 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); }; /** * run all tests. */ const runTests = function () { console.assert(fsm.matchesSubstringAbb("123abb123") === true); console.assert(fsm.matchesSubstringAbb("123ab123") === 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); Loading @@ -141,6 +232,17 @@ console.assert(fsm.matchesSubstringAbb("123abb") === true); console.assert(fsm.matchesSubstringAbb("abb") === true); rpnTestBasic(); rpnTestNumberHandling(); rpnTestAutoCommitNumber(); rpnTestOperator(); }; // page init showDumbData(); runTests(); // setup event handler $('.keyboard td').click(function (evt) { const target = evt.target; Loading
fsm.js +202 −16 Original line number Diff line number Diff line Loading @@ -12,37 +12,37 @@ var fsm = function () { * matchesSubstringAbb. */ const matchAbbMachine = function () { this.stateWaitForA = "wait for a"; this.stateWaitForFirstB = "wait for 1st b"; this.stateWaitForSecondB = "wait for 2nd b"; this.stateSucceed = "succeed"; const stateWaitForA = "wait for a"; const stateWaitForFirstB = "wait for 1st b"; const stateWaitForSecondB = "wait for 2nd b"; const stateSucceed = "succeed"; this.currentState = "wait for a"; this.readch = function (ch) { switch (this.currentState) { case this.stateWaitForA: case stateWaitForA: if (ch === 'a') { this.currentState = this.stateWaitForFirstB; this.currentState = stateWaitForFirstB; } else { this.currentState = this.stateWaitForA; this.currentState = stateWaitForA; } break; case this.stateWaitForFirstB: case stateWaitForFirstB: if (ch === 'b') { this.currentState = this.stateWaitForSecondB; this.currentState = stateWaitForSecondB; } else if (ch === 'a') { // do nothing, keep current state. } else { this.currentState = this.stateWaitForA; this.currentState = stateWaitForA; } break; case this.stateWaitForSecondB: case stateWaitForSecondB: if (ch === 'b') { this.currentState = this.stateSucceed; this.currentState = stateSucceed; } else if (ch === 'a') { this.currentState = this.stateWaitForFirstB; this.currentState = stateWaitForFirstB; } else { this.currentState = this.stateWaitForA; this.currentState = stateWaitForA; } break; case this.succeed: Loading @@ -59,7 +59,7 @@ var fsm = function () { * implemented using fsm. */ const matchesSubstringAbb = function (str) { var machine = new matchAbbMachine(); const machine = new matchAbbMachine(); for (i = 0; i < str.length; ++i) { machine.readch(str[i]); if (machine.currentState === "succeed") { Loading @@ -70,7 +70,193 @@ var fsm = function () { return false; }; /** * a fsm that implement a RPN calculator. see document in ./operational */ const RPNCalculator = function () { // states const stateIdle = "idle"; const stateWaitingForNumberOrAction = "waiting for number or action"; // keynames const keyNames = { num0: "num0", num1: "num1", num2: "num2", num3: "num3", num4: "num4", num5: "num5", num6: "num6", num7: "num7", num8: "num8", num9: "num9", dot: "dot", backspace: "backspace", times: "times", divide: "divide", plus: "plus", minus: "minus", swap: "swap", undo: "undo", "return": "return", change_sign: "change-sign", }; this.currentState = stateIdle; this.numberStack = []; this.currentNumber = ""; this.lastError = null; /** * save current state of this FSM. You can use load to restore the FSM state. */ this.save = function () { return { "currentState": this.currentState, "numberStack": this.numberStack, "currentNumber": this.currentNumber, }; }; /** * load saved data back to this FSM. current FSM status will be lost. */ this.load = function (data) { this.currentState = data.currentState; this.numberStack = data.numberStack; this.currentNumber = data.currentNumber; }; /** * set error msg. only the last error msg can be fetched via this.lastError. */ this.setErrorMsg = function (msg) { console.log(msg); this.lastError = msg; }; /** * return true if given keyName is one of number keys. * number keys include num0-9 and dot. */ const isNumberKey = function (keyName) { return keyName.startsWith("num") || keyName === keyNames.dot; }; /** * operator key names. */ const operatorKeyList = [ keyNames.change_sign, keyNames.plus, keyNames.minus, keyNames.times, keyNames.divide, keyNames.swap, ]; const isOperator = function (keyName) { return operatorKeyList.indexOf(keyName) !== -1; }; /** * keys that is not a simple operator. */ const miscKeyList = [keyNames["return"], keyNames.undo, keyNames.backspace]; const isMiscKey = function (keyName) { return miscKeyList.index(keyName) !== -1; }; /** * convert number key to a number string. */ const numKeyToNumString = function (keyName) { console.assert(isNumberKey(keyName), "not a number key: " + keyName); if (keyName === keyNames.dot) { return "."; } else { return keyName.substring(3); } }; this.sendKey = function (keyName) { var num, num1, num2; switch (this.currentState) { case stateIdle: if (isNumberKey(keyName)) { // TODO handle errors and special cases this.currentNumber += numKeyToNumString(keyName); this.currentState = stateWaitingForNumberOrAction; } else { switch (keyName) { case keyNames.plus: this.numberStack.push(this.numberStack.pop() + this.numberStack.pop()); break; case keyNames.minus: num2 = this.numberStack.pop(); num1 = this.numberStack.pop(); this.numberStack.push(num1 - num2); break; case keyNames.change_sign: this.numberStack.push(-this.numberStack.pop()); break; case keyNames.times: this.numberStack.push(this.numberStack.pop() * this.numberStack.pop()); break; case keyNames.divide: num2 = this.numberStack.pop(); num1 = this.numberStack.pop(); this.numberStack.push(num1 / num2); break; case keyNames.swap: num2 = this.numberStack.pop(); num1 = this.numberStack.pop(); this.numberStack.push(num2); this.numberStack.push(num1); break; case keyNames['return']: // duplicate number num = this.numberStack.pop(); this.numberStack.push(num); this.numberStack.push(num); break; default: console.assert(false, "action not implemented, key is " + keyName); } } break; case stateWaitingForNumberOrAction: if (isNumberKey(keyName)) { // TODO handle errors and special cases this.currentNumber += numKeyToNumString(keyName); // keep current state } else if (keyName === keyNames['return']) { this.numberStack.push(parseFloat(this.currentNumber)); this.currentNumber = ""; this.currentState = stateIdle; } else if (keyName === keyNames.backspace) { if (this.currentNumber.length > 0) { this.currentNumber = this.currentNumber.substring(0, this.currentNumber.length - 1); } else { setErrorMsg("no digits to delete"); } } else if (isOperator(keyName)) { // commit number console.log("send return key automatically"); this.sendKey(keyNames['return']); console.assert(this.currentState === stateIdle, "auto commit number should set state to idle"); // consume and resend this key. notice the early return. console.log("resend key " + keyName); return this.sendKey(keyName); } else { switch (keyName) { default: console.assert(false, "action not implemented, key is " + keyName); } } break; default: console.assert(false, "unknown state: " + this.currentState); } debug("sendKey " + keyName + ", currentState is " + this.currentState); return this.currentState; }; }; return { matchesSubstringAbb: matchesSubstringAbb matchesSubstringAbb: matchesSubstringAbb, RPNCalculator: RPNCalculator, }; }();
operational +16 −6 Original line number Diff line number Diff line * COMMENT -*- mode: org -*- #+Date: 2017-01-26 Time-stamp: <2017-01-26> Time-stamp: <2017-01-27> #+STARTUP: content * notes :entry: * later :entry: Loading @@ -15,6 +15,10 @@ the parent DOM. * current :entry: ** ** 2017-01-26 when testing the fsm, always sync this.numberStack and this.currentNumber with the web UI. ** 2017-01-26 handle error for every this.numberStack.pop() ** 2017-01-26 design data structure for the calculator. - this can be written and tested independently from the web UI. - basic data: Loading @@ -24,17 +28,19 @@ the parent DOM. - how many states are there? init state is "waiting for number". init state is "idle". | this state | event | next state | |------------------------------+---------------------------+--------------------------------------------------| | waiting | press num keys or dot key | modify number, then waiting for number or action | | idle | press num keys or dot key | modify number, then waiting for number or action | | idle | press +-*/ swap | do op, then idle | | waiting for number or action | press num keys or dot key | modify number, then waiting for number or action | | waiting for number or action | press return key | commit number, then waiting | | waiting for number or action | press +-*/ | commit number, do op, then waiting | | waiting for number or action | press return key | commit number, then idle | | waiting for number or action | press +-*/ | commit number, do op, then idle | | waiting for number or action | press < key | modify number, then waiting for number or action | | idle | press return key | dup number, then idle | There is no "ending state", the machine can always accept new keyboard events. There is no ending state, the machine can always accept new keyboard events. - a few display and error handling. Loading Loading @@ -67,6 +73,10 @@ trail cache. it's not a problem. - make DUP and SWP work - make trail work * done :entry: ** 2017-01-26 what to do with negate operator? emacs use n (change sign) add this button. move divide button to somewhere else. ** 2017-01-26 how to write a fsm simulator in javascript. - try write an easier example before writing the real thing. - easy example: Loading