www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - asForwardRange, a ForwardRange based on an InputRange

After my recent experiments with ranges and bearophile's thread "Phobos 
usability with text files" I decided to try a ForwardRange based on an 
InputRange.

Of course it's pretty trivial: the elements that are read from the 
original range are cached. The elements that have all been consumed by 
all the "views" are dropped from the beginning of the cache and 
eventually freed by the GC.

I decided to represent each view as a pointer into the cached elements. 
The pointers need adjustment if the cached array is relocated by the GC.

The test program creates files /tmp/deleteme_* and copies each 
ForwardRange's view into those files. (That is commented out in the 
included program.)

// --- 8< ---------- as_forward_range.d ---------- >8 ---

module as_forward_range;

import std.range : isInputRange, ElementType;

/**
  * A ForwardRange based on an InputRange
  *
  * Consumes the original InputRange lazily
  */
class ForwardRangeLazyCache(Range)
     if (isInputRange!Range)
{
     alias ElementType!Range E;

     Range range;                 /* The original range */
     immutable(E)[] cache;        /* Cached elements from the original 
range */
     immutable(E)*[] viewPtrs;    /* Views into the cached elements */
     immutable(E)* endPtr;        /* One-past-the-end pointer of the 
cache */

     this(Range range)
     {
         this.range = range;
         this.endPtr = cache.ptr + cache.length;
     }

     /**
      * Register a new view (i.e. a ForwardRange) as a user of this cache
      */
     AsForwardRange!Range newView(immutable(E) * ptr)
     {
         viewPtrs ~= ptr;
         return new AsForwardRange!Range(this, viewPtrs.length - 1);
     }

     /**
      * Whether the specified view has seen all elements from the cache
      */
     bool viewIsEmpty(size_t id)
     {
         return viewPtrs[id] == endPtr;
     }

     /**
      * Whether the specified view is empty
      */
      property bool empty(size_t id)
     {
         return range.empty && viewIsEmpty(id);
     }

     /**
      * Ensure that the element at the specified address is read
      */
     void ensureRead(immutable(E) * ptr)
     in
     {
         /* Since we are iterating, only one more than what is actually 
read is
          * acceptable */
         assert(ptr <= endPtr);
     }
     body
     {
         if (ptr == endPtr) {
             immutable oldPtr = cache.ptr;
             cache ~= range.front.idup;
             range.popFront();

             if (cache.ptr != oldPtr) {
                 /* The cache has been relocated */
                 adjustPtrs(oldPtr, cache.ptr);

             } else {
                 ++endPtr;
             }
         }
     }

     /**
      * The front element of the specified view
      */
      property immutable(E) front(size_t id)
     {
         ensureRead(viewPtrs[id]);
         return *(viewPtrs[id]);
     }

     /**
      * Pop the front element of the specified view
      */
     void popFront(size_t id)
     {
         ++(viewPtrs[id]);
     }

     /**
      * Make a copy of the specified view
      */
     AsForwardRange!Range save(size_t id)
     {
         return newView(viewPtrs[id]);
     }

     /**
      * Adjust the view pointers into the newly relocated cache
      */
     void adjustPtrs(immutable(E) * oldPtr,
                     immutable(E) * newPtr)
     {
         endPtr = cache.ptr + cache.length;
         immutable(E) * minPtr = endPtr;

         foreach (i, ref ptr; viewPtrs) {
             ptr = newPtr + (ptr - oldPtr);
             if (ptr < minPtr) {
                 minPtr = ptr;
             }
         }

         /* Nobody is using the first part of the cache anymore */
         cache = cache[(minPtr - cache.ptr) .. $];
     }
}

/**
  * A thin layer as a view (i.e. a ForwardRange) into the cached 
elements of a
  * ForwardRangeLazyCache
  */
class AsForwardRange(Range)
{
     ForwardRangeLazyCache!Range lazyCache;
     size_t myId;

     this(ForwardRangeLazyCache!Range lazyCache, size_t myId)
     {
         this.lazyCache = lazyCache;
         this.myId = myId;
     }

      property bool empty()
     {
         return lazyCache.empty(myId);
     }

      property auto front()
     {
         return lazyCache.front(myId);
     }

      property void popFront()
     {
         lazyCache.popFront(myId);
     }

     AsForwardRange!Range save()
     {
         return lazyCache.save(myId);
     }
}

/**
  * A convenience function to make a new view (i.e. a ForwardRange) from an
  * InputRange
  */
AsForwardRange!Range asForwardRange(Range)(Range range)
{
     auto lazyCache = new ForwardRangeLazyCache!Range(range);
     return lazyCache.newView(lazyCache.cache.ptr);
}

// --- 8<---------- The test program ---------- >8 ---

import std.stdio;
import std.range;
import std.random;
import as_forward_range;
import std.conv;

void main()
{
     auto range = asForwardRange(stdin.byLine());

     typeof(range)[] ranges;
     ranges ~= range;

     File[] files;
     files ~= File("/tmp/deleteme_" ~ to!string(files.length), "w");

     string line;

     while (!allIsDone(ranges)) {
         if ((ranges.length < 10) && (uniform(0, ranges.length) == 0)) {
             ranges ~= ranges.front.save();
             files ~= File("/tmp/deleteme_" ~ to!string(files.length), "w");
         }

         foreach (i, r; ranges) {
             if (!r.empty) {
                 auto popCount = uniform(1, 10);

                 for ( ; popCount && !r.empty; --popCount, r.popFront) {
//                     files[i].writeln(r.front);
                     line = r.front;
                 }
             }
         }
     }
}

bool allIsDone(Range)(Range[] ranges)
{
     foreach (range; ranges) {
         if (!range.empty) {
             return false;
         }
     }

     return true;
}

Ali
Dec 29 2010