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
or implicitly in the method body by writing:
def some_method
yield if block_given?
end
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 }
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]]]]]]]]]
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]]]]]]]]
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)
Can you share how you are generating the parse trees in these examples? 🤔 It seems like it would be fun to play around with!
It's a library in Ruby called ripper:
Thanks!