AttoParsec ITeratee
-
28-10-2019 - |
Pregunta
Quería, solo para aprender un poco sobre iterate, reimplegar un analizador simple que hice, usando datos. Iteratee y data.attoparsec.iteratee. Sin embargo, estoy bastante perplejo. A continuación tengo un ejemplo simple que puede analizar una línea desde un archivo. Mi analizador lee una línea a la vez, por lo que necesito una forma de alimentar las líneas al iteradoe hasta que esté listo. He leído todo lo que he encontrado buscando en Google esto, pero gran parte del material sobre iterado/enumeradores está bastante avanzado. Esta es la parte del código que importa:
-- 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'
Este ejemplo analizará e imprimirá una línea desde un archivo con múltiples líneas. El script original mapeó el analizador en una lista de bytestrings. Así que me gustaría hacer lo mismo aquí. encontré enumLines
En iteradoe, pero no puedo por mi vida descubrir cómo usarlo. ¿Quizás malinterpré su propósito?
Solución
Dado que su analizador trabaja en una línea a la vez, ni siquiera necesita usar AttoParsec-iteratee. Escribiría 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 comprender esto es el "enumeratee", que es solo el término iteradoe para un convertidor de flujo. Se necesita un procesador de transmisión (iteratee) de un tipo de flujo y lo convierte para que funcione con otra transmisión. Ambas cosas enumLinesBS
y mapStream
son enumerados.
Para mapear su 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 iterados anidados solo significan que esto convierte una corriente de [ByteString]
a una corriente de [POut]
, y cuando se ejecuta el ITeratee final (Stream2List), devuelve esa transmisión como [POut]
. Así que ahora solo necesitas el equivalente de iteradoe de lines
para crear ese flujo de [ByteString]
, Que es que enumLinesBS
lo hace:
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 debido a toda la anidación. Lo que realmente queremos es una forma de obtener la salida directamente entre los convertidores de la corriente, y al final simplifica todo a un solo iteratee. Para hacer esto usamos 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 cómo lo escribí arriba, con e1
Entrada.
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 algo más, como combinar los resultados con un pliegue.
editar: Data.Iteratee.ListLike.mapM_
a menudo es útil para crear consumidores. En ese punto, cada elemento de la secuencia es un resultado de análisis, por lo que si desea imprimirlos, puede usar
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á solo los analizados exitosos. También puede informar fácilmente errores a Stderr o manejarlos de otras maneras.