I am trying to parse F# type syntax. I started writing an [F]Parsec grammar and ran into problems, so I simplified the grammar down to this:
type ::= identifier | type -> type
identifier ::= [A-Za-z0-9.`]+
After running into problems with FParsec, I switched to Parsec, since I have a full chapter of a book dedicated to explaining it. My code for this grammar is
typeP = choice [identP, arrowP]
identP = do
id <- many1 (digit <|> letter <|> char '.' <|> char '`')
-- more complicated code here later
return id
arrowP = do
domain <- typeP
string "->"
range <- typeP
return $ "("++domain++" -> "++range++")"
run = parse (do t <- typeP
eof
return t) "F# type syntax"
The problem is that Parsec doesn't backtrack by default, so
> run "int"
Right "int"
-- works!
> run "int->int"
Left "F# type syntax"
unexpected "-"
expecting digit, letter, ".", "`" or end of input
-- doesn't work!
The first thing I tried was to reorder typeP:
typeP = choice [arrowP, identP]
But this just stack overflows because the grammar is left-recursive--typeP never gets to trying identP
because it keeps trying arrowP
over and over. Next I tried try
in various places, for example:
typeP = choice [try identP, arrowP]
But nothing I do seems to change the basic behaviours of (1) stack overflow or (2) non-recognition of "->" following an identifier.
My mistake is probably obvious to anybody who has successfully written a Parsec grammar. Can somebody point it out?
I think the problem is that, and I'm making an assumption for F# (because I don't know it), arrows are right associative. I'm not sure how precise the linked grammar is supposed to be, as I'm not well versed in different grammars. But if we can assume arrows are right associative that makes the problem easier.
So with that assumption we can trivially do:
identP = many1 (digit <|> letter <|> char '.' <|> char '`')
typeP = try arrowP <|> identP
arrowP = do
i <- identP
string "->"
t <- typeP
return $ "(" ++ i ++ " -> " ++ t ++ ")"
run = flip parse "F# type syntax" $ do
t <- typeP
eof
return t
So:
Haskell> run "int"
Right "int"
Haskell> run "int->int"
Right "(int -> int)"
Haskell> run "int->int->int->int"
Right "(int -> (int -> (int -> int)))"
Expanding further, what might be confusing you is that in that grammar it says type -> type, which means you could have an arrow on the left side. That's fine, but it needs to be in parentheses. Which helps, maybe seeing the following in action is helpful. It helped me.
typeP = try arrowP <|> parens typeP <|> identP
arrowP = do
i <- parens typeP <|> identP
string "->"
t <- typeP
return $ "(" ++ i ++ " -> " ++ t ++ ")"
parens p = between (char '(') (char ')') p
Now we can write arrows on the left or the right side of an arrow:
Haskell> run "int->int->int"
Right "(int -> (int -> int))"
Haskell> run "(int->int)->int"
Right "((int -> int) -> int)"