Background
I've been using the C preprocessor to manage and "compile" semi-large javascript projects with multiple files and build targets. This gives full access to C preprocessor directives like #include
, #define
, #ifdef
, etc. from within javascript. Here's a sample build script so you can test the example code:
#!/bin/bash
export OPTS="-DDEBUG_MODE=1 -Isrc"
for FILE in `find src/ | egrep '\.js?$'`
do
echo "Processing $FILE"
cat $FILE \
| sed 's/^\s*\/\/#/#/' \
| cpp $OPTS \
| sed 's/^[#:<].*// ; /^$/d' \
> build/`basename $FILE`;
done
Make a src
and a build
directory, and put the .js files in src
.
Convenience Macros
Originally, I just wanted the preprocessor stuff for #include
and maybe a few #ifdef
s, but I got to thinking, wouldn't it be nice to have some convenience macros too? Experimentation ensued.
#define EACH(o,k) for (var k in o) if (o.hasOwnProperty(k))
Cool, so now I can write something like this:
EACH (location, prop) {
console.log(prop + " : " location[prop]);
}
And it will expand to:
for (var prop in location) if (location.hasOwnProperty(prop)) {
console.log(prop + " : " location[prop]);
}
How about foreach?
#define FOREACH(o,k,v) var k,v; for(k in o) if (v=o[k], o.hasOwnProperty(k))
// ...
FOREACH (location, prop, val) { console.log(prop + " : " + val) }
Notice how we sneak v=o[k]
inside the if
condition so it doesn't disturb the curly braces that should follow the invocation of this macro.
Class-like OOP
Let's start with a NAMESPACE macro and an obscure but useful js pattern...
#define NAMESPACE(ns) var ns = this.ns = new function()
new function(){ ... }
does some neat stuff. It calls an anonymous function as a constructor, so it doesn't need an extra ()
at the end to call it, and within it this
refers to the object being created by the constructor, in other words, the namespace itself. This also allows us to nest namespaces within namespaces.
Here is my full set of class-like OOP macros:
#define NAMESPACE(ns) var ns=this.ns=new function()
#define CLASS(c) var c=this;new function()
#define CTOR(c) (c=c.c=this.constructor=$$ctor).prototype=this;\
function $$ctor
#define PUBLIC(fn) this.fn=fn;function fn
#define PRIVATE(fn) function fn
#define STATIC(fn) $$ctor.fn=fn;function fn
As you can see, these macros define many things both in the Variable Object
(for convenience) and in this
(from necessity). Here's some example code:
NAMESPACE (Store) {
CLASS (Cashier) {
var nextId = 1000;
this.fullName = "floater";
CTOR (Cashier) (fullName) {
if (fullName) this.fullName = fullName;
this.id = ++nextId;
this.transactions = 0;
}
PUBLIC (sell) (item, customer) {
this.transactions += 1;
customer.inventory.push(item);
}
STATIC (hire) (count) {
var newCashiers = [];
for (var i=count; i--;) {
newCashiers.push(new Cashier());
}
return newCashiers;
}
}
CLASS (Customer) {
CTOR (Customer) (name) {
this.name = name;
this.inventory = [];
this.transactions = 0;
}
PUBLIC (buy) (item, cashier) {
cashier.sell(this, item);
}
}
}
What about EXTENDS?
So this brings me to the question... how can we implement EXTENDS as a macro to wrap the usual "clone the prototype, copy constructor properties" js prototype inheritance? I haven't found a way to do it outside of requiring the EXTENDS to appear after the class definition, which is silly. This experiment needs EXTENDS or it's useless. Feel free to change the other macros as long as they give the same results.
Edit - These might come in handy for EXTENDS; listing them here for completeness.
#define EACH(o,k) for(var k in o)if(o.hasOwnProperty(k))
#define MERGE(d,s) EACH(s,$$i)d[$$i]=s[$$i]
#define CLONE(o) (function(){$$C.prototype=o;return new $$C;function $$C(){}}())
Thanks in advance for any help, advice, or lively discussion. :)