Friday, September 13, 2013

Exploring the Nemerle macro system

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);
  }
}

No comments: