1.9K Views
December 23, 21
スライド概要
https://rubykaigi.org/2021-takeout/presentations/pink_bangbi.html
Ruby / Vim / C++
Use Macro all the time ~ マクロを使いまくろ ~ RubyKaigi Takeout 2021
やあ、みんな おはよう こんにちわ こんばんわ
みんな Ruby を使ってい る???
Ruby を使っていると…
1 CONST_VALUE = [1, 2, 3] こういう定数定義を
1 CONST_VALUE = [1, 2, 3] こういう定数定義を 1 CONST_VALUE = [1, 2, 3].freeze 暗黙的に `freeze` させたり
1 puts config.hoge_flag 2 puts config.foo_flag みたいなデバッグ出力を
1 puts config.hoge_flag 2 puts config.foo_flag みたいなデバッグ出力を 1 # output: 2 "config.hoge_flag # => true" 3 "config.foo_flag # => false" みたいに出力内容と出力結果を一緒に出力させたり
1 ![a, b, c] こういうコードを
1 ![a, b, c] こういうコードを 1 { a: a, b: b, c: c } みたいに Hash で展開させたりとか
やりたくなりますよね!!
それマクロでできるよ!!!!
自己紹介 名前:osyo Twitter : @pink_bangbi https://twitter.com/pink_bangbi github : osyo-manga https://github.com/osyo-manga ブログ : Secret Garden(Instrumental) http://secret-garden.hatenablog.com Rails エンジニア 好きな Ruby の機能は Refinements RubyKaigi は初参加 10 / 89
今日話すこと 11 / 89
今日話すこと Ruby でマクロを実装した話 11 / 89
アジェンダ Ruby のマクロとは AST とは マクロの変換プロセスの解説 Rensei - 錬成 - AST から Ruby のコードを生成するライブラリ Kenma - 研磨 - 任意の AST を別の AST に変換するライブラリ マクロの使用例 これからの課題 12 / 89
マクロとは? 13 / 89
マクロとは 14 / 89
マクロとは 世の中にはいろいろなマクロがある C言語マクロ、LISP マクロ、Rust マクロ、エクセルのマクロ etc… マクロと言ってもそれぞれ意味が異なる 14 / 89
マクロとは 世の中にはいろいろなマクロがある C言語マクロ、LISP マクロ、Rust マクロ、エクセルのマクロ etc… マクロと言ってもそれぞれ意味が異なる この登壇では『Ruby の AST を別の AST に変換すること』を『Ruby のマクロ』と定義 14 / 89
マクロとは 世の中にはいろいろなマクロがある C言語マクロ、LISP マクロ、Rust マクロ、エクセルのマクロ etc… マクロと言ってもそれぞれ意味が異なる この登壇では『Ruby の AST を別の AST に変換すること』を『Ruby のマクロ』と定義 この『マクロ』を使用すると Ruby のコードを構文レベルで変更する事ができる 例えば `hoge.foo` を `hoge&.foo` に変更したり 理論上は valid な Ruby のコードであればどんなコードにでも変換できる 14 / 89
そもそも AST って? 15 / 89
AST とは 16 / 89
AST とは AST とは抽象構文木(Abstract Syntax Tree)の略 16 / 89
AST とは AST とは抽象構文木(Abstract Syntax Tree)の略 今回は `RubyVM::AbstractSyntaxTree` を使用する で使用される実データは `RubyVM::AbstractSyntaxTree::Node` だが、 このスライドでは一部配列形式で記述している 以下 `RubyVM::AST::Node` と略 `RubyVM::AbstractSyntaxTree` 16 / 89
AST とは AST とは抽象構文木(Abstract Syntax Tree)の略 今回は `RubyVM::AbstractSyntaxTree` を使用する で使用される実データは `RubyVM::AbstractSyntaxTree::Node` だが、 このスライドでは一部配列形式で記述している 以下 `RubyVM::AST::Node` と略 `RubyVM::AbstractSyntaxTree` 抽象化されたデータ構造なので異なるコードでも同じ AST になることがある 例えば `cond ? foo : bar` と `if cond; foo; else bar; end` は同じ AST になる 16 / 89
AST とは AST とは抽象構文木(Abstract Syntax Tree)の略 今回は `RubyVM::AbstractSyntaxTree` を使用する で使用される実データは `RubyVM::AbstractSyntaxTree::Node` だが、 このスライドでは一部配列形式で記述している 以下 `RubyVM::AST::Node` と略 `RubyVM::AbstractSyntaxTree` 抽象化されたデータ構造なので異なるコードでも同じ AST になることがある 例えば `cond ? foo : bar` と `if cond; foo; else bar; end` は同じ AST になる AST の種類は構文ごとに細かく分かれていて100種類以上ある 16 / 89
RubyVM::AbstractSyntaxTree のサンプル
[コード]
1
2
3
node = RubyVM::AbstractSyntaxTree.parse("1 + 2")
# Proc
AST
# RubyVM::AbstractSyntaxTree.of(-> { 1 + 2 })
4
5
6
7
pp node
pp node.type
pp node.children
8
9
10
11
12
オブジェクトを渡すとブロックの中身の
を返す
node2 = node.children.last
pp node2
pp node2.type
pp node2.children
17 / 89
RubyVM::AbstractSyntaxTree のサンプル
[コード]
`RubyVM::AbstractSyntaxTree.parse`
1
node = RubyVM::AbstractSyntaxTree.parse("1 + 2")
2
3
# Proc
AST
# RubyVM::AbstractSyntaxTree.of(-> { 1 + 2 })
4
5
pp node
6
7
pp node.type
pp node.children
オブジェクトを渡すとブロックの中身の
を返す
で `1 + 2` の AST を取得する
8
9
10
11
node2 = node.children.last
pp node2
pp node2.type
12
pp node2.children
17 / 89
RubyVM::AbstractSyntaxTree のサンプル
[コード]
`RubyVM::AbstractSyntaxTree.parse`
1
2
3
4
5
node = RubyVM::AbstractSyntaxTree.parse("1 + 2")
# Proc
AST
# RubyVM::AbstractSyntaxTree.of(-> { 1 + 2 })
6
7
pp node.type
pp node.children
オブジェクトを渡すとブロックの中身の
pp node
を返す
で `1 + 2` の AST を取得する
`.of` で `Proc` から取得することもでき
る
8
9
10
11
node2 = node.children.last
pp node2
pp node2.type
12
pp node2.children
17 / 89
RubyVM::AbstractSyntaxTree のサンプル
[コード]
1
2
3
`RubyVM::AbstractSyntaxTree.parse`
node = RubyVM::AbstractSyntaxTree.parse("1 + 2")
# Proc
AST
# RubyVM::AbstractSyntaxTree.of(-> { 1 + 2 })
オブジェクトを渡すとブロックの中身の
4
5
6
7
pp node
pp node.type
pp node.children
8
9
10
11
node2 = node.children.last
pp node2
pp node2.type
12
pp node2.children
[出力結果]
1
2
3
4
を返す
で `1 + 2` の AST を取得する
`.of` で `Proc` から取得することもでき
る
取得した AST のデータはこのようになっ
ている
これが `RubyVM::AbstractSyntaxTree::Node`
のデータ形式
(SCOPE@1:0-1:5
tbl: []
args: nil
body: (OPCALL@1:0-1:5 (LIT@1:0-1:1 1) :+ (LIST@1:4-1:5 (LIT@1:4-1:5 2) nil)))
17 / 89
RubyVM::AbstractSyntaxTree のサンプル
[コード]
1
2
3
node = RubyVM::AbstractSyntaxTree.parse("1 + 2")
# Proc
AST
# RubyVM::AbstractSyntaxTree.of(-> { 1 + 2 })
4
5
pp node
6
7
8
オブジェクトを渡すとブロックの中身の
を返す
AST は `type` と `children` の2つの情報
を持っており、これが木構造になっている
pp node.type
pp node.children
9
10
11
node2 = node.children.last
pp node2
pp node2.type
12
pp node2.children
[出力結果]
1
2
3
4
:SCOPE
[[],
nil,
(OPCALL@1:0-1:5 (LIT@1:0-1:1 1) :+ (LIST@1:4-1:5 (LIT@1:4-1:5 2) nil))]
18 / 89
RubyVM::AbstractSyntaxTree のサンプル
[コード]
1
2
3
node = RubyVM::AbstractSyntaxTree.parse("1 + 2")
# Proc
AST
# RubyVM::AbstractSyntaxTree.of(-> { 1 + 2 })
4
5
pp node
6
7
8
オブジェクトを渡すとブロックの中身の
pp node.type
pp node.children
9
10
11
node2 = node.children.last
pp node2
pp node2.type
12
pp node2.children
を返す
AST は `type` と `children` の2つの情報
を持っており、これが木構造になっている
大枠に `SCOPE` という AST があり、その
下に `1 + 2` の AST がぶら下がっている
[出力結果]
1
2
3
4
:SCOPE
[[],
nil,
(OPCALL@1:0-1:5 (LIT@1:0-1:1 1) :+ (LIST@1:4-1:5 (LIT@1:4-1:5 2) nil))]
18 / 89
RubyVM::AbstractSyntaxTree のサンプル
[コード]
の AST を取得する場合は `SCOPE`
の子から取得する
`1 + 2`
1
2
3
node = RubyVM::AbstractSyntaxTree.parse("1 + 2")
# Proc
AST
# RubyVM::AbstractSyntaxTree.of(-> { 1 + 2 })
4
5
pp node
6
7
pp node.type
pp node.children
オブジェクトを渡すとブロックの中身の
を返す
8
9
10
11
node2 = node.children.last
pp node2
pp node2.type
12
pp node2.children
[出力結果]
1
(OPCALL@1:0-1:5 (LIT@1:0-1:1 1) :+ (LIST@1:4-1:5 (LIT@1:4-1:5 2) nil))
19 / 89
RubyVM::AbstractSyntaxTree のサンプル
[コード]
の AST もまた `type` と
`children` を持っている
`1 + 2`
1
2
3
node = RubyVM::AbstractSyntaxTree.parse("1 + 2")
# Proc
AST
# RubyVM::AbstractSyntaxTree.of(-> { 1 + 2 })
4
5
pp node
6
7
pp node.type
pp node.children
オブジェクトを渡すとブロックの中身の
を返す
8
9
10
node2 = node.children.last
pp node2
11
12
pp node2.type
pp node2.children
[出力結果]
1
2
:OPCALL
[(LIT@1:0-1:1 1), :+, (LIST@1:4-1:5 (LIT@1:4-1:5 2) nil)]
20 / 89
RubyVM::AbstractSyntaxTree のサンプル
[コード]
の AST もまた `type` と
`children` を持っている
このように AST は複数の AST から成り立
っている
`1 + 2`
1
2
3
node = RubyVM::AbstractSyntaxTree.parse("1 + 2")
# Proc
AST
# RubyVM::AbstractSyntaxTree.of(-> { 1 + 2 })
4
5
pp node
6
7
pp node.type
pp node.children
オブジェクトを渡すとブロックの中身の
を返す
8
9
10
node2 = node.children.last
pp node2
11
12
pp node2.type
pp node2.children
[出力結果]
1
2
:OPCALL
[(LIT@1:0-1:1 1), :+, (LIST@1:4-1:5 (LIT@1:4-1:5 2) nil)]
20 / 89
AST の対応表(一部) type コード AST `LIT` `1` `[:LIT, [1]]` `STR` `"string"` `[:STR, ["string"]]` `VCALL` `func` `[:VCALL, [:func]]` `CALL` `func.bar` `[:CALL, [[:VCALL, [:func]], :bar, nil]]` `QCALL` `func&.bar` `[:QCALL, [[:VCALL, [:func]], :bar, nil]]` 文字列リテラル メソッド呼び出し `.` 呼び出し `&.` 呼び出し `OPCALL` `1 + a` `[:OPCALL, [[:LIT, [1]], :+, [:LIST, [[:VCALL, [:a]], nil]]]]` 演算子呼び出し `AND` `a && b` `[:AND, [[:LIT, [1]], [:VCALL, [:b]]]]` && 演算子 NOTE: 実データは `RubyVM::AST::Node` になるがわかりやすく配列で表記している 意味 数値やシンボルリテ ラルなど 21 / 89
マクロの変換プロセスの解説 22 / 89
マクロの変換プロセスの解説 簡単な例として `hoge.foo` を `hoge&.foo` に変換してみる 22 / 89
AST の変換プロセス 変換前の Ruby のコード 1 hoge.foo 23 / 89
AST の変換プロセス 変換前の Ruby のコード 1 hoge.foo このコードを AST に変換する 23 / 89
AST の変換プロセス 変換前の Ruby のコード 1 hoge.foo AST データ(RubyVM::AST::Node) 1 2 (CALL@9:9-9:17 (VCALL@9:9-9:13 :hoge) :foo nil) 24 / 89
AST の変換プロセス 変換前の Ruby のコード 1 hoge.foo AST データ(RubyVM::AST::Node) 1 2 (CALL@9:9-9:17 (VCALL@9:9-9:13 :hoge) :foo nil) このデータ構造のままだと扱いづらいの で一旦自前で配列に変換する 24 / 89
AST の変換プロセス 変換前の Ruby のコード 1 hoge.foo AST データ(RubyVM::AST::Node) 1 2 (CALL@9:9-9:17 (VCALL@9:9-9:13 :hoge) :foo nil) AST (配列) 1 2 [:CALL, [[:VCALL, [:hoge]], :foo, nil]] 25 / 89
AST の変換プロセス 変換前の Ruby のコード 1 hoge.foo AST データ(RubyVM::AST::Node) 1 2 (CALL@9:9-9:17 (VCALL@9:9-9:13 :hoge) :foo nil) AST (配列) 1 2 [:CALL, [[:VCALL, [:hoge]], :foo, nil]] AST 内の `CALL` という命令を これが `.` 演算子の命令 25 / 89
AST の変換プロセス 変換前の Ruby のコード 1 hoge.foo AST データ(RubyVM::AbstractSyntaxTree::Node) 1 2 (CALL@9:9-9:17 (VCALL@9:9-9:13 :hoge) :foo nil) AST (配列) 1 2 [:QCALL, [[:VCALL, [:hoge]], :foo, nil]] `QCALL` という命令に置き換える `QCALL` が `&.` 演算子の命令 26 / 89
AST の変換プロセス 変換前の Ruby のコード 1 hoge.foo AST データ(RubyVM::AbstractSyntaxTree::Node) 1 2 (CALL@9:9-9:17 (VCALL@9:9-9:13 :hoge) :foo nil) AST (配列) 1 2 [:QCALL, [[:VCALL, [:hoge]], :foo, nil]] この AST を Ruby のコードに変換する 27 / 89
AST の変換プロセス 変換前の Ruby のコード 1 hoge.foo 変換後の Ruby のコード 1 hoge&.foo AST データ(RubyVM::AbstractSyntaxTree::Node) 1 2 (CALL@9:9-9:17 (VCALL@9:9-9:13 :hoge) :foo nil) AST (配列) 1 2 [:QCALL, [[:VCALL, [:hoge]], :foo, nil]] 28 / 89
AST の変換プロセス 変換前の Ruby のコード 1 hoge.foo 変換後の Ruby のコード 1 hoge&.foo AST データ(RubyVM::AbstractSyntaxTree::Node) 1 2 (CALL@9:9-9:17 (VCALL@9:9-9:13 :hoge) :foo nil) AST (配列) 1 2 [:QCALL, [[:VCALL, [:hoge]], :foo, nil]] このようにして AST を書き換えることで別の Ruby のコードへと変更する事ができる 28 / 89
これがマクロだ!!! 29 / 89
このようにして AST の中身を書き換える事をマクロと定義する 30 / 89
このようにして AST の中身を書き換える事をマクロと定義する なのでマクロは AST から Ruby のコードへと変換する機能が必要になる 30 / 89
〜 AST から Ruby のコードに変換する 〜 Rensei - 錬成 - 31 / 89
Rensei - 錬成 - 32 / 89
Rensei - 錬成 - AST から Ruby のコードに復元するライブラリ https://github.com/osyo-manga/gem-rensei 32 / 89
Rensei - 錬成 - AST から Ruby のコードに復元するライブラリ https://github.com/osyo-manga/gem-rensei このライブラリを用いて `RubyVM::AST::Node` から Ruby のコードへと変換する からではなくて配列からも変換することができる 詳しくはこちらを(日本語) 【Ruby Advent Calendar 2020】Ruby の AST から Ruby のソースコードを復元しよう【1日目】 - Secret Garden(Instrumental) `RubyVM::AST::Node` https://secret-garden.hatenablog.com/entry/2020/12/01/093316 32 / 89
Rensei - 錬成 - AST から Ruby のコードに復元するライブラリ https://github.com/osyo-manga/gem-rensei このライブラリを用いて `RubyVM::AST::Node` から Ruby のコードへと変換する からではなくて配列からも変換することができる 詳しくはこちらを(日本語) 【Ruby Advent Calendar 2020】Ruby の AST から Ruby のソースコードを復元しよう【1日目】 - Secret Garden(Instrumental) `RubyVM::AST::Node` https://secret-garden.hatenablog.com/entry/2020/12/01/093316 AST が既に抽象化したデータになっているので元のコードを完全復元するわけではない ので注意 コメントの情報などは復元できない `()` などが追加されることもある 32 / 89
Rensei - 錬成 - AST から Ruby のコードに復元するライブラリ https://github.com/osyo-manga/gem-rensei このライブラリを用いて `RubyVM::AST::Node` から Ruby のコードへと変換する からではなくて配列からも変換することができる 詳しくはこちらを(日本語) 【Ruby Advent Calendar 2020】Ruby の AST から Ruby のソースコードを復元しよう【1日目】 - Secret Garden(Instrumental) `RubyVM::AST::Node` https://secret-garden.hatenablog.com/entry/2020/12/01/093316 AST が既に抽象化したデータになっているので元のコードを完全復元するわけではない ので注意 コメントの情報などは復元できない `()` などが追加されることもある 名前の由来は新しく Ruby のコードを生成する、という意味で付けた 32 / 89
Rensei の使い方 [コード] 1 2 3 require "rensei" 4 5 6 7 src = Rensei.unparse(node) puts src # => (((1 + 2) && hoge) || bar) 8 9 puts Rensei.unparse([:OPCALL, [[:LIT, [1]], :+, [:LIST, [[:LIT, [2]], nil]]]]) # => (1 + 2) node = RubyVM::AbstractSyntaxTree.parse("1 + 2 && hoge || bar") 33 / 89
Rensei の使い方 [コード] 1 require "rensei" 2 3 4 5 node = RubyVM::AbstractSyntaxTree.parse("1 + 2 && hoge || bar") src = Rensei.unparse(node) puts src 6 7 # => (((1 + 2) && hoge) || bar) 8 puts Rensei.unparse([:OPCALL, [[:LIT, [1]], :+, [:LIST, [[:LIT, [2]], nil]]]]) 9 # => (1 + 2) Ruby のコードを AST に変更し 33 / 89
Rensei の使い方 [コード] 1 require "rensei" 2 3 node = RubyVM::AbstractSyntaxTree.parse("1 + 2 && hoge || bar") 4 5 src = Rensei.unparse(node) puts src 6 7 # => (((1 + 2) && hoge) || bar) 8 puts Rensei.unparse([:OPCALL, [[:LIT, [1]], :+, [:LIST, [[:LIT, [2]], nil]]]]) 9 # => (1 + 2) Ruby のコードを AST に変更し `RubyVM::AST::Node` から Ruby のコードへと復元し 33 / 89
Rensei の使い方 [コード] 1 require "rensei" 2 3 node = RubyVM::AbstractSyntaxTree.parse("1 + 2 && hoge || bar") 4 src = Rensei.unparse(node) 5 6 7 puts src # => (((1 + 2) && hoge) || bar) 8 puts Rensei.unparse([:OPCALL, [[:LIT, [1]], :+, [:LIST, [[:LIT, [2]], nil]]]]) 9 # => (1 + 2) Ruby のコードを AST に変更し `RubyVM::AST::Node` から Ruby のコードへと復元し 結果このような Ruby のコードが生成される `()` など元のコードにはない情報が付加されている 33 / 89
Rensei の使い方 [コード] 1 require "rensei" 2 3 node = RubyVM::AbstractSyntaxTree.parse("1 + 2 && hoge || bar") 4 5 src = Rensei.unparse(node) puts src 6 7 # => (((1 + 2) && hoge) || bar) 8 9 puts Rensei.unparse([:OPCALL, [[:LIT, [1]], :+, [:LIST, [[:LIT, [2]], nil]]]]) # => (1 + 2) Ruby のコードを AST に変更し `RubyVM::AST::Node` から Ruby のコードへと復元し 結果このような Ruby のコードが生成される `()` など元のコードにはない情報が付加されている また配列の AST データからも復元することができる 33 / 89
Rensei を利用してマクロを実装した!!! 34 / 89
〜 Ruby で簡単にマクロを扱えるようにする 〜 Kenma - 研磨 - 35 / 89
Kenma - 研磨 - 36 / 89
Kenma - 研磨 - 任意の AST を別の AST に変換するライブラリ https://github.com/osyo-manga/gem-kenma 36 / 89
Kenma - 研磨 - 任意の AST を別の AST に変換するライブラリ https://github.com/osyo-manga/gem-kenma これを利用すると簡単にマクロを実現する事ができる 36 / 89
Kenma - 研磨 - 任意の AST を別の AST に変換するライブラリ https://github.com/osyo-manga/gem-kenma これを利用すると簡単にマクロを実現する事ができる 名前の由来は Ruby のコードを更に磨き上げるという意味で付けた 36 / 89
Kenma の使い方 37 / 89
マクロを定義しよう! 38 / 89
マクロの定義方法 39 / 89
マクロの定義方法 マクロの定義方法には複数の種類がある 39 / 89
マクロの定義方法 マクロの定義方法には複数の種類がある 関数マクロ メソッド呼び出しに対して AST を置き換える 39 / 89
マクロの定義方法 マクロの定義方法には複数の種類がある 関数マクロ メソッド呼び出しに対して AST を置き換える ノードマクロ 特定の AST の種類に対して AST を置き換える 39 / 89
マクロの定義方法 マクロの定義方法には複数の種類がある 関数マクロ メソッド呼び出しに対して AST を置き換える ノードマクロ 特定の AST の種類に対して AST を置き換える パターンマクロ 特定の Ruby の構文に対して AST を置き換える 39 / 89
マクロの定義方法 マクロの定義方法には複数の種類がある 関数マクロ メソッド呼び出しに対して AST を置き換える ノードマクロ 特定の AST の種類に対して AST を置き換える パターンマクロ 特定の Ruby の構文に対して AST を置き換える いずれかの定義方法でも『 `AST` を受け取って `AST` を返すメソッド』を定義すること になる 39 / 89
関数マクロ 40 / 89
関数マクロ 41 / 89
関数マクロ レシーバのないメソッド呼び出しを別の AST に置き換えるマクロ 41 / 89
関数マクロ レシーバのないメソッド呼び出しを別の AST に置き換えるマクロ `cat!` を `'nyaaaaan'` に置き換えるマクロを書いてみる 41 / 89
関数マクロ レシーバのないメソッド呼び出しを別の AST に置き換えるマクロ `cat!` を `'nyaaaaan'` に置き換えるマクロを書いてみる 1 2 puts cat! # AST => [:FCALL, [:puts, [:LIST, [[:FCALL, [:cat!, nil]], nil]]]] を 41 / 89
関数マクロ レシーバのないメソッド呼び出しを別の AST に置き換えるマクロ `cat!` を `'nyaaaaan'` に置き換えるマクロを書いてみる 1 2 puts cat! # AST => [:FCALL, [:puts, [:LIST, [[:FCALL, [:cat!, nil]], nil]]]] を 1 2 puts 'nyaaaaan' # AST => [:FCALL, [:puts, [:LIST, [[:STR, ["nyaaaaan"]], nil]]]] のように変換する 41 / 89
関数マクロを定義する
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
require "kenma"
using Kenma::Refine::Source
module CatMacro
using Kenma::Macroable
def cat!
RubyVM::AbstractSyntaxTree.parse("'nyaaaaan'")
end
macro_function :cat!
end
body = proc {
use_macro! CatMacro
puts cat!
}
compiled = Kenma.compile_of(body)
pp compiled
src = compiled.source
puts src
eval(src)
42 / 89
関数マクロを定義する
1
2
3
4
5
6
require "kenma"
using Kenma::Refine::Source
module CatMacro
using Kenma::Macroable
7
8
9
10
def cat!
RubyVM::AbstractSyntaxTree.parse("'nyaaaaan'")
end
11
12
13
14
macro_function :cat!
end
15
16
17
18
use_macro! CatMacro
19
20
21
22
23
24
マクロを定義するためのモジュールを定
義する
body = proc {
puts cat!
}
compiled = Kenma.compile_of(body)
pp compiled
src = compiled.source
puts src
eval(src)
43 / 89
関数マクロを定義する
1
2
3
4
5
6
require "kenma"
using Kenma::Refine::Source
module CatMacro
using Kenma::Macroable
7
8
9
10
def cat!
RubyVM::AbstractSyntaxTree.parse("'nyaaaaan'")
end
11
12
13
14
macro_function :cat!
end
15
16
17
18
use_macro! CatMacro
19
20
21
22
23
24
マクロを定義するためのモジュールを定
義する
このモジュール内でマクロで使用するメ
ソッドを定義する
body = proc {
puts cat!
}
compiled = Kenma.compile_of(body)
pp compiled
src = compiled.source
puts src
eval(src)
43 / 89
関数マクロを定義する
1
require "kenma"
2
3
4
using Kenma::Refine::Source
5
6
7
8
module CatMacro
using Kenma::Macroable
def cat!
9
10
RubyVM::AbstractSyntaxTree.parse("'nyaaaaan'")
end
11
macro_function :cat!
12
13
14
15
16
17
18
19
20
21
22
23
24
end
マクロを定義するためのモジュールを定
義する
このモジュール内でマクロで使用するメ
ソッドを定義する
また `using` するとマクロを定義するた
めに必要なメソッドが使えるようになる
body = proc {
use_macro! CatMacro
puts cat!
}
compiled = Kenma.compile_of(body)
pp compiled
src = compiled.source
puts src
eval(src)
43 / 89
関数マクロを定義する
1
require "kenma"
2
3
4
using Kenma::Refine::Source
5
6
module CatMacro
using Kenma::Macroable
7
8
def cat!
9
10
RubyVM::AbstractSyntaxTree.parse("'nyaaaaan'")
end
11
12
13
14
macro_function :cat!
end
15
16
17
18
use_macro! CatMacro
19
20
21
22
23
24
body = proc {
puts cat!
}
compiled = Kenma.compile_of(body)
pp compiled
src = compiled.source
puts src
eval(src)
マクロを定義するためのモジュールを定
義する
このモジュール内でマクロで使用するメ
ソッドを定義する
また `using` するとマクロを定義するた
めに必要なメソッドが使えるようになる
`macro_function` など
他にも `Kernel` や `Module` に必要なメソッド
が定義される
43 / 89
関数マクロを定義する
1
require "kenma"
2
3
4
using Kenma::Refine::Source
5
6
module CatMacro
using Kenma::Macroable
マクロとして呼び出すメソッドを定義す
る
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def cat!
RubyVM::AbstractSyntaxTree.parse("'nyaaaaan'")
end
macro_function :cat!
end
body = proc {
use_macro! CatMacro
puts cat!
}
compiled = Kenma.compile_of(body)
pp compiled
src = compiled.source
puts src
eval(src)
44 / 89
関数マクロを定義する
1
require "kenma"
2
3
4
using Kenma::Refine::Source
5
6
module CatMacro
using Kenma::Macroable
7
8
def cat!
9
10
RubyVM::AbstractSyntaxTree.parse("'nyaaaaan'")
end
11
macro_function :cat!
12
13
14
15
16
17
18
19
20
21
22
23
24
マクロとして呼び出すメソッドを定義す
る
ここで返した AST が呼び出し元のメソッ
ドと置き換わる
`"nyaaaaan"`
という文字列の AST を返してる
end
body = proc {
use_macro! CatMacro
puts cat!
}
compiled = Kenma.compile_of(body)
pp compiled
src = compiled.source
puts src
eval(src)
44 / 89
関数マクロを定義する
1
require "kenma"
2
3
4
using Kenma::Refine::Source
5
6
module CatMacro
using Kenma::Macroable
7
8
def cat!
9
10
RubyVM::AbstractSyntaxTree.parse("'nyaaaaan'")
end
11
macro_function :cat!
12
13
14
15
16
17
18
19
20
21
22
23
24
マクロとして呼び出すメソッドを定義す
る
ここで返した AST が呼び出し元のメソッ
ドと置き換わる
end
`"nyaaaaan"`
1
という文字列の AST を返してる
puts cat!
body = proc {
use_macro! CatMacro
puts cat!
}
compiled = Kenma.compile_of(body)
pp compiled
src = compiled.source
puts src
eval(src)
44 / 89
関数マクロを定義する
1
require "kenma"
2
3
4
using Kenma::Refine::Source
5
6
module CatMacro
using Kenma::Macroable
7
8
def cat!
9
10
RubyVM::AbstractSyntaxTree.parse("'nyaaaaan'")
end
11
macro_function :cat!
12
13
14
15
16
17
18
19
20
21
22
23
24
マクロとして呼び出すメソッドを定義す
る
ここで返した AST が呼び出し元のメソッ
ドと置き換わる
end
body = proc {
use_macro! CatMacro
puts cat!
`"nyaaaaan"`
1
という文字列の AST を返してる
puts cat!
↓↓↓↓↓
1
puts "nyaaaaan"
}
compiled = Kenma.compile_of(body)
pp compiled
src = compiled.source
puts src
eval(src)
44 / 89
関数マクロを定義する
1
require "kenma"
2
3
4
using Kenma::Refine::Source
5
6
module CatMacro
using Kenma::Macroable
7
8
def cat!
9
10
RubyVM::AbstractSyntaxTree.parse("'nyaaaaan'")
end
11
macro_function :cat!
12
13
14
15
16
17
18
19
20
21
22
23
24
マクロとして呼び出すメソッドを定義す
る
ここで返した AST が呼び出し元のメソッ
ドと置き換わる
end
body = proc {
use_macro! CatMacro
puts cat!
}
compiled = Kenma.compile_of(body)
pp compiled
src = compiled.source
puts src
eval(src)
`"nyaaaaan"`
1
という文字列の AST を返してる
puts cat!
↓↓↓↓↓
1
puts "nyaaaaan"
また
`RubyVM::AbstractSyntaxTree.parse`
を
44 / 89
関数マクロを定義する
1
require "kenma"
2
3
4
using Kenma::Refine::Source
5
6
module CatMacro
using Kenma::Macroable
7
8
ast { 'nyaaaaan' }
end
11
macro_function :cat!
15
16
17
18
19
20
21
22
23
24
に置き換える事ができる
def cat!
9
10
12
13
14
`ast {}`
end
body = proc {
use_macro! CatMacro
puts cat!
}
compiled = Kenma.compile_of(body)
pp compiled
src = compiled.source
puts src
eval(src)
45 / 89
関数マクロを定義する
1
require "kenma"
2
3
4
using Kenma::Refine::Source
5
6
module CatMacro
using Kenma::Macroable
7
8
ast { 'nyaaaaan' }
end
11
macro_function :cat!
15
16
17
18
19
20
21
22
23
24
`using Kenma::Macroable`
def cat!
9
10
12
13
14
に置き換える事ができる
`ast {}` はブロックの中のコードの AST
を返す
`ast {}`
end
body = proc {
use_macro! CatMacro
puts cat!
}
compiled = Kenma.compile_of(body)
pp compiled
src = compiled.source
puts src
eval(src)
使える
1
2
3
4
5
6
7
8
9
したコンテキストで
pp ast { 'nyaaaaan' }
# => (STR@5:9-5:19 "nyaaaaan")
pp ast { 1 + 2 }
# => (OPCALL@12:10-12:15
#
(LIT@12:10-12:11 1) :+
#
(LIST@12:14-12:15
#
(LIT@12:14-12:15 2)
#
nil))
45 / 89
関数マクロを定義する
1
require "kenma"
2
3
4
using Kenma::Refine::Source
5
6
module CatMacro
using Kenma::Macroable
7
8
`using Kenma::Macroable`
def cat!
9
10
ast { 'nyaaaaan' }
end
11
12
13
14
macro_function :cat!
end
15
16
17
18
use_macro! CatMacro
19
20
21
22
23
24
に置き換える事ができる
`ast {}` はブロックの中のコードの AST
を返す
`ast {}`
body = proc {
puts cat!
}
compiled = Kenma.compile_of(body)
pp compiled
src = compiled.source
puts src
eval(src)
使える
1
2
3
4
5
6
7
8
9
したコンテキストで
pp ast { 'nyaaaaan' }
# => (STR@5:9-5:19 "nyaaaaan")
pp ast { 1 + 2 }
# => (OPCALL@12:10-12:15
#
(LIT@12:10-12:11 1) :+
#
(LIST@12:14-12:15
#
(LIT@12:14-12:15 2)
#
nil))
最後にマクロ関数であることを宣言する
45 / 89
関数マクロを定義する
1
2
3
4
5
6
using Kenma::Refine::Source
module CatMacro
using Kenma::Macroable
7
8
9
10
`using Kenma::Macroable`
def cat!
ast { 'nyaaaaan' }
end
11
12
13
14
macro_function :cat!
end
15
16
17
18
use_macro! CatMacro
19
20
21
22
23
24
に置き換える事ができる
`ast {}` はブロックの中のコードの AST
を返す
`ast {}`
require "kenma"
body = proc {
puts cat!
}
compiled = Kenma.compile_of(body)
pp compiled
src = compiled.source
puts src
eval(src)
使える
1
2
3
4
5
6
7
8
9
したコンテキストで
pp ast { 'nyaaaaan' }
# => (STR@5:9-5:19 "nyaaaaan")
pp ast { 1 + 2 }
# => (OPCALL@12:10-12:15
#
(LIT@12:10-12:11 1) :+
#
(LIST@12:14-12:15
#
(LIT@12:14-12:15 2)
#
nil))
最後にマクロ関数であることを宣言する
ここまでがマクロの定義になる
45 / 89
マクロを使おう! 46 / 89
定義した関数マクロを使う
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
require "kenma"
using Kenma::Refine::Source
module CatMacro
using Kenma::Macroable
def cat!
ast { 'nyaaaaan' }
end
macro_function :cat!
end
body = proc {
use_macro! CatMacro
puts cat!
}
compiled = Kenma.compile_of(body)
pp compiled
src = compiled.source
puts src
eval(src)
47 / 89
定義した関数マクロを使う
1
require "kenma"
2
3
4
using Kenma::Refine::Source
5
6
module CatMacro
using Kenma::Macroable
7
8
マクロを適用させるコードを `Proc` で定義する
ブロック内のコードに対してマクロを適用させる
def cat!
9
10
ast { 'nyaaaaan' }
end
11
macro_function :cat!
12
13
end
14
15
16
17
18
body = proc {
use_macro! CatMacro
19
20
21
22
23
24
compiled = Kenma.compile_of(body)
pp compiled
puts cat!
}
src = compiled.source
puts src
eval(src)
48 / 89
定義した関数マクロを使う
1
require "kenma"
2
3
4
using Kenma::Refine::Source
5
6
module CatMacro
using Kenma::Macroable
7
8
def cat!
9
10
ast { 'nyaaaaan' }
end
11
macro_function :cat!
12
13
end
14
15
16
17
18
body = proc {
use_macro! CatMacro
19
20
21
22
23
24
compiled = Kenma.compile_of(body)
pp compiled
マクロを適用させるコードを `Proc` で定義する
ブロック内のコードに対してマクロを適用させる
NOTE: 今回の実装ではまだ完成度が低いのでファ
イル単位ではなくて特定のブロック内でのみマク
ロを適用させるような実装にしている
puts cat!
}
src = compiled.source
puts src
eval(src)
48 / 89
定義した関数マクロを使う
1
require "kenma"
2
3
4
using Kenma::Refine::Source
5
6
module CatMacro
using Kenma::Macroable
7
8
def cat!
9
10
ast { 'nyaaaaan' }
end
11
macro_function :cat!
12
13
14
end
body = proc {
15
use_macro! CatMacro
16
17
18
puts cat!
19
20
21
22
23
24
ブロック内で `use_macro!` を使用すると定義し
たマクロが使用できるようになる
}
compiled = Kenma.compile_of(body)
pp compiled
src = compiled.source
puts src
eval(src)
49 / 89
定義した関数マクロを使う
1
require "kenma"
2
3
4
using Kenma::Refine::Source
5
6
module CatMacro
using Kenma::Macroable
7
8
def cat!
9
10
ast { 'nyaaaaan' }
end
11
macro_function :cat!
12
13
14
end
body = proc {
15
use_macro! CatMacro
16
17
18
puts cat!
19
20
21
22
23
24
ブロック内で `use_macro!` を使用すると定義し
たマクロが使用できるようになる
この `use_macro!` もマクロで実装されている
}
compiled = Kenma.compile_of(body)
pp compiled
src = compiled.source
puts src
eval(src)
49 / 89
定義した関数マクロを使う
1
require "kenma"
2
3
4
using Kenma::Refine::Source
5
6
module CatMacro
using Kenma::Macroable
7
8
def cat!
9
10
ast { 'nyaaaaan' }
end
11
macro_function :cat!
12
13
14
end
body = proc {
15
16
17
18
19
20
21
22
23
24
ブロック内で `use_macro!` を使用すると定義し
たマクロが使用できるようになる
この `use_macro!` もマクロで実装されている
また `use_macro!` は呼び出したコンテキスト内
でのみ反映される
use_macro! CatMacro
puts cat!
}
compiled = Kenma.compile_of(body)
pp compiled
src = compiled.source
puts src
eval(src)
1
2
3
4
5
6
7
8
9
10
class X
クラス内でのみ
マクロが反映される
#
Hoge
use_macro! HogeMacro
def foo
#
FooMacro
use_macro! FooMacro
# ...
end
end
メソッド内でのみ
が反映される
49 / 89
定義した関数マクロを使う
1
require "kenma"
2
3
4
using Kenma::Refine::Source
5
6
module CatMacro
using Kenma::Macroable
7
8
def cat!
9
10
ast { 'nyaaaaan' }
end
11
macro_function :cat!
12
13
14
end
1
body = proc {
2
3
15
16
17
18
19
20
21
22
23
24
ブロック内で `use_macro!` を使用すると定義し
たマクロが使用できるようになる
この `use_macro!` もマクロで実装されている
また `use_macro!` は呼び出したコンテキスト内
でのみ反映される
use_macro! CatMacro
puts cat!
}
compiled = Kenma.compile_of(body)
pp compiled
src = compiled.source
puts src
eval(src)
4
5
6
7
8
9
10
class X
クラス内でのみ
マクロが反映される
#
Hoge
use_macro! HogeMacro
def foo
#
FooMacro
use_macro! FooMacro
# ...
end
end
メソッド内でのみ
が反映される
49 / 89
定義した関数マクロを使う
1
require "kenma"
2
3
4
using Kenma::Refine::Source
5
6
module CatMacro
using Kenma::Macroable
7
8
def cat!
9
10
ast { 'nyaaaaan' }
end
11
macro_function :cat!
12
13
14
end
1
body = proc {
2
3
#
Hoge
use_macro! HogeMacro
4
5
6
7
def foo
#
FooMacro
use_macro! FooMacro
15
16
17
18
19
20
21
22
23
24
ブロック内で `use_macro!` を使用すると定義し
たマクロが使用できるようになる
この `use_macro!` もマクロで実装されている
また `use_macro!` は呼び出したコンテキスト内
でのみ反映される
use_macro! CatMacro
puts cat!
}
compiled = Kenma.compile_of(body)
pp compiled
src = compiled.source
puts src
eval(src)
8
9
10
class X
クラス内でのみ
メソッド内でのみ
マクロが反映される
が反映される
# ...
end
end
49 / 89
定義した関数マクロを使う
1
require "kenma"
2
3
4
using Kenma::Refine::Source
5
6
module CatMacro
using Kenma::Macroable
7
8
def cat!
9
10
ast { 'nyaaaaan' }
end
11
macro_function :cat!
12
13
14
マクロを適用させると この `cat!` が
end
body = proc {
15
16
17
use_macro! CatMacro
puts cat!
18
}
19
20
21
22
23
24
compiled = Kenma.compile_of(body)
pp compiled
src = compiled.source
puts src
eval(src)
50 / 89
定義した関数マクロを使う
1
require "kenma"
2
3
4
using Kenma::Refine::Source
5
6
module CatMacro
using Kenma::Macroable
7
8
へと置き換わるイメージ
def cat!
9
10
ast { 'nyaaaaan' }
end
11
macro_function :cat!
12
13
14
`'nyaaaaan'`
end
body = proc {
15
16
17
use_macro! CatMacro
puts 'nyaaaaan'
18
}
19
20
21
22
23
24
compiled = Kenma.compile_of(body)
pp compiled
src = compiled.source
puts src
eval(src)
51 / 89
定義した関数マクロを使う
1
require "kenma"
2
3
4
using Kenma::Refine::Source
5
6
module CatMacro
using Kenma::Macroable
7
8
ast { 'nyaaaaan' }
end
11
macro_function :cat!
15
16
17
18
19
20
21
22
23
24
せる
を使用してマクロを適用さ
def cat!
9
10
12
13
14
`Kenma.compile_of`
end
body = proc {
use_macro! CatMacro
puts cat!
}
compiled = Kenma.compile_of(body)
pp compiled
src = compiled.source
puts src
eval(src)
52 / 89
定義した関数マクロを使う
`Kenma.compile_of`
1
require "kenma"
2
3
4
using Kenma::Refine::Source
せる
5
6
module CatMacro
using Kenma::Macroable
`Proc`
7
8
ast { 'nyaaaaan' }
end
11
macro_function :cat!
15
16
17
18
19
20
21
22
23
24
の中身に対してマクロが実行される
def cat!
9
10
12
13
14
を使用してマクロを適用さ
end
body = proc {
use_macro! CatMacro
puts cat!
}
compiled = Kenma.compile_of(body)
pp compiled
src = compiled.source
puts src
eval(src)
52 / 89
定義した関数マクロを使う
`Kenma.compile_of`
1
require "kenma"
2
3
4
using Kenma::Refine::Source
せる
5
6
module CatMacro
using Kenma::Macroable
`Proc`
7
8
def cat!
9
10
ast { 'nyaaaaan' }
end
11
macro_function :cat!
12
13
14
end
body = proc {
15
16
17
18
use_macro! CatMacro
}
19
20
compiled = Kenma.compile_of(body)
pp compiled
21
22
23
24
src = compiled.source
puts src
eval(src)
puts cat!
を使用してマクロを適用さ
の中身に対してマクロが実行される
[適用後の結果]
1
2
3
4
5
6
7
8
[:SCOPE,
[[],
nil,
[:BLOCK,
[[:FCALL,
[:puts,
[:LIST,
[(STR@1:0-1:10 "nyaaaaan"), nil]]]]]]]]
52 / 89
変換前と変換後の AST の比較 53 / 89
変換前と変換後の AST の比較 [変換前の AST] 1 2 3 4 5 6 7 8 9 10 11 (SCOPE@28:12-32:1 tbl: [] args: nil body: (BLOCK@29:2-31:11 (FCALL@29:2-29:21 :use_macro! (LIST@29:13-29:21 (CONST@29:13-29:21 :CatMacro) nil)) (FCALL@31:2-31:11 :puts (LIST@31:7-31:11 (FCALL@31:7-31:11 :cat! nil) nil)))) 53 / 89
変換前と変換後の AST の比較 [変換前の AST] 1 2 3 4 5 6 7 8 9 10 11 (SCOPE@28:12-32:1 tbl: [] args: nil body: (BLOCK@29:2-31:11 (FCALL@29:2-29:21 :use_macro! (LIST@29:13-29:21 (CONST@29:13-29:21 :CatMacro) nil)) (FCALL@31:2-31:11 :puts (LIST@31:7-31:11 (FCALL@31:7-31:11 :cat! nil) nil)))) [変換後の AST] 1 2 3 4 5 6 7 8 [:SCOPE, [[], nil, [:BLOCK, [[:FCALL, [:puts, [:LIST, [(STR@1:0-1:10 "nyaaaaan"), nil]]]]]]]] 53 / 89
変換前と変換後の AST の比較 [変換前の AST] 1 2 3 4 5 6 7 8 9 10 11 (SCOPE@28:12-32:1 tbl: [] args: nil body: (BLOCK@29:2-31:11 (FCALL@29:2-29:21 :use_macro! (LIST@29:13-29:21 (CONST@29:13-29:21 :CatMacro) nil)) (FCALL@31:2-31:11 :puts (LIST@31:7-31:11 (FCALL@31:7-31:11 :cat! nil) nil)))) [変換後の AST] 1 2 3 4 5 6 7 8 [:SCOPE, [[], nil, [:BLOCK, [[:FCALL, [:puts, [:LIST, [(STR@1:0-1:10 "nyaaaaan"), nil]]]]]]]] ここの部分が `cat!` メソッドの戻り値の AST に置き換えられている 53 / 89
変換前と変換後の AST の比較 [変換前の AST] 1 2 3 4 5 6 7 8 9 10 11 (SCOPE@28:12-32:1 tbl: [] args: nil body: (BLOCK@29:2-31:11 (FCALL@29:2-29:21 :use_macro! (LIST@29:13-29:21 (CONST@29:13-29:21 :CatMacro) nil)) (FCALL@31:2-31:11 :puts (LIST@31:7-31:11 (FCALL@31:7-31:11 :cat! nil) nil)))) [変換後の AST] 1 2 3 4 5 6 7 8 [:SCOPE, [[], nil, [:BLOCK, [[:FCALL, [:puts, [:LIST, [(STR@1:0-1:10 "nyaaaaan"), nil]]]]]]]] ここの部分が `cat!` メソッドの戻り値の AST に置き換えられている 変換後の AST は `RubyVM::AST::Node` と配列が混ざっているのに注意 53 / 89
定義した関数マクロを使う
1
require "kenma"
2
3
4
using Kenma::Refine::Source
5
6
module CatMacro
using Kenma::Macroable
7
8
def cat!
9
10
ast { 'nyaaaaan' }
end
11
macro_function :cat!
12
13
14
15
16
17
18
最後に AST から Ruby のコードに変換する
end
body = proc {
use_macro! CatMacro
puts cat!
}
19
20
21
22
compiled = Kenma.compile_of(body)
pp compiled
23
24
puts src
eval(src)
src = compiled.source
54 / 89
定義した関数マクロを使う
1
require "kenma"
2
3
using Kenma::Refine::Source
4
5
6
module CatMacro
using Kenma::Macroable
7
8
def cat!
9
10
ast { 'nyaaaaan' }
end
11
macro_function :cat!
12
13
14
15
16
17
18
19
20
21
22
23
24
最後に AST から Ruby のコードに変換する
`using` すると `#source` が使えるようになり
end
body = proc {
use_macro! CatMacro
puts cat!
}
compiled = Kenma.compile_of(body)
pp compiled
src = compiled.source
puts src
eval(src)
54 / 89
定義した関数マクロを使う
1
require "kenma"
2
3
4
using Kenma::Refine::Source
5
6
module CatMacro
using Kenma::Macroable
7
8
def cat!
9
10
ast { 'nyaaaaan' }
end
11
macro_function :cat!
12
13
14
15
16
17
18
最後に AST から Ruby のコードに変換する
`using` すると `#source` が使えるようになり
AST から Ruby のコードが取得できる
1
2
puts compiled.source
# => puts("nyaaaaan");
end
body = proc {
use_macro! CatMacro
puts cat!
}
19
20
21
22
23
compiled = Kenma.compile_of(body)
pp compiled
24
eval(src)
src = compiled.source
puts src
54 / 89
定義した関数マクロを使う
1
require "kenma"
2
3
4
using Kenma::Refine::Source
5
6
module CatMacro
using Kenma::Macroable
7
8
def cat!
9
10
ast { 'nyaaaaan' }
end
11
macro_function :cat!
12
13
14
15
16
17
18
19
20
21
22
23
24
最後に AST から Ruby のコードに変換する
`using` すると `#source` が使えるようになり
AST から Ruby のコードが取得できる
1
2
最後に変換したコードを `eval` で評価する
end
body = proc {
use_macro! CatMacro
puts cat!
puts compiled.source
# => puts("nyaaaaan");
[出力結果]
1
2
eval(src)
# => nyaaaaan
}
compiled = Kenma.compile_of(body)
pp compiled
src = compiled.source
puts src
eval(src)
54 / 89
定義した関数マクロを使う
1
require "kenma"
2
3
4
using Kenma::Refine::Source
5
6
module CatMacro
using Kenma::Macroable
7
8
def cat!
9
10
ast { 'nyaaaaan' }
end
11
macro_function :cat!
12
13
end
14
15
16
17
18
19
20
21
22
23
24
body = proc {
use_macro! CatMacro
puts cat!
}
compiled = Kenma.compile_of(body)
pp compiled
src = compiled.source
puts src
eval(src)
最後に AST から Ruby のコードに変換する
`using` すると `#source` が使えるようになり
AST から Ruby のコードが取得できる
1
2
puts compiled.source
# => puts("nyaaaaan");
最後に変換したコードを `eval` で評価する
[出力結果]
1
2
eval(src)
# => nyaaaaan
また `Proc` から `eval` するまでの動作をまとめ
て
54 / 89
定義した関数マクロを使う
1
require "kenma"
2
3
4
using Kenma::Refine::Source
5
6
module CatMacro
using Kenma::Macroable
7
8
`Kenma.macro_eval`
でまとめることができる
def cat!
9
10
ast { 'nyaaaaan' }
end
11
macro_function :cat!
12
13
end
14
15
16
17
18
Kenma.macro_eval {
use_macro! CatMacro
puts cat!
}
55 / 89
定義した関数マクロを使う
1
require "kenma"
2
3
4
using Kenma::Refine::Source
5
6
module CatMacro
using Kenma::Macroable
7
8
でまとめることができる
`Kenma.macro_eval` のブロック内のコードにマ
クロが反映され評価される
`Kenma.macro_eval`
def cat!
9
10
ast { 'nyaaaaan' }
end
11
macro_function :cat!
12
13
end
14
15
16
17
18
Kenma.macro_eval {
use_macro! CatMacro
puts cat!
}
55 / 89
定義した関数マクロを使う
1
require "kenma"
2
3
4
using Kenma::Refine::Source
5
6
module CatMacro
using Kenma::Macroable
7
8
def cat!
9
10
ast { 'nyaaaaan' }
end
11
macro_function :cat!
12
13
end
14
15
16
17
18
Kenma.macro_eval {
use_macro! CatMacro
でまとめることができる
`Kenma.macro_eval` のブロック内のコードにマ
クロが反映され評価される
このようにして特定のメソッドを別の AST に置き
換える事ができる
`Kenma.macro_eval`
puts cat!
}
55 / 89
関数マクロの引数の話 56 / 89
関数マクロの引数の話 1 2 3 4 module CatMacro using Kenma::Macroable 5 6 7 8 ast { "nyaaaaan" * num } end macro_function :cat! end 9 10 11 12 13 14 15 def cat!(num) body = proc { use_macro! CatMacro puts cat!(3) } puts Kenma.compile_of(body).source 57 / 89
関数マクロの引数の話 1 2 3 4 5 6 7 8 9 10 module CatMacro using Kenma::Macroable def cat!(num) ast { "nyaaaaan" * num } したい のようにマクロ関数に対して引数を渡 end macro_function :cat! end body = proc { 11 12 use_macro! CatMacro 13 puts cat!(3) 14 15 `cat!(3)` } puts Kenma.compile_of(body).source 57 / 89
関数マクロの引数の話 1 2 3 4 5 6 7 8 9 10 module CatMacro using Kenma::Macroable def cat!(num) ast { "nyaaaaan" * num } end macro_function :cat! end のようにマクロ関数に対して引数を渡 したい この引数は `cat!` メソッドの引数として受け取 ることができる body = proc { 11 12 use_macro! CatMacro 13 puts cat!(3) 14 15 `cat!(3)` } puts Kenma.compile_of(body).source 57 / 89
関数マクロの引数の話
1
2
3
4
5
6
7
8
9
10
def cat!(num)
ast { "nyaaaaan" * num }
end
macro_function :cat!
end
body = proc {
use_macro! CatMacro
13
puts cat!(3)
}
puts Kenma.compile_of(body).source
のようにマクロ関数に対して引数を渡
したい
この引数は `cat!` メソッドの引数として受け取
ることができる
ただし、`num` は `3` という値ではなくて AST と
して受け取る
using Kenma::Macroable
11
12
14
15
`cat!(3)`
module CatMacro
1
2
cat!(3)
# num => (LIT@24:12-24:13 3)
57 / 89
関数マクロの引数の話
1
2
3
4
5
6
7
8
9
10
def cat!(num)
ast { "nyaaaaan" * num }
end
macro_function :cat!
end
body = proc {
use_macro! CatMacro
13
puts cat!(3)
}
puts Kenma.compile_of(body).source
のようにマクロ関数に対して引数を渡
したい
この引数は `cat!` メソッドの引数として受け取
ることができる
ただし、`num` は `3` という値ではなくて AST と
して受け取る
using Kenma::Macroable
11
12
14
15
`cat!(3)`
module CatMacro
1
2
cat!(3)
# num => (LIT@24:12-24:13 3)
また `1 + 2` みたいな式も AST として受け取る
1
2
3
cat!(1 + 2)
# num => (OPCALL@24:12-24:17 (LIT@24:12-24:13 1) :+
#
(LIST@24:16-24:17 (LIT@24:16-24:17 2) nil))
57 / 89
関数マクロの引数の話 1 2 3 4 5 6 7 8 9 10 module CatMacro using Kenma::Macroable def cat!(num) ast { "nyaaaaan" * num } end macro_function :cat! end body = proc { 11 12 use_macro! CatMacro 13 puts cat!(3) 14 15 この時に `ast {}` 内でそのまま変数 `num` を参 照しようとすると } puts Kenma.compile_of(body).source 58 / 89
関数マクロの引数の話 1 2 3 4 5 6 7 8 9 10 module CatMacro using Kenma::Macroable def cat!(num) ast { "nyaaaaan" * num } end macro_function :cat! end body = proc { 11 12 use_macro! CatMacro 13 puts "nyaaaaan" * num 14 15 値ではなくて `num` というコードがそのまま展開 されてしまう } puts Kenma.compile_of(body).source 59 / 89
関数マクロの引数の話 1 2 3 4 5 6 7 8 9 10 module CatMacro using Kenma::Macroable def cat!(num) ast { "nyaaaaan" * num } end macro_function :cat! end body = proc { 11 12 use_macro! CatMacro 13 puts cat!(3) 14 15 なので `num` ではなくて } puts Kenma.compile_of(body).source 60 / 89
関数マクロの引数の話 1 2 3 4 5 6 7 8 9 10 module CatMacro using Kenma::Macroable def cat!(num) ast { "nyaaaaan" * node_bind!(num) } end macro_function :cat! end body = proc { 11 12 use_macro! CatMacro 13 puts cat!(3) 14 15 という特別なマクロを介して参照 する必要がある `node_bind!` } puts Kenma.compile_of(body).source 61 / 89
関数マクロの引数の話
1
2
3
4
5
6
7
8
9
10
using Kenma::Macroable
def cat!(num)
ast { "nyaaaaan" * node_bind!(num) }
end
macro_function :cat!
end
body = proc {
11
12
use_macro! CatMacro
13
puts cat!(3)
14
15
という特別なマクロを介して参照
する必要がある
`node_bind!` を使用する事で引数の AST が直接
AST に展開される
`node_bind!`
module CatMacro
}
puts Kenma.compile_of(body).source
1
2
3
4
5
6
7
node = ast { "nyaaaaan" }
が展開される
# AST
pp ast { node_bind!(node) }
# => (STR@30:13-30:23 "nyaaaaan")
a = ast { 1 }
b = ast { 2 }
61 / 89
関数マクロの引数の話
1
2
3
4
5
6
7
8
9
10
using Kenma::Macroable
def cat!(num)
ast { "nyaaaaan" * node_bind!(num) }
end
macro_function :cat!
end
body = proc {
11
12
use_macro! CatMacro
13
puts cat!(3)
14
15
という特別なマクロを介して参照
する必要がある
`node_bind!` を使用する事で引数の AST が直接
AST に展開される
`node_bind!`
module CatMacro
}
puts Kenma.compile_of(body).source
1
2
3
4
5
6
7
node = ast { "nyaaaaan" }
が展開される
# AST
pp ast { node_bind!(node) }
# => (STR@30:13-30:23 "nyaaaaan")
a = ast { 1 }
b = ast { 2 }
なので `puts cat!(3)` は
61 / 89
関数マクロの引数の話 1 2 3 4 5 6 7 8 9 10 module CatMacro using Kenma::Macroable と展開されるイメージ def cat!(num) ast { "nyaaaaan" * node_bind!(num) } end macro_function :cat! end body = proc { 11 12 use_macro! CatMacro 13 puts "nyaaaaan" * 3 14 15 `puts "nyaaaaan" * 3` } puts Kenma.compile_of(body).source 62 / 89
関数マクロの引数の話 1 2 3 4 5 6 7 8 9 10 module CatMacro using Kenma::Macroable def cat!(num) ast { "nyaaaaan" * node_bind!(num) } end macro_function :cat! end body = proc { 11 12 use_macro! CatMacro 13 puts "nyaaaaan" * 3 14 15 と展開されるイメージ また `node_bind!(num)` は `puts "nyaaaaan" * 3` } puts Kenma.compile_of(body).source 62 / 89
関数マクロの引数の話
1
2
3
4
5
6
7
8
9
10
def cat!(num)
ast { "nyaaaaan" * $num }
end
macro_function :cat!
end
body = proc {
use_macro! CatMacro
13
puts cat!(3)
と記述する事もできる
グローバル変数を `node_bind!` に置き換えるような仕組
みを内部で実装してる
using Kenma::Macroable
11
12
14
15
`$num`
module CatMacro
1
2
node = ast { "nyaaaaan" }
3
4
5
# AST are expanded
ppp ast { $node }
# => (STR@30:13-30:23 "nyaaaaan")
}
puts Kenma.compile_of(body).source
63 / 89
関数マクロの引数の話
1
2
3
4
5
6
7
8
9
10
def cat!(num)
ast { "nyaaaaan" * $num }
end
macro_function :cat!
end
body = proc {
use_macro! CatMacro
13
puts cat!(3)
}
puts Kenma.compile_of(body).source
と記述する事もできる
グローバル変数を `node_bind!` に置き換えるような仕組
みを内部で実装してる
using Kenma::Macroable
11
12
14
15
`$num`
module CatMacro
1
2
node = ast { "nyaaaaan" }
3
4
5
# AST are expanded
ppp ast { $node }
# => (STR@30:13-30:23 "nyaaaaan")
最終的な以下のようなコードに展開される
1
puts("nyaaaaan" * 3)
63 / 89
関数マクロの引数の話
1
2
3
4
5
6
7
8
9
10
def cat!(num)
ast { "nyaaaaan" * $num }
end
macro_function :cat!
end
body = proc {
use_macro! CatMacro
13
puts cat!(3)
}
puts Kenma.compile_of(body).source
と記述する事もできる
グローバル変数を `node_bind!` に置き換えるような仕組
みを内部で実装してる
using Kenma::Macroable
11
12
14
15
`$num`
module CatMacro
1
2
node = ast { "nyaaaaan" }
3
4
5
# AST are expanded
ppp ast { $node }
# => (STR@30:13-30:23 "nyaaaaan")
最終的な以下のようなコードに展開される
1
puts("nyaaaaan" * 3)
他にも `stringify!` マクロが標準で使用できる
引数の式を文字列の AST にするマクロ
1
2
pp ast { stringify! 1 + 2 * 3 }
# => [:STR, ["(1 + (2 * 3))"]]
63 / 89
ノードマクロ 64 / 89
ノードマクロ 65 / 89
ノードマクロ 特定の AST の種類に対して AST を置き換えるマクロ 65 / 89
ノードマクロ 特定の AST の種類に対して AST を置き換えるマクロ 基本的には関数マクロと同じで『置き換えたい AST を返すメソッド』を定義する 65 / 89
ノードマクロ 特定の AST の種類に対して AST を置き換えるマクロ 基本的には関数マクロと同じで『置き換えたい AST を返すメソッド』を定義する `hoge.foo.bar` を `hoge&.foo&.bar` に置き換えるマクロを書いてみる 65 / 89
ノードマクロ 特定の AST の種類に対して AST を置き換えるマクロ 基本的には関数マクロと同じで『置き換えたい AST を返すメソッド』を定義する `hoge.foo.bar` を `hoge&.foo&.bar` に置き換えるマクロを書いてみる 1 hoge.foo.bar 2 # AST => [:CALL, [[:CALL, [[:VCALL, [:hoge]], :foo, nil]], :bar, nil]] を 65 / 89
ノードマクロ 特定の AST の種類に対して AST を置き換えるマクロ 基本的には関数マクロと同じで『置き換えたい AST を返すメソッド』を定義する `hoge.foo.bar` を `hoge&.foo&.bar` に置き換えるマクロを書いてみる 1 hoge.foo.bar 2 # AST => [:CALL, [[:CALL, [[:VCALL, [:hoge]], :foo, nil]], :bar, nil]] を 1 2 hoge&.foo&.bar # AST => [:QCALL, [[:QCALL, [[:VCALL, [:hoge]], :foo, nil]], :bar, nil]] のよう `CALL` を `QCALL` へと変換する 65 / 89
ノードマクロを定義する
1
2
3
4
require "kenma"
5
6
7
8
module BocchiMacro
using Kenma::Macroable
9
10
11
12
13
14
15
16
17
18
19
using Kenma::Refine::Source
def bocchi(node, parent)
[:QCALL, node.children]
end
macro_node :CALL, :bocchi
end
body = proc {
use_macro! BocchiMacro
hoge.foo.bar
}
puts Kenma.compile_of(body).source
66 / 89
ノードマクロを定義する
1
2
3
4
5
6
7
8
require "kenma"
using Kenma::Refine::Source
module BocchiMacro
using Kenma::Macroable
def bocchi(node, parent)
9
10
11
12
13
[:QCALL, node.children]
end
macro_node :CALL, :bocchi
end
14
15
16
body = proc {
use_macro! BocchiMacro
17
18
19
関数マクロと同様にまずモジュールを定義する
hoge.foo.bar
}
puts Kenma.compile_of(body).source
66 / 89
ノードマクロを定義する
1
2
3
4
5
require "kenma"
using Kenma::Refine::Source
module BocchiMacro
6
7
using Kenma::Macroable
8
def bocchi(node, parent)
9
10
11
12
関数マクロと同様にまずモジュールを定義する
AST を受け取り AST を返すメソッドを定義する
今回は配列として AST の情報を返している
[:QCALL, node.children]
end
macro_node :CALL, :bocchi
end
13
14
15
16
17
18
19
body = proc {
use_macro! BocchiMacro
hoge.foo.bar
}
puts Kenma.compile_of(body).source
66 / 89
ノードマクロを定義する
1
2
3
4
5
6
7
8
require "kenma"
using Kenma::Refine::Source
module BocchiMacro
using Kenma::Macroable
関数マクロと同様にまずモジュールを定義する
AST を受け取り AST を返すメソッドを定義する
今回は配列として AST の情報を返している
でどの AST に対して処理をフック
するか指定してマクロであることを宣言する
`macro_node`
def bocchi(node, parent)
9
10
[:QCALL, node.children]
end
11
12
macro_node :CALL, :bocchi
end
13
14
15
16
17
18
19
body = proc {
use_macro! BocchiMacro
hoge.foo.bar
}
puts Kenma.compile_of(body).source
66 / 89
ノードマクロを定義する
1
2
3
4
5
6
7
require "kenma"
using Kenma::Refine::Source
module BocchiMacro
using Kenma::Macroable
def bocchi(node, parent)
[:QCALL, node.children]
end
11
12
macro_node :CALL, :bocchi
end
13
17
18
19
今回は配列として AST の情報を返している
でどの AST に対して処理をフック
するか指定してマクロであることを宣言する
この `CALL` という AST のデータが `node` の引数
として渡ってくる
`macro_node`
8
9
10
14
15
16
関数マクロと同様にまずモジュールを定義する
AST を受け取り AST を返すメソッドを定義する
body = proc {
use_macro! BocchiMacro
`parent`
は親のノード情報
hoge.foo.bar
}
puts Kenma.compile_of(body).source
66 / 89
ノードマクロを定義する
1
2
3
4
5
6
7
8
require "kenma"
using Kenma::Refine::Source
module BocchiMacro
using Kenma::Macroable
def bocchi(node, parent)
[:QCALL, node.children]
end
11
12
macro_node :CALL, :bocchi
end
13
17
18
19
今回は配列として AST の情報を返している
でどの AST に対して処理をフック
するか指定してマクロであることを宣言する
この `CALL` という AST のデータが `node` の引数
として渡ってくる
`macro_node`
9
10
14
15
16
関数マクロと同様にまずモジュールを定義する
AST を受け取り AST を返すメソッドを定義する
body = proc {
use_macro! BocchiMacro
hoge.foo.bar
}
puts Kenma.compile_of(body).source
`parent`
は親のノード情報
今回は `CALL` を `QCALL` という命令に置き換え
たいので子情報はそのままで種類を変えた AST を
返している
66 / 89
ノードマクロを定義する
1
2
3
4
5
6
7
8
require "kenma"
使い方は関数マクロと同じ
using Kenma::Refine::Source
module BocchiMacro
using Kenma::Macroable
def bocchi(node, parent)
9
10
[:QCALL, node.children]
end
11
12
macro_node :CALL, :bocchi
end
13
14
15
16
17
18
19
body = proc {
use_macro! BocchiMacro
hoge.foo.bar
}
puts Kenma.compile_of(body).source
67 / 89
ノードマクロを定義する
1
2
3
4
5
6
7
8
require "kenma"
using Kenma::Refine::Source
使い方は関数マクロと同じ
使用したい場所で `use_macro!` を使用する
module BocchiMacro
using Kenma::Macroable
def bocchi(node, parent)
9
10
[:QCALL, node.children]
end
11
12
macro_node :CALL, :bocchi
end
13
14
15
body = proc {
use_macro! BocchiMacro
16
17
18
19
hoge.foo.bar
}
puts Kenma.compile_of(body).source
67 / 89
ノードマクロを定義する
1
2
3
4
5
6
7
8
require "kenma"
using Kenma::Refine::Source
module BocchiMacro
using Kenma::Macroable
使い方は関数マクロと同じ
使用したい場所で `use_macro!` を使用する
`hoge.foo.bar` が
def bocchi(node, parent)
9
10
[:QCALL, node.children]
end
11
12
macro_node :CALL, :bocchi
end
13
14
15
16
17
body = proc {
use_macro! BocchiMacro
18
19
}
puts Kenma.compile_of(body).source
hoge.foo.bar
67 / 89
ノードマクロを定義する
1
2
3
4
5
6
7
8
require "kenma"
`hoge&.foo&.bar`
に置き換わる
using Kenma::Refine::Source
module BocchiMacro
using Kenma::Macroable
def bocchi(node, parent)
9
10
[:QCALL, node.children]
end
11
12
macro_node :CALL, :bocchi
end
13
14
15
16
17
body = proc {
use_macro! BocchiMacro
18
19
}
puts Kenma.compile_of(body).source
hoge&.foo&.bar
68 / 89
ノードマクロを定義する
1
2
3
4
5
6
7
8
require "kenma"
using Kenma::Refine::Source
module BocchiMacro
using Kenma::Macroable
`hoge&.foo&.bar`
[出力結果]
に置き換わる
1
puts Kenma.compile_of(body).source
2
# => hoge&.foo()&.bar();
def bocchi(node, parent)
9
10
[:QCALL, node.children]
end
11
12
macro_node :CALL, :bocchi
end
13
14
15
16
17
18
19
body = proc {
use_macro! BocchiMacro
hoge&.foo&.bar
}
puts Kenma.compile_of(body).source
68 / 89
パターンマクロ 69 / 89
パターンマクロ 70 / 89
パターンマクロ 特定の Ruby の構文に対して AST を置き換えるマクロ 70 / 89
パターンマクロ 特定の Ruby の構文に対して AST を置き換えるマクロ `value = [1, 2, 3]` を `value = [1, 2, 3].freeze` に置き換えるマクロを書く 70 / 89
パターンマクロ 特定の Ruby の構文に対して AST を置き換えるマクロ `value = [1, 2, 3]` を `value = [1, 2, 3].freeze` に置き換えるマクロを書く 1 2 value = [1, 2, 3] # AST => [:DASGN_CURR, [:value, [:LIST, [[:LIT, [1]], [:LIT, [2]], [:LIT, [3]], nil]]]] を 70 / 89
パターンマクロ 特定の Ruby の構文に対して AST を置き換えるマクロ `value = [1, 2, 3]` を `value = [1, 2, 3].freeze` に置き換えるマクロを書く 1 2 value = [1, 2, 3] # AST => [:DASGN_CURR, [:value, [:LIST, [[:LIT, [1]], [:LIT, [2]], [:LIT, [3]], nil]]]] を 1 2 3 4 5 value = [1, 2, 3].freeze # AST => [:DASGN_CURR, # [:value, # [:CALL, # [[:LIST, [[:LIT, [1]], [:LIT, [2]], [:LIT, [3]], nil]], :freeze, nil]]]] のよう `[:CALL, [..., :freeze]]` を付加させる 70 / 89
パターンマクロを定義する
1
2
3
4
require "kenma"
5
6
7
8
module FreezeMacro
def freezing(node, name:, value:)
ast { $name = $value.freeze }
end
9
10
11
12
13
14
15
16
17
using Kenma::Refine::Source
macro_pattern pat { $name = $value }, :freezing
end
body = proc {
use_macro! FreezeMacro
value = [1, 2, 3]
}
puts Kenma.compile_of(body).source
71 / 89
パターンマクロを定義する
1
2
3
4
5
require "kenma"
using Kenma::Refine::Source
どの構文でマッチするのかを
`macro_pattern` の引数で定義する
module FreezeMacro
6
7
8
def freezing(node, name:, value:)
ast { $name = $value.freeze }
end
9
10
macro_pattern pat { $name = $value }, :freezing
end
11
12
body = proc {
13
use_macro! FreezeMacro
14
15
16
value = [1, 2, 3]
}
17
puts Kenma.compile_of(body).source
71 / 89
パターンマクロを定義する
1
2
3
4
5
どの構文でマッチするのかを
`macro_pattern` の引数で定義する
`pat` で抽象的に『どの構文でマッチする
のか』を定義する事ができる
require "kenma"
using Kenma::Refine::Source
module FreezeMacro
6
7
8
def freezing(node, name:, value:)
ast { $name = $value.freeze }
end
9
10
macro_pattern pat { $name = $value }, :freezing
end
11
12
body = proc {
13
use_macro! FreezeMacro
14
15
16
value = [1, 2, 3]
}
17
puts Kenma.compile_of(body).source
はグローバル変数ではなくてマッチした AST
を束縛する
`$`
1
2
3
4
5
6
7
8
マッチした場合、束縛した
の
マッチしなかった場合は
を返す
を返す
#
AST
Hash
pp pat { $left < $right }.match(ast { 3 < 10 })
# => {:left=>(LIT@16:39-16:40 3),
#
:right=>(LIT@16:43-16:45 10)}
#
nil
pp pat { $left < $right }.match(ast { 3 > 10 })
# => nil
71 / 89
パターンマクロを定義する
1
2
3
4
5
require "kenma"
using Kenma::Refine::Source
今回は `name = value` という代入式に
マッチするパターンを指定している
module FreezeMacro
6
7
8
def freezing(node, name:, value:)
ast { $name = $value.freeze }
end
9
10
macro_pattern pat { $name = $value }, :freezing
end
11
12
body = proc {
13
use_macro! FreezeMacro
14
15
16
value = [1, 2, 3]
}
17
puts Kenma.compile_of(body).source
72 / 89
パターンマクロを定義する
1
2
3
4
5
今回は `name = value` という代入式に
マッチするパターンを指定している
なのでこのようなパターンになる
require "kenma"
using Kenma::Refine::Source
module FreezeMacro
6
7
8
def freezing(node, name:, value:)
ast { $name = $value.freeze }
end
9
10
macro_pattern pat { $name = $value }, :freezing
end
11
12
body = proc {
13
use_macro! FreezeMacro
14
15
16
value = [1, 2, 3]
}
17
puts Kenma.compile_of(body).source
1
pp pat { $name = $value }.match(ast { hoge = 42 })
2
# => {:name=>:hoge, :value=>(LIT@19:45-19:47 42)}
72 / 89
パターンマクロを定義する
1
2
3
4
5
今回は `name = value` という代入式に
マッチするパターンを指定している
なのでこのようなパターンになる
require "kenma"
using Kenma::Refine::Source
module FreezeMacro
6
7
8
def freezing(node, name:, value:)
ast { $name = $value.freeze }
end
9
10
macro_pattern pat { $name = $value }, :freezing
end
11
12
body = proc {
13
use_macro! FreezeMacro
14
15
16
value = [1, 2, 3]
}
17
puts Kenma.compile_of(body).source
1
pp pat { $name = $value }.match(ast { hoge = 42 })
2
# => {:name=>:hoge, :value=>(LIT@19:45-19:47 42)}
この `match` の結果の `Hash` が
72 / 89
パターンマクロを定義する
1
2
3
4
5
今回は `name = value` という代入式に
マッチするパターンを指定している
なのでこのようなパターンになる
require "kenma"
using Kenma::Refine::Source
module FreezeMacro
6
7
8
def freezing(node, name:, value:)
ast { $name = $value.freeze }
end
9
10
macro_pattern pat { $name = $value }, :freezing
end
11
12
body = proc {
13
use_macro! FreezeMacro
14
15
16
value = [1, 2, 3]
}
17
puts Kenma.compile_of(body).source
1
pp pat { $name = $value }.match(ast { hoge = 42 })
2
# => {:name=>:hoge, :value=>(LIT@19:45-19:47 42)}
この `match` の結果の `Hash` が
指定したメソッドのキーワード引数とし
て渡される
`node`
はマッチした構文全体の `AST`
72 / 89
パターンマクロを定義する
1
2
3
4
5
今回は `name = value` という代入式に
マッチするパターンを指定している
なのでこのようなパターンになる
require "kenma"
using Kenma::Refine::Source
module FreezeMacro
6
def freezing(node, name:, value:)
7
8
ast { $name = $value.freeze }
end
9
10
macro_pattern pat { $name = $value }, :freezing
end
11
12
body = proc {
13
use_macro! FreezeMacro
14
15
16
value = [1, 2, 3]
}
17
puts Kenma.compile_of(body).source
1
pp pat { $name = $value }.match(ast { hoge = 42 })
2
# => {:name=>:hoge, :value=>(LIT@19:45-19:47 42)}
この `match` の結果の `Hash` が
指定したメソッドのキーワード引数とし
て渡される
`node`
はマッチした構文全体の `AST`
と `value` は AST なので `$` で束
縛し `freeze` メソッドを呼び出す
`name`
72 / 89
パターンマクロを定義する
1
2
3
4
5
require "kenma"
`value = [1, 2, 3]`
が
using Kenma::Refine::Source
module FreezeMacro
6
7
8
def freezing(node, name:, value:)
ast { $name = $value.freeze }
end
9
10
macro_pattern pat { $name = $value }, :freezing
end
11
12
body = proc {
13
use_macro! FreezeMacro
14
15
value = [1, 2, 3]
16
}
17
puts Kenma.compile_of(body).source
73 / 89
パターンマクロを定義する
1
2
3
4
5
require "kenma"
using Kenma::Refine::Source
`value = [1, 2, 3].freeze`
わる
に置き換
module FreezeMacro
6
7
8
def freezing(node, name:, value:)
ast { $name = $value.freeze }
end
9
10
macro_pattern pat { $name = $value }, :freezing
end
11
12
body = proc {
13
use_macro! FreezeMacro
14
15
value = [1, 2, 3].freeze
16
}
17
puts Kenma.compile_of(body).source
74 / 89
パターンマクロを定義する
1
2
3
4
5
6
7
8
using Kenma::Refine::Source
module FreezeMacro
def freezing(node, name:, value:)
ast { $name = $value.freeze }
end
9
10
macro_pattern pat { $name = $value }, :freezing
end
11
12
body = proc {
13
14
15
16
17
`value = [1, 2, 3].freeze`
require "kenma"
わる
に置き換
[出力結果]
1
puts Kenma.compile_of(body).source
2
# => (value = [1, 2, 3].freeze());
use_macro! FreezeMacro
value = [1, 2, 3].freeze
}
puts Kenma.compile_of(body).source
74 / 89
マクロの使用例を紹介 75 / 89
デバッグ出力マクロ 1 debug! 1 + 2 76 / 89
デバッグ出力マクロ 1 debug! 1 + 2 → 76 / 89
デバッグ出力マクロ 1 debug! 1 + 2 → 1 puts "1 + 2 # => #{1 + 2}" 76 / 89
デバッグ出力マクロ
1
debug! 1 + 2
[コード]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
→
1
puts "1 + 2 # => #{1 + 2}"
module DebugMacro
using Kenma::Macroable
def debug!(expr)
ast { puts "#{stringify! $expr} # => #{$expr}" }
end
macro_function :debug!
end
body = proc {
use_macro! DebugMacro
debug! 1 + 2 + 3
}
puts Kenma.compile_of(body).source
76 / 89
デバッグ出力マクロ
1
debug! 1 + 2
[コード]
1
2
→
module DebugMacro
using Kenma::Macroable
1
puts "1 + 2 # => #{1 + 2}"
`debug!`
の引数を AST で受け取り
3
4
5
def debug!(expr)
ast { puts "#{stringify! $expr} # => #{$expr}" }
6
end
7
8
9
macro_function :debug!
end
10
11
12
13
body = proc {
use_macro! DebugMacro
14
15
}
puts Kenma.compile_of(body).source
debug! 1 + 2 + 3
76 / 89
デバッグ出力マクロ
1
debug! 1 + 2
[コード]
1
2
→
module DebugMacro
using Kenma::Macroable
3
4
def debug!(expr)
5
6
ast { puts "#{stringify! $expr} # => #{$expr}" }
end
7
8
9
10
11
12
13
14
15
1
puts "1 + 2 # => #{1 + 2}"
の引数を AST で受け取り
`stringify!` で文字列に変換して
`debug!`
macro_function :debug!
end
body = proc {
use_macro! DebugMacro
debug! 1 + 2 + 3
}
puts Kenma.compile_of(body).source
76 / 89
デバッグ出力マクロ
1
debug! 1 + 2
[コード]
1
2
→
module DebugMacro
using Kenma::Macroable
3
4
def debug!(expr)
5
6
ast { puts "#{stringify! $expr} # => #{$expr}" }
end
7
8
9
10
11
12
13
14
15
1
puts "1 + 2 # => #{1 + 2}"
の引数を AST で受け取り
`stringify!` で文字列に変換して
式はそのまま `#{}` で文字列に埋め込む
`debug!`
macro_function :debug!
end
body = proc {
use_macro! DebugMacro
debug! 1 + 2 + 3
}
puts Kenma.compile_of(body).source
76 / 89
デバッグ出力マクロ
1
debug! 1 + 2
[コード]
1
2
→
def debug!(expr)
ast { puts "#{stringify! $expr} # => #{$expr}" }
6
end
10
11
12
13
14
15
macro_function :debug!
end
puts "1 + 2 # => #{1 + 2}"
の引数を AST で受け取り
`stringify!` で文字列に変換して
式はそのまま `#{}` で文字列に埋め込む
`debug!`
module DebugMacro
using Kenma::Macroable
3
4
5
7
8
9
1
[出力結果]
1
puts("#{"((1 + 2) + 3)"} # => #{((1 + 2) + 3)}");
body = proc {
use_macro! DebugMacro
debug! 1 + 2 + 3
}
puts Kenma.compile_of(body).source
76 / 89
複数のモジュールを1回で using するマクロ 1 using Hoge, Foo, Bar 77 / 89
複数のモジュールを1回で using するマクロ 1 using Hoge, Foo, Bar → 77 / 89
複数のモジュールを1回で using するマクロ 1 using Hoge, Foo, Bar → 1 2 3 using Hoge using Foo using Bar 77 / 89
複数のモジュールを1回で using するマクロ
1
using Hoge, Foo, Bar
[コード]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
→
1
2
3
using Hoge
using Foo
using Bar
require "kenma"
using Kenma::Refine::Source
module MultiUsingMacro
using Kenma::Macroable
def using(*args)
args.compact.inject(ast { {} }) { |result, name|
ast { $result; using $name }
}
end
macro_function :using
end
body = proc {
use_macro! MultiUsingMacro
using Hoge, Foo, Bar
}
result = Kenma.compile_of(body)
puts result.source
77 / 89
複数のモジュールを1回で using するマクロ
1
using Hoge, Foo, Bar
[コード]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
→
require "kenma"
using Kenma::Refine::Source
module MultiUsingMacro
using Kenma::Macroable
1
2
3
using Hoge
using Foo
using Bar
の引数 `Hoge, Foo, Bar` を可
変長引数で受け取る
`using`
`Hoge` `Foo` `Bar`
の AST を配列で受け取る
def using(*args)
args.compact.inject(ast { {} }) { |result, name|
ast { $result; using $name }
}
end
macro_function :using
end
body = proc {
use_macro! MultiUsingMacro
using Hoge, Foo, Bar
}
result = Kenma.compile_of(body)
puts result.source
77 / 89
複数のモジュールを1回で using するマクロ
1
using Hoge, Foo, Bar
[コード]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
→
require "kenma"
using Kenma::Refine::Source
module MultiUsingMacro
using Kenma::Macroable
def using(*args)
args.compact.inject(ast { {} }) { |result, name|
ast { $result; using $name }
}
end
macro_function :using
end
1
2
3
using Hoge
using Foo
using Bar
の引数 `Hoge, Foo, Bar` を可
変長引数で受け取る
`using`
`Hoge` `Foo` `Bar`
の AST を配列で受け取る
`Hoge` `Foo` `Bar`
を1つずつイテレー
ションして `using Hoge; using Foo;
using Bar` になるように `using` する
body = proc {
use_macro! MultiUsingMacro
using Hoge, Foo, Bar
}
result = Kenma.compile_of(body)
puts result.source
77 / 89
複数のモジュールを1回で using するマクロ
1
using Hoge, Foo, Bar
[コード]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
→
require "kenma"
using Kenma::Refine::Source
module MultiUsingMacro
using Kenma::Macroable
def using(*args)
args.compact.inject(ast { {} }) { |result, name|
ast { $result; using $name }
}
end
macro_function :using
end
body = proc {
use_macro! MultiUsingMacro
using Hoge, Foo, Bar
}
result = Kenma.compile_of(body)
puts result.source
1
2
3
using Hoge
using Foo
using Bar
の引数 `Hoge, Foo, Bar` を可
変長引数で受け取る
`using`
`Hoge` `Foo` `Bar`
の AST を配列で受け取る
`Hoge` `Foo` `Bar`
を1つずつイテレー
ションして `using Hoge; using Foo;
using Bar` になるように `using` する
[出力結果]
1
begin begin begin {}; using(Hoge); end;
using(Foo); end; using(Bar); end;
77 / 89
{ a: a, b: b, c: c } を簡略的に定義する 1 ![a, b, c] 78 / 89
{ a: a, b: b, c: c } を簡略的に定義する 1 ![a, b, c] → 78 / 89
{ a: a, b: b, c: c } を簡略的に定義する 1 ![a, b, c] → 1 { a: a, b: b, c: c } 78 / 89
{ a: a, b: b, c: c } を簡略的に定義する
1
![a, b, c]
[コード]
1
require "kenma"
2
3
using Kenma::Refine::Source
4
5
module ShorthandHashLiteralMacro
6
using Kenma::Macroable
7
8
def shorthand_hash_literal(node, args:)
9
args.children.compact.inject(ast { {} }) {
|result, name|
10
ast { $result.merge({ symbolify!($name) =>
$name }) }
11
}
12
end
13
macro_pattern pat { ![*$args] },
:shorthand_hash_literal
14
end
15
16
body = proc {
17
use_macro! ShorthandHashLiteralMacro
18
19
![a, b, c]
20
}
21
22
result = Kenma.compile_of(body)
23
puts result.source
→
1
{ a: a, b: b, c: c }
78 / 89
{ a: a, b: b, c: c } を簡略的に定義する
1
![a, b, c]
[コード]
1
require "kenma"
2
3
using Kenma::Refine::Source
4
5
module ShorthandHashLiteralMacro
6
using Kenma::Macroable
7
8
def shorthand_hash_literal(node, args:)
9
args.children.compact.inject(ast { {} }) {
|result, name|
10
ast { $result.merge({ symbolify!($name) =>
$name }) }
11
}
12
end
13
macro_pattern pat { ![*$args] },
:shorthand_hash_literal
14
end
15
16
body = proc {
17
use_macro! ShorthandHashLiteralMacro
18
19
![a, b, c]
20
}
21
22
result = Kenma.compile_of(body)
23
puts result.source
→
1
{ a: a, b: b, c: c }
の配列の中身を受け取るパターン
マクロを定義する
`![]`
`a, b, c`
を1つの AST として受け取る
78 / 89
{ a: a, b: b, c: c } を簡略的に定義する
1
![a, b, c]
[コード]
1
require "kenma"
2
3
using Kenma::Refine::Source
4
5
module ShorthandHashLiteralMacro
6
using Kenma::Macroable
7
8
def shorthand_hash_literal(node, args:)
9
args.children.compact.inject(ast { {} }) {
|result, name|
10
ast { $result.merge({ symbolify!($name) =>
$name }) }
11
}
12
end
13
macro_pattern pat { ![*$args] },
:shorthand_hash_literal
14
end
15
16
body = proc {
17
use_macro! ShorthandHashLiteralMacro
18
19
![a, b, c]
20
}
21
22
result = Kenma.compile_of(body)
23
puts result.source
→
1
{ a: a, b: b, c: c }
の配列の中身を受け取るパターン
マクロを定義する
`![]`
`a, b, c`
を1つの AST として受け取る
で `a, b, c` を1つの AST とし
て受け取る
`args`
78 / 89
{ a: a, b: b, c: c } を簡略的に定義する
1
![a, b, c]
[コード]
1
require "kenma"
2
3
using Kenma::Refine::Source
4
5
module ShorthandHashLiteralMacro
6
using Kenma::Macroable
7
8
def shorthand_hash_literal(node, args:)
9
args.children.compact.inject(ast { {} }) {
|result, name|
10
ast { $result.merge({ symbolify!($name) =>
$name }) }
11
}
12
end
13
macro_pattern pat { ![*$args] },
:shorthand_hash_literal
14
end
15
16
body = proc {
17
use_macro! ShorthandHashLiteralMacro
18
19
![a, b, c]
20
}
21
22
result = Kenma.compile_of(body)
23
puts result.source
→
1
{ a: a, b: b, c: c }
の配列の中身を受け取るパターン
マクロを定義する
`![]`
`a, b, c`
を1つの AST として受け取る
で `a, b, c` を1つの AST とし
て受け取る
`a` `b` `c` を1つずつイテレーションし
て `{}` に `merge` する
`args`
78 / 89
{ a: a, b: b, c: c } を簡略的に定義する
1
![a, b, c]
[コード]
1
require "kenma"
2
3
using Kenma::Refine::Source
4
5
module ShorthandHashLiteralMacro
6
using Kenma::Macroable
7
8
def shorthand_hash_literal(node, args:)
9
args.children.compact.inject(ast { {} }) {
|result, name|
10
ast { $result.merge({ symbolify!($name) =>
$name }) }
11
}
12
end
13
macro_pattern pat { ![*$args] },
:shorthand_hash_literal
14
end
15
16
body = proc {
17
use_macro! ShorthandHashLiteralMacro
18
19
![a, b, c]
20
}
21
22
result = Kenma.compile_of(body)
23
puts result.source
→
1
{ a: a, b: b, c: c }
の配列の中身を受け取るパターン
マクロを定義する
`![]`
`a, b, c`
を1つの AST として受け取る
で `a, b, c` を1つの AST とし
て受け取る
`a` `b` `c` を1つずつイテレーションし
て `{}` に `merge` する
`args`
[出力結果]
1
});
{}.merge({ a: a }).merge({ b: b }).merge({ c: c
78 / 89
a < b < c を a < b && b < c に置き換える 1 a < b < c 79 / 89
a < b < c を a < b && b < c に置き換える → 1 a < b < c 79 / 89
a < b < c を a < b && b < c に置き換える → 1 a < b < c 1 a < b && b < c 79 / 89
a < b < c を a < b && b < c に置き換える
→
[コード]
1
a < b < c
1
a < b && b < c
1
module ChainingComparisonOperatorsMacro
2
using Kenma::Macroable
3
4
def chaining_comparison_operators(node, parent)
5
case node.to_a
6
in [:OPCALL, [
7
[:OPCALL, [left, op1, [:LIST, [middle,
nil]]]],
8
op2, [:LIST, [right, nil]]]]
9
ast {
10
$left.send(eval!(op1), $middle) &&
11
$middle.send(eval!(op2), $right)
12
}
13
else
14
node
15
end
16
end
17
macro_node :OPCALL, :chaining_comparison_operators
18
end
79 / 89
a < b < c を a < b && b < c に置き換える
→
[コード]
1
a < b < c
1
module ChainingComparisonOperatorsMacro
2
using Kenma::Macroable
3
4
def chaining_comparison_operators(node, parent)
5
case node.to_a
6
in [:OPCALL, [
7
[:OPCALL, [left, op1, [:LIST, [middle,
nil]]]],
8
op2, [:LIST, [right, nil]]]]
9
ast {
10
$left.send(eval!(op1), $middle) &&
11
$middle.send(eval!(op2), $right)
12
}
13
else
14
node
15
end
16
end
17
macro_node :OPCALL, :chaining_comparison_operators
18
end
1
a < b && b < c
`a < b < c`
クロを定義
全体の AST を受け取るマ
79 / 89
a < b < c を a < b && b < c に置き換える
→
[コード]
1
a < b < c
1
module ChainingComparisonOperatorsMacro
2
using Kenma::Macroable
3
4
def chaining_comparison_operators(node, parent)
5
case node.to_a
6
in [:OPCALL, [
7
[:OPCALL, [left, op1, [:LIST, [middle,
nil]]]],
8
op2, [:LIST, [right, nil]]]]
9
ast {
10
$left.send(eval!(op1), $middle) &&
11
$middle.send(eval!(op2), $right)
12
}
13
else
14
node
15
end
16
end
17
macro_node :OPCALL, :chaining_comparison_operators
18
end
1
a < b && b < c
`a < b < c`
全体の AST を受け取るマ
クロを定義
ここで AST を一旦配列に変換する
79 / 89
a < b < c を a < b && b < c に置き換える
→
[コード]
1
a < b < c
1
module ChainingComparisonOperatorsMacro
2
using Kenma::Macroable
3
4
def chaining_comparison_operators(node, parent)
5
case node.to_a
6
in [:OPCALL, [
7
[:OPCALL, [left, op1, [:LIST, [middle,
nil]]]],
8
op2, [:LIST, [right, nil]]]]
9
ast {
10
$left.send(eval!(op1), $middle) &&
11
$middle.send(eval!(op2), $right)
12
}
13
else
14
node
15
end
16
end
17
macro_node :OPCALL, :chaining_comparison_operators
18
end
1
a < b && b < c
`a < b < c`
全体の AST を受け取るマ
クロを定義
ここで AST を一旦配列に変換する
パターンマッチで `a < b < c` にマッ
チする AST の配列の値を束縛する
79 / 89
a < b < c を a < b && b < c に置き換える
→
[コード]
1
a < b < c
1
module ChainingComparisonOperatorsMacro
2
using Kenma::Macroable
3
4
def chaining_comparison_operators(node, parent)
5
case node.to_a
6
in [:OPCALL, [
7
[:OPCALL, [left, op1, [:LIST, [middle,
nil]]]],
8
op2, [:LIST, [right, nil]]]]
9
ast {
10
$left.send(eval!(op1), $middle) &&
11
$middle.send(eval!(op2), $right)
12
}
13
else
14
node
15
end
16
end
17
macro_node :OPCALL, :chaining_comparison_operators
18
end
1
a < b && b < c
`a < b < c`
全体の AST を受け取るマ
クロを定義
ここで AST を一旦配列に変換する
パターンマッチで `a < b < c` にマッ
チする AST の配列の値を束縛する
ここで `a.send(:<, b) && b.send(:
<, c)` になるような AST に変換する
79 / 89
a < b < c を a < b && b < c に置き換える
→
[コード]
1
a < b < c
1
module ChainingComparisonOperatorsMacro
2
using Kenma::Macroable
3
4
def chaining_comparison_operators(node, parent)
5
case node.to_a
6
in [:OPCALL, [
7
[:OPCALL, [left, op1, [:LIST, [middle,
nil]]]],
8
op2, [:LIST, [right, nil]]]]
9
ast {
10
$left.send(eval!(op1), $middle) &&
11
$middle.send(eval!(op2), $right)
12
}
13
else
14
node
15
end
16
end
17
macro_node :OPCALL, :chaining_comparison_operators
18
end
1
a < b && b < c
`a < b < c`
全体の AST を受け取るマ
クロを定義
ここで AST を一旦配列に変換する
パターンマッチで `a < b < c` にマッ
チする AST の配列の値を束縛する
ここで `a.send(:<, b) && b.send(:
<, c)` になるような AST に変換する
[変換結果]
1
(0.send(:<=, value) && value.send(:<, 10));
79 / 89
他にも 80 / 89
81 / 89
1 const! value = [1, 2, 3] 81 / 89
1 const! value = [1, 2, 3] → 81 / 89
1 const! value = [1, 2, 3] → 1 2 3 既に定義済みなら例外にする # raise if defined? value value = [1, 2, 3].freeze 81 / 89
1 const! value = [1, 2, 3] 1 2 H[name, age] = user disp H[name, age] → 1 2 3 既に定義済みなら例外にする # raise if defined? value value = [1, 2, 3].freeze 81 / 89
1 const! value = [1, 2, 3] 1 2 H[name, age] = user disp H[name, age] → → 1 2 3 既に定義済みなら例外にする # raise if defined? value value = [1, 2, 3].freeze 81 / 89
1
const! value = [1, 2, 3]
1
2
H[name, age] = user
disp H[name, age]
→
→
既に定義済みなら例外にする
1
2
3
#
raise if defined? value
value = [1, 2, 3].freeze
1
2
3
# Hash
name = user[:name]; age = user[:age]
disp({ name: name, age: age })
値を展開したりショートハンドで定義したり
81 / 89
1
const! value = [1, 2, 3]
1
2
H[name, age] = user
disp H[name, age]
1
!i!(hoge foo bar)
→
→
既に定義済みなら例外にする
1
2
3
#
raise if defined? value
value = [1, 2, 3].freeze
1
2
3
# Hash
name = user[:name]; age = user[:age]
disp({ name: name, age: age })
値を展開したりショートハンドで定義したり
81 / 89
1
const! value = [1, 2, 3]
1
2
H[name, age] = user
disp H[name, age]
1
!i!(hoge foo bar)
→
→
→
既に定義済みなら例外にする
1
2
3
#
raise if defined? value
value = [1, 2, 3].freeze
1
2
3
# Hash
name = user[:name]; age = user[:age]
disp({ name: name, age: age })
値を展開したりショートハンドで定義したり
81 / 89
1
const! value = [1, 2, 3]
1
2
H[name, age] = user
disp H[name, age]
1
!i!(hoge foo bar)
→
→
→
既に定義済みなら例外にする
1
2
3
#
raise if defined? value
value = [1, 2, 3].freeze
1
2
3
# Hash
name = user[:name]; age = user[:age]
disp({ name: name, age: age })
1
2
# %i
[:hoge,:foo,:bar]
値を展開したりショートハンドで定義したり
みたいに展開したり
81 / 89
1
const! value = [1, 2, 3]
1
2
H[name, age] = user
disp H[name, age]
1
!i!(hoge foo bar)
1
2
3
def func(name: String, age: (0...20))
# ...
end
→
→
→
既に定義済みなら例外にする
1
2
3
#
raise if defined? value
value = [1, 2, 3].freeze
1
2
3
# Hash
name = user[:name]; age = user[:age]
disp({ name: name, age: age })
1
2
# %i
[:hoge,:foo,:bar]
値を展開したりショートハンドで定義したり
みたいに展開したり
81 / 89
1
const! value = [1, 2, 3]
1
2
H[name, age] = user
disp H[name, age]
1
!i!(hoge foo bar)
1
2
3
def func(name: String, age: (0...20))
# ...
end
→
→
→
→
既に定義済みなら例外にする
1
2
3
#
raise if defined? value
value = [1, 2, 3].freeze
1
2
3
# Hash
name = user[:name]; age = user[:age]
disp({ name: name, age: age })
1
2
# %i
[:hoge,:foo,:bar]
値を展開したりショートハンドで定義したり
みたいに展開したり
81 / 89
1
const! value = [1, 2, 3]
1
2
H[name, age] = user
disp H[name, age]
1
!i!(hoge foo bar)
1
2
3
def func(name: String, age: (0...20))
# ...
end
→
→
→
→
既に定義済みなら例外にする
1
2
3
#
raise if defined? value
value = [1, 2, 3].freeze
1
2
3
# Hash
name = user[:name]; age = user[:age]
disp({ name: name, age: age })
1
2
# %i
[:hoge,:foo,:bar]
1
2
3
4
5
6
#
def func(name, age)
raise TypeError unless String === name
raise TypeError unless (0..20) === age
# ...
end
値を展開したりショートハンドで定義したり
みたいに展開したり
型チェックのように定義したり
81 / 89
82 / 89
1
2
3
4
5
6
7
8
9
module MyMacro
macro_rule {
pat { $name = $value } --> {
$name = $value.freeze
}
pat { cat! } --> { "nyaaaaan" }
pat { cat!($num) } --> { "nyaaaaan" * $num }
}
end
82 / 89
1
2
3
4
5
6
7
8
9
module MyMacro
macro_rule {
pat { $name = $value } --> {
$name = $value.freeze
}
pat { cat! } --> { "nyaaaaan" }
pat { cat!($num) } --> { "nyaaaaan" * $num }
}
end
→
82 / 89
1
2
3
4
5
6
7
8
9
module MyMacro
macro_rule {
pat { $name = $value } --> {
$name = $value.freeze
}
pat { cat! } --> { "nyaaaaan" }
pat { cat!($num) } --> { "nyaaaaan" * $num }
}
end
→
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
マクロを簡略化的に定義したり
#
module MyMacro
def macro1(node, name:, value:)
ast { $name = $value.freeze }
end
macro_pattern pat { $name = $value }, :macro1
def macro2(node)
ast { "nyaaaaan" }
end
macro_pattern pat { cat! }, :macro2
def macro3(node, num:)
ast { "nyaaaaan" * $num }
end
macro_pattern pat { cat!($num) }, :macro3
end
82 / 89
1
2
3
4
5
6
7
8
9
1
2
3
4
module MyMacro
macro_rule {
pat { $name = $value } --> {
$name = $value.freeze
}
pat { cat! } --> { "nyaaaaan" }
pat { cat!($num) } --> { "nyaaaaan" * $num }
}
end
→
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
マクロを簡略化的に定義したり
#
module MyMacro
def macro1(node, name:, value:)
ast { $name = $value.freeze }
end
macro_pattern pat { $name = $value }, :macro1
def macro2(node)
ast { "nyaaaaan" }
end
macro_pattern pat { cat! }, :macro2
def macro3(node, num:)
ast { "nyaaaaan" * $num }
end
macro_pattern pat { cat!($num) }, :macro3
end
@initialize[:name, :age]
class User
end
82 / 89
1
2
3
4
5
6
7
8
9
1
2
3
4
module MyMacro
macro_rule {
pat { $name = $value } --> {
$name = $value.freeze
}
pat { cat! } --> { "nyaaaaan" }
pat { cat!($num) } --> { "nyaaaaan" * $num }
}
end
@initialize[:name, :age]
class User
end
→
→
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
マクロを簡略化的に定義したり
#
module MyMacro
def macro1(node, name:, value:)
ast { $name = $value.freeze }
end
macro_pattern pat { $name = $value }, :macro1
def macro2(node)
ast { "nyaaaaan" }
end
macro_pattern pat { cat! }, :macro2
def macro3(node, num:)
ast { "nyaaaaan" * $num }
end
macro_pattern pat { cat!($num) }, :macro3
end
82 / 89
1
2
3
4
5
6
7
8
9
1
2
3
4
module MyMacro
macro_rule {
pat { $name = $value } --> {
$name = $value.freeze
}
pat { cat! } --> { "nyaaaaan" }
pat { cat!($num) } --> { "nyaaaaan" * $num }
}
end
@initialize[:name, :age]
class User
end
→
→
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
1
2
3
4
5
6
7
8
9
マクロを簡略化的に定義したり
#
module MyMacro
def macro1(node, name:, value:)
ast { $name = $value.freeze }
end
macro_pattern pat { $name = $value }, :macro1
def macro2(node)
ast { "nyaaaaan" }
end
macro_pattern pat { cat! }, :macro2
def macro3(node, num:)
ast { "nyaaaaan" * $num }
end
macro_pattern pat { cat!($num) }, :macro3
end
アクセッサを暗黙的に定義するアノテーション
#
class User
attr_reader :name, :age
def initialize(name:, age:)
@name = name
@age = age
end
end
82 / 89
夢が広がる 83 / 89
これからの課題 84 / 89
これからの課題 85 / 89
これからの課題 Rensei の復元率がまだ100%でない まだエッジケースの問題が多い ActiveRecord の全ファイルをパースした結果 `15/240` ファイル失敗している 85 / 89
これからの課題 Rensei の復元率がまだ100%でない まだエッジケースの問題が多い ActiveRecord の全ファイルをパースした結果 `15/240` ファイル失敗している `ast {}` や `pat {}` はグローバル変数を `node` の束縛に使っているので制限がキツイ `ast { def $name(); end }` みたいにはかけない これをもっと簡略的にかけるようにしたい 85 / 89
これからの課題 Rensei の復元率がまだ100%でない まだエッジケースの問題が多い ActiveRecord の全ファイルをパースした結果 `15/240` ファイル失敗している `ast {}` や `pat {}` はグローバル変数を `node` の束縛に使っているので制限がキツイ `ast { def $name(); end }` みたいにはかけない これをもっと簡略的にかけるようにしたい `RubyVM::AbstractSyntaxTree` 自体の制限がきつい `RubyVM::AbstractSyntaxTree.of` が `eval` の中で使用できないので `eval("ast { ... }")` みた いなことができない これがかなりボトルネックになっている 85 / 89
まとめ 86 / 89
まとめ 87 / 89
まとめ Ruby でマクロを実装してみたらこんな感じになった これらはすべてピュア Ruby で実装されている 87 / 89
まとめ Ruby でマクロを実装してみたらこんな感じになった これらはすべてピュア Ruby で実装されている まだ完璧ではないが比較的簡単にマクロを実現する事ができた 87 / 89
まとめ Ruby でマクロを実装してみたらこんな感じになった これらはすべてピュア Ruby で実装されている まだ完璧ではないが比較的簡単にマクロを実現する事ができた 通常のメタプログラミングとは違い Ruby を構文レベルで変更する事ができるので今ま でのメタプログラミング以上の事が実現できる 87 / 89
まとめ Ruby でマクロを実装してみたらこんな感じになった これらはすべてピュア Ruby で実装されている まだ完璧ではないが比較的簡単にマクロを実現する事ができた 通常のメタプログラミングとは違い Ruby を構文レベルで変更する事ができるので今ま でのメタプログラミング以上の事が実現できる Ruby のマクロの可能性はまだまだ本気を出していないのでこれからも継続して開発して 行きたい もっと抽象的にマクロを定義できるようにしたい 87 / 89
将来 Ruby 本体にマクロが実装されるのを楽しみにし ていますね! 88 / 89
Thank you all! 89 / 89