Search code examples
parsinghaskellparsec

Problems with Haskell's Parsec <|> operator


I am new to both Haskell and Parsec. In an effort to learn more about the language and that library in particular I am trying to create a parser that can parse Lua saved variable files. In these files variables can take the following forms:

varname = value

varname = {value, value,...}

varname = {{value, value},{value,value,...}}

I've created parsers for each of these types but when I string them together with the choice <|> operator I get a type error.

Couldn't match expected type `[Char]' against inferred type `Char'
  Expected type: GenParser Char st [[[Char]]]
  Inferred type: GenParser Char st [[Char]]
In the first argument of `try', namely `lList'
In the first argument of `(<|>)', namely `try lList'

My assumption is (although I can't find it in the documentation) that each parser passed to the choice operator must return the same type. Here's the code in question:

data Variable = LuaString ([Char], [Char])
          | LuaList ([Char], [[Char]])
          | NestedLuaList ([Char], [[[Char]]])
          deriving (Show)

main:: IO()
main = do
       case (parse varName "" "variable = {{1234,\"Josh\"},{123,222}}") of
            Left err -> print err
            Right xs -> print xs 

varName :: GenParser Char st Variable
varName = do{
        vName <- (many letter);
        eq <- string " = ";
        vCon <- try nestList
             <|> try lList 
             <|> varContent;
        return (vName, vCon)}

varContent :: GenParser Char st [Char]
varContent =  quotedString 
    <|> many1 letter
    <|> many1 digit

quotedString :: GenParser Char st [Char]
quotedString = do{
         s1 <- string "\""; 
         s2 <- varContent;
         s3 <- string "\"";
         return (s1++s2++s3)}

lList :: GenParser Char st [[Char]]
lList = between (string "{") (string "}") (sepBy varContent (string ","))

nestList :: GenParser Char st [[[Char]]]
nestList = between (string "{") (string "}") (sepBy lList (string ","))

Solution

  • That's correct.

    (<|>) :: (Alternative f) => f a -> f a -> f a
    

    Notice how both arguments are exactly the same type.

    I don't exactly understand your Variable data type. This is the way I would do it:

    data LuaValue = LuaString String | LuaList [LuaValue]
    data Binding = Binding String LuaValue
    

    This allows values to be arbitrarily nested, not just nested two levels deep like yours has. Then write:

    luaValue :: GenParser Char st LuaValue
    luaValue = (LuaString <$> identifier)
           <|> (LuaList <$> between (string "{") (string "}") (sepBy (string ",") luaValue))
    

    This is the parser for luaValue. Then you just need to write:

    binding :: GenParser Char st Binding
    content :: GenParser Char st [Binding]
    

    And you'll have it. Using a data type that accurately represents what is possible is important.