Dado que su analizador funciona en línea a la vez, ni siquiera necesita usar attoparsec-iteratee. Me gustaría escribir esto como:
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 clave para entender este es el "enumeratee", que es simplemente el término iteratee para un convertidor de corriente. Toma un procesador de flujo (iteratee) de un tipo de flujo y lo convierte para trabajar con otro flujo. Ambos enumLinesBS
y mapStream
son enumerados.
Para asignar el analizador a través de múltiples líneas, mapStream
es suficiente:
i1 :: Iteratee [ByteString] IO (Iteratee [POut] IO [POut]
i1 = mapStream (A.parseOnly parser) stream2list
Los iteratees anidados sólo significa que este convierte un flujo de [ByteString]
a una corriente de [POut]
, y cuando el iteratee final (stream2list) es ejecutarlo devuelve esa secuencia como [POut]
. Así que ahora sólo tiene el equivalente de iteratee lines
para crear esa corriente de [ByteString]
, que es lo que hace enumLinesBS
:
i2 :: Iteratee ByteString IO (Iteratee [ByteString] IO (Iteratee [POut] m [POut])))
i2 = enumLinesBS $ mapStream f stream2list
Pero esta función es bastante difícil de manejar para usar debido a toda la anidación. Lo que realmente queremos es una forma de canalizar la producción directamente entre convertidores de flujo y, al final, simplificar todo a un solo iteratee. Para ello utilizamos joinI
, (><>)
y (><>)
:
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
que es equivalente a la forma en que lo escribí anteriormente, con e1
inline.
Sin embargo, todavía queda un elemento importante. Esta función simplemente devuelve los resultados de análisis en una lista. Por lo general, le gustaría hacer otra cosa, como combinar los resultados con un doblez.
editar: Data.Iteratee.ListLike.mapM_
suele ser útil para crear consumidores. En ese momento, cada elemento de la corriente es el resultado de análisis, por lo que si desea imprimir ellos se puede utilizar
consumeParse :: Iteratee [POut] IO()
consumeParse = I.mapM_ (either (\e -> return()) print)
processLines2 :: Iteratee ByteString IO()
processLines2 = joinI $ (enumLinesBS ><> I.mapStream (A.parseOnly parser)) consumeParse
Esto imprimirá sólo los análisis sintácticos de éxito. Podría informar fácilmente los errores a STDERR, o manejarlos de otras maneras también.
¡Respuesta asombrosa, ojalá pudiera revocarla dos veces! ¿Puedo pedir un ejemplo de cómo escribir a ese consumidor? Digamos que todo lo que quiero hacer es imprimir los resultados de análisis exitosos, si ese es un ejemplo simple. –
@shintoist: He agregado esto ahora. –
¡Perfecto!¡Gracias! –