Macros!

Atomo sports a macro system similar to that of Common Lisp, in addition to a fancy macro-quoting system similar to Haskell's QuasiQuotes extension.

Defining Macros

To define a macro, you use the macro keyword, followed by its dispatch pattern in parentheses, and then the body of the macro (an expression). See also macro.

The entire Atomo runtime is usable during macro expansion; you can do whatever you want in a macro method's body, as long as it yields an Expression value.

A macro's dispatch pattern is very similar to definition message patterns, except (and perhaps obviously) it can only match expressions in the message's roles. If a macro's role patterns do not match the expressions in a dispatch, the macro isn't found, so nothing happens and the dispatch is left alone.

A macro's role patterns are as follows:

_, foo, ...

Possibly-named wildcard matches.

Example:

> macro (x squared) `(~x * ~x)
@ok
> '(42 squared) expand
'(42 * 42)
> 42 squared
1764
foo: pattern

A named pattern-match. Matches pattern, binding the expression it matches to foo.

> macro ((x: Primitive) square-root) `(~x ^ (1.0 / 2.0))
@ok
> 2 square-root
1.4142135623730951
> [1] square-root
ERROR: <error @(did-not-understand: <message [1] square-root>)>
Dispatch, Operator, Primitive, Block, List, Macro, ForMacro, Particle, Top, Quote, Unquote, MacroQuote

Match on the type of the expression.

> macro (Top foo) `10
@ok
> foo
10
> $a foo
ERROR: <error @(did-not-understand: <message $a foo>)>
`a, `(1 + ~b), ...

Structurally matches an expression recursively. Unquotes serve as named wildcard patterns, with the same recursive semantics as quasiquotation.

Example:

> macro (`(~a -> ~b) from) a
@ok
> (1 -> $a) from
1
> (1 + 2) from
ERROR: <error @(did-not-understand: <message 3 from>)>

Macro Environment

Macro expansion is performed in a separate environment from the runtime, in a clone of Lobby. The bodies of macro methods are evaluated here, as a kind of sandbox from the "real world."

Often you may want to specify some variables for your macros to use during expansion, or just evaluate some expression before everything gets going. Enter for-macro. This simple reserved word tells Atomo to evaluate an arbitrary expression in the macro environment, one step before macro expansion begins.

Example:

for-macro 1 print
for-macro Foo = Object clone

-- "escaping" the macro area and defining A in the Lobby
for-macro super A = Object clone

Macro Quotes

Macro-quotes are a generalized string quotation mechanism similar to Haskell's QuasiQuotes extension. They are used for creating arbitrary values from a given string at macroexpansion time, and are very cheap.

They can make code very clear and concise, adding "almost literal" syntax for things like URLs, paths, byte strings, and regular expressions. That being said, please do not write entire DSLs using them; it's much better to just use the language itself.

Let's take a look at two common macro-quoters, w (word list) and r (regular expression):

w{foo bar baz}
r`"\d+"`m

During the macroexpansion phase, these macro-quotes send the following messages, respectively:

quote: "foo bar baz" as: @w
quote: "\"\\d+\"" as: @r &flags: [$m]

To define a macro-quoter, use for-macro to define quote:as:, pattern-matching the quoter's name as a particle for the as: role. The value yielded by this method will be what the macro-quote expands to. For example, the w macro-quoter expands into a list of the words in the string:

> w{foo bar baz}
["foo", "bar", "baz"]

The r macro-quoter expands into a Regexp value (which pretty-prints as a macro-quote):

> r`"\d+"`m
r{"\d+"}m
> r`"\d+"`m == r{"\d+"}m
True
> r{"\d+"}m is-a?: Regexp
True

Both of these quoters are implemented trivially in prelude/core.atomo like so:

for-macro quote: s as: @w := s words

for-macro quote: s as: @r &flags: [] :=
  Regexp new: s &flags: (flags to: String)

Note that the r macro-quoter makes use of the flags added to the end of the literal, by sending them along to Regexp new: _ &flags: _.

If a macro-quoter is not found, an @unknown-quoter:for:&flags: error is signaled:

> foo{bar}baz
ERROR: <error @(unknown-quoter: @foo for: "bar" &flags: [$b, $a, $z])>