The Crystal Programming Language Forum

Macro generating a do ... end block?

I know that a macro has to output a complete structure, and if I’m not mistaken, a do … end block is complete. So why does this code not work? Is there a way to achieve the intended result?

macro do_mac
  do |n|
    puts "works, #{n}"
  end
end

2.times do |n| puts "works, #{n}" end
2.times do_mac # Error: expecting token 'EOF', not 'do'
There was a problem expanding macro 'do_mac'


Called macro defined in eval:1:1

Which expanded to:

 > 1 | do |n| puts "works, #{n}" end
       ^
Error: expecting token 'EOF', not 'do'

do ... end is not a complete expression. It’s part of a call.

What you can do is put the rest of the call into the macro:

macro do_mac(call)
  {{ call }} do |n|
    puts "works, #{n}"
  end
end

do_mac(2.times)

Or put the entire call outside the macro (and let it just return the block expressions):

macro do_mac
  puts "works, #{n}"
end

2.times do |n| do_mac end
1 Like

Ah, thanks.

The first one could do in my case.

Meanwhile I found this one also works, though it comes with the complication of having to specify the argument type explicitly – and something “generic” like Object or Number or Int won’t cut it:

macro do_mac2
  ->(n : Int32){ puts "works, #{n}" }
end

2.times &do_mac2

Assuming instead I pass the call (2.times in this case) as macro argument, is there a way to know what type(s) the call would yield?

It’s easy when it’s a yield self, but in other cases it would be useful information to be exposed, together with raised exceptions.

For context, I’m playing around inspired by RFC: with … yield replacement.


totally off topic: I often do too many things at once and leave replies hanging for even days before I decide to click “reply”. I hope they don’t show up as “replying…” all the while, sorry for that in case and let me know.

Sure:

macro do_mac(call)
  typeof({{ call }} { |n| break n } || raise "") # => Int32

  {{ call }} do |n|
    puts "works, #{n}"
  end
end

do_mac(2.times)
2 Likes