%% Default fonts %deffont "standard" xfont "helvetica-medium-r", tfont "arial.ttf" %deffont "thick" xfont "helvetica-bold-r", tfont "arialb.ttf" %deffont "typewriter" xfont "fixed-medium-r", tfont "cour.ttf" %% %% Default settings per each line number %default 1 area 90 90, leftfill, size 2, fore "yellow", back "black", font "thick", bgrad %default 2 size 6, vgap 10, prefix " ", ccolor "black" %default 3 size 2, bar "gray70", vgap 10 %default 4 size 5, fore "white", vgap 30, prefix " ", font "standard" %% %% Default settings for tab-indented lines %tab 1 size 5, vgap 40, prefix " ", icon box "green" 50 %tab 2 size 4, vgap 40, prefix " ", icon arc "yellow" 50 %tab 3 size 3, vgap 40, prefix " ", icon delta3 "white" 40 %% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %page %center Hacking Ruby Ruby Conference 2004 Paul Brannan paul@atdesk.com %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %page What is "hacking ruby"? From foldoc: %font "typewriter", size 3 hack 6. To interact with a computer in a playful and exploratory rather than goal-directed way. "Whatcha up to?" "Oh, just hacking." %font "standard" This interaction often results in code that lets ruby do/express things it was never specifically designed to %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %page Why hack? Ruby is lacking a feature from that you can't bear to live without To do a task that would otherwise be difficult Proof-of-concept for an RCR To explore/debug extensions and/or ruby itself For fun! %% This last reason is, of course, the most _____ reason %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %page Example of a "hack" #ruby < [1, 2, 3, 1, 2, 3, 4] ENDCODEBLOCK END %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %page Ruby, how do I hack thee? Let me count the ways... Pure Ruby (using set_trace_func, callcc, etc.) Already-written extensions (enumerator, stringio, etc.) Using libdl (e.g. evil.rb) Writing your own extensions Hacking the interpreter itself %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %page The Ruby Hacker's Guide %% A talk on hacking Ruby could not possibly be complete without %% mentioning this book! In Japanese: http://i.loveruby.net/ja/rhg/ Can't read Japanese? Use babelfish! But babelfish doesn't work with iso-2022-jp? Convert it to euc-jp using iconv. But babelfish doesn't like large documents? Split the html into multiple files! Maybe someone has already done this work: http://rubystuff.org/rhg/convertrhg.rb %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %page Tools for hacking in pure Ruby set_trace_func method_missing append_features/extend_object binding callcc Thread.new %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %page set_trace_func Provides a callback whenever the interpreter calls a method, evaluates a statement, etc. Used by the debugger for breakpoints, watchpoints, etc. #ruby <, false] ["call", "test.rb", 1, :foo, #, Object] ["line", "test.rb", 1, :foo, #, Object] ["c-call", "test.rb", 1, :puts, #, Kernel] ["c-call", "test.rb", 1, :write, #, IO] foo!["c-return", "test.rb", 1, :write, #, IO] ["c-call", "test.rb", 1, :write, #, IO] ["c-return", "test.rb", 1, :write, #, IO] ["c-return", "test.rb", 1, :puts, #, Kernel] ["return", "test.rb", 1, :foo, #, Object] ENDCODEBLOCK END %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %page callcc Used to save the current point of execution Execution can be resumed from the saved point Similar to creating a stopped thread Can be used to access data you can't normally access %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %page Put it together... #ruby < | |-class = # | +-super = # | |-class = # (*) | +-super = # (*) +-super = # +-class = M |-class = Module (*) +-super = # +-class = Kernel (*) ENDCODEBLOCK END %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %page Put the class hierarchy to work Overriding methods cleanly (not so useful on Ruby 2.0...): #ruby < [1, 2, 3, 1, 2, 3, 4] ENDCODEBLOCK END %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %page Overriding methods (cont'd) #ruby < | |-class = # | +-super = # (*) +-super = #<#:0x401af438> +-class = # |-class = #> | |-class = # (*) | +-super = Module (*) +-super = # +-class = Kernel (*) ENDCODEBLOCK END %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %page evil.rb %% evil.rb uses a technique for accessing the ruby interpreter's internals %% using libdl. Why is this desirable? Why not just use an extension? Well, %% because extensions have to be compiled; a library that uses libdl can be run %% as-is. %% http://evil.rubyforge.org Reproduces Ruby's C data structures using DL Why not just use an extension? Extensions have to be compiled; DL-based libraries run as-is. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %page Object#become Swaps one object for another One stupid implementation: #ruby < require 'nodewrap' => true irb(main):002:0> l = lambda { 1 + 1 } => # irb(main):003:0> l.body => # irb(main):004:0> l.body.members.each { |m| p [m, l[m]] } ["recv", 2] ["args", 2] ["mid", 2] ENDCODEBLOCK END %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %page Marshalling methods with Nodewrap #ruby < require 'nodewrap' => true irb(main):002:0> class Foo; def foo; puts 'foo!'; end; end => nil irb(main):003:0> s = Marshal.dump(Foo.instance_method(:foo).body) => "\004\010u:\020Node::SCOPE..." irb(main):004:0> n = Marshal.load(s) => # irb(main):005:0> class Bar; end; Bar.instance_eval { add_method(:foo, n, 0) } => nil irb(main):006:0> Bar.new.foo foo! => nil ENDCODEBLOCK END %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %page Dumping nodes to stdout with Nodewrap %font "typewriter", size 2 $ cat test.rb def foo 1+1 end $ ruby -rnwdump test.rb NODE_NEWLINE at test.rb:1 |-nth = 1 +-next = NODE_DEFN at test.rb:1 |-defn = NODE_SCOPE at test.rb:3 | |-rval = false | |-tbl = nil | +-next = NODE_BLOCK at test.rb:1 | |-next = NODE_BLOCK at test.rb:3 | | |-next = false | | +-head = NODE_NEWLINE at test.rb:2 | | |-nth = 2 | | +-next = NODE_CALL at test.rb:2 | | |-recv = NODE_LIT at test.rb:2 | | | +-lit = 1 | | |-args = NODE_ARRAY at test.rb:2 | | | |-alen = 1 | | | |-head = NODE_LIT at test.rb:2 | | | | +-lit = 1 | | | +-next = false | | +-mid = :+ | +-head = NODE_ARGS at test.rb:1 | |-cnt = 0 | |-rest = -1 | +-opt = false |-mid = :foo +-noex = PRIVATE %font "standard" %% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% %page %% %% Inspecting Ruby's internals: bruby %% %% TODO %% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% %page %% %% TODO: interpreter/VM hacks %% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% %page %% %% TODO: lazy-evaluation of expresions? %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %page Using Ripper Could be used to implement macros in Ruby? Interface documented in ripper.y Examples can be found on Rubyforge %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %page Where to learn more The Pickaxe book README.EXT (came with ruby) The ruby source (especially object.c and eval.c -- grep through them by searching for define_method) The Ruby Hacker's Guide ruby-talk mailing list %%http://84.128.196.153/exotic/