Slide 1

Slide 1 text

Use Macro all the time ~ マクロを使いまくろ ~ RubyKaigi Takeout 2021

Slide 2

Slide 2 text

やあ、みんな おはよう こんにちわ こんばんわ

Slide 3

Slide 3 text

みんな Ruby を使ってい る???

Slide 4

Slide 4 text

Ruby を使っていると…

Slide 5

Slide 5 text

1 CONST_VALUE = [1, 2, 3] こういう定数定義を

Slide 6

Slide 6 text

1 CONST_VALUE = [1, 2, 3] こういう定数定義を 1 CONST_VALUE = [1, 2, 3].freeze 暗黙的に freeze させたり ` `

Slide 7

Slide 7 text

1 puts config.hoge_flag 2 puts config.foo_flag みたいなデバッグ出力を

Slide 8

Slide 8 text

1 puts config.hoge_flag 2 puts config.foo_flag みたいなデバッグ出力を 1 # output: 2 "config.hoge_flag # => true" 3 "config.foo_flag # => false" みたいに出力内容と出力結果を一緒に出力させたり

Slide 9

Slide 9 text

1 ![a, b, c] こういうコードを

Slide 10

Slide 10 text

1 ![a, b, c] こういうコードを 1 { a: a, b: b, c: c } みたいに Hash で展開させたりとか

Slide 11

Slide 11 text

やりたくなりますよね!!

Slide 12

Slide 12 text

それマクロでできるよ!!!!

Slide 13

Slide 13 text

自己紹介 名前: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

Slide 14

Slide 14 text

今日話すこと 11 / 89

Slide 15

Slide 15 text

今日話すこと Ruby でマクロを実装した話 11 / 89

Slide 16

Slide 16 text

アジェンダ Ruby のマクロとは AST とは マクロの変換プロセスの解説 Rensei - 錬成 - AST から Ruby のコードを生成するライブラリ Kenma - 研磨 - 任意の AST を別の AST に変換するライブラリ マクロの使用例 これからの課題 12 / 89

Slide 17

Slide 17 text

マクロとは? 13 / 89

Slide 18

Slide 18 text

マクロとは 14 / 89

Slide 19

Slide 19 text

マクロとは 世の中にはいろいろなマクロがある C言語マクロ、LISP マクロ、Rust マクロ、エクセルのマクロ etc… マクロと言ってもそれぞれ意味が異なる 14 / 89

Slide 20

Slide 20 text

マクロとは 世の中にはいろいろなマクロがある C言語マクロ、LISP マクロ、Rust マクロ、エクセルのマクロ etc… マクロと言ってもそれぞれ意味が異なる この登壇では『Ruby の AST を別の AST に変換すること』を『Ruby のマクロ』と定義 14 / 89

Slide 21

Slide 21 text

マクロとは 世の中にはいろいろなマクロがある C言語マクロ、LISP マクロ、Rust マクロ、エクセルのマクロ etc… マクロと言ってもそれぞれ意味が異なる この登壇では『Ruby の AST を別の AST に変換すること』を『Ruby のマクロ』と定義 この『マクロ』を使用すると Ruby のコードを構文レベルで変更する事ができる 例えば hoge.foo を hoge&.foo に変更したり 理論上は valid な Ruby のコードであればどんなコードにでも変換できる ` ` ` ` 14 / 89

Slide 22

Slide 22 text

そもそも AST って? 15 / 89

Slide 23

Slide 23 text

AST とは 16 / 89

Slide 24

Slide 24 text

AST とは AST とは抽象構文木(Abstract Syntax Tree)の略 16 / 89

Slide 25

Slide 25 text

AST とは AST とは抽象構文木(Abstract Syntax Tree)の略 今回は RubyVM::AbstractSyntaxTree を使用する RubyVM::AbstractSyntaxTree で使用される実データは RubyVM::AbstractSyntaxTree::Node だが、 このスライドでは一部配列形式で記述している 以下 RubyVM::AST::Node と略 ` ` ` ` ` ` ` ` 16 / 89

Slide 26

Slide 26 text

AST とは AST とは抽象構文木(Abstract Syntax Tree)の略 今回は RubyVM::AbstractSyntaxTree を使用する RubyVM::AbstractSyntaxTree で使用される実データは RubyVM::AbstractSyntaxTree::Node だが、 このスライドでは一部配列形式で記述している 以下 RubyVM::AST::Node と略 抽象化されたデータ構造なので異なるコードでも同じ AST になることがある 例えば cond ? foo : bar と if cond; foo; else bar; end は同じ AST になる ` ` ` ` ` ` ` ` ` ` ` ` 16 / 89

Slide 27

Slide 27 text

AST とは AST とは抽象構文木(Abstract Syntax Tree)の略 今回は RubyVM::AbstractSyntaxTree を使用する RubyVM::AbstractSyntaxTree で使用される実データは RubyVM::AbstractSyntaxTree::Node だが、 このスライドでは一部配列形式で記述している 以下 RubyVM::AST::Node と略 抽象化されたデータ構造なので異なるコードでも同じ AST になることがある 例えば cond ? foo : bar と if cond; foo; else bar; end は同じ AST になる AST の種類は構文ごとに細かく分かれていて100種類以上ある ` ` ` ` ` ` ` ` ` ` ` ` 16 / 89

Slide 28

Slide 28 text

RubyVM::AbstractSyntaxTree のサンプル [コード] 1 node = RubyVM::AbstractSyntaxTree.parse("1 + 2") 2 # Proc オブジェクトを渡すとブロックの中身の AST を返す 3 # RubyVM::AbstractSyntaxTree.of(-> { 1 + 2 }) 4 5 pp node 6 pp node.type 7 pp node.children 8 9 node2 = node.children.last 10 pp node2 11 pp node2.type 12 pp node2.children 17 / 89

Slide 29

Slide 29 text

RubyVM::AbstractSyntaxTree のサンプル [コード] 1 node = RubyVM::AbstractSyntaxTree.parse("1 + 2") RubyVM::AbstractSyntaxTree.parse で 1 + 2 の AST を取得する 2 # Proc オブジェクトを渡すとブロックの中身の AST を返す 3 # RubyVM::AbstractSyntaxTree.of(-> { 1 + 2 }) 4 5 pp node 6 pp node.type 7 pp node.children 8 9 node2 = node.children.last 10 pp node2 11 pp node2.type 12 pp node2.children ` ` ` ` 17 / 89

Slide 30

Slide 30 text

RubyVM::AbstractSyntaxTree のサンプル [コード] 2 # Proc オブジェクトを渡すとブロックの中身の AST を返す 3 # RubyVM::AbstractSyntaxTree.of(-> { 1 + 2 }) RubyVM::AbstractSyntaxTree.parse で 1 + 2 の AST を取得する .of で Proc から取得することもでき る 1 node = RubyVM::AbstractSyntaxTree.parse("1 + 2") 4 5 pp node 6 pp node.type 7 pp node.children 8 9 node2 = node.children.last 10 pp node2 11 pp node2.type 12 pp node2.children ` ` ` ` ` ` ` ` 17 / 89

Slide 31

Slide 31 text

RubyVM::AbstractSyntaxTree のサンプル [コード] 5 pp node RubyVM::AbstractSyntaxTree.parse で 1 + 2 の AST を取得する .of で Proc から取得することもでき る 取得した AST のデータはこのようになっ ている これが RubyVM::AbstractSyntaxTree::Node のデータ形式 [出力結果] 1 (SCOPE@1:0-1:5 2 tbl: [] 3 args: nil 4 body: (OPCALL@1:0-1:5 (LIT@1:0-1:1 1) :+ (LIST@1:4-1:5 (LIT@1:4-1:5 2) nil))) 1 node = RubyVM::AbstractSyntaxTree.parse("1 + 2") 2 # Proc オブジェクトを渡すとブロックの中身の AST を返す 3 # RubyVM::AbstractSyntaxTree.of(-> { 1 + 2 }) 4 6 pp node.type 7 pp node.children 8 9 node2 = node.children.last 10 pp node2 11 pp node2.type 12 pp node2.children ` ` ` ` ` ` ` ` ` ` 17 / 89

Slide 32

Slide 32 text

RubyVM::AbstractSyntaxTree のサンプル [コード] 6 pp node.type 7 pp node.children AST は type と children の2つの情報 を持っており、これが木構造になっている [出力結果] 1 :SCOPE 2 [[], 3 nil, 4 (OPCALL@1:0-1:5 (LIT@1:0-1:1 1) :+ (LIST@1:4-1:5 (LIT@1:4-1:5 2) nil))] 1 node = RubyVM::AbstractSyntaxTree.parse("1 + 2") 2 # Proc オブジェクトを渡すとブロックの中身の AST を返す 3 # RubyVM::AbstractSyntaxTree.of(-> { 1 + 2 }) 4 5 pp node 8 9 node2 = node.children.last 10 pp node2 11 pp node2.type 12 pp node2.children ` ` ` ` 18 / 89

Slide 33

Slide 33 text

RubyVM::AbstractSyntaxTree のサンプル [コード] 6 pp node.type 7 pp node.children AST は type と children の2つの情報 を持っており、これが木構造になっている 大枠に SCOPE という AST があり、その 下に 1 + 2 の AST がぶら下がっている [出力結果] 1 :SCOPE 2 [[], 3 nil, 4 (OPCALL@1:0-1:5 (LIT@1:0-1:1 1) :+ (LIST@1:4-1:5 (LIT@1:4-1:5 2) nil))] 1 node = RubyVM::AbstractSyntaxTree.parse("1 + 2") 2 # Proc オブジェクトを渡すとブロックの中身の AST を返す 3 # RubyVM::AbstractSyntaxTree.of(-> { 1 + 2 }) 4 5 pp node 8 9 node2 = node.children.last 10 pp node2 11 pp node2.type 12 pp node2.children ` ` ` ` ` ` ` ` 18 / 89

Slide 34

Slide 34 text

RubyVM::AbstractSyntaxTree のサンプル [コード] 9 node2 = node.children.last 10 pp node2 1 + 2 の AST を取得する場合は SCOPE の子から取得する [出力結果] 1 (OPCALL@1:0-1:5 (LIT@1:0-1:1 1) :+ (LIST@1:4-1:5 (LIT@1:4-1:5 2) nil)) 1 node = RubyVM::AbstractSyntaxTree.parse("1 + 2") 2 # Proc オブジェクトを渡すとブロックの中身の AST を返す 3 # RubyVM::AbstractSyntaxTree.of(-> { 1 + 2 }) 4 5 pp node 6 pp node.type 7 pp node.children 8 11 pp node2.type 12 pp node2.children ` ` ` ` 19 / 89

Slide 35

Slide 35 text

RubyVM::AbstractSyntaxTree のサンプル [コード] 11 pp node2.type 12 pp node2.children 1 + 2 の AST もまた type と children を持っている [出力結果] 1 :OPCALL 2 [(LIT@1:0-1:1 1), :+, (LIST@1:4-1:5 (LIT@1:4-1:5 2) nil)] 1 node = RubyVM::AbstractSyntaxTree.parse("1 + 2") 2 # Proc オブジェクトを渡すとブロックの中身の AST を返す 3 # RubyVM::AbstractSyntaxTree.of(-> { 1 + 2 }) 4 5 pp node 6 pp node.type 7 pp node.children 8 9 node2 = node.children.last 10 pp node2 ` ` ` ` ` ` 20 / 89

Slide 36

Slide 36 text

RubyVM::AbstractSyntaxTree のサンプル [コード] 11 pp node2.type 12 pp node2.children 1 + 2 の AST もまた type と children を持っている このように AST は複数の AST から成り立 っている [出力結果] 1 :OPCALL 2 [(LIT@1:0-1:1 1), :+, (LIST@1:4-1:5 (LIT@1:4-1:5 2) nil)] 1 node = RubyVM::AbstractSyntaxTree.parse("1 + 2") 2 # Proc オブジェクトを渡すとブロックの中身の AST を返す 3 # RubyVM::AbstractSyntaxTree.of(-> { 1 + 2 }) 4 5 pp node 6 pp node.type 7 pp node.children 8 9 node2 = node.children.last 10 pp node2 ` ` ` ` ` ` 20 / 89

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

マクロの変換プロセスの解説 22 / 89

Slide 39

Slide 39 text

マクロの変換プロセスの解説 簡単な例として hoge.foo を hoge&.foo に変換してみる ` ` ` ` 22 / 89

Slide 40

Slide 40 text

AST の変換プロセス 変換前の Ruby のコード 1 hoge.foo 23 / 89

Slide 41

Slide 41 text

AST の変換プロセス 変換前の Ruby のコード 1 hoge.foo このコードを AST に変換する 23 / 89

Slide 42

Slide 42 text

AST の変換プロセス 変換前の Ruby のコード 1 hoge.foo AST データ(RubyVM::AST::Node) 1 (CALL@9:9-9:17 2 (VCALL@9:9-9:13 :hoge) :foo nil) 24 / 89

Slide 43

Slide 43 text

AST の変換プロセス 変換前の Ruby のコード 1 hoge.foo AST データ(RubyVM::AST::Node) 1 (CALL@9:9-9:17 2 (VCALL@9:9-9:13 :hoge) :foo nil) このデータ構造のままだと扱いづらいの で一旦自前で配列に変換する 24 / 89

Slide 44

Slide 44 text

AST の変換プロセス 変換前の Ruby のコード 1 hoge.foo AST データ(RubyVM::AST::Node) 1 (CALL@9:9-9:17 2 (VCALL@9:9-9:13 :hoge) :foo nil) AST (配列) 1 [:CALL, 2 [[:VCALL, [:hoge]], :foo, nil]] 25 / 89

