Nemerle macros intro - vilinski/nemerle GitHub Wiki

Nemerle language – part 5

Author: Chistyakov Vladislav Yuryevich
Original: http://www.rsdn.ru/article/nemerle/Nemerle-macros-intro.rsdnml.xml
Translator: Ilya Gerasimets

Nemerle macros

NOTE
The information provided here is applicable to Nemerle 1.1 since it uses some new types and new user interface elements that are not available in Nemerle 1.0.

What is a macro?

WARNING
If you haven’t had the experience of working with macros, and even more so if you don’t have meta-programming experience, you need to carefully read this section (and possibly more than once). Otherwise you may experience serious difficulties in mastering macros.

Macros can be (and should be) considered as a compiler extensions.
From a developer’s point of view, the macro is a function that is called by the compiler during program compilation and it aims to modify existing code or generate a new code.

Since a macro is in fact a program, sometimes the program that uses (expands) the macro is called the target program or the object program (but it’s worse reflects the meaning).

To not make logical errors, arguing about macros, you need to understand that the macro code does not directly affect the target program. Instead, it affects the compilation process of this program.
Since Nemerle is compiled, statically typed language (i.e. its programs are transformed into executable code before running), macros cannot be located in the same assembly where they are supposed to be used.

WARNING
The target assembly can be compiled under a platform different from the platform on which the compiler is executed (for example, the target assembly platform is Silverlight, but compilation is performed in the normal .NET Framework). On the other side macroassembly should necessarily be built for the platform of the compiler.

The assembly that contains at least one macro is called macroassembly. Macroassemblies are required to reference the assembly Nemerle.Compiler.dll.
If you are using Microsoft Visual Studio for Nemerle development (which is the most convenient way to design Nemerle applications), the easiest way to create a macroassembly is to use the project template “Macro Library”:
Figure 1. Macroassembly project template.
Figure 1. Macroassembly project template.

Types of macros

Nemerle macros can be divided into expression level macros and top level macros (also known as macroattributes).
In addition, macros can be divided into introducing and not introducing new syntax. Syntax can be introduced by expression level macros as well as by top level macros.
Expression level macros, as their name implies, can be used within expressions (for example, inside the method bodies).
Top level macros can be applied at one of the following levels:

  • assembly;
  • type;
  • member (method, property, field, event);
  • parameter.

Top level macros are often referred to as macroattributes, because if you don’t introduce the new syntax, you use the macro as a custom attribute.

Expression level macros

Expression level macros are used to generate the code inside the method bodies or to extend expression syntax.

HINT
Many Nemerle constructions are expression level macros. For example, all of the cycle operators are expression level macros (foreach, for, while, etc.), operators &&, ||, if, return, continue, break, using, assert, late, lazy, ++, +=, =>, etc. There was an example of an expression level macro in the second part.

In principle, from the expression level macro, you can create new types and have access to the entire contents of the compiled project. But be careful when you change something outside the body of the method in which you use the macro. The fact is that macros are expanded not only during command-line compilation when method bodies are processed exactly once and in a specific sequence, but also during work in the IDE. Besides during work in the the IDE method bodies can be processed multiple times (for example, after each change of a method body) and in any order. Therefore, changing the type structure of the project from macro level expression, you need to ensure that the type structure remains in the consistent state. For example, adding a type, you must ensure that it has not been added earlier by another call of the same or another macro.

WARNING
If you are not an experienced Nemerle macros designer, it is best to make it a rule not to do any side effects in expression level macros.

The best way to get all necessary information in the expression level macro is through its parameters or from the UserData (that stores the dictionary of type System.Collections.IDictionary) of types ManagerClass (which describes the compiled project) and TypeBuilder (which describes a type in the context of which the macro is expanded). Note that you can add values to these dictionaries only from the top level macros. Expression level macros should use data dictionaries only for reading.
Dictionary (the field) UserData is legitimate (and safe) way to transfer data between the various levels and phases of macros. It will be described below.
If you do decide to use side effects (for example, adding types) from within an expression level macro, you must remember that even if your code has only one call to the macro, it still can be called more than once (when working in the IDE) and that you should check that the side effect will not conflict with side effects by other calls of the same or other macros.
If you need to perform some configuration, verification or create some type, which instance would be to store the necessary information during the execution of the target program, it is best to split the macro to two macros. Create the first one as a top level macro. Perform the initialization and new types creation in this macro. Create the second one as an expression level macro. Generate code to be inserted instead of the macro to the second part. Move all preparatory activities causing side effects to the first macro. Data between these macros can be passed through the TypeBuilder. UserData […] or ManagerClass. UserData […].

Top level macros

Top level macros are executed when the compiler handles top-level program elements, such as:

  • assembly-level attributes;
  • types;
  • type members;
  • parameters of type members.

In contrast to the expression level macros in top level macros you can safely perform any side effects including causing the creation and/or modification of the types in the target program.
In fact, top level macros are mainly designed for side effects, because they (unlike expression level macros) are not expanded, which means they do not return any code.
Safety of top level macros is due to the fact that before running them the compiler performs parsing of all project files and builds a new copy of the top level AST (which is the data structure that describes the project).
Changing the top level AST entails its full rebuild, therefore top level macros are run on the “fresh” AST.
Like every coin has two sides, so the fact that before running a top level macro the whole project are reloaded, there are positive and negative consequences.
A negative consequence is that top level macros work during the rereading of the project, which slows down the reload. Slowness of the top level macros is not a big deal during the project compilation, but it becomes critical when you work in the IDE. Therefore computations that can slow down macros execution should be avoided in the top level macros.
If your macros needs to perform such calculations you can do one of the following ways:

  1. Cache the results of calculations (for example, in external files) and perform them only when the cache is out of date.
  2. Check that the macro is running from the IDE (use the property ManagerClass.IsIntelliSenseMode), and do not to carry out complex calculations. For example, if you need to generate a class that implements the parser based on a formal grammar, in IDE mode you can only generate stub methods in this class (by leaving their bodies blank), and actual method bodies generation (and accompanying complex calculation) can run under the compiler control. This greatly improves performance of the macro. In this case, however, the macro can produce a grammar check and give users error messages, if any.

Expression level macros are useful to describe a DSL that generates/modifies types or its members.

Creating a macro

To create a macro it is best to use the macro creation wizard that is path of Nemerle integration for VS 2010.
To run this wizard, select an appropriate branch in the macro library in VS 2010 and choose the menu item “Project\Add New Item …” (or choose “Add\New Item …” from the context menu in Solution Explorer). In the dialog box (see figure 2), enter a name for the new macro (it also will be the file name where it will be located), choose the template “Macro wizard” and click “Add”.

"Add New Item" Dialog Box
Figure 2. “Add New Item” Dialog Box.

After clicking the “Add” button the “Macro Wizard” dialog box will be displayed, as shown in Figure 3.

"Macro Wizard" dialog box
Figure 3. “Macro Wizard” dialog box.

You can select the type of the macro to create using the “Macro Type” drop-down list box:

  • Macro attribute – to create a top level macro.
  • Expression level – to create an expression level macro.

If you select the type “Expression level” then all controls except option Define Syntax Extension are disabled, because you can’t apply the concepts of compilation phase (Macro Phase) and scope (Valid On) to expression level macros. In contrast if you select the type “Macro Attribute” then these controls are available.
“Macro Phase” and “Valid On” set the corresponding properties of the attribute Nemerle.MacroUsageAttribute.
Let’s go through their values in details.

Macro Phase

The drop-down list box “Macro Phase” specifies compilation phase, on which the macroattribute is called. There are three phases:

  • BeforeInheritance – the macro runs before the start of the first phase of the typing, that is before inheritance dependencies and interface implementations calculation. This phase is optimal for changing the base class of a class declared as part of the project being compiled, for adding interfaces to be implemented by a class, and for adding methods or other members. However, it is awkward to analyze types in this phase, because the information about type members and even about type dependencies is not available yet. You can also create new types in this phase, because they can easily participate in the inheritance of other types.
  • BeforeTypedMembers – the macro runs after the inheritance dependencies and interface implementations calculation, but before type members binding (parameters and return value of methods, properties, fields). This phase is appropriate when you want to change or add type members.
  • WithTypedMembers – macro runs after binding type members. At this phase, you cannot change the structure of types or change the type inheritance list. However, you can still create new types or add new type members at this phase (although such operations are better carried out in the previous phases). It will be problematic to add interface implementations at this phase, because interface member validations are performed before this phase. In general, a good idea is to use this phase to generate member bodies. At this point almost all information about types declared in the project is already known, and it can be used in generating the bodies.

Phase selection
The choice of the phase at which the macro is working, depends on many conditions. Typically, it’s best to choose the earliest possible phase, since the changes made by the macro will be seen during the following phases, which will allow macros to use the outcome of each other’s labor. However, it is sometimes beneficial to put the macro to the latest phase WithTypedMembers, even if it does not require type information. For example, it may be necessary to conceal the results of the macro from the other macros that work on earlier phases.

One macro that runs on several phases
Several macros may have the same name if they have different running phases or different applications. It is often advantageous instead of using one big macro that makes all the work create a smaller number of macros and thus break the task into subtasks. Thus, a useful technique is to split the macro into two (with the same name) running at different phases. The macro that runs at phase BeforeInheritance, can add interface implementations, new members (for example, for added interfaces), and the macro that runs at phase WithTypedMembers, can generate an implementation for methods that have been added at the BeforeInheritance phase.
Suppose we want to create a macroattribute that is applicable to a class and would add IDisposable interface implementation, in which the Dispose method is automatically called for all fields and for the base implementation of the method IDisposable.Dispose, if any. In this case it is convenient to create two macros. The first one is applied to a class and runs at BeforeInheritance phase. It would add the IDisposable interface and the Dispose method (leaving the body blank). The second one is applied to the method and runs at WithTypedMembers phase. It would implement the Dispose method body added by the first macro. The first macro can simply add a second meta-attribute to the created Dispose method, leading to its expanding in the future. We’ll walk through this example in the chapter “Example of IDisposable interface implementation”.

Valid On

The drop-down list box “Valid On” allows you to specify the type of AST element, to which the macroattribute can be applied. Possible values:

  • Assembly – the attribute is applicable to an assembly (i.e. is a global attribute).
  • Type – the attribute is applicable to any type, including interfaces, enumerations, options , and delegates. But the values Module, Struct, Enum, Interface, Delegate can’t be used as macroattribute type. If you would like to create a macroattribute, which can be applicable to, say, only to interfaces, you can find out what the type is compiled through a the instance of TypeBuilder passed as a parameter.
  • Method – the attribute is applicable to methods and property accessors.
  • Field – the attribute is applicable to type fields.
  • Property – the attribute is applicable to properties.
  • Event – the attribute is applicable to events.
  • Parameter – the attribute is applicable to method parameters.

All values except “Type” correspond to the values of the MacroTargets enumeration. The value of “Type” corresponds to the MacroTargets.Class. The wizard uses the caption “Type” because it better reflects the reality, because this value determines the applicability to any type, and not just to a class.

Parameters of a macro

A macro can, and sometimes simply must, have parameters. Macro parameters can be required and optional (additional, custom).

Required parameters of a macro

A top level macro of types: Type, Method, Property, Field, and Event must have parameters that pass the references to Builders of entities to which the macro is applied. The macro can get information about the entities or modify them. The table 1 contains the list of types of required parameters of top level macros (macroattributes). Types are in the order in which parameters should go.
Expression level macros and of assembly type don’t have the additional parameters because they can’t be applied directly to any entity.

Table 1. Required parameters of top level macros.

Applies to BeforeInheritance BeforeTypedMembers WithTypedMembers
Assembly
Type TypeBuilder TypeBuilder TypeBuilder
Method TypeBuilder, ClassMember.Function TypeBuilder, ClassMember.Function TypeBuilder, MethodBuilder
Field TypeBuilder, ClassMemberField TypeBuilder, ClassMemberField TypeBuilder, FieldBuilder
Property TypeBuilder, ClassMember.Property TypeBuilder, ClassMember.Property TypeBuilder, PropertyBuilder
Event TypeBuilder, ClassMember.Event TypeBuilder, ClassMember.Event TypeBuilder, EventBuilder
Parameter TypeBuilder, ClassMember.Function, PParameter TypeBuilder, ClassMember.Function, PParameter TypeBuilder, MethodBuilder, TParameter

