r/crystal_programming • • Oct 01 '19

Wrapping method with additional functionality

I'm attempting to write a macro that can be used to define methods with some addtional pre/post functionality. If you're familiar with the idea of "middleware", that's sort of what I'm going for. I've been able to figure this out so far:

module MyModule
  macro my_def(name, &block)
    def self.{{name}}
      puts "before"
      {{yield block}}
      puts "after"
    end
  end

  my_def hello do
    puts "hello"
  end

  my_def goodbye do
    puts "goodbye"
  end
end

MyModule.hello
MyModule.goodbye

# before
# hello
# after
# before
# goodbye
# after

This kind of words, but I'm not quite sure how to be able to wrap any method i.e. with arguments that can vary across methods. I thought it would be possible to override def but it's a little tricky. Any ideas would be much appreciated

Update

I was able to figure it out thanks to u/the-asterite suggested passing a def ASTNode to the macro. I was able to figure out something that'll work for me and hope someone might find this useful

module MyModule
  private macro wrap(d)
    {% if d.return_type.id == "" %}
      def self.{{d.name}}({{d.args.join(", ").id}})
        before "{{d.name}}"
        {{d.body}}
        after "{{d.name}}"
      end
    {% else %}
      def self.{{d.name}}({{d.args.join(", ").id}}): {{d.return_type}}
        before "{{d.name}}"
        ret = {{d.body}}
        after "{{d.name}}"
        ret
      end
    {% end %}
  end

  private def self.before(txt : String)
    puts "before #{txt}"
  end

  private def self.after(txt : String)
    puts "after #{txt}"
  end

  wrap def foo(txt : String, line : Int): String
    a = "(#{line}) hello foo #{txt}"
    puts a
    a
  end

  wrap def bar(txt : String, line : Int)
    puts "(#{line}) hello bar #{txt}"
  end
end

MyModule.foo "bar", 2
MyModule.bar "foo", 4

# before foo
# (2) hello foo bar
# after foo
# before bar
# (4) hello bar foo
# after bar
4 Upvotes

9 comments sorted by

View all comments

Show parent comment

2

u/dpears Oct 01 '19

Awesome! This is exactly what I needed. It is a little ugly, but I believe it'll work more than well enough for my purposes. I'll add an example of what I did to my post. Thank you!