I'm having trouble figuring out how to debug my parser written in Parsec. The most curious thing is the order of execution. I have the following code:
import Data.Text (pack)
import Debug.Trace
import Text.Parsec.Text (Parser)
import Text.Parsec
material :: Parser MTL
material = do
name <- many finishLine >> string "newmtl" >> whitespaces >> manyTill anyChar (try finishLine)
illumCmds <- parseIllum
texMapCmds <- parseTexMaps
let mtl = constructMaterial name illumCmds texMapCmds
traceShow mtl $ return ()
return mtl
parseMTL :: FilePath -> IO [MTL]
parseMTL filepath = do
s <- readFile filepath
case parse (many material) filepath (pack s) of
Left x -> error $ show x
Right x -> return x
loadMTL :: FilePath -> IO ()
loadMTL fp = do
putStrLn $ "Start loading MTL: " ++ show fp
parseMTL fp >>= print
putStrLn $ "Finished."
In my program, I'm calling loadMTL
on a file and I'm receiving the following error:
lobjview.exe: Text.ParserCombinators.Parsec.Prim.many:
combinator 'many' is applied to a parser that accepts an empty string.
So I figured that somewhere in my program there was an error and I was hoping that through the use of trace
I could narrow it down (wherein showing a thunk would force it to be evaluated and it would resolve all previous parsing up until the call to trace
). However, I'm getting some behavior that I don't quite understand. My output is as follows:
... other stuff ...
MTL { ... everything looks good here ... }
... other stuff ...
Loading MTL: filepath.mtl
lobjview.exe: Text.ParserCombinators.Parsec.Prim.many:
combinator 'many' is applied to a parser that accepts an empty string.
So my first question is this:
- Why does my
trace
show prior to the output from theIO
monad?
I decided to profile my code and run it using +RTS -xc
to see a stack trace of where this error was coming from. Best guess is this:
Text.Parsec.Prim.CAF
--> evaluated by: Lambency.Loaders.MTLLoader.whitespace,
called from Lambency.Loaders.MTLLoader.finishLine.comment,
called from Lambency.Loaders.MTLLoader.finishLine.endMarker,
called from Lambency.Loaders.MTLLoader.finishLine,
That corresponds to this code:
whitespace :: Parser Char
whitespace = tab <|> char ' '
whitespaces :: Parser [Char]
whitespaces = many whitespace
finishLine :: Parser ()
finishLine = many (whitespace <|> comment) >> endMarker >> return ()
where
endMarker = endOfLine
comment :: Parser Char
comment = char '#' >> manyTill anyChar (try $ lookAhead endMarker) >> return '_'
So my second question is this:
- Is there any way that this code can be causing the error that I'm getting? I don't see how.
EDIT
The link to my code can be found here:
https://github.com/Mokosha/Lambency/blob/MTLLoader/lib/Lambency/Loaders/MTLLoader.hs#L555
This code runs as I'd expect it to, but I arrived at this solution mostly through guesswork. I'd still like answers to my original questions. The main differences between this code and the code in the question are:
material = do
changed tomaterial = try $ do
many material
changed tomany material >>= (\x -> many finishLine >> eof >> return x)
Perhaps this can shed some light on what was causing the problems in the first place. An example of a data file parsed by this code is as follows:
# Default material file. Created by Morgan McGuire and released into
# the Public Domain on July 16, 2011.
#
# http://graphics.cs.williams.edu/data
newmtl default
Ns 10.0000
Ni 1.5000
Tr 0 0
illum 2
Ka 1 1 1
Kd 1 1 1
Ks 0.2 0.2 0.2
Ke 0 0 0
map_Kd default.png