Tu as déjà utilisé des flux dans channel-chatter et tu les as libérés dans boatswains-bilge. Cet exercice est ton premier flux personnalisé : une classe qui implémente le protocole, si bien que le même code with-input-stream / read1 / stream-read1 qui fonctionnait sur un <string-reader> fonctionne sur le tien.
Tu connais déjà TUPLE: child < parent … depuis dragons-descendants : une hiérarchie à parent unique où les slots descendent. Factor dispose d'une seconde façon d'étendre le graphe de classes : les mixins ouverts. Un MIXIN: m déclare une classe sans slots que d'autres classes peuvent rejoindre après coup avec INSTANCE: c m. L'appartenance est plusieurs à plusieurs : une classe peut être instance d'autant de mixins qu'elle le souhaite.
Le protocole des flux utilise deux mixins pour marquer quelle moitié de l'interface une classe implémente :
INSTANCE: my-stream input-stream ! readable
INSTANCE: my-stream output-stream ! writable
Un flux peut être l'un, l'autre, ou les deux. Les méthodes génériques de input-stream (ou output-stream) s'adressent alors à toute classe ayant rejoint le mixin : c'est ainsi que les read1, stream-contents, with-input-stream et autres génériques ambiants trouvent le chemin jusqu'à ta classe.
Les mixins sont ouverts : tu peux ajouter un nouveau membre après la déclaration du mixin, même dans un autre vocabulaire. C'est là toute la différence avec TUPLE: < parent, où la relation est figée au moment de la déclaration de la classe enfant.
Les génériques du protocole vivent dans 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 renvoie l'octet suivant (ou f à la fin du flux) ; stream-write1 en écrit un. stream-read-unsafe est la lecture par lots de plus bas niveau que fournissent la plupart des flux d'entrée ; stream-read s'appuie dessus et gère l'allocation. stream-element-type indique si le flux travaille en octets (+byte+) ou en caractères (+character+) ; quand tu enveloppes un autre flux, l'implémentation la plus simple délègue simplement au flux sous-jacent.
destructors
Les flux sont des ressources à libérer : un flux personnalisé étend disposable et implémente dispose*. C'est le framework de boatswains-bilge, appliqué à une classe de flux :
TUPLE: my-stream < disposable underlying ;
: <my-stream> ( underlying -- s )
my-stream new-disposable swap >>underlying ;
M: my-stream dispose* underlying>> dispose ;
Une fois cela en place, tous les combinateurs with-… de channel-chatter fonctionnent automatiquement sur ton flux.
Tu es le télégraphiste radio d'un cargo. Le signal arrive par une ligne bruitée (les machines du bord, les parasites météo, quelques conversations étrangères par-ci par-là), entremêlé au vrai Morse que tu cherches à capter. Pour capter le message, tu fais passer le fil dans un appareil à bande qui élimine tout sauf les symboles Morse valides (., - et l'espace qui sépare les lettres).
Ton travail consiste à construire la bande sous la forme d'un flux d'entrée Factor personnalisé.
Définis un tuple tape qui étend disposable et contient un seul emplacement, wire, pour le flux d'entrée sous-jacent.
TUPLE: tape < disposable wire ;
Écris ensuite le constructeur <tape> ( wire -- tape ). Utilise new-disposable (et non new) pour que l'infrastructure des destructeurs enregistre la bande, et stocke le flux sous-jacent dans l'emplacement wire.
Déclare tape comme un input-stream :
INSTANCE: tape input-stream
Ainsi, les combinateurs par défaut du protocole sont dirigés vers ta classe.
Implémente M: tape stream-read1 ( tape -- elt/f ). Lis les octets du fil sous-jacent jusqu'à tomber sur l'un de CHAR: ., CHAR: - ou CHAR: \s (un espace). Renvoie cet octet. Si le fil renvoie f (fin de flux), renvoie f toi aussi : la bande a fini son travail.
"x.y -ab cz" <string-reader> <tape>
[ stream-read1 ] [ stream-read1 ] [ stream-read1 ] tri
! => CHAR: . CHAR: - CHAR: \s
Implémente M: tape stream-element-type ( tape -- type ) en déléguant au fil : la bande ne change pas le type des éléments lus.
Implémente M: tape dispose* ( tape -- ). L'infrastructure appelle ta méthode exactement une fois par dispose ; il suffit de libérer le fil sous-jacent.
Une fois ces cinq éléments en place, la bande fonctionne comme un flux d'entrée ordinaire : with-input-stream, stream-read et les autres mots du protocole arrivent automatiquement à tes méthodes.
Inscris-toi sur Exercism pour apprendre et maîtriser Factor avec 47 concepts163 exercices, et un vrai mentorat humain, le tout gratuitement.