In order to manipulate syntax trees or provide annotations on source code, Slate provides another form of message-send called a macro-level message send. Sends of this sort have as their arguments the objects built for the expressions' shapes. Furthermore, the results of evaluation of macro-sends are placed in the syntax tree in the same location as the macro-send occupied.
Preceding any selector with a back-tick (`) will cause it to be sent as a macro. This means that the message sent will be dispatched on Slate's Syntax Node objects, which are produced by the parser and consumed by the compiler. Macros fit into the process of compiling in this order: the text is processed by the Lexer into a stream of tokens, which are consumed by the Parser to produce a (tree-)stream of Syntax Nodes. Before being passed to the compiler, the macroexpand method is recursively called on these syntax trees, which invokes every macro-level message-send and performs the mechanics of replacing the macro-send with the result of the method invoked. With this in mind, the Slate macro system is offered as a flexible communication system between the code-writer and the compiler or other tools or even other users, using parse trees as the medium.
As an example of the expressiveness of this system, we can express the type annotation and comment features of the Slate language in terms of macros:
Macros must be dispatched (if at all) upon the traits of expressions' syntactic representation. This introduces a few difficulties, in that some familiarity is needed with the parse node types in order to name them. However, only two things need to be remembered:
A fundamental application of the macro message-send system is the ability to obtain syntax trees for any expression at run-time. The most basic methods for this are `quote, which causes the surrounding expression to use its quoted value as the input for even normal methods, and `unquote results in an inversion of the action of `quote, so it can only be provided within quoted expressions.
An abbreviated form of quotation may be used by surrounding an expression with `() which has the same effect as sending `quote. The parentheses may be omitted if the inner expression cannot be interpreted as a message-send (applicable to literal blocks, arrays, numbers, and so on).4
In experience with Lisp macros, nested quotation is often found necessary. In order to adequately control this, often the quotation prefix symbols have to be combined in non-intuitive ways to produce the correct code. Slate includes, as an alternative, two operations which set a label on a quotation and can unquote within that to the original quotation by means of referencing the label.
Most users need time to develop the understanding of the need for higher-order macros, and this relates to users who employ them. For reference, a Lisp book which covers the subject of higher-order macros better than any other is On Lisp[Graham 94]. However, it's also been said that Lisp's notation and the conceptual overhead required to manage the notation in higher-order macros keeps programmers from entering the field, so perhaps this new notation will help.
The operators are expr1 `quote: aLiteral and expr2 `unquote: aLiteral, and in order for this to work syntactically, the labels must be equal in value and must be literals. As well, the unquoting expression has to be a sub-expression of the quotation. The effect is that nesting an expression more deeply does not require altering the quotation operators to compensate, and it does indicate better what the unquoting is intended to do.
Many object-oriented code idioms involve repeated message-sends to the same object. This is common in creating and setting up a new object, or activating many behaviors in sequence on the same thing (used in a lot of UI code - or more generally with objects that have many variables or are "facade" objects).
Smalltalk-80 included a syntax feature to elide the first argument when repeating message-sends. In Slate, the first argument of a message send is not a special "receiver", so support in the basic syntax does not make sense. The solution is a macro-method which takes an expression (well, its result) and a block, and enhances the block so that the result of the expression becomes the implicit context for statement-level (top-level) message expressions. For example:
There is an additional possibility that it takes care of by allowing the user to specify an input variable to the block, which will also allow the code within to refer to the object explicitly. Also, the default return value for empty-last-statements is modified to be this object instead of the usual Nil. Without such features, a method which returns its only argument is needed; in Smalltalk-80, the yourself method performs this role at the end of a cascade. To see the effect this would have on Slate library code, take a collection creation method as an example:
Another macro allows for the slots of an object to appear as local variables in a method, responding both to accessors and mutators in the appropriate way (by modifying the object's slots).
So, the following expression names some slots in the first argument which should be available as inputs to the second argument, a block. This is analogous to having Smalltalk's direct slot reference syntax (or Self's, for that matter), or to Lisp's with-slots macro.
`with:as: is a proposed protocol for transparent substitution of temporary or locally-provided proxies for environment values and other system elements. This should provide an effective correspondent of the functionality of Lisp's "with-" style macros.
A future framework for expansion will involve accommodating the types of source-level pattern-matching used in tools for manipulating code for development, as in the Smalltalk Refactoring Browser.