/diamondback-ruby-0.20090726/lib/druby/contract/wrap.rb

http://github.com/adamdoupe/find_ear_rails · Ruby · 256 lines · 156 code · 36 blank · 64 comment · 13 complexity · 0c9439a6ea737af5833637a4615fd668 MD5 · raw file

  1. require 'druby/contract'
  2. require 'druby/utils'
  3. module DRuby
  4. module Contract
  5. ##
  6. # A class for wrapped immediate values such as fixnums
  7. #
  8. class Box
  9. attr_reader :val
  10. def initialize(x)
  11. @val = x
  12. meta = (class << self;self;end)
  13. ##
  14. # define this constant as a marker to later detect if we're
  15. # inside a box without using a method call
  16. #
  17. meta.const_set(:DRuby_Box, true)
  18. ##
  19. # Insert these binary comparison methods into the eigen class
  20. # of the box to perform the comparison on the original value
  21. #
  22. %w{equal? eql? == ===}.each do |m|
  23. meta.send(:define_method,m) do |other|
  24. super(Wrap.unwrap(other))
  25. end
  26. end
  27. ##
  28. # Add delegation methods to the eigen class to pass everything
  29. # on to the boxed value. We don't intercept the __*__ methods
  30. # (confuses the interpreter), send (used by us!), and val
  31. # (needed to unwrap the box). We also skip "methods" and the
  32. # singleton callback since immediate objects have no eigen
  33. # class, but we do, and we need to expose this fact for the
  34. # Wrap class
  35. #
  36. meths = x.methods - ["__id__","__send__","send","val",
  37. "methods", "singleton_method_added"]
  38. meths.each do |m|
  39. new_m = "def #{m}(*args,&blk) @val.send(#{m.inspect},*args,&blk) end"
  40. if Box.respond_to? :__druby_class_eval
  41. Box.__druby_class_eval new_m
  42. else
  43. Box.class_eval new_m
  44. end
  45. end
  46. end
  47. end # Box
  48. module Wrap
  49. def self.unwrap(x)
  50. if defined?((class << x;self;end)::DRuby_Box)
  51. return x.val
  52. else
  53. return x
  54. end
  55. end
  56. def self.wrap_block(ctx,blk)
  57. return nil unless blk
  58. proc do |*args|
  59. res = blk.call(*args.map {|a| DRuby::Safe_eval::Wrap.wrap_with_ctx(ctx,a)})
  60. next wrap_with_ctx(ctx,res)
  61. end
  62. end
  63. def self.wrap(file,line,code,x)
  64. ctx = Origin.new(file,line,code)
  65. wrap_with_ctx(ctx,x)
  66. end
  67. def self.wrap_with_ctx(ctx,x)
  68. case x
  69. when Numeric, Symbol
  70. x = Box.new(x)
  71. end
  72. meta = class << x; self; end
  73. return x if defined?(meta::DRuby_Wrapped)
  74. ##
  75. # We treat send as a trusted primitive to perform our
  76. # delegation and thus do not wrap it. The __id__ method is
  77. # also included since Ruby complains about redefining it and
  78. # I'm not fully aware of the consequences of doing so
  79. #
  80. orig_methods = x.methods - ["send","__send__","__id__", "class_eval"]
  81. meta.const_set(:DRuby_Wrapped, orig_methods)
  82. meta.const_set(:DRuby_ctx, ctx)
  83. meta.class_eval do
  84. ##
  85. # remove all of the original methods
  86. #
  87. orig_methods.sort.each do |meth|
  88. next if meth =~ /^__druby_/
  89. alias_method "__druby_#{meth.to_sym.to_i}", meth
  90. undef_method meth
  91. end
  92. def respond_to?(m)
  93. (class << self; self; end)::DRuby_Wrapped.include? m.to_s || super
  94. end
  95. ##
  96. # add our new method missing handler, obviously this must occur
  97. # *after* the above method removal
  98. #
  99. def method_missing(mname,*args,&blk)
  100. meta = class << self; self; end
  101. meths = meta::DRuby_Wrapped
  102. ctx = meta::DRuby_ctx
  103. if meths.include?(mname.to_s)
  104. wrapped_args = args.map {|arg|
  105. Wrap.wrap_with_ctx(ctx,arg)
  106. }
  107. wrapped_blk = Wrap.wrap_block(ctx,blk)
  108. begin
  109. send("__druby_#{mname.to_sym.to_i}",*wrapped_args,&wrapped_blk)
  110. rescue ArgumentError => exn
  111. msg = "calling #{mname} on #{self.inspect} threw an ArgumentError\n"
  112. msg << exn.message << "\n"
  113. ctx.violation msg
  114. rescue TypeError => exn
  115. msg = "calling #{self.inspect}.#{mname} with arguments "
  116. msg << args.map{|x|x.inspect}.join(', ')
  117. msg << " raised a TypeError\n"
  118. ctx.violation msg
  119. end
  120. else
  121. file,line = Utils.find_caller(Object.send(:caller,2))
  122. fname = File.basename(file)
  123. call_stack = Object.send(:caller,3).map {|x| " from "+x}.join "\n"
  124. ctx.violation "#{fname}:#{line}: undefined method `#{mname}' for #{self.inspect}:#{self.class} (NoMethodError)\n#{call_stack}"
  125. end
  126. end
  127. ##
  128. # capture any subsequently defined singleton methods and wrap them
  129. #
  130. define_method(:singleton_method_added) do |meth|
  131. return if meth.to_s =~ /^__druby/ || meth == :singleton_method_added
  132. w = "__druby_#{meth}"
  133. meta::DRuby_Wrapped << meth.to_s
  134. meta.send(:alias_method, w, meth)
  135. meta.send(:undef_method, meth)
  136. end
  137. end #meta.class_eval
  138. ##
  139. # capture any subsequently defined instance methods and wrap them
  140. # TODO: we actually need keep a list of these handlers inside the
  141. # class, one for each singleton
  142. #
  143. (class << x.class; self; end).send(:define_method,:method_added) do |meth|
  144. return if meth.to_s =~ /^__druby/
  145. STDOUT.puts "added instance method: #{meth.inspect}"
  146. w = "__druby_#{meth}"
  147. meta::DRuby_Wrapped << meth.to_s
  148. meta.send(:alias_method, w, meth.to_sym)
  149. meta.send(:undef_method, meth.to_sym)
  150. end
  151. ## return the final wrapped value (which is only different for
  152. ## immediate values)
  153. return x
  154. end # wrap_with_ctx
  155. end
  156. end # Contract
  157. end # DRuby
  158. module DRuby::Contract::Wrap::EqualMixin
  159. def equal?(other)
  160. super(DRuby::Contract::Wrap.unwrap(other))
  161. end
  162. end
  163. module DRuby::Contract::Wrap::EqlMixin
  164. def eql?(other)
  165. super(DRuby::Contract::Wrap.unwrap(other))
  166. end
  167. end
  168. ##
  169. # Fixnum is well behaved, it calls super when its equality operators
  170. # fail, allowing us to intercept them using mixins, which minimizes
  171. # overhead
  172. #
  173. class Fixnum
  174. include DRuby::Contract::Wrap::EqualMixin
  175. include DRuby::Contract::Wrap::EqlMixin
  176. ##
  177. # We also have to augment coerce so that a boxed value doesn't
  178. # degrade into using Float operations
  179. #
  180. alias_method :__druby_coerce, :coerce
  181. def coerce(x)
  182. lhs = DRuby::Contract::Wrap.unwrap(self)
  183. rhs = DRuby::Contract::Wrap.unwrap(x)
  184. lhs.__druby_coerce(rhs)
  185. end
  186. end
  187. ##
  188. # Float is almost well behaved, it calls super when equal? fails, but
  189. # not eql?, allowing us to intercept just the first
  190. #
  191. class Float
  192. include DRuby::Contract::Wrap::EqualMixin
  193. alias_method :__druby_eql?, :eql?
  194. def eql?(other)
  195. __druby_eql?(DRuby::Contract::Wrap.unwrap(other))
  196. end
  197. end
  198. ##
  199. # Symbol really doesn't play nice. It even has its own === which
  200. # doesn't use super
  201. #
  202. class Symbol
  203. %w{== === equal? eql?}.each do |meth|
  204. old_meth = :"__druby_eq_#{meth.to_sym.to_i}"
  205. alias_method old_meth, meth
  206. define_method meth do |other|
  207. send(old_meth,DRuby::Contract::Wrap.unwrap(other))
  208. end
  209. end
  210. end
  211. ##
  212. # Similarly, const_set must take an actual symbol, no public method is
  213. # called to test this, only an internal check
  214. #
  215. class Module
  216. alias_method :__druby_const_set, :const_set
  217. def const_set(sym,val)
  218. __druby_const_set(DRuby::Contract::Wrap.unwrap(sym),val)
  219. end
  220. end