Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Use Macro all the time ~ マクロを使いまくろ ~ (日本語)

Avatar for osyo osyo
September 11, 2021

Use Macro all the time ~ マクロを使いまくろ ~ (日本語)

Ruby では `RubyVM::AbstractSyntaxTree` で AST を取得する事ができます。
この AST 情報を元にして Ruby のコードに変換する機能を実装しました。
これを利用することで AST レベルで Ruby のコードを変更して実行する事ができるようになります。

- Ruby のコード -> AST に変換 -> 別の AST に変換 -> AST から Ruby のコードに変換 -> Ruby のコードを実行

このセッションでは『AST から Ruby のコードに変換する実装』、また『AST から別の AST に変換する実装』について解説を行います。
この『別の AST に変換する』という機能は他の言語では『マクロ』と呼ばれている機能に近いです。
Ruby で『マクロ』を実装するとどうなるのか一緒に考えてみましょう。

Avatar for osyo

osyo

September 11, 2021
Tweet

More Decks by osyo

Other Decks in Programming

Transcript

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

    [1, 2, 3].freeze 暗黙的に freeze させたり ` `
  2. 1 puts config.hoge_flag 2 puts config.foo_flag みたいなデバッグ出力を 1 # output:

    2 "config.hoge_flag # => true" 3 "config.foo_flag # => false" みたいに出力内容と出力結果を一緒に出力させたり
  3. 1 ![a, b, c] こういうコードを 1 { a: a, b:

    b, c: c } みたいに Hash で展開させたりとか
  4. 自己紹介 名前: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
  5. アジェンダ Ruby のマクロとは AST とは マクロの変換プロセスの解説 Rensei - 錬成 -

    AST から Ruby のコードを生成するライブラリ Kenma - 研磨 - 任意の AST を別の AST に変換するライブラリ マクロの使用例 これからの課題 12 / 89
  6. マクロとは 世の中にはいろいろなマクロがある C言語マクロ、LISP マクロ、Rust マクロ、エクセルのマクロ etc… マクロと言ってもそれぞれ意味が異なる この登壇では『Ruby の AST

    を別の AST に変換すること』を『Ruby のマクロ』と定義 この『マクロ』を使用すると Ruby のコードを構文レベルで変更する事ができる 例えば hoge.foo を hoge&.foo に変更したり 理論上は valid な Ruby のコードであればどんなコードにでも変換できる ` ` ` ` 14 / 89
  7. AST とは AST とは抽象構文木(Abstract Syntax Tree)の略 今回は RubyVM::AbstractSyntaxTree を使用する RubyVM::AbstractSyntaxTree

    で使用される実データは RubyVM::AbstractSyntaxTree::Node だが、 このスライドでは一部配列形式で記述している 以下 RubyVM::AST::Node と略 ` ` ` ` ` ` ` ` 16 / 89
  8. 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
  9. 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
  10. 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
  11. 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
  12. 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
  13. 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
  14. 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
  15. 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
  16. 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
  17. 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
  18. 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
  19. 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
  20. 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
  21. 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
  22. 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
  23. 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
  24. 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
  25. 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
  26. 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
  27. 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
  28. 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
  29. 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
  30. 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
  31. 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
  32. 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
  33. 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
  34. 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
  35. Kenma - 研磨 - 任意の AST を別の AST に変換するライブラリ https://github.com/osyo-manga/gem-kenma

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

    これを利用すると簡単にマクロを実現する事ができる 名前の由来は Ruby のコードを更に磨き上げるという意味で付けた 36 / 89
  37. マクロの定義方法 マクロの定義方法には複数の種類がある 関数マクロ メソッド呼び出しに対して AST を置き換える ノードマクロ 特定の AST の種類に対して

    AST を置き換える パターンマクロ 特定の Ruby の構文に対して AST を置き換える いずれかの定義方法でも『 AST を受け取って AST を返すメソッド』を定義すること になる ` ` ` ` 39 / 89
  38. 関数マクロ レシーバのないメソッド呼び出しを別の 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
  39. 関数マクロを定義する 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
  40. 関数マクロを定義する 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
  41. 関数マクロを定義する 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
  42. 関数マクロを定義する 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
  43. 関数マクロを定義する 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
  44. 関数マクロを定義する 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
  45. 関数マクロを定義する 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
  46. 関数マクロを定義する 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
  47. 関数マクロを定義する 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
  48. 関数マクロを定義する 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
  49. 関数マクロを定義する 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
  50. 関数マクロを定義する 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
  51. 関数マクロを定義する 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
  52. 関数マクロを定義する 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
  53. 定義した関数マクロを使う 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
  54. 定義した関数マクロを使う 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
  55. 定義した関数マクロを使う 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
  56. 定義した関数マクロを使う 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
  57. 定義した関数マクロを使う 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
  58. 定義した関数マクロを使う 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
  59. 定義した関数マクロを使う 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
  60. 定義した関数マクロを使う 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
  61. 定義した関数マクロを使う 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
  62. 定義した関数マクロを使う 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
  63. 定義した関数マクロを使う 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
  64. 定義した関数マクロを使う 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
  65. 定義した関数マクロを使う 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
  66. 変換前と変換後の 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
  67. 変換前と変換後の 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
  68. 変換前と変換後の 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
  69. 変換前と変換後の 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
  70. 定義した関数マクロを使う 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
  71. 定義した関数マクロを使う 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
  72. 定義した関数マクロを使う 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
  73. 定義した関数マクロを使う 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
  74. 定義した関数マクロを使う 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
  75. 定義した関数マクロを使う 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
  76. 定義した関数マクロを使う 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
  77. 定義した関数マクロを使う 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
  78. 関数マクロの引数の話 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
  79. 関数マクロの引数の話 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
  80. 関数マクロの引数の話 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
  81. 関数マクロの引数の話 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
  82. 関数マクロの引数の話 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
  83. 関数マクロの引数の話 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
  84. 関数マクロの引数の話 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
  85. 関数マクロの引数の話 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
  86. 関数マクロの引数の話 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
  87. 関数マクロの引数の話 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
  88. 関数マクロの引数の話 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
  89. 関数マクロの引数の話 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
  90. 関数マクロの引数の話 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
  91. 関数マクロの引数の話 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
  92. 関数マクロの引数の話 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
  93. 関数マクロの引数の話 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
  94. ノードマクロ 特定の 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
  95. ノードマクロ 特定の 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
  96. ノードマクロを定義する 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
  97. ノードマクロを定義する 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
  98. ノードマクロを定義する 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
  99. ノードマクロを定義する 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
  100. ノードマクロを定義する 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
  101. ノードマクロを定義する 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
  102. ノードマクロを定義する 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
  103. ノードマクロを定義する 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
  104. ノードマクロを定義する 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
  105. ノードマクロを定義する 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
  106. ノードマクロを定義する 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
  107. パターンマクロ 特定の Ruby の構文に対して AST を置き換えるマクロ value = [1, 2,

    3] を value = [1, 2, 3].freeze に置き換えるマクロを書く ` ` ` ` 70 / 89
  108. パターンマクロ 特定の 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
  109. パターンマクロ 特定の 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
  110. パターンマクロを定義する 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
  111. パターンマクロを定義する 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
  112. パターンマクロを定義する 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
  113. パターンマクロを定義する 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
  114. パターンマクロを定義する 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
  115. パターンマクロを定義する 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
  116. パターンマクロを定義する 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
  117. パターンマクロを定義する 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
  118. パターンマクロを定義する 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
  119. パターンマクロを定義する 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
  120. パターンマクロを定義する 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
  121. デバッグ出力マクロ 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
  122. デバッグ出力マクロ 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
  123. デバッグ出力マクロ 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
  124. デバッグ出力マクロ 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
  125. デバッグ出力マクロ 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
  126. 複数のモジュールを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
  127. 複数のモジュールを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
  128. 複数のモジュールを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
  129. 複数のモジュールを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
  130. { a: a, b: b, c: c } を簡略的に定義する 1

    ![a, b, c] → 1 { a: a, b: b, c: c } 78 / 89
  131. { 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
  132. { 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
  133. { 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
  134. { 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
  135. { 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
  136. a < b < c を a < b &&

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

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

    b < c に置き換える 1 a < b < c → 1 a < b && b < c 79 / 89
  139. 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
  140. 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
  141. 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
  142. 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
  143. 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
  144. 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
  145. 1 const! value = [1, 2, 3] → 1 #

    既に定義済みなら例外にする 2 raise if defined? value 3 value = [1, 2, 3].freeze 81 / 89
  146. 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
  147. 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
  148. 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
  149. 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
  150. 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
  151. 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
  152. 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
  153. 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
  154. 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
  155. 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
  156. 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
  157. 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
  158. 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
  159. 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
  160. 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
  161. これからの課題 Rensei の復元率がまだ100%でない まだエッジケースの問題が多い ActiveRecord の全ファイルをパースした結果 15/240 ファイル失敗している ast {}

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

    や pat {} はグローバル変数を node の束縛に使っているので制限がキツイ ast { def $name(); end } みたいにはかけない これをもっと簡略的にかけるようにしたい RubyVM::AbstractSyntaxTree 自体の制限がきつい RubyVM::AbstractSyntaxTree.of が eval の中で使用できないので eval("ast { ... }") みた いなことができない これがかなりボトルネックになっている ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` 85 / 89
  163. まとめ Ruby でマクロを実装してみたらこんな感じになった これらはすべてピュア Ruby で実装されている まだ完璧ではないが比較的簡単にマクロを実現する事ができた 通常のメタプログラミングとは違い Ruby を構文レベルで変更する事ができるので今ま

    でのメタプログラミング以上の事が実現できる Ruby のマクロの可能性はまだまだ本気を出していないのでこれからも継続して開発して 行きたい もっと抽象的にマクロを定義できるようにしたい 87 / 89