Search code examples
crystal-lang

Is it possible to define instructions to be executed at the end of compilation? (Crystal lang)


Let's say a Crystal project is using different shards. And each shard wants to do cleanup at the end of compilation of the overall project. Is it possible using Macros?

Something like this for example:

{% at_end %}
  {% system("rm 'tmp files'") %}
{% end %}

Solution

  • If you need to share data between multiple macros, you can use Hash or Array, and access them through a Constant which associated AST node is accessible from macros.

    Here is an example demonstrating this (live at https://carc.in/#/r/372k):

    STORAGE = [] of _
    
    macro add(node)
      {% STORAGE << node %}
    end
    
    macro list
      {% for elem in STORAGE %}
        {% puts "Elem: #{elem}" %}
      {% end %}
    end
    
    add 1
    add 2
    add "hello"
    add :world
    add({a: 1, b: 2})
    add 3 + 4
    add a_call(arg1, 2)
    
    list
    

    The important parts here is:

    STORAGE = [] of _
    

    Here I'm declaring an array of a certain type (which is non relevant in this case, as the array is only accessed through macros, you can't use this type (_) in normal code). The macro system only needs to know that it is an array.

    macro add(node)
      {% STORAGE << node %}
    end
    

    Then I create a macro that will be able to mutate the array AST node (which is type ArrayLiteral in the macro system). Note that it allows you to store any kind of AST node, from simple nodes like NumberLiteral or SymbolLiteral, to complex nodes like a Call, a Def or even a ClassDef.

    macro list
      {% for elem in STORAGE %}
        {% puts "Elem: #{elem}" %}
      {% end %}
    end
    

    And finally I create a macro that will simply print (at compile time) the content of the STORAGE array.

    The same can be done with a HashLiteral:

    STORAGE = {} of _ => _
    

    And in the same way as explained above, you can use any kind of AST nodes as the key or the value.


    Note that the Constants you use in the macro system can also be seen by normal code, and it's possible to have name clash (what if the user wants to use a constant STORAGE too for his own code?). To minimize this possibility (if this is not wanted), I suggest you to put your constants in a special module like MyShard::MacroStorage::SomeInterestingNodes or use some complicated naming pattern like M____ACRO_Storage____01234 (<-- this is unlikely to be used by the user ;) )