マクロ¶
マクロとは、コンパイル時に AST ノードを受け取り、コードを生成してそれをプログラムに書き込むメソッドです。例をあげます。
macro define_method(name, content)
def {{name}}
{{content}}
end
end
# これで以下が生成されます。
#
# def foo
# 1
# end
define_method foo, 1
foo # => 1
マクロ定義の本体はほぼ通常の Crystal コードですが、AST ノードを扱うための拡張されたシンタックスを利用します。生成されたコードは正しい Crystal コードでなくてはいけません。例えば、対応する end
の無い def
や、case
式の when
部分だけのものなどは、完全な式として正しいものではないので生成することができません。詳しくは落とし穴を参照してください。
スコープ¶
トップレベルで宣言されたマクロはどこからでもアクセス可能です。トップレベルのマクロが private
指定されていた場合は、そのファイル内でのみアクセスできます。
クラスやモジュール内で定義することも可能で、それはそのスコープ内でアクセスできます。また、マクロは継承チェーン (スーパクラスとインクルードされたモジュール) からも探索されます。
例えば、ブロックが与えられていて、with ... yield
によってデフォルトのレシーバが設定されているときには、そのオブジェクトの継承チェーンの中で定義されているマクロにアクセスすることが可能です。
class Foo
macro emphasize(value)
"***#{ {{value}} }***"
end
def yield_with_self
with self yield
end
end
Foo.new.yield_with_self { emphasize(10) } # => "***10***"
クラスやモジュールに定義されたマクロを、その外側から呼び出すこともできます。
class Foo
macro emphasize(value)
"***#{ {{value}} }***"
end
end
Foo.emphasize(10) # => "***10***"
文字列の補間¶
前述した例にもあったように、AST ノードを貼り付ける、もしくは埋め込むには {{...}}
を使います。
ノードは「そのまま」貼り付けされることに注意してください。例えばもし、上記の例でシンボルを渡した場合には、生成されたコードは不正なものとなります。
# これで以下が生成されます:
#
# def :foo
# 1
# end
define_method :foo, 1
マクロに渡されたものがそのまま埋め込まれるので、結果は :foo
となっています。こういった、識別子を必要とする場合には、ASTNode#id
を利用することができます。
マクロにおけるメソッド呼び出し¶
コンパイル時に、メソッドの既定のサブセットを AST ノードに対して実行することが可能です。これらのメソッドはCrystal::Macrosというフェクトのモジュールでドキュメント化されています。
例えば、上の例では ASTNode#id
を実行することで問題を解決できます。
macro define_method(name, content)
def {{name.id}}
{{content}}
end
end
# 以下が正しく生成される:
#
# def foo
# 1
# end
define_method :foo, 1
モジュールとクラス¶
モジュールやクラス、構造体を生成することもできます。
macro define_class(module_name, class_name, method, content)
module {{module_name}}
class {{class_name}}
def initialize(@name : String)
end
def {{method}}
{{content}} + @name
end
end
end
end
# これで以下が生成される:
# module Foo
# class Bar
# def initialize(@name : String)
# end
#
# def say
# "hi " + @name
# end
# end
# end
define_class Foo, Bar, say, "hi "
p Foo::Bar.new("John").say # => "hi John"
条件分岐¶
{% if condition %}
... {% end %}
を使うことで、条件に応じてコードを生成することが可能になります。
macro define_method(name, content)
def {{name}}
{% if content == 1 %}
"one"
{% elsif content == 2 %}
"two"
{% else %}
{{content}}
{% end %}
end
end
define_method foo, 1
define_method bar, 2
define_method baz, 3
foo # => one
bar # => two
baz # => 3
通常のコードと同様に、Nop
と NilLiteral
そして偽の BoolLiteral
は偽となり、それ意外はすべて真となります。
マクロの条件分岐は、マクロの外側でも使用することができます。
{% if env("TEST") %}
puts "We are in test mode"
{% end %}
繰り返し¶
有限回の繰り返しをすることができます。
macro define_constants(count)
{% for i in (1..count) %}
PI_{{i.id}} = Math::PI * {{i}}
{% end %}
end
define_constants(3)
PI_1 # => 3.14159...
PI_2 # => 6.28318...
PI_3 # => 9.42477...
ArrayLiteral
の各要素に対して繰り返し実行するには、次のようにします。
macro define_dummy_methods(names)
{% for name, index in names %}
def {{name.id}}
{{index}}
end
{% end %}
end
define_dummy_methods [foo, bar, baz]
foo # => 0
bar # => 1
baz # => 2
上記の index
変数は任意です。
HashLiteral
の各要素に対して繰り返し実行するには、次のようにします。
macro define_dummy_methods(hash)
{% for key, value in hash %}
def {{key.id}}
{{value}}
end
{% end %}
end
define_dummy_methods({foo: 10, bar: 20})
foo # => 10
bar # => 20
マクロの繰り返し構文は、マクロの外側でも使用することができます。
{% for name, index in ["foo", "bar", "baz"] %}
def {{name.id}}
{{index}}
end
{% end %}
foo # => 0
bar # => 1
baz # => 2
可変長引数とスプラット展開¶
マクロは可変長引数を受け取ることができます。
macro define_dummy_methods(*names)
{% for name, index in names %}
def {{name.id}}
{{index}}
end
{% end %}
end
define_dummy_methods foo, bar, baz
foo # => 0
bar # => 1
baz # => 2
The arguments are packed into a TupleLiteral
and passed to the macro.
Additionally, using *
when interpolating a TupleLiteral
interpolates the elements separated by commas:
macro println(*values)
print {{*values}}, '\n'
end
println 1, 2, 3 # outputs 123\n
型の情報¶
マクロが実行される際に、@type
という項別なインスタンス変数を使うことで、現在のスコープ、もしくは型にアクセスすることが可能です。この変数の型は TypeNode
で、コンパイル時の型情報にアクセスできます。
@type
は常に (もしクラスメソッドの中で実行されたとしても) インスタンスの型になることに注意してください。
例をあげます。
macro add_describe_methods
def describe
"Class is: " + {{ @type.stringify }}
end
def self.describe
"Class is: " + {{ @type.stringify }}
end
end
class Foo
add_describe_methods
end
Foo.new.describe # => "Class is Foo"
Foo.describe # => "Class is Foo"
The top level module¶
It is possible to access the top-level namespace, as a TypeNode
, with a special variable: @top_level
. The following example shows its utility:
A_CONSTANT = 0
{% if @top_level.has_constant?("A_CONSTANT") %}
puts "this is printed"
{% else %}
puts "this is not printed"
{% end %}
メソッドの情報¶
マクロが実行される際に、@def
という特別なインスタンス変数を使うことで、メソッド、もしくはマクロにアクセスすることが可能です。この変数の型は Def
で、もしマクロがメソッドの外で実行されていた場合は NilLiteral
となります。
例:
module Foo
def Foo.boo(arg1, arg2)
{% @def.receiver %} # => Foo
{% @def.name %} # => boo
{% @def.args %} # => [arg1, arg2]
end
end
Foo.boo(0, 1)
定数¶
マクロは定数にアクセスすることができます。例をあげます。
VALUES = [1, 2, 3]
{% for value in VALUES %}
puts {{value}}
{% end %}
もし定数が型を示していれば、そのとき得られるのは TypeNode
となります。
ネストしたマクロ¶
いくつかのマクロを生成するようなマクロを定義することができます。このとき、内側のマクロが外側のマクロによって評価されるのを防ぐため、バックラッシュでエスケープする必要があります。
macro define_macros(*names)
{% for name in names %}
macro greeting_for_{{name.id}}(greeting)
\{% if greeting == "hola" %}
"¡hola {{name.id}}!"
\{% else %}
"\{{greeting.id}} {{name.id}}"
\{% end %}
end
{% end %}
end
# This generates:
#
# macro greeting_for_alice(greeting)
# {% if greeting == "hola" %}
# "¡hola alice!"
# {% else %}
# "{{greeting.id}} alice"
# {% end %}
# end
# macro greeting_for_bob(greeting)
# {% if greeting == "hola" %}
# "¡hola bob!"
# {% else %}
# "{{greeting.id}} bob"
# {% end %}
# end
define_macros alice, bob
greeting_for_alice "hello" # => "hello alice"
greeting_for_bob "hallo" # => "hallo bob"
greeting_for_alice "hej" # => "hej alice"
greeting_for_bob "hola" # => "¡hola bob!"
verbatim¶
ネストしたマクロを定義する他の方法としては、verbatim
という特別なメソッドを使うものがあります。これを使うことで、内側のマクロをエスケープする必要がなくなります。
macro define_macros(*names)
{% for name in names %}
macro greeting_for_{{name.id}}(greeting)
# name は verbatim ブロックの中では有効ではありません
\{% name = {{name.stringify}} %}
{% verbatim do %}
{% if greeting == "hola" %}
"¡hola {{name.id}}!"
{% else %}
"{{greeting.id}} {{name.id}}"
{% end %}
{% end %}
end
{% end %}
end
# This generates:
#
# macro greeting_for_alice(greeting)
# {% name = "alice" %}
# {% if greeting == "hola" %}
# "¡hola {{name.id}}!"
# {% else %}
# "{{greeting.id}} {{name.id}}"
# {% end %}
# end
# macro greeting_for_bob(greeting)
# {% name = "bob" %}
# {% if greeting == "hola" %}
# "¡hola {{name.id}}!"
# {% else %}
# "{{greeting.id}} {{name.id}}"
# {% end %}
# end
define_macros alice, bob
greeting_for_alice "hello" # => "hello alice"
greeting_for_bob "hallo" # => "hallo bob"
greeting_for_alice "hej" # => "hej alice"
greeting_for_bob "hola" # => "¡hola bob!"
内側のマクロの変数は verbatim
ブロックの中では有効ではないことに注意してください。ブロックの中身は「そのまま」文字列のようにコンパイラに渡され、再度検査されます。
コメント¶
コメント中のマクロの式は、コンパイルされるコードと同様に評価されます。これは関係するドキュメントコメントを生成するのに使えます。
{% for name, index in ["foo", "bar", "baz"] %}
# Provides a placeholder {{name.id}} method. Always returns {{index}}.
def {{name.id}}
{{index}}
end
{% end %}
この評価は埋め込みだけでなくディレクティブに対してもはたらきます。結果として、マクロをコメントアウトすることはできません。
macro a
# {% if false %}
puts 42
# {% end %}
end
a
上記の式は何も出力しないでしょう。
落とし穴¶
マクロを書く際 (とくにマクロ定義の外で)、マクロによって生成されたコードは、メインのプログラムのコードにマージされる前から、それ自身として有効なコードである必要があることを覚えておいてください。要するに、例えば、マクロは冒頭の case
が生成するコードに含まれていないようなcase
式の when
節を生成することはできない、ということです。
次が、そのような無効なマクロの例になります。
case 42
{% for klass in [Int32, String] %} # Syntax Error: unexpected token: {% (expecting when, else or end)
when {{klass.id}}
p "is {{klass}}"
{% end %}
end
case
がマクロの外側にあることに注目してください。マクロによって生成されたコードは2つの孤立した when
節からなりますが、これは有効な Crystal のコードではありません。case
をマクロによって生成されるコードに含めるために、begin
と end
を使うことができます。
{% begin %}
case 42
{% for klass in [Int32, String] %}
when {{klass.id}}
p "is {{klass}}"
{% end %}
end
{% end %}