Sunday, December 12, 2010

Annotations

An exciting new feature for 0.3 (currently working in the latest repository code) is "annotations."  Annotations allow you to extend the compiler with the language.  For example if I have module A:

import crack.compiler CrackContext;

void myann(CrackContext ctx) {
    ctx.inject('import crack.io cout; cout `hello world`;'.buffer);
}
And module B:


@import A myann;
@myann


The code injected from myann()would be injected into the token stream of module B.


There are a number of things that you can do from an annotation, you can read tokens, put them back, set parser callbacks, and create other annotations.  You can't yet do introspection (examine classes or functions or other compile-time objects) but that feature will be coming.


We currently use annotations to implement compiler-level macros:


import crack.exp.ann define;

@define attr(type, name) {
    type name;
    type get_$$name() { return name; }
    void set_$$name(type newVal) { name = newVal; }
} 
class Foo {
@attr(String, bar)
}


To use a macro like this from another module, you need to export it:
import crack.exp.ann define, export, exporter;
@exporter  # import all of the stuff we need to define exporter functions

@define attr(type, name) {
    type name;
    type get_$$name() { return name; }
    void set_$$name(type newVal) { name = newVal; }
}

@export attr

From another module we can import the macro as an annotation:

@import attrmod attr;

class Foo {
    @attr(String, bar);
}