Macros

    A macro’s definition body looks like regular Crystal code with extra syntax to manipulate the AST nodes. The generated code must be valid Crystal code, meaning that you can’t for example generate a without a matching end, or a single when expression of a case, since both of them are not complete valid expressions. Refer to Pitfalls for more information.

    Macros declared at the top-level are visible anywhere. If a top-level macro is marked as private it is only accessible in that file.

    They can also be defined in classes and modules, and are visible in those scopes. Macros are also looked-up in the ancestors chain (superclasses and included modules).

    For example, a block which is given an object to use as the default receiver by being invoked with with ... yield can access macros defined within that object’s ancestors chain:

    1. class Foo
    2. macro emphasize(value)
    3. "***#{ {{value}} }***"
    4. end
    5. def yield_with_self
    6. with self yield
    7. end
    8. end
    9. Foo.new.yield_with_self { emphasize(10) } # => "***10***"

    Macros defined in classes and modules can be invoked from outside of them too:

    1. class Foo
    2. macro emphasize(value)
    3. "***#{ {{value}} }***"
    4. end
    5. end
    6. Foo.emphasize(10) # => "***10***"

    Interpolation

    You use {{...}} to paste, or interpolate, an AST node, as in the above example.

    Note that the node is pasted as-is. If in the previous example we pass a symbol, the generated code becomes invalid:

    1. # This generates:
    2. #
    3. # def :foo
    4. # 1
    5. # end
    6. define_method :foo, 1

    Note that :foo was the result of the interpolation, because that’s what was passed to the macro. You can use the method ASTNode#id in these cases, where you just need an identifier.

    Macro calls

    You can invoke a fixed subset of methods on AST nodes at compile-time. These methods are documented in a fictitious Crystal::Macros module.

    For example, invoking in the above example solves the problem:

    1. macro define_method(name, content)
    2. def {{name.id}}
    3. {{content}}
    4. end
    5. end
    6. # This correctly generates:
    7. #
    8. # def foo
    9. # 1
    10. # end
    11. define_method :foo, 1

    Modules and classes

    Modules, classes and structs can also be generated:

    1. macro define_class(module_name, class_name, method, content)
    2. module {{module_name}}
    3. class {{class_name}}
    4. def initialize(@name : String)
    5. end
    6. def {{method}}
    7. {{content}} + @name
    8. end
    9. end
    10. end
    11. end
    12. # This generates:
    13. # module Foo
    14. # class Bar
    15. # def initialize(@name : String)
    16. # end
    17. #
    18. # def say
    19. # "hi " + @name
    20. # end
    21. # end
    22. # end
    23. define_class Foo, Bar, say, "hi "
    24. p Foo::Bar.new("John").say # => "hi John"

    You use {% if condition %}{% end %} to conditionally generate code:

    1. macro define_method(name, content)
    2. def {{name}}
    3. {% if content == 1 %}
    4. "one"
    5. {% elsif content == 2 %}
    6. "two"
    7. {% else %}
    8. {{content}}
    9. {% end %}
    10. end
    11. end
    12. define_method foo, 1
    13. define_method bar, 2
    14. define_method baz, 3
    15. foo # => one
    16. bar # => two
    17. baz # => 3

    Macro conditionals can be used outside a macro definition:

    1. {% if env("TEST") %}
    2. puts "We are in test mode"
    3. {% end %}

    Iteration

    You can iterate a finite amount of times:

    To iterate an ArrayLiteral:

    1. macro define_dummy_methods(names)
    2. {% for name, index in names %}
    3. {{index}}
    4. end
    5. {% end %}
    6. end
    7. define_dummy_methods [foo, bar, baz]
    8. foo # => 0
    9. bar # => 1
    10. baz # => 2

    The index variable in the above example is optional.

    To iterate a :

    1. macro define_dummy_methods(hash)
    2. {% for key, value in hash %}
    3. def {{key.id}}
    4. {{value}}
    5. end
    6. {% end %}
    7. end
    8. define_dummy_methods({foo: 10, bar: 20})
    9. foo # => 10
    10. bar # => 20

    Macro iterations can be used outside a macro definition:

    1. {% for name, index in ["foo", "bar", "baz"] %}
    2. def {{name.id}}
    3. {{index}}
    4. end
    5. {% end %}
    6. foo # => 0
    7. bar # => 1
    8. baz # => 2

    Variadic arguments and splatting

    A macro can accept variadic arguments:

    1. macro define_dummy_methods(*names)
    2. {% for name, index in names %}
    3. def {{name.id}}
    4. {{index}}
    5. end
    6. {% end %}
    7. end
    8. define_dummy_methods foo, bar, baz
    9. foo # => 0
    10. bar # => 1
    11. baz # => 2

    The arguments are packed into a and passed to the macro.

    Additionally, using * when interpolating a TupleLiteral interpolates the elements separated by commas:

    1. macro println(*values)
    2. print {{*values}}, '\n'
    3. end
    4. println 1, 2, 3 # outputs 123\n

    Type information

    When a macro is invoked you can access the current scope, or type, with a special instance variable: @type. The type of this variable is TypeNode, which gives you access to type information at compile time.

    Note that @type is always the instance type, even when the macro is invoked in a class method.

    For example:

    1. macro add_describe_methods
    2. def describe
    3. "Class is: " + {{ @type.stringify }}
    4. end
    5. def self.describe
    6. "Class is: " + {{ @type.stringify }}
    7. end
    8. end
    9. class Foo
    10. add_describe_methods
    11. end
    12. Foo.new.describe # => "Class is Foo"
    13. Foo.describe # => "Class is Foo"
    1. A_CONSTANT = 0
    2. {% if @top_level.has_constant?("A_CONSTANT") %}
    3. puts "this is printed"
    4. {% else %}
    5. puts "this is not printed"
    6. {% end %}

    Method information

    When a macro is invoked you can access the method, the macro is in with a special instance variable: @def. The type of this variable is Def unless the macro is outside of a method, in this case it’s .

    Example:

    Constants

    Macros can access constants. For example:

    1. VALUES = [1, 2, 3]
    2. {% for value in VALUES %}
    3. puts {{value}}
    4. {% end %}

    If the constant denotes a type, you get back a .

    Nested macros

    It is possible to define a macro which generates one or more macro definitions. You must escape macro expressions of the inner macro by preceding them with a backslash character “\“ to prevent them from being evaluated by the outer macro.

    1. macro define_macros(*names)
    2. {% for name in names %}
    3. macro greeting_for_{{name.id}}(greeting)
    4. \{% if greeting == "hola" %}
    5. "¡hola {{name.id}}!"
    6. \{% else %}
    7. "\{{greeting.id}} {{name.id}}"
    8. \{% end %}
    9. {% end %}
    10. end
    11. # This generates:
    12. #
    13. # {% if greeting == "hola" %}
    14. # "¡hola alice!"
    15. # {% else %}
    16. # "{{greeting.id}} alice"
    17. # {% end %}
    18. # end
    19. # macro greeting_for_bob(greeting)
    20. # {% if greeting == "hola" %}
    21. # "¡hola bob!"
    22. # {% else %}
    23. # "{{greeting.id}} bob"
    24. # {% end %}
    25. # end
    26. define_macros alice, bob
    27. greeting_for_alice "hello" # => "hello alice"
    28. greeting_for_bob "hallo" # => "hallo bob"
    29. greeting_for_alice "hej" # => "hej alice"
    30. greeting_for_bob "hola" # => "¡hola bob!"

    Another way to define a nested macro is by using the special verbatim call. Using this you will not be able to use any variable interpolation but will not need to escape the inner macro characters.

    1. macro define_macros(*names)
    2. {% for name in names %}
    3. macro greeting_for_{{name.id}}(greeting)
    4. # name will not be available within the verbatim block
    5. \{% name = {{name.stringify}} %}
    6. {% verbatim do %}
    7. {% if greeting == "hola" %}
    8. "¡hola {{name.id}}!"
    9. {% else %}
    10. "{{greeting.id}} {{name.id}}"
    11. {% end %}
    12. {% end %}
    13. end
    14. {% end %}
    15. end
    16. # This generates:
    17. #
    18. # macro greeting_for_alice(greeting)
    19. # {% name = "alice" %}
    20. # {% if greeting == "hola" %}
    21. # "¡hola {{name.id}}!"
    22. # {% else %}
    23. # "{{greeting.id}} {{name.id}}"
    24. # {% end %}
    25. # end
    26. # macro greeting_for_bob(greeting)
    27. # {% name = "bob" %}
    28. # {% if greeting == "hola" %}
    29. # "¡hola {{name.id}}!"
    30. # {% else %}
    31. # "{{greeting.id}} {{name.id}}"
    32. # {% end %}
    33. # end
    34. define_macros alice, bob
    35. greeting_for_alice "hello" # => "hello alice"
    36. greeting_for_bob "hallo" # => "hallo bob"
    37. greeting_for_alice "hej" # => "hej alice"
    38. greeting_for_bob "hola" # => "¡hola bob!"

    Notice the variables in the inner macro are not available within the verbatim block. The contents of the block are transferred “as is”, essentially as a string, until re-examined by the compiler.

    Macro expressions are evaluated both within comments as well as compilable sections of code. This may be used to provide relevant documentation for expansions:

    1. {% for name, index in ["foo", "bar", "baz"] %}
    2. # Provides a placeholder {{name.id}} method. Always returns {{index}}.
    3. def {{name.id}}
    4. {{index}}
    5. end
    6. {% end %}

    This evaluation applies to both interpolation and directives. As a result of this, macros cannot be commented out.

    1. macro a
    2. # {% if false %}
    3. puts 42
    4. # {% end %}
    5. end
    6. a

    The expression above will result in no output.

    Pitfalls

    When writing macros (especially outside of a macro definition) it is important to remember that the generated code from the macro must be valid Crystal code by itself even before it is merged into the main program’s code. This means, for example, a macro cannot generate a one or more when expressions of a case statement unless case was a part of the generated code.

    Here is an example of such an invalid macro:

    1. case 42
    2. {% for klass in [Int32, String] %} # Syntax Error: unexpected token: {% (expecting when, else or end)
    3. when {{klass.id}}
    4. p "is {{klass}}"
    5. {% end %}
    6. end
    1. {% begin %}
    2. case 42
    3. {% for klass in [Int32, String] %}
    4. when {{klass.id}}
    5. p "is {{klass}}"
    6. {% end %}