Chapter 10

Macros

Overview

A macro is an extension to the core language that can be defined by the user, by the implementation, or as part of the Dylan language specification. Much of the grammatical structure of Dylan is built with macros. A macro defines the meaning of one construct in terms of another construct. The compiler substitutes the new construct for the original. The purpose of macros is to allow programmers to extend the Dylan language, for example by creating new control structures or new definitions. Unlike C, Dylan does not intend macros to be used to optimize code by inlining. Other parts of the language, such as sealing and define constant, address that need.

Throughout this chapter, italic font and small caps are used to indicate references to the formal grammar given in Appendix A, BNF.

Compilation and Macro Processing

Compilation consists of six conceptual phases:

  1. Parsing a stream of characters into tokens, according to the lexical grammar in Appendix A, BNF.
  2. Parsing a stream of tokens into a program, according to the phrase grammar in Appendix A, BNF.
  3. Macro expansion, which translates the program to a core language.
  4. Definition processing, which recognizes special and built-in definitions and builds a compile-time model of the static structure of the program.
  5. Optimization, which rewrites the program for improved performance.
  6. Code generation, which translates the program to executable form.

Portions of a program can be macro calls. Macro expansion replaces a macro call with another construct, which can itself be a macro call or contain macro calls. This expansion process repeats until there are no macro calls remaining in the program, thus macros have no space or speed cost at run time. Of course, expanding macros affects the speed and space cost of compilation.

A macro definition describes both the syntax of a macro call and the process for creating a new construct to replace the macro call. Typically the new construct contains portions of the old one, which can be regarded as arguments to the macro. A macro definition consists of a sequence of rewrite rules. The left-hand side of each rule is a pattern that matches a macro call. The right-hand side is a template for the expansion of a matching call. Pattern variables appearing in the left-hand side act as names for macro arguments. Pattern variables appearing in the right-hand side substitute arguments into the expansion. Macro arguments can be constrained to match specified elements of the Dylan grammar. Auxiliary rule sets enhance the rewrite rule notation with named subrules.

Some implementations and a future version of the Dylan language specification might allow macro expansions to be produced by compile-time computation using the full Dylan language and an object-oriented representation for programs. Such a procedural macro facility is not part of Dylan at this time.

The input to, and output from, macro expansion is a fragment, which is a sequence of elementary fragments. An elementary fragment is one of the following:

The second and third phases of compilation (parsing and macro expansion) are interleaved, not sequential. The parsing phase of the compiler parses a macro call just enough to find its end. See definition-macro-call, statement, function-macro-call, body-fragment, list-fragment, and basic-fragment in Appendix A, BNF. This process of parsing a macro call also parses any macro calls nested inside it. The result is a macro call fragment.

This loose grammar for macro calls gives users a lot of flexibility to choose the grammar that their macros will accept. For example, the grammar of macro calls doesn't care whether a bracketed fragment will be interpreted as an argument list, a parameter list, a set of for clauses, or a module import list.

The compiler delays computing the expansion of a macro call fragment until it is needed. If an argument to a macro is a macro call, the outer macro call is always expanded first. When the compiler computes the expansion of a macro call fragment, it obeys the macro's definition. Constraints on pattern variables can cause reparsing of portions of the macro call.

A constituent, operand, or leaf that is a macro call expands the macro during or before the definition processing and optimization phases. The compiler brackets the expansion in beginend, using the standard binding of begin in the Dylan module, and then reparses it as a statement. This reparsing may discover more macro calls. A parse error while reparsing a macro expansion could indicate an invalid macro definition or an incorrect call to the macro that was not detected during pattern matching. Once the cycle of macro expansion and reparsing has been completed, no tokens, bracketed fragments, or macro call fragments remain and the entire source record has become one parsed fragment.

This beginend bracketing ensures that the expansion of a macro call will not be broken apart by operator precedence rules when the macro call is a subexpression. Similarly, it ensures that the scopes of local declarations introduced by a macro will not extend outside that macro expansion when the macro call is a statement in a body.

The fragment produced by parsing a macro call, which is the input to macro expansion, is as follows:

The fragment produced by parsing an expression is as follows:

The term parsed expression fragment refers to any of the above.

The parser recognizes parsed fragments as well as raw tokens. The nonterminals definition and local-declaration in the phrase grammar accept parsed fragments of the same kind. The nonterminal operand accepts parsed function call fragments and macro call fragments. The nonterminal literal accepts list constant and vector constant fragments. The nonterminal simple-fragment accepts parsed function call fragments and macro call fragments. The nonterminal macro accepts macro call fragments. The parser expands bracketed fragments into their constituent tokens before parsing them.