Du hast Streams bereits in channel-chatter verwendet und sie in boatswains-bilge wieder freigegeben. Diese Übung ist dein erster eigener Stream: eine Klasse, die das Protokoll implementiert, sodass derselbe Code mit with-input-stream / read1 / stream-read1, der mit einem <string-reader> funktioniert hat, auch mit deinem funktioniert.
Du kennst TUPLE: child < parent … bereits aus dragons-descendants: eine Hierarchie mit einem einzigen Elternteil, in der Slots nach unten weitergegeben werden. Factor hat einen zweiten Weg, den Klassen-Graphen zu erweitern: offene Mixins. Ein MIXIN: m deklariert eine Klasse ohne Slots, der andere Klassen nachträglich mit INSTANCE: c m beitreten können. Die Zugehörigkeit ist m:n: Eine Klasse kann Instanz beliebig vieler Mixins sein.
Das Stream-Protokoll verwendet zwei Mixins, um zu markieren, welche Hälfte der Schnittstelle eine Klasse implementiert:
INSTANCE: my-stream input-stream ! readable
INSTANCE: my-stream output-stream ! writable
Ein Stream kann das eine, das andere oder beides sein. Generische Methoden auf input-stream (oder output-stream) leiten dann an jede Klasse weiter, die dem Mixin beigetreten ist. So finden die allgemein verfügbaren read1, stream-contents, with-input-stream usw. ihren Weg zu deiner Klasse.
Mixins sind offen: Du kannst noch nach der Deklaration des Mixins ein neues Mitglied hinzufügen, sogar in einem anderen Vokabular. Das ist der Unterschied zu TUPLE: < parent, wo die Beziehung feststeht, sobald die Kindklasse deklariert wird.
Die generischen Methoden des Protokolls liegen 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 gibt das nächste Byte zurück (oder f am Ende des Streams); stream-write1 schreibt eines. stream-read-unsafe ist das gebündelte Lesen auf niedrigerer Ebene, das die meisten Eingabe-Streams anbieten; stream-read baut darauf auf und kümmert sich um die Speicherzuweisung. stream-element-type gibt an, ob der Stream mit Bytes (+byte+) oder Zeichen (+character+) arbeitet. Wenn du einen anderen Stream umhüllst, delegiert die einfachste Implementierung einfach an den darunterliegenden.
destructors
Streams sind Disposables: Ein eigener Stream erweitert disposable und implementiert dispose*. Das ist das Framework aus boatswains-bilge, angewendet auf eine Stream-Klasse:
TUPLE: my-stream < disposable underlying ;
: <my-stream> ( underlying -- s )
my-stream new-disposable swap >>underlying ;
M: my-stream dispose* underlying>> dispose ;
Sobald das steht, funktionieren alle Kombinatoren with-… aus channel-chatter automatisch mit deinem Stream.
Du bist der Funktelegrafist auf einem Frachter. Das Signal kommt über eine verrauschte Leitung herein: Schiffsmaschinen, Wetterrauschen, gelegentlich fremdes Geplauder, alles durchsetzt mit dem echten Morsecode, auf den du horchst. Um die Nachricht aufzufangen, schickst du den Draht durch ein Bandgerät, das alles außer gültigen Morsesymbolen (., - und dem Leerzeichen, das die Buchstaben trennt) herausfiltert.
Deine Aufgabe ist es, das Band als eigenen Factor-Eingabestream zu bauen.
Definiere ein tape-Tupel, das disposable erweitert und einen Slot wire für den darunterliegenden Eingabestream enthält.
TUPLE: tape < disposable wire ;
Dann schreib den Konstruktor <tape> ( wire -- tape ). Verwende new-disposable (nicht new), damit das Destruktor-Framework das Band registriert, und speichere den darunterliegenden Stream im Slot wire.
Deklariere tape als input-stream:
INSTANCE: tape input-stream
Dadurch leiten die Standard-Kombinatoren des Protokolls an deine Klasse weiter.
Implementiere M: tape stream-read1 ( tape -- elt/f ). Lies Bytes vom darunterliegenden Draht, bis du auf CHAR: ., CHAR: - oder CHAR: \s (Leerzeichen) stößt. Gib dieses Byte zurück. Wenn der Draht f zurückgibt (Ende des Streams), gib ebenfalls f zurück, dann ist das Band fertig.
"x.y -ab cz" <string-reader> <tape>
[ stream-read1 ] [ stream-read1 ] [ stream-read1 ] tri
! => CHAR: . CHAR: - CHAR: \s
Implementiere M: tape stream-element-type ( tape -- type ), indem du an den Draht delegierst. Das Band ändert die Art der gelesenen Elemente nicht.
Implementiere M: tape dispose* ( tape -- ). Das Framework ruft deine Methode genau einmal pro dispose auf. Du musst nur den darunterliegenden Draht freigeben.
Wenn diese fünf Teile stehen, funktioniert das Band wie ein normaler Eingabestream: with-input-stream, stream-read und die übrigen Wörter des Protokolls leiten automatisch an deine Methoden weiter.
Melde dich bei Exercism an, um Factor mit 47 Konzepte163 Übungen und echtem menschlichen Mentoring zu lernen und zu meistern, alles kostenlos.