DIP78
Title: | AST Macros Lite |
---|---|
DIP: | 78 |
Version: | 1 |
Status: | Draft |
Created: | 2015-05-26 |
Last Modified: | 2015-05-26 |
Links: | NG Discussion — DIP50 |
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;
}
Copyright
This document has been placed in the Public Domain.