Slide 45

Slide 45 text

AST の変換プロセス 変換前の Ruby のコード 1 hoge.foo AST データ(RubyVM::AST::Node) 1 (CALL@9:9-9:17 2 (VCALL@9:9-9:13 :hoge) :foo nil) AST (配列) 1 [:CALL, AST 内の CALL という命令を これが . 演算子の命令 2 [[:VCALL, [:hoge]], :foo, nil]] ` ` ` ` 25 / 89

Slide 46

Slide 46 text

AST の変換プロセス 変換前の Ruby のコード 1 hoge.foo AST データ(RubyVM::AbstractSyntaxTree::Node) 1 (CALL@9:9-9:17 2 (VCALL@9:9-9:13 :hoge) :foo nil) AST (配列) 1 [:QCALL, QCALL という命令に置き換える QCALL が &. 演算子の命令 2 [[:VCALL, [:hoge]], :foo, nil]] ` ` ` ` ` ` 26 / 89

Slide 47

Slide 47 text

AST の変換プロセス 変換前の Ruby のコード 1 hoge.foo AST データ(RubyVM::AbstractSyntaxTree::Node) 1 (CALL@9:9-9:17 2 (VCALL@9:9-9:13 :hoge) :foo nil) AST (配列) 1 [:QCALL, 2 [[:VCALL, [:hoge]], :foo, nil]] この AST を Ruby のコードに変換する 27 / 89

Slide 48

Slide 48 text

AST の変換プロセス 変換前の Ruby のコード 1 hoge.foo AST データ(RubyVM::AbstractSyntaxTree::Node) 1 (CALL@9:9-9:17 2 (VCALL@9:9-9:13 :hoge) :foo nil) 変換後の Ruby のコード 1 hoge&.foo AST (配列) 1 [:QCALL, 2 [[:VCALL, [:hoge]], :foo, nil]] 28 / 89

Slide 49

Slide 49 text

AST の変換プロセス 変換前の Ruby のコード 1 hoge.foo AST データ(RubyVM::AbstractSyntaxTree::Node) 1 (CALL@9:9-9:17 2 (VCALL@9:9-9:13 :hoge) :foo nil) 変換後の Ruby のコード 1 hoge&.foo AST (配列) 1 [:QCALL, 2 [[:VCALL, [:hoge]], :foo, nil]] このようにして AST を書き換えることで別の Ruby のコードへと変更する事ができる 28 / 89

Slide 50

Slide 50 text

これがマクロだ!!! 29 / 89

Slide 51

Slide 51 text

このようにして AST の中身を書き換える事をマクロと定義する 30 / 89

Slide 52

Slide 52 text

このようにして AST の中身を書き換える事をマクロと定義する なのでマクロは AST から Ruby のコードへと変換する機能が必要になる 30 / 89

Slide 53

Slide 53 text

〜 AST から Ruby のコードに変換する 〜 Rensei - 錬成 - 31 / 89

Slide 54

Slide 54 text

Rensei - 錬成 - 32 / 89

Slide 55

Slide 55 text

Rensei - 錬成 - AST から Ruby のコードに復元するライブラリ https://github.com/osyo-manga/gem-rensei 32 / 89

Slide 56

Slide 56 text

Rensei - 錬成 - AST から Ruby のコードに復元するライブラリ https://github.com/osyo-manga/gem-rensei このライブラリを用いて RubyVM::AST::Node から Ruby のコードへと変換する RubyVM::AST::Node からではなくて配列からも変換することができる 詳しくはこちらを(日本語) 【Ruby Advent Calendar 2020】Ruby の AST から Ruby のソースコードを復元しよう【1日目】 - Secret Garden(Instrumental) https://secret-garden.hatenablog.com/entry/2020/12/01/093316 ` ` ` ` 32 / 89

Slide 57

Slide 57 text

Rensei - 錬成 - AST から Ruby のコードに復元するライブラリ https://github.com/osyo-manga/gem-rensei このライブラリを用いて RubyVM::AST::Node から Ruby のコードへと変換する RubyVM::AST::Node からではなくて配列からも変換することができる 詳しくはこちらを(日本語) 【Ruby Advent Calendar 2020】Ruby の AST から Ruby のソースコードを復元しよう【1日目】 - Secret Garden(Instrumental) https://secret-garden.hatenablog.com/entry/2020/12/01/093316 AST が既に抽象化したデータになっているので元のコードを完全復元するわけではない ので注意 コメントの情報などは復元できない () などが追加されることもある ` ` ` ` ` ` 32 / 89

Slide 58

Slide 58 text

Rensei - 錬成 - AST から Ruby のコードに復元するライブラリ https://github.com/osyo-manga/gem-rensei このライブラリを用いて RubyVM::AST::Node から Ruby のコードへと変換する RubyVM::AST::Node からではなくて配列からも変換することができる 詳しくはこちらを(日本語) 【Ruby Advent Calendar 2020】Ruby の AST から Ruby のソースコードを復元しよう【1日目】 - Secret Garden(Instrumental) https://secret-garden.hatenablog.com/entry/2020/12/01/093316 AST が既に抽象化したデータになっているので元のコードを完全復元するわけではない ので注意 コメントの情報などは復元できない () などが追加されることもある 名前の由来は新しく Ruby のコードを生成する、という意味で付けた ` ` ` ` ` ` 32 / 89

Slide 59

Slide 59 text

Rensei の使い方 [コード] 1 require "rensei" 2 3 node = RubyVM::AbstractSyntaxTree.parse("1 + 2 && hoge || bar") 4 src = Rensei.unparse(node) 5 puts src 6 # => (((1 + 2) && hoge) || bar) 7 8 puts Rensei.unparse([:OPCALL, [[:LIT, [1]], :+, [:LIST, [[:LIT, [2]], nil]]]]) 9 # => (1 + 2) 33 / 89

Slide 60

Slide 60 text

Rensei の使い方 [コード] 3 node = RubyVM::AbstractSyntaxTree.parse("1 + 2 && hoge || bar") Ruby のコードを AST に変更し 1 require "rensei" 2 4 src = Rensei.unparse(node) 5 puts src 6 # => (((1 + 2) && hoge) || bar) 7 8 puts Rensei.unparse([:OPCALL, [[:LIT, [1]], :+, [:LIST, [[:LIT, [2]], nil]]]]) 9 # => (1 + 2) 33 / 89

Slide 61

Slide 61 text

Rensei の使い方 [コード] 4 src = Rensei.unparse(node) Ruby のコードを AST に変更し RubyVM::AST::Node から Ruby のコードへと復元し 1 require "rensei" 2 3 node = RubyVM::AbstractSyntaxTree.parse("1 + 2 && hoge || bar") 5 puts src 6 # => (((1 + 2) && hoge) || bar) 7 8 puts Rensei.unparse([:OPCALL, [[:LIT, [1]], :+, [:LIST, [[:LIT, [2]], nil]]]]) 9 # => (1 + 2) ` ` 33 / 89

Slide 62

Slide 62 text

Rensei の使い方 [コード] 5 puts src 6 # => (((1 + 2) && hoge) || bar) Ruby のコードを AST に変更し RubyVM::AST::Node から Ruby のコードへと復元し 結果このような Ruby のコードが生成される () など元のコードにはない情報が付加されている 1 require "rensei" 2 3 node = RubyVM::AbstractSyntaxTree.parse("1 + 2 && hoge || bar") 4 src = Rensei.unparse(node) 7 8 puts Rensei.unparse([:OPCALL, [[:LIT, [1]], :+, [:LIST, [[:LIT, [2]], nil]]]]) 9 # => (1 + 2) ` ` ` ` 33 / 89

Slide 63

Slide 63 text

Rensei の使い方 [コード] 8 puts Rensei.unparse([:OPCALL, [[:LIT, [1]], :+, [:LIST, [[:LIT, [2]], nil]]]]) 9 # => (1 + 2) Ruby のコードを AST に変更し RubyVM::AST::Node から Ruby のコードへと復元し 結果このような Ruby のコードが生成される () など元のコードにはない情報が付加されている また配列の AST データからも復元することができる 1 require "rensei" 2 3 node = RubyVM::AbstractSyntaxTree.parse("1 + 2 && hoge || bar") 4 src = Rensei.unparse(node) 5 puts src 6 # => (((1 + 2) && hoge) || bar) 7 ` ` ` ` 33 / 89

Slide 64

Slide 64 text

Rensei を利用してマクロを実装した!!! 34 / 89

Slide 65

Slide 65 text

〜 Ruby で簡単にマクロを扱えるようにする 〜 Kenma - 研磨 - 35 / 89

Slide 66

Slide 66 text

Kenma - 研磨 - 36 / 89

Slide 67

Slide 67 text

Kenma - 研磨 - 任意の AST を別の AST に変換するライブラリ https://github.com/osyo-manga/gem-kenma 36 / 89

Slide 68

Slide 68 text

Kenma - 研磨 - 任意の AST を別の AST に変換するライブラリ https://github.com/osyo-manga/gem-kenma これを利用すると簡単にマクロを実現する事ができる 36 / 89

Slide 69

Slide 69 text

Kenma - 研磨 - 任意の AST を別の AST に変換するライブラリ https://github.com/osyo-manga/gem-kenma これを利用すると簡単にマクロを実現する事ができる 名前の由来は Ruby のコードを更に磨き上げるという意味で付けた 36 / 89

Slide 70

Slide 70 text

Kenma の使い方 37 / 89

Slide 71

Slide 71 text

マクロを定義しよう! 38 / 89

Slide 72

Slide 72 text

マクロの定義方法 39 / 89

Slide 73

Slide 73 text

マクロの定義方法 マクロの定義方法には複数の種類がある 39 / 89

Slide 74

Slide 74 text

マクロの定義方法 マクロの定義方法には複数の種類がある 関数マクロ メソッド呼び出しに対して AST を置き換える 39 / 89

Slide 75

Slide 75 text

マクロの定義方法 マクロの定義方法には複数の種類がある 関数マクロ メソッド呼び出しに対して AST を置き換える ノードマクロ 特定の AST の種類に対して AST を置き換える 39 / 89

Slide 76

Slide 76 text

マクロの定義方法 マクロの定義方法には複数の種類がある 関数マクロ メソッド呼び出しに対して AST を置き換える ノードマクロ 特定の AST の種類に対して AST を置き換える パターンマクロ 特定の Ruby の構文に対して AST を置き換える 39 / 89

Slide 77

Slide 77 text

マクロの定義方法 マクロの定義方法には複数の種類がある 関数マクロ メソッド呼び出しに対して AST を置き換える ノードマクロ 特定の AST の種類に対して AST を置き換える パターンマクロ 特定の Ruby の構文に対して AST を置き換える いずれかの定義方法でも『 AST を受け取って AST を返すメソッド』を定義すること になる ` ` ` ` 39 / 89

Slide 78

Slide 78 text

関数マクロ 40 / 89

Slide 79

Slide 79 text

関数マクロ 41 / 89

Slide 80

Slide 80 text

関数マクロ レシーバのないメソッド呼び出しを別の AST に置き換えるマクロ 41 / 89

Slide 81

Slide 81 text

関数マクロ レシーバのないメソッド呼び出しを別の AST に置き換えるマクロ cat! を 'nyaaaaan' に置き換えるマクロを書いてみる ` ` ` ` 41 / 89

Slide 82

Slide 82 text

関数マクロ レシーバのないメソッド呼び出しを別の AST に置き換えるマクロ cat! を 'nyaaaaan' に置き換えるマクロを書いてみる 1 puts cat! 2 # AST => [:FCALL, [:puts, [:LIST, [[:FCALL, [:cat!, nil]], nil]]]] を ` ` ` ` 41 / 89

Slide 83

Slide 83 text

関数マクロ レシーバのないメソッド呼び出しを別の AST に置き換えるマクロ cat! を 'nyaaaaan' に置き換えるマクロを書いてみる 1 puts cat! 2 # AST => [:FCALL, [:puts, [:LIST, [[:FCALL, [:cat!, nil]], nil]]]] を 1 puts 'nyaaaaan' 2 # AST => [:FCALL, [:puts, [:LIST, [[:STR, ["nyaaaaan"]], nil]]]] のように変換する ` ` ` ` 41 / 89

Slide 84

Slide 84 text

関数マクロを定義する 1 require "kenma" 2 3 using Kenma::Refine::Source 4 5 module CatMacro 6 using Kenma::Macroable 7 8 def cat! 9 RubyVM::AbstractSyntaxTree.parse("'nyaaaaan'") 10 end 11 macro_function :cat! 12 end 13 14 body = proc { 15 use_macro! CatMacro 16 17 puts cat! 18 } 19 compiled = Kenma.compile_of(body) 20 pp compiled 21 22 src = compiled.source 23 puts src 24 eval(src) 42 / 89

Slide 85

Slide 85 text

関数マクロを定義する 5 module CatMacro 6 using Kenma::Macroable 7 8 def cat! 9 RubyVM::AbstractSyntaxTree.parse("'nyaaaaan'") 10 end 11 macro_function :cat! 12 end マクロを定義するためのモジュールを定 義する 1 require "kenma" 2 3 using Kenma::Refine::Source 4 13 14 body = proc { 15 use_macro! CatMacro 16 17 puts cat! 18 } 19 compiled = Kenma.compile_of(body) 20 pp compiled 21 22 src = compiled.source 23 puts src 24 eval(src) 43 / 89

Slide 86

Slide 86 text

