http://pobox.com/~kragen/sw/js-calc.html
<html> <head><title>Javascript/DHTML Reverse Polish Calculator</title> <!-- See text at end for details, or just type random numbers and letters for a while. to-do: - LRU move-to-front, along the lines of alt-tab window switching - graph construction - or at least parenthesis removal - the ability to change values inside existing expressions - and define them as functions - complex numbers - APL-style array operations - units - labels - programmability --> <style type="text/css"> #output div, input { padding: 1px; width: 60% } #output div { border: 1px solid lightslategrey; margin: 1px 0 } .instructions { float: right; width: 35%; color: darkslategrey; background-color: wheat; padding: 0.5em; font-size: smaller } input { border: 1px solid yellow; font: inherit; margin: 0 } .expr { color: darkslategrey; font-size: smaller } </style> <script> ///////// Debugging stuff function td(textcontent) { var rv = document.createElement('TD') rv.appendChild(document.createTextNode(textcontent)) return rv } // table row containing two pieces of text function tablerow(a, b) { var rv = document.createElement('TR') rv.appendChild(td(a)) rv.appendChild(td(b)) return rv } // dom node containing table displaying object properties function domdump(obj) { var rv = document.createElement('TABLE') for (var prop in obj) rv.appendChild(tablerow(prop, obj[prop])) return rv } // subset of an object function slice(obj, props) { var rv = {} for (var i = 0; i < props.length; i++) rv[props[i]] = obj[props[i]] return rv } // display relevant properties of an event function debug_output(key) { var out = document.getElementById('debug_output') if (!out) return out.removeChild(out.firstChild) out.appendChild(domdump(slice(key, ['isChar', 'keyCode', 'charCode', 'shiftKey', 'type']))) // out.appendChild(domdump(key)) } ///////// Calculator primitive stuff // At first it seemed like a good idea to just use DOM nodes for the // calculator's stack; now that each stack item has three attributes, // it seems like a less good idea. // find the input we're using for this function thefield() { return document.forms.theform.the_input } // return the value from the top of the stack (can't fail) function pop_stack() { var o = document.getElementById('output') var tos = o.lastChild if (!tos) return {value: 0, expr: 0, atomic: 1} // crude hack var rv_value = new Number(tos.firstChild.nodeValue) var rv_expr = tos.lastChild.firstChild.nodeValue var atomic = tos.getAttribute('atomic') o.removeChild(tos) return {value: rv_value, expr: rv_expr, atomic: atomic} } // push a value function push_stack(val) { var n = document.createElement('DIV') n.appendChild(document.createTextNode(val.value)) var eq = document.createElement('SPAN') eq.appendChild(document.createTextNode(' = ')) n.appendChild(eq) var m = document.createElement('SPAN') m.className = 'expr' m.appendChild(document.createTextNode(val.expr)) n.appendChild(m) if (val.atomic) n.setAttribute('atomic', 1) document.getElementById('output').appendChild(n) } ///////// Calculator user operations // before you invoke any operation, or when you hit Enter, push // current text field onto stack function append_output() { // "atomic" means no parens are ever needed around this expression var val = {value: thefield().value, expr: thefield().value, atomic: 1} if (val.value == '') { val = pop_stack() push_stack(val) } push_stack(val) thefield().value = '' return false } // is the input field empty? function empty_field() { return (thefield().value == '') } function handle_backspace(key) { if (empty_field()) { pop_stack() return false } else return true } // get properly wrapped value function get_expr(obj) { if (obj.atomic) return obj.expr return "(" + obj.expr + ")" } // binary numerical operation function bin_num_op(fun, op) { if (!empty_field()) append_output() var x = pop_stack() var y = pop_stack() push_stack({value: fun(y.value, x.value), expr: get_expr(y) + " " + op + " " + get_expr(x)}) return false } // unary numerical operation function un_num_op(fun, op) { if (!empty_field()) append_output() var v = pop_stack() push_stack({value: fun(v.value), expr: op + get_expr(v)}) return false } // swap top two items on stack. function swap_stack() { var x = pop_stack() var y = pop_stack() push_stack(x) push_stack(y) } // called when a non-modifier key is pressed and released in the input field function handle_keypress(key) { var code = key.keyCode var chr = key.charCode debug_output(key) // I tried rewriting this with tables, and it was more complicated and // no shorter. if (code == key.DOM_VK_RETURN) { append_output() return false } else if (code == key.DOM_VK_BACK_SPACE) { return handle_backspace(key) } else if (chr == 43) { // + return bin_num_op(function(a, b){return a + b}, '+') } else if (chr == 42) { // * return bin_num_op(function(a, b){return a * b}, '*') } else if (chr == 47) { // '/' return bin_num_op(function(a, b){return a / b}, '/') } else if (chr == 45) { // - return bin_num_op(function(a, b){return a - b}, '-') } else if (chr == 94) { // ^ return bin_num_op(Math.pow, '^') } else if (code == 9) { // tab if (!empty_field()) append_output() swap_stack() return false } else if (chr == 69 || chr == 101) { // e return un_num_op(Math.exp, 'exp ') } else if (chr == 76 || chr == 108) { // L return un_num_op(Math.log, 'ln ') } else if (chr == 65 || chr == 97) { // a return un_num_op(Math.atan, 'arctan ') } else if (chr == 83 || chr == 115) { // s return un_num_op(Math.sin, 'sin ') } else if (chr == 67 || chr == 99) { // c return un_num_op(Math.cos, 'cos ') } else if (chr == 82 || chr == 114) { // r return un_num_op(function(x){return 1/x}, '1/') } else if (chr == 95) { // _ return un_num_op(function(x){return -x}, '-') } else if (chr == 46) { // . return true } else if (chr >= 48 && chr < 58) { // 0-9 return true } else if (chr == 0) { // some special key // other keys we might care about here are // DOM_VK_LEFT, DOM_VK_RIGHT, DOM_VK_UP, DOM_VK_DOWN return true } else return false } function startup() { thefield().focus() thefield().onkeypress = handle_keypress } </script> </head><body onLoad="startup()"> <div class="instructions"><p>Reverse Polish calculator.<br /> +*/- for arithmetic<br /> ^ for power<br /> Tab to swap<br /> Enter to dup<br /> Backspace to delete<br /> r for reciprocal<br /> _ to change sign<br /> e for exponential<br /> L for natural log<br /> s for sine<br /> c for cosine<br /> a for arctangent (in radians) </p> <p>You can compute most common functions this way. log base 10 is "L10L/", sin of degrees is "1A4*180/*S", cube root is "L3/E" or "3r^". I used techniques like this to find that 1/((arctan (.5 / (exp ((ln (1 - (.5 ^ 2))) / 2)))) / ((arctan 1) * 4)) is 6, which was sort of what I was hoping --- I managed to take the arcsine of 0.5. </p> <p>Many JavaScript RPN calculators already exist, like <a href="http://www.naveen.net/calculator/">Alexander Rau's</a>, <a href="http://www.arachnoid.com/lutusp/calculator.html">P. Lutus's</a>, <a href="http://en.tldp.org/linuxfocus/common/src/article319/rpnjcalc.html">Guido Socher's</a>, and <a href="http://users.aol.com/jgrochow/html/calc.html">Jerrold Grochow's</a>, <a href="http://home.att.net/~srschmitt/script_reverse_polish.html">Stephen R. Schmitt's</a>, <a href="http://hp.vector.co.jp/authors/VA004808/rpncalc.html">H. Tanuma's</a>, <a href="http://www3.brinkster.com/Redline/toys/rpn.asp">Roland Stolfa's</a>, <a href="http://dspace.dial.pipex.com/town/square/gd86/calc.htm">Nigel Bromley's</a>, <a href="http://www.danbbs.dk/~erikoest/rpn.htm">Erik Østergaard's</a>, <a href="http://www.cfd-online.com/Calcs/rpncalc.html">Nick Glover's</a>, and <a href="http://www.google.com/search?q=javascript+rpn">many others</a>. This one differs from the others by being nicer-looking (if perhaps harder to figure out how to use), more pleasant to use (being entirely keyboard-driven and instantly responsive), less powerful than some of them, and more powerful than others, having no hidden state, and showing the provenance of each value. I'm thinking that this one would fit nicely in a bookmarklet, and I have other plans up my sleeve as well. </p> </div> <div id="output"></div> <form name="theform"><input name="the_input"/></form> <!-- <div id="debug_output">debug output goes here</div> --> </html>