Exploring the Nemerle macro system
13 Sep 2013This post was imported from blogspot.
To illustrate the properties of Nemerle's macro system, I will show a list of macros, followed by a program that calls the macros./* Macro.n */ using System; using System.Linq; using System.Console; namespace Macro { using P; public macro Macro1(e:PExpr) { // This macro calls Console.WriteLine(...) even if the call site is // not "using System.Console". <[ WriteLine($e); ]> } public macro Macro2(e:PExpr) { // This macro creates its own "x" variable and prints it. Notice // that Main() calls Macro2() twice and there is no error on the // second call. So two separate versions of "x" are being defined. // (It's important that "x" is mutable. You can redefine an // immutable variable, but not a mutable one.) <[ mutable x = $e; WriteLine(x); ]> } public macro Macro3(e:PExpr) { // This macro tries to assign an expression to an existing // "x" variable, but it always fails at expansion time; we // can't use an "x" defined by the caller. <[ x = $e; WriteLine(x); ]> } public macro Macro4(e:PExpr) { // This macro shows the effects of Nemerle's hygiene system. // The first statement creates an "x" variable and the second // statement (returned by Q.Abs()) takes the absolute value of // that "x" variable. The way this works is a little peculiar // because: // - Q.Abs() is not aware of x, but somehow can still use it // - Q.Abs() returns <[ Abs(x) ]>, but Macro4's context is not // aware of Abs(). P.Q is using System.Math, but the namespace // of Macro4 is NOT using System.Math... yet it still works. def stmt1 = <[ def x = $e; ]>; def stmt2 = Q.Abs(); <[ $stmt1; $stmt2; ]>; } namespace P { using System.Math; using Nemerle.Compiler.Parsetree; // for PExpr internal partial module Q { public Abs() : PExpr { <[ Abs(x); ]> } public static Seven = 777; } } public macro Macro5(e:PExpr) { // Although "WriteLine" is normally interpreted as Console.WriteLine, // we can also define a "WriteLine" variable and it will not be // misinterpreted as Console.WriteLine. <[ { def WriteLine = $e; Console.WriteLine(WriteLine); } WriteLine($e); ]> } public macro Macro6(e:PExpr) { // This example is tricky. We "call" some unknown expression and // then call writeline. The call site in Main() actually invokes // Macro6(MakeALambdaCalled), so the first line expands to // MakeALambdaCalled(WriteLine). So the MakeALambdaCalled macro // is invoked, which creates a lambda in a variable called "WriteLine" // that calls Trace.WriteLine() instead of Console.WriteLine(). // Consequently, the line WriteLine("Hello, Macro") calls // Trace.WriteLine() instead of Console.WriteLine(). // // So what's the point of this example? It says something about how // Nemerle binds variables. It proves that the compiler does not // decide what "WriteLine" means inside any of the macros themselves, // but rather the decision is made after the macros have been fully // expanded. <[ $e(WriteLine); WriteLine("Hello, Macro6!"); ]> } public macro MakeALambdaCalled(name:PExpr) { <[ def $name = s => System.Diagnostics.Trace.WriteLine(s); ]> } public macro Macro7() { // A demonstration of how Nemerle does name lookup. In this example we // are apparently referring to P.Q.Seven which is 777. However, when // the macro is called from Main(), it actually uses System.Linq.Q.Seven // which is defined as "seven". Notice that Main() doesn't have a // "using System.Linq" -- the compiler picked up the "using" from // this file. Also notice that System.Collections.Generic.Q.Seven exists // but the compiler ignores it. <[ Q.Seven ]> } }
/* Main.n */ using System.Collections.Generic; using System; using Macro; namespace System.Linq { module Q { public static Seven = "seven"; } } namespace System.Collections.Generic { module Q { public static Seven = 7; } } module Program { Main() : void { Macro1("Hi!"); // Output: "Hi!" //WriteLine("Hi!"); // Error: unbound name 'WriteLine' Macro2(2.0); // Output: 2 Macro2("Too"); // Output: "Too" mutable x = ""; //Macro3("Three"); // Error: unbound name 'x' def four = Macro4(-4); // { def x = -4; System.Math.Abs(x) } Console.WriteLine(four);// Output: "4" mutable five = "five"; Macro5(five); // Output: two lines: "five", "five" Macro6(MakeALambdaCalled); // "Hello, Macro6!" as debug trace Console.WriteLine(Macro7()); // Output: "seven" _ = Console.ReadKey(false); } }