{-# LANGUAGE TemplateHaskell #-}
module AOC.Discover (
mkChallengeMap
, solutionList
, ChallengeMap
, ChallengeSpec(..)
, dayToInt
, solSpec
, charPart
) where
import AOC.Solver
import AOC.Util
import Advent
import Data.Bifunctor
import Data.Data
import Data.Finite
import Data.Map (Map)
import Data.Maybe
import Data.Traversable
import Data.Void
import Language.Haskell.Exts as E
import Language.Haskell.Names
import Language.Haskell.TH as TH
import Language.Haskell.TH.Syntax (TExp(..))
import Prelude
import System.Directory
import System.FilePath
import Text.Printf
import Text.Read (readMaybe)
import qualified Data.Map as M
import qualified Hpack.Config as H
import qualified Text.Megaparsec as P
import qualified Text.Megaparsec.Char as P
data ChallengeSpec = CS { _csDay :: Finite 25
, _csPart :: Part
}
deriving (Show, Eq, Ord)
type ChallengeMap = Map (Finite 25) (Map Part SomeSolution)
solSpec :: TH.Name -> ChallengeSpec
solSpec n = either error id $ do
(d0, p') <- case nameBase n of
'd':'a':'y':d1:d2:p:_ -> pure ([d1,d2], p)
_ -> Left "Function name doesn't fit naming convention."
d1 <- subtract 1 <$> maybeToEither "Could not parse day" (readMaybe d0)
d2 <- maybeToEither "Day out of range" (packFinite d1)
p <- maybeToEither "Could not parse part" . charPart $ p'
pure $ CS d2 p
type Parser = P.Parsec Void String
dayToInt :: Finite 25 -> Int
dayToInt = fromIntegral . (+ 1) . getFinite
solutionList :: FilePath -> Q (TExp [(Finite 25, (Part, SomeSolution))])
solutionList dir = TExp
. ListE
. map (unType . specExp)
<$> runIO (getChallengeSpecs dir)
mkChallengeMap :: [(Finite 25, (Part, SomeSolution))] -> ChallengeMap
mkChallengeMap = M.unionsWith M.union
. map (uncurry M.singleton . second (uncurry M.singleton))
specExp :: ChallengeSpec -> TExp (Finite 25, (Part, SomeSolution))
specExp s@(CS d p) = TExp $ TupE
[ LitE (IntegerL (getFinite d))
, TupE
[ ConE (partCon p)
, ConE 'MkSomeSol `AppE` VarE (mkName (specName s))
]
]
where
partCon Part1 = 'Part1
partCon Part2 = 'Part2
specName :: ChallengeSpec -> String
specName (CS d p) = printf "day%02d%c" (dayToInt d) (partChar p)
getChallengeSpecs
:: FilePath
-> IO [ChallengeSpec]
getChallengeSpecs dir = do
exts <- defaultExtensions
files <- listDirectory dir
parsed <- forM files $ \f -> do
let mode = defaultParseMode { extensions = exts
, fixities = Just []
, parseFilename = f
}
res <- parseFileWithMode mode (dir </> f)
case res of
ParseOk x -> pure x
ParseFailed l e -> fail $ printf "Failed parsing %s at %s: %s" f (show l) e
pure $ moduleSolutions parsed
defaultExtensions :: IO [E.Extension]
defaultExtensions = do
Right (H.DecodeResult{..}) <- H.readPackageConfig H.defaultDecodeOptions
Just H.Section{..} <- pure $ H.packageLibrary decodeResultPackage
pure $ parseExtension <$> sectionDefaultExtensions
moduleSolutions :: (Data l, Eq l) => [Module l] -> [ChallengeSpec]
moduleSolutions = (foldMap . foldMap) (maybeToList . isSolution)
. flip resolve M.empty
isSolution :: Symbol -> Maybe ChallengeSpec
isSolution s = do
Value _ (Ident _ n) <- pure s
Right c <- pure $ P.runParser challengeName "" n
pure c
challengeName :: Parser ChallengeSpec
challengeName = do
_ <- P.string "day"
dStr <- P.many P.numberChar
dInt <- maybe (fail "Failed parsing integer") (pure . subtract 1) $
readMaybe dStr
dFin <- maybe (fail $ "Day not in range: " ++ show dInt) pure $
packFinite dInt
c <- P.lowerChar
p <- maybe (fail $ printf "Part not parsed: %c" c) pure $
charPart c
pure $ CS dFin p
charPart :: Char -> Maybe Part
charPart 'a' = Just Part1
charPart 'b' = Just Part2
charPart _ = Nothing