On Fri, 15 Oct 2010 00:37:55 -0400, Andrei Alexandrescu <[email protected]> wrote:

On 10/14/10 21:58 CDT, Steven Schveighoffer wrote:
On Thu, 14 Oct 2010 16:47:13 -0400, Steven Schveighoffer
<[email protected]> wrote:

On Thu, 14 Oct 2010 14:43:56 -0400, Andrei Alexandrescu

How? Denis' implementation has two copies in the mix. (I'm not
counting .dup etc.) Anyhow, let's do this - write down your
interfaces so I can comment on them. We talk "oh that's a buffering
interface" and "that requires buffering" and "that's an extra copy"
and so on but we have little concrete contenders. I put my cards on
the table, you put yours.

I'll see if I can put something together.

Here's a rough outline:

Thanks!

enum Anchor
{
Begin,
Current,
End
}

interface Seek
{
ulong seek(long delta, Anchor whence);
final ulong tell() { return seek(0, Anchor.Current); }
bool seekable(); // define as false if seeking is not supported, true if it
// is supported (this doesn't necessarily mean a seek will
// succeed).
}

So far so good.

interface InputTransport : Seek
{
size_t read(ubyte[] data); // returns 0 on EOF.
}

No way to check for end of stream except by reading some of it?

This is often the only way in the low level interface, and since we have no buffer at this point, yes, it's required to read. How do you implement EOF without a buffer to hold the data you tried to read to see if you were at EOF?

It might be feasible to ask for EOF on the buffered version, but I still think it's not necessary.

// defined to implement either a D buffered object or wrap a FILE *.
//
interface BufferedInputTransport : Seek
{
size_t read(ubyte[] data); // returns 0 on EOF.

Since this method has the same sig, why doesn't BufferedInputTransport inherit InputTransport?

I thought of that, but then a buffered input class could accept a buffered input transport interface as its low-level implementation, so then you have unnecessarily double-buffered streams.


// read data into the buffer until the delegate returns other than ~0
//
// The delegate is passed the entire buffer so far, with the start of the // new data just read. It returns other than ~0 when it determines the end
// of the data in question.
//
ubyte[] readUntil(uint delegate(ubyte[] data, uint start) process);

How does the delegate say "you know what, I'm fine with the first 1000 bytes of the data; please take the rest of 1048 back"? Is that the result of the delegate? The process feels a bit odd.

You are using the internal buffer, no copying is necessary. So all that happens is the read position is moved up 1000 bytes and the first 1000 bytes is returned.


// same as readUntil except append to the given arr, Any excess
// data will be pushed into the internal buffer.
//
size_t appendUntil(uint delegate(ubyte[] data, uint start) process, ref
ubyte[] arr)

So indeed the delegate seems to return the length it wants to keep? And the rest would be copied back into the stream's internal buffers? I'm not sure I understand this API.

Yes, this involves a copy of the data you aren't interested in, but how else could you do it? You can't know "hey this data is not going to satisfy the condition, so I'll preemptively read it into the buffer instead".


// various buffer functions.
@property size_t bufsize();
@property size_t readable();
// etc.

Can one set bufsize?

Probably in the etc. functions ;) I left that up in the air, because I haven't given full thought to a buffer implementation.


}

The way I see it working is, there are two implementations for
BufferedInputTransport: FILEInputTransport and DBufferInputTransport.
There are numerous implementations of InputTransport, each of which can
be passed to the DBufferInputTransport, which uses its own buffer
implementation. For example, a network socket, file, inter-thread
stream, an array, etc.

This way, you can play nice with C's stdio when necessary (i.e. for
stdin/stdout/stderr) and avoid the FILE limitations and performance
issues otherwise.

I'm a bit unclear on the delegate stuff, but it's promising because it could be quite flexible. But I wouldn't want to aggravate the users with an API that's difficult to use. Could you please give a few examples using delegates that implement common patterns - e.g. readline and readDelim?

Sure, readline is probably easiest. Note, I'll assume for this example that \n signifies a line, windows would be slightly more difficult but doesn't really improve the example clarity, just assume it can also be done:

char[] readline(BufferedInputTransport trans, bool makeCopy=false)
{
   uint checkForNL(ubyte[] data, uint start)
   {
      char[] d = cast(char[])data; // need to switch to utf8
      foreach(i, dchar d; d[start..$])
      {
         if(d == '\n')
            return i + 1 + start; // consume including the newline
      }
      return ~0;
   }

   ubyte[] result;
   if(makeCopy)
       trans.read(&checkForNL, result);
   else
       result = trans.read(&checkForNL);

   auto cresult = cast(char[])result;

   // don't include any newline read
   if(cresult[$-1] == '\n')
      cresult = cresult[0..$-1];
   return result;
}

If you specify makeCopy is true, the resulting data is unique and can be used wherever. Otherwise, the resulting data is actually the buffer of the BufferedInputTransport stream, and shouldn't be saved as it may be reused.

One can easily make a range based on this as well.

-Steve

Reply via email to