Terminada la corrección de la Tarea 2, que nuevamente podía resolverse mirando el código fuente de Control.Monad.Identity, resta aclarar que la identidad propuesta es cierta. A continuación una posible respuesta para la Pregunta 2. > import Control.Monad > import Text.ParserCombinators.Parsec > import System Un archivo .lhs tiene cero o más líneas de contenido, seguidas por el fin de archivo. En ese caso el reconocedor produce la lista de líneas reconocidas. > litfile = do > html <- many content > eof > return html Una línea de contenido puede ser un bloque de código, una línea de encabezado o un párrafo. Noten que los prefijos izquierdos de codeBlock ("> ") y headerLine ("*" o "#") son disjuntos, así que no necesito usar try. > content = codeBlock > <|> headerLine > <|> paragraph Un bloque de código tiene múltiples líneas de código continuas, y el reconocedor debe envolverlas en un bloque HTML , insertando saltos de línea entre ellas. > codeBlock = do > code <- many1 codeLine > return $ "\n" ++ unlines code ++ "\n" Una línea de código comienza por "> ", que ignoramos, y luego se extiende hasta el final de la línea para conservar espacios en blanco y cualquier caracter presente conviertiendo a entidades, excepto el final de línea. > codeLine = do > string "> " > content <- many entity > char '\n' > return $ concat content En todas las respuestas noté que (por falta de costumbre o por apuro) no factorizan código [1] sino que hacen "copy&paste" y cambian cosas puntuales. El típico caso era tener dos combinadores para reconocer los encabezados, y el combinador para los párrafos que son muy similares y lo único que cambia es el envoltorio. Al no factorizar, el mantenimiento se complica pues hay múltiples rutinas similares que deben estar en sincronía. Entonces, escribí una función auxiliar para "envolver" un contenido en marcas HTML concordantes, parametrizable y extensible con facilidad... > wrap t s = "<" ++ tag ++ ">" ++ s ++ "\n" > where tag = case t of > '*' -> "h1" > '#' -> "h2" > 'p' -> "p" ...y entonces puedo tener _un_ sólo combinador para procesar la línea de encabezados. La línea comienza por alguno de los símbolos válidos para el encabezado, luego ignoro cualquier cantidad de espacios que pueda haber y reconozco el resto de los caracteres hasta el final de la línea, consumiendo este último. El reconocedor envuelve la línea con la marca adecuada antes de retornarla. > headerLine = do > spaces > t <- oneOf "*#" > spaces > content <- many entity > char '\n' > return $ wrap t $ concat content Un párrafo es un conjunto de múltiples líneas que deben tener una línea en blanco inmediatamente después. Noten como, nuevamente, aprovecho mi función auxiliar para envolver el contenido con la marca adecuada. > paragraph = do > content <- many1 line > spaces > return $ wrap 'p' $ "\n" ++ unlines content Una línea de un párrafo debe tener al menos una entidad y un final de línea, que se consume. > line = do > content <- many1 cleanEntity > char '\n' > return $ concat content Una entidad es cualquier caracter, posiblemente convertido a su forma escape HTML. Una entidad "limpia" es aquella que colapsa varios espacios en blanco consecutivos por uno sólo. > cleanEntity = (many1 (oneOf " \t") >> return " ") > <|> entity > > entity = (char '>' >> return ">") > <|> (char '<' >> return "<") > <|> (char '&' >> return "&") > <|> (noneOf "\n\r" >>= \c -> return [c]) > " a character" El encabezado y pié de página mínimos para un documento HTML válido. La mayoría olvidó este detalle. > header t = "" ++ t ++ "\n\n" > footer = "\n\n" Procesar un archivo consiste en abrirlo para leer su contenido, crear o vaciar un nuevo archivo (pero con extensión .html) y luego utilizar el reconocedor para obtener la transformación al HTML, envolverla en el encabezado y pié de páginas. Noten como se usa mapM_ sobre la lista de líneas resultado del reconocedor, para escribirlas una a una en el archivo de salida. > run file = do > input <- readFile file > let filename = file ++ ".html" > writeFile filename "" > case parse litfile ("Procesando " ++ file) input of > Left e -> do > putStr "Error: " > print e > Right r -> do > appendFile filename (header filename) > mapM_ (appendFile filename) r > appendFile filename footer El programa principal es trivial > main = getArgs >>= mapM_ run Hay muchos 'concat' consecuencia de tener que limpiar los caracteres uno a uno. ¿Pueden escribir el parser usando menos concat?