// Driving functions for Sinclair Cambridge Programmable model // nib - 2006-03-26 .. 2006-04-07 // Copyright Nigel Bromley 2006. Free use for any non-commercial purpose // with attribution. // Pretty much everything is driven by global state and event-driven // keypress routines, which makes it a bit complicated-looking but // conceptually simple. // This is an afternoon's hack, not a properly-structured effort. Although // surprisingly I found one odd consequence of the flag logic I used // actually reflects what the real thing does! // The 36-step program, initialised to all "stop" and the program counter var program = new Array(); for (i=0; i35) pc = 0; } // Produce display contents from current state and display mode and // load it into the display window in the form on the document function display() { if (dispstate == showx) { if (f_flag) document.calculator.display.value = 'F '; else if (g_flag) document.calculator.display.value = 'G '; else document.calculator.display.value = ' '; document.calculator.display.value += x; } else { if (f_flag) document.calculator.display.value = 'F '; else if (g_flag) document.calculator.display.value = 'G '; else document.calculator.display.value = ' '; document.calculator.display.value += program[pc]+" "+pc; } } // Complete the pending operation. Called by close bracket, equals or another // operator. Does either the complete operation between x and y, or if no // second argument was entered (autok), then on the one operand in x only function do_pend() { if (autok) switch (pend) { case add: x = 2 * x; break; case sub: x = - x; break; case mul: x = x * x; break; case div: x = 1 / x; break; default: break; } else switch (pend) { case add: x = y + x; break; case sub: x = y - x; break; case mul: x = y * x; break; case div: x = y / x; break; default: break; } autok = 0; } // When running, make a goto address out of the two steps following the // goto step function pick_gto() { var j = parseInt(program[pc]); inc_pc(); j = 10*j + parseInt(program[pc]); inc_pc(); if (j>35) j=0; return j; } // Main function to run a program. Is stopped by running out of steps (safety // against loops) or executing a stop instruction, which sets calcstate // other than running function run_program() { var max=maxsteps; while (calcstate == running && max>0) { var next = program[pc]; if (debug) alert('step='+pc+' action='+next+' x='+x+' y=' +y+' m='+m); inc_pc(); max--; switch (next) { case '0': if (hash) { f_flag=0; g_flag=0; } else if (!g_flag) { f_flag=1; } do_0() break; case '1': if (hash) { f_flag=0; g_flag=0; } else if (!g_flag) { f_flag=1; } do_1() break; case '2': if (hash) { f_flag=0; g_flag=0; } else if (!g_flag) { f_flag=1; } do_2() break; case '3': if (hash) { f_flag=0; g_flag=0; } else if (!g_flag) { f_flag=1; } do_3() break; case '4': if (hash) { f_flag=0; g_flag=0; } else if (!g_flag) { f_flag=1; } do_4() break; case '5': if (hash) { f_flag=0; g_flag=0; } else if (!g_flag) { f_flag=1; } do_5() break; case '6': if (hash) { f_flag=0; g_flag=0; } else if (!g_flag) { f_flag=1; } do_6() break; case '7': if (hash) { f_flag=0; g_flag=0; } else if (!g_flag) { f_flag=1; } do_7() break; case '8': if (hash) { f_flag=0; g_flag=0; } else if (!g_flag) { f_flag=1; } do_8() break; case '9': if (hash) { f_flag=0; g_flag=0; } else if (!g_flag) { f_flag=1; } do_9() break; case 'A': if (hash) { f_flag=0; g_flag=0; do_EE(); } else { f_flag=0; g_flag=1; } break; case 'G': do_DIV(); break; case '.': do_MUL(); break; case '-': do_EQU(); break; case 'E': do_ADD(); break; case 'F': do_MIN(); break; default: } display(); } calcstate = calc; } // Action one of the number keys, taking account of current number input // state. Builds either a floating point number in x or a goto address // in pc function number_key(n) { if (stale) { x = 0; stale = 0; } if (numstate == num) { x = x*10 + n; } else if (numstate == deci) { x = x + n * pos; pos /= 10; } else if (numstate == posex) { expon = expon*10 + n; x = mant * Math.pow(10, expon); } else if (numstate == negex) { expon = expon*10 + n; x = mant * Math.pow(10, -expon); } else if (numstate == gto1) { gto = n; numstate = gto2; } else if (numstate == gto2) { gto = gto*10 + n; numstate = num; pc = gto; if (pc > 35) pc = 0; dispstate=showstep; } f_flag = 0; g_flag = 0; autok = 0; display(); } // Put one instruction into the next program step location function add_step(w) { program[pc] = w; inc_pc(); } // All of the 20 functions from here are driven by pressing one of the 19 // keys or the power switch // Base - ./EE/- changes state of number entry. With f - set g-shift function do_EE() { if (calcstate == learn) { add_step('A'); display(); } else if (f_flag) { f_flag=0; g_flag=1; display(); } else if (g_flag) { g_flag=0; display(); } else { switch (numstate) { case num: numstate = deci; pos = 1/10; break; case deci: numstate = posex; mant = x; expon = 0; break; case posex: numstate = negex; x = mant * Math.pow(10, -expon); display(); break; default: break; } } } // Shift key - once for f-shift, twice for g-shift function do_FG() { if (f_flag) { f_flag=0; g_flag=1; } else if (g_flag) { f_flag=0; g_flag=0; } else { f_flag=1; g_flag=0; } display(); } // C/CE - normally clears just about everything except program and pc (C). // If there is a pending operator, clear that operator and any operand // after it, and bring the 1st argument back into x (CE) function do_CCE() { if (f_flag) { inc_pc(); f_flag=0; dispstate=showstep; display(); } else if (g_flag) { g_flag=0; display(); } else { if (pend) { pend = 0; autok = 0; x = y; y = 0; } else { x = 0; y = 0; f_flag = 0; g_flag = 0; pend = 0; autok = 0; stale = 1; bracket=0; } dispstate=showx; numstate=num; calcstate=calc; display(); } } // Power switch used as a complete reset, but unlike the calc does not // lose the program function do_power() { x = 0; y = 0; m = 0; f_flag = 0; g_flag = 0; numstate=num; dispstate=showx; calcstate=calc; bracket=0; pc=0; pend=0; stale=1; autok=0; hash=0; display(); } // 7 key. Base state enters a 7. With f does sin. With g does arcsin function do_7() { if (calcstate == learn) { add_step('7'); display(); } else if (f_flag) { x = Math.sin(x); f_flag=0; stale = 1; numstate=num; display(); } else if (g_flag) { x = Math.asin(x); g_flag=0; stale = 1; numstate=num; display(); } else number_key(7); } // 8 key. Base state enters a 8. With f does cos. With g does arccos function do_8() { if (calcstate == learn) { add_step('8'); display(); } else if (f_flag) { x = Math.cos(x); f_flag=0; stale = 1; numstate=num; display(); } else if (g_flag) { x = Math.acos(x); g_flag=0; stale = 1; numstate=num; display(); } else number_key(8); } // 9 key. Base state enters a 9. With f does tan. With g does arctan function do_9() { if (calcstate == learn) { add_step('9'); display(); } else if (f_flag) { x = Math.tan(x); f_flag=0; stale = 1; numstate=num; display(); } else if (g_flag) { x = Math.atan(x); g_flag=0; stale = 1; numstate=num; display(); } else number_key(9); } // RUN key. Base calls the program execution function. With f switches to // learn mode. Don't want recursion, so error if somehow called while still // running function do_RUN() { if (calcstate == running) { alert('run in run!'); calcstate = calc; } else if (f_flag) { calcstate=learn; f_flag=0; numstate=num; dispstate=showstep; display(); } else if (g_flag) { g_flag=0; display(); } else { calcstate = running; run_program(); display(); } } // 4 key. Base state enters a 4. With f does natural log. With g does e^x function do_4() { if (calcstate == learn) { add_step('4'); display(); } else if (f_flag) { x = Math.log(x); f_flag=0; stale = 1; numstate=num; display(); } else if (g_flag) { x = Math.exp(x); g_flag=0; stale = 1; numstate=num; display(); } else number_key(4); } // 5 key. Base state enters a 5. With f does recall from store m. With g // does exchange with m. See 2 key for oddities of recall function function do_5() { if (calcstate == learn) { add_step('5'); display(); } else if (f_flag) { x = m; f_flag=0; stale = 1; autok = 0; numstate=num; display(); } else if (g_flag) { z = x; x = m; m = z; g_flag=0; stale = 1; autok = 0; numstate=num; display(); } else number_key(5); } // 6 key. Base state enters a 6. With f does open and close 1-level // brackets. With g converts radians to degrees function do_6() { if (calcstate == learn) { add_step('6'); display(); } else if (f_flag) { if (!bracket) { bra_y = y; bra_pend = pend; pend = 0; bracket=1; stale = 1; } else { do_pend(); y = bra_y; pend = bra_pend; bracket=0; stale = 1; } f_flag=0; numstate=num; display(); } else if (g_flag) { x = x*180/Math.PI; g_flag=0; stale = 1; numstate=num; display(); } else number_key(6); } // Division key. Not affected by shifts. Completes any pending binary // operator, saves divide as pending operator. Keeps x reg but marks it // for overwriting if any number entered following. Sets autok so that // if no following number the pending divide will get done as 1-operand // version (1/x). When running, clears numeric mode. function do_DIV() { if (calcstate == learn) { add_step('G'); } else { do_pend(); y = x; pend = div; stale = 1; autok = 1; f_flag = 0; g_flag = 0; numstate=num; hash=0; } display(); } // 1 key. Base state enters a 1. With f does square root. With g does go // if neg, which is available only when running. Goifneg picks jump // address from program if x