{-# OPTIONS_GHC -fno-warn-unused-do-bind #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE NoMonomorphismRestriction #-} module Parser where import Control.Applicative import Control.Monad.Fix import Control.Monad.RWS import Data.Char import Data.CharSet hiding (map) import Data.Maybe import qualified Data.Set as S import Lens.Micro.Platform import Parser.Types import Text.Parsec hiding (many) import Text.ParserCombinators.ReadP (readP_to_S) import Text.Read.Lex (lexChar) type Warning = String parseStr :: String -> Either ParseError ([Atom], [[Warning]]) parseStr = fmap (unzip . map normalizeAndWarn) . parse printfStr "" . lexChars where lexChars x = (`fix` x) $ \f s -> if Prelude.null s then [] else case readP_to_S lexChar s of ((c, rest):_) -> c : f rest [] -> error "malformed input" normalizeAndWarn :: Atom -> (Atom, [Warning]) normalizeAndWarn s@Str {} = (s, []) normalizeAndWarn (Arg f) = (Arg a, b) where (_, a, b) = runRWS (warnLength f >> go (spec f)) () f go c | c `elem` "aAeEfFgGxXo" = return () go c | c `elem` "cs?" = warnSign >> warnPrefix >> warnZero >> warnSpace go c | c `elem` "diu" = warnPrefix go 'p' = warnSign >> warnPrefix >> warnZero go _ = undefined warnFlag :: (Eq a, MonadWriter [String] m, MonadState FormatArg m) => Lens' FlagSet a -> a -> a -> Char -> m () warnFlag lens' bad good flagName = do oldVal <- use (flags_ . lens') when (oldVal == bad) $ do c <- use spec_ flags_ . lens' .= good tell ["`" ++ [flagName] ++ "` flag has no effect on `" ++ [c] ++ "` specifier"] warnSign = warnFlag signed_ True False '+' warnPrefix = warnFlag prefixed_ True False '#' warnSpace = warnFlag spaced_ True False ' ' warnZero = warnFlag adjustment_ (Just ZeroPadded) Nothing '0' phonyLengthSpec = S.fromList $ [(x, y) | x <- "diuoxX", y <- ["L"]] ++ [(x, y) | x <- "fFeEgGaA", y <- ["hh", "h", "l", "ll", "j", "z", "t"]] ++ [(x, y) | x <- "cs", y <- ["hh", "h", "ll", "j", "z", "t", "L"]] ++ map ((,) 'p') ["hh", "h", "l", "ll", "j", "z", "t", "L"] warnLength FormatArg {spec, lengthSpec = Just l} | (spec, show l) `S.member` phonyLengthSpec = tell [ "`" ++ show l ++ "` length modifier has no effect when combined with `" ++ [spec] ++ "` specifier" ] warnLength _ = return () flagSet :: CharSet flagSet = fromList "-+ #0" specSet :: CharSet specSet = fromList "diuoxXfFeEaAgGpcs?" lengthSpecifiers :: [(String, LengthSpecifier)] lengthSpecifiers = [ ("hh", DoubleH) , ("h", H) , ("ll", DoubleL) , ("l", L) , ("j", J) , ("z", Z) , ("t", T) , ("L", BigL) ] oneOfSet :: Stream s m Char => CharSet -> ParsecT s u m Char oneOfSet s = satisfy (`member` s) printfStr :: Stream s m Char => ParsecT s u m [Atom] printfStr = do atoms <- many $ choice [Str "%" <$ try (string "%%"), Arg <$> fmtArg, Str . return <$> noneOf "%"] return $ go atoms where go (Str s:Str s1:as) = go (Str (s ++ s1) : as) go (a:as) = a : go as go [] = [] fmtArg :: Stream s m Char => ParsecT s u m FormatArg fmtArg = do char '%' flags <- do fs <- many $ do c <- oneOfSet flagSet <?> "flag" pure $ case c of '-' -> FlagLJust '+' -> FlagSigned ' ' -> FlagSpaced '#' -> FlagPrefixed '0' -> FlagZeroPadded _ -> error "???" let flagSet' = S.fromList fs if S.size flagSet' < length fs then fail "Duplicate flags specified" else pure $ toFlagSet flagSet' width <- optionMaybe (choice [Given <$> nat, Need <$ char '*']) <?> "width" precision <- optionMaybe (do char '.' optionMaybe $ choice [Given <$> nat, Need <$ char '*']) <?> "precision" lengthSpec <- optionMaybe $ choice $ Prelude.map (\(a, b) -> b <$ string a) lengthSpecifiers spec <- oneOfSet specSet <?> "valid specifier" pure $ FormatArg flags width (fromMaybe (Given 0) <$> precision) spec lengthSpec where nat = do c <- many1 $ satisfy isDigit return (read c :: Integer)