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
&Oslash;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>

Reply via email to