Where:

  • ClassMember.Function – AST of a function or a method.
  • ClassMember.Property – AST of a property.
  • ClassMember.Field – AST of a field.
  • ClassMember.Event – AST of an event.
  • PParameter – AST of a parameter.
  • TypeBuilder – for forming a type. Using it you can access to its AST (AstParts property), modifiers and attributes, inheritance information (at phase BeforeTypedMembers and later) and to information about the member types (at phase WithTypedMembers).
  • MethodBuilder – for forming a function. Using it you can access information about the function types (types of parameters and return value type, constraints, etc.).
  • FieldBuilder – for forming a field. Using it you can access information about the field type.
  • PropertyBuilder – for forming a property. Using it you can access information about the property types (simple or indexed).
  • EventBuilder – for forming an event. Using it you can access information about the event types.
  • TParameter – for forming a parameter. Using it you can access information about the parameter types.

Additional macro parameters

In addition to the required parameters, the macro can have any number of additional parameters.
The types of parameters of macros are quite diverse. But no matter what type an additional parameter has, the value to be passed through it will be obtained by transforming the AST of the expression, which the user of the macro uses as a parameter.

WARNING
Pay attention to this fact! It is very important for the understanding of the macro.
It is often that beginning macro authors think about macros as about functions (possibly generalized), which can lead astray such as they can come to conclusion they can pass some runtime value to a macro.
It should be clearly understood that a macro exists at compile time and it can get only compile time entities. And only AST and type information exist at compile time. Only AST can be passed to a macro as a parameter.
AST can contain some data in the form of literals (strings, numbers). This means that a macro can get also strings and numbers. However, the macro can’t get a date because it can’t be represented as a literal. In this case, the date can be represented as a string or as an AST of the date constructor. This macro should analyze the passed parameters and extract the necessary information.
It is simply not possible to pass to a macro some object from the program being compiling. If you have such a desire then you did not understand the essence of macros. Once again – the macro generates or modifies code of the target program, and it is not a part of the target program.

Below is a list of types allowed for additional parameters of macros:

  • PExpr – AST of an expression. This is the most common type of a macro parameter, so it is the default. If you do not specify a type of a macro parameter, it will be PExpr by default. Through a parameter of this type you can pass any single expression that can be processed by the Nemerle parser (also including using other macros).
  • Token – token (lexeme). For details see lexical analysis in Wikipedia. Examples of tokens are: the keywords (for, match, class), identifiers (such as variable names), operators, parentheses, literals (“some string”, “42”). Tokens in Nemerle 1.x “fold” into the groups. There are following types of groups: LooseGroup, QuoteGroup (quasi-quote), SquareGroup (brackets), BracesGroup (braces) and RoundGroup (parentheses). LooseGroup combines a sequence of tokens. The others combine the tokens that are enclosed in one or another type of brackets. Thus, describing a parameter as a Token, you do not get a token, but (possibly) a hierarchy of tokens. This allows you to create complex syntax extensions that do not obey the Nemerle syntax, but complying Nemerle lexical rules. More information about the tokens you can find in the series of articles “Nemerle Macros – an advanced course.”
  • params array[PExpr] – an array of parameters. Allows you to organize the macro with a variable number of parameters. The parameters array must be the last parameter of the macro, and may occur only once.
  • params list[PExpr] – a list of parameters. Similar to the array of parameters in Nemerle/C#, but it uses the type of list[T] for storing the values. This type is more convenient, since in the quasi-quoted expressions lists appear exactly of the type of list[PExpr]. In all other respects this type is similar to the previous type (params array[PExpr]).

There are literal values (literal constants such as “some string” or “42”) in a list of possible AST expressions (as in other languages). They can be used for sending to a macro some information. For example, if the macro requires the path to some file, the parameter can take the form of a string literal. The macro can analyze its parameters and show an error message if the parameter doesn’t contain a literal expression. However, such analysis requires writing some code of little use. In order to avoid this useless work the following macro parameter types were introduced: bool, byte, decimal, double, float, int, long, sbyte, short, string, uint, ulong, and ushort.

WARNING
Notice that the parameters of these types can not accept expressions other than literal constants. You can not pass the value of a function (it is simply impossible for macros), or a calculation.
In addition, the use of these parameters may lead to the problem. If you want the macro to display diagnostic messages about the content of these parameters, it will not be able to specify the exact location of the parameter, as it is passed to the macro as a value, and location information is lost.

As mentioned above, the macro with the same name can be introduced twice or even thrice, if each of its definitions apply at the different phases of compilation. In this case the list of additional macro parameters must be identical for all definitions (a list of required parameters is determined by the phase and location of the macro). However, this is not necessarily for the same name macros that are applied to different entities. Thus, you can declare the same name macros but different parameter lists, if one macro is applied to the field and the other to the property.
As for the default values of the parameters of the macro that is implemented for a number of phases, they can be different for different phases. However, if the default values for macro parameters are set for one of the phases, it is necessary to ensure that some values are set for these parameters of macros that work on the other phases. Otherwise, the compiler generates the error message stating that there is no macro with the required number of parameters. In the example of the Disposable macro below, you can see how different default values can be set for the different phases of the same macro.

Simple examples of macros

Even the best explanation may be unclear if it is not backed up by examples. Let us practice.
Open Visual Studio 2010 and create a new solution named «MacroIntro» and a new project based on the template «Macro Library» named «MacroIntroLibrary» (see Figure 4). To do this, select the menu «File», click «New Project …» or press Ctrl+Shift+N.

Figure  4. Creating solution «MacroIntro»
Figure 4. Creating solution «MacroIntro».

Add one more project to the solution (menu «File\Add\New Project …» or «Add\New Project …» from the context menu of the solution) based on the template «Console Application». Give the name «Test» to the project.
Add to the project “Test” a reference to the project «MacroIntroLibrary» (see Figures 5 and 6).

Figure 5. Adding references to MacroIntroLibrary
Figure 5. Adding references to MacroIntroLibrary

NOTE
In fact, it would be reasonable to add macroassemblies to the «Macro Reference» branch. The assemblies added to the «Macro Reference» are passed to the compiler exactly as macroassemblies. This prevents the usage of types that are in macroassemblies in the target projects. But there is a nasty bug in the current implementation of the Nemerle integration for VS 2010 and VS 2008. The bug is that assemblies added to “Macro Reference” are not included to the project dependencies list. This leads to the fact that after any change of macroassembly the project that references it is not automatically updated and recompiled. This is not critical when you use external macroassemblies but very inconvenient during your own macros development. Therefore, during debugging it is better to has the reference in the “Reference” section (at least until correction of this shortcoming).

Figure 6. «Add Reference» dialog
Figure 6. «Add Reference» dialog.

After completing this procedure references in your “Test” project should look like shown in Figure 7.

Figure  7. “References” list of the "Test" project after adding a reference to “MacroIntroLibrary”
Figure 7. “References” list of the “Test” project after adding a reference to “MacroIntroLibrary”

Rebuild the solution in order to make “MacroIntroLibrary” macros available in the “Test” project.

WARNING
Since macros are compiler extension modules they must be compiled before use. IDE monitors referenced macroassemblies. If one of the assemblies is changed, IDE reads all the assemblies, which makes the new macros available. Thus, to “see” changes in macroassemblies, they just need to be recompiled.

So, we finished the preparatory stage. Now we can begin to experiment with macros.
A macro does not have to change or generate something. It can be used as a tool of code analysis or project monitoring. Let’s make the first macro that will display information about the available project types.
To do this, use the macro-wizard (as described above) and create a macroattribute ProjectInfo in the MacroIntroLibrary project. It should be applied to the assembly («Macro Type» = «Macro Attribute», «Valid On» = «Assembly») and work at phase BeforeInheritance. Additional parameters are not necessary.
At the end of wizard you will see a file ProjectInfo.n with the following content:

using Nemerle;
using Nemerle.Collections;
using Nemerle.Compiler;
using Nemerle.Compiler.Parsetree;
using Nemerle.Compiler.Typedtree;
using Nemerle.Text;
using Nemerle.Utility;

using System;
using System.Collections.Generic;
using System.Linq;


namespace MacroIntroLibrary
{
  [MacroUsage(MacroPhase.BeforeInheritance, MacroTargets.Assembly)]
  macro ProjectInfo()
  {
    ProjectInfoImpl.DoTransform(Macros.ImplicitCTX(), )
  }
  
  module ProjectInfoImpl
  {
    public DoTransform(typer : Typer, ) : void
    {
      Macros.DefineCTX(typer);
      // TODO: Add implementation here.
      ;
    }
  }
}

NOTE
Current version of VS integration doesn’t support IntelliSence within macros. Therefore, macro-wizard generates a module with a single method, which is called from the macro. IntelliSence is available within this method.

HINT
The definition of the method and the macro above contains an “extra” comma. Nemerle ignores the last comma if there are no any other tokens after it except a closing parenthesis. The Wizard uses this feature to simplify its implementation. You can safely remove this extra comma. Nothing will change.

Description of the macro begins with the keyword «macro» and consists of a header and a body. In addition, you can apply custom attributes to the macro (but really only MacroUsage attribute is needed).
Formal macro syntax is as follows:

Macro      = Attributes? "macro" Name "(" Parameters? ")" "{" Expression "}";
Name       = Identifier;
Parameters = Parameter ("," Parameter)* ","?;
Parameter  = Identifier (":" Type)? ("=" Default);
Type       = "PExpr" | "Token" | "params array[PExpr]" | "params list[PExpr]" 
           | "bool" | "byte" | "decimal" | "double" | "float" | "int" | "long" 
           | "sbyte" | "short" | "string" | "uint" | "ulong" | "ushort";

Default    = "<[" PExpr "]>" // a code quote
           |  Literal;       // a number or a string
Expression = PExpr;

The code generated by macros should be clear to you. Only two lines require explanation.
ImplicitCTX is a standard library macro that allows you to retrieve the current typer (an object of type Nemerle.Compiler.Typer). Typer – is a compiler object that runs (expands) a macro. A macro can get additional project information or consume compiler services through this object.
The string:

      Macros.DefineCTX(typer);

defines so called compiler context. In fact it defines a local immutable variable of the Typer type. This variable is used by some compiler services. Later we will show in what cases this line is necessary.

Getting information about project AST.

Change the body of the ProjectInfoImpl.DoTransform method as shown below:

Macros.DefineCTX(typer);

def types = typer.Manager.NameTree.NamespaceTree.GetTypeBuilders( // 1
              onlyTopDeclarations=true);

foreach (type in types)
{
  def partsCount = type.AstParts.Length;

  Message.Hint(type.AstParts.Head.Location, // 2
    $"Type: '$(type.FullName)'  Parts count: $partsCount");

  foreach (ast in type.AstParts with i) // 3
  {
    Message.Hint(ast.Location, // 4
      $"  Part $(i + 1) of type '$type'  $(ast.GetMembers().Length)");

    foreach (memberAst in ast.GetMembers()) // 5
    {
      Message.Hint(memberAst.NameLocation, $"    $memberAst"); // 6

      match (memberAst) // 7
      {
        | <[ decl: ..$_attrs $_name(..$parameters) : $retType $_body ]> => // 8
          foreach (param in parameters with i)
            Message.Hint(param.Location, $"      Parameter $(i + 1): $param"); // 9

          Message.Hint(retType.Location, $"      Return type: $retType"); // 10
        
        | _ => ()
      }
    }
  }
}

This code outputs information about the project types and its members. The information is displayed in the “Output” window and in the “Error list”. By double clicking to the line with this information you can go to the corresponding declaration in the source code.
Let’s examine in detail what this macro does.
Typer is perhaps the major object that is (implicitly) passed to a macro. Typer is created for typing methods, functions or expressions. A Typer has the Manager property. This property holds a reference to the object (ManagerClass), which contains the general information about the whole compiled project. Among other things, this object stores the type tree. You can query the type tree of the list of TypeBuilders. This is done by calling the GetTypeBuilders method. The onlyTopDeclarations parameter allows you to specify whether you want to receive only top-level types, or all types including nested.

NOTE
Typing is the process of calculating the additional attributes that are types. In the process of typing the compiler evaluates the real value of the program constructs. For example, it specifies that one expression is a function call, and the other is a constructor call. In the process of typing the resolution of overloads and other ambiguities takes place. At the end of the typing process the compiler generates so-called typed AST. For expressions the typed AST is defined as TExpr type.
More information about the type safety can be found in the forth part of the article «Nemerle macros – extended course».

