I try to parse a command (create) with the ReadP standard lib. My command should start by the string create
, then contains at least one word/tag/due, and potentially an option. Here my actual expression:
createExpr :: ReadP [Arg]
createExpr = do
skipSpaces
cmd <- SetCmd <$> cmdAliasExpr ["create", "add"]
skipSpaces
rest <-
many1
$ (AddWord <$> wordExpr)
<|> (AddTag <$> addTagExpr)
<|> (SetDue <$> dueExpr)
<|> (AddOpt <$> optExpr)
skipSpaces
return $ cmd : rest
The problem is, if I call create
with just one option, it parses well. But it shouldn't, since I expect at least one word/tag/due. How can I express this?
In fact, I used the wrong operator. <++
, the local, exclusive, left-biased choice, fits better my needs. Once an expression is matched, it should not check for the other ones:
notAnOpt arg = case arg of
AddOpt _ -> False
_ -> True
createExpr :: ReadP [Arg]
createExpr = do
skipSpaces
cmd <- SetCmd <$> cmdAliasExpr ["create", "add"]
skipSpaces
rest <-
many1
$ (AddTag <$> addTagExpr)
<++ (SetDue <$> dueExpr)
<++ (AddOpt <$> optExpr)
<++ (AddWord <$> wordExpr)
skipSpaces
guard $ isJust $ find notAnOpt rest
return $ cmd : rest
A simple solution would be to use guard
from Control.Monad
.
Assuming a function like isOpt :: Arg -> Bool
which is along the lines of
isOpt :: Arg -> Bool
isOpt (AddOpt _) = True
isOpt _ = False
then your definition of createExpr
changes to
createExpr :: ReadP [Arg]
createExpr = do
skipSpaces
cmd <- SetCmd <$> cmdAliasExpr ["create", "add"]
skipSpaces
rest <-
many1
$ (AddWord <$> wordExpr)
<|> (AddTag <$> addTagExpr)
<|> (SetDue <$> dueExpr)
<|> (AddOpt <$> optExpr)
guard $ at_least_one_non_optional rest
skipSpaces
return $ cmd : rest
where at_least_one_non_optional = not . null . filter (not . isOpt)
guard
basically fails the parser when its argument is False
, and more generally it works with any Alternative
by returning empty
when the argument is False
.