Created
September 8, 2012 14:50
-
-
Save norswap/3675610 to your computer and use it in GitHub Desktop.
Jaffar, the Evil Advisor Library
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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