TypeBuilder (from namespace Nemerle.Compiler) is an object that describes the types defined in the project (decoded from code within the project, or created by macros). A TypeBuilder is created for each type that is defined in a project. Even if a type is a partial-type (i.e., it consists of several parts), still only one TypeBuilder object is created in which the information about all the parts is stored.
Thus, line 1 receives all the project types except the nested.
Only the AST is available at the BeforeInheritance phase. AST of types is available through the AstParts property of a TypeBuilder. AstParts maintains a list of TopDeclaration from the Nemerle.Compiler.Parsetree namespace. TopDeclaration is a variant type that defines AST of the top-level declarations (types).

NOTE
The AstParts has the name and stores the list because types in Nemerle can consist of more than one definition. These types are called partial-types. Each part of a partial type must contain the “partial” modifier in its definition. AstParts maintains a list of such parts. If the type is not partial, then this property contains one item. In addition to the property AstParts there is the Ast property in the TypeBuilder . It stores the AST of the first encountered by the compiler part. However, it is always better to use the AstParts property in macros, in order to not generate (by chance) code that does not handle partial types.

Line 2 displays the compiler informational message (Hint) that shows the type name and number of its parts.

NOTE
A Nemerle macro can interact with the compiler. In particular, it may display messages: error messages, warnings and informational messages (hints/tips). Messages appear in the log of the compiler and in the IDE. The IDE messages are displayed in the “Error list” window and as well as in the form of a wavy colored underscores, when you hover mouse on which tool tips with descriptions appear.
Since macros are executed at compile time, the messages from them will be displayed as if it were regular compiler messages.
The direct purpose of messages is to display (to the user) the information about the problems encountered during compilation (and thus during execution of the macros). But they can also be used for debugging purposes (as is done in this example).
For displaying messages, the Message module is used (from the Nemerle.Compiler namespace).
To display error messages, you can use the methods Error, FatalError, and FatalError2 (actually FatalError is a macro, but it’s not important).
To display warnings, use the Warning method. To display informational messages, the Hint and HintOnce are used. The latter displays a message with the specified text or number once.
As a parameter to all methods, you can pass a string with the message and the location (the Location structure from the Nemerle.Compiler namespace). You can also specify the number for warnings and informational messages. Message number is a positive integer that is displayed with the message, and which can be used by the user to block this message.

In this example, messages are used to display information to the user. If the message is double clicked the cursor moves to the corresponding place in the code. Thus we will not only see the messages describing the program structure, but we also can jump directly to definitions.
The expression "type.AstParts.Head " returns the first item from the list of parts of the AST type. And the Location property returns the same name structure that describes the location of the piece of code.

NOTE
The Location structure (from the Nemerle.Compiler namespace) determines the file and the location in it. This structure stores the file name (File), the file index (FileIndex) (in Nemerle each file opened during compilation or in the IDE maps to the specified index), the starting line (Line), the starting column (Column), the ending line (EndLine), and the ending column (EndColumn). You can also get the starting (Begin) and the ending (End) text points (TextPoint structure from the Nemerle.Compiler namespace).
This structure is used to store the location of the code. Almost any entity describing Nemerle code has the Location property and other properties of the Location type. This allows you to specify the exact coordinates of the problem location. This, in turn, makes life easier for the end user. Try to always specify the location in the messages. If you do not specify a location in the message, the compiler will use the so-called location stack that is filled by the compiler. But the compiler is not familiar with the details of the problem identified by a macro, and pushes only the location of the expanding macro.
You can modify the locations stack. There is the locate macro from the Nemerle.Compiler.Util namespace for this.

Thus, the message displayed in the line 2 will point to the first part of the type declaration. The message will contain the type name and number of the parts, of which it is composed.
Enumeration of all the parts that make up the type takes place in line 3.

HINT
The structure with “index name” in the foreach loop enables you to not only get elements of the enumerated list but also indexes of these elements. This is useful if it is necessary to generate sequence names, or when working with arrays. Such things are possible because foreach is also a macro, and extension of its functionality is a relatively simple task that anyone can do. Good extensions after discussion with the community are added to the standard macro library. You can make your suggestion by creating a pool request on github and by posting corresponding message on English forum or on RSDN.

Line 4 displays information about the parts that make up the type. This will allow you to jump to their definitions. From the message you will be able to know how many members defined in each of the parts. GetMembers method is used for this purpose. It returns the ClassMember list. ClassMember is a variant data type that describes AST of the type members (methods, properties, fields, events, and nested types).
The same method is used in the line 5, where enumeration of the members defined in the current part is performed.
Line 6 outputs a description of each of the members. Simple member cast to string gives an intelligible description of its header. The NameLocation property is used here as a location. This is done in order to not overlap an underline that is displayed for any message and underlines of messages for method parameters (messages for which are generated later).
Lines 7 and 8 perhaps are the most interesting. AST of the type members is parsed here using pattern matching. It uses a template in the form of quasi-quote (line 8).

HINT
Possibility to parse code using a high-level DSL such as quasi-quotes and mainly the ability to analyze compiled code at all are the main thing that distinguishes Nemerle macros from text code generation, often used in the competing approaches.

Details of quasi-quotes will be described further. Now you need to understand and remember only that quasi-quotes allow you to design (create) and perform decomposition (analyze) code at a very high level.
Quasi-quote on the line 8 defines a template for a method declaration. AST of any method will be matched with this template. Information from splices (constructs of the forms $x and ..$x) is used to obtain information about parts of the AST, which is then displayed in the messages. Please note that this information is also branches of AST. $x and ..$ differ in that $x is used for matching or analyzing a single expression, but ..$x for a list of expressions. For more information about splices see the “Splices” section.

HINT
The above quote has splices which contain names starting with the underscore character (_attrs, _name and _body). The names in the splices of the match operator introduce local variables. If a local variable is declared but is not used, the Nemerle compiler shows the warning about it. There is a convention in Nemerle that states that names beginning with an underscore, are ignored by the compiler (it does not display a warning about their non-use).
If you use a wildcard symbol (i.e. a single underscore) as a variable name, the compiler won’t only show a warning about non-used variable, but also it won’t generally introduce the variable.
In the above example, names with underlines are used only in order to help you to understand what corresponding splices mean. In real quotes, it is better to use the wildcard character. However, this technique (specifying names that begin with an underscore) can be used for debugging purposes (to see their values in the debugger).

WARNING
Note that in the above quote you cannot just omit “unnecessary” splices. If you do this, the compiler will not be able to understand what you are trying to match, or it will take the default value that is not appropriate for you. We will talk about what and how you can omit in quotes later in the section about quasi-quotation.

Line 9 outputs information about the parameters. Line 10 – about the return value.
To apply this macro edit the file Main.n of the Test.nproj project as follows:

using Nemerle.Collections;
using Nemerle.Text;
using Nemerle.Utility;

using System;
using System.Collections.Generic;
using System.Console;
using System.Linq;

using MacroIntroLibrary; // 1

[assembly: ProjectInfo] // 2

public partial class TestClass { }

module Program
{
  Main() : void
  {
    WriteLine("Hi!");
    _ = ReadLine();
  }
}

public partial class TestClass
{
  public Method(param1 : int, param2 : System.Int32, param3 : Int32) : int
  {
    param1 + param2 + param3
  }
}

Line 1 opens the namespace in which the macro is declared.
There is a macro call in the line 2. It is easy to see that it is similar to custom attributes (hereinafter simply “attributes”) in C# and Nemerle. But, unlike simple attributes, this one does not lead to the generation of attributes in the assembly meta information, but only runs the macro.
Compile the solution. You will see informational messages in the “Error List” and “Output” windows. The following is the contents of the “Output” window.

C:\...\Main.n(14,1): warning : hint: Type: 'TestClass' Parts count: 2
C:\...\Main.n(14,1): warning : hint:   Part 1 of type 'TestClass'  0
C:\...\Main.n(25,1): warning : hint:   Part 2 of type 'TestClass'  1
C:\...\Main.n(27,10): warning : hint:     Function: public Method(param1 : int, param2 : System.Int32, param3 : Int32) : int;
C:\...\Main.n(27,17): warning : hint:       Parameter 1: param1 : int
C:\...\Main.n(27,31): warning : hint:       Parameter 2: param2 : System.Int32
C:\...\Main.n(27,54): warning : hint:       Parameter 3: param3 : Int32
C:\...\Main.n(27,72): warning : hint:       Return type: int
C:\...\Main.n(16,1): warning : hint: Type: 'Program' Parts count: 1
C:\...\Main.n(16,1): warning : hint:   Part 1 of type 'Program'  1
C:\...\Main.n(18,3): warning : hint:     Function: static Main() : void ;
C:\...\Main.n(18,12): warning : hint:       Return type: void

As mentioned above, double click on these records activates the place in the code where the constraction is declared.

Getting type information

From the last example you learned how to get the AST information. But in a statically-typed language (which is Nemerle) it is often not enough to get only the AST information. Sometime it is necessary to get type information of different project elements.
There is two ways to do it:

  • Manual binding of types of different AST parts.
  • Use the last compilation phase MacroPhase.WithTypedMembers.

Manual binding

At first let’s try the first option. To do it we need to change the macro after the line number 8:

| <[ decl: ..$_attrs $_name(..$parameters) : $retType $_body ]> => // 8
  foreach (param in parameters with i)
  {
    def paramType = typer.BindType(param.Type);
    Message.Hint(param.Location, $"      Parameter $(i + 1): $(param.Name) : $paramType"); // 9
  }

After project recompilation the information about the method parameters will change to:

C:\...\Main.n(27,10): warning : hint:     Function: public Method(param1 : int, param2 : System.Int32, param3 : Int32) : int;
C:\...\Main.n(27,17): warning : hint:       Parameter 1: param1 : int
C:\...\Main.n(27,31): warning : hint:       Parameter 2: param2 : int
C:\...\Main.n(27,54): warning : hint:       Parameter 3: param3 : int

As you can see, the parameter types became look the same (only “int” rather than int, System.Int32, and Int32). But the main thing is not that we got a nice view, but getting a type reference instead of the AST. Nemerle types like .Net types are represented by objects. You can perform different operations with these objects You can be sure that the type is the type you expected, or check whether the type can be a subtype of (or supertype) of another type.

WARNING
.Net types can’t be manipulated in Nemerle macros. There are several reasons:
1. Types which are manipulated by the compiler may not be yet converted into .Net types. Hence, it simple is not possible to manipulate them.
2. The Nemerle type system is richer than .NET type system. Nemerle supports types such as: variant types, tuples, functional types, type aliases.
3. Macros are expanded during the so-called process of typing. At that time the types may not be still inferred or inferred partially. An attempt to query the system type can lead to problems in the type inference algorithm.

Nemerle types are expressed as the two types (excuse the tautology) defined in the Nemerle.Compiler namespace:

  • TypeVar – variable of types. Used during type inference.
  • FixedType – fixed type. Used to specify exact types. All of the TypeVars are eventually converted to FixedType. Actually FixedType is the successor of TypeVar, so everywhere where TypeVar is required you can pass FixedType. FixedType is a variant type that describes such specialized types as tuples and arrays, as well as the “usual” types (which have a slightly inaccurate name FixedType.Class).
    BindType method’s return value is TypeVar.

HINT
Take advantage of VS integration to pry types. If you hover the mouse cursor over some identifier in your code you’ll see a tool tip which will provide a detailed description of the type of the selected identifier. So, if you point the mouse at the BindType method, you will see the tool tip shown in Figure 8. Pointing the mouse cursor to the names of the types (highlighted with sea wave color) will show additional hints that provide information about these types.
“Walk” in the macro code to find out what types have some elements of the code.

Figure 8. A tool tip with a type description
Figure 8. A tool tip with a type description.

This allows you to associate partially specified types or even a type not specified at all (yes, yes, even so can happen in Nemerle). Partially specified type is a type in which the type name and/or some parameters are specified with wildcard character “_”. If the whole expression declaring the type is specified with only the wildcard character, the BindType method will return the so called fresh type variable (a new object of TypeVar whose property IsFresh is equal to true).
The following are examples of partially specified types:

Dictionary[_, int] // the first type parameter is not defined (TKey)
Dictionary[_, _] // both type parameters are not defined

// the type parameter of the nested type is not defined
Dictionary[string, System.Collections.Generic.List[_]]
                               
_? // no type is specified, but the constraint is that it must be a Nullable-type

The main feature of TypeVar is that it supports the so-called unification (as in Prolog language). Unification allows, on the one hand, to verify type compatibility (you can do this by using the methods TryUnify, TryRequire and TryProvide), and on the other hand, impose restrictions on the types. This will be discussed later.
Instead of the BindType method you can use the BindFixedType method. It returns FixedType and does not allow you to use the wildcard “_” character.
In the example above, the BindType method can be replaced by the BindFixedType method without any visible effects. But if you later change the declaration of the parameter so that the type is not specified explicitly:

  public Method(param1 : int, param2 : System.Int32, param3 = 42) : int
  {
    param1 + param2 + param3
  }

