STATUS: PROPOSED, DISCUSSED, SOME IMPLEMENTATIONS
- [[Implementations/RingoJS|]], [[Implementations/common-node|]]
Second draft. See Update Notes below for changes from first draft
This document describes an interface for reading and writing stream of raw bytes, and classes built on top of it to provide string based reading and writing.
This specification refers to the ByteString and ByteArray classes defined in the Binary/B proposal, but it should be easy to adapt it to any definition of binary buffers or arrays.
Platforms implementing this specification must provide a top level io module. The io module must export the classes and interfaces described below.
The Stream class implements a I/O stream for reading and writing raw bytes.
Whether the Stream class provides a public constructor, and what the arguments of the constructor are, is not part of this specification. The process of creating actual Stream objects is implementation specific.
Note: the length and position properties are used to implement random access streams traditionally implemented using the tell()/seek()/truncate() protocol. The rationale for departing from that nomenclature is that this puts stream more in line with other core objects such as String and Array. Also, random access streams are not often used in day-to-day programming, so these properties may most commonly be used for in-memory stream implementations, where they should fit in nicely.
- length Number
- The length of the stream, in bytes. This property is only defined for seekable streams, and is only writable for streams that are both seekable and writable. Decreasing the length property of a stream causes the stream to be truncated. Increasing the length of a stream causes the stream to be extended. The content of the extended section of the stream are not defined. Accessing this property may throw an error if the underlying operation did not succeed.
- position Number
- The position within the stream at which the next read or write operation occurs, in bytes. This property is only defined for seekable streams. Setting it to a new value moves the stream's positon to the given byte offset. Accessing this property may throw an error if the underlying operation did not succeed.
- read([n Number]) ByteString
- Read up to n bytes from the stream, or until the end of the stream has been reached. In n is null, reads up to the block size of the underlying device, or up to 1024 bytes if the block size is not discernible. If n is not specified, this method always reads the full stream until its end is reached. An empty ByteString is returned when the end of the stream has been reached. Throws an error if the operation could not be completed.
- readInto(buffer ByteArray, [begin Number], [end Number]) Number
- Read bytes from the stream into the ByteArray buffer. This method does not increase the length of the ByteArray buffer. Returns the number of bytes read, or -1 if the end of the stream has been reached. Note: Could we safely return 0 on EOF?
- skip(n Number) Number
- Try to skip over n bytes in the stream. Throws an error if the operation could not be completed.
- write(b Binary, [begin Number], [end Number])
- Write bytes from b to the stream. If begin and end are specified, only the range starting at begin and ending before end are written. Throws an error if the contents of b couldn't be written to the stream.
- copy(target Stream)
- reads from this stream with
read(null), writing the results to the target stream and flushing, until the source has been exhausted. Throws a TODO error if the target closes before the source has been exhausted.
- readable() Boolean
- Returns true if the stream supports reading, false otherwise.
- writable() Boolean
- Returns true if the stream supports writing, false otherwise.
- seekable() Boolean
- Returns true if the stream supports the length and position properties, false otherwise. Note: we're not impelementing seek(), so should we find another term for this, too? 'positionable' comes to mind, but feels slightly awkward.
- closed() Boolean
- Returns true if the stream is closed, false otherwise.
- Flushes the bytes written to the stream to the underlying medium. Throws an error if the operation did not succeed.
- Closes the stream, freeing the resources it is holding. Throws an error if the operation did not succeed.
The TextStream class wraps a raw stream and exposes a similar interface as the Stream class, but its write() method takes a string as first arguments and its read() method returns a string. Additionally, it defines the properties and methods listed below.
- [new] TextStream(raw Stream, [options Object]) TextStream
- Build a TextStream around a raw byte stream. The options argument may contain the following properties:
- charset: a string containing the name of the encoding to use
- newline: a string containing the newline character sequence to use
- delimiter: a string containing the delimiter to use in print()
- A readonly property containing the raw byte stream wrapped by this text stream.
- A readonly property containing the buffer used internally by this text stream, if it is buffered. This property may be undefined.
- readLine() String
- Reads a line from the reader. If the end of the stream is reached before any data is gathered, returns an empty string. Otherwise, returns the line including the newline. Throws an error if the operation did not succeed.
- readLines() Array*String
- Returns an Array of Strings accumulated by calling readLine until an empty string turns up. Does not include the final empty string, and does include newline at the end of every line. Throws an error if the operation did not succeed.
- next() String throws StopIteration
- Returns the next line of input without the newline. Throws StopIteration if the end of stream is reached. Throws an error if the operation did not succeed.
- iterator() Iterator
- Returns the reader itself.
- writeLine(line String)
- Writes line followed by a newline. Throws an error if the operation did not succeed.
- writes a delimiter delimited array of values as Strings terminated with a newline, then flushes. Throws an error if the operation did not succeed.
- copy(target Stream)
- reads from this stream with
readLine(), writing the results to the target stream and flushing, until the source has been exhausted. Throws a TODO error if the target closes before the source has been exhausted.
- This proposal does not describe non-blocking I/O, nor does it describe buffered I/O. These may be introduced in a later revision of the spec, possibly in conjunction with a Socket API. Platforms are free to implement non-blocking and buffered I/O in their own specific way.
- Text streams need read() for read-all ~KrisKowal
- Daniel Friesen recommended read(Infinity) instead of read(null), sounds good to me. ~KrisKowal
Changes from First Draft
- Removed Memory based I/O section.
- I realized how easy it is to build a stream interface to the existing binary specification, so in the interest of a simple spec I think it's best to leave that part to implementors for now.
- Removed IOError section but added notes about which methods can throw errors.
- Introducing a new error class in a module may be tricky as error classes should be global and immutable to allow instanceof checks. Instead, defining what methods can throw errors under what circumstances seems more important at this stage.
- Removed readAll() method
- Folded the functionality into read() being called without argument.
- write() no longer returns the number of bytes written
- If the method terminates without throwing an exception it means that all bytes passed to it were written to the stream.