Pre-compiler directives

2010-12-17

If you've ever done anything in C (or related) you'll know about #define, #import, #ifdef and such. Being a pre-compiled language, these directives are invaluable for creating small branches (or even platform independent) software. But also to make the code easily changeable without having to replace some value throughout the code. And of course get rid of magic numbers without slowing down execution.

Although js isn't a compiled language (it's an interpreted language), there's still some compilation going on the back. And lately, this compilation is also happening before execution. See engines like Google's Closure Compiler, Mascara or even derived languages like Coffeescript. Even minification is a form of pre-compilation.

When I say "directive" I'm talking about a pre-compile command like #define or #ifdef. A token is the "name" that's targeted by a directive, like #define THIS_IS_A_TOKEN.

So I created a simple proof of concept for a compiler that offers a few of the directives so common in compiled languages. As it's just a poc, I've only created suppert for #define, #ifdef, #endif, #macro, #inline, #endline, #finline and #fendline. Let me explain them.

All directive's must be at the start of a line, only tabs and spaces may preceed it. All directives are prefixed by // as to allow the script to run as is, but in this blog post I'll leave the forward slashes out for brevity. All lines with (proper) directives are stripped as a whole in the resulting code. See also directive specific behavior on replacing tokens. Tokens are only separated by a directive by whitespace and at least one such character should separate them. Except for #macro, all tokens are delimited by a return. For #macro it's another whitespace char.

#define TOKEN goes hand in hand with #ifdef TOKEN and #endif. I've not added #else and #elseif but that shouldn't be that hard. They also don't nest, because that'd require a slightly more complex approach. With #define TOKEN you simply set the token after the directive to true. Any token not defined that way is deemed to be false.

#ifdef TOKEN ... #endif conditionally does or does not compile the code enclosed up to the first #endif. The condition depends on whether the condition was defined with #define. If not defined, the enclosed code will not show up in the compiled code. This makes it easy to add conditional debugger statements or test snippets.

#macro TOKEN value will replace any instance of the token by the value. The value is delimited by the end of the line and trimmed. For future versions, this directive should probably contain some kind of fallback (like allowing to declare a variable in the "decompiled" code).

#inline ... #endline is meant to be able to replace the token by the (possibly multi-line) content of the inline block. Unless you're never going to execute the "decompiled" code, or have some kind of different trick in mind, you're probably not going to use this a lot. But that's where #finline comes in handy.

#finline TOKEN ... #fendline will take the contents of the second to second-last line (discarding the first and last line of content as well as the lines of the directives entirely) and replace any line containing the token completely by those lines. This means that you can create functions in your "decompiled" script and inline them when compiling the script.

One usage of these directives is to be able to add tests inline, without having to worry about them being in the production code. Sometimes you want some debug tests in your code during development. But you don't want to have to strip them all out manually to create a production version, only to find out there's a bug and having to do it all over again.

Another usage is creating benchmarks. You can easily create functions for small snippets of code which are part of larger functions. When production code needs the speed, you can't have that many function calls. But benchmarking in js relies on functions. So you can use the function inlining directive to help you with the benchmarking, without having to worry about speed or rewriting.

You can find the demo here.

Anyways, as said it's just a proof of concept. A simple script to demonstrate to you and myself the usage of these directives.