instead of binding the error will be generated:

C:\...\Main.n(27,54): error : type inference not allowed here

Usage of the WithTypedMembers compilation phase

To demonstrate how to use the WithTypedMembers compilation phase lets create one more macro named ProjectTypeInfo. Use the Macro wizard again to create the new macro. But this time choose “WithTypedMembers” instead of “BeforeInheritance” in the “Macro Phase” drop-down list. Leave all other settings as in the previous case. As a result, you will have the following prototype for the macro:

using Nemerle;
using Nemerle.Collections;
using Nemerle.Compiler;
using Nemerle.Compiler.Parsetree;
using Nemerle.Compiler.Typedtree;
using Nemerle.Text;
using Nemerle.Utility;

using System;
using System.Collections.Generic;
using System.Linq;

namespace MacroIntroLibrary
{
  [MacroUsage(MacroPhase.WithTypedMembers, MacroTargets.Assembly)]
  macro ProjectTypeInfo()
  {
    ProjectTypeInfoImpl.DoTransform(Macros.ImplicitCTX(), )
  }
  
  module ProjectTypeInfoImpl
  {
    public DoTransform(typer : Typer, ) : void
    {
      Macros.DefineCTX(typer);
      // TODO: Add implementation here.
      ;
    }
  }
}

Place the following code to the DoTransform method:

Macros.DefineCTX(typer);
      
def types = typer.Manager.NameTree.NamespaceTree.GetTypeBuilders( // 1
        onlyTopDeclarations=true);

foreach (type in types)
{
  Message.Hint(type.Location, // 1
    $"Type: '$(type.FullName)' : $(type.BaseClass)");

  def bindingFlags = BindingFlags.DeclaredOnly | BindingFlags.Instance
    | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;
        
  def members = type.GetMembers(bindingFlags); // 2
        
  foreach (member in members)
  {
    Message.Hint(member.NameLocation, $"    $member"); // 3

    match (member)
    {
      | method is MethodBuilder =>
        def header = method.Header;
        foreach (param in header.Parameters with i) // 4
          Message.Hint(param.Location,
            $"      Paramert $(i + 1): $(param.Name) : $(param.Type)"); // 5
              
        Message.Hint(header.ReturnTypeLocation,
          $"      Return type $(header.ReturnType)"); // 6

      | _                       => ()
    }
  }
}

Now, replace the line 2 in the file main.n of the project Test. Nproj with:

[assembly: ProjectTypeInfo] // 2

and recompile the project.
In the “Output” window you will see the following:

C:\...\Main.n(14,1): warning : hint: Type: 'TestClass' : object
C:\...\Main.n(14,1): warning : hint:     constructor TestClass..ctor() : TestClass
C:\...\Main.n(14,1): warning : hint:       Return type: void
C:\...\Main.n(27,10): warning : hint:     method TestClass.Method(param1 : int, param2 : int, param3 : int) : int
C:\...\Main.n(27,17): warning : hint:       Parameter 1: param1 : int
C:\...\Main.n(27,31): warning : hint:       Parameter 2: param2 : int
C:\...\Main.n(27,54): warning : hint:       Parameter 3: param3 : int
C:\...\Main.n(27,69): warning : hint:       Return type: int
C:\...\Main.n(16,1): warning : hint: Type: 'Program' : object
C:\...\Main.n(18,3): warning : hint:     method Program.Main() : void
C:\...\Main.n(18,12): warning : hint:       Return type: void

As you can see, the result is very similar to that was generated by the ProjectInfo macro, but code of the ProjectTypeInfo code looks more like a use of reflection (System.Reflection).
If you look carefully, you can find differences in the output. Thus, a string declaring the “Method” method looked like this:

Function: public Method(param1 : int, param2 : System.Int32, param3 : _ ) : int;

and now it looks like this:

method TestClass.Method(param1 : int, param2 : int, param3 : int) : int

This happened because all types in the output are bounded, and you are not working with an AST, but with a typed tree. Suchwise, a type now does not have the form of an AST (PExpr), but it is a FixedType (i.e. the param.Type property in the line 5 has such type).

WARNING
The article uses the new names of properties and types. Former names do not meet the .Net standards. If you are using older versions of the compiler and VS integration the code of the examples may not be compiled.

In addition, ProjectTypeInfo does not generate messages for individual parts of a partial type. This happens because the higher-level abstractions where a type is represented by a single type (TypeBuilder) are used. But nothing prevents you from using the AstParts property. The AST information is still there. It is also available on the WithTypedMembers phase. The only thing is that any modifications of an existing AST an this phase will not be taken into account by the compiler. But you can still add new types (as AST) and their members. This way they will be immediately typed and added to the proper places.
The only thing left untyped at the WithTypedMembers phase is the code of method bodies (and other members). This allows you to use the WithTypedMembers phase to generate code of the method bodies using the information about types.
Let us conduct another experiment. Replace the “Method” method definition in the test project with:

public Method(param1 : int, param2 : System.Int32, param3 = 42) : int

and compile the project alternately with the two macros. If you now look at the declaration of the third parameter of the method it will be different.
The ProjectInfo project infers it as follows:

Parameter 3: param3 : ?

and the ProjectTypeInfo macro as follows:

Parameter 3: param3 : int

A question mark indicates that a type variable is free (its type is not inferred). This happens because the type of a parameter that does not have an explicit type declaration is specified by the compiler as “_”. Logical, that when the BindType method binds it this leads us to getting a variable of the free type.
In principle, you can complicate the ProjectInfo macro in a such way that it would analyze a default value of the parameter and try to infer its type. But it’s a lot easier to use the WithTypedMembers phase and the corresponding API.

Disposable macro

WARNING
There is a well-founded opinion that the design pattern IDisposable offered by Microsoft for use is controversial. The main issue is a violation of one of the empirical rules of OOPSRP (single responsibility principle). There is a good article about this pattern IDisposable: What Your Mother Never Told You About Resource Deallocation on codeproject.com.
However, implementation of IDisposable design pattern is perfect for learning. Besides the Disposable macro (which implements this pattern) can be used without violating the SRP. Another problem mentioned in the article – the complexity of implementation – is offset by the fact that Disposable hides it by taking responsibility for the correctness of the pattern implementation to itself.

This section will describe the Disposable macro. This macro implements design pattern IDisposable, proposed to be used by Microsoft (right in the interface description and, for example, here). A disadvantage of this pattern is that a lot of code should be written (or copied) for its implementation. And most of those who copied the code can hardly understand its meaning. As a result of applying this pattern, a class that implements this pattern must implement the IDisposable interface, and provide (although not necessarily) a method to clean up resources (usually the name of this method is Dispose but it may be different).

The only goal of providing this macro here is demonstration of techniques that can be used in the development of Nemerle macros.
To help you understand what is happening here is a blank of this pattern:

using System;
using System.Console;

class Base : IDisposable
{
  private mutable _disposed : bool;

  public Dispose() : void
    implements IDisposable.Dispose
  {
    Dispose(true);
    GC.SuppressFinalize(this);
  }

  protected virtual Dispose(disposing : bool) : void
  {
    unless (_disposed)
    {
      when (disposing)
      {
        // Free managed resources.
        // Unsubscribe from the events and delete the current object from different lists.
      }
      // Cleaning unmanaged resources.
      // Reset fields that refer to big objects 
      // (or all mutable reference fields).
      _disposed = true;
    }
  }

  //If the object owns unmanaged resources implement:
  protected override Finalize() : void
  {
    Dispose(false);
  }
}

class Derived : Base, IDisposable
{
  private mutable _disposed : bool;

  protected override Dispose(disposing : bool) : void
  {
    unless (_disposed)
    {
      when (disposing)
      {
        // Free managed resources.
        // Unsubscribe from the events and delete the current object from different lists.
      }
      // Cleaning unmanaged resources.
      // Reset fields that refer to big objects 
      // (or all mutable reference fields).
      _disposed = true;
    }
    base.Dispose(disposing);
  }

  //If the object owns unmanaged resources implement:
  protected override Finalize() : void
  {
    Dispose(false);
  }
}

module Program
{
  Main() : void
  {
    Derived().Dispose();
    (Derived() : IDisposable).Dispose();
    using (obj = Derived())
      ();
  }
}

As you can see the code is fairly long, flowery, and very boring. It is not very desirable to write it manually from time to time. Meanwhile neither OOP nor FP paradigms allow to satisfactorily encapsulate this pattern.
In such cases, macros are extremely helpful.
The development of such a macro should be started with writing the code sample which must eventually become the result. Places that should differ in specific implementations can be marked with comments. For example, the Dispose method of the previous listing might look like this:

protected virtual Dispose(disposing : bool) : void
{
  unless (_disposed)
  {
    when (disposing)
    {
      // Generate call to IDisposable.Dispose() of fields
      //  whose type implements IDisposable.
      // The code to clean up managed resources provided through 
      // the parameters of the macro.
    }
    // The code to clean up unmanaged resources provided through 
    // the parameters of the macro.
    // Resetting mutable fields defined in the class.
    _disposed = true;
  }
}

This approach allows you to avoid problems with misinterpretation of macros, and simplifies the development of strategies for the macro implementation.
The above example shows that the pattern is applied to the class. Therefore, it is reasonable to make a macro as a macroattribute applied to the class.
Since the macro have to add IDisposable to the list of interfaces implemented by the class, the macro should work at phase BeforeInheritance. You can’t use the later phases because it is not possible to extend the list of implemented interfaces or change the base class at that point.
Pattern implementation is different for the classes that implement IDisposable for the first time, and classes that inherit from classes already implement this pattern. Hence, we must somehow distinguish these cases. There may be the following ways:

  1. You can simply create two different macros.
  2. You can add a macro parameter that will determine which option to generate.
  3. You can check if there is a method with the signature “protected virtual Dispose (disposing: bool) : void” in the base class. bool) : void».
  4. You can check if the base class implements the IDisposable interface.

This example will use the latest option (check the implementation of the interface IDisposable in the base class), but in other cases different options may be more appropriate. The reasons for choosing the last option in this case are the following:
#it will demonstrate the types handling technique within the macros;
#this decision leaves fewer opportunities to be mistaken for those who will use the macro.

HINT
In order to consolidate your knowledge implement the other ways on your own.

To check whether a base class implements IDisposable, the macro should run at phase not below than BeforeTypedMembers.
The macro can automatically determine the list of fields that implement IDisposable, and generate the code that will call the Dispose method for each. However, the generated IDisposable.Dispose implementation can also unsubscribe from the events or perform other activities except calling IDisposable.Dispose for nested managed resources. The macro cannot guess what is required in each particular case. Hence such code should be passed to the macro as a parameter. The same is true in the case of releasing unmanaged resources. However in would be better if you won’t mix handling of managed and unmanaged resources in one place (see the note at the beginning of the chapter).
It is better to perform code generation for IDisposable.Dispose method calls of the nested objects at the WithTypedMembers phase.
I think you noticed that there is a serious problem. Phase requirements for running the macro conflict with each other. What to do?
In such cases, you need to split the macro to two macros or more. It is possible to create a macro with a different name or create a macro with the same name, but used at the different phase or applied to the different kind of AST. In our case it is reasonable to do both.
Adding IDisposable to the list of implemented interfaces can be done in the macro Disposable running at the BeforeInheritance phase. Selecting a pattern depending on the base class analysis can be done in the macro Disposable, but working at the BeforeTypedMembers phase. And code generation of the IDisposable.Dispose method calls for the nested objects, can be done on the separate macro ImplementDisposeFields that will work at WithTypedMembers phase and will be applied to the Dispose method. Why to the method and how it will be activated? More on this later.
For now, add to the MacroIntroLibrary project a folder and add to it the following files: Disposable_BeforeInheritance.n, Disposable_BeforeTypedMembers.n и ImplementDisposeFields.n. Their content and explanations are listed below.

- Disposable_BeforeInheritance.n

using Nemerle;
using Nemerle.Compiler;
using Nemerle.Compiler.Parsetree;

namespace MacroIntroLibrary
{
  [MacroUsage(MacroPhase.BeforeInheritance, MacroTargets.Class)]
  macro Disposable(typeBuilder : TypeBuilder, disposeManaged = null, 
                   disposeUnmanaged = null, disposeName = null)
  {
    _ = disposeManaged; _ = disposeUnmanaged; _ = disposeName;
    typeBuilder.AddImplementedInterface(<[ System.IDisposable ]>); // 1
  }
}

