info | twitter | github
J-_-L | Writings on Ruby Programming
19.08.10 20.08.10

Do you know the official Ruby interpreter "goruby"?

fun · golf · regex · ruby · ruby1-9
##              ##    ##     ##    ##    ##    ####           ###        ##    ##
 ##            ##     ##     ##     ##  ##     ##  ##        ## ##        ##  ##
  ##    ##    ##      #########      ####      ##   ##     #########       ####
   ##  ####  ##       ##     ##       ##       ##  ##      ##     ##        ##
    ####  ####        ##     ##       ##       ####       ##       ##       ##

If you don’t know what to do on whyday, do not play code golf! It is frustrating! And it is addictive! And you don’t create something useful by golfing!

But it is also fun!

And I am not the only one who wants to squeeze code even more. The original Ruby 1.9 interpreter comes with pretty crazy version of itself: a version for code golfing! Just navigate to the Ruby source and make golf. All it does is adding about 100 lines of Ruby meta programming :)

Do not miss Object#method_missing

Let’s take look at the main modification and try to understand it line by line (or scroll down, if you just want to know what it does ;) ).

Listing 1
/34/goruby.rb ruby 1.9
 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# code from the Ruby soure, golf_prelude.rb, commented

class Object
  # initialize a global hash cache
  @@golf_hash = {}

  # let's begin the madness: our favourite method
  def method_missing m, *a, &b
    # search the cache for the missing method/class combination
    #  or assign it with the best matching constant name
    t = @@golf_hash[ [m,self.class] ] ||= matching_methods(m)[0]

    # if found in the hash and block_given?
    if t && b
      # call the method named by t and pass the arguments
      __send__(t, *a) {|*args|
        # also pass found regexes
        b.binding.eval("proc{|golf_matchdata| $~ = golf_matchdata }").call($~) if $~
        # and execute the block
        b.call(*args)
      }

    # if no block is given
    else
      # call the method named by t or throw method_missing again
      t ? __send__(t, *a, &b) : super
    end
  end


  # here comes the magic
  def matching_methods(s='', m=callable_methods)
    # build a regex which starts with ^ (beginning)
    # take every letter of the method_name
    #   insert "(.*?)" instead, which means: match anything and take the smallest match
    #   and insert the letter itself (but escape regex chars)
    #
    # for example s='matz'
    # => /^(.*?)m(.*?)a(.*?)t(.*?)z/
    #
    r=/^#{s.to_s.gsub(/./){"(.*?)"+Regexp.escape($&)}}/

    # match all available methods for this regex
    #  and sort them by the least matches of the "fill" regex groups
    m.grep(r).sort_by do |i|
      i.to_s.match(r).captures.map(&:size) << i
    end
  end

  # capture not only methods, but also constants
  def self.const_missing c

    # search the cache for the missing constant/class combination
    #  or assign it with the best matching method name
    t = @@golf_hash[ [c,self.class] ] ||= matching_methods(c,constants)[0]

    # return the constant if it exist
    t and return const_get(t)

    # or raise the error
    raise NameError, "uninitialized constant #{c}", caller(1)
  end

  # get the shortest shortcut for a callable method
  def shortest_abbreviation(s='', m=callable_methods)
    # check if s (method_name) is capitalized
    s=s.to_s
    our_case = (?A..?Z)===s[0]

    # check if method_name is callable (and get its index in the callable method array)
    if m.index(s.to_sym)
      # begin with length 1 and try until method_name's size
      1.upto(s.size){|z|
        # try for all combinations of the method_name letters for the current length
        #  combination:  %w[a b c].combination 2 # is [["a","b"],["a","c"],["b","c"]]
        s.scan(/./).combination(z).map{|trial|

        # skip if current combination is capitalized, but out method_name not
        #  (and vice versa)
        next unless ((?A..?Z)===trial[0]) == our_case

        # join the combination and return it if it is the best matching method name
        trial*=''
        return trial if matching_methods(trial,m)[0].to_s==s
      }}

    # method_name not found
    else
      nil
    end
  end

  # get *all* callable methods of an Object
  def callable_methods
    self.class == Object ? methods + private_methods : methods
  end
  
  # ...
end

By “abusing” method_missing and const_missing for Object, it lets you call methods and constants with the shortest name possible (e.g: [[1,2,3]].fle means [[1,2,3]].flatten). To get the shortest name of a method for an object, you can run for example: "an object".shortest_abbreviation :reverse # => 'rv'

List of further small changes

  • do_while(&block) and do_until(block) methods which return 0 as long/until the block is true.
  • String modifications
    • Alias: String#split —> String#/
    • String#to_a is done via String#split('')
    • It adds Array methods to String which operate on each char
  • Enumerator also uses Array methods that do not exist for Enumerator by converting its instance to an Array
  • Alias: puts —> say
  • Array#to_s via Array#join
  • FalseClass#to_s is '' (instead of false)
  • Integer includes Enumerable via times
  • The most important method h, which [try it out] ;)

GoRuby

Navigate to your Ruby 1.9 source and run make golf

To see goruby in action, checkout shinhs golf server and inspect a problem that is labeled with “post mortem”!

Creative Commons License

Comments