Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

hi all Good morning Good day Good evening

Slide 3

Slide 3 text

Is everyone using Ruby???

Slide 4

Slide 4 text

If you are using Ruby…

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

1 CONST_VALUE = [1, 2, 3] This constant definitions for 1 CONST_VALUE = [1, 2, 3].freeze Implicitly freeze or ` `

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

1 ![a, b, c] Code like this. 1 { a: a, b: b, c: c } You can use Hash to expand it, for example

Slide 11

Slide 11 text

You’ll want to do it, right?

Slide 12

Slide 12 text

You can do that with a Macro!!!

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

Today’s talk Theme 11 / 89

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

What is a Macro? 13 / 89

Slide 18

Slide 18 text

What is a Macro? 14 / 89

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

What is AST? 15 / 89

Slide 23

Slide 23 text

What is AST? 16 / 89

Slide 24

Slide 24 text

What is AST? AST stands for Abstract Syntax Tree 16 / 89

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

Explanation of the Macro conversion process 22 / 89

Slide 39

Slide 39 text

Explanation of the Macro conversion process As an example, let’s convert hoge.foo to hoge&.foo ` ` ` ` 22 / 89

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

This is Macro!! 29 / 89

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

~ Convert AST to Ruby code ~ Rensei - 錬成 - 31 / 89

Slide 54

Slide 54 text

Rensei - 錬成 - 32 / 89

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

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

Slide 63

Slide 63 text

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

Slide 64

Slide 64 text

Implemented a Macro using Rensei! 34 / 89

Slide 65

Slide 65 text

~ Make it easy to handle macros in Ruby ~ Kenma - 研磨 - 35 / 89

Slide 66

Slide 66 text

Kenma - 研磨 - 36 / 89

Slide 67

Slide 67 text

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

Slide 68

Slide 68 text

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

Slide 69

Slide 69 text

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

Slide 70

Slide 70 text

How to use Kenma 37 / 89

Slide 71

Slide 71 text

Define a Macro 38 / 89

Slide 72

Slide 72 text

How to define a Macro 39 / 89

Slide 73

Slide 73 text

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

Slide 74

Slide 74 text

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

Slide 75

Slide 75 text

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

Slide 76

Slide 76 text

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

Slide 77

Slide 77 text

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

Slide 78

Slide 78 text

Function Macro 40 / 89

Slide 79

Slide 79 text

Function Macro 41 / 89

Slide 80

Slide 80 text

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

Slide 81

Slide 81 text

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

Slide 82

Slide 82 text

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

Slide 83

Slide 83 text

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

Slide 84

Slide 84 text

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

Slide 85

Slide 85 text

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

Slide 86

Slide 86 text

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

Slide 87

Slide 87 text

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

Slide 88

Slide 88 text

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

Slide 89

Slide 89 text

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

Slide 90

Slide 90 text

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

Slide 91

Slide 91 text

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

Slide 92

Slide 92 text

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

Slide 93

Slide 93 text

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

Slide 94

Slide 94 text

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

Slide 95

Slide 95 text

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

Slide 96

Slide 96 text

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

Slide 97

Slide 97 text

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

Slide 98

Slide 98 text

Let’s use macros! 46 / 89

Slide 99

Slide 99 text

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

Slide 100

Slide 100 text

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

Slide 101

Slide 101 text

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

Slide 102

Slide 102 text

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

Slide 103

Slide 103 text

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

Slide 104

Slide 104 text

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

Slide 105

Slide 105 text

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

Slide 106

Slide 106 text

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

Slide 107

Slide 107 text

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

Slide 108

Slide 108 text

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

Slide 109

Slide 109 text

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

Slide 110

Slide 110 text

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

Slide 111

Slide 111 text

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

Slide 112

Slide 112 text

Comparison of AST before and after conversion 53 / 89

Slide 113

Slide 113 text

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

Slide 114

Slide 114 text

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

Slide 115

Slide 115 text

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

Slide 116

Slide 116 text

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

Slide 117

Slide 117 text

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

Slide 118

Slide 118 text

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

Slide 119

Slide 119 text

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

Slide 120

Slide 120 text

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

Slide 121

Slide 121 text

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

Slide 122

Slide 122 text

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

Slide 123

Slide 123 text

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

Slide 124

Slide 124 text

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

Slide 125

Slide 125 text

Talk about Function Macro arguments 56 / 89

Slide 126

Slide 126 text

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

Slide 127

Slide 127 text

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

Slide 128

Slide 128 text

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

Slide 129

Slide 129 text

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

Slide 130

Slide 130 text

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

Slide 131

Slide 131 text

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

Slide 132

Slide 132 text

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

Slide 133

Slide 133 text

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

Slide 134

Slide 134 text

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

Slide 135

Slide 135 text

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

Slide 136

Slide 136 text

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

Slide 137

Slide 137 text

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

Slide 138

Slide 138 text

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

Slide 139

Slide 139 text

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

Slide 140

Slide 140 text

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

Slide 141

Slide 141 text

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

Slide 142

Slide 142 text

Node Macro 64 / 89

Slide 143

Slide 143 text

Node Macro 65 / 89

Slide 144

Slide 144 text

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

Slide 145

Slide 145 text

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

Slide 146

Slide 146 text

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

Slide 147

Slide 147 text

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

Slide 148

Slide 148 text

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

Slide 149

Slide 149 text

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

Slide 150

Slide 150 text

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

Slide 151