関数マクロを定義する 5 module CatMacro 6 using Kenma::Macroable 7 8 def cat! 9 RubyVM::AbstractSyntaxTree.parse("'nyaaaaan'") 10 end 11 macro_function :cat! 12 end マクロを定義するためのモジュールを定 義する このモジュール内でマクロで使用するメ ソッドを定義する 1 require "kenma" 2 3 using Kenma::Refine::Source 4 13 14 body = proc { 15 use_macro! CatMacro 16 17 puts cat! 18 } 19 compiled = Kenma.compile_of(body) 20 pp compiled 21 22 src = compiled.source 23 puts src 24 eval(src) 43 / 89

Slide 87

Slide 87 text

関数マクロを定義する 6 using Kenma::Macroable マクロを定義するためのモジュールを定 義する このモジュール内でマクロで使用するメ ソッドを定義する また using するとマクロを定義するた めに必要なメソッドが使えるようになる 1 require "kenma" 2 3 using Kenma::Refine::Source 4 5 module CatMacro 7 8 def cat! 9 RubyVM::AbstractSyntaxTree.parse("'nyaaaaan'") 10 end 11 macro_function :cat! 12 end 13 14 body = proc { 15 use_macro! CatMacro 16 17 puts cat! 18 } 19 compiled = Kenma.compile_of(body) 20 pp compiled 21 22 src = compiled.source 23 puts src 24 eval(src) ` ` 43 / 89

Slide 88

Slide 88 text

関数マクロを定義する 11 macro_function :cat! マクロを定義するためのモジュールを定 義する このモジュール内でマクロで使用するメ ソッドを定義する また using するとマクロを定義するた めに必要なメソッドが使えるようになる macro_function など 他にも Kernel や Module に必要なメソッド が定義される 1 require "kenma" 2 3 using Kenma::Refine::Source 4 5 module CatMacro 6 using Kenma::Macroable 7 8 def cat! 9 RubyVM::AbstractSyntaxTree.parse("'nyaaaaan'") 10 end 12 end 13 14 body = proc { 15 use_macro! CatMacro 16 17 puts cat! 18 } 19 compiled = Kenma.compile_of(body) 20 pp compiled 21 22 src = compiled.source 23 puts src 24 eval(src) ` ` ` ` ` ` ` ` 43 / 89

Slide 89

Slide 89 text

関数マクロを定義する 8 def cat! 9 RubyVM::AbstractSyntaxTree.parse("'nyaaaaan'") 10 end マクロとして呼び出すメソッドを定義す る 1 require "kenma" 2 3 using Kenma::Refine::Source 4 5 module CatMacro 6 using Kenma::Macroable 7 11 macro_function :cat! 12 end 13 14 body = proc { 15 use_macro! CatMacro 16 17 puts cat! 18 } 19 compiled = Kenma.compile_of(body) 20 pp compiled 21 22 src = compiled.source 23 puts src 24 eval(src) 44 / 89

Slide 90

Slide 90 text

関数マクロを定義する 9 RubyVM::AbstractSyntaxTree.parse("'nyaaaaan'") マクロとして呼び出すメソッドを定義す る ここで返した AST が呼び出し元のメソッ ドと置き換わる "nyaaaaan" という文字列の AST を返してる 1 require "kenma" 2 3 using Kenma::Refine::Source 4 5 module CatMacro 6 using Kenma::Macroable 7 8 def cat! 10 end 11 macro_function :cat! 12 end 13 14 body = proc { 15 use_macro! CatMacro 16 17 puts cat! 18 } 19 compiled = Kenma.compile_of(body) 20 pp compiled 21 22 src = compiled.source 23 puts src 24 eval(src) ` ` 44 / 89

Slide 91

Slide 91 text

関数マクロを定義する 9 RubyVM::AbstractSyntaxTree.parse("'nyaaaaan'") マクロとして呼び出すメソッドを定義す る ここで返した AST が呼び出し元のメソッ ドと置き換わる "nyaaaaan" という文字列の AST を返してる 1 puts cat! 1 require "kenma" 2 3 using Kenma::Refine::Source 4 5 module CatMacro 6 using Kenma::Macroable 7 8 def cat! 10 end 11 macro_function :cat! 12 end 13 14 body = proc { 15 use_macro! CatMacro 16 17 puts cat! 18 } 19 compiled = Kenma.compile_of(body) 20 pp compiled 21 22 src = compiled.source 23 puts src 24 eval(src) ` ` 44 / 89

Slide 92

Slide 92 text

関数マクロを定義する 9 RubyVM::AbstractSyntaxTree.parse("'nyaaaaan'") マクロとして呼び出すメソッドを定義す る ここで返した AST が呼び出し元のメソッ ドと置き換わる "nyaaaaan" という文字列の AST を返してる 1 puts cat! ↓↓↓↓↓ 1 puts "nyaaaaan" 1 require "kenma" 2 3 using Kenma::Refine::Source 4 5 module CatMacro 6 using Kenma::Macroable 7 8 def cat! 10 end 11 macro_function :cat! 12 end 13 14 body = proc { 15 use_macro! CatMacro 16 17 puts cat! 18 } 19 compiled = Kenma.compile_of(body) 20 pp compiled 21 22 src = compiled.source 23 puts src 24 eval(src) ` ` 44 / 89

Slide 93

Slide 93 text

関数マクロを定義する 9 RubyVM::AbstractSyntaxTree.parse("'nyaaaaan'") マクロとして呼び出すメソッドを定義す る ここで返した AST が呼び出し元のメソッ ドと置き換わる "nyaaaaan" という文字列の AST を返してる 1 puts cat! ↓↓↓↓↓ 1 puts "nyaaaaan" また RubyVM::AbstractSyntaxTree.parse を 1 require "kenma" 2 3 using Kenma::Refine::Source 4 5 module CatMacro 6 using Kenma::Macroable 7 8 def cat! 10 end 11 macro_function :cat! 12 end 13 14 body = proc { 15 use_macro! CatMacro 16 17 puts cat! 18 } 19 compiled = Kenma.compile_of(body) 20 pp compiled 21 22 src = compiled.source 23 puts src 24 eval(src) ` ` ` ` 44 / 89

Slide 94

Slide 94 text

関数マクロを定義する 9 ast { 'nyaaaaan' } ast {} に置き換える事ができる 1 require "kenma" 2 3 using Kenma::Refine::Source 4 5 module CatMacro 6 using Kenma::Macroable 7 8 def cat! 10 end 11 macro_function :cat! 12 end 13 14 body = proc { 15 use_macro! CatMacro 16 17 puts cat! 18 } 19 compiled = Kenma.compile_of(body) 20 pp compiled 21 22 src = compiled.source 23 puts src 24 eval(src) ` ` 45 / 89

Slide 95

Slide 95 text

関数マクロを定義する 9 ast { 'nyaaaaan' } ast {} に置き換える事ができる ast {} はブロックの中のコードの AST を返す using Kenma::Macroable したコンテキストで 使える 1 pp ast { 'nyaaaaan' } 2 # => (STR@5:9-5:19 "nyaaaaan") 3 4 pp ast { 1 + 2 } 5 # => (OPCALL@12:10-12:15 6 # (LIT@12:10-12:11 1) :+ 7 # (LIST@12:14-12:15 8 # (LIT@12:14-12:15 2) 9 # nil)) 1 require "kenma" 2 3 using Kenma::Refine::Source 4 5 module CatMacro 6 using Kenma::Macroable 7 8 def cat! 10 end 11 macro_function :cat! 12 end 13 14 body = proc { 15 use_macro! CatMacro 16 17 puts cat! 18 } 19 compiled = Kenma.compile_of(body) 20 pp compiled 21 22 src = compiled.source 23 puts src 24 eval(src) ` ` ` ` ` ` 45 / 89

Slide 96

Slide 96 text

関数マクロを定義する 11 macro_function :cat! ast {} に置き換える事ができる ast {} はブロックの中のコードの AST を返す using Kenma::Macroable したコンテキストで 使える 1 pp ast { 'nyaaaaan' } 2 # => (STR@5:9-5:19 "nyaaaaan") 3 4 pp ast { 1 + 2 } 5 # => (OPCALL@12:10-12:15 6 # (LIT@12:10-12:11 1) :+ 7 # (LIST@12:14-12:15 8 # (LIT@12:14-12:15 2) 9 # nil)) 最後にマクロ関数であることを宣言する 1 require "kenma" 2 3 using Kenma::Refine::Source 4 5 module CatMacro 6 using Kenma::Macroable 7 8 def cat! 9 ast { 'nyaaaaan' } 10 end 12 end 13 14 body = proc { 15 use_macro! CatMacro 16 17 puts cat! 18 } 19 compiled = Kenma.compile_of(body) 20 pp compiled 21 22 src = compiled.source 23 puts src 24 eval(src) ` ` ` ` ` ` 45 / 89

Slide 97

Slide 97 text

関数マクロを定義する 5 module CatMacro 6 using Kenma::Macroable 7 8 def cat! 9 ast { 'nyaaaaan' } 10 end 11 macro_function :cat! 12 end ast {} に置き換える事ができる ast {} はブロックの中のコードの AST を返す using Kenma::Macroable したコンテキストで 使える 1 pp ast { 'nyaaaaan' } 2 # => (STR@5:9-5:19 "nyaaaaan") 3 4 pp ast { 1 + 2 } 5 # => (OPCALL@12:10-12:15 6 # (LIT@12:10-12:11 1) :+ 7 # (LIST@12:14-12:15 8 # (LIT@12:14-12:15 2) 9 # nil)) 最後にマクロ関数であることを宣言する ここまでがマクロの定義になる 1 require "kenma" 2 3 using Kenma::Refine::Source 4 13 14 body = proc { 15 use_macro! CatMacro 16 17 puts cat! 18 } 19 compiled = Kenma.compile_of(body) 20 pp compiled 21 22 src = compiled.source 23 puts src 24 eval(src) ` ` ` ` ` ` 45 / 89

Slide 98

Slide 98 text

マクロを使おう! 46 / 89

Slide 99

Slide 99 text

定義した関数マクロを使う 1 require "kenma" 2 3 using Kenma::Refine::Source 4 5 module CatMacro 6 using Kenma::Macroable 7 8 def cat! 9 ast { 'nyaaaaan' } 10 end 11 macro_function :cat! 12 end 13 14 body = proc { 15 use_macro! CatMacro 16 17 puts cat! 18 } 19 compiled = Kenma.compile_of(body) 20 pp compiled 21 22 src = compiled.source 23 puts src 24 eval(src) 47 / 89

Slide 100

Slide 100 text

定義した関数マクロを使う 14 body = proc { 15 use_macro! CatMacro 16 17 puts cat! 18 } マクロを適用させるコードを Proc で定義する ブロック内のコードに対してマクロを適用させる 1 require "kenma" 2 3 using Kenma::Refine::Source 4 5 module CatMacro 6 using Kenma::Macroable 7 8 def cat! 9 ast { 'nyaaaaan' } 10 end 11 macro_function :cat! 12 end 13 19 compiled = Kenma.compile_of(body) 20 pp compiled 21 22 src = compiled.source 23 puts src 24 eval(src) ` ` 48 / 89

Slide 101

Slide 101 text

定義した関数マクロを使う 14 body = proc { 15 use_macro! CatMacro 16 17 puts cat! 18 } マクロを適用させるコードを Proc で定義する ブロック内のコードに対してマクロを適用させる NOTE: 今回の実装ではまだ完成度が低いのでファ イル単位ではなくて特定のブロック内でのみマク ロを適用させるような実装にしている 1 require "kenma" 2 3 using Kenma::Refine::Source 4 5 module CatMacro 6 using Kenma::Macroable 7 8 def cat! 9 ast { 'nyaaaaan' } 10 end 11 macro_function :cat! 12 end 13 19 compiled = Kenma.compile_of(body) 20 pp compiled 21 22 src = compiled.source 23 puts src 24 eval(src) ` ` 48 / 89

Slide 102

Slide 102 text

定義した関数マクロを使う 15 use_macro! CatMacro ブロック内で use_macro! を使用すると定義し たマクロが使用できるようになる 1 require "kenma" 2 3 using Kenma::Refine::Source 4 5 module CatMacro 6 using Kenma::Macroable 7 8 def cat! 9 ast { 'nyaaaaan' } 10 end 11 macro_function :cat! 12 end 13 14 body = proc { 16 17 puts cat! 18 } 19 compiled = Kenma.compile_of(body) 20 pp compiled 21 22 src = compiled.source 23 puts src 24 eval(src) ` ` 49 / 89

Slide 103

Slide 103 text

定義した関数マクロを使う 15 use_macro! CatMacro ブロック内で use_macro! を使用すると定義し たマクロが使用できるようになる この use_macro! もマクロで実装されている 1 require "kenma" 2 3 using Kenma::Refine::Source 4 5 module CatMacro 6 using Kenma::Macroable 7 8 def cat! 9 ast { 'nyaaaaan' } 10 end 11 macro_function :cat! 12 end 13 14 body = proc { 16 17 puts cat! 18 } 19 compiled = Kenma.compile_of(body) 20 pp compiled 21 22 src = compiled.source 23 puts src 24 eval(src) ` ` ` ` 49 / 89

Slide 104

Slide 104 text

