The April 2015 Community Challenge The April 2015 Community Challenge requires building a calculator.
The April 2015 Community Challenge requires building a calculator.
The April 2015 Community Challenge requires building a calculator.
var calc = {
stack: {
values: new Array(1024),
size: 0,
decimal: 0
},
display: {
value: 0.0,
integral: true
},
layout: [
{
compact: false,
buttons: ["seven", "eight", "nine", "root", "clear"]
},
{
compact: false,
buttons: ["four", "five", "six", "times", "divide"]
},
{
compact: false,
buttons: ["one", "two", "three", "plus", "minus"]
},
{
compact: false,
buttons: ["zero", "point", "plusminus", "exp", "equals"]
},
],
keys: {},
buttons: {
zero: {
action: function() {return digit(0);},
label: "0",
key: [48]
},
one: {
action: function() {return digit(1);},
label: "1",
key: [49]
},
two: {
action: function() {return digit(2);},
label: "2",
key: [50]
},
three: {
action: function() {return digit(3);},
label: "3",
key: [51]
},
four: {
action: function() {return digit(4);},
label: "4",
key: [52]
},
five: {
action: function() {return digit(5);},
label: "5",
key: [53]
},
six: {
action: function() {return digit(6);},
label: "6",
key: [54]
},
seven: {
action: function() {return digit(7);},
label: "7",
key: [55]
},
eight: {
action: function() {return digit(8);},
label: "8",
key: [56]
},
nine: {
action: function() {return digit(9);},
label: "9",
key: [57]
},
point: {
action: function() {
if (calc.stack.decimal === 0) {
calc.stack.decimal = 1;
}
return peek();
},
label: ".",
key: [46]
},
root: {
action: function() {
return unaryOp(function(val) {
return Math.sqrt(val);
});
},
label: "r\u221A",
color: "black",
key: [114]
},
plusminus: {
action: function() {
return unaryOp(function(val) {
return -val;
});
},
label: "~\u00B1",
key: [126]
},
plus: {
action: function() {
return binaryOp(calc.buttons.plus);
},
operate: function(left, right) {
return left + right;
},
label: "+",
color: "black",
key: [43]
},
minus: {
action: function() {
return binaryOp(calc.buttons.minus);
},
operate: function(left, right) {
return left - right;
},
label: "-",
color: "black",
key: [45]
},
times: {
action: function() {
return binaryOp(calc.buttons.times);
},
operate: function(left, right) {
return left * right;
},
label: "*",
color: "black",
key: [42,120]
},
divide: {
action: function() {
return binaryOp(calc.buttons.divide);
},
operate: function(left, right) {
return left / right;
},
label: "/\u00F7",
color: "black",
key: [47]
},
exp: {
action: function() {
return binaryOp(calc.buttons.exp);
},
operate: function(left, right) {
return Math.pow(left , right);
},
label: "EXP^",
color: "black",
key: [94]
},
equals: {
action: function() {resolve();},
label: "=",
color: "black",
key: [61,13]
},
clear: {
action: function() {reset();},
label: "del",
color: "orange",
key: [127][32, 127]
}
}
};
function peek() {
if (calc.stack.size == 0) {
throw "Empty stack peek()";
}
return calc.stack.values[calc.stack.size - 1];
}
function pop() {
if (calc.stack.size == 0) {
throw "Empty stack pop()";
}
calc.stack.size--;
var val = calc.stack.values[calc.stack.size];
calc.stack.values[calc.stack.size] = null;
return val;
}
function push(val) {
calc.stack.values[calc.stack.size++] = val;
return val;
}
function cleared() {
return calc.stack.size == 0;
}
function reset() {
while(calc.stack.size != 0) {
pop();
}
push(0);
calc.stack.decimal = 0;
}
function updateDisplay() {
var disp = "";
for (var i = 0; i < calc.stack.size; i++) {
if (typeof calc.stack.values[i] == "number") {
disp += " " + calc.stack.values[i];
} else {
disp += " " + calc.stack.values[i].label;
}
}
d3.select("#display")
.attr("value", "" + disp);
}
function resolve() {
var val = pop();
while (!cleared()) {
var op = pop();
var left = pop();
var eq = op.operate(left,val);
console.log("Compute: " + left + " " + op.label + " " + val + " -> " + eq);
val = eq;
}
push(val);
calc.stack.decimal = 0;
}
function unaryOp(fn) {
// unary expects a value at stack-top.
if ("number" != typeof peek()) {
console.log("Evict " + pop());
}
push(fn(pop()));
}
function binaryOp(fn) {
resolve();
push(fn);
push(0);
}
function digit(val) {
if (calc.stack.decimal === 0) {
return push(pop() * 10 + val);
}
calc.stack.decimal /= 10.0;
var current = pop();
var n = calc.stack.decimal * val;
var tp = n + current;
//console.log("Current " + current + " decimal " + calc.stack.decimal + "... new " + n + " to push " + tp);
return push(tp);
}
function pressButton(name) {
var detail = calc.buttons[name];
//console.log("Button pressed: " + name);
detail.action();
d3.select("#" + name)
.style("background-color", "red")
.transition()
.style("background-color", detail.color);
updateDisplay();
}
function keyHook(kevent) {
var k = kevent.keyCode || kevent.which;
if (calc.keys[k]) {
console.log("Keypress code " + kevent.keyCode + " which: " + kevent.which + " linked to " + calc.keys[k]);
pressButton(calc.keys[k]);
return;
}
console.log("Unrecognized Keypress " + k);
}
function setup() {
var buttons = d3.select("#buttons");
calc.layout.forEach(function(row) {
row.buttons.forEach(function(bid){
var b = calc.buttons[bid];
b.id = bid;
b.color = b.color || "darkslategray";
var title = "Key:";
b.key.forEach(function(k){
var kname = String.fromCharCode(k);
title = title + " "'" + kname;kname + "'";
calc.keys[k] = function() {
pressButton(bid);
};bid;
});
buttons.append("input")
.classed("button",true)
.style("background-color", b.color)
.attr("title", title)
.attr("type", "button")
.attr("id", bid)
.attr("value", b.label)
.attr("onclick", "pressButton(this.id);");
});
buttons.append("br");
});
window.onkeypress=keyHook;
reset();
updateDisplay();
}
setup();
#display {
text-align: right;
font: bold 50px monospace;
background-color: darkgray;
width: 100%;
box-sizing: border-box;
}
.button {
font: bold 35px monospace;
color: white;
margin: 10px;
width: 3em;
}
table {
border-collapse: collapse;
margin: 0 auto
}
table.main {
background-color: lightgray;
border: 2px solid darkgray;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<table class="main">
<caption>CodeReview Calculator</caption>
<tr>
<td>
<input id="display" value="0" size="15" readonly="true" >
</td>
</tr>
<tr>
<td>
<div id="buttons"></div>
</td>
</tr>
</table>
var calc = {
stack: {
values: new Array(1024),
size: 0,
decimal: 0
},
display: {
value: 0.0,
integral: true
},
layout: [
{
compact: false,
buttons: ["seven", "eight", "nine", "root", "clear"]
},
{
compact: false,
buttons: ["four", "five", "six", "times", "divide"]
},
{
compact: false,
buttons: ["one", "two", "three", "plus", "minus"]
},
{
compact: false,
buttons: ["zero", "point", "plusminus", "exp", "equals"]
},
],
keys: {},
buttons: {
zero: {
action: function() {return digit(0);},
label: "0",
key: [48]
},
one: {
action: function() {return digit(1);},
label: "1",
key: [49]
},
two: {
action: function() {return digit(2);},
label: "2",
key: [50]
},
three: {
action: function() {return digit(3);},
label: "3",
key: [51]
},
four: {
action: function() {return digit(4);},
label: "4",
key: [52]
},
five: {
action: function() {return digit(5);},
label: "5",
key: [53]
},
six: {
action: function() {return digit(6);},
label: "6",
key: [54]
},
seven: {
action: function() {return digit(7);},
label: "7",
key: [55]
},
eight: {
action: function() {return digit(8);},
label: "8",
key: [56]
},
nine: {
action: function() {return digit(9);},
label: "9",
key: [57]
},
point: {
action: function() {
if (calc.stack.decimal === 0) {
calc.stack.decimal = 1;
}
return peek();
},
label: ".",
key: [46]
},
root: {
action: function() {
return unaryOp(function(val) {
return Math.sqrt(val);
});
},
label: "r\u221A",
color: "black",
key: [114]
},
plusminus: {
action: function() {
return unaryOp(function(val) {
return -val;
});
},
label: "~\u00B1",
key: [126]
},
plus: {
action: function() {
return binaryOp(calc.buttons.plus);
},
operate: function(left, right) {
return left + right;
},
label: "+",
color: "black",
key: [43]
},
minus: {
action: function() {
return binaryOp(calc.buttons.minus);
},
operate: function(left, right) {
return left - right;
},
label: "-",
color: "black",
key: [45]
},
times: {
action: function() {
return binaryOp(calc.buttons.times);
},
operate: function(left, right) {
return left * right;
},
label: "*",
color: "black",
key: [42,120]
},
divide: {
action: function() {
return binaryOp(calc.buttons.divide);
},
operate: function(left, right) {
return left / right;
},
label: "/\u00F7",
color: "black",
key: [47]
},
exp: {
action: function() {
return binaryOp(calc.buttons.exp);
},
operate: function(left, right) {
return Math.pow(left , right);
},
label: "EXP^",
color: "black",
key: [94]
},
equals: {
action: function() {resolve();},
label: "=",
color: "black",
key: [61,13]
},
clear: {
action: function() {reset();},
label: "del",
color: "orange",
key: [127]
}
}
};
function peek() {
if (calc.stack.size == 0) {
throw "Empty stack peek()";
}
return calc.stack.values[calc.stack.size - 1];
}
function pop() {
if (calc.stack.size == 0) {
throw "Empty stack pop()";
}
calc.stack.size--;
var val = calc.stack.values[calc.stack.size];
calc.stack.values[calc.stack.size] = null;
return val;
}
function push(val) {
calc.stack.values[calc.stack.size++] = val;
return val;
}
function cleared() {
return calc.stack.size == 0;
}
function reset() {
while(calc.stack.size != 0) {
pop();
}
push(0);
calc.stack.decimal = 0;
}
function updateDisplay() {
var disp = "";
for (var i = 0; i < calc.stack.size; i++) {
if (typeof calc.stack.values[i] == "number") {
disp += " " + calc.stack.values[i];
} else {
disp += " " + calc.stack.values[i].label;
}
}
d3.select("#display")
.attr("value", "" + disp);
}
function resolve() {
var val = pop();
while (!cleared()) {
var op = pop();
var left = pop();
var eq = op.operate(left,val);
console.log("Compute: " + left + " " + op.label + " " + val + " -> " + eq);
val = eq;
}
push(val);
calc.stack.decimal = 0;
}
function unaryOp(fn) {
// unary expects a value at stack-top.
if ("number" != typeof peek()) {
console.log("Evict " + pop());
}
push(fn(pop()));
}
function binaryOp(fn) {
resolve();
push(fn);
push(0);
}
function digit(val) {
if (calc.stack.decimal === 0) {
return push(pop() * 10 + val);
}
calc.stack.decimal /= 10.0;
var current = pop();
var n = calc.stack.decimal * val;
var tp = n + current;
//console.log("Current " + current + " decimal " + calc.stack.decimal + "... new " + n + " to push " + tp);
return push(tp);
}
function pressButton(name) {
var detail = calc.buttons[name];
//console.log("Button pressed: " + name);
detail.action();
d3.select("#" + name)
.style("background-color", "red")
.transition()
.style("background-color", detail.color);
updateDisplay();
}
function keyHook(kevent) {
var k = kevent.keyCode || kevent.which;
if (calc.keys[k]) {
calc.keys[k]();
return;
}
console.log("Unrecognized Keypress " + k);
}
function setup() {
var buttons = d3.select("#buttons");
calc.layout.forEach(function(row) {
row.buttons.forEach(function(bid){
var b = calc.buttons[bid];
b.id = bid;
b.color = b.color || "darkslategray";
var title = "Key:";
b.key.forEach(function(k){
var kname = String.fromCharCode(k);
title = title + " " + kname;
calc.keys[k] = function() {
pressButton(bid);
};
});
buttons.append("input")
.classed("button",true)
.style("background-color", b.color)
.attr("title", title)
.attr("type", "button")
.attr("id", bid)
.attr("value", b.label)
.attr("onclick", "pressButton(this.id);");
});
buttons.append("br");
});
window.onkeypress=keyHook;
reset();
updateDisplay();
}
setup();
#display {
text-align: right;
font: bold 50px monospace;
background-color: darkgray;
width: 100%;
box-sizing: border-box;
}
.button {
font: bold 35px monospace;
color: white;
margin: 10px;
width: 3em;
}
table {
border-collapse: collapse;
margin: 0 auto
}
table.main {
background-color: lightgray;
border: 2px solid darkgray;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<table class="main">
<caption>CodeReview Calculator</caption>
<tr>
<td>
<input id="display" value="0" size="15" readonly="true" >
</td>
</tr>
<tr>
<td>
<div id="buttons"></div>
</td>
</tr>
</table>
var calc = {
stack: {
values: new Array(1024),
size: 0,
decimal: 0
},
display: {
value: 0.0,
integral: true
},
layout: [
{
compact: false,
buttons: ["seven", "eight", "nine", "root", "clear"]
},
{
compact: false,
buttons: ["four", "five", "six", "times", "divide"]
},
{
compact: false,
buttons: ["one", "two", "three", "plus", "minus"]
},
{
compact: false,
buttons: ["zero", "point", "plusminus", "exp", "equals"]
},
],
keys: {},
buttons: {
zero: {
action: function() {return digit(0);},
label: "0",
key: [48]
},
one: {
action: function() {return digit(1);},
label: "1",
key: [49]
},
two: {
action: function() {return digit(2);},
label: "2",
key: [50]
},
three: {
action: function() {return digit(3);},
label: "3",
key: [51]
},
four: {
action: function() {return digit(4);},
label: "4",
key: [52]
},
five: {
action: function() {return digit(5);},
label: "5",
key: [53]
},
six: {
action: function() {return digit(6);},
label: "6",
key: [54]
},
seven: {
action: function() {return digit(7);},
label: "7",
key: [55]
},
eight: {
action: function() {return digit(8);},
label: "8",
key: [56]
},
nine: {
action: function() {return digit(9);},
label: "9",
key: [57]
},
point: {
action: function() {
if (calc.stack.decimal === 0) {
calc.stack.decimal = 1;
}
return peek();
},
label: ".",
key: [46]
},
root: {
action: function() {
return unaryOp(function(val) {
return Math.sqrt(val);
});
},
label: "r\u221A",
color: "black",
key: [114]
},
plusminus: {
action: function() {
return unaryOp(function(val) {
return -val;
});
},
label: "~\u00B1",
key: [126]
},
plus: {
action: function() {
return binaryOp(calc.buttons.plus);
},
operate: function(left, right) {
return left + right;
},
label: "+",
color: "black",
key: [43]
},
minus: {
action: function() {
return binaryOp(calc.buttons.minus);
},
operate: function(left, right) {
return left - right;
},
label: "-",
color: "black",
key: [45]
},
times: {
action: function() {
return binaryOp(calc.buttons.times);
},
operate: function(left, right) {
return left * right;
},
label: "*",
color: "black",
key: [42,120]
},
divide: {
action: function() {
return binaryOp(calc.buttons.divide);
},
operate: function(left, right) {
return left / right;
},
label: "/\u00F7",
color: "black",
key: [47]
},
exp: {
action: function() {
return binaryOp(calc.buttons.exp);
},
operate: function(left, right) {
return Math.pow(left , right);
},
label: "EXP^",
color: "black",
key: [94]
},
equals: {
action: function() {resolve();},
label: "=",
color: "black",
key: [61,13]
},
clear: {
action: function() {reset();},
label: "del",
color: "orange",
key: [32, 127]
}
}
};
function peek() {
if (calc.stack.size == 0) {
throw "Empty stack peek()";
}
return calc.stack.values[calc.stack.size - 1];
}
function pop() {
if (calc.stack.size == 0) {
throw "Empty stack pop()";
}
calc.stack.size--;
var val = calc.stack.values[calc.stack.size];
calc.stack.values[calc.stack.size] = null;
return val;
}
function push(val) {
calc.stack.values[calc.stack.size++] = val;
return val;
}
function cleared() {
return calc.stack.size == 0;
}
function reset() {
while(calc.stack.size != 0) {
pop();
}
push(0);
calc.stack.decimal = 0;
}
function updateDisplay() {
var disp = "";
for (var i = 0; i < calc.stack.size; i++) {
if (typeof calc.stack.values[i] == "number") {
disp += " " + calc.stack.values[i];
} else {
disp += " " + calc.stack.values[i].label;
}
}
d3.select("#display")
.attr("value", "" + disp);
}
function resolve() {
var val = pop();
while (!cleared()) {
var op = pop();
var left = pop();
var eq = op.operate(left,val);
console.log("Compute: " + left + " " + op.label + " " + val + " -> " + eq);
val = eq;
}
push(val);
calc.stack.decimal = 0;
}
function unaryOp(fn) {
// unary expects a value at stack-top.
if ("number" != typeof peek()) {
console.log("Evict " + pop());
}
push(fn(pop()));
}
function binaryOp(fn) {
resolve();
push(fn);
push(0);
}
function digit(val) {
if (calc.stack.decimal === 0) {
return push(pop() * 10 + val);
}
calc.stack.decimal /= 10.0;
var current = pop();
var n = calc.stack.decimal * val;
var tp = n + current;
//console.log("Current " + current + " decimal " + calc.stack.decimal + "... new " + n + " to push " + tp);
return push(tp);
}
function pressButton(name) {
var detail = calc.buttons[name];
//console.log("Button pressed: " + name);
detail.action();
d3.select("#" + name)
.style("background-color", "red")
.transition()
.style("background-color", detail.color);
updateDisplay();
}
function keyHook(kevent) {
var k = kevent.keyCode || kevent.which;
if (calc.keys[k]) {
console.log("Keypress code " + kevent.keyCode + " which: " + kevent.which + " linked to " + calc.keys[k]);
pressButton(calc.keys[k]);
return;
}
console.log("Unrecognized Keypress " + k);
}
function setup() {
var buttons = d3.select("#buttons");
calc.layout.forEach(function(row) {
row.buttons.forEach(function(bid){
var b = calc.buttons[bid];
b.id = bid;
b.color = b.color || "darkslategray";
var title = "Key:";
b.key.forEach(function(k){
var kname = String.fromCharCode(k);
title = title + " '" + kname + "'";
calc.keys[k] = bid;
});
buttons.append("input")
.classed("button",true)
.style("background-color", b.color)
.attr("title", title)
.attr("type", "button")
.attr("id", bid)
.attr("value", b.label)
.attr("onclick", "pressButton(this.id);");
});
buttons.append("br");
});
window.onkeypress=keyHook;
reset();
updateDisplay();
}
setup();
#display {
text-align: right;
font: bold 50px monospace;
background-color: darkgray;
width: 100%;
box-sizing: border-box;
}
.button {
font: bold 35px monospace;
color: white;
margin: 10px;
width: 3em;
}
table {
border-collapse: collapse;
margin: 0 auto
}
table.main {
background-color: lightgray;
border: 2px solid darkgray;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<table class="main">
<caption>CodeReview Calculator</caption>
<tr>
<td>
<input id="display" value="0" size="15" readonly="true" >
</td>
</tr>
<tr>
<td>
<div id="buttons"></div>
</td>
</tr>
</table>
Note: I found that the snippet does odd things with key-presses occasionally: here is the standalone 'original' version that I 'hacked' to make in to a snippet: standalone calc.html . I find the experience of the standalone version is better.
Note: I found that the snippet does odd things with key-presses occasionally: here is the standalone 'original' version that I 'hacked' to make in to a snippet: standalone calc.html . I find the experience of the standalone version is better.