1

I would like the below code to return [LoadInt 1,LoadDub 2.5,LoadInt 3], but it fails after parsing [LoadInt 1,LoadDub 2] and facing .5,3. How do I make it so it must parse all the way to the comma for a parse to succeed, and an int parse on 2.5 is a fail?

import qualified Data.Attoparsec.ByteString.Char8 as A
import Data.Attoparsec.ByteString.Char8 (Parser)
import Data.ByteString.Char8 (pack)
import Data.Attoparsec.Combinator
import Control.Applicative ((*>),(<$>),(<|>))
data LoadNum = LoadInt Int | LoadDub Double deriving (Show)

someFunc :: IO ()
someFunc = putStrLn . show $ A.parseOnly (lnParser <* A.endOfInput) (pack testString)


testString :: String
testString = "1,2.5,3"

lnParser :: Parser [LoadNum]
lnParser = (sepBy1' (ld <* A.atEnd) (A.char ','))

double :: Parser Double
double = A.double

int :: Parser Int
int = A.signed A.decimal

ld :: Parser LoadNum
ld = ((LoadInt <$> int ) <|> (LoadDub <$> double))
Carbon
  • 3,828
  • 3
  • 24
  • 51
  • Is the best thing to do a 2 part parse here, first to a comma separated list, then on each element? Isn't that slow? – Carbon May 20 '19 at 18:08
  • I have this horrible solution but it makes me feel bad because it is bad `join $ sequence <$> (fmap . fmap) (A.parseOnly ld . pack) (A.parseOnly parseCommaList (pack "1,2,34.2"))` – Carbon May 20 '19 at 18:30
  • where `parseCommaList = sepBy1' (A.many1 (A.notChar ',')) (A.char ',')` – Carbon May 20 '19 at 18:30

1 Answers1

2

You could use a tiny bit of lookahead to decide whether you reached the end of a list element. So:

int :: Parser Int
int = do
    i <- A.signed A.decimal
    next <- A.peekChar
    case next of
        Nothing -> pure i
        Just ',' -> pure i
        _ -> fail "nah"
Daniel Wagner
  • 145,880
  • 9
  • 220
  • 380