定義した関数マクロを使う 15 use_macro! CatMacro ブロック内で use_macro! を使用すると定義し たマクロが使用できるようになる この use_macro! もマクロで実装されている また use_macro! は呼び出したコンテキスト内 でのみ反映される 1 class X 2 # クラス内でのみ Hoge マクロが反映される 3 use_macro! HogeMacro 4 5 def foo 6 # メソッド内でのみ FooMacro が反映される 7 use_macro! FooMacro 8 # ... 9 end 10 end 1 require "kenma" 2 3 using Kenma::Refine::Source 4 5 module CatMacro 6 using Kenma::Macroable 7 8 def cat! 9 ast { 'nyaaaaan' } 10 end 11 macro_function :cat! 12 end 13 14 body = proc { 16 17 puts cat! 18 } 19 compiled = Kenma.compile_of(body) 20 pp compiled 21 22 src = compiled.source 23 puts src 24 eval(src) ` ` ` ` ` ` 49 / 89

Slide 105

Slide 105 text

定義した関数マクロを使う 15 use_macro! CatMacro ブロック内で use_macro! を使用すると定義し たマクロが使用できるようになる この use_macro! もマクロで実装されている また use_macro! は呼び出したコンテキスト内 でのみ反映される 2 # クラス内でのみ Hoge マクロが反映される 3 use_macro! HogeMacro 1 require "kenma" 2 3 using Kenma::Refine::Source 4 5 module CatMacro 6 using Kenma::Macroable 7 8 def cat! 9 ast { 'nyaaaaan' } 10 end 11 macro_function :cat! 12 end 13 14 body = proc { 16 17 puts cat! 18 } 19 compiled = Kenma.compile_of(body) 20 pp compiled 21 22 src = compiled.source 23 puts src 24 eval(src) ` ` ` ` ` ` 1 class X 4 5 def foo 6 # メソッド内でのみ FooMacro が反映される 7 use_macro! FooMacro 8 # ... 9 end 10 end 49 / 89

Slide 106

Slide 106 text

定義した関数マクロを使う 15 use_macro! CatMacro ブロック内で use_macro! を使用すると定義し たマクロが使用できるようになる この use_macro! もマクロで実装されている また use_macro! は呼び出したコンテキスト内 でのみ反映される 6 # メソッド内でのみ FooMacro が反映される 7 use_macro! FooMacro 1 require "kenma" 2 3 using Kenma::Refine::Source 4 5 module CatMacro 6 using Kenma::Macroable 7 8 def cat! 9 ast { 'nyaaaaan' } 10 end 11 macro_function :cat! 12 end 13 14 body = proc { 16 17 puts cat! 18 } 19 compiled = Kenma.compile_of(body) 20 pp compiled 21 22 src = compiled.source 23 puts src 24 eval(src) ` ` ` ` ` ` 1 class X 2 # クラス内でのみ Hoge マクロが反映される 3 use_macro! HogeMacro 4 5 def foo 8 # ... 9 end 10 end 49 / 89

Slide 107

Slide 107 text

定義した関数マクロを使う 17 puts cat! マクロを適用させると この cat! が 1 require "kenma" 2 3 using Kenma::Refine::Source 4 5 module CatMacro 6 using Kenma::Macroable 7 8 def cat! 9 ast { 'nyaaaaan' } 10 end 11 macro_function :cat! 12 end 13 14 body = proc { 15 use_macro! CatMacro 16 18 } 19 compiled = Kenma.compile_of(body) 20 pp compiled 21 22 src = compiled.source 23 puts src 24 eval(src) ` ` 50 / 89

Slide 108

Slide 108 text

定義した関数マクロを使う 17 puts 'nyaaaaan' 'nyaaaaan' へと置き換わるイメージ 1 require "kenma" 2 3 using Kenma::Refine::Source 4 5 module CatMacro 6 using Kenma::Macroable 7 8 def cat! 9 ast { 'nyaaaaan' } 10 end 11 macro_function :cat! 12 end 13 14 body = proc { 15 use_macro! CatMacro 16 18 } 19 compiled = Kenma.compile_of(body) 20 pp compiled 21 22 src = compiled.source 23 puts src 24 eval(src) ` ` 51 / 89

Slide 109

Slide 109 text

定義した関数マクロを使う 19 compiled = Kenma.compile_of(body) Kenma.compile_of を使用してマクロを適用さ せる 1 require "kenma" 2 3 using Kenma::Refine::Source 4 5 module CatMacro 6 using Kenma::Macroable 7 8 def cat! 9 ast { 'nyaaaaan' } 10 end 11 macro_function :cat! 12 end 13 14 body = proc { 15 use_macro! CatMacro 16 17 puts cat! 18 } 20 pp compiled 21 22 src = compiled.source 23 puts src 24 eval(src) ` ` 52 / 89

Slide 110

Slide 110 text

定義した関数マクロを使う 19 compiled = Kenma.compile_of(body) Kenma.compile_of を使用してマクロを適用さ せる Proc の中身に対してマクロが実行される 1 require "kenma" 2 3 using Kenma::Refine::Source 4 5 module CatMacro 6 using Kenma::Macroable 7 8 def cat! 9 ast { 'nyaaaaan' } 10 end 11 macro_function :cat! 12 end 13 14 body = proc { 15 use_macro! CatMacro 16 17 puts cat! 18 } 20 pp compiled 21 22 src = compiled.source 23 puts src 24 eval(src) ` ` ` ` 52 / 89

Slide 111

Slide 111 text

定義した関数マクロを使う 20 pp compiled Kenma.compile_of を使用してマクロを適用さ せる Proc の中身に対してマクロが実行される [適用後の結果] 1 [:SCOPE, 2 [[], 3 nil, 4 [:BLOCK, 5 [[:FCALL, 6 [:puts, 7 [:LIST, 8 [(STR@1:0-1:10 "nyaaaaan"), nil]]]]]]]] 1 require "kenma" 2 3 using Kenma::Refine::Source 4 5 module CatMacro 6 using Kenma::Macroable 7 8 def cat! 9 ast { 'nyaaaaan' } 10 end 11 macro_function :cat! 12 end 13 14 body = proc { 15 use_macro! CatMacro 16 17 puts cat! 18 } 19 compiled = Kenma.compile_of(body) 21 22 src = compiled.source 23 puts src 24 eval(src) ` ` ` ` 52 / 89

Slide 112

Slide 112 text

変換前と変換後の AST の比較 53 / 89

Slide 113

Slide 113 text

変換前と変換後の AST の比較 [変換前の AST] 1 (SCOPE@28:12-32:1 2 tbl: [] 3 args: nil 4 body: 5 (BLOCK@29:2-31:11 6 (FCALL@29:2-29:21 :use_macro! 7 (LIST@29:13-29:21 8 (CONST@29:13-29:21 :CatMacro) nil)) 9 (FCALL@31:2-31:11 :puts 10 (LIST@31:7-31:11 11 (FCALL@31:7-31:11 :cat! nil) nil)))) 53 / 89

Slide 114

Slide 114 text

変換前と変換後の AST の比較 [変換前の AST] 1 (SCOPE@28:12-32:1 2 tbl: [] 3 args: nil 4 body: 5 (BLOCK@29:2-31:11 6 (FCALL@29:2-29:21 :use_macro! 7 (LIST@29:13-29:21 8 (CONST@29:13-29:21 :CatMacro) nil)) 9 (FCALL@31:2-31:11 :puts 10 (LIST@31:7-31:11 11 (FCALL@31:7-31:11 :cat! nil) nil)))) [変換後の AST] 1 [:SCOPE, 2 [[], 3 nil, 4 [:BLOCK, 5 [[:FCALL, 6 [:puts, 7 [:LIST, 8 [(STR@1:0-1:10 "nyaaaaan"), nil]]]]]]]] 53 / 89

Slide 115

Slide 115 text

