-- {-# LANGUAGE RankNTypes #-} {-# LANGUAGE DataKinds, ExistentialQuantification, GADTs, KindSignatures #-} module Parser (parseCards) where import qualified Data.List.NonEmpty as NE import Text.Parsec import Types uncurry3 f (a, b, c) = f a b c parseCards :: String -> Either ParseError [Card] parseCards = parse pCards "failed when parsing cards" pCards = pCard `sepEndBy1` seperator pCard = uncurry3 MultipleChoice<$> try pMultChoice <|> uncurry MultipleAnswer <$> try pMultAnswer <|> uncurry OpenQuestion <$> try pOpen <|> uncurry Definition <$> pDef pHeader = do many eol char '#' spaces many notEOL pMultChoice = do header <- pHeader many eol choices <- pChoice `sepBy1` lookAhead (try choicePrefix) let (correct, incorrects) = makeMultipleChoice choices return (header, correct, incorrects) pChoice = do kind <- oneOf "*-" space text <- manyTill anyChar $ lookAhead (try (try choicePrefix <|> seperator)) return (kind, text) choicePrefix = string "- " <|> string "* " pMultAnswer = do header <- pHeader many eol options <- pOption `sepBy1` lookAhead (try (char '[')) return (header, NE.fromList options) pOption = do char '[' kind <- oneOf "*x " string "] " text <- manyTill anyChar $ lookAhead (try (seperator <|> string "[")) return $ makeOption kind text pOpen = do header <- pHeader many eol (pre, gap) <- pGap sentence <- pSentence return (header, P pre gap sentence) pSentence = try pPerforated <|> pNormal pPerforated = do (pre, gap) <- pGap Perforated pre gap <$> pSentence chars = escaped <|> anyChar escaped = char '\\' >> char '_' pGap = do pre <- manyTill chars $ lookAhead (try gappedSpecialChars) char '_' gaps <- manyTill (noneOf "_|") (lookAhead (try gappedSpecialChars)) `sepBy1` string "|" char '_' return (pre, NE.fromList gaps) gappedSpecialChars = seperator <|> string "|" <|> string "_" pNormal = do text <- manyTill (noneOf "_") $ lookAhead (try gappedSpecialChars) return (Normal text) pDef = do header <- pHeader many eol descr <- manyTill chars $ lookAhead (try seperator) return (header, descr) eol = try (string "\n\r") <|> try (string "\r\n") <|> string "\n" <|> string "\r" "end of line" seperator = string "---" notEOL = noneOf "\n\r" makeMultipleChoice :: [(Char, String)] -> (CorrectOption, [IncorrectOption]) makeMultipleChoice options = makeMultipleChoice' [] [] 0 options where makeMultipleChoice' [] _ _ [] = error ("multiple choice had no correct answer: \n" ++ show options) makeMultipleChoice' [c] ics _ [] = (c, reverse ics) makeMultipleChoice' _ _ _ [] = error ("multiple choice had multiple correct answers: \n" ++ show options) makeMultipleChoice' cs ics i (('-', text) : opts) = makeMultipleChoice' cs (IncorrectOption text : ics) (i+1) opts makeMultipleChoice' cs ics i (('*', text) : opts) = makeMultipleChoice' (CorrectOption i text : cs) ics (i+1) opts makeMultipleChoice' _ _ _ _ = error "impossible" makeOption :: Char -> String -> Option makeOption kind text | kind `elem` "*x" = Option Correct text | otherwise = Option Incorrect text