As mentioned above, the Disposable macro is divided into two phases. Since this is a single macro (its parts can not be called separately), the list of parameters of both parts of the macro must be the same. However, since the parameters are not processed at this phase “null” can be used as the default values.
In line 1 the interface IDisposable is added to the list of interfaces implemented by a class. Note that it is added in the form of AST (i.e. not typed form). Subsequently the compiler will go through the list and will bind stored expressions with their real types. If you specify the name partially:

<[ IDisposable ]>

then the compiler will try to resolve it using the list of namespaces opened at the moment of forming of the quasi-quote (i.e. in our case, given in the file of the macro). At this time type search will take place in the tree of types of the project in which the macro is applied. This behavior of the Nemerle compiler is handy but not obvious to newcomers. Therefore, you should pay special attention to this.
It is also not obvious that the list of namespaces is stored directly inside the macro-assembly as a set of strings. No checks are not done at that time (because types can be not available). Checks will be made when the compiler will do binding of the names (i.e., in our case, during the binding of types of the implemented interfaces). Meanwhile, if in the list of stored namespaces there will not be the namespace in which the desired type is declared, or if the project will not reference the library in which the type is declared then the error will be displayed. Such errors are not easy to find. So be careful when specifying the type. It is better to specify full type names (at least until you are confident in macros).

- Disposable_BeforeTypedMembers.n

using Nemerle;
using Nemerle.Collections;
using Nemerle.Compiler;
using Nemerle.Compiler.Parsetree;
using Nemerle.Compiler.Typedtree;
using Nemerle.Text;
using Nemerle.Utility;

using System;
using System.Collections.Generic;
using System.Linq;

namespace MacroIntroLibrary
{
  [MacroUsage(MacroPhase.BeforeTypedMembers, MacroTargets.Class)]
  macro Disposable(typeBuilder : TypeBuilder, disposeManaged = <[ () ]>, 
  /* 2 */          disposeUnmanaged = <[ () ]>, disposeName = <[ Dispose ]>)
  {
    DisposableImpl.DoTransform(Macros.ImplicitCTX(), typeBuilder, 
      disposeManaged, disposeUnmanaged, disposeName)
  }
  
  module DisposableImpl
  {
    public DoTransform(typer : Typer, typeBuilder : TypeBuilder, 
     disposeManaged   : PExpr, 
     disposeUnmanaged : PExpr, 
     disposeName      : PExpr) : void
    {
      Macros.DefineCTX(typer); // 3

      def needUnmanagedDispose = !(disposeUnmanaged is <[ () ]>); // 4
      def iDisposableType = <[ ttype: System.IDisposable ]>;      // 5
      def needOverride = typeBuilder.BaseClass.TryRequire(        // 6
                           iDisposableType);
      def defineMember(ast) { typeBuilder.Define(ast) }                 // 7
      //def defineMember(ast) { _ = typeBuilder.DefineWithSource(ast) } // 8
      
      defineMember(<[ decl: 
        [RecordIgnore] private mutable _disposed : bool; ]>); // 9
      
      def disposeIDisposableFields = 
        Macros.NewSymbol("DisposeIDisposableFields"); // 10
      
      defineMember(
        <[ decl: [ImplementDisposeFields] // 11
                 private $(disposeIDisposableFields : name)() : void { } ]>);
      
      def disposeImple =
        if (needOverride) // 12
          <[ decl:
            protected override Dispose(disposing : bool) : void
            {
              unless (_disposed)
              {
                when (disposing)
                {
                  //Generate calls to Dispose of IDisposable-fields.
                  $(disposeIDisposableFields : name)(); // 13
                  // Insert the code to clean up managed resources 
                  // provided by user.
                  $disposeManaged; // 14
                }
                // Insert the code to clean up unmanaged resources 
                // provided by user.
                $disposeUnmanaged;
                // TODO: Reset all mutable fields.
                base.Dispose(disposing);
                _disposed = true;
              }
            } ]>
        else
          <[ decl: 
            protected virtual Dispose(disposing : bool) : void
            {
              unless (_disposed)
              {
                when (disposing)
                {
                  //Generate calls to Dispose of IDisposable-fields.
                  $(disposeIDisposableFields : name)();
                  // Insert the user code to clean up managed resources
                  $disposeManaged;
                }
                // Insert the user code to clean up unmanaged resources
                $disposeUnmanaged;
                // TODO: Reset all mutable fields.
                _disposed = true;
              }
            } ]>;

      defineMember(disposeImple);

      when (needUnmanagedDispose) // 15
        defineMember(<[ decl:
          protected override Finalize() : void { Dispose(false); } ]>);
          
      unless (needOverride)
      {
        def disposeMethodName = 
          match (disposeName) // 16
          {
            | <[ $(disposeMethodName : name) ]> => disposeMethodName
            | _ => // 17
              Message.Error(disposeName.Location, "Expected simple name");
              Name("Dispose")
          };
        defineMember(<[ decl:
          public $(disposeMethodName : name)() : void
            implements IDisposable.Dispose
          {
            Dispose(true);
            GC.SuppressFinalize(this);
          } ]>);
      }
    }
  }
}

Line 2 sets the real default values for the parameters. Let’s have a look at them and also at the parameters themselves.

  • disposeManaged = <[ () ]> – user code, which will clean up managed resources; by default it returns void-literal which means it does nothing.
  • disposeUnmanaged = <[ () ]> – user code to clean up unmanaged resources. Void-literal used by default means code doing nothing.
  • disposeName = <[ Dispose ]> – the name of the public method to be called to free resources.

In the line 4 the value of the disposeUnmanaged parameter is matched with the pattern <[ () ]>. The needUnmanagedDispose variable becomes equal to «true» if the disposeUnmanaged parameter contains a value other than void-literal.

NOTE
The operator «is» in Nemerle is a kind of pattern matching. Therefore, it can match sophisticated values.

In the line 5 there is a special type of quasi-quotes – a quote of a type. Unlike other quotes which are converted to an untyped AST, a quote of this kind is converted into a call that returns the type defined in the quote. Syntactically, this kind of quotes differs by prefix “ttype:”.

NOTE
Quote of a type can return a fixed type (FixedType), and a variable of a type – TypeVar (if specified by pattern “_”). Definition of the type can contain a wildcard character “_” as a parameter of the type, which leads to the creation of partially fixed type. These types can be matched with other types, and parameters of the types in this case will be ignored.

In the line 6 the resulting in the previous line definition of the IDisposable type is matched with the type of the base class. If the base class implements IDisposable (or derived from it, in the case of a class), the method TryRequire returns true. Try prefix, indicates that this method does not change the types and does not generate error messages in case of failure (as do methods without this prefix). This is useful when you want to just test the type compatibility, as in our case. You can find more details about working with types in the forth part of the article “Nemerle macros – extended course”.

WARNING
Once again draw your attention to the fact that usage of the System.Type and everything associated with it (for example, the typeof() operator) in macros must not be done. To work with types use only types FixedType and TypeVar (and its associated APIs).

In the line 7 there is declaration of the defineMember function which will be used to add AST of generated class members to the type. This function simply uses the typer.Define method. This function is introduced only to demonstrate the ability to debug the code generated by macros. If you comment out the line 7 and uncomment line 8 then members of the class will be added using the DefineWithSource method. This will lead to source code generation for the generated AST, and the compiler will associate a location in the AST with it. As a result debugging of the generated code will be available. Error messages will also be pointed to it (in case of errors).
The use of the DefineWithSource method is a good technique for debugging complex macros.
The next line (9) adds the private field _disposed to the class. The field is added in the form of AST. Creation of the AST is performed by using quasi-quotes with the “decl:” prefix. This kind of quotes you have already seen above. It generates the AST for types and their members. All kinds of quotes and specifics of their application will be fully discussed in the section about quasi-quotes.
Note that there is a RecordIgnore attribute applied to the added _disposed field. This is a macroattribute from the standard library. It suppresses generation of the parameter for a field if the Record macroattribute is used (which automatically generates a constructor for all fields of the type).
In line 10 a unique name is generated for a method that will contain the code for calling the IDisposable.Dispose methods for all the fields declared in the class. A unique name is required in order to prevent unintentional calls to this method from the written code, and to avoid accidental name conflict. The name generated by the NewSymbol function, is formed on the template: _N_stringPassedAsParameter_uniqueNumber. For example, in line 10 generated name could be like: _N_DisposeIDisposableFields_3681 (real name found by decompiler). The NewSymbol function returns special object of the Name type. This object stores a “hygienic” information. Wherever the compiler demands names, it expects exactly this type.

NOTE
Nemerle offers so-called hygiene of the macros. This guarantees that the names generated by a macro, do not overlap with the names of the other macros or the source code. But when you work with the top level AST (of types and their members) hygiene is turned off. This happens because otherwise the code that generates the top level AST would be very poorly readable. But it can be done manually as described above.

In the line 11 a method with the unique name generated in the previous line is created and added. It is necessary to say more about this method. Automatically generated code will be placed to the body of this method. This code will call IDisposable.Dispose method for all the type fields which implement the IDisposable interface and that are declared exactly in this class.
A separate method is required because it is more convinient to create calls to IDisposable.Dispose method of the nested objects at the WithTypedMembers phase, but the described macro Disposable runs at the previous phases.
Besides, this solution allows to demonstrate you an interesting technique – transfer of control to another macro by generating an AST of the call to it. As you can see, the method added in the line 11 is marked with the attribute “ImplementDisposeFields”. This is an macroattribute declared in the same macroassembly. It will be discussed below.
The macro ImplementDisposeFields is invoked when the compiler will type the method that is declares with the attribute “ImplementDisposeFields”. Since the macro “ImplementDisposeFields” works at the WithTypedMembers compile phase it can use the information about the types.
Another feature of this quote is that there is a usage of the following expression in it:

$(disposeIDisposableFields : name)

By default, it is possible to pass to a splice (i.e. the expression proceeding after the $ sign in quotes) only a value of the PExpr type. However, using the $ syntax (expression : xxx) it is also possible to pass to slices some other types. The possible values of “xxx” and corresponding types can be found below, in the section about quasi-quotation. For now it is enough to know that “name” indicates that the value of the type Name is passed to a splice (just the type that has the disposeIDisposableFields variable).
Line 12 chooses one of two quasi-quotes based on the value of the needOverride. Since quotes generates AST, their resulting values can easily be placed in variables, passed as parameters, etc.
If needOverride is equal “true”, quasi-quote creating an override of the method is used, otherwise quasi-quote defining a new virtual method is used. These methods are nearly identical, except that the methods have different modifiers, and the first quote also contains the code of calling the Dispose method of the base class. In the section about quasi-quotation, you will see how to get rid of such duplication.

NOTE
In the code of the methods there are comments like “TODO: Reset all mutable fields”. It is a place where it would be desirable to insert generation of the code that will reset mutable reference fields declared in the class. I didn’t provide such code intentionally. Implement it yourself to consolidate the material.

An interesting feature of the quotes defining the method "Dispose (disposing: bool) : void», is that there are splices it them.
In the line 13 generation of the call to the method which name is in the disposeIDisposableFields variable (generated in line 11) takes place. There is also usage of the “name” splice here.
In the line 14 the code that is passed to the disposeManaged macro as a parameter is inserted. By default it contains the value «()» (void-literal). When substituting the void-literal in the code the compiler just does not do anything. Therefore void literal is useful for unification of code generation or, as in this case, to set defaults when no need to perform any action.
There is nothing interesting in the line 15. It just adds the Finalize method if needUnmanagedDispose is true. This causes the finalizer is added only if the user of the macro has assigned the value to disposeUnmanaged and this value is different from the void-literal.
In line 16 creation of a name for the public method takes place. Its value is passed to the macro as the disposeName parameter. The default value is <[ Dispose ]>, which means that the method name would be «Dispose» unless something else would be specified.
Not all expressions can be used as a name of the method but only expressions that define a name. The above-described splice “name” as well as other types of quasi-quotes works in two directions. You can use it to create an AST as well as to recognize it. Thus, the pattern <[ $(disposeMethodName : name) ]> recognizes an expression that references the name (object Name). By that the variable disposeMethodName will be set to the value of the name.
If the disposeName parameter is an expression that is different from the name, then the line 17 will be executed resulting in showing an error message to the user, and the value of the variable disposeMethodName will be the Name(“Dispose”).

Let’s Sum It Up.

