# Forward referencing is one of those painful problems in programming - if # you try to use something before it's defined, trouble ensues. Using the # ForwardReferencing module and the ForwardReference class can let you # gracefully recover from problems caused by forward references in data and # processing. require 'eymiha' # The ForwardReferencing module can be mixed into a class to allow it to # capture and resolve ForwardReferences. module ForwardReferencing # An array containing the set of unresolved forward references. attr_reader :forward_references # To be called from the initializer of the includer, this sets up the forward # reference capture and resolution mechanisms. def start_forward_referencing forward_references_clear @had_forward_reference_resolution = false @forward_reference_resolver = nil end # To be called when a section of code that could contain a forward reference # is entered. The method returns a newly created ForwardReference with the # given dependency that can be jumped to during resolution. def create_forward_reference(dependency=nil,context=nil) forward_reference = ForwardReference.new(dependency,context) @forward_references << forward_reference forward_reference end # To be called when a section of code that could contain a forward reference # has successfully been reached. It is used to remove the ForwardReference # that was created at the start of the section, and asserts that a # resolution was made. def remove_forward_reference(forward_reference=nil) @forward_references.delete forward_reference if (forward_reference.kind_of? ForwardReference) @had_forward_reference_resolution = true end # To be called to try to resolve any unresolved ForwardReferences by jumping # to each in turn and retrying the code that caused it. This method repeats # until nothing more is resolved. At that point unresolved forward reference # may still exist, to be possibly resolved by another call to this method # downstream. Prior to continuing to a forward reference, the # establish_forward_reference context method is called with the context that # was provided at the time the forward reference was created to give the # receiver a chance to reset any transcient infromation. def resolve_forward_references forward_references = @forward_references forward_references_clear @had_forward_reference_resolution = false if forward_references.size > 0 @forward_reference_resolver ||= callcc {|cont| cont} while (@forward_reference_resolver == nil) forward_reference = forward_references.shift if forward_reference != nil establish_forward_reference_context(forward_reference.context) if respond_to?(:establish_forward_reference_context,true) forward_reference.continuation.call end end @forward_reference_resolver = nil resolve_forward_references if @had_forward_reference_resolution end # To be called at the end of a section of code that could contain a forward # reference, it will continue during normal processing and jump back to the # resolve_forward_references method during resolution. def continue_forward_reference_resolution @forward_reference_resolver.call if @forward_reference_resolver end # Returns a hash of dependencies to arrays of the ForwardReferences that # have them as dependencies. def forward_reference_dependencies dependencies = {} @forward_references.each { |forward_reference| dependency = forward_reference.dependency || "nil" forward_references = dependencies[dependency] dependencies[dependency] = [] if forward_references == nil dependencies[dependency] << forward_reference } dependencies end # Returns a string indicating the current state of ForwardReferencing. def forward_references_to_s "#{self_name} #{forward_references_remaining} unresolved" end # Returns the number of unresolved forward references. def forward_references_remaining @forward_references.size end # Remove the remaining unresolved forward references. def forward_references_clear @forward_references = [] end end # A ForwardReferencer is simply a class-wrapper for the ForwardReferencing # module. method have been shortened there is reduced potential for conflict # from inheritence than from inclusion or extension. class ForwardReferencer understands ForwardReferencing alias initialize start_forward_referencing alias create create_forward_reference alias remove remove_forward_reference alias resolve resolve_forward_references alias continue continue_forward_reference_resolution alias dependencies forward_reference_dependencies alias to_s forward_references_to_s alias remaining forward_references_remaining alias clear forward_references_clear end # A ForwardReference holds a continuation and a dependency, the where and # the why of forward referencing. class ForwardReference # Holds the place to jump back to for attempting to resolve a forward # reference. attr_reader :continuation # Holds an arbitrary object that indicates why the forward reference # occurred. attr_accessor :dependency # Holds an arbitrary object that holds context that can be re-established # to help resolve the forward reference. attr_accessor :context # Returns a new instance with a valid continuation, the given dependency # and contextual information. def initialize(dependency=nil,context=nil) @continuation = nil @continuation = callcc{|cont| cont} while (@continuation == nil) @dependency = dependency @context = context end # Returns a string indicating the current state of the ForwardReference. def to_s "#{self_name} dependency #{dependency} #{continuation}" end end