Hai già usato gli stream in channel-chatter e li hai smaltiti in boatswains-bilge. Questo esercizio è il primo stream personalizzato che scriverai: una classe che implementa il protocollo, così lo stesso codice with-input-stream / read1 / stream-read1 che funzionava su un <string-reader> funziona anche su quello che scrivi.
Conosci già TUPLE: child < parent … da dragons-descendants: una gerarchia a genitore singolo in cui gli slot scendono verso il basso. Factor ha un secondo modo per estendere il grafo delle classi: i mixin aperti. Un MIXIN: m dichiara una classe senza slot a cui altre classi possono unirsi in un secondo momento con INSTANCE: c m. L'appartenenza è molti-a-molti: una classe può essere istanza di quanti mixin vuole.
Il protocollo degli stream usa due mixin per indicare quale metà dell'interfaccia implementa una classe:
INSTANCE: my-stream input-stream ! readable
INSTANCE: my-stream output-stream ! writable
Uno stream può essere l'uno, l'altro o entrambi. I metodi generici su input-stream (o output-stream) vanno poi in dispatch verso qualunque classe che abbia aderito al mixin: è così che le funzioni ambientali come read1, stream-contents, with-input-stream e così via trovano la strada verso la classe che scrivi.
I mixin sono aperti: puoi aggiungere un nuovo membro dopo che il mixin è stato dichiarato, anche in un vocabolario diverso. È qui il contrasto con TUPLE: < parent, dove la relazione è fissata nel momento in cui si dichiara la classe figlia.
I generici del protocollo vivono 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 restituisce il byte successivo (o f alla fine dello stream); stream-write1 ne scrive uno. stream-read-unsafe è la lettura in blocco di livello più basso che la maggior parte degli stream di input fornisce; stream-read è costruita sopra di essa e gestisce l'allocazione. stream-element-type dice se lo stream tratta byte (+byte+) o caratteri (+character+); quando avvolgi un altro stream, l'implementazione più semplice delega semplicemente a quello sottostante.
destructors
Gli stream sono disposable: uno stream personalizzato estende disposable e implementa dispose*. È il framework di boatswains-bilge, applicato a una classe di stream:
TUPLE: my-stream < disposable underlying ;
: <my-stream> ( underlying -- s )
my-stream new-disposable swap >>underlying ;
M: my-stream dispose* underlying>> dispose ;
Una volta che tutto questo è a posto, tutti i combinatori with-… di channel-chatter funzionano automaticamente sul tuo stream.
Sei il telegrafista senza fili su un mercantile. Il segnale arriva
su una linea disturbata: rumori dei macchinari della nave, disturbi
atmosferici, ogni tanto chiacchiere in lingue straniere, frammisto
al Morse che stai cercando di ascoltare. Per catturare il messaggio
fai passare il filo attraverso un dispositivo a tape che elimina
tutto tranne i simboli Morse validi (., - e lo spazio che separa
le lettere).
Il tuo compito è costruire la tape come stream di input personalizzato in Factor.
Definisci una tupla tape che estende disposable e contiene un solo
slot, wire, per lo stream di input sottostante.
TUPLE: tape < disposable wire ;
Poi scrivi il costruttore <tape> ( wire -- tape ). Usa
new-disposable (non new) così il framework dei distruttori
registra la tape, e memorizza lo stream sottostante nello slot
wire.
Dichiara che tape è un input-stream:
INSTANCE: tape input-stream
In questo modo i combinatori predefiniti del protocollo vengono indirizzati alla tua classe.
Implementa M: tape stream-read1 ( tape -- elt/f ). Leggi i byte
dal wire sottostante finché non ne incontri uno tra CHAR: .,
CHAR: - o CHAR: \s (spazio). Restituisci quel byte. Se il wire
restituisce f (fine dello stream), restituisci f anche tu: la
tape ha finito.
"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 al wire: la tape non cambia il tipo di elementi che
vengono letti.
Implementa M: tape dispose* ( tape -- ). Il framework chiama il
tuo metodo esattamente una volta per ogni dispose: basta chiamare
dispose sul wire sottostante.
Con questi cinque pezzi a posto, la tape funziona come un normale
stream di input: with-input-stream, stream-read e tutte le
altre parole del protocollo vengono indirizzate automaticamente ai
tuoi metodi.
Iscriviti a Exercism per imparare e padroneggiare Factor con 47 concetti163 esercizi e il mentoring di persone reali, tutto gratis.