Execution order in Parsec computation

112 views Asked by At

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 the IO 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:

  1. material = do changed to material = try $ do
  2. many material changed to many 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
0

There are 0 answers