Skip to content

Instantly share code, notes, and snippets.

@norswap
Created September 8, 2012 14:50
Show Gist options
  • Save norswap/3675610 to your computer and use it in GitHub Desktop.
Save norswap/3675610 to your computer and use it in GitHub Desktop.
Jaffar, the Evil Advisor Library
# Jaffar, the Evil Advisor Library
#
# This is a simple experiment in Ruby metaprogmming, to see how to implement
# a lisp-style advising construct. Advising is basically augmenting existing
# functions with your own code. See the exemple below.
#
# Advising is very much related to context-oriented programming. I fortunately
# have no use for either an advising or context-oriented programming right now
# and this is why I did not expand upon this simple implementation.
#
# An extended implementation would need to keep track of defined advices
# (e.g. by giving them a name) and allow to activate/deactivate
# them. Implementing contexts then becomes a breeze.
#
# A feature that would be neat is the ability to arbitrate conflicts between
# advices. This would be done by allowing each advice to define "locks" (for
# instance on instance variables). If two advices for the same methods have
# conflicting locks, the conflict would need to be arbitrated manually, using a
# construct that would basically say "this advice goes before that one".
module Jaffar
module Include
def proceed *args ; Jaffar.proceed *args end
def defadvice *args, &block ; Jaffar.defadvice *args, &block end
end
class MethRef < Struct.new :klass, :name ; end
class Advice < Struct.new :meth_ref, :proc
def run args ; Jaffar.stack.last.instance.instance_exec *args, &proc end
end
class Original < Advice ; end
class StackElem < Struct.new :meth_ref, :instance, :index ; end
@@advices = Hash.new([])
@@stacks = Hash.new([])
@@mutex = Mutex.new
def self.stack
@@stacks[Thread.current.object_id]
end
def self.next_advice
@@advices[stack.last.meth_ref][stack.last.index]
end
def self.proceed *args
stack.last.index -= 1
next_advice.run args
stack.last.index += 1
end
def self.defadvice klass, method_name, &proc
@@mutex.synchronize do
meth_ref = MethRef.new klass, method_name
make_advisable(meth_ref) if @@advices[meth_ref] == []
@@advices[meth_ref] << Advice.new(meth_ref, proc)
end
end
def self.make_advisable meth_ref
umeth = meth_ref.klass.instance_method(meth_ref.name)
original = proc do |*args| umeth.bind(self).call *args end
@@advices[meth_ref] << Original.new(meth_ref, original)
meth_ref.klass.send :define_method, meth_ref.name do |*args|
Jaffar.advised_method_body self, meth_ref, args
end
end
def self.advised_method_body this, meth_ref, args
stack << StackElem.new(meth_ref, this, -1)
next_advice.run args
stack.pop
end
end
# A simple exemple.
# class Test
# def ok ; puts "okay" end
# end
# Jaffar.defadvice Test, :ok do
# puts "before"
# Jaffar.proceed
# puts "after"
# end
# include Jaffar::Include
# defadvice Test, :ok do
# puts "one"
# proceed
# puts "two"
# end
# Test.new.ok # prints "one before okay after two"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment