Alright, let’s get cracking. The easiest way to write a calculator is not to write it at all. Javascript itself already is a calculator, isn’t it? Mathematical expressions are automatically evaluated by the Javascript interpreter, so if you write something like
1 | var result = 1 + 1; |
the variable result
will reference the number 2
.
Even better, it’s prefectly capable of handling operator precedence — for instance multiplication always has to be done before addition, and if you write
1 | var result = 1 + 2 * 3; |
the result will (correctly) be 7
, and not 9
, which a naive calculator that handles all operations from left to right, no matter what, might come up with.
Work Delegation
So, the general idea is this: If you have a string that contains a mathematical expression, like 1 + 2 * 3
, you can use eval
to evaluate that expression and get its value. The only code you have to write is for handling the input and output — the hard part of actually calculating stuff can be left to the Javascript interpreter.
This typically means you have to do the following:
1.) Set up a bunch of buttons, so clicking on them will add a character to the input/output field. Those characters are the numbers and mathematical operations needed to form the mathematical expression.
2.) Set up a ‘calculate’ or ‘=’ button, so clicking on it will read the current contents of the input/output field, evaluate them, and write the result back to the input/output field.
And that’s all there is to it. Here’s an example of how you might do that:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <title>Simple Calculator</title> <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.0/jquery.js"></script> <script> $(function () { // Add containers to the DOM var $calculator = $('<div/>', {id: 'calculator'}).appendTo('body'); var $input = $('<input/>', {id: 'input'}).appendTo($calculator); var $buttons = $('<div/>', {id: 'buttons'}).appendTo($calculator); // Add buttons to the DOM $.each('1234567890.=+-*/←C'.split(''), function () { var $button = $('<button/>', {text: this.toString(), click: function () { // Handle button clicks switch ($(this).text()) { // '=' will fetch the current expression string, evaluate it, // and write the result back into the input/output field. // That's where the actual calculation happens. case '=': try {$input.val(eval($input.val()));} catch (e) {$input.val('ERROR');} // 'C' will clear the input/output field break; case 'C': return $input.val(''); // 'CE' will delete the last character from the input/output field break; case '←': return $input.val($input.val().replace(/.$/, '')); // All other buttons will add a character to the input/output field break; default: $input.val($input.val() + $(this).text()); } }}).appendTo($buttons); }); }); </script> <style type="text/css"> #calculator {border: 1px solid; width: 130px;} #input {border: 1px solid; height: 20px; width: 114px; margin: 5px; padding: 2px; text-align: right;} #buttons {margin: 5px;} button {width: 30px; height: 30px;} </style> </head> <body></body> </html> |
I used jQuery here, since there’s nothing going on except for a bit of DOM manipulation, which would be more tedious using plain Javascript.
Wait, Isn’t eval Evil?
You might have read somewhere never to use eval
, since it’s ostensibly “evil”, whatever that means. That blanket statement is a bit harsh — eval
certainly doesn’t give AIDS to little children. There’s a bit of overcompensation going on, because beginners have thoroughly abused eval
for ages, but it does have its legitimate uses, if you understand its downsides:
Debuggability issues: Dynamically generated code (and that’s what’s happening when using eval
) is hard to debug. It won’t have line numbers, you can’t easily set breakpoints, and you’re generally forfeiting most of your debugging tools. That means that putting executable code into a string in one of your code files is never a good idea. There’s always — I repeat, always — a better way. Consider this example:
1 2 3 4 5 | // DON'T DO IT LIKE THIS var fliesLike = {time: 'arrow', fruit: 'banana'}; for (var property in fliesLike) { eval('alert(property + " flies like: " + fliesLike.' + property + ')'); } |
Quite obviously, whoever wrote that piece of code learned that the right syntax for accessing object properties is fliesLike.time
, but since the property name is variable, this won’t work — so they resorted to dynamically creating the string "fliesLike.time"
and evaluating it. And for good measure, they threw the whole command into the string. Just to make sure.
Just by looking at that mess, it should be immediately obvious that that’s pretty ugly and generally no good. If only the author knew that object properties can also be accessed using brackets, their code might have been much saner:
1 2 3 4 | var fliesLike = {time: 'arrow', fruit: 'banana'}; for (var property in fliesLike) { alert(property + ' flies like: ' + fliesLike[property]); } |
That’s a great example of ignorance resulting in eval
-abuse, which is the main culprit for the horrible reputation eval
has today. So, what’s a legitimate use? Let’s see why this wasn’t: That guy knew full well which object to use, which property to access, and what to do with it. So, just do it, for god’s sake.
In our calculator, on the other hand, the situation is quite different: The source of those calculations isn’t some other piece of code, but user input, which results in a string. That means that we (the coders) aren’t putting code into a string (which is always horrible and wrong) — we just happen to have access to a string that contains code, and in order to execute that code, it’s okay to eval
the string.
Security issues: This is the main argument for eval
being evil, although many people using that argument don’t fully understand exactly what security concerns are involved here.
I want you to be one of those people that do, and it’s actually pretty simple: Whenever a user accesses your site, there are, easily enough, two parties involved: the user and your site. You can put all the Javascript you want into your site, but you can’t do any harm — the browser will prevent you from accessing any sensitive user data outside of your own site (like the user’s file system or cookies the user got from other sites). The user, on the other hand, can use the browser’s console or custom user scripts to run any piece of Javascript they want while on your site, but they can’t do any harm either — it runs locally in their browser, and no one else will ever know what they did.
Here’s where eval
comes in, which is able to convert a string to executable code — and whether or not that code can do any harm depends on both where it’s coming from, and where it’s going:
a) If it comes from you and goes to the user, you’re doing it wrong. You have full control over your own code, so there’s no need to eval
anything. That said, you can’t do any harm, because you’re already able to run any arbitrary piece of Javascript in the user’s browser, while they are on your own website.
b) If it comes from the user and stays at the user, no harm can be done either. The user is already able to run any arbitrary piece of Javascript in their browser while on your website. This is what’s happening with our calculator.
c) If it comes from another web service and goes to the user, there’s potentially trouble: The user’s browser will run it with the same privileges your own code has, which means it can access and steal the cookies the user got from your website (the session cookie being tantamount to the user’s identity on your website), monitor the user’s every move on your website, and quite generally do anything that can be done on your website — all in the user’s name. For instance, if you allow logged in users to post comments or place an order, that piece of code can do just that.
Alright, so why would you even think about eval
ing code that came from another web service? Most often, it’s done for converting a JSON string to an object in order to access third party data — for instance, you might want to display a photo stream from flickr on your website, so you fetch the photo data as JSON from their site. Newer browsers implement a native JSON object that does away with the need of using eval
, but the point is, this case comes down to trust. If you don’t trust a service not to inject malicious code into your website, don’t use it.
d) If it comes from one user and goes to another user, there’s definitely trouble. All the issues mentioned above still apply, only now it’s really stupid to trust, since the source of the code is some arbitrary user you don’t know.
There are basically two ways in which this can happen: First, you save user input on the server and make it available to other users. That’s happening all the time — one user writes a comment and another user can read it — and that’s the reason why any user input that will end up in another user’s browser has to be thoroughly filtered. If any piece of executable code makes it through, you’re screwed. Using eval
to generate a piece of executable code from a string that might have been perfectly harmless on its own is particularly stupid in this case. Second, you eval
part of the querystring. In this case, it suffices to coax other users into clicking a link that has been prepped up with a malicious querystring, or into submitting a malicious form from another website, in order to do harm. Here, again, it’s bad enough to print any unfiltered piece of the querystring back to the page, and it’s particularly stupid to eval
it.
What next?
Alright, now that we’ve established that this simple approach isn’t all bad, since the gravest downsides of eval
don’t apply, let’s get real: it isn’t very good either.
For instance, there’s nothing to prevent the user from entering a malformed string, like "1.2.3++*"
, which will cause an error when trying to evaluate it. We could counter that by thoroughly filtering the input string before passing it to eval
.
Being well-formed still doesn’t mean it’s valid, though — for instance 1/0
looks like a perfectly well-formed mathematical expression, but it still won’t result in a number, since division by 0 is not allowed. We could counter that by introducing some sort of error handling.
What if we want to implement more than just the basic operations, for instance x^y
(exponentiation)? There isn’t a Javascript operator that can be put between two numbers in order to accomplish that — it has to be done with Math.pow(x, y)
, which means that our buttons can’t just add to the input string; they have to somehow retroactively change it depending on what’s already in there and on which button has been pressed — and that’s where the trouble begins:
Exponentiation has higher precedence than multiplication, and we might just be able to convert the button input "2 [*] 3 [^] 4"
to the string "2*Math.pow(3,4)"
, but the problem is this: Up until now, we didn’t have to care about operator precedence, because we just let the Javascript interpreter handle that. *
comes before +
, and Javascript knows that. If we start using functions to represent mathematical operations, though, that built-in precedence handling flies right out the window. If, for instance, some custom operation has lower precedence than *
, the button sequence "2 [*] 3 [customButton] 4"
would have to be parsed to "customFunction(2*3,4)"
, and not to "2*customFunction(3,4)"
.
How would our code know about operator precedence? We would need some data structure where we can store that sort of information. How would it translate button presses into a valid expression that can not only be eval
ed but that also respects operator precedence? Some heavy and potentially ugly parsing.
The answers to those last two questions suggest that, going forward, we’re better off ditching the eval
approach, and should try instead to come up with code for an actual calculator that knows what it’s doing, as opposed to the translator between the user’s input and the Javascript interpreter we’ve created so far.
And that’s exactly what we’re going do do next.