This macro evaluates the base type of the class to which it is applied, and whether it implements the IDisposable interface, generates the IDisposable pattern implementation for the base class or for the inheritor.
The code that calls Dispose of the nested objects is generated separately in the ImplementDisposeFields macro, which is described below. A method with a unique name and with the attribute “ImplementDisposeFields” is added to the code of the class, which subsequently leads to invocation of this macro.

- ImplementDisposeFields.n

using Nemerle;
using Nemerle.Collections;
using Nemerle.Compiler;
using Nemerle.Compiler.Parsetree;
using Nemerle.Compiler.Typedtree;
using Nemerle.Text;
using Nemerle.Utility;

using System;
using System.Collections.Generic;
using System.Linq;

namespace MacroIntroLibrary
{
  [MacroUsage(MacroPhase.WithTypedMembers, MacroTargets.Method)]
  macro ImplementDisposeFields(typeBuilder : TypeBuilder, 
    method : MethodBuilder)
  {
    ImplementDisposeFieldsImpl.DoTransform(Macros.ImplicitCTX(), 
                                           typeBuilder, method)
  }
  
  module ImplementDisposeFieldsImpl
  {
    public DoTransform(typer : Typer, typeBuilder : TypeBuilder, 
      method : MethodBuilder) : void
    {
      Macros.DefineCTX(typer);
      
      def iDisposableType = <[ ttype: System.IDisposable ]>;
      
      def isMemberTypeImplementIDisposable(member : IMember) : bool // 1
      {
        member is IField // 2
        && member.GetMemType().TryRequire(iDisposableType) // 3
      }
      
      def members = typeBuilder.GetMembers(BindingFlags.DeclaredOnly // 4
                                         | BindingFlags.Instance 
                                         | BindingFlags.Public 
                                         | BindingFlags.NonPublic)
                               .Filter(isMemberTypeImplementIDisposable); 
      
      def exprs = members.Map(m => // 5
        <[ (this.$(m.Name :  usesite) : System.IDisposable)?.Dispose() ]>);
      
      method.Body = <[ { ..$exprs } ]>; // 6
    }
  }
}

In the line 1 there is a declaration of the local function that checks whether a member is a field (line 2), and whether its type is a subtype of IDisposable, i.e. it implements the IDisposable interface (line 3).
In the line 4 a list of the class members is retrieved. Calling to Dispose() makes sense only for the instance fields that are defined in the current class, therefore the DeclaredOnly and Instance flags are passed to the GetMembers method.
The list produced by the GetMembers method is additionally filtered using the function isMemberTypeImplementIDisposable.
In the line 5 the transformation (map) of the list of members of the type that is compatible with the IDisposable to expressions invoking their IDisposable.Dispose method takes place.
Since these fields can contain null their methods are invoked using the macro “?.”. In this case, it will prevent access to a null pointer.
Besides the splice $ is used in this line (m.Name : usesite). The splice allows you to specify the name as a string (m.Name returns a string). Usesite modifier tells the compiler that it should expect a string and that string should be “colored” in the color of usage location (which is the context in which the macro is called).
As mentioned above, the colors allow to separate names created in different contexts. This ensures hygienic of macros. And splice “usesite” allows to break the hygienic in a controllable way and to refer to the name from the external context. There are four kinds of modifiers: name, global, dyn, and usesite. They will be described in the section about quasi-quotes.
In the line 6 the body of the method to which the macro is applied is replaced with the code calling the Dispose methods generated in the line 5.
A splice like “..$x” applied inside a code block (braces), is expanded to a list of expressions, separated by commas. Thus, the method body will become code of calling the Dispose methods of the nested objects (objects that are referenced by the fields of the current object). The parameter of a splice like “..$x” should be a list of expressions (list[PExpr]). Indeed this is the type of the exprs variable.
You may ask: “Why are only the fields processed rather than fields and auto-properties?” This is because there are corresponding fields for auto-properties in a class, and they are included in the list of Members returned by GetMembers.
To test the macro described above, create another test console project named “DisposableTest”. Place the following code to the Main.n file:

using System;
using System.Console;
using System.IO;

using MacroIntroLibrary;

[Disposable(WriteLine("Dispose managed resources."), 
            WriteLine("Dispose unmanaged resources."), 
            Close)
]
class Base
{
}

[Record]
[Disposable]
class Derived : Base
{
  private FileStream : FileStream;
  private Str        : string;
  public Reader      : TextReader { get; private set; }
}

module Program2
{
  Main() : void
  {
    
    def x = Derived(null, "", null);
    x.Close();
    _ = ReadLine();
  }
}

p.
If you now compile the solution and decompile the resulting assembly DisposableTest.exe using for example Reflector you will see something like the following:
internal class Derived : Base
{
  // Fields
  [IgnoreField]
  private bool _disposed;
  [CompilerGenerated, DebuggerBrowsable(DebuggerBrowsableState.Never)]
  private TextReader _N_Reader_3702;
  private readonly FileStream FileStream;
  private readonly string Str;

  // Methods
  public Derived(FileStream fileStream, string str, TextReader reader)
  {
    this.FileStream = fileStream;
    this.Str = str;
    this.Reader = reader;
  }

  private void _N_DisposeIDisposableFields_3682()
  {
    IDisposable fileStream = this.FileStream;

    if (fileStream != null)
      fileStream.Dispose();

    IDisposable disposable2 = this._N_Reader_3702;

    if (disposable2 != null)
      disposable2.Dispose();
  }

  protected override void Dispose(bool disposing)
  {
    if (!this._disposed)
    {
      if (disposing)
        this._N_DisposeIDisposableFields_3682();

      base.Dispose(disposing);
      this._disposed = true;
    }
  }

  // Properties
  public TextReader Reader
  {
    [CompilerGenerated]
    Get { return this._N_Reader_3702; }
    [CompilerGenerated]
    private set { this._N_Reader_3702 = value; }
  }
}

As you can see, our macro works very well.

Quasi-quotation

Quotes

You have already met the quasi-quotes above and should generally understand what it is. Below you will see a more formal description of them.
So, quasi-quote is a code quote that is converted by the compiler into a kind of AST (object model of the code). Nemerle supports seven kinds of quotes (and this number can grow over time). Six of them have prefixes, and one does not have a prefix. A quote without a prefix returns the PExpr type (from the Nemerle.Compiler namespace) and describes a Nemerle expression. The other prefixes are used to generate top-level AST (types, their members, local functions, and their parameters) and to describe some special cases. For more information, see the section “quotes-quotes with a prefix”.
All kinds of quasi-quotes, except prefixed with ttype, can generate an AST as well as be used as a pattern in pattern matching (in the match operator). This allows to uniform construction of AST and its decomposition.

NOTE
The possibility of using quasi-quotes to decompose code is one of the most powerful capabilities of Nemerle macro system and differentiates Nemerle from languages that support meta programming as strings handling (such as Ruby, Python, D).

Below each kind of the quotes will be discussed individually.

Splices

Prefix “quasi” in the title “quasi-quote” was added because the quotes could be not monolithic. In quotes you can use special constructs – splices that let you insert to the quote a piece of external AST or an object of another type.
There are two types of splices: $x and ..$x, where “x” is an identifier or any expression in brackets that returns a value to be inserted.
..$x differs in that it allows inserting a list instead of a single value. The splice ..$x can be used only inside the brackets (braces, square or round). For example, the following expression is not a valid quote:

<[ ..$x ]>

The following are examples of using the quote ..$x.
Generation of a block of expressions:

def expressions = [<[ def a = 1; ]>, <[ def b = 2; ]>, <[ a + b ]>];
<[ { ..$expressions } ]>

This example generates a block of code as follows:

{
  def a = 1;
  def b = 2;
  a + b
}

A tuple generation:

def expressions = [<[ 42 ]>, <[ "String" ]>, <[ a + b ]>];
<[ (..$expressions) ]>

This example generates a block of code as follows:

(42, "String", a + b)

HINT
If in the preceding example, replace the parentheses with square brackets, then a list will be generated instead of a tuple. However, such a list would not be typed, since lists can’t contain elements of different types.

Quotes can be used for code decomposition. For example, since a function call in Nemerle is the function name and a tuple of arguments successively going after each other, we can match AST of the function call using a pattern like this:

match (expr : PExpr)
{
  | <[ Test(..$args) ]> => // args contains a list of arguments
  | _                   => ...
}

NOTE
The expression “expr: PExpr” is an enforcement of the type. In Nemerle an enforcement of a type can be anywhere in an expression. In this case the type enforcement is used for better understanding of which type has the «expr» expression. Nemerle compiler compares the real type of the expression and the type of the enforcement. If the types are the same, the compiler does not react to it. If the types are not compatible, but there is an implicit cast, the compiler substitutes it. If the types are not compatible, and there is no an implicit cast, the compiler generates an error message.

In this case, the pattern introduces a local variable args binded to the list of the parameters. Note that the function name is fixed. This means that the pattern will be matched only to a function called “Test”. At the same time the number of calling parameters is not important, since the splice “..$args” will be matched to a list of expressions of any length (including empty).
The splice $x allows you to insert or match a single expression (PExpr). Let’s say that in order to match any calls in the previous example, not just the “Test” function calls, you can use the splice $x in the pattern:

match (expr : PExpr)
{
  | <[ $func(..$args) ]> => // matches to any call
  | _                    => ...
}

The splice $x has a number of varieties that allow to use expressions which types are different from PExpr. They have syntax of type enforcement – $(“expression”: «type of the quote»). The following kinds of quotes are supported:

  • name – allows to create or match a name (Object of the Name type).
  • usesite – allows to create or match a name as a string. The generated name is substituted with the “color” used in the outer scope. When used as a pattern it matches to any name.
  • dyn – allows to create or match a name as a string. The generated name is substituted with the special “color”, which is interpreted by the compiler as an order to use the same name with any color available in the current context. When used as a pattern, likewise usesite, matches to any name. In fact this type of quotes is a full bypass of the macro hygiene, so use it with caution.
  • global – allows you to create (but not match) a name as a string. The generated name is substituted with the “color” of the global context. It makes sense to use this kind of quotes when you want to access the same names from various macros.
  • typed – allows you to insert (but not match) an untyped AST (PExpr) into typed expression (TExpr from the Nemerle.Compiler.Typedtree namespace). Typed AST can be obtained by typing PExpr using the typer.TypeExpr method.
  • byte, sbyte, short, ushort, int, uint, long, ulong, string, bool, char, float, double, decimal, enum allow to create or match literals (constants) of the corresponding types.

An example of using the name and usesite to generate names you can see above. And here is how this might look in patterns (if you paste this code into the Disposable macro):

match (disposeName)
{
  | <[ $(name : dyn) ]> => Message.Hint($"$name ($(name.GetType()))");
  | _ => ()
}

Will output at the compilation:

Main.n(7,2): warning : hint: Close (System.String)
Main.n(13,2): warning : hint: Dispose (System.String)

The same effect can be achieved by using usesite instead of dyn in the above example.
If you change dyn to name, the output will be changed as follows:

Main.n(7,2): warning : hint: Close (Nemerle.Compiler.Parsetree.Name)
Main.n(13,2): warning : hint: Dispose (Nemerle.Compiler.Parsetree.Name)

And here is the use of literal quotations:

def message : string = "a string";
<[ def message : string = $(message : string) ]>

Another example:

def ast : PExpr = <[ $("Строка" : string) ]>;
match (ast)
{
  | <[ $(str : string) ]> => Message.Hint($"$str ($(str.GetType()))");
  | _ => ()
}

Will output at the compilation:

Main.n(7,2): warning : hint: String (System.string)

WARNING
A common mistake of novice macro authors is attempting to substitute in a splice an expression which type is different from the PExpr without specifying the type of the quote. Unfortunately, the diagnostic messages issued as a result are very poor. Be careful, defining splices.

Places where using Name is required

It is impossible to list all valid expression quotes since it is infinite set. Macros can introduce new syntactic constructs that are also available for constructing and deconstructing using quasi-quotes. It is important to understand the general principle of quotation, which is that a quote can contain any expression, which subexpressions can be defined in place or using a splice. The same applies to using quasi-quotes in pattern matching.
However, there are some features that you need to know to don’t feel discomfort writing macros in Nemerle.
This section will tell about one of such features. Almost anywhere in quotes when you insert a splice, it is expected that it will contain an expression of type PExpr. However, there are places where the compiler requires the Name type. This is related to Nemerle grammar.
A sample of such places is an expression “member access”:

obj.Member

and the name of the method in its declaration is:

public MethodName() : void { }

