Search code examples
haskellparsec

Haskell - Parsec :: Parse spaces until string literal


I am currently trying to design a Parser in Haskell using Parsec. The syntax for declaring type should look something like this:

Fruit is a Apple

Types should also be able have parameters:

Fruit a b is a Apple

Where

  • Fruit has type Name
  • a b has type [Parameter]
  • Apple has type Value

The problem here is that my parser currently does not know when to stop parsing the parameters and start parsing the value.

The code is as follows:

newtype Name = Name String deriving (Show)
newtype Parameter = Parameter String deriving (Show)
newtype Value = Value String deriving (Show)

data TypeAssignment = TypeAssignment Name [Parameter] Value deriving (Show)

-- first variant using `sepBy`
typeAssigment :: Parser TypeAssignment
typeAssigment =
    TypeAssignment
    <$> name
    <*> (space *> parameter `sepBy` space)
    <*> (string "is a" *> value)

-- second variant using manyTill
typeAssigment2 :: Parser TypeAssignment
typeAssigment2 =
    TypeAssignment 
    <$> name
    <*> (space *> manyTill parameter (string "is a"))
    <*> value

name :: Parser Name
name = Name <$> word

parameter :: Parser Parameter
parameter = Parameter <$> word

value :: Parser Value
value = Value <$> word

word :: Parser String
word = (:) <$> letter <*> many (letter <|> digit)

I have tried parsing the parameters/value in the two ways I would know how to (once with sepBy and once with manyTill) both failed with the nearly the parse-error:

*EParser> parseTest typeAssigment "Fruit a b is a Apple"
parse error at (line 1, column 21):
unexpected end of input
expecting space or "is a"

*EParser> parseTest typeAssigment2 "Fruit a b is a Apple"
parse error at (line 1, column 8):
unexpected " "
expecting letter, digit or "is a"

Solution

  • The problem with typeAssignment1 is that "is" and "a" are perfectly valid parameter parses. So, the parameter parsing gobbles up the whole input until nothing is left, and then you get an error. In fact, if you look closely at that error you see this to be true: the parser is expecting either a space (for more parameters) or "is a" (the terminal of your whole parser).

    On the other hand, typeAssignment2 is really close, but it seems that you're not handling spaces properly. In order to parse many parameters, you need to parse all the spaces between those parameter, not just the first one.

    I think the following alternative should do the trick:

    typeAssigment3 :: Parser TypeAssignment
    typeAssigment3 =
        TypeAssignment 
        <$> name
        <*> manyTill (space *> parameter) (try $ string "is a")
        <*> (space *> value)