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

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

3931098ae486b6df619050c63e24f6cc?s=47 osyo
September 11, 2021

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

RubyKaigi Takeout 2021
https://rubykaigi.org/2021-takeout/presentations/pink_bangbi.html

Ruby can get AST with RubyVM::AbstractSyntaxTree. I have implemented to convert AST into Ruby code. This will allow you to modify and execute Ruby code at AST level.

* Ruby code -> Convert to AST -> Convert to another AST -> Convert AST to Ruby code -> Run Ruby code
In this session, I will discuss "Implementations for converting AST to Ruby code" and "Implementations for converting AST to another AST". This feature of "converting to another AST" is similar to what is called a "Macro" in other languages. Let's think together about what happens when we implement "Macro" in Ruby.

3931098ae486b6df619050c63e24f6cc?s=128

osyo

September 11, 2021
Tweet

Transcript

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

    2021
  2. hi all Good morning Good day Good evening

  3. Is everyone using Ruby???

  4. If you are using Ruby…

  5. 1 CONST_VALUE = [1, 2, 3] This constant definitions for

  6. 1 CONST_VALUE = [1, 2, 3] This constant definitions for

    1 CONST_VALUE = [1, 2, 3].freeze Implicitly freeze or ` `
  7. 1 puts config.hoge_flag 2 puts config.foo_flag Debug output such as

  8. 1 puts config.hoge_flag 2 puts config.foo_flag Debug output such as

    1 # output: 2 "config.hoge_flag # => true" 3 "config.foo_flag # => false" The output contents and the output result can be output together like
  9. 1 ![a, b, c] Code like this.

  10. 1 ![a, b, c] Code like this. 1 { a:

    a, b: b, c: c } You can use Hash to expand it, for example
  11. You’ll want to do it, right?

  12. You can do that with a Macro!!!

  13. Profile name: osyo Twitter : @pink_bangbi https://twitter.com/pink_bangbi github : osyo-manga

    https://github.com/osyo-manga Blog : Secret Garden(Instrumental) http://secret-garden.hatenablog.com Rails engineer My favorite Ruby feature is Refinements First time to attend RubyKaigi 10 / 89
  14. Today’s talk Theme 11 / 89

  15. Today’s talk Theme Implemented a Macro in Ruby 11 /

    89
  16. Agenda What are Ruby Macro? What is AST? Macro Conversion

    Process Rensei - 錬成 - A library to generate Ruby code from AST Kenma - 研磨 - A library that converts an AST to another AST Example of Macro usage Future challenges 12 / 89
  17. What is a Macro? 13 / 89

  18. What is a Macro? 14 / 89

  19. What is a Macro? There are many different Macro in

    the world C language macros, LISP macros, Rust macros, Excel macros etc… Each Macro has a different meaning 14 / 89
  20. What is a Macro? There are many different Macro in

    the world C language macros, LISP macros, Rust macros, Excel macros etc… Each Macro has a different meaning In this talk, "Ruby Macro" are defined as "converting a Ruby AST to another AST" 14 / 89
  21. What is a Macro? There are many different Macro in

    the world C language macros, LISP macros, Rust macros, Excel macros etc… Each Macro has a different meaning In this talk, "Ruby Macro" are defined as "converting a Ruby AST to another AST" This "Macro" can be used to modify Ruby code at the syntax level For example, changing hoge.foo to hoge&.foo or Theoretically, any valid Ruby code can be converted to any code ` ` ` ` 14 / 89
  22. What is AST? 15 / 89

  23. What is AST? 16 / 89

  24. What is AST? AST stands for Abstract Syntax Tree 16

    / 89
  25. What is AST? AST stands for Abstract Syntax Tree This

    time, use RubyVM::AbstractSyntaxTree The real data used in RubyVM::AbstractSyntaxTree is RubyVM::AbstractSyntaxTree::Node , but in this slide, some of it is written in array format Abbreviated as RubyVM::AST::Node ` ` ` ` ` ` ` ` 16 / 89
  26. What is AST? AST stands for Abstract Syntax Tree This

    time, use RubyVM::AbstractSyntaxTree The real data used in RubyVM::AbstractSyntaxTree is RubyVM::AbstractSyntaxTree::Node , but in this slide, some of it is written in array format Abbreviated as RubyVM::AST::Node Different codes can have the same AST because of the abstracted data structure For example, cond ? foo : bar and if cond; foo; else bar; end will have the same AST ` ` ` ` ` ` ` ` ` ` ` ` 16 / 89
  27. What is AST? AST stands for Abstract Syntax Tree This

    time, use RubyVM::AbstractSyntaxTree The real data used in RubyVM::AbstractSyntaxTree is RubyVM::AbstractSyntaxTree::Node , but in this slide, some of it is written in array format Abbreviated as RubyVM::AST::Node Different codes can have the same AST because of the abstracted data structure For example, cond ? foo : bar and if cond; foo; else bar; end will have the same AST There are more than 100 types of AST, subdivided by syntax. ` ` ` ` ` ` ` ` ` ` ` ` 16 / 89
  28. Sample RubyVM::AbstractSyntaxTree [Code] 1 node = RubyVM::AbstractSyntaxTree.parse("1 + 2") 2

    # Pass a Proc object and return AST of that Proc 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
  29. Sample RubyVM::AbstractSyntaxTree [Code] 1 node = RubyVM::AbstractSyntaxTree.parse("1 + 2") Get

    AST of 1 + 2 in RubyVM::AbstractSyntaxTree.parse 2 # Pass a Proc object and return AST of that Proc 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
  30. Sample RubyVM::AbstractSyntaxTree [Code] 2 # Pass a Proc object and

    return AST of that Proc 3 # RubyVM::AbstractSyntaxTree.of(-> { 1 + 2 }) Get AST of 1 + 2 in RubyVM::AbstractSyntaxTree.parse Also get it from Proc with .of 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
  31. Sample RubyVM::AbstractSyntaxTree [Code] 5 pp node Get AST of 1

    + 2 in RubyVM::AbstractSyntaxTree.parse Also get it from Proc with .of The acquired AST data looks like this This is the data format of RubyVM::AbstractSyntaxTree::Node [Output] 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 # Pass a Proc object and return AST of that Proc 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
  32. Sample RubyVM::AbstractSyntaxTree [Code] 6 pp node.type 7 pp node.children AST

    have two pieces of information, type and children , which form a tree structure [Output] 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 # Pass a Proc object and return AST of that Proc 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
  33. Sample RubyVM::AbstractSyntaxTree [Code] 6 pp node.type 7 pp node.children AST

    have two pieces of information, type and children , which form a tree structure There is an AST called SCOPE in the main frame, with an AST of 1 + 2 hanging below it [Output] 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 # Pass a Proc object and return AST of that Proc 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
  34. Sample RubyVM::AbstractSyntaxTree [Code] 9 node2 = node.children.last 10 pp node2

    To get AST of 1 + 2, get it from children in SCOPE [Output] 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 # Pass a Proc object and return AST of that Proc 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
  35. Sample RubyVM::AbstractSyntaxTree [Code] 11 pp node2.type 12 pp node2.children The

    1 + 2 AST also has type and children [Output] 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 # Pass a Proc object and return AST of that Proc 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
  36. Sample RubyVM::AbstractSyntaxTree [Code] 11 pp node2.type 12 pp node2.children The

    1 + 2 AST also has type and children Thus, an AST is made up of multiple AST [Output] 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 # Pass a Proc object and return AST of that Proc 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
  37. Correspondence table for AST (partial) type Code AST Meaning LIT

    1 [:LIT, [1]] Numbers, symbol literals, etc. STR "string" [:STR, ["string"]] String literals VCALL func [:VCALL, [:func]] Method call CALL func.bar [:CALL, [[:VCALL, [:func]], :bar, nil]] . call QCALL func&.bar [:QCALL, [[:VCALL, [:func]], :bar, nil]] &. call OPCALL 1 + a [:OPCALL, [[:LIT, [1]], :+, [:LIST, [[:VCALL, [:a]], nil]]]] Operator call AND a && b [:AND, [[:LIT, [1]], [:VCALL, [:b]]]] && operator NOTE: The real data is in RubyVM::AST::Node , but it’s written as an array for clarity. ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` 21 / 89
  38. Explanation of the Macro conversion process 22 / 89

  39. Explanation of the Macro conversion process As an example, let’s

    convert hoge.foo to hoge&.foo ` ` ` ` 22 / 89
  40. AST conversion process Ruby code before conversion 1 hoge.foo 23

    / 89
  41. AST conversion process Ruby code before conversion 1 hoge.foo Convert

    this code to AST 23 / 89
  42. AST conversion process Ruby code before conversion 1 hoge.foo AST

    (RubyVM::AST::Node) 1 (CALL@9:9-9:17 2 (VCALL@9:9-9:13 :hoge) :foo nil) 24 / 89
  43. AST conversion process Ruby code before conversion 1 hoge.foo AST

    (RubyVM::AST::Node) 1 (CALL@9:9-9:17 2 (VCALL@9:9-9:13 :hoge) :foo nil) This data structure is difficult to handle, so we will convert it to an array by ourselves 24 / 89
  44. AST conversion process Ruby code before conversion 1 hoge.foo AST

    (RubyVM::AST::Node) 1 (CALL@9:9-9:17 2 (VCALL@9:9-9:13 :hoge) :foo nil) AST (Array) 1 [:CALL, 2 [[:VCALL, [:hoge]], :foo, nil]] 25 / 89
  45. AST conversion process Ruby code before conversion 1 hoge.foo AST

    (RubyVM::AST::Node) 1 (CALL@9:9-9:17 2 (VCALL@9:9-9:13 :hoge) :foo nil) AST (Array) 1 [:CALL, The instruction CALL in the AST is replaced by This is the . operator instruction 2 [[:VCALL, [:hoge]], :foo, nil]] ` ` ` ` 25 / 89
  46. AST conversion process Ruby code before conversion 1 hoge.foo AST

    (RubyVM::AST::Node) 1 (CALL@9:9-9:17 2 (VCALL@9:9-9:13 :hoge) :foo nil) AST (Array) 1 [:QCALL, Replace with the instruction QCALL If QCALL is a &. operator instruction 2 [[:VCALL, [:hoge]], :foo, nil]] ` ` ` ` ` ` 26 / 89
  47. AST conversion process Ruby code before conversion 1 hoge.foo AST

    (RubyVM::AST::Node) 1 (CALL@9:9-9:17 2 (VCALL@9:9-9:13 :hoge) :foo nil) AST (Array) 1 [:QCALL, 2 [[:VCALL, [:hoge]], :foo, nil]] Convert this AST to Ruby code 27 / 89
  48. AST conversion process Ruby code before conversion 1 hoge.foo AST

    (RubyVM::AST::Node) 1 (CALL@9:9-9:17 2 (VCALL@9:9-9:13 :hoge) :foo nil) Ruby code after conversion 1 hoge&.foo AST (Array) 1 [:QCALL, 2 [[:VCALL, [:hoge]], :foo, nil]] 28 / 89
  49. AST conversion process Ruby code before conversion 1 hoge.foo AST

    (RubyVM::AST::Node) 1 (CALL@9:9-9:17 2 (VCALL@9:9-9:13 :hoge) :foo nil) Ruby code after conversion 1 hoge&.foo AST (Array) 1 [:QCALL, 2 [[:VCALL, [:hoge]], :foo, nil]] In this way, you can rewrite the AST to change it to another Ruby code 28 / 89
  50. This is Macro!! 29 / 89

  51. Macro is defined as rewriting the contents of an AST

    in this way 30 / 89
  52. Macro is defined as rewriting the contents of an AST

    in this way So the Macro needs to have the ability to convert from AST to Ruby code 30 / 89
  53. ~ Convert AST to Ruby code ~ Rensei - 錬成

    - 31 / 89
  54. Rensei - 錬成 - 32 / 89

  55. Rensei - 錬成 - A library for restoring AST to

    Ruby code https://github.com/osyo-manga/gem-rensei 32 / 89
  56. Rensei - 錬成 - A library for restoring AST to

    Ruby code https://github.com/osyo-manga/gem-rensei Use this library to convert from RubyVM::AST::Node to Ruby code Can be converted from an array instead of from RubyVM::AST::Node See here for details (in Japanese) 【Ruby Advent Calendar 2020】Ruby の AST から Ruby のソースコードを復元しよう【1日目】 - Secret Garden(Instrumental) https://secret-garden.hatenablog.com/entry/2020/12/01/093316 ` ` ` ` 32 / 89
  57. Rensei - 錬成 - A library for restoring AST to

    Ruby code https://github.com/osyo-manga/gem-rensei Use this library to convert from RubyVM::AST::Node to Ruby code Can be converted from an array instead of from RubyVM::AST::Node See here for details (in Japanese) 【Ruby Advent Calendar 2020】Ruby の AST から Ruby のソースコードを復元しよう【1日目】 - Secret Garden(Instrumental) https://secret-garden.hatenablog.com/entry/2020/12/01/093316 Note that the AST is already an abstraction of the data, so it does not fully restore the original code Information in comments, etc. cannot be recovered () and others may be added ` ` ` ` ` ` 32 / 89
  58. Rensei - 錬成 - A library for restoring AST to

    Ruby code https://github.com/osyo-manga/gem-rensei Use this library to convert from RubyVM::AST::Node to Ruby code Can be converted from an array instead of from RubyVM::AST::Node See here for details (in Japanese) 【Ruby Advent Calendar 2020】Ruby の AST から Ruby のソースコードを復元しよう【1日目】 - Secret Garden(Instrumental) https://secret-garden.hatenablog.com/entry/2020/12/01/093316 Note that the AST is already an abstraction of the data, so it does not fully restore the original code Information in comments, etc. cannot be recovered () and others may be added The name comes from the idea of generating new Ruby code ` ` ` ` ` ` 32 / 89
  59. How to 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
  60. How to Rensei [コード] 3 node = RubyVM::AbstractSyntaxTree.parse("1 + 2

    && hoge || bar") Change the Ruby code to 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
  61. How to Rensei [コード] 4 src = Rensei.unparse(node) Change the

    Ruby code to AST Restore Ruby code from RubyVM::AST::Node to Ruby code, and 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
  62. How to Rensei [コード] 5 puts src 6 # =>

    (((1 + 2) && hoge) || bar) Change the Ruby code to AST Restore Ruby code from RubyVM::AST::Node to Ruby code, and Generates Ruby code that looks like this () and other information that is not in the original code is added 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
  63. How to Rensei [コード] 8 puts Rensei.unparse([:OPCALL, [[:LIT, [1]], :+,

    [:LIST, [[:LIT, [2]], nil]]]]) 9 # => (1 + 2) Change the Ruby code to AST Restore Ruby code from RubyVM::AST::Node to Ruby code, and Generates Ruby code that looks like this () and other information that is not in the original code is added It can also be recovered from array AST data 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
  64. Implemented a Macro using Rensei! 34 / 89

  65. ~ Make it easy to handle macros in Ruby ~

    Kenma - 研磨 - 35 / 89
  66. Kenma - 研磨 - 36 / 89

  67. Kenma - 研磨 - A library to convert any AST

    to another AST https://github.com/osyo-manga/gem-kenma 36 / 89
  68. Kenma - 研磨 - A library to convert any AST

    to another AST https://github.com/osyo-manga/gem-kenma This can be used to implement macros easily 36 / 89
  69. Kenma - 研磨 - A library to convert any AST

    to another AST https://github.com/osyo-manga/gem-kenma This can be used to implement macros easily The name comes from the idea of further improving Ruby code 36 / 89
  70. How to use Kenma 37 / 89

  71. Define a Macro 38 / 89

  72. How to define a Macro 39 / 89

  73. How to define a Macro There are multiple ways to

    define a Macro 39 / 89
  74. How to define a Macro There are multiple ways to

    define a Macro Function Macro Replace AST for method calls 39 / 89
  75. How to define a Macro There are multiple ways to

    define a Macro Function Macro Replace AST for method calls Node Macro Replace an AST for a specific AST type 39 / 89
  76. How to define a Macro There are multiple ways to

    define a Macro Function Macro Replace AST for method calls Node Macro Replace an AST for a specific AST type Pattern Macro Replace an AST for a specific Ruby syntax 39 / 89
  77. How to define a Macro There are multiple ways to

    define a Macro Function Macro Replace AST for method calls Node Macro Replace an AST for a specific AST type Pattern Macro Replace an AST for a specific Ruby syntax Either of these definitions will define a method that receives AST and returns AST ` ` ` ` 39 / 89
  78. Function Macro 40 / 89

  79. Function Macro 41 / 89

  80. Function Macro Macro to replace a method call without a

    receiver with another AST 41 / 89
  81. Function Macro Macro to replace a method call without a

    receiver with another AST Try to write a Macro that replaces cat! with 'nyaaaan' ` ` ` ` 41 / 89
  82. Function Macro Macro to replace a method call without a

    receiver with another AST Try to write a Macro that replaces cat! with 'nyaaaan' 1 puts cat! 2 # AST => [:FCALL, [:puts, [:LIST, [[:FCALL, [:cat!, nil]], nil]]]] For ` ` ` ` 41 / 89
  83. Function Macro Macro to replace a method call without a

    receiver with another AST Try to write a Macro that replaces cat! with 'nyaaaan' 1 puts cat! 2 # AST => [:FCALL, [:puts, [:LIST, [[:FCALL, [:cat!, nil]], nil]]]] For 1 puts 'nyaaaaan' 2 # AST => [:FCALL, [:puts, [:LIST, [[:STR, ["nyaaaaan"]], nil]]]] Convert to something like ` ` ` ` 41 / 89
  84. Define a Function Macro 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
  85. Define a Function Macro 5 module CatMacro 6 using Kenma::Macroable

    7 8 def cat! 9 RubyVM::AbstractSyntaxTree.parse("'nyaaaaan'") 10 end 11 macro_function :cat! 12 end Define a module for defining macros 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
  86. Define a Function Macro 5 module CatMacro 6 using Kenma::Macroable

    7 8 def cat! 9 RubyVM::AbstractSyntaxTree.parse("'nyaaaaan'") 10 end 11 macro_function :cat! 12 end Define a module for defining macros In this module, define the methods to be used by the macro 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
  87. Define a Function Macro 6 using Kenma::Macroable Define a module

    for defining macros In this module, define the methods to be used by the macro Also, using allows you to use the methods you need to define macros 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
  88. Define a Function Macro 11 macro_function :cat! Define a module

    for defining macros In this module, define the methods to be used by the macro Also, using allows you to use the methods you need to define macros macro_function etc Other methods required for Kernel and Module are defined 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
  89. Define a Function Macro 8 def cat! 9 RubyVM::AbstractSyntaxTree.parse("'nyaaaaan'") 10

    end Define a method to be called as a macro 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
  90. Define a Function Macro 9 RubyVM::AbstractSyntaxTree.parse("'nyaaaaan'") Define a method to

    be called as a macro AST returned here replaces the caller’s method Returning an AST of the string "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
  91. Define a Function Macro 9 RubyVM::AbstractSyntaxTree.parse("'nyaaaaan'") Define a method to

    be called as a macro AST returned here replaces the caller’s method Returning an AST of the string "nyaaaaan" 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
  92. Define a Function Macro 9 RubyVM::AbstractSyntaxTree.parse("'nyaaaaan'") Define a method to

    be called as a macro AST returned here replaces the caller’s method Returning an AST of the string "nyaaaaan" 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
  93. Define a Function Macro 9 RubyVM::AbstractSyntaxTree.parse("'nyaaaaan'") Define a method to

    be called as a macro AST returned here replaces the caller’s method Returning an AST of the string "nyaaaaan" 1 puts cat! ↓↓↓↓↓ 1 puts "nyaaaaan" Also use RubyVM::AbstractSyntaxTree.parse in 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
  94. Define a Function Macro 9 ast { 'nyaaaaan' } Can

    be replaced with 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
  95. Define a Function Macro 9 ast { 'nyaaaaan' } Can

    be replaced with ast {} ast {} returns the AST of the code in the block Can be used in the context of 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
  96. Define a Function Macro 11 macro_function :cat! Can be replaced

    with ast {} ast {} returns the AST of the code in the block Can be used in the context of 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)) Declare that it is a Function 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 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
  97. Define a Function Macro 5 module CatMacro 6 using Kenma::Macroable

    7 8 def cat! 9 ast { 'nyaaaaan' } 10 end 11 macro_function :cat! 12 end Can be replaced with ast {} ast {} returns the AST of the code in the block Can be used in the context of 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)) Declare that it is a Function Macro So far, this is the definition of a macro 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
  98. Let’s use macros! 46 / 89

  99. Use a defined Function 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 { 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
  100. Use a defined Function Macro 14 body = proc {

    15 use_macro! CatMacro 16 17 puts cat! 18 } Define the code to which the Macro is to be applied with Proc Apply the Macro to the code in the block 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
  101. Use a defined Function Macro 14 body = proc {

    15 use_macro! CatMacro 16 17 puts cat! 18 } Define the code to which the Macro is to be applied with Proc Apply the Macro to the code in the block NOTE: This implementation is not yet complete, so the Macro is applied only within a specific block, not per file 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
  102. Use a defined Function Macro 15 use_macro! CatMacro Use use_macro!

    in a block to enable the use of defined macros 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
  103. Use a defined Function Macro 15 use_macro! CatMacro Use use_macro!

    in a block to enable the use of defined macros This use_macro! is also implemented as a 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
  104. Use a defined Function Macro 15 use_macro! CatMacro Use use_macro!

    in a block to enable the use of defined macros This use_macro! is also implemented as a macro Also, use_macro! is only reflected in the calling context 1 class X 2 # HogeMacro is only reflected in the class 3 use_macro! HogeMacro 4 5 def foo 6 # FooMacro is only reflected in the method 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
  105. Use a defined Function Macro 15 use_macro! CatMacro Use use_macro!

    in a block to enable the use of defined macros This use_macro! is also implemented as a macro Also, use_macro! is only reflected in the calling context 2 # HogeMacro is only reflected in the class 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 is only reflected in the method 7 use_macro! FooMacro 8 # ... 9 end 10 end 49 / 89
  106. Use a defined Function Macro 15 use_macro! CatMacro Use use_macro!

    in a block to enable the use of defined macros This use_macro! is also implemented as a macro Also, use_macro! is only reflected in the calling context 6 # FooMacro is only reflected in the method 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 # HogeMacro is only reflected in the class 3 use_macro! HogeMacro 4 5 def foo 8 # ... 9 end 10 end 49 / 89
  107. Use a defined Function Macro 17 puts cat! When the

    Macro is applied, this 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
  108. Use a defined Function Macro 17 puts 'nyaaaaan' will be

    replaced by '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
  109. Use a defined Function Macro 19 compiled = Kenma.compile_of(body) Use

    Kenma.compile_of to apply the 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 { 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
  110. Use a defined Function Macro 19 compiled = Kenma.compile_of(body) Use

    Kenma.compile_of to apply the macro A Macro is executed against the contents 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
  111. Use a defined Function Macro 20 pp compiled Use Kenma.compile_of

    to apply the macro A Macro is executed against the contents of Proc [Output compiled] 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
  112. Comparison of AST before and after conversion 53 / 89

  113. Comparison of AST before and after conversion [AST before conversion]

    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
  114. Comparison of AST before and after conversion [AST before conversion]

    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 after conversion] 1 [:SCOPE, 2 [[], 3 nil, 4 [:BLOCK, 5 [[:FCALL, 6 [:puts, 7 [:LIST, 8 [(STR@1:0-1:10 "nyaaaaan"), nil]]]]]]]] 53 / 89
  115. Comparison of AST before and after conversion [AST before conversion]

    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 after conversion] 8 [(STR@1:0-1:10 "nyaaaaan"), nil]]]]]]]] This part has been replaced by the AST of the return value of the cat! method 1 [:SCOPE, 2 [[], 3 nil, 4 [:BLOCK, 5 [[:FCALL, 6 [:puts, 7 [:LIST, ` ` 53 / 89
  116. Comparison of AST before and after conversion [AST before conversion]

    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 after conversion] 8 [(STR@1:0-1:10 "nyaaaaan"), nil]]]]]]]] This part has been replaced by the AST of the return value of the cat! method Note that the converted AST is a mixture of RubyVM::AST::Node and arrays 1 [:SCOPE, 2 [[], 3 nil, 4 [:BLOCK, 5 [[:FCALL, 6 [:puts, 7 [:LIST, ` ` ` ` 53 / 89
  117. Use a defined Function Macro 22 src = compiled.source Finally,

    convert the AST to Ruby code 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
  118. Use a defined Function Macro 3 using Kenma::Refine::Source Finally, convert

    the AST to Ruby code using will allow you to use #source , and 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
  119. Use a defined Function Macro 22 src = compiled.source 23

    puts src Finally, convert the AST to Ruby code using will allow you to use #source , and get Ruby code from AST 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
  120. Use a defined Function Macro 24 eval(src) Finally, convert the

    AST to Ruby code using will allow you to use #source , and get Ruby code from AST 1 puts compiled.source 2 # => puts("nyaaaaan"); Evaluate the last transformed code with eval [Output] 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
  121. Use a defined Function Macro 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) Finally, convert the AST to Ruby code using will allow you to use #source , and get Ruby code from AST 1 puts compiled.source 2 # => puts("nyaaaaan"); Evaluate the last transformed code with eval [Output] 1 eval(src) 2 # => nyaaaaan Also, we can summarize the behavior from Proc to 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
  122. Use a defined Function Macro 14 Kenma.macro_eval { 15 use_macro!

    CatMacro 16 17 puts cat! 18 } Can be summarized in 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
  123. Use a defined Function Macro 14 Kenma.macro_eval { 15 use_macro!

    CatMacro 16 17 puts cat! 18 } Can be summarized in Kenma.macro_eval Macros are reflected and evaluated in the code in the block of 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
  124. Use a defined Function Macro 14 Kenma.macro_eval { 15 use_macro!

    CatMacro 16 17 puts cat! 18 } Can be summarized in Kenma.macro_eval Macros are reflected and evaluated in the code in the block of Kenma.macro_eval You can replace a specific method with another AST in this way 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
  125. Talk about Function Macro arguments 56 / 89

  126. Talk about Function Macro arguments 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
  127. Talk about Function Macro arguments 13 puts cat!(3) Pass an

    argument to a Macro Function like cat! 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
  128. Talk about Function Macro arguments 4 def cat!(num) Pass an

    argument to a Macro Function like cat! This argument can be taken as an argument to the cat! method 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
  129. Talk about Function Macro arguments 4 def cat!(num) Pass an

    argument to a Macro Function like cat! This argument can be taken as an argument to the cat! method But num is taken as AST, not as 3 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
  130. Talk about Function Macro arguments 4 def cat!(num) Pass an

    argument to a Macro Function like cat! This argument can be taken as an argument to the cat! method But num is taken as AST, not as 3 1 cat!(3) 2 # num => (LIT@24:12-24:13 3) It also takes expressions like 1 + 2 as 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
  131. Talk about Function Macro arguments 5 ast { "nyaaaaan" *

    num } If you try to refer to the variable num directly in ast {} at this time 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
  132. Talk about Function Macro arguments 13 puts "nyaaaaan" * num

    The code num is expanded directly, not the value 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
  133. Talk about Function Macro arguments 5 ast { "nyaaaaan" *

    num } so it’s not 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
  134. Talk about Function Macro arguments 5 ast { "nyaaaaan" *

    node_bind!(num) } Needs to be referenced via a special Macro called 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
  135. Talk about Function Macro arguments 5 ast { "nyaaaaan" *

    node_bind!(num) } Needs to be referenced via a special Macro called node_bind! Use node_bind! to expand the AST argument directly into an AST 1 node = ast { "nyaaaaan" } 2 3 # AST are expanded 4 pp ast { node_bind!(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 ` ` ` ` 61 / 89
  136. Talk about Function Macro arguments 5 ast { "nyaaaaan" *

    node_bind!(num) } Needs to be referenced via a special Macro called node_bind! Use node_bind! to expand the AST argument directly into an AST 1 node = ast { "nyaaaaan" } 2 3 # AST are expanded 4 pp ast { node_bind!(node) } 5 # => (STR@30:13-30:23 "nyaaaaan") So, puts cat!(3) is 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
  137. Talk about Function Macro arguments 13 puts "nyaaaaan" * 3

    expands to puts "nyaaaaaan" * 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
  138. Talk about Function Macro arguments 5 ast { "nyaaaaan" *

    node_bind!(num) } expands to puts "nyaaaaaan" * 3 Also, node_bind!(num) is a 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
  139. Talk about Function Macro arguments 5 ast { "nyaaaaan" *

    $num } It can also be written as $num Implemented internally to replace global variables with 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
  140. Talk about Function Macro arguments 15 puts Kenma.compile_of(body).source It can

    also be written as $num Implemented internally to replace global variables with node_bind! 1 node = ast { "nyaaaaan" } 2 3 # AST are expanded 4 ppp ast { $node } 5 # => (STR@30:13-30:23 "nyaaaaan") Expand to the final code that looks like this 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
  141. Talk about Function Macro arguments 15 puts Kenma.compile_of(body).source It can

    also be written as $num Implemented internally to replace global variables with node_bind! 1 node = ast { "nyaaaaan" } 2 3 # AST are expanded 4 ppp ast { $node } 5 # => (STR@30:13-30:23 "nyaaaaan") Expand to the final code that looks like this 1 puts("nyaaaaan" * 3) Other stringify! macros in the standard Macro to make the argument a string 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
  142. Node Macro 64 / 89

  143. Node Macro 65 / 89

  144. Node Macro A Macro that replaces an AST for a

    specific AST type 65 / 89
  145. Node Macro A Macro that replaces an AST for a

    specific AST type Basically the same as a function Macro, define a method that returns the AST to be replaced 65 / 89
  146. Node Macro A Macro that replaces an AST for a

    specific AST type Basically the same as a function Macro, define a method that returns the AST to be replaced Let’s write a Macro that replaces hoge.foo.bar with hoge&.foo&.bar ` ` ` ` 65 / 89
  147. Node Macro A Macro that replaces an AST for a

    specific AST type Basically the same as a function Macro, define a method that returns the AST to be replaced Let’s write a Macro that replaces hoge.foo.bar with hoge&.foo&.bar 1 hoge.foo.bar 2 # AST => [:CALL, [[:CALL, [[:VCALL, [:hoge]], :foo, nil]], :bar, nil]] For ` ` ` ` 65 / 89
  148. Node Macro A Macro that replaces an AST for a

    specific AST type Basically the same as a function Macro, define a method that returns the AST to be replaced Let’s write a Macro that replaces hoge.foo.bar with hoge&.foo&.bar 1 hoge.foo.bar 2 # AST => [:CALL, [[:CALL, [[:VCALL, [:hoge]], :foo, nil]], :bar, nil]] For 1 hoge&.foo&.bar 2 # AST => [:QCALL, [[:QCALL, [[:VCALL, [:hoge]], :foo, nil]], :bar, nil]] Convert CALL to QCALL as in ` ` ` ` ` ` ` ` 65 / 89
  149. Define a Node 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 { 15 use_macro! BocchiMacro 16 17 hoge.foo.bar 18 } 19 puts Kenma.compile_of(body).source 66 / 89
  150. Define a Node Macro 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 Define the module first, just like a function macro Define a node macro 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
  151. Define a Node Macro 8 def bocchi(node, parent) 9 [:QCALL,

    node.children] 10 end Define the module first, just like a function macro Define a node macro Define a method that receives the AST and returns the AST In this case, we are returning the AST information as an array 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
  152. Define a Node Macro 11 macro_node :CALL, :bocchi Define the

    module first, just like a function macro Define a node macro Define a method that receives the AST and returns the AST In this case, we are returning the AST information as an array Declare that it is a Macro by specifying which AST to hook into with macro_node 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
  153. Define a Node Macro 8 def bocchi(node, parent) 11 macro_node

    :CALL, :bocchi Define the module first, just like a function macro Define a node macro Define a method that receives the AST and returns the AST In this case, we are returning the AST information as an array Declare that it is a Macro by specifying which AST to hook into with macro_node The data of this AST called CALL will be passed as an argument of node parent is the node information of the 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
  154. Define a Node Macro 9 [:QCALL, node.children] Define the module

    first, just like a function macro Define a node macro Define a method that receives the AST and returns the AST In this case, we are returning the AST information as an array Declare that it is a Macro by specifying which AST to hook into with macro_node The data of this AST called CALL will be passed as an argument of node parent is the node information of the parent In this case, we want to replace CALL with QCALL , so we keep the child information and return AST with different type 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
  155. Define a Node Macro 14 body = proc { 15

    use_macro! BocchiMacro 16 17 hoge.foo.bar 18 } 19 puts Kenma.compile_of(body).source Usage is the same as for Function 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 67 / 89
  156. Define a Node Macro 15 use_macro! BocchiMacro Usage is the

    same as for Function Macro Use use_macro! where you want to use it 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
  157. Define a Node Macro 17 hoge.foo.bar Usage is the same

    as for Function Macro Use use_macro! where you want to use it If hoge.foo.bar is 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
  158. Define a Node Macro 17 hoge&.foo&.bar Replaced by 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
  159. Define a Node Macro 19 puts Kenma.compile_of(body).source Replaced by hoge&.foo&.bar

    [Output] 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
  160. Pattern Macro 69 / 89

  161. Pattern Macro 70 / 89

  162. Pattern Macro Macros to replace AST for a specific Ruby

    syntax 70 / 89
  163. Pattern Macro Macros to replace AST for a specific Ruby

    syntax Write a Macro to replace value = [1, 2, 3] with value = [1, 2, 3].freeze ` ` ` ` 70 / 89
  164. Pattern Macro Macros to replace AST for a specific Ruby

    syntax Write a Macro to replace value = [1, 2, 3] with value = [1, 2, 3].freeze 1 value = [1, 2, 3] 2 # AST => [:DASGN_CURR, [:value, [:LIST, [[:LIT, [1]], [:LIT, [2]], [:LIT, [3]], nil]]]] For ` ` ` ` 70 / 89
  165. Pattern Macro Macros to replace AST for a specific Ruby

    syntax Write a Macro to replace value = [1, 2, 3] with value = [1, 2, 3].freeze 1 value = [1, 2, 3] 2 # AST => [:DASGN_CURR, [:value, [:LIST, [[:LIT, [1]], [:LIT, [2]], [:LIT, [3]], nil]]]] For 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]]]] to add [:CALL, [... , :freeze]] to be added ` ` ` ` ` ` 70 / 89
  166. Define a Pattern Macro 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
  167. Define a Pattern Macro 9 macro_pattern pat { $name =

    $value }, :freezing Define which syntax to match with the argument of 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
  168. Define a Pattern Macro 9 macro_pattern pat { $name =

    $value }, :freezing Define which syntax to match with the argument of macro_pattern pat can be used to abstractly define "what syntax to match with" $ is not a global variable, but binds the matched AST 1 # If a match is found, return a Hash of bound AST 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 # Return nil if no match 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
  169. Define a Pattern Macro 9 macro_pattern pat { $name =

    $value }, :freezing In this case, we are specifying a pattern that matches the assignment expression 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
  170. Define a Pattern Macro 9 macro_pattern pat { $name =

    $value }, :freezing In this case, we are specifying a pattern that matches the assignment expression name = value So the pattern will look like this 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
  171. Define a Pattern Macro 9 macro_pattern pat { $name =

    $value }, :freezing In this case, we are specifying a pattern that matches the assignment expression name = value So the pattern will look like this 1 pp pat { $name = $value }.match(ast { hoge = 42 }) 2 # => {:name=>:hoge, :value=>(LIT@19:45-19:47 42)} The result of this match , Hash , is 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
  172. Define a Pattern Macro 6 def freezing(node, name:, value:) In

    this case, we are specifying a pattern that matches the assignment expression name = value So the pattern will look like this 1 pp pat { $name = $value }.match(ast { hoge = 42 }) 2 # => {:name=>:hoge, :value=>(LIT@19:45-19:47 42)} The result of this match , Hash , is Passed as keyword arguments for the specified method node is the AST of the entire matched syntax 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
  173. Define a Pattern Macro 7 ast { $name = $value.freeze

    } In this case, we are specifying a pattern that matches the assignment expression name = value So the pattern will look like this 1 pp pat { $name = $value }.match(ast { hoge = 42 }) 2 # => {:name=>:hoge, :value=>(LIT@19:45-19:47 42)} The result of this match , Hash , is Passed as keyword arguments for the specified method node is the AST of the entire matched syntax 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
  174. Define a Pattern Macro 15 value = [1, 2, 3]

    If value = [1, 2, 3] is 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
  175. Define a Pattern Macro 15 value = [1, 2, 3].freeze

    Replaced by 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
  176. Define a Pattern Macro 17 puts Kenma.compile_of(body).source Replaced by value

    = [1, 2, 3].freeze [Output] 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
  177. Show examples of Macro usage 75 / 89

  178. Debug output Macro 1 debug! 1 + 2 76 /

    89
  179. Debug output Macro 1 debug! 1 + 2 → 76

    / 89
  180. Debug output Macro 1 debug! 1 + 2 → 1

    puts "1 + 2 # => #{1 + 2}" 76 / 89
  181. Debug output Macro 1 debug! 1 + 2 → 1

    puts "1 + 2 # => #{1 + 2}" [Code] 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
  182. Debug output Macro 1 debug! 1 + 2 → 1

    puts "1 + 2 # => #{1 + 2}" [Code] 4 def debug!(expr) 13 debug! 1 + 2 + 3 Take debug! argument as 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
  183. Debug output Macro 1 debug! 1 + 2 → 1

    puts "1 + 2 # => #{1 + 2}" [Code] 5 ast { puts "#{stringify! $expr} # => #{$expr}" } Take debug! argument as AST stringify! to convert it to a string and 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
  184. Debug output Macro 1 debug! 1 + 2 → 1

    puts "1 + 2 # => #{1 + 2}" [Code] 5 ast { puts "#{stringify! $expr} # => #{$expr}" } Take debug! argument as AST stringify! to convert it to a string and Expressions are directly embedded in the string with #{} 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
  185. Debug output Macro 1 debug! 1 + 2 → 1

    puts "1 + 2 # => #{1 + 2}" [Code] 15 puts Kenma.compile_of(body).source Take debug! argument as AST stringify! to convert it to a string and Expressions are directly embedded in the string with #{} [Output] 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
  186. Macro for using multiple modules in one go 1 using

    Hoge, Foo, Bar 77 / 89
  187. Macro for using multiple modules in one go 1 using

    Hoge, Foo, Bar → 77 / 89
  188. Macro for using multiple modules in one go 1 using

    Hoge, Foo, Bar → 1 using Hoge 2 using Foo 3 using Bar 77 / 89
  189. Macro for using multiple modules in one go 1 using

    Hoge, Foo, Bar → 1 using Hoge 2 using Foo 3 using Bar [Code] 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
  190. Macro for using multiple modules in one go 1 using

    Hoge, Foo, Bar → 1 using Hoge 2 using Foo 3 using Bar [Code] 8 def using(*args) Take using arguments Hoge, Foo, Bar as variable length arguments Takes an array of 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
  191. Macro for using multiple modules in one go 1 using

    Hoge, Foo, Bar → 1 using Hoge 2 using Foo 3 using Bar [Code] 9 args.compact.inject(ast { {} }) { |result, name| 10 ast { $result; using $name } 11 } Take using arguments Hoge, Foo, Bar as variable length arguments Takes an array of Hoge Foo Bar AST Iterate over Hoge Foo Bar one by one, using it so that it becomes using Hoge; using Foo; using Bar 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
  192. Macro for using multiple modules in one go 1 using

    Hoge, Foo, Bar → 1 using Hoge 2 using Foo 3 using Bar [Code] 21 result = Kenma.compile_of(body) 22 puts result.source Take using arguments Hoge, Foo, Bar as variable length arguments Takes an array of Hoge Foo Bar AST Iterate over Hoge Foo Bar one by one, using it so that it becomes using Hoge; using Foo; using Bar [Output] 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
  193. Define { a: a, b: b, c: c } in

    a simplified way 1 ![a, b, c] 78 / 89
  194. Define { a: a, b: b, c: c } in

    a simplified way 1 ![a, b, c] → 78 / 89
  195. Define { a: a, b: b, c: c } in

    a simplified way 1 ![a, b, c] → 1 { a: a, b: b, c: c } 78 / 89
  196. Define { a: a, b: b, c: c } in

    a simplified way 1 ![a, b, c] → 1 { a: a, b: b, c: c } [Code] 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
  197. Define { a: a, b: b, c: c } in

    a simplified way 1 ![a, b, c] → 1 { a: a, b: b, c: c } [Code] 13 macro_pattern pat { ![*$args] }, :shorthand_hash_literal Define Pattern Macro to receive the contents of an array of ![] Take a, b, c as a single 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
  198. Define { a: a, b: b, c: c } in

    a simplified way 1 ![a, b, c] → 1 { a: a, b: b, c: c } [Code] 8 def shorthand_hash_literal(node, args:) Define Pattern Macro to receive the contents of an array of ![] Take a, b, c as a single AST Take a, b, c as a single AST in args 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
  199. Define { a: a, b: b, c: c } in

    a simplified way 1 ![a, b, c] → 1 { a: a, b: b, c: c } [Code] 9 args.children.compact.inject(ast { {} }) { |result, name| 10 ast { $result.merge({ symbolify!($name) => $name }) } 11 } Define Pattern Macro to receive the contents of an array of ![] Take a, b, c as a single AST Take a, b, c as a single AST in args iterate over a b c one by one and merge it into {} 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
  200. Define { a: a, b: b, c: c } in

    a simplified way 1 ![a, b, c] → 1 { a: a, b: b, c: c } [Code] 22 result = Kenma.compile_of(body) 23 puts result.source Define Pattern Macro to receive the contents of an array of ![] Take a, b, c as a single AST Take a, b, c as a single AST in args iterate over a b c one by one and merge it into {} [Output] 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
  201. Replace a < b < c with a < b

    && b < c 1 a < b < c 79 / 89
  202. Replace a < b < c with a < b

    && b < c 1 a < b < c → 79 / 89
  203. Replace a < b < c with a < b

    && b < c 1 a < b < c → 1 a < b && b < c 79 / 89
  204. Replace a < b < c with a < b

    && b < c 1 a < b < c → 1 a < b && b < c [Code] 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
  205. Replace a < b < c with a < b

    && b < c 1 a < b < c → 1 a < b && b < c [Code] 4 def chaining_comparison_operators(node, parent) 17 macro_node :OPCALL, :chaining_comparison_operators Define a macro that takes the AST of the whole a < b < c 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
  206. Replace a < b < c with a < b

    && b < c 1 a < b < c → 1 a < b && b < c [Code] 5 case node.to_a Define a macro that takes the AST of the whole a < b < c Now convert the AST to an array once 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
  207. Replace a < b < c with a < b

    && b < c 1 a < b < c → 1 a < b && b < c [Code] 6 in [:OPCALL, [ 7 [:OPCALL, [left, op1, [:LIST, [middle, nil]]]], 8 op2, [:LIST, [right, nil]]]] Define a macro that takes the AST of the whole a < b < c Now convert the AST to an array once Bind the values of an array of AST that match a < b < c in pattern matching 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
  208. Replace a < b < c with a < b

    && b < c 1 a < b < c → 1 a < b && b < c [Code] 9 ast { 10 $left.send(eval!(op1), $middle) && 11 $middle.send(eval!(op2), $right) 12 } Define a macro that takes the AST of the whole a < b < c Now convert the AST to an array once Bind the values of an array of AST that match a < b < c in pattern matching Convert to AST such that a.send(:<, b) && b.send(:<, c) here 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
  209. Replace a < b < c with a < b

    && b < c 1 a < b < c → 1 a < b && b < c [Code] Define a macro that takes the AST of the whole a < b < c Now convert the AST to an array once Bind the values of an array of AST that match a < b < c in pattern matching Convert to AST such that a.send(:<, b) && b.send(:<, c) here [Convert result] 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
  210. Other examples 80 / 89

  211. 81 / 89

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

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

    89
  214. 1 const! value = [1, 2, 3] → 1 #

    If it's already defined, 2 # make an exception 3 raise if defined? value 4 value = [1, 2, 3].freeze 81 / 89
  215. 1 const! value = [1, 2, 3] → 1 #

    If it's already defined, 2 # make an exception 3 raise if defined? value 4 value = [1, 2, 3].freeze 1 H[name, age] = user 2 disp H[name, age] 81 / 89
  216. 1 const! value = [1, 2, 3] → 1 #

    If it's already defined, 2 # make an exception 3 raise if defined? value 4 value = [1, 2, 3].freeze 1 H[name, age] = user 2 disp H[name, age] → 81 / 89
  217. 1 const! value = [1, 2, 3] → 1 #

    If it's already defined, 2 # make an exception 3 raise if defined? value 4 value = [1, 2, 3].freeze 1 H[name, age] = user 2 disp H[name, age] → 1 # Expanding or short-hand defining a Hash 2 name = user[:name]; age = user[:age] 3 disp({ name: name, age: age }) 81 / 89
  218. 1 const! value = [1, 2, 3] → 1 #

    If it's already defined, 2 # make an exception 3 raise if defined? value 4 value = [1, 2, 3].freeze 1 H[name, age] = user 2 disp H[name, age] → 1 # Expanding or short-hand defining a Hash 2 name = user[:name]; age = user[:age] 3 disp({ name: name, age: age }) 1 !i!(hoge foo bar) 81 / 89
  219. 1 const! value = [1, 2, 3] → 1 #

    If it's already defined, 2 # make an exception 3 raise if defined? value 4 value = [1, 2, 3].freeze 1 H[name, age] = user 2 disp H[name, age] → 1 # Expanding or short-hand defining a Hash 2 name = user[:name]; age = user[:age] 3 disp({ name: name, age: age }) 1 !i!(hoge foo bar) → 81 / 89
  220. 1 const! value = [1, 2, 3] → 1 #

    If it's already defined, 2 # make an exception 3 raise if defined? value 4 value = [1, 2, 3].freeze 1 H[name, age] = user 2 disp H[name, age] → 1 # Expanding or short-hand defining a Hash 2 name = user[:name]; age = user[:age] 3 disp({ name: name, age: age }) 1 !i!(hoge foo bar) → 1 # Expand like %i 2 [:hoge,:foo,:bar] 81 / 89
  221. 1 const! value = [1, 2, 3] → 1 #

    If it's already defined, 2 # make an exception 3 raise if defined? value 4 value = [1, 2, 3].freeze 1 H[name, age] = user 2 disp H[name, age] → 1 # Expanding or short-hand defining a Hash 2 name = user[:name]; age = user[:age] 3 disp({ name: name, age: age }) 1 !i!(hoge foo bar) → 1 # Expand like %i 2 [:hoge,:foo,:bar] 1 def func(name: String, age: (0...20)) 2 # ... 3 end 81 / 89
  222. 1 const! value = [1, 2, 3] → 1 #

    If it's already defined, 2 # make an exception 3 raise if defined? value 4 value = [1, 2, 3].freeze 1 H[name, age] = user 2 disp H[name, age] → 1 # Expanding or short-hand defining a Hash 2 name = user[:name]; age = user[:age] 3 disp({ name: name, age: age }) 1 !i!(hoge foo bar) → 1 # Expand like %i 2 [:hoge,:foo,:bar] 1 def func(name: String, age: (0...20)) 2 # ... 3 end → 81 / 89
  223. 1 const! value = [1, 2, 3] → 1 #

    If it's already defined, 2 # make an exception 3 raise if defined? value 4 value = [1, 2, 3].freeze 1 H[name, age] = user 2 disp H[name, age] → 1 # Expanding or short-hand defining a Hash 2 name = user[:name]; age = user[:age] 3 disp({ name: name, age: age }) 1 !i!(hoge foo bar) → 1 # Expand like %i 2 [:hoge,:foo,:bar] 1 def func(name: String, age: (0...20)) 2 # ... 3 end → 1 # Define it like a type check 2 def func(name, age) 3 raise TypeError unless String === name 4 raise TypeError unless (0..20) === age 5 # ... 6 end 81 / 89
  224. 82 / 89

  225. 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
  226. 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
  227. 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 # Define macros in a simplified way 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
  228. 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 # Define macros in a simplified way 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
  229. 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 # Define macros in a simplified way 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
  230. 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 # Define macros in a simplified way 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 # Annotations that implicitly define accessor 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
  231. If you can you do Ruby, You can dream more

    with Ruby! 83 / 89
  232. Future challenges 84 / 89

  233. Future challenges 85 / 89

  234. Future challenges Rensei’s recovery rate is still not 100% Still

    a lot of edge case issues Parsing all ActiveRecord files resulted in 15/240 file failure ` ` 85 / 89
  235. Future challenges Rensei’s recovery rate is still not 100% Still

    a lot of edge case issues Parsing all ActiveRecord files resulted in 15/240 file failure ast {} and pat {} uses global variables for node bindings, so the restriction is tight ast { def $name(); end } does not work I want to be able to apply this in a more simplified way ` ` ` ` ` ` ` ` ` ` 85 / 89
  236. Future challenges Rensei’s recovery rate is still not 100% Still

    a lot of edge case issues Parsing all ActiveRecord files resulted in 15/240 file failure ast {} and pat {} uses global variables for node bindings, so the restriction is tight ast { def $name(); end } does not work I want to be able to apply this in a more simplified way RubyVM::AbstractSyntaxTree itself is too restrictive RubyVM::AbstractSyntaxTree.of can’t be used in eval , so you can’t do eval("ast { ... }") can’t be used in eval This is pretty much the bottleneck ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` 85 / 89
  237. Summary 86 / 89

  238. Summary 87 / 89

  239. Summary This is what I got when I tried to

    implement a Macro in Ruby These are all implemented in Pure Ruby 87 / 89
  240. Summary This is what I got when I tried to

    implement a Macro in Ruby These are all implemented in Pure Ruby It’s not perfect yet, but I was able to implement the Macro relatively easily 87 / 89
  241. Summary This is what I got when I tried to

    implement a Macro in Ruby These are all implemented in Pure Ruby It’s not perfect yet, but I was able to implement the Macro relatively easily Unlike ordinary meta programming, Ruby can be changed at the syntactic level, so it is possible to do more than conventional meta programming 87 / 89
  242. Summary This is what I got when I tried to

    implement a Macro in Ruby These are all implemented in Pure Ruby It’s not perfect yet, but I was able to implement the Macro relatively easily Unlike ordinary meta programming, Ruby can be changed at the syntactic level, so it is possible to do more than conventional meta programming I’d like to continue to develop the possibility of Ruby Macro, as I haven’t really explored it yet I want to be able to define Macro in a more abstract way 87 / 89
  243. I’m looking forward to the future implementation of Macro in

    Ruby itself! 88 / 89
  244. Thank you all! 89 / 89