If you want to create a quote that specifies the member name to access the member or the method name in its declaration, instead of an expression of the type PExpr, you must use an expression of the type Name.
For example, for the following code:

def nameExpr = <[ Member ]>;
_ = <[ obj.$nameExpr ]>;

the compiler generates the error message (and not the most intuitive):

(48,8): error : each overload has an error during a call:

(48,8): error : overload #1, "constructor Nemerle.Compiler.Parsetree.PExpr.Member..ctor(obj : Nemerle.Compiler.Parsetree.PExpr, member : Nemerle.Compiler.Parsetree.Splicable) : Nemerle.Compiler.Parsetree.PExpr.Member" fail because:
(48,13): error : in argument #2 (member), needed a Nemerle.Compiler.Parsetree.Splicable, got Nemerle.Compiler.Parsetree.PExpr.Ref: Nemerle.Compiler.Parsetree.PExpr.Ref is not a subtype of Nemerle.Compiler.Parsetree.Splicable [simple require]

(48,8): error : overload #2, "constructor Nemerle.Compiler.Parsetree.PExpr.Member..ctor(loc : Nemerle.Compiler.Location, obj : Nemerle.Compiler.Parsetree.PExpr, member : Nemerle.Compiler.Parsetree.Splicable) : Nemerle.Compiler.Parsetree.PExpr.Member" fail because:
(48,8): error : wrong number of parameters in the call, needed 3, got 2

There are the following options to correct this error:
1. Substitute an object of the type Name in the quote, and also change the quote type to Name.
For example:

def nameExpr = Macros.UseSiteSymbol("Member");
<[ obj.$(nameExpr : name) ]>

The name can be generated in different ways. You can use UseSiteSymbol или NewSymbol functions. You can define the name in a quasi-quote and get access to it through the field «name»:

def nameExpr = <[ Member ]>.name;
<[ obj.$(nameExpr : name) ]>

NOTE
PExpr does not have the field “name”. This field belongs to a subtype PExpr.Ref which defines a reference to a name. But actually a quasi-quote doesn’t generate a PExpr but a specific type of PExpr. Thus in the above expression the type of the quasi-quote is indeed PExpr.Ref. This allows you to access its members. At the same time even IntelliSence works.

In general, it doesn’t matter how you will get the object of type Name. It is important that you have to use a quote of type Name.
However, it is not always convenient to manage a name of the type Name. If you have a string, you can handle it as a name using quotes of the types usesite, dyn, or global. For example:

<[ obj.$("Member" : usesite) ]>

This example is similar in functionality to the example where the function Macros.UseSiteSymbol was used.

Creating a qualified name

Sometimes you don’t need to get just a simple name such as “String”, but a qualified name, as for example, the name of the type “Nemerle.Collections.Hashtable”. There is no such type of quotes in Nemerle that allows to get such name from string. Therefore, to convert a string into a PExpr that describes the qualified name, you have to use the function PExpr.FromQualifiedIdentifier:

def str = "Nemerle.Collections.Hashtable";
def expr = PExpr.FromQualifiedIdentifier(typer.Manager, str);
Message.Hint($"'$expr' ($(expr.GetType()))");

This code displays:

... 'Nemerle.Collections.Hashtable' (Nemerle.Compiler.Parsetree.PExpr+Member)

However, this method would be useless if the string will contain a more complex expression.
For example, a fairly common task is to create an expression that describes a type (or a return value or a constructor call). Sometimes it is possible that definition of the type is already exists in the form of a string. To convert it to an expression you can use the Nemerle parser. It is available as a function. Here’s how it might look like:

def typeAsString = "Hashtable[int, List[string]]";
def parsedType   = MainParser.ParseExpr(typer.Env, typeAsString, true);
Message.Hint($"'$parsedType' ($(parsedType.GetType()))");

This code will display:

... 'Hashtable[int, List[string]]' (Nemerle.Compiler.Parsetree.PExpr+Indexer)

Type reference

Sometimes you can have a type reference instead of the string representation. In this case, it does not make sense to convert it to a string and then parse. In this case it is better to use a quote of the type “type”. Here’s how it might look like:

def typedType = typeBuilder.GetMemType();
def expr = <[ $(typedType : typed) ]>;
Message.Hint($"'$expr' ($(expr.GetType()))");

This code will output:

... 'Base' (Nemerle.Compiler.Parsetree.PExpr+TypedType)
... 'Derived' (Nemerle.Compiler.Parsetree.PExpr+TypedType)

GetMemType method returns the so-called non-incarnate type, i.e. the type whose type parameters are not substituted with type variables. It can be used only within members of the same type. If you need a type with free type parameters or with type parameters that are associated with other types, you should use the function GetFreshType:

def typedType = typeBuilder.GetFreshType();
unless (typedType.args.IsEmpty)
  _ = typedType.args.Head.Unify(typer.InternalType.Int32);
def expr = <[ $(typedType : typed) ]>;
Message.Hint($"'$expr' ($(expr.GetType()))");

This code will output:

... 'Base' (Nemerle.Compiler.Parsetree.PExpr+TypedType)
... 'Derived[int]' (Nemerle.Compiler.Parsetree.PExpr+TypedType)

provided that the Derived class is a generic type with one type parameter.
I’ll explain what’s going on in this example. The GetFreshType function returns a value of type FixedType.Class. This is the type that describes user-defined types (classes and structures). It has two main fields: tycon of type TypeInfo from Nemerle.Compiler namespace (the best way to get it is to use property TypeInfo) and field args of type list[TypeVar]. The args field stores type variables corresponding to the type parameters of the type defined by the tycon field. The expression typedType.args.Head returns a type variable corresponding to the first type parameter.
Type variables can be unified with other types. If you unify it with some FixedType, then, if successful, the value of the type variable will be the value of the fixed type. Therefore, the expression:

typedType.args.Head.Unify(typer.InternalType.Int32);

makes the first type parameter equal to Int32. Field InternalType, available in many objects of the compiler refers to the type InternalTypeClass, which declares a lot of reference to the often used types. Instead of it you can use the quasi-quote of type «ttype», as already shown in the Disposable example.
You can find more details about behavior of the function Unify (and also Require, etc.) in the forth part of the article «Nemerle macros – extended course».

Quasi-quotes with a prefix

As mentioned earlier, a quasi-quote without a prefix defines an expression (PExpr). If you want to get an AST that is different from the expression, then you need to use special prefixes. You’ve already seen the use of quotes with the prefixes «decl» and «ttype» in the examples above. It is time to get acquainted with all sorts of prefixes.
Here is a list of the prefixes supported in quasi-quotes in Nemerle and their meanings:

  • decl – generates an AST for the top-level declarations. Types or their members, except for instances of variant types.
  • fundecl – generates an AST for a local function declaration.
  • case – generates an AST of an instance of the match operator.
  • parameter – generates an AST of a parameter of a function.
  • variant_option – generates an AST for an instance of a variant type.
  • ttype – returns a type (FixedType or TypeVar, depending on the contents of the quote).

The syntax for all types of quasi-quotes will be discussed in a separate section devoted to the description of quotes. In the meantime let’s focus on the practical use of the quasi-quotes.

Quotes with the «decl» prefix

Using quasi-quotes with the prefix «decl» it is possible to generate and decompose a fair number of AST types:

  • Types: classes, structures, enumerations (enum), variant types, delegates, type synonyms (type).
  • Type members: methods, constructors, properties (including auto-properties), fields, events, enumeration instances.

Quasi-quote with the prefix «decl» can not construct or decompose instances of variants (because their grammar is in conflict with the grammar of enumeration instances), namespaces, and «using» constructs (since they do not map to AST in Nemerle).
The general format of the quasi-quotes with the prefix «decl»:

<[ decl: «attributes and modifiers» «declaration of a type or a member» ]>

“Declaration of a type or a member” is different for each type of quoted entity, and can support syntactic extensions. «attributes and modifiers» always describe the modifiers (public, private, partial, abstract, …) and user-defined attributes (including macro-attributes).
If a quasi-quote does not define attributes and modifiers, then default modifier values will be used (for example, internal modifier for top-level types and private for members).

WARNING
Omitting modifiers and attributes in a quasi-quote used as a pattern you have to be careful, since this pattern will be matched only with an AST, which also do not specify modifiers and attributes.

To match to an AST with any set of modifiers and attributes use the splice of type “..$x”. Note that the splice will be matched to modifiers and attributes at the same time. Thus, the construct of the form:

<[ decl: [Record] ..$attrs class MyClass { } ]>

is not valid. If there is a splice of type ..$x, then modifiers (public, abstract, etc.) defined in the quote code will be ignored (but in the future versions of the compiler will probably generate an error message):

def attrs = AttributesAndModifiers(NemerleAttributes.Private, []);
<[ decl: ..$attrs public class MyClass { } ]> // public will be ignored

So do not define modifiers and attributes as a splice and as a code at the same time.
If you need to programmatically set the values of modifiers or attributes, use the splice ..$x used at the beginning of a quote, and specify access modifiers in the first parameter of a constructor of the class AttributesAndModifiers.

NOTE
The AttributesAndModifiers class defines access modifiers and user-defined attributes. Modifiers are specified as a bit mask using enumeration NemerleModifiers.

To demonstrate how to programmatically set the access modifiers, let’s rewrite the part of the implementation of the Disposable macro responsible for adding the method «Dispose (disposing: bool) : void». In the original implementation the quote is simply duplicated.

def modifiers = // 1
  if (needOverride) NemerleModifiers.Protected | NemerleModifiers.Override
  else              NemerleModifiers.Protected | NemerleModifiers.Virtual;
def attributesAndModifiers = AttributesAndModifiers(modifiers, []); // 2
def baseCall = if (needOverride) <[ base.Dispose(disposing); ]> else <[ () ]>;
                 
defineMember(
    <[ decl:
      ..$attributesAndModifiers Dispose(disposing : bool) : void
      {
        unless (_disposed)
        {
          when (disposing)
          {
            //Generate calls to Dispose of IDisposable-fields.
            $(disposeIDisposableFields : name)();
            // The code provided by user to clean up managed resources
            $disposeManaged;
          }
          // The code provided by user to clean up unmanaged resources
          $disposeUnmanaged;
          // TODO: Reset all mutable fields.
          $baseCall;
          _disposed = true;
        }
      } ]>);

As you can see, we managed to get rid of the duplication of the quasi-quote by adding two splices (they are marked) to the quote code. The second splice is not of interest, since it is just inserting a piece of code (calling Dispose of the base class), but let’s consider the first one in detail.
The line 1 in the above example creates the method modifiers. Modifiers are defined by the NemerleModifiers enumeration. This enumeration is marked with attribute System.Flags, which allows to use it as a bit mask (a combination of flags). Here is a definition of the enumeration:

[System.Flags]
public enum NemerleModifiers
{
  | None            = 0x00000
  | Public          = 0x00001
  | Private         = 0x00002
  | New             = 0x00004
  | Protected       = 0x00008
  | Abstract        = 0x00010
  | Virtual         = 0x00020
  | Sealed          = 0x00040
  | Static          = 0x00080
  | Mutable         = 0x00100
  | Internal        = 0x00200
  | Override        = 0x00400
  | Struct          = 0x01000
  | Macro           = 0x02000
  | Volatile        = 0x04000
  | SpecialName     = 0x08000
  | Partial         = 0x10000
  | Extern          = 0x20000
  /// field is immutable, but compiler overrides it and can assign something
  | CompilerMutable = 0x40000 

  | VirtualityModifiers = New | Abstract | Virtual | Override
  | OverrideModifiers   = Abstract | Virtual | Override
  | AccessModifiers     = Public | Private | Protected | Internal
}

The line 2 creates an object of type AttributesAndModifiers defining modifiers and custom attributes. The first constructor parameter specifies the modifiers, and the second – the list of custom attributes.
Custom attributes are specified as AST expressions. Among the custom attributes there can be expressions defining usual custom attributes as well as macroattributes. Macroattributes binding takes place at the each phase of compilation. If in the current visibility scope there is a macro with the appropriate compilation phase, scope, and name, then the expression is removed from the list of attributes, and is interpreted as the macro. If the expression is not interpreted as a macro at any compilation phase, then the compiler tries to interpret it as a macroattribute. If this fails, the compiler generates an error message. This compiler behavior allows to very flexibly use attributes and macroattributes. For example, a macroattribute can be specified just for marking a type member, and then this mark can be used in another macro.
For example, in the standard macro library there is macro RecordIgnore, which allows you to specify that some of the fields or properties should be ignored (should not be added as parameters to a constructor generated by Record macro). The macro RecordIgnore works at the BeforeInheritance phase. It adds members marked by it to a list (actually to a hash table), which in turn is placed to the named custom property with the key «RecordIgnored-MemberIgnoreMapKey».
Class TypeBuilder has indexed property UserData of type System.Collections.IDictionary:

    public UserData : SC.IDictionary
    {
      get
      {
        when (_userData == null)
          _userData = ListDictionary();

        _userData
      }
    }

