require 'util/visitor' # An EnterLeaveVisitor is a Visitor that visits objects both on the way down # and on the way up when traversing an object structure/hierarchy. # It will first call enter_X(x) if available, then visit_X(x) if avilable # then traverse down into children objects and then call leave_X(x) if # available. # Objects use visit_all to indicate structure. The first argument to visit_all # is a parent object and the rest are children objects. # NOTE! If they simply call visit the leave method will never be called but # the enter and visit methods will. module EnterLeaveVisitor include Visitor def visit(obj) visit_me_then_children(obj) end def enter(obj) method = _enter_method(obj) self.send(method, obj) if method end def leave(obj) method = _leave_method(obj) self.send(method, obj) if method end # Each children should be an array with children def visit_me_then_children(parent, *children) enter_method, visit_method, leave_method = _evl_methods(parent) self.send(enter_method, parent) if enter_method self.send(visit_method, parent) if visit_method children.each {|cs| cs.each {|c| c.accept_visitor(self)}} self.send(leave_method, parent) if leave_method end def _evl_methods(obj) obj.class.ancestors.map {|c| _evl_methods_for_class(c)}.compact.first end def _evl_methods_for_class(klass) # Memoized so not dynamic, maybe we should clear cache when a method is # added? (@_memoized_methods ||= Hash.new)[klass] ||= _methodnames_from_class(klass) end def _methodname_from_classname(klassName, prefix) name = prefix + klassName self.class.instance_methods.include?(name) ? name : nil end def _methodnames_from_class(klass) class_name = klass.inspect.split("::").last l = ["enter_", "visit_", "leave_"].map do |prefix| _methodname_from_classname(class_name, prefix) end # Return nil unless we found at least one method to visit (l.compact.length > 0) ? l : nil end end module EnterLeaveVisitable include Visitable end class Object include EnterLeaveVisitable end