Ya has usado streams en channel-chatter y te has deshecho de ellos en boatswains-bilge. Este ejercicio es tu primer stream personalizado: una clase que implementa el protocolo para que el mismo código with-input-stream / read1 / stream-read1 que funcionaba con un <string-reader> funcione con el tuyo.
Ya conoces TUPLE: child < parent … de dragons-descendants, una jerarquía de un solo padre en la que los slots descienden. Factor tiene una segunda forma de extender el grafo de clases: los mixins abiertos. Un MIXIN: m declara una clase sin slots a la que otras clases pueden unirse a posteriori con INSTANCE: c m. La pertenencia es de muchos a muchos: una clase puede ser instancia de tantos mixins como quiera.
El protocolo de streams usa dos mixins para marcar qué mitad de la interfaz implementa una clase:
INSTANCE: my-stream input-stream ! readable
INSTANCE: my-stream output-stream ! writable
Un stream puede ser uno, el otro o ambos. Los métodos genéricos de input-stream (o output-stream) despachan entonces a cualquier clase que se haya unido al mixin: así es como el read1, stream-contents, with-input-stream, etc. del entorno encuentran el camino hasta tu clase.
Los mixins son abiertos: puedes añadir un nuevo miembro después de que se declare el mixin, incluso en un vocabulario diferente. Ese es el contraste con TUPLE: < parent, donde la relación queda fijada cuando se declara la clase hija.
Los genéricos del protocolo viven en 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 devuelve el siguiente byte (o f al llegar al final del stream); stream-write1 escribe uno. stream-read-unsafe es la lectura por lotes de más bajo nivel que proporcionan la mayoría de los streams de entrada; stream-read se construye sobre ella y se encarga de la asignación. stream-element-type indica si el stream trabaja con bytes (+byte+) o con caracteres (+character+); cuando envuelves otro stream, la implementación más sencilla simplemente delega en el stream subyacente.
destructors
Los streams son desechables: un stream personalizado extiende disposable e implementa dispose*. Ese es el framework de boatswains-bilge, aplicado a una clase de stream:
TUPLE: my-stream < disposable underlying ;
: <my-stream> ( underlying -- s )
my-stream new-disposable swap >>underlying ;
M: my-stream dispose* underlying>> dispose ;
Una vez que eso está en su sitio, todos los combinadores with-… de channel-chatter funcionan en tu stream automáticamente.
Eres el telegrafista inalámbrico de un carguero. La señal llega por una línea ruidosa, entremezclada con el Morse que intentas escuchar: la maquinaria del barco, la estática del tiempo y alguna que otra conversación ajena. Para captar el mensaje, haces pasar el cable por un dispositivo de cinta que descarta todo salvo los símbolos Morse válidos (., - y el espacio que separa las letras).
Tu tarea es construir la cinta como un flujo de entrada personalizado de Factor.
Define una tupla tape que extienda disposable y tenga un slot, wire, para el flujo de entrada subyacente.
TUPLE: tape < disposable wire ;
Después, escribe el constructor <tape> ( wire -- tape ). Usa new-disposable (no new) para que el framework de destructores registre la cinta, y guarda el flujo subyacente en el slot wire.
input-stream
Declara que tape es un input-stream:
INSTANCE: tape input-stream
Eso hace que los combinadores predeterminados del protocolo se despachen a tu clase.
Implementa M: tape stream-read1 ( tape -- elt/f ). Lee bytes del cable subyacente hasta que te encuentres con uno de CHAR: ., CHAR: - o CHAR: \s (espacio). Devuelve ese byte. Si el cable devuelve f (fin del flujo), devuelve f también: la cinta ha terminado.
"x.y -ab cz" <string-reader> <tape>
[ stream-read1 ] [ stream-read1 ] [ stream-read1 ] tri
! => CHAR: . CHAR: - CHAR: \s
Implementa M: tape stream-element-type ( tape -- type ) delegando en el cable: la cinta no cambia el tipo de elementos que se leen.
Implementa M: tape dispose* ( tape -- ). El framework llama a tu método exactamente una vez por cada dispose; solo tienes que liberar el cable subyacente.
Con esas cinco piezas en su sitio, la cinta funciona como un flujo de entrada normal: with-input-stream, stream-read y las demás palabras del protocolo se despachan a tus métodos automáticamente.
Regístrate en Exercism para aprender y dominar Factor con 47 conceptos163 ejercicios y mentoría humana real, todo gratis.