DEV Community

Penelope Phippen
Penelope Phippen

Posted on

Understanding Ruby's block/proc parsing

In Ruby, methods can take a single block as an argument, which can be specified explicitly in the method signature by writing:

def some_method(&blk)
end

Enter fullscreen mode Exit fullscreen mode

or implicitly in the method body by writing:

def some_method
  yield if block_given?
end
Enter fullscreen mode Exit fullscreen mode

There are a few ways of specifying the block to the method:

def some_method(&blk)
  p blk
end

some_method(&:foo)

some_method do
  foo
end

some_method { foo }

Enter fullscreen mode Exit fullscreen mode

These are all (roughly) equivalent, calling the foo method in the place of the passed blk parameter. How they are parsed, however, is very different:

Passing an expression with &

Imagine we call this method as: some_method(&:foo). The parse tree looks like this:

[:program,
 [[:method_add_arg,
   [:fcall, [:@ident, "some_method", [1, 0]]],
   [:arg_paren,
    [:args_add_block,
     [],
     [:symbol_literal, [:symbol, [:@ident, "foo", [1, 14]]]]]]]]]
Enter fullscreen mode Exit fullscreen mode

Specifically, the argument structure to the call is parsed as a parser node called args_add_block, which contains an expression list (that's the empty array in the code example above), and a single expression at the end (that's the :symbol_literal expression that represents the to_proc expression). The first expression list is the non proc arguments to the call.

Passing a block

Imagine we call the method as: some_method { foo }. The parse tree looks like this:

[:program,
 [[:method_add_block,
   [:method_add_arg, [:fcall, [:@ident, "some_method", [1, 0]]], []],
   [:brace_block, nil, [[:vcall, [:@ident, "foo", [1, 14]]]]]]]]
Enter fullscreen mode Exit fullscreen mode

in particular, note that we no longer have an args_add_block parse node. This is because in this style of call, the block is not treated as part of the arguments to the call by the parser, instead, it is treated as a modifier to the method call, that changes which call type is being made.

why did you write this?

This acts as documentation for the ongoing development of Rubyfmt.

Top comments (3)

Collapse
 
daniel13rady profile image
Daniel Brady

Can you share how you are generating the parse trees in these examples? 🤔 It seems like it would be fun to play around with!

Collapse
 
penelope_zone profile image
Penelope Phippen

It's a library in Ruby called ripper:

require "ripper"
pp Ripper.sexp("hi")
# => [:program, [[:vcall, [:@ident, "hi", [1,0]]]]]
Collapse
 
daniel13rady profile image
Daniel Brady

Thanks!