This feature allows you to store information required by macros. Since TypeBuilders are recreated on project changes affecting top-level AST, the information from the UserData property should not be deleted. But since TypeBuilders are not recreated between the compilation phases, it is very convenient to pass information through their UserData property between macros or between the same macro at the different compilation phases.
Here is the implementation of the RecordIgnore macro from the standard macro library:

[MacroUsage(MacroPhase.BeforeInheritance, MacroTargets.Field)]
macro RecordIgnore(ty : TypeBuilder, fld : ParsedField)
{
  MacrosHelper.MarkIgnored(ty, fld);
}

[MacroUsage(MacroPhase.BeforeInheritance, MacroTargets.Property)]
macro RecordIgnore(ty : TypeBuilder, property : ParsedProperty)
{
  MacrosHelper.MarkIgnored (ty, property);
}
...
private IgnoreMapKey = "RecordIgnored-MemberIgnoreMapKey"; 

public MarkIgnored (ty : TypeBuilder, member : PT.ClassMember) : void
{
  ...
  
  def saveMarkInUserData(ty : TypeBuilder, member : PT.ClassMember) : void
  {
    mutable ignoreMap = 
      ty.UserData[IgnoreMapKey] :> SCG.Dictionary[PT.ClassMember, byte];
  
    when (ignoreMap == null)
    {
      ignoreMap = SCG.Dictionary();
      ty.UserData[IgnoreMapKey] = ignoreMap;
    }

    ignoreMap[member] = 1; // the hash-table is used as a set
  }
  
  saveMarkInUserData(ty, member);
}

HINT
Please note that the local function saveMarkInUserData is used only once. Basically, you can easily avoid using it. But local functions perfectly document your code. The name of a local function explains what makes the code contained in it. Local functions that are used exactly once and are not used as higher-order functions, are guaranteed to be inlined by the compiler. Thus, you can safely use them in order to improve code readability.

In the future, the values stored in UserData are used in the code of the Record macro to filter the list of fields:

...
def ignoreMap = tb.UserData[IgnoreMapKey] 
  :> SCG.Dictionary[PT.ClassMember, byte];
def isIgnored(member : PT.ClassMember) : bool
{
  ignoreMap != null && ignoreMap.ContainsKey(member);
}
// we use the isIgnored function for filtering the fields ...

Adding types

When you create a macro quite common task is to add new types and members. There was a lot of examples of adding type members. Let’s now add a new type.
In general, the addition of types is very similar to adding members, but there are two subtleties. First, a type should be created either in a namespace or inside another type. Second, it is better to create a type in top level macros (macroattributes), since otherwise it would be difficult to enforce uniqueness of the added types.
The easiest way is to define a nested type. To do this, use the method TypeBuilder.DefineNestedType. Since nested types have access to private members of the type in which they are nested, creating nested types is convenient for implementing various helper types (comparators, serializers, etc.). DefineNestedType returns a TypeBuilder for the created type.

WARNING
Do not try to add a nested type using TypeBuilder.Define method. Such code will be compiled by will fail at run time.

Here is an example of adding a nested type:

def members = creating a list of ASTs of the members...
def nested = tb.DefineNestedType(<[ decl:
  [Nemerle.Core.Record]
  public class MyNestedClass
  {
    ..$members
  } ]>);
  
nested.Compile();

As you can see, everything is pretty simple. The main thing is to remember to call Compile at the end of working with the type. But it should be done after the completion of working with the type.
To add a type to a namespace, you have to use GlobalEnv.Define method.
The GlobalEnv class defines the so-called environment. It includes the current namespace, the opened namespaces, and the opened types. To put the type into a namespace, you need to get the GlobalEnv object, in which it is current.
The object Typer which implicitly passed to every macro has the Env field. It contains an instance of GlobalEnv that describes the environment in which the current macro was used. It can be used to declare a type in the same namespace. The following sample of a macro generates a class in the same namespace in which the macro is applied:

[MacroUsage(MacroPhase.BeforeInheritance, MacroTargets.Assembly)]
macro AddClass()
{
  Macros.DefineCTX(typer);
  def builder = typer.Env.Define(<[ decl:
      public class MyType
      {
        public Test() : void {  }
      } ]>);
          
  builder.Compile();
}

The usage of the macro:
namespace MyNamespace
{
  [assembly: AddClass]
}

namespace OtherNamespace
{
  [assembly: AddClass]
}

module Program
{
  Main() : void
  {

    MyNamespace.MyType().Test();
    OtherNamespace.MyType().Test();
  }
}

If you want to define a type in some known namespace, you have to use the so-called root (Core) environment, in which the current namespace is the root (unnamed) namespace. You can get it as follows:

typer.Manager.CoreEnv

The environment allows you to “enter” to a nested namespace. For example, if in the environment object the current namespace is “X”, then you can enter, for example, to the namespace “X.Y”, but you cannot enter to “Z”. From the main namespace, you can enter to any namespace.
Below is AddClassInsideNamespace macro, which is an example that demonstrates how to add a type to a namespace specified by a qualified identifier.

macro AddClassInsideNamespace(namespaceName : PExpr)
{
  def typer = Macros.ImplicitCTX();
      
  match (Util.QidOfExpr(namespaceName))
  {
    | Some((qualifiedIdentifier, _)) =>
      def env = typer.Manager.CoreEnv.EnterIntoNamespace(qualifiedIdentifier);
      def builder = env.Define(<[ decl:
          public class MyType
          {
            public Test() : void {  }
          } ]>);
          
      builder.Compile();

    | None => Message.FatalError(namespaceName.Location, 
                "expected qualified identifier");
  }
}

The QidOfExpr function takes an expression (PExpr) and returns the type «option[list[string] * Name]», i.e. a tuple consisting of a list of strings and describing a qualified identifier and a name packed to option[T] type. If the input expression contains invalid qualified identifier (i.e. not a name delimited by dots) then QidOfExpr returns None(). If successful, the first element of the tuple is a list of strings defining a qualified identifier. This way, for the expression “A.B.C” it will return a list “[”A",“B”,“C”]". The name, which is the second element of the tuple, is the last simple name in an expression (i.e. “C” for the previous example). Since a name in Nemerle contains a reference to the environment in which it is declared, it can be used for name resolution. But in this example, the name is not used.
Here is the usage of the macro:

namespace MyNamespace
{
  [assembly: AddClassInsideNamespace(A.B.C)]
  [assembly: AddClassInsideNamespace(X.Y.Z)]
}

module Program
{
  Main() : void
  {
    A.B.C.MyType().Test();
    X.Y.Z.MyType().Test();
  }
}

Adding members to the added type

In the examples above, the type already contains a single method. You can declare a type with any number of members. But this approach does not allow you to create a list of members programmatically. If you want to generate a list of members programmatically, you can do it one of the following ways:
1. Generate a list of the members programmatically (for example, by using quasi-quotes “decl”) and substitute it with a quasi-quotes of the type ..$x:

def members : list[PExpr] = ...;
def builder = env.Define(<[ decl:
  public class MyType
  {
    ..$members
  } ]>);
...

2. It is possible to add members using the Define method of the TypeBuilder object that is returned by GlobalEnv.Define or TypeBuilder.DefineNestedType functions:

def builder = env.Define(<[ decl:
  public class MyType
  {
  } ]>);
builder.Define(<[ decl: mutable field : int; ]>);
builder.Compile();

Learn more about macros

The subject of macros is rather vast. Therefore, it is impossible to cover all the details in one article. If you’ve started using macros and you have questions, you may find the answers in the following inexhaustible sources of information:

Debugging macros

Since macros are ordinary programs in Nemerle language for debugging purposes you can use the same tools as for other .Net-based applications. The only difference between macros and for example console applications is that they are executed within the compiler or IDE process. Hence, you will need to debug ncc32.exe application (or ncc64.exe depending on the platform), which by default is located in the directory “ProgramFiles\Nemerle\Net-4.0”) or devenv.exe (VS 2010), which by default is located in the directory "%ProgramFiles%\Microsoft Visual Studio 10.0\ Common7\IDE ".
What to debug (devenv.exe or ncc.exe) depends on the environment in which you want to debug your macros.
For debugging these executables in VS you have to specify the path to them in the macro project properties in the “Debug\Start Program” section. Besides, you have to provide to the compiler (ncc.exe) command-line options that will cause the compiler to compile the project using your macros. The easiest way to do this is to create a file that contains the compiler options and provide to the compiler command line option “-from-file:the-name-of-this-file”. For example, to debug compilation of your project DisposableTest you can create a file CompilerOptions.txt as follows:

/no-color 
/no-stdlib 
/greedy-references:- 
/define:DEBUG;TRACE 
/target:exe 
/debug+ 
/project-path:C:\Nemerle-Articles\Nemerle-macros-intro\Projects\MacroIntro\DisposableTest\DisposableTest.nproj 
/root-namespace:DisposableTest

Main.n
Properties\AssemblyInfo.n
 
/ref:C:\Nemerle-Articles\Nemerle-macros-intro\Projects\MacroIntro\MacroIntroLibrary\bin\Debug\MacroIntroLibrary.dll 
/ref:"C:\Program Files\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\mscorlib.dll" 
/ref:"C:\Program Files\Nemerle\Net-4.0\Nemerle.dll" 
/ref:"C:\Program Files\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\System.Core.dll" 
/ref:"C:\Program Files\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\System.dll" 
/ref:"C:\Program Files\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\System.Xml.dll" 
/ref:"C:\Program Files\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\System.Xml.Linq.dll" 
/macros:"C:\Program Files\Nemerle\Net-4.0\Nemerle.Linq.dll" 
/out:obj\Debug\DisposableTest.exe

HINT
The content was obtained by copy-paste from a window “Output” after compiling the project “DisposableTest”.

After that you can set a breakpoint anywhere in the macro and perform step-through debugging.

HINT
If you compile the Nemerle compiler and VS integration from the source code you will be able to debug the code of the compiler as well. Information about how to build Nemerle from the source code can be found here.

If this way is too difficult for you, the easier way could be to put this line of code anywhere in your macro:

assert2(false); 

and to recompile the solution that contains the macro project and the project with a usage of the macro. In this case «Assert» dialog will appear, see figure 9. If you press the button “Retry”, a standard dialog to connect a debugger to the process will appear. After closing a series of dialog boxes, you will open a new copy of VS, which can be used for debugging the macro.

Figure  9.  "Assert" dialog box allows you to start debugging of a macro
Figure 9. “Assert” dialog box allows you to start debugging of a macro.

WARNING
The “Assert” dialog box will only appear in Debug-versions of macros, and only at the compile time. The dialog will not appear while working in IDE, because all dialogs in IDE are disabled in order to don’t annoy a user.

To debug a macro in IDE mode, you will need to run one instance of IDE from the another. To do this, you have to specify the path to the devenv.exe as an application for debugging, run a second copy of VS, place breakpoints and open a project that uses your macros. It will fire a breakpoint, and you can start step-through debugging.
During step-through debugging you can see fragments of the code in the “Watch” and “Locals” windows. You can also use the “Immediate” window for experiments and for viewing large code fragments (this is useful because the “Watch” and “Locals” windows do not show line endings).
If you need to debug the code generated by macro then to add members and types you have to use the DefineWithSource method instead of TypeBuilder.Define. This will lead to generation of source code and associated debug information. Naturally, you don’t have to debug a macro but an application in which the macro is used.
Of course, you can use logging for debugging macros. But there is nothing to tell, because the debugging techniques that use logging are the same for all kinds of applications. As shown above, you can display information to the “Output” window using Message.Hint(). Also for logging you can use Log macro from the standard Nemerle library or any other logging library.
The main thing to remember during debugging as well as developing macros is that macro is not a part of the target program, but an extension to the compiler and IDE.

Conclusion

This article is rather big, but I hope it will help you in development of non-trivial macros. In the following parts of the series “Nemerle language” there will be supplemental information with concise and simple examples. This part is designed to explain and show you possibilities of macros.
If you have questions or difficulties, feel free to ask them in the forums, and also feel free to criticize the text of this (and other) section. This will make the text more accessible and understandable.

References

⚠️ **GitHub.com Fallback** ⚠️