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