Affe

Introducing my new CLI language, Affe. While not exactly flexible, the language will be useful to me.

Since the goal was to have an expression-evaluating language that targets DynamicMethod instead of Assembly, it's not used the same way most other languages are. It cannot be used to define subroutines or manipulate objects. In fact the only supported variable type is float.

Methods and variables are provided by a class that the embedder provides. Attributes are used to control which members are exposed to Affe.

A picture is worth a thousand words, so here is an example of how it could be used:

public class AffeTest {
    private delegate void AffeCall(AffeTest t);

    [AffeBound]
    public float X = 0;

    [AffeBound]
    public static readonly float Y = 10;

    [AffeBound]
    public static float abs(float a) {
        if (a < 0)
            return -a;

        return a;
    }

    public static void Run() {
        DynamicMethod dm = new AffeCompiler().Compile(
            typeof(AffeTest), "X = abs((5 - Y) * 3);");

        AffeTest obj = new AffeTest();

        // Invoke with reflection -- slow.
        dm.Invoke(null, new object[] { obj });
        Console.WriteLine(obj.X);

        obj.X = 0;

        // Invoke with a delegate -- very fast.
        AffeCall d = (AffeCall)
            Delegate.CreateDelegate(typeof(AffeCall), dm);

        d(obj);

        Console.WriteLine(obj.X);
    }
}

While not much, this serves to illustrate most of Affe's features. A class is created and various fields and methods are bound (note that Affe correctly handles static fields and methods), a script is compiled against the class, and run against an instance of the class.

Note that any field whose type is not float and any method whose return type and all parameter types are not float will be ignored. There is one special exception to this case. A method tagged [AffeTransform(n)] is a sort of "virtual" method taking n parameters. It will be called with two parameters, an AffeCompiler and an Expression[]. The method can then use the compiler object to emit IL. Therefore these are compile-time methods and can be used to make up for the otherwise limited language features without requiring method calls during run-time.

In case I forgot anything, here is a feature list:

  • Implicit variable declaration. (Variables used prior to assignment are set to 0.)
  • Binding of variables and calls to class members.
  • Compile-time transform methods.
  • Operators +, -, /, *, |, &, and %.

The next feature on the list to add is variable state persistence. In other words, the ability for the values in local variables to persist from call to call, possibly by way of a state object passed by the caller. Of course values in bound fields will persist, but this will allow all variables to persist without prior knowledge of their existence.