Slide 151 text

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

Slide 152

Slide 152 text

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

Slide 153

Slide 153 text

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

Slide 154

Slide 154 text

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

Slide 155

Slide 155 text

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

Slide 156

Slide 156 text

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

Slide 157

Slide 157 text

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

Slide 158

Slide 158 text

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

Slide 159

Slide 159 text

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

Slide 160

Slide 160 text

Pattern Macro 69 / 89

Slide 161

Slide 161 text

Pattern Macro 70 / 89

Slide 162

Slide 162 text

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

Slide 163

Slide 163 text

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

Slide 164

Slide 164 text

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

Slide 165

Slide 165 text

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

Slide 166

Slide 166 text

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

Slide 167

Slide 167 text

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

Slide 168

Slide 168 text

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

Slide 169

Slide 169 text

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

Slide 170

Slide 170 text

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

Slide 171

Slide 171 text

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

Slide 172

Slide 172 text

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

Slide 173

Slide 173 text

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

Slide 174

Slide 174 text

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

Slide 175

Slide 175 text

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

Slide 176

Slide 176 text

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

Slide 177

Slide 177 text

Show examples of Macro usage 75 / 89

Slide 178

Slide 178 text

Debug output Macro 1 debug! 1 + 2 76 / 89

Slide 179

Slide 179 text

Debug output Macro 1 debug! 1 + 2 → 76 / 89

Slide 180

Slide 180 text

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

Slide 181

Slide 181 text

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

Slide 182

Slide 182 text

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

Slide 183

Slide 183 text

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

Slide 184

Slide 184 text

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

Slide 185

Slide 185 text

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

Slide 186

Slide 186 text

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

Slide 187

Slide 187 text

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

Slide 188

Slide 188 text

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

Slide 189

Slide 189 text

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

Slide 190

Slide 190 text

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

Slide 191

Slide 191 text

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

Slide 192

Slide 192 text

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

Slide 193

Slide 193 text

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

Slide 194

Slide 194 text

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

Slide 195

Slide 195 text

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

Slide 196

Slide 196 text

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

Slide 197

Slide 197 text

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

Slide 198

Slide 198 text

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

Slide 199

Slide 199 text

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

Slide 200

Slide 200 text

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

Slide 201

Slide 201 text

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

Slide 202

Slide 202 text

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

Slide 203

Slide 203 text

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

Slide 204

Slide 204 text

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

Slide 205

Slide 205 text

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

Slide 206

Slide 206 text

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

Slide 207

Slide 207 text

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

Slide 208

Slide 208 text

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

Slide 209

Slide 209 text

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

Slide 210

Slide 210 text

Other examples 80 / 89

Slide 211

Slide 211 text

81 / 89

Slide 212

Slide 212 text

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

Slide 213

Slide 213 text

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

Slide 214

Slide 214 text

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

Slide 215

Slide 215 text

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

Slide 216

Slide 216 text

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

Slide 217

Slide 217 text

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

Slide 218

Slide 218 text

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

Slide 219

Slide 219 text

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

Slide 220

Slide 220 text

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

Slide 221

Slide 221 text

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

Slide 222

Slide 222 text

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

Slide 223

Slide 223 text

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

Slide 224

Slide 224 text

82 / 89

Slide 225

Slide 225 text

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

Slide 226

Slide 226 text

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

Slide 227

Slide 227 text

1 module MyMacro 2 macro_rule { 3 pat { $name = $value } --> { 4 $name = $value.freeze 5 } 6 pat { cat! } --> { "nyaaaaan" } 7 pat { cat!($num) } --> { "nyaaaaan" * $num } 8 } 9 end → 1 # 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

Slide 228

Slide 228 text

1 module MyMacro 2 macro_rule { 3 pat { $name = $value } --> { 4 $name = $value.freeze 5 } 6 pat { cat! } --> { "nyaaaaan" } 7 pat { cat!($num) } --> { "nyaaaaan" * $num } 8 } 9 end → 1 # 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

Slide 229

Slide 229 text

1 module MyMacro 2 macro_rule { 3 pat { $name = $value } --> { 4 $name = $value.freeze 5 } 6 pat { cat! } --> { "nyaaaaan" } 7 pat { cat!($num) } --> { "nyaaaaan" * $num } 8 } 9 end → 1 # 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

Slide 230

Slide 230 text

1 module MyMacro 2 macro_rule { 3 pat { $name = $value } --> { 4 $name = $value.freeze 5 } 6 pat { cat! } --> { "nyaaaaan" } 7 pat { cat!($num) } --> { "nyaaaaan" * $num } 8 } 9 end → 1 # 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

Slide 231

Slide 231 text

If you can you do Ruby, You can dream more with Ruby! 83 / 89

Slide 232

Slide 232 text

Future challenges 84 / 89

Slide 233

Slide 233 text

Future challenges 85 / 89

Slide 234

Slide 234 text

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

Slide 235

Slide 235 text

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

Slide 236

Slide 236 text

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

Slide 237

Slide 237 text

Summary 86 / 89

Slide 238

Slide 238 text

Summary 87 / 89

Slide 239

Slide 239 text

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

Slide 240

Slide 240 text

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

Slide 241

Slide 241 text

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

Slide 242

Slide 242 text

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

Slide 243

Slide 243 text

I’m looking forward to the future implementation of Macro in Ruby itself! 88 / 89

Slide 244

Slide 244 text

Thank you all! 89 / 89