You've already used streams in channel-chatter and disposed
of them in boatswains-bilge. This exercise is your first
custom stream — a class that implements the protocol so the
same with-input-stream / read1 / stream-read1 code that
worked on a <string-reader> works on yours.
You already know TUPLE: child < parent … from
dragons-descendants — a single-parent hierarchy where slots
flow down. Factor has a second way to extend the class graph:
open mixins. A MIXIN: m declares a class with no slots
that other classes can join after the fact with INSTANCE: c m.
Membership is many-to-many: a class can be an instance of as
many mixins as it likes.
The streams protocol uses two mixins to mark which half of the interface a class implements:
INSTANCE: my-stream input-stream ! readable
INSTANCE: my-stream output-stream ! writable
A stream can be one, the other, or both. Generic methods on
input-stream (or output-stream) then dispatch to any class
that has joined the mixin — that's how the ambient read1,
stream-contents, with-input-stream, etc. find their way to
your class.
Mixins are open: you can add a new member after the mixin is
declared, even in a different vocabulary. That's the contrast
with TUPLE: < parent, where the relationship is fixed when
the child class is declared.
The protocol generics live in io:
stream-read1 ( stream -- elt/f )
stream-read ( n stream -- seq/f )
stream-read-unsafe ( n buf stream -- count )
stream-write1 ( elt stream -- )
stream-write ( seq stream -- )
stream-flush ( stream -- )
stream-element-type ( stream -- type )
stream-read1 returns the next byte (or f at end-of-stream);
stream-write1 writes one. stream-read-unsafe is the
lower-level batch read most input streams provide;
stream-read is built on top of it and handles allocation.
stream-element-type says whether the stream deals in bytes
(+byte+) or characters (+character+); when you wrap another
stream, the simplest implementation just delegates to the
underlying one.
destructors
Streams are disposables: a custom stream extends disposable
and implements dispose*. That's the framework from
boatswains-bilge, applied to a stream class:
TUPLE: my-stream < disposable underlying ;
: <my-stream> ( underlying -- s )
my-stream new-disposable swap >>underlying ;
M: my-stream dispose* underlying>> dispose ;
Once that's in place, all the with-… combinators from
channel-chatter work on your stream automatically.
You're the wireless telegrapher on a freighter. The signal comes
in over a noisy line — ship's machinery, weather static, the
occasional foreign chatter — interleaved with the actual Morse
you're listening for. To catch the message you feed the wire
through a tape device that strips out everything except valid
Morse symbols (., -, and the space that separates letters).
Your job is to build the tape as a custom Factor input stream.
Define a tape tuple that extends disposable and holds one
slot, wire, for the underlying input stream.
TUPLE: tape < disposable wire ;
Then write the constructor <tape> ( wire -- tape ). Use
new-disposable (not new) so the destructor framework
registers the tape, and store the underlying stream in the
wire slot.
Declare tape to be an input-stream:
INSTANCE: tape input-stream
That makes the protocol's default combinators dispatch to your class.
Implement M: tape stream-read1 ( tape -- elt/f ). Read bytes
from the underlying wire until you hit one of CHAR: .,
CHAR: -, or CHAR: \s (space). Return that byte. If the wire
returns f (end-of-stream), return f too — the tape is done.
"x.y -ab cz" <string-reader> <tape>
[ stream-read1 ] [ stream-read1 ] [ stream-read1 ] tri
! => CHAR: . CHAR: - CHAR: \s
Implement M: tape stream-element-type ( tape -- type ) by
delegating to the wire — the tape doesn't change the kind of
elements being read.
Implement M: tape dispose* ( tape -- ). The framework calls
your method exactly once per dispose; just dispose the
underlying wire.
With those five pieces in place the tape works as a regular
input stream — with-input-stream, stream-read, and the
other protocol words all dispatch to your methods automatically.
Sign up to Exercism to learn and master Factor with 47 concepts163 exercises, and real human mentoring, all for free.