Report a bug
If you spot a problem with this page, click here to create a Bugzilla issue.
Improve this page
Quickly fork, edit online, and submit a pull request for this page. Requires a signed-in GitHub account. This works well for small changes. If you'd like to make larger changes you may want to consider using a local clone.

AST Macros Lite

Version 1
Created 2015-05-26
StatusRejected
Last modified 2015-05-26
Author

Abstract

Proposal for a macro system without syntactical extensions to the language. Hence it doesn’t allow arbitrary syntax.

Rationale

Like in DIP50, macros are implemented as almost normal functions executed at compile time. Syntax tree is handled as plain data in imperative style. The only difference from normal functions is how their arguments are prepared and how their return values are handled at the point of invocation, function itself runs as usual on current CTFE engine.

Description

Macro functions integrate with the compiler using a new special type, say, core.macros.Auto.

module core.macros;

// well, declare it in any way which makes the compiler happy
alias Auto = __auto_ast_converter__;

// or
struct Auto
{
    Node node;
    // converts to and from ast node automatically
    alias node this;
}

/// Root for ast nodes type hierarchy
class Node
{
}

This type makes the compiler convert expressions into a graph of objects from core.macros module to pass it to the macro function as arguments. It also makes the returned ast to automatically mix into code at the point of invocation.

import core.macros;

Auto myAssert(Auto condition, Auto message)
{
    // condition and message are automatically converted to Node
    return
        new IfStatement(
            new NotExpression(condition),
            new CallStatement(new Identifier("fail"), message));
}

void f()
{
    myAssert(a==b, "test");
}

// is translated to
void f()
{
    if(!(a==b))fail("test");
}

When the function being invoked accepts a parameter of type core.macros.Auto automatic conversion of expressions happens roughly as follows:

void f()
{
    mixin(myAssert(parse(`a==b`), parse(`"test"`)).toString());
}

This can be optimized by the compiler, say, by having a macro component before CTFE engine, which would prepare a deep copy of syntax tree for function arguments from the compiler internal ast presentation and/or convert the return value back.

Other than handling the core.macros.Auto type at the call site, macro functions have no other special treatment. Parameters and return value can be typed core.macros.Auto independently:

// essentially stringifies passed identifier
string identifierName(Auto id)
{
    // cast is ok because happens at compile time
    return (cast(IdentifierExpression)id).name;
}

// use:
enum string name = identifierName(myid);
assert(name=="myid");


// mixes the passed ast into code
Auto mix(Node node)
{
    return node;
}

// use:
//another function builds the tree as plain data
enum Node node = myBuildTree();
mix(node); //mix the result

Examples of ast hierarchies: System.Linq.Expressions in .Net, macros module in Nim.

Future directions

Attribute macro receives the declaration it’s applied to as the last parameter:

@attributeMacro
Auto test1(Auto decl)
{
    return handle(decl);
}

//use:
@test1 int myvar;

Statement macro is similar:

@statementMacro
Auto myforeach(Auto i, Auto c, Auto statement)
{
    return handle(i,c,statement);
}

//use (currently accepted as valid syntax):
    myforeach(i,e in a) = {
        int b;
        c=0;
    };

//or use attribute syntax? (currently rejected as invalid syntax)
    @myforeach(i,e in a)
    {
        int b;
        c=0;
    }

This document has been placed in the Public Domain.