変換前と変換後の AST の比較 [変換前の AST] 1 (SCOPE@28:12-32:1 2 tbl: [] 3 args: nil 4 body: 5 (BLOCK@29:2-31:11 6 (FCALL@29:2-29:21 :use_macro! 7 (LIST@29:13-29:21 8 (CONST@29:13-29:21 :CatMacro) nil)) 9 (FCALL@31:2-31:11 :puts 10 (LIST@31:7-31:11 11 (FCALL@31:7-31:11 :cat! nil) nil)))) [変換後の AST] 8 [(STR@1:0-1:10 "nyaaaaan"), nil]]]]]]]] ここの部分が cat! メソッドの戻り値の AST に置き換えられている 1 [:SCOPE, 2 [[], 3 nil, 4 [:BLOCK, 5 [[:FCALL, 6 [:puts, 7 [:LIST, ` ` 53 / 89

Slide 116

Slide 116 text

変換前と変換後の AST の比較 [変換前の AST] 1 (SCOPE@28:12-32:1 2 tbl: [] 3 args: nil 4 body: 5 (BLOCK@29:2-31:11 6 (FCALL@29:2-29:21 :use_macro! 7 (LIST@29:13-29:21 8 (CONST@29:13-29:21 :CatMacro) nil)) 9 (FCALL@31:2-31:11 :puts 10 (LIST@31:7-31:11 11 (FCALL@31:7-31:11 :cat! nil) nil)))) [変換後の AST] 8 [(STR@1:0-1:10 "nyaaaaan"), nil]]]]]]]] ここの部分が cat! メソッドの戻り値の AST に置き換えられている 変換後の AST は RubyVM::AST::Node と配列が混ざっているのに注意 1 [:SCOPE, 2 [[], 3 nil, 4 [:BLOCK, 5 [[:FCALL, 6 [:puts, 7 [:LIST, ` ` ` ` 53 / 89

Slide 117

Slide 117 text

定義した関数マクロを使う 22 src = compiled.source 最後に AST から Ruby のコードに変換する 1 require "kenma" 2 3 using Kenma::Refine::Source 4 5 module CatMacro 6 using Kenma::Macroable 7 8 def cat! 9 ast { 'nyaaaaan' } 10 end 11 macro_function :cat! 12 end 13 14 body = proc { 15 use_macro! CatMacro 16 17 puts cat! 18 } 19 compiled = Kenma.compile_of(body) 20 pp compiled 21 23 puts src 24 eval(src) 54 / 89

Slide 118

Slide 118 text

定義した関数マクロを使う 3 using Kenma::Refine::Source 最後に AST から Ruby のコードに変換する using すると #source が使えるようになり 1 require "kenma" 2 4 5 module CatMacro 6 using Kenma::Macroable 7 8 def cat! 9 ast { 'nyaaaaan' } 10 end 11 macro_function :cat! 12 end 13 14 body = proc { 15 use_macro! CatMacro 16 17 puts cat! 18 } 19 compiled = Kenma.compile_of(body) 20 pp compiled 21 22 src = compiled.source 23 puts src 24 eval(src) ` ` ` ` 54 / 89

Slide 119

Slide 119 text

定義した関数マクロを使う 22 src = compiled.source 23 puts src 最後に AST から Ruby のコードに変換する using すると #source が使えるようになり AST から Ruby のコードが取得できる 1 puts compiled.source 2 # => puts("nyaaaaan"); 1 require "kenma" 2 3 using Kenma::Refine::Source 4 5 module CatMacro 6 using Kenma::Macroable 7 8 def cat! 9 ast { 'nyaaaaan' } 10 end 11 macro_function :cat! 12 end 13 14 body = proc { 15 use_macro! CatMacro 16 17 puts cat! 18 } 19 compiled = Kenma.compile_of(body) 20 pp compiled 21 24 eval(src) ` ` ` ` 54 / 89

Slide 120

Slide 120 text

定義した関数マクロを使う 24 eval(src) 最後に AST から Ruby のコードに変換する using すると #source が使えるようになり AST から Ruby のコードが取得できる 1 puts compiled.source 2 # => puts("nyaaaaan"); 最後に変換したコードを eval で評価する [出力結果] 1 eval(src) 2 # => nyaaaaan 1 require "kenma" 2 3 using Kenma::Refine::Source 4 5 module CatMacro 6 using Kenma::Macroable 7 8 def cat! 9 ast { 'nyaaaaan' } 10 end 11 macro_function :cat! 12 end 13 14 body = proc { 15 use_macro! CatMacro 16 17 puts cat! 18 } 19 compiled = Kenma.compile_of(body) 20 pp compiled 21 22 src = compiled.source 23 puts src ` ` ` ` ` ` 54 / 89

Slide 121

Slide 121 text

定義した関数マクロを使う 14 body = proc { 15 use_macro! CatMacro 16 17 puts cat! 18 } 19 compiled = Kenma.compile_of(body) 20 pp compiled 21 22 src = compiled.source 23 puts src 24 eval(src) 最後に AST から Ruby のコードに変換する using すると #source が使えるようになり AST から Ruby のコードが取得できる 1 puts compiled.source 2 # => puts("nyaaaaan"); 最後に変換したコードを eval で評価する [出力結果] 1 eval(src) 2 # => nyaaaaan また Proc から eval するまでの動作をまとめ て 1 require "kenma" 2 3 using Kenma::Refine::Source 4 5 module CatMacro 6 using Kenma::Macroable 7 8 def cat! 9 ast { 'nyaaaaan' } 10 end 11 macro_function :cat! 12 end 13 ` ` ` ` ` ` ` ` ` ` 54 / 89

Slide 122

Slide 122 text

定義した関数マクロを使う 14 Kenma.macro_eval { 15 use_macro! CatMacro 16 17 puts cat! 18 } Kenma.macro_eval でまとめることができる 1 require "kenma" 2 3 using Kenma::Refine::Source 4 5 module CatMacro 6 using Kenma::Macroable 7 8 def cat! 9 ast { 'nyaaaaan' } 10 end 11 macro_function :cat! 12 end 13 ` ` 55 / 89

Slide 123

Slide 123 text

定義した関数マクロを使う 14 Kenma.macro_eval { 15 use_macro! CatMacro 16 17 puts cat! 18 } Kenma.macro_eval でまとめることができる Kenma.macro_eval のブロック内のコードにマ クロが反映され評価される 1 require "kenma" 2 3 using Kenma::Refine::Source 4 5 module CatMacro 6 using Kenma::Macroable 7 8 def cat! 9 ast { 'nyaaaaan' } 10 end 11 macro_function :cat! 12 end 13 ` ` ` ` 55 / 89

Slide 124

Slide 124 text

定義した関数マクロを使う 14 Kenma.macro_eval { 15 use_macro! CatMacro 16 17 puts cat! 18 } Kenma.macro_eval でまとめることができる Kenma.macro_eval のブロック内のコードにマ クロが反映され評価される このようにして特定のメソッドを別の AST に置き 換える事ができる 1 require "kenma" 2 3 using Kenma::Refine::Source 4 5 module CatMacro 6 using Kenma::Macroable 7 8 def cat! 9 ast { 'nyaaaaan' } 10 end 11 macro_function :cat! 12 end 13 ` ` ` ` 55 / 89

Slide 125

Slide 125 text

関数マクロの引数の話 56 / 89

Slide 126

Slide 126 text

関数マクロの引数の話 1 module CatMacro 2 using Kenma::Macroable 3 4 def cat!(num) 5 ast { "nyaaaaan" * num } 6 end 7 macro_function :cat! 8 end 9 10 body = proc { 11 use_macro! CatMacro 12 13 puts cat!(3) 14 } 15 puts Kenma.compile_of(body).source 57 / 89

Slide 127

Slide 127 text

関数マクロの引数の話 13 puts cat!(3) cat!(3) のようにマクロ関数に対して引数を渡 したい 1 module CatMacro 2 using Kenma::Macroable 3 4 def cat!(num) 5 ast { "nyaaaaan" * num } 6 end 7 macro_function :cat! 8 end 9 10 body = proc { 11 use_macro! CatMacro 12 14 } 15 puts Kenma.compile_of(body).source ` ` 57 / 89

Slide 128

Slide 128 text

関数マクロの引数の話 4 def cat!(num) cat!(3) のようにマクロ関数に対して引数を渡 したい この引数は cat! メソッドの引数として受け取 ることができる 1 module CatMacro 2 using Kenma::Macroable 3 5 ast { "nyaaaaan" * num } 6 end 7 macro_function :cat! 8 end 9 10 body = proc { 11 use_macro! CatMacro 12 13 puts cat!(3) 14 } 15 puts Kenma.compile_of(body).source ` ` ` ` 57 / 89

Slide 129

Slide 129 text

関数マクロの引数の話 4 def cat!(num) cat!(3) のようにマクロ関数に対して引数を渡 したい この引数は cat! メソッドの引数として受け取 ることができる ただし、 num は 3 という値ではなくて AST と して受け取る 1 cat!(3) 2 # num => (LIT@24:12-24:13 3) 1 module CatMacro 2 using Kenma::Macroable 3 5 ast { "nyaaaaan" * num } 6 end 7 macro_function :cat! 8 end 9 10 body = proc { 11 use_macro! CatMacro 12 13 puts cat!(3) 14 } 15 puts Kenma.compile_of(body).source ` ` ` ` ` ` ` ` 57 / 89

Slide 130

Slide 130 text

関数マクロの引数の話 4 def cat!(num) cat!(3) のようにマクロ関数に対して引数を渡 したい この引数は cat! メソッドの引数として受け取 ることができる ただし、 num は 3 という値ではなくて AST と して受け取る 1 cat!(3) 2 # num => (LIT@24:12-24:13 3) また 1 + 2 みたいな式も AST として受け取る 1 cat!(1 + 2) 2 # num => (OPCALL@24:12-24:17 (LIT@24:12-24:13 1) :+ 3 # (LIST@24:16-24:17 (LIT@24:16-24:17 2) nil)) 1 module CatMacro 2 using Kenma::Macroable 3 5 ast { "nyaaaaan" * num } 6 end 7 macro_function :cat! 8 end 9 10 body = proc { 11 use_macro! CatMacro 12 13 puts cat!(3) 14 } 15 puts Kenma.compile_of(body).source ` ` ` ` ` ` ` ` ` ` 57 / 89

Slide 131

Slide 131 text

関数マクロの引数の話 5 ast { "nyaaaaan" * num } この時に ast {} 内でそのまま変数 num を参 照しようとすると 1 module CatMacro 2 using Kenma::Macroable 3 4 def cat!(num) 6 end 7 macro_function :cat! 8 end 9 10 body = proc { 11 use_macro! CatMacro 12 13 puts cat!(3) 14 } 15 puts Kenma.compile_of(body).source ` ` ` ` 58 / 89

Slide 132

Slide 132 text

関数マクロの引数の話 13 puts "nyaaaaan" * num 値ではなくて num というコードがそのまま展開 されてしまう 1 module CatMacro 2 using Kenma::Macroable 3 4 def cat!(num) 5 ast { "nyaaaaan" * num } 6 end 7 macro_function :cat! 8 end 9 10 body = proc { 11 use_macro! CatMacro 12 14 } 15 puts Kenma.compile_of(body).source ` ` 59 / 89

Slide 133

Slide 133 text

関数マクロの引数の話 5 ast { "nyaaaaan" * num } なので num ではなくて 1 module CatMacro 2 using Kenma::Macroable 3 4 def cat!(num) 6 end 7 macro_function :cat! 8 end 9 10 body = proc { 11 use_macro! CatMacro 12 13 puts cat!(3) 14 } 15 puts Kenma.compile_of(body).source ` ` 60 / 89

Slide 134

Slide 134 text

関数マクロの引数の話 5 ast { "nyaaaaan" * node_bind!(num) } node_bind! という特別なマクロを介して参照 する必要がある 1 module CatMacro 2 using Kenma::Macroable 3 4 def cat!(num) 6 end 7 macro_function :cat! 8 end 9 10 body = proc { 11 use_macro! CatMacro 12 13 puts cat!(3) 14 } 15 puts Kenma.compile_of(body).source ` ` 61 / 89

Slide 135

Slide 135 text

関数マクロの引数の話 5 ast { "nyaaaaan" * node_bind!(num) } node_bind! という特別なマクロを介して参照 する必要がある node_bind! を使用する事で引数の AST が直接 AST に展開される 1 node = ast { "nyaaaaan" } 2 3 # AST が展開される 4 pp ast { node_bind!(node) } 5 # => (STR@30:13-30:23 "nyaaaaan") 6 a = ast { 1 } 7 b = ast { 2 } 1 module CatMacro 2 using Kenma::Macroable 3 4 def cat!(num) 6 end 7 macro_function :cat! 8 end 9 10 body = proc { 11 use_macro! CatMacro 12 13 puts cat!(3) 14 } 15 puts Kenma.compile_of(body).source ` ` ` ` 61 / 89

Slide 136

Slide 136 text

関数マクロの引数の話 5 ast { "nyaaaaan" * node_bind!(num) } node_bind! という特別なマクロを介して参照 する必要がある node_bind! を使用する事で引数の AST が直接 AST に展開される 1 node = ast { "nyaaaaan" } 2 3 # AST が展開される 4 pp ast { node_bind!(node) } 5 # => (STR@30:13-30:23 "nyaaaaan") 6 a = ast { 1 } 7 b = ast { 2 } なので puts cat!(3) は 1 module CatMacro 2 using Kenma::Macroable 3 4 def cat!(num) 6 end 7 macro_function :cat! 8 end 9 10 body = proc { 11 use_macro! CatMacro 12 13 puts cat!(3) 14 } 15 puts Kenma.compile_of(body).source ` ` ` ` ` ` 61 / 89

Slide 137

Slide 137 text

関数マクロの引数の話 13 puts "nyaaaaan" * 3 puts "nyaaaaan" * 3 と展開されるイメージ 1 module CatMacro 2 using Kenma::Macroable 3 4 def cat!(num) 5 ast { "nyaaaaan" * node_bind!(num) } 6 end 7 macro_function :cat! 8 end 9 10 body = proc { 11 use_macro! CatMacro 12 14 } 15 puts Kenma.compile_of(body).source ` ` 62 / 89

Slide 138

Slide 138 text

関数マクロの引数の話 5 ast { "nyaaaaan" * node_bind!(num) } puts "nyaaaaan" * 3 と展開されるイメージ また node_bind!(num) は 1 module CatMacro 2 using Kenma::Macroable 3 4 def cat!(num) 6 end 7 macro_function :cat! 8 end 9 10 body = proc { 11 use_macro! CatMacro 12 13 puts "nyaaaaan" * 3 14 } 15 puts Kenma.compile_of(body).source ` ` ` ` 62 / 89

Slide 139

Slide 139 text

関数マクロの引数の話 5 ast { "nyaaaaan" * $num } $num と記述する事もできる グローバル変数を node_bind! に置き換えるような仕組 みを内部で実装してる 1 node = ast { "nyaaaaan" } 2 3 # AST are expanded 4 ppp ast { $node } 5 # => (STR@30:13-30:23 "nyaaaaan") 1 module CatMacro 2 using Kenma::Macroable 3 4 def cat!(num) 6 end 7 macro_function :cat! 8 end 9 10 body = proc { 11 use_macro! CatMacro 12 13 puts cat!(3) 14 } 15 puts Kenma.compile_of(body).source ` ` ` ` 63 / 89

Slide 140

Slide 140 text

関数マクロの引数の話 15 puts Kenma.compile_of(body).source $num と記述する事もできる グローバル変数を node_bind! に置き換えるような仕組 みを内部で実装してる 1 node = ast { "nyaaaaan" } 2 3 # AST are expanded 4 ppp ast { $node } 5 # => (STR@30:13-30:23 "nyaaaaan") 最終的な以下のようなコードに展開される 1 puts("nyaaaaan" * 3) 1 module CatMacro 2 using Kenma::Macroable 3 4 def cat!(num) 5 ast { "nyaaaaan" * $num } 6 end 7 macro_function :cat! 8 end 9 10 body = proc { 11 use_macro! CatMacro 12 13 puts cat!(3) 14 } ` ` ` ` 63 / 89

Slide 141

Slide 141 text

関数マクロの引数の話 15 puts Kenma.compile_of(body).source $num と記述する事もできる グローバル変数を node_bind! に置き換えるような仕組 みを内部で実装してる 1 node = ast { "nyaaaaan" } 2 3 # AST are expanded 4 ppp ast { $node } 5 # => (STR@30:13-30:23 "nyaaaaan") 最終的な以下のようなコードに展開される 1 puts("nyaaaaan" * 3) 他にも stringify! マクロが標準で使用できる 引数の式を文字列の AST にするマクロ 1 pp ast { stringify! 1 + 2 * 3 } 2 # => [:STR, ["(1 + (2 * 3))"]] 1 module CatMacro 2 using Kenma::Macroable 3 4 def cat!(num) 5 ast { "nyaaaaan" * $num } 6 end 7 macro_function :cat! 8 end 9 10 body = proc { 11 use_macro! CatMacro 12 13 puts cat!(3) 14 } ` ` ` ` ` ` 63 / 89

Slide 142

Slide 142 text

ノードマクロ 64 / 89

Slide 143

Slide 143 text

ノードマクロ 65 / 89

Slide 144

Slide 144 text

ノードマクロ 特定の AST の種類に対して AST を置き換えるマクロ 65 / 89

Slide 145

Slide 145 text

ノードマクロ 特定の AST の種類に対して AST を置き換えるマクロ 基本的には関数マクロと同じで『置き換えたい AST を返すメソッド』を定義する 65 / 89

Slide 146

Slide 146 text

ノードマクロ 特定の AST の種類に対して AST を置き換えるマクロ 基本的には関数マクロと同じで『置き換えたい AST を返すメソッド』を定義する hoge.foo.bar を hoge&.foo&.bar に置き換えるマクロを書いてみる ` ` ` ` 65 / 89

Slide 147

Slide 147 text

ノードマクロ 特定の 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

Slide 148

Slide 148 text

ノードマクロ 特定の AST の種類に対して AST を置き換えるマクロ 基本的には関数マクロと同じで『置き換えたい AST を返すメソッド』を定義する hoge.foo.bar を hoge&.foo&.bar に置き換えるマクロを書いてみる 1 hoge.foo.bar 2 # AST => [:CALL, [[:CALL, [[:VCALL, [:hoge]], :foo, nil]], :bar, nil]] を 1 hoge&.foo&.bar 2 # AST => [:QCALL, [[:QCALL, [[:VCALL, [:hoge]], :foo, nil]], :bar, nil]] のよう CALL を QCALL へと変換する ` ` ` ` ` ` ` ` 65 / 89

Slide 149

Slide 149 text

ノードマクロを定義する 1 require "kenma" 2 3 using Kenma::Refine::Source 4 5 module BocchiMacro 6 using Kenma::Macroable 7 8 def bocchi(node, parent) 9 [:QCALL, node.children] 10 end 11 macro_node :CALL, :bocchi 12 end 13 14 body = proc { 15 use_macro! BocchiMacro 16 17 hoge.foo.bar 18 } 19 puts Kenma.compile_of(body).source 66 / 89

Slide 150

Slide 150 text

ノードマクロを定義する 5 module BocchiMacro 6 using Kenma::Macroable 7 8 def bocchi(node, parent) 9 [:QCALL, node.children] 10 end 11 macro_node :CALL, :bocchi 12 end 関数マクロと同様にまずモジュールを定義する 1 require "kenma" 2 3 using Kenma::Refine::Source 4 13 14 body = proc { 15 use_macro! BocchiMacro 16 17 hoge.foo.bar 18 } 19 puts Kenma.compile_of(body).source 66 / 89

Slide 151

Slide 151 text

ノードマクロを定義する 8 def bocchi(node, parent) 9 [:QCALL, node.children] 10 end 関数マクロと同様にまずモジュールを定義する AST を受け取り AST を返すメソッドを定義する 今回は配列として AST の情報を返している 1 require "kenma" 2 3 using Kenma::Refine::Source 4 5 module BocchiMacro 6 using Kenma::Macroable 7 11 macro_node :CALL, :bocchi 12 end 13 14 body = proc { 15 use_macro! BocchiMacro 16 17 hoge.foo.bar 18 } 19 puts Kenma.compile_of(body).source 66 / 89

Slide 152

Slide 152 text

ノードマクロを定義する 11 macro_node :CALL, :bocchi 関数マクロと同様にまずモジュールを定義する AST を受け取り AST を返すメソッドを定義する 今回は配列として AST の情報を返している macro_node でどの AST に対して処理をフック するか指定してマクロであることを宣言する 1 require "kenma" 2 3 using Kenma::Refine::Source 4 5 module BocchiMacro 6 using Kenma::Macroable 7 8 def bocchi(node, parent) 9 [:QCALL, node.children] 10 end 12 end 13 14 body = proc { 15 use_macro! BocchiMacro 16 17 hoge.foo.bar 18 } 19 puts Kenma.compile_of(body).source ` ` 66 / 89

Slide 153

Slide 153 text

ノードマクロを定義する 8 def bocchi(node, parent) 11 macro_node :CALL, :bocchi 関数マクロと同様にまずモジュールを定義する AST を受け取り AST を返すメソッドを定義する 今回は配列として AST の情報を返している macro_node でどの AST に対して処理をフック するか指定してマクロであることを宣言する この CALL という AST のデータが node の引数 として渡ってくる parent は親のノード情報 1 require "kenma" 2 3 using Kenma::Refine::Source 4 5 module BocchiMacro 6 using Kenma::Macroable 7 9 [:QCALL, node.children] 10 end 12 end 13 14 body = proc { 15 use_macro! BocchiMacro 16 17 hoge.foo.bar 18 } 19 puts Kenma.compile_of(body).source ` ` ` ` ` ` ` ` 66 / 89

Slide 154

Slide 154 text

ノードマクロを定義する 9 [:QCALL, node.children] 関数マクロと同様にまずモジュールを定義する AST を受け取り AST を返すメソッドを定義する 今回は配列として AST の情報を返している macro_node でどの AST に対して処理をフック するか指定してマクロであることを宣言する この CALL という AST のデータが node の引数 として渡ってくる parent は親のノード情報 今回は CALL を QCALL という命令に置き換え たいので子情報はそのままで種類を変えた AST を 返している 1 require "kenma" 2 3 using Kenma::Refine::Source 4 5 module BocchiMacro 6 using Kenma::Macroable 7 8 def bocchi(node, parent) 10 end 11 macro_node :CALL, :bocchi 12 end 13 14 body = proc { 15 use_macro! BocchiMacro 16 17 hoge.foo.bar 18 } 19 puts Kenma.compile_of(body).source ` ` ` ` ` ` ` ` ` ` ` ` 66 / 89

Slide 155

Slide 155 text

ノードマクロを定義する 14 body = proc { 15 use_macro! BocchiMacro 16 17 hoge.foo.bar 18 } 19 puts Kenma.compile_of(body).source 使い方は関数マクロと同じ 1 require "kenma" 2 3 using Kenma::Refine::Source 4 5 module BocchiMacro 6 using Kenma::Macroable 7 8 def bocchi(node, parent) 9 [:QCALL, node.children] 10 end 11 macro_node :CALL, :bocchi 12 end 13 67 / 89

Slide 156

Slide 156 text

ノードマクロを定義する 15 use_macro! BocchiMacro 使い方は関数マクロと同じ 使用したい場所で use_macro! を使用する 1 require "kenma" 2 3 using Kenma::Refine::Source 4 5 module BocchiMacro 6 using Kenma::Macroable 7 8 def bocchi(node, parent) 9 [:QCALL, node.children] 10 end 11 macro_node :CALL, :bocchi 12 end 13 14 body = proc { 16 17 hoge.foo.bar 18 } 19 puts Kenma.compile_of(body).source ` ` 67 / 89

Slide 157

Slide 157 text

ノードマクロを定義する 17 hoge.foo.bar 使い方は関数マクロと同じ 使用したい場所で use_macro! を使用する hoge.foo.bar が 1 require "kenma" 2 3 using Kenma::Refine::Source 4 5 module BocchiMacro 6 using Kenma::Macroable 7 8 def bocchi(node, parent) 9 [:QCALL, node.children] 10 end 11 macro_node :CALL, :bocchi 12 end 13 14 body = proc { 15 use_macro! BocchiMacro 16 18 } 19 puts Kenma.compile_of(body).source ` ` ` ` 67 / 89

Slide 158

Slide 158 text

ノードマクロを定義する 17 hoge&.foo&.bar hoge&.foo&.bar に置き換わる 1 require "kenma" 2 3 using Kenma::Refine::Source 4 5 module BocchiMacro 6 using Kenma::Macroable 7 8 def bocchi(node, parent) 9 [:QCALL, node.children] 10 end 11 macro_node :CALL, :bocchi 12 end 13 14 body = proc { 15 use_macro! BocchiMacro 16 18 } 19 puts Kenma.compile_of(body).source ` ` 68 / 89

Slide 159

Slide 159 text

ノードマクロを定義する 19 puts Kenma.compile_of(body).source hoge&.foo&.bar に置き換わる [出力結果] 1 puts Kenma.compile_of(body).source 2 # => hoge&.foo()&.bar(); 1 require "kenma" 2 3 using Kenma::Refine::Source 4 5 module BocchiMacro 6 using Kenma::Macroable 7 8 def bocchi(node, parent) 9 [:QCALL, node.children] 10 end 11 macro_node :CALL, :bocchi 12 end 13 14 body = proc { 15 use_macro! BocchiMacro 16 17 hoge&.foo&.bar 18 } ` ` 68 / 89

Slide 160

Slide 160 text

パターンマクロ 69 / 89

Slide 161

Slide 161 text

パターンマクロ 70 / 89

Slide 162

Slide 162 text

パターンマクロ 特定の Ruby の構文に対して AST を置き換えるマクロ 70 / 89

Slide 163

Slide 163 text

パターンマクロ 特定の Ruby の構文に対して AST を置き換えるマクロ value = [1, 2, 3] を value = [1, 2, 3].freeze に置き換えるマクロを書く ` ` ` ` 70 / 89

Slide 164

Slide 164 text

パターンマクロ 特定の Ruby の構文に対して AST を置き換えるマクロ value = [1, 2, 3] を value = [1, 2, 3].freeze に置き換えるマクロを書く 1 value = [1, 2, 3] 2 # AST => [:DASGN_CURR, [:value, [:LIST, [[:LIT, [1]], [:LIT, [2]], [:LIT, [3]], nil]]]] を ` ` ` ` 70 / 89

Slide 165

Slide 165 text

パターンマクロ 特定の Ruby の構文に対して AST を置き換えるマクロ value = [1, 2, 3] を value = [1, 2, 3].freeze に置き換えるマクロを書く 1 value = [1, 2, 3] 2 # AST => [:DASGN_CURR, [:value, [:LIST, [[:LIT, [1]], [:LIT, [2]], [:LIT, [3]], nil]]]] を 1 value = [1, 2, 3].freeze 2 # AST => [:DASGN_CURR, 3 # [:value, 4 # [:CALL, 5 # [[:LIST, [[:LIT, [1]], [:LIT, [2]], [:LIT, [3]], nil]], :freeze, nil]]]] のよう [:CALL, [..., :freeze]] を付加させる ` ` ` ` ` ` 70 / 89

Slide 166

Slide 166 text

パターンマクロを定義する 1 require "kenma" 2 3 using Kenma::Refine::Source 4 5 module FreezeMacro 6 def freezing(node, name:, value:) 7 ast { $name = $value.freeze } 8 end 9 macro_pattern pat { $name = $value }, :freezing 10 end 11 12 body = proc { 13 use_macro! FreezeMacro 14 15 value = [1, 2, 3] 16 } 17 puts Kenma.compile_of(body).source 71 / 89

Slide 167

Slide 167 text

パターンマクロを定義する 9 macro_pattern pat { $name = $value }, :freezing どの構文でマッチするのかを macro_pattern の引数で定義する 1 require "kenma" 2 3 using Kenma::Refine::Source 4 5 module FreezeMacro 6 def freezing(node, name:, value:) 7 ast { $name = $value.freeze } 8 end 10 end 11 12 body = proc { 13 use_macro! FreezeMacro 14 15 value = [1, 2, 3] 16 } 17 puts Kenma.compile_of(body).source ` ` 71 / 89

Slide 168

Slide 168 text

パターンマクロを定義する 9 macro_pattern pat { $name = $value }, :freezing どの構文でマッチするのかを macro_pattern の引数で定義する pat で抽象的に『どの構文でマッチする のか』を定義する事ができる $ はグローバル変数ではなくてマッチした AST を束縛する 1 # マッチした場合、束縛した AST の Hash を返す 2 pp pat { $left < $right }.match(ast { 3 < 10 }) 3 # => {:left=>(LIT@16:39-16:40 3), 4 # :right=>(LIT@16:43-16:45 10)} 5 6 # マッチしなかった場合は nil を返す 7 pp pat { $left < $right }.match(ast { 3 > 10 }) 8 # => nil 1 require "kenma" 2 3 using Kenma::Refine::Source 4 5 module FreezeMacro 6 def freezing(node, name:, value:) 7 ast { $name = $value.freeze } 8 end 10 end 11 12 body = proc { 13 use_macro! FreezeMacro 14 15 value = [1, 2, 3] 16 } 17 puts Kenma.compile_of(body).source ` ` ` ` ` ` 71 / 89

Slide 169

Slide 169 text

パターンマクロを定義する 9 macro_pattern pat { $name = $value }, :freezing 今回は name = value という代入式に マッチするパターンを指定している 1 require "kenma" 2 3 using Kenma::Refine::Source 4 5 module FreezeMacro 6 def freezing(node, name:, value:) 7 ast { $name = $value.freeze } 8 end 10 end 11 12 body = proc { 13 use_macro! FreezeMacro 14 15 value = [1, 2, 3] 16 } 17 puts Kenma.compile_of(body).source ` ` 72 / 89

Slide 170

Slide 170 text

パターンマクロを定義する 9 macro_pattern pat { $name = $value }, :freezing 今回は name = value という代入式に マッチするパターンを指定している なのでこのようなパターンになる 1 pp pat { $name = $value }.match(ast { hoge = 42 }) 2 # => {:name=>:hoge, :value=>(LIT@19:45-19:47 42)} 1 require "kenma" 2 3 using Kenma::Refine::Source 4 5 module FreezeMacro 6 def freezing(node, name:, value:) 7 ast { $name = $value.freeze } 8 end 10 end 11 12 body = proc { 13 use_macro! FreezeMacro 14 15 value = [1, 2, 3] 16 } 17 puts Kenma.compile_of(body).source ` ` 72 / 89

Slide 171

Slide 171 text

パターンマクロを定義する 9 macro_pattern pat { $name = $value }, :freezing 今回は name = value という代入式に マッチするパターンを指定している なのでこのようなパターンになる 1 pp pat { $name = $value }.match(ast { hoge = 42 }) 2 # => {:name=>:hoge, :value=>(LIT@19:45-19:47 42)} この match の結果の Hash が 1 require "kenma" 2 3 using Kenma::Refine::Source 4 5 module FreezeMacro 6 def freezing(node, name:, value:) 7 ast { $name = $value.freeze } 8 end 10 end 11 12 body = proc { 13 use_macro! FreezeMacro 14 15 value = [1, 2, 3] 16 } 17 puts Kenma.compile_of(body).source ` ` ` ` ` ` 72 / 89

Slide 172

Slide 172 text

パターンマクロを定義する 6 def freezing(node, name:, value:) 今回は name = value という代入式に マッチするパターンを指定している なのでこのようなパターンになる 1 pp pat { $name = $value }.match(ast { hoge = 42 }) 2 # => {:name=>:hoge, :value=>(LIT@19:45-19:47 42)} この match の結果の Hash が 指定したメソッドのキーワード引数とし て渡される node はマッチした構文全体の AST 1 require "kenma" 2 3 using Kenma::Refine::Source 4 5 module FreezeMacro 7 ast { $name = $value.freeze } 8 end 9 macro_pattern pat { $name = $value }, :freezing 10 end 11 12 body = proc { 13 use_macro! FreezeMacro 14 15 value = [1, 2, 3] 16 } 17 puts Kenma.compile_of(body).source ` ` ` ` ` ` ` ` ` ` 72 / 89

Slide 173

Slide 173 text

パターンマクロを定義する 7 ast { $name = $value.freeze } 今回は name = value という代入式に マッチするパターンを指定している なのでこのようなパターンになる 1 pp pat { $name = $value }.match(ast { hoge = 42 }) 2 # => {:name=>:hoge, :value=>(LIT@19:45-19:47 42)} この match の結果の Hash が 指定したメソッドのキーワード引数とし て渡される node はマッチした構文全体の AST name と value は AST なので $ で束 縛し freeze メソッドを呼び出す 1 require "kenma" 2 3 using Kenma::Refine::Source 4 5 module FreezeMacro 6 def freezing(node, name:, value:) 8 end 9 macro_pattern pat { $name = $value }, :freezing 10 end 11 12 body = proc { 13 use_macro! FreezeMacro 14 15 value = [1, 2, 3] 16 } 17 puts Kenma.compile_of(body).source ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` 72 / 89

Slide 174

Slide 174 text

パターンマクロを定義する 15 value = [1, 2, 3] value = [1, 2, 3] が 1 require "kenma" 2 3 using Kenma::Refine::Source 4 5 module FreezeMacro 6 def freezing(node, name:, value:) 7 ast { $name = $value.freeze } 8 end 9 macro_pattern pat { $name = $value }, :freezing 10 end 11 12 body = proc { 13 use_macro! FreezeMacro 14 16 } 17 puts Kenma.compile_of(body).source ` ` 73 / 89

Slide 175

Slide 175 text

パターンマクロを定義する 15 value = [1, 2, 3].freeze value = [1, 2, 3].freeze に置き換 わる 1 require "kenma" 2 3 using Kenma::Refine::Source 4 5 module FreezeMacro 6 def freezing(node, name:, value:) 7 ast { $name = $value.freeze } 8 end 9 macro_pattern pat { $name = $value }, :freezing 10 end 11 12 body = proc { 13 use_macro! FreezeMacro 14 16 } 17 puts Kenma.compile_of(body).source ` ` 74 / 89

Slide 176

Slide 176 text

パターンマクロを定義する 17 puts Kenma.compile_of(body).source value = [1, 2, 3].freeze に置き換 わる [出力結果] 1 puts Kenma.compile_of(body).source 2 # => (value = [1, 2, 3].freeze()); 1 require "kenma" 2 3 using Kenma::Refine::Source 4 5 module FreezeMacro 6 def freezing(node, name:, value:) 7 ast { $name = $value.freeze } 8 end 9 macro_pattern pat { $name = $value }, :freezing 10 end 11 12 body = proc { 13 use_macro! FreezeMacro 14 15 value = [1, 2, 3].freeze 16 } ` ` 74 / 89

Slide 177

Slide 177 text

マクロの使用例を紹介 75 / 89

Slide 178

Slide 178 text

デバッグ出力マクロ 1 debug! 1 + 2 76 / 89

Slide 179

Slide 179 text

デバッグ出力マクロ 1 debug! 1 + 2 → 76 / 89

Slide 180

Slide 180 text

デバッグ出力マクロ 1 debug! 1 + 2 → 1 puts "1 + 2 # => #{1 + 2}" 76 / 89

Slide 181

Slide 181 text

デバッグ出力マクロ 1 debug! 1 + 2 → 1 puts "1 + 2 # => #{1 + 2}" [コード] 1 module DebugMacro 2 using Kenma::Macroable 3 4 def debug!(expr) 5 ast { puts "#{stringify! $expr} # => #{$expr}" } 6 end 7 macro_function :debug! 8 end 9 10 body = proc { 11 use_macro! DebugMacro 12 13 debug! 1 + 2 + 3 14 } 15 puts Kenma.compile_of(body).source 76 / 89

Slide 182

Slide 182 text

デバッグ出力マクロ 1 debug! 1 + 2 → 1 puts "1 + 2 # => #{1 + 2}" [コード] 4 def debug!(expr) 13 debug! 1 + 2 + 3 debug! の引数を AST で受け取り 1 module DebugMacro 2 using Kenma::Macroable 3 5 ast { puts "#{stringify! $expr} # => #{$expr}" } 6 end 7 macro_function :debug! 8 end 9 10 body = proc { 11 use_macro! DebugMacro 12 14 } 15 puts Kenma.compile_of(body).source ` ` 76 / 89

Slide 183

Slide 183 text

デバッグ出力マクロ 1 debug! 1 + 2 → 1 puts "1 + 2 # => #{1 + 2}" [コード] 5 ast { puts "#{stringify! $expr} # => #{$expr}" } debug! の引数を AST で受け取り stringify! で文字列に変換して 1 module DebugMacro 2 using Kenma::Macroable 3 4 def debug!(expr) 6 end 7 macro_function :debug! 8 end 9 10 body = proc { 11 use_macro! DebugMacro 12 13 debug! 1 + 2 + 3 14 } 15 puts Kenma.compile_of(body).source ` ` ` ` 76 / 89

Slide 184

Slide 184 text

デバッグ出力マクロ 1 debug! 1 + 2 → 1 puts "1 + 2 # => #{1 + 2}" [コード] 5 ast { puts "#{stringify! $expr} # => #{$expr}" } debug! の引数を AST で受け取り stringify! で文字列に変換して 式はそのまま #{} で文字列に埋め込む 1 module DebugMacro 2 using Kenma::Macroable 3 4 def debug!(expr) 6 end 7 macro_function :debug! 8 end 9 10 body = proc { 11 use_macro! DebugMacro 12 13 debug! 1 + 2 + 3 14 } 15 puts Kenma.compile_of(body).source ` ` ` ` ` ` 76 / 89

Slide 185

Slide 185 text

デバッグ出力マクロ 1 debug! 1 + 2 → 1 puts "1 + 2 # => #{1 + 2}" [コード] 15 puts Kenma.compile_of(body).source debug! の引数を AST で受け取り stringify! で文字列に変換して 式はそのまま #{} で文字列に埋め込む [出力結果] 1 puts("#{"((1 + 2) + 3)"} # => #{((1 + 2) + 3)}"); 1 module DebugMacro 2 using Kenma::Macroable 3 4 def debug!(expr) 5 ast { puts "#{stringify! $expr} # => #{$expr}" } 6 end 7 macro_function :debug! 8 end 9 10 body = proc { 11 use_macro! DebugMacro 12 13 debug! 1 + 2 + 3 14 } ` ` ` ` ` ` 76 / 89

Slide 186

Slide 186 text

複数のモジュールを1回で using するマクロ 1 using Hoge, Foo, Bar 77 / 89

Slide 187

Slide 187 text

複数のモジュールを1回で using するマクロ 1 using Hoge, Foo, Bar → 77 / 89

Slide 188

Slide 188 text

複数のモジュールを1回で using するマクロ 1 using Hoge, Foo, Bar → 1 using Hoge 2 using Foo 3 using Bar 77 / 89

Slide 189

Slide 189 text

複数のモジュールを1回で using するマクロ 1 using Hoge, Foo, Bar → 1 using Hoge 2 using Foo 3 using Bar [コード] 1 require "kenma" 2 3 using Kenma::Refine::Source 4 5 module MultiUsingMacro 6 using Kenma::Macroable 7 8 def using(*args) 9 args.compact.inject(ast { {} }) { |result, name| 10 ast { $result; using $name } 11 } 12 end 13 macro_function :using 14 end 15 16 body = proc { 17 use_macro! MultiUsingMacro 18 using Hoge, Foo, Bar 19 } 20 21 result = Kenma.compile_of(body) 22 puts result.source 77 / 89

Slide 190

Slide 190 text

複数のモジュールを1回で using するマクロ 1 using Hoge, Foo, Bar → 1 using Hoge 2 using Foo 3 using Bar [コード] 8 def using(*args) using の引数 Hoge, Foo, Bar を可 変長引数で受け取る Hoge Foo Bar の AST を配列で受け取る 1 require "kenma" 2 3 using Kenma::Refine::Source 4 5 module MultiUsingMacro 6 using Kenma::Macroable 7 9 args.compact.inject(ast { {} }) { |result, name| 10 ast { $result; using $name } 11 } 12 end 13 macro_function :using 14 end 15 16 body = proc { 17 use_macro! MultiUsingMacro 18 using Hoge, Foo, Bar 19 } 20 21 result = Kenma.compile_of(body) 22 puts result.source ` ` ` ` ` ` ` ` ` ` 77 / 89

Slide 191

Slide 191 text

複数のモジュールを1回で using するマクロ 1 using Hoge, Foo, Bar → 1 using Hoge 2 using Foo 3 using Bar [コード] 9 args.compact.inject(ast { {} }) { |result, name| 10 ast { $result; using $name } 11 } using の引数 Hoge, Foo, Bar を可 変長引数で受け取る Hoge Foo Bar の AST を配列で受け取る Hoge Foo Bar を1つずつイテレー ションして using Hoge; using Foo; using Bar になるように using する 1 require "kenma" 2 3 using Kenma::Refine::Source 4 5 module MultiUsingMacro 6 using Kenma::Macroable 7 8 def using(*args) 12 end 13 macro_function :using 14 end 15 16 body = proc { 17 use_macro! MultiUsingMacro 18 using Hoge, Foo, Bar 19 } 20 21 result = Kenma.compile_of(body) 22 puts result.source ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` 77 / 89

Slide 192

Slide 192 text

複数のモジュールを1回で using するマクロ 1 using Hoge, Foo, Bar → 1 using Hoge 2 using Foo 3 using Bar [コード] 21 result = Kenma.compile_of(body) 22 puts result.source using の引数 Hoge, Foo, Bar を可 変長引数で受け取る 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; 1 require "kenma" 2 3 using Kenma::Refine::Source 4 5 module MultiUsingMacro 6 using Kenma::Macroable 7 8 def using(*args) 9 args.compact.inject(ast { {} }) { |result, name| 10 ast { $result; using $name } 11 } 12 end 13 macro_function :using 14 end 15 16 body = proc { 17 use_macro! MultiUsingMacro 18 using Hoge, Foo, Bar 19 } 20 ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` 77 / 89

Slide 193

Slide 193 text

{ a: a, b: b, c: c } を簡略的に定義する 1 ![a, b, c] 78 / 89

Slide 194

Slide 194 text

{ a: a, b: b, c: c } を簡略的に定義する 1 ![a, b, c] → 78 / 89

Slide 195

Slide 195 text

{ a: a, b: b, c: c } を簡略的に定義する 1 ![a, b, c] → 1 { a: a, b: b, c: c } 78 / 89

Slide 196

Slide 196 text

{ a: a, b: b, c: c } を簡略的に定義する 1 ![a, b, c] → 1 { a: a, b: b, c: 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 78 / 89

Slide 197

Slide 197 text

{ a: a, b: b, c: c } を簡略的に定義する 1 ![a, b, c] → 1 { a: a, b: b, c: c } [コード] 13 macro_pattern pat { ![*$args] }, :shorthand_hash_literal ![] の配列の中身を受け取るパターン マクロを定義する a, b, c を1つの AST として受け取る 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 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 ` ` ` ` 78 / 89

Slide 198

Slide 198 text

{ a: a, b: b, c: c } を簡略的に定義する 1 ![a, b, c] → 1 { a: a, b: b, c: c } [コード] 8 def shorthand_hash_literal(node, args:) ![] の配列の中身を受け取るパターン マクロを定義する a, b, c を1つの AST として受け取る args で a, b, c を1つの AST とし て受け取る 1 require "kenma" 2 3 using Kenma::Refine::Source 4 5 module ShorthandHashLiteralMacro 6 using Kenma::Macroable 7 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 ` ` ` ` ` ` ` ` 78 / 89

Slide 199

Slide 199 text

{ a: a, b: b, c: c } を簡略的に定義する 1 ![a, b, c] → 1 { a: a, b: b, c: c } [コード] 9 args.children.compact.inject(ast { {} }) { |result, name| 10 ast { $result.merge({ symbolify!($name) => $name }) } 11 } ![] の配列の中身を受け取るパターン マクロを定義する a, b, c を1つの AST として受け取る args で a, b, c を1つの AST とし て受け取る a b c を1つずつイテレーションし て {} に merge する 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:) 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 ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` 78 / 89

Slide 200

Slide 200 text

{ a: a, b: b, c: c } を簡略的に定義する 1 ![a, b, c] → 1 { a: a, b: b, c: c } [コード] 22 result = Kenma.compile_of(body) 23 puts result.source ![] の配列の中身を受け取るパターン マクロを定義する a, b, c を1つの AST として受け取る args で a, b, c を1つの AST とし て受け取る a b c を1つずつイテレーションし て {} に merge する [出力結果] 1 {}.merge({ a: a }).merge({ b: b }).merge({ c: 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 ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` 78 / 89

Slide 201

Slide 201 text

a < b < c を a < b && b < c に置き換える 1 a < b < c 79 / 89

Slide 202

Slide 202 text

a < b < c を a < b && b < c に置き換える 1 a < b < c → 79 / 89

Slide 203

Slide 203 text

a < b < c を a < b && b < c に置き換える 1 a < b < c → 1 a < b && b < c 79 / 89

Slide 204

Slide 204 text

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

Slide 205

Slide 205 text

a < b < c を a < b && b < c に置き換える 1 a < b < c → 1 a < b && b < c [コード] 4 def chaining_comparison_operators(node, parent) 17 macro_node :OPCALL, :chaining_comparison_operators a < b < c 全体の AST を受け取るマ クロを定義 1 module ChainingComparisonOperatorsMacro 2 using Kenma::Macroable 3 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 18 end ` ` 79 / 89

Slide 206

Slide 206 text

a < b < c を a < b && b < c に置き換える 1 a < b < c → 1 a < b && b < c [コード] 5 case node.to_a a < b < c 全体の AST を受け取るマ クロを定義 ここで AST を一旦配列に変換する 1 module ChainingComparisonOperatorsMacro 2 using Kenma::Macroable 3 4 def chaining_comparison_operators(node, parent) 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

Slide 207

Slide 207 text

a < b < c を a < b && b < c に置き換える 1 a < b < c → 1 a < b && b < c [コード] 6 in [:OPCALL, [ 7 [:OPCALL, [left, op1, [:LIST, [middle, nil]]]], 8 op2, [:LIST, [right, nil]]]] a < b < c 全体の AST を受け取るマ クロを定義 ここで AST を一旦配列に変換する パターンマッチで a < b < c にマッ チする AST の配列の値を束縛する 1 module ChainingComparisonOperatorsMacro 2 using Kenma::Macroable 3 4 def chaining_comparison_operators(node, parent) 5 case node.to_a 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

Slide 208

Slide 208 text

a < b < c を a < b && b < c に置き換える 1 a < b < c → 1 a < b && b < c [コード] 9 ast { 10 $left.send(eval!(op1), $middle) && 11 $middle.send(eval!(op2), $right) 12 } a < b < c 全体の AST を受け取るマ クロを定義 ここで AST を一旦配列に変換する パターンマッチで a < b < c にマッ チする AST の配列の値を束縛する ここで a.send(:<, b) && b.send(: <, c) になるような AST に変換する 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]]]] 13 else 14 node 15 end 16 end 17 macro_node :OPCALL, :chaining_comparison_operators 18 end ` ` ` ` ` ` 79 / 89

Slide 209

Slide 209 text

a < b < c を a < b && b < c に置き換える 1 a < b < c → 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)); 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

Slide 210

Slide 210 text

他にも 80 / 89

Slide 211

Slide 211 text

81 / 89

Slide 212

Slide 212 text

1 const! value = [1, 2, 3] 81 / 89

Slide 213

Slide 213 text

1 const! value = [1, 2, 3] → 81 / 89

Slide 214

Slide 214 text

1 const! value = [1, 2, 3] → 1 # 既に定義済みなら例外にする 2 raise if defined? value 3 value = [1, 2, 3].freeze 81 / 89

Slide 215

Slide 215 text

1 const! value = [1, 2, 3] → 1 # 既に定義済みなら例外にする 2 raise if defined? value 3 value = [1, 2, 3].freeze 1 H[name, age] = user 2 disp H[name, age] 81 / 89

Slide 216

Slide 216 text

1 const! value = [1, 2, 3] → 1 # 既に定義済みなら例外にする 2 raise if defined? value 3 value = [1, 2, 3].freeze 1 H[name, age] = user 2 disp H[name, age] → 81 / 89

Slide 217

Slide 217 text

1 const! value = [1, 2, 3] → 1 # 既に定義済みなら例外にする 2 raise if defined? value 3 value = [1, 2, 3].freeze 1 H[name, age] = user 2 disp H[name, age] → 1 # Hash 値を展開したりショートハンドで定義したり 2 name = user[:name]; age = user[:age] 3 disp({ name: name, age: age }) 81 / 89

Slide 218

Slide 218 text

1 const! value = [1, 2, 3] → 1 # 既に定義済みなら例外にする 2 raise if defined? value 3 value = [1, 2, 3].freeze 1 H[name, age] = user 2 disp H[name, age] → 1 # Hash 値を展開したりショートハンドで定義したり 2 name = user[:name]; age = user[:age] 3 disp({ name: name, age: age }) 1 !i!(hoge foo bar) 81 / 89

Slide 219

Slide 219 text

1 const! value = [1, 2, 3] → 1 # 既に定義済みなら例外にする 2 raise if defined? value 3 value = [1, 2, 3].freeze 1 H[name, age] = user 2 disp H[name, age] → 1 # Hash 値を展開したりショートハンドで定義したり 2 name = user[:name]; age = user[:age] 3 disp({ name: name, age: age }) 1 !i!(hoge foo bar) → 81 / 89

Slide 220

Slide 220 text

1 const! value = [1, 2, 3] → 1 # 既に定義済みなら例外にする 2 raise if defined? value 3 value = [1, 2, 3].freeze 1 H[name, age] = user 2 disp H[name, age] → 1 # Hash 値を展開したりショートハンドで定義したり 2 name = user[:name]; age = user[:age] 3 disp({ name: name, age: age }) 1 !i!(hoge foo bar) → 1 # %i みたいに展開したり 2 [:hoge,:foo,:bar] 81 / 89

Slide 221

Slide 221 text

1 const! value = [1, 2, 3] → 1 # 既に定義済みなら例外にする 2 raise if defined? value 3 value = [1, 2, 3].freeze 1 H[name, age] = user 2 disp H[name, age] → 1 # Hash 値を展開したりショートハンドで定義したり 2 name = user[:name]; age = user[:age] 3 disp({ name: name, age: age }) 1 !i!(hoge foo bar) → 1 # %i みたいに展開したり 2 [:hoge,:foo,:bar] 1 def func(name: String, age: (0...20)) 2 # ... 3 end 81 / 89

Slide 222

Slide 222 text

1 const! value = [1, 2, 3] → 1 # 既に定義済みなら例外にする 2 raise if defined? value 3 value = [1, 2, 3].freeze 1 H[name, age] = user 2 disp H[name, age] → 1 # Hash 値を展開したりショートハンドで定義したり 2 name = user[:name]; age = user[:age] 3 disp({ name: name, age: age }) 1 !i!(hoge foo bar) → 1 # %i みたいに展開したり 2 [:hoge,:foo,:bar] 1 def func(name: String, age: (0...20)) 2 # ... 3 end → 81 / 89

Slide 223

Slide 223 text

1 const! value = [1, 2, 3] → 1 # 既に定義済みなら例外にする 2 raise if defined? value 3 value = [1, 2, 3].freeze 1 H[name, age] = user 2 disp H[name, age] → 1 # Hash 値を展開したりショートハンドで定義したり 2 name = user[:name]; age = user[:age] 3 disp({ name: name, age: age }) 1 !i!(hoge foo bar) → 1 # %i みたいに展開したり 2 [:hoge,:foo,:bar] 1 def func(name: String, age: (0...20)) 2 # ... 3 end → 1 # 型チェックのように定義したり 2 def func(name, age) 3 raise TypeError unless String === name 4 raise TypeError unless (0..20) === age 5 # ... 6 end 81 / 89

Slide 224

Slide 224 text

82 / 89

Slide 225

Slide 225 text

1 module MyMacro 2 macro_rule { 3 pat { $name = $value } --> { 4 $name = $value.freeze 5 } 6 pat { cat! } --> { "nyaaaaan" } 7 pat { cat!($num) } --> { "nyaaaaan" * $num } 8 } 9 end 82 / 89

Slide 226

Slide 226 text

1 module MyMacro 2 macro_rule { 3 pat { $name = $value } --> { 4 $name = $value.freeze 5 } 6 pat { cat! } --> { "nyaaaaan" } 7 pat { cat!($num) } --> { "nyaaaaan" * $num } 8 } 9 end → 82 / 89

Slide 227

Slide 227 text

1 module MyMacro 2 macro_rule { 3 pat { $name = $value } --> { 4 $name = $value.freeze 5 } 6 pat { cat! } --> { "nyaaaaan" } 7 pat { cat!($num) } --> { "nyaaaaan" * $num } 8 } 9 end → 1 # マクロを簡略化的に定義したり 2 module MyMacro 3 def macro1(node, name:, value:) 4 ast { $name = $value.freeze } 5 end 6 macro_pattern pat { $name = $value }, :macro1 7 8 def macro2(node) 9 ast { "nyaaaaan" } 10 end 11 macro_pattern pat { cat! }, :macro2 12 13 def macro3(node, num:) 14 ast { "nyaaaaan" * $num } 15 end 16 macro_pattern pat { cat!($num) }, :macro3 17 end 82 / 89

Slide 228

Slide 228 text

1 module MyMacro 2 macro_rule { 3 pat { $name = $value } --> { 4 $name = $value.freeze 5 } 6 pat { cat! } --> { "nyaaaaan" } 7 pat { cat!($num) } --> { "nyaaaaan" * $num } 8 } 9 end → 1 # マクロを簡略化的に定義したり 2 module MyMacro 3 def macro1(node, name:, value:) 4 ast { $name = $value.freeze } 5 end 6 macro_pattern pat { $name = $value }, :macro1 7 8 def macro2(node) 9 ast { "nyaaaaan" } 10 end 11 macro_pattern pat { cat! }, :macro2 12 13 def macro3(node, num:) 14 ast { "nyaaaaan" * $num } 15 end 16 macro_pattern pat { cat!($num) }, :macro3 17 end 1 @initialize[:name, :age] 2 class User 3 4 end 82 / 89

Slide 229

Slide 229 text

1 module MyMacro 2 macro_rule { 3 pat { $name = $value } --> { 4 $name = $value.freeze 5 } 6 pat { cat! } --> { "nyaaaaan" } 7 pat { cat!($num) } --> { "nyaaaaan" * $num } 8 } 9 end → 1 # マクロを簡略化的に定義したり 2 module MyMacro 3 def macro1(node, name:, value:) 4 ast { $name = $value.freeze } 5 end 6 macro_pattern pat { $name = $value }, :macro1 7 8 def macro2(node) 9 ast { "nyaaaaan" } 10 end 11 macro_pattern pat { cat! }, :macro2 12 13 def macro3(node, num:) 14 ast { "nyaaaaan" * $num } 15 end 16 macro_pattern pat { cat!($num) }, :macro3 17 end 1 @initialize[:name, :age] 2 class User 3 4 end → 82 / 89

Slide 230

Slide 230 text

1 module MyMacro 2 macro_rule { 3 pat { $name = $value } --> { 4 $name = $value.freeze 5 } 6 pat { cat! } --> { "nyaaaaan" } 7 pat { cat!($num) } --> { "nyaaaaan" * $num } 8 } 9 end → 1 # マクロを簡略化的に定義したり 2 module MyMacro 3 def macro1(node, name:, value:) 4 ast { $name = $value.freeze } 5 end 6 macro_pattern pat { $name = $value }, :macro1 7 8 def macro2(node) 9 ast { "nyaaaaan" } 10 end 11 macro_pattern pat { cat! }, :macro2 12 13 def macro3(node, num:) 14 ast { "nyaaaaan" * $num } 15 end 16 macro_pattern pat { cat!($num) }, :macro3 17 end 1 @initialize[:name, :age] 2 class User 3 4 end → 1 # アクセッサを暗黙的に定義するアノテーション 2 class User 3 attr_reader :name, :age 4 5 def initialize(name:, age:) 6 @name = name 7 @age = age 8 end 9 end 82 / 89

Slide 231

Slide 231 text

夢が広がる 83 / 89

Slide 232

Slide 232 text

これからの課題 84 / 89

Slide 233

Slide 233 text

これからの課題 85 / 89

Slide 234

Slide 234 text

これからの課題 Rensei の復元率がまだ100%でない まだエッジケースの問題が多い ActiveRecord の全ファイルをパースした結果 15/240 ファイル失敗している ` ` 85 / 89

Slide 235

Slide 235 text

これからの課題 Rensei の復元率がまだ100%でない まだエッジケースの問題が多い ActiveRecord の全ファイルをパースした結果 15/240 ファイル失敗している ast {} や pat {} はグローバル変数を node の束縛に使っているので制限がキツイ ast { def $name(); end } みたいにはかけない これをもっと簡略的にかけるようにしたい ` ` ` ` ` ` ` ` ` ` 85 / 89

Slide 236

Slide 236 text

これからの課題 Rensei の復元率がまだ100%でない まだエッジケースの問題が多い ActiveRecord の全ファイルをパースした結果 15/240 ファイル失敗している ast {} や pat {} はグローバル変数を node の束縛に使っているので制限がキツイ ast { def $name(); end } みたいにはかけない これをもっと簡略的にかけるようにしたい RubyVM::AbstractSyntaxTree 自体の制限がきつい RubyVM::AbstractSyntaxTree.of が eval の中で使用できないので eval("ast { ... }") みた いなことができない これがかなりボトルネックになっている ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` 85 / 89

Slide 237

Slide 237 text

まとめ 86 / 89

Slide 238

Slide 238 text

まとめ 87 / 89

Slide 239

Slide 239 text

まとめ Ruby でマクロを実装してみたらこんな感じになった これらはすべてピュア Ruby で実装されている 87 / 89

Slide 240

Slide 240 text

まとめ Ruby でマクロを実装してみたらこんな感じになった これらはすべてピュア Ruby で実装されている まだ完璧ではないが比較的簡単にマクロを実現する事ができた 87 / 89

Slide 241

Slide 241 text

まとめ Ruby でマクロを実装してみたらこんな感じになった これらはすべてピュア Ruby で実装されている まだ完璧ではないが比較的簡単にマクロを実現する事ができた 通常のメタプログラミングとは違い Ruby を構文レベルで変更する事ができるので今ま でのメタプログラミング以上の事が実現できる 87 / 89

Slide 242

Slide 242 text

まとめ Ruby でマクロを実装してみたらこんな感じになった これらはすべてピュア Ruby で実装されている まだ完璧ではないが比較的簡単にマクロを実現する事ができた 通常のメタプログラミングとは違い Ruby を構文レベルで変更する事ができるので今ま でのメタプログラミング以上の事が実現できる Ruby のマクロの可能性はまだまだ本気を出していないのでこれからも継続して開発して 行きたい もっと抽象的にマクロを定義できるようにしたい 87 / 89

Slide 243

Slide 243 text

将来 Ruby 本体にマクロが実装されるのを楽しみにし ていますね! 88 / 89

Slide 244

Slide 244 text

Thank you all! 89 / 89