views:

37

answers:

1

I'm a bit confused about how Ruby handles the creation of Enumerators. Block-based iteration makes sense and is working for me; I am still confused how the return of an Enumerator is supposed to function code-wise.

Here is the code I am working with:

VALUE rb_RPRuby_Sender_Kernel_each_backtrace_frame( int      argc,
                                                       VALUE*   args,
                                                       VALUE    rb_self )   {

    rb_thread_t*            c_thread  = GET_THREAD();
    //  Get the current frame - we're doing a backtrace, so our current working frame to start is the first previous thread
    rb_control_frame_t*     c_current_context_frame    = RUBY_VM_PREVIOUS_CONTROL_FRAME( RUBY_VM_PREVIOUS_CONTROL_FRAME( c_thread->cfp ) );

    //  c_top_of_control_frame describes the top edge of the stack trace
    //  set c_top_of_control_frame to the first frame in <main>
    rb_control_frame_t*     c_top_of_control_frame  =   RUBY_VM_NEXT_CONTROL_FRAME( RUBY_VM_NEXT_CONTROL_FRAME( (void *)( c_thread->stack + c_thread->stack_size ) ) );

    //  for each control frame:
    while ( c_current_context_frame < c_top_of_control_frame ) {

        VALUE   rb_frame_hash   =   rb_RPRuby_Sender_Kernel_internal_backtraceHashForControlFrame(  & c_current_context_frame );

        //  if we don't have a block, return enumerator
        RETURN_ENUMERATOR( rb_self, 0, NULL );

        //  otherwise, yield the block
        rb_yield( rb_frame_hash );

        c_current_context_frame = RUBY_VM_PREVIOUS_CONTROL_FRAME( c_current_context_frame );        
    }

    return Qnil;
}

How would the final line in the while loop be called in the case of an Enumerator?

Does all of my loop activity have to take place before calls to RETURN_ENUMERATOR (since RETURN_ENUMERATOR presumably has to come before rb_yield())?

What if I want something to happen once the internal iteration finishes? With the block I can simply put it after the while loop; presumably the same works in the case of an Enumerator- but how? It seems like every time through the loop it returns an Enumerator, so how does the Enumerator know to return the appropriate corresponding object? rb_yield gets rb_frame_hash as a passed arg, but RETURN_ENUMERATOR seems to take the args that are relayed to the method when the Enumerator calls the method internally. So clearly the Enumerator is calling the method itself- perhaps with some sort of internal block that simply returns the instance of rb_frame_hash?

Any insight into the internals is appreciated.

-Asher

A: 

To attempt to answer my own question:

When RETURN_ENUMERATOR is called, rb_enumeratorize is called, which creates an Enumerator. The Enumerator is returned; when :next is called on the Enumerator, a Fiber is initialized (if necessary) or resumed. Each time :next is called, the Fiber iterates an internally provided block once in order to get the next iterator item (setting no_next in the Enumerator's C struct and calling rb_fiber_yield on the Enumerator's Fiber).

So it would seem that the loop activity does not have to take place before RETURN_ENUMERATOR. I'm not yet clear on actions after enumeration in the function returning an Enumerator in the case that a block was not provided.

Asher