Domanda

Volevo solo imparare un po 'su iteratees, reimplement un semplice parser che ho realizzato, usando data.iteee e data.attopArsec.iteratee. Sono praticamente sconcertato però. Di seguito ho un semplice esempio che è in grado di analizzare uno riga da un file. Il mio parser legge una riga alla volta, quindi ho bisogno di un modo per nutrire le linee per iterare fino a quando non è finito. Ho letto tutto ciò che ho trovato a cercare su Google questo, ma molto materiale su Iterati/Enumerators è piuttosto avanzato. Questa è la parte del codice che conta:

-- There are more imports above.
import Data.Attoparsec.Iteratee
import Data.Iteratee (joinI, run)
import Data.Iteratee.IO (defaultBufSize, enumFile)

line :: Parser ByteString -- left the implementation out (it doesn't check for 
                             new line)

iter = parserToIteratee line

main = do
    p <- liftM head getArgs
    i <- enumFile defaultBufSize p $ iter
    i' <- run i
    print i'

Questo esempio analizzerà e stampa una riga da un file con più righe. La sceneggiatura originale ha mappato il parser su un elenco di spettacoli. Quindi vorrei fare la stessa cosa qui. ho trovato enumLinesIn Iteratoe, ma non posso per la vita di me capire come usarlo. Forse fraintengo il suo scopo?

È stato utile?

Soluzione

Dal momento che il parser funziona su una linea alla volta, non è nemmeno necessario usare AttopArsec-Iteratee. Scriverei questo come:

import Data.Iteratee as I
import Data.Iteratee.Char
import Data.Attoparsec as A

parser :: Parser ParseOutput
type POut = Either String ParseOutput

processLines :: Iteratee ByteString IO [POut]
processLines = joinI $ (enumLinesBS ><> I.mapStream (A.parseOnly parser)) stream2list

La chiave per comprenderlo è il "enumeratee", che è solo il termine iterato per un convertitore di flusso. Ci vuole un processore di flusso (iterato) di un tipo di flusso e lo converte per funzionare con un altro flusso. Tutti e due enumLinesBS e mapStream sono elencati.

Per mappare il parser su più righe, mapStream è sufficiente:

i1 :: Iteratee [ByteString] IO (Iteratee [POut] IO [POut]
i1 = mapStream (A.parseOnly parser) stream2list

Gli iterati nidificati significano solo che questo converte un flusso di [ByteString] a un flusso di [POut], e quando viene eseguito l'ultimo iterato (Stream2List), restituisce quel flusso come [POut]. Quindi ora hai solo bisogno dell'equivalente di iTratee di lines per creare quel flusso di [ByteString], che è cosa enumLinesBS fa:

i2 :: Iteratee ByteString IO (Iteratee [ByteString] IO (Iteratee [POut] m [POut])))
i2 = enumLinesBS $ mapStream f stream2list

Ma questa funzione è piuttosto ingombrante da usare a causa di tutta la nidificazione. Ciò che vogliamo veramente è un modo per il tubo di output direttamente tra i convertitori di flusso e alla fine semplificare tutto a un singolo Iterato. Per fare questo che usiamo joinI, (><>), e (><>):

e1 :: Iteratee [POut] IO a -> Iteratee ByteString IO (Iteratee [POut] IO a)
e1 = enumLinesBS ><> mapStream (A.parseOnly parser)

i' :: Iteratee ByteString IO [POut]
i' = joinI $ e1 stream2list

che equivale a come l'ho scritto sopra, con e1 inlineato.

Rimango ancora un elemento importante. Questa funzione restituisce semplicemente i risultati di analisi in un elenco. In genere vorresti fare qualcos'altro, come combinare i risultati con una piega.

modificare: Data.Iteratee.ListLike.mapM_ è spesso utile per creare consumatori. A quel punto ogni elemento del flusso è un risultato di analisi, quindi se si desidera stamparli puoi usare

consumeParse :: Iteratee [POut] IO ()
consumeParse = I.mapM_ (either (\e -> return ()) print)

processLines2 :: Iteratee ByteString IO ()
processLines2 = joinI $ (enumLinesBS ><> I.mapStream (A.parseOnly parser)) consumeParse

Questo stamperà solo le analisi di successo. Potresti facilmente segnalare errori a Stderr o gestirli anche in altri modi.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top