是否可以定义在编译结束时执行的指令? (Crystal 语言)

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

假设一个 Crystal 项目正在使用不同的分片。每个分片都希望在整个项目编译结束时进行清理。是否可以使用宏?

例如这样的事情:

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

如果需要在多个宏之间共享数据,可以使用 HashArray,并通过 Constant 访问它们,关联的 AST 节点可从宏访问。

这是一个演示此的示例(在 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

这里的重要部分是:

STORAGE = [] of _

在这里我声明了一个特定类型的数组(在这种情况下这是不相关的,因为数组只能通过宏访问,你不能在正常情况下使用这种类型(_)代码)。宏系统只需要知道它是一个数组即可。

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

然后我创建一个能够改变数组 AST 节点(在宏系统中是 ArrayLiteral 类型)的宏。 请注意,它允许您存储任何类型的 AST 节点,从 NumberLiteralSymbolLiteral 等简单节点到 CallDef 甚至 ClassDef.

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

最后我创建了一个宏,它将简单地打印(在编译时)STORAGE 数组的内容。

同样可以用 HashLiteral:

STORAGE = {} of _ => _

和上面解释的一样,你可以使用任何类型的 AST 节点作为键或值。


注意你在宏系统中使用的 Constants 也可以被普通代码看到,并且可能会出现名称冲突(如果用户也想使用常量 STORAGE 怎么办为他自己的代码?)。 为了尽量减少这种可能性(如果不需要),我建议您将常量放在一个特殊的模块中,例如 MyShard::MacroStorage::SomeInterestingNodes 或使用一些复杂的命名模式,例如 M____ACRO_Storage____01234 (<-这不太可能用户使用 ;) )