garnet/haskell/Pre.hs

369 lines
11 KiB
Haskell
Raw Normal View History

2025-12-08 12:48:49 +00:00
{-# LANGUAGE PackageImports #-}
{-# LANGUAGE TypeFamilies #-}
2026-01-05 18:31:34 +00:00
{-# LANGUAGE UndecidableInstances #-}
2025-12-08 12:48:49 +00:00
module Pre (
module BasePrelude,
module Control.Applicative,
module Control.DeepSeq,
2025-12-08 12:48:49 +00:00
module Control.Monad,
module Control.Monad.Loops,
module Control.Monad.State,
module Data.Bifunctor,
module Data.Bool,
module Data.Char,
module Data.Foldable,
module Data.Foldable1,
2025-12-08 22:48:29 +00:00
module Data.Function,
2025-12-08 12:48:49 +00:00
module Data.Functor,
module Data.Functor.Compose,
2025-12-08 12:48:49 +00:00
module Data.List,
module Data.List.Extra,
module Data.List.NonEmpty,
module Data.Maybe,
module Data.Ord,
module Data.Sequence,
module Data.Stream.Infinite,
module Data.Text,
module Data.Text.Encoding,
2025-12-08 16:34:30 +00:00
module Data.Traversable,
2026-01-06 18:54:40 +00:00
module Data.Tree,
2025-12-09 13:37:39 +00:00
module Data.Tuple.Extra,
2025-12-08 12:48:49 +00:00
module Data.Void,
module Data.Word,
module GHC.Generics,
2025-12-08 12:48:49 +00:00
module Linear,
2025-12-08 22:01:49 +00:00
module Safe,
2025-12-08 12:48:49 +00:00
module Text.Megaparsec,
module Text.Megaparsec.Char,
module Text.Megaparsec.Char.Lexer,
2026-01-06 19:44:50 +00:00
module Text.Pretty.Simple,
2025-12-08 12:48:49 +00:00
Puzzle (..),
2026-01-06 22:32:40 +00:00
mwhen,
2026-01-06 22:32:11 +00:00
(<<$>>),
(<<&>>),
takeUntil,
2025-12-08 23:38:48 +00:00
digit,
digitsToInt,
2025-12-09 00:02:54 +00:00
listIndex,
2025-12-09 10:47:32 +00:00
allUnorderedPairs,
2025-12-09 13:37:39 +00:00
adjacentPairs,
sortPair,
2026-01-05 14:37:25 +00:00
HList (..),
HListC (..),
2026-01-04 02:14:35 +00:00
HListF (..),
foldHListF,
foldHListF0,
2026-01-04 02:14:35 +00:00
mapHListF,
2026-01-05 18:31:34 +00:00
lookupHList,
(/\),
(/\\),
nil,
Constrained (..),
2026-01-06 17:36:37 +00:00
withConstrained,
2026-01-04 02:14:35 +00:00
Fanout (..),
2026-01-05 18:31:34 +00:00
Length,
TestTree,
test,
testLazy,
2026-01-05 14:37:25 +00:00
TestName,
mkTestName,
getTestTree,
2026-01-06 22:38:34 +00:00
displayTestResultsConsole,
2026-01-05 14:37:25 +00:00
runTests,
assertEqual,
assert,
golden,
2025-12-08 12:48:49 +00:00
)
where
import "base" Prelude as BasePrelude hiding (
foldl1,
foldr1,
head,
init,
last,
maximum,
minimum,
tail,
unzip,
2025-12-09 00:02:54 +00:00
(!!),
2025-12-08 12:48:49 +00:00
)
import Control.Applicative
import Control.DeepSeq (NFData, deepseq)
import Control.DeepSeq qualified as DeepSeq
import Control.Exception (SomeException, evaluate)
2025-12-08 12:48:49 +00:00
import Control.Monad
2026-01-05 14:37:25 +00:00
import Control.Monad.Catch (MonadCatch, try)
2025-12-08 12:48:49 +00:00
import Control.Monad.Loops
import Control.Monad.State
import Data.Bifunctor
import Data.Bool
import Data.Char
2026-01-05 18:31:34 +00:00
import Data.Finite
2026-01-06 22:38:34 +00:00
import Data.Fixed (Fixed (MkFixed))
2025-12-08 12:48:49 +00:00
import Data.Foldable hiding (foldl1, foldr1, maximum, maximumBy, minimum, minimumBy)
import Data.Foldable1
2025-12-08 22:48:29 +00:00
import Data.Function
2025-12-08 12:48:49 +00:00
import Data.Functor
import Data.Functor.Compose (Compose (Compose), getCompose)
2026-01-04 02:14:35 +00:00
import Data.Functor.Contravariant
import Data.Kind (Constraint, Type)
2026-01-06 22:38:34 +00:00
import Data.List (List, genericLength, sortOn, transpose)
2025-12-11 11:34:36 +00:00
import Data.List.Extra (dropEnd, enumerate, firstJust, notNull, splitOn)
2025-12-09 00:26:22 +00:00
import Data.List.NonEmpty (NonEmpty ((:|)), nonEmpty, some1, tail, tails)
2025-12-08 12:48:49 +00:00
import Data.Maybe
import Data.Ord
import Data.Sequence (Seq)
import Data.Stream.Infinite (Stream ((:>)))
2026-01-05 14:37:25 +00:00
import Data.String (IsString)
2025-12-08 12:48:49 +00:00
import Data.Text (Text)
import Data.Text qualified as T
2025-12-08 12:48:49 +00:00
import Data.Text.Encoding (encodeUtf8)
2026-01-05 14:37:25 +00:00
import Data.Text.IO qualified as T
2026-01-06 22:38:34 +00:00
import Data.Text.Lazy qualified as TL
import Data.Time
2025-12-08 16:34:30 +00:00
import Data.Traversable
2026-01-05 14:37:25 +00:00
import Data.Tree
2025-12-09 13:37:39 +00:00
import Data.Tuple.Extra ((&&&))
2025-12-08 12:48:49 +00:00
import Data.Void
import Data.Word
import GHC.Generics (Generic)
2026-01-05 18:31:34 +00:00
import GHC.TypeNats (KnownNat, Nat, type (+))
2025-12-08 12:48:49 +00:00
import Linear (V2 (..))
2025-12-08 22:01:49 +00:00
import Safe
2026-01-06 22:38:34 +00:00
import System.Console.ANSI
2025-12-08 12:48:49 +00:00
import Text.Megaparsec hiding (Pos, State, Stream, many, some)
import Text.Megaparsec.Char
import Text.Megaparsec.Char.Lexer (decimal)
2026-01-06 19:44:50 +00:00
import Text.Pretty.Simple (pPrint, pPrintForceColor, pShow)
2025-12-08 12:48:49 +00:00
data Puzzle = forall input outputs. (KnownNat (Length outputs), NFData input) => Puzzle
2025-12-08 12:48:49 +00:00
{ number :: Word
, parser :: Bool -> Parsec Void Text input
, parts :: PuzzleParts input outputs
, extraTests :: Bool -> FilePath -> [TestTree IO (input, HList outputs)]
2025-12-08 12:48:49 +00:00
}
2025-12-08 23:38:48 +00:00
2026-01-06 22:32:11 +00:00
(<<$>>) :: (Functor f1, Functor f2) => (a -> b) -> f1 (f2 a) -> f1 (f2 b)
(<<$>>) = fmap . fmap
(<<&>>) :: (Functor f1, Functor f2) => f1 (f2 a) -> (a -> b) -> f1 (f2 b)
(<<&>>) = flip (<<$>>)
2026-01-06 22:32:40 +00:00
mwhen :: (Monoid p) => Bool -> p -> p
mwhen b x = if b then x else mempty
2026-01-06 22:32:11 +00:00
takeUntil :: (Foldable t) => (a -> Bool) -> t a -> [a]
takeUntil p = foldr (\x xs -> x : if p x then [] else xs) []
2025-12-08 23:38:48 +00:00
digit :: (Token s ~ Char, Num b, MonadParsec e s f) => f b
digit = fromIntegral . digitToInt <$> digitChar
digitsToInt :: (Integral a) => [a] -> Int
digitsToInt = foldl' (\acc d -> acc * 10 + fromIntegral d) 0
2025-12-09 00:02:54 +00:00
listIndex :: Int -> [a] -> Maybe a
listIndex n =
if n < 0
then const Nothing
else \case
[] -> Nothing
x : xs -> if n == 0 then Just x else listIndex (n - 1) xs
2025-12-09 10:47:32 +00:00
allUnorderedPairs :: Bool -> [a] -> [(a, a)]
allUnorderedPairs diagonals = concat . join (zipWith (flip $ map . (,)) . (bool tail toList diagonals) . tails)
2025-12-09 13:37:39 +00:00
adjacentPairs :: [b] -> [(b, b)]
adjacentPairs = \case
[] -> []
x : xs -> zip (x : xs) xs
sortPair :: (Ord a) => (a, a) -> (a, a)
sortPair (a, b) = if a <= b then (a, b) else (b, a)
2025-12-09 16:47:12 +00:00
type PuzzleParts input = HListF ((Fanout ((->) input) (Op Text)))
infixr 9 /\\
(/\\) :: (input -> output, output -> Text) -> PuzzleParts input outputs -> PuzzleParts input (output : outputs)
(/\\) (f, o) = HConsF $ Fanout (f, Op o)
infixr 9 /\
(/\) :: (Show output) => (input -> output) -> PuzzleParts input outputs -> PuzzleParts input (output : outputs)
(/\) f = HConsF $ Fanout (f, Op T.show)
nil :: PuzzleParts input '[]
2026-01-04 02:14:35 +00:00
nil = HNilF
2026-01-05 14:37:25 +00:00
data HList (as :: List Type) :: Type where
HNil :: HList '[]
HCons ::
a ->
HList as ->
HList (a ': as)
data HListC (c :: Type -> Constraint) (as :: List Type) :: Type where
HNilC :: HListC c '[]
HConsC ::
(c a) =>
a ->
HListC c as ->
HListC c (a ': as)
instance NFData (HListC NFData outputs) where
rnf = \case
HNilC -> ()
HConsC x xs -> deepseq x $ DeepSeq.rnf xs
2026-01-04 02:14:35 +00:00
data HListF (f :: Type -> Type) (as :: List Type) :: Type where
HNilF :: HListF f '[]
HConsF ::
f a ->
HListF f as ->
HListF f (a ': as)
foldHListF :: (forall x xs. f x -> r xs -> r (x ': xs)) -> r '[] -> HListF f as -> r as
foldHListF f e = \case
HNilF -> e
HConsF x xs -> f x $ foldHListF f e xs
foldHListF0 :: (forall x. f x -> r -> r) -> r -> HListF f as -> r
foldHListF0 f e = \case
HNilF -> e
HConsF x xs -> f x $ foldHListF0 f e xs
2026-01-04 02:14:35 +00:00
mapHListF :: (forall a. f a -> g a) -> HListF f as -> HListF g as
mapHListF t = foldHListF (\x r -> HConsF (t x) $ r) HNilF
2026-01-04 02:14:35 +00:00
2026-01-05 18:31:34 +00:00
lookupHList :: (forall a. f a -> r) -> HListF f as -> Finite (Length as) -> r
lookupHList f = \case
HNilF -> absurd . separateZero
HConsF x xs -> maybe (f x) (lookupHList f xs) . unshift
data Constrained c a where
2026-01-06 17:36:37 +00:00
Constrained :: (c a) => a -> Constrained c a
2026-01-06 17:39:18 +00:00
withConstrained :: ((c a) => a -> b) -> Constrained c a -> b
withConstrained f (Constrained x) = f x
2026-01-04 02:14:35 +00:00
newtype Fanout f g a = Fanout (f a, g a)
2026-01-05 14:37:25 +00:00
2026-01-05 18:31:34 +00:00
type family Length as :: Nat where
Length '[] = 0
Length (x ': xs) = Length xs + 1
2026-01-05 14:37:25 +00:00
data TestTree m input where
TestTree :: TestName -> TestCase m input output -> [TestTree m output] -> TestTree m input
data TestCase m input output where
TestCase :: (NFData output) => (input -> m output) -> TestCase m input output
TestCaseLazy :: (input -> m output) -> TestCase m input output
-- | See `testLazy` for avoiding the `NFData` constraint.
test :: (NFData output) => TestName -> (input -> m output) -> [TestTree m output] -> TestTree m input
test n f = TestTree n $ TestCase f
{- | This is `test` without the `NFData` constraint.
It doesn't force the output before completion, which means that reported timings may be less accurate.
-}
testLazy :: TestName -> (input -> m output) -> [TestTree m output] -> TestTree m input
testLazy n f = TestTree n $ TestCaseLazy f
2026-01-05 14:37:25 +00:00
data TestResult
= Pass TestName NominalDiffTime [TestResult]
2026-01-05 14:37:25 +00:00
| Fail TestName SomeExceptionLegalShow
deriving (Show)
newtype TestName = TestName String
deriving newtype (IsString, Show)
mkTestName :: String -> TestName
mkTestName = TestName
newtype SomeExceptionLegalShow = SomeExceptionLegalShow SomeException
instance Show SomeExceptionLegalShow where
show (SomeExceptionLegalShow e) = show $ show e
getTestTree :: TestTree m r -> Tree TestName
getTestTree (TestTree name _ ts) = Node name $ map getTestTree ts
2026-01-06 22:38:34 +00:00
displayTestResultsConsole :: TestResult -> TL.Text
displayTestResultsConsole testResult =
displayResult 0 testResult <> TL.pack (setSGRCode [Reset])
where
displayResult indent =
(TL.replicate indent " " <>) . \case
Pass (TestName name) dt children ->
TL.fromStrict (header Green '✓' name (Just dt))
<> TL.concat (map (displayResult (indent + 1)) children)
Fail (TestName name) (SomeExceptionLegalShow e) ->
TL.fromStrict
( header Red '✗' name Nothing
<> setColour Vivid Red
)
<> TL.show e
<> "\n"
header colour icon name time =
setColour Vivid colour
<> T.singleton icon
<> " "
<> setColour Dull White
<> T.pack name
<> maybe
mempty
( \_t@(showTime -> tt) ->
" "
<> setColour Dull Black
<> tt
)
time
<> "\n"
showTime (nominalDiffTimeToSeconds -> MkFixed duration) =
-- SI prefixes, and always exactly 2 decimal places, or 3 if there's no prefix
T.show res
<> T.singleton '.'
<> T.take (if isNothing unit then 3 else 2) (T.show frac <> "000")
<> case unit of
Nothing -> setColour Dull Red
Just (u, h) -> setColour Dull h <> T.singleton u
<> T.singleton 's'
where
(frac, res, unit) = case duration of
0 -> (0, 0, Nothing)
d -> go (0 :: Int) 0 d
go = \case
4 -> (,,Nothing)
iterations -> \carried n ->
case n `divMod` 1000 of
(0, r) ->
( carried
, r
, Just case iterations of
3 -> ('m', Yellow)
2 -> ('μ', Green)
1 -> ('n', Green)
_ -> ('p', Green)
)
(d, r) -> go (succ iterations) r d
sgr = T.pack . setSGRCode
setColour d c = sgr [SetColor Foreground d c]
runTests :: (MonadIO m, MonadCatch m) => a -> TestTree m a -> m TestResult
runTests r0 (TestTree name tc ts) =
Control.Monad.Catch.try (runTest tc) >>= \case
2026-01-05 14:37:25 +00:00
Left e ->
pure $ Fail name $ SomeExceptionLegalShow e
Right (r, dt) ->
Pass name dt <$> for ts (runTests r)
where
runTest = \case
TestCase f -> timed (liftIO . evaluate . DeepSeq.force) $ f r0
TestCaseLazy f -> timed pure $ f r0
timed f x = do
t0 <- liftIO getCurrentTime
r <- x
rf <- f r
t1 <- liftIO getCurrentTime
pure (rf, diffUTCTime t1 t0)
2026-01-05 14:37:25 +00:00
assertEqual :: (Eq p, MonadFail f) => p -> p -> f ()
assertEqual expected actual = assert "not equal" (expected == actual)
assert :: (MonadFail f) => String -> Bool -> f ()
assert s b = if b then pure () else fail s
golden :: FilePath -> Text -> IO ()
golden p x = do
expected <- T.readFile p
if expected == x then pure () else fail "golden test failure"