packages feed

twentyseven 0.0.0 → 1.0.0

raw patch · 15 files changed

+210/−173 lines, 15 filesdep +taggeddep +tastydep +tasty-hunitdep −Cabaldep −HUnit-Plusdep −cabal-test-quickcheckdep ~MonadRandomdep ~containersdep ~deepseq

Dependencies added: tagged, tasty, tasty-hunit, tasty-quickcheck

Dependencies removed: Cabal, HUnit-Plus, cabal-test-quickcheck

Dependency ranges changed: MonadRandom, containers, deepseq, directory, filepath, heap, monad-loops, mtl, newtype, optparse-applicative, primitive, ref-fd, template-haskell, time, transformers, vector

Files

+ CHANGELOG.md view
@@ -0,0 +1,7 @@+# Twentyseven
+
+## 1.0.0 - 2025-08-01
+
+- Update to GHC 9.12
+
+## 0.0.0 - 2016-03-16
README.md view
@@ -7,25 +7,20 @@  The main idea is to precompute, for every configuration, the number of moves required to put certain subsets of the 27 cubies composing the 3x3 Rubik's cube-in their right place and/or in the right orientation. This gives lower bounds+in their right place or in the right orientation. This gives lower bounds used for an A⋆-like search in the graph of scrambled cubes. ----+Two algorithms are available in Twentyseven: -By default, a suboptimal "two-phase" solver is used, as it runs rather quickly.-It currently solves 1000 random cubes (uniformly distributed) in about one-minute. The optimal solver is quite slow however, taking between five minutes-and two hours to solve a random cube (18 moves in average).+- the two-phase solver, which is suboptimal but fast: it solves 1000 random cubes (uniformly distributed) in one minute;+- the optimal solver, which is quite slow, taking between five minutes and two hours to solve a random cube (18 moves in average). -The solver must precompute a certain number of lookup tables, which can be-stored in files. These tables take fifteen seconds to compute and weigh 13MB-for the two-phase solver, compare that to about 8 hours and 2GB for the optimal-one!+The lookup tables for the two-phase solver take ten seconds to compute and weigh 13MB,+compare that to about four hours and 2GB for the optimal solver!+(Timed on a i7-10750H.) -You may check the produced files with the checksums in `ts-tables.sha256`.-A compressed archive `ts-tables.zip` (723MB) of all precomputed tables is-available in the branch `fetch-tables` via `git-lfs`. Unzip it in `$HOME/.27/`,-or wherever (see usage below).+A compressed archive `ts-tables.tar.gz` (720MB) of all precomputed tables is+available on Google Drive; see instructions at the bottom.  Usage summary -------------@@ -34,11 +29,14 @@  - For the first invocation, use `-p` to precompute nonexistent lookup tables,   otherwise an error is thrown when `twentyseven` tries to load them;-- `--strict` loads tables immediately, otherwise they are loaded "by need" (so-  you can also send it a cube to solve);+- `--strict` loads tables immediately, otherwise they are loaded "by need"; - `-d DIR` specifies the directory where the tables should be read and written   (default: `$HOME/.27/`). +For more options:++    twentyseven --help+ The input is read line by line.  Input format@@ -52,7 +50,7 @@    Output: a sequence of moves to unscramble it. -  Facelets are numbered in base 9. Faces `0,1,2,3,4,5` correspond to `U,L,F,R,B,D`.+  In the following figure, facelets are numbered in base 9. Faces `0,1,2,3,4,5` correspond to `U,L,F,R,B,D`.                    00 01 02                   03 04 05@@ -93,7 +91,7 @@  ### Initialization -    $ echo quit|twentyseven -p --strict+    $ twentyseven -p --strict < /dev/null  ### Example @@ -122,11 +120,10 @@  --- -Detail of current heuristics-----------------------------+Detail of heuristics+-------------------- -The distance estimations are based on cosets corresponding to the following-elements.+The distance estimations are based on the following projections of the cube state.  ### Two-phase @@ -135,8 +132,8 @@ - Corner Orientation × UD Slice - Edge Orientation × UD Slice -It is possible to store the actual distances to the goal set in phase 1 but-the current speed seems good enough for now.+It is possible to store the actual distances to the goal set in phase 1 to+make it even faster, but the two phase algorithm is fairly quick already.  #### Phase 2 @@ -148,3 +145,28 @@ - Corner Orientation × Edge Orientation   × XY Slice Permutation, for XY in {UD, LR, FB} - Corner Orientation × Corner Permutation++Download the tables+-------------------++If you can't wait four hours to precompute the optimal tables,+you can download them from Google Drive.+Pray to the Google gods that the URL still works.+There are checksums for the archive as well as the uncompressed files in `ts-tables.sha256`.++```sh+# 1. This command prints a Google Drive URL (drive.usercontent.google.com) where you can download ts-tables.tar.gz+sh print-ts-tables-url.sh twentyseven++# 2. Download ts-tables.tar.gz++# 3. The checksums should match+sha256sum ts-tables.tar.gz+tail -1 ts-tables.sha256++# 4. Unpack tables into ~/.27+mkdir ~/.27+mv ts-tables.tar.gz ~/.27+cd ~/.27+tar zxf ts-tables.tar.gz+```
exec-src/twentyseven.hs view
@@ -12,12 +12,10 @@ import Data.Time.Clock  import Data.Char-import Data.Monoid  import Numeric ( showFFloat )  import Options.Applicative hiding ( value )-import qualified Options.Applicative as Opt  import System.Exit import System.IO.Error@@ -25,35 +23,35 @@ type Solver = Cube -> Move  data Parameters = Parameters {-    verbose :: Bool,     solve :: Solver,-    tsPath :: Maybe FilePath,     precompute :: Bool,+    strict :: Bool,     overwrite :: Bool,     noFiles :: Bool,-    strict :: Bool,+    timed :: Bool,+    tsPath :: Maybe FilePath,     debug :: Bool   }  optparse :: Parser Parameters optparse = Parameters-  <$> switch ( long "verbose" <> short 'v'-        <> help "Print time taken to solve every cube" )-  <*> flag TwoPhase.solve Optimal.solve ( long "optimal"-        <> help "Use optimal solver (experimental)" )-  <*> (optional . strOption) ( long "ts-dir" <> short 'd'-        <> metavar "DIR"-        <> help "Location of precomputed tables" )+  <$> flag TwoPhase.solve Optimal.solve ( long "optimal"+        <> help "Use optimal solver (slow!)" )   <*> switch ( long "precompute" <> short 'p'         <> help "Precompute and store tables \                 \(do enable this at the first invocation)" )+  <*> switch ( long "strict"+        <> help "Force loading tables before doing anything else" )   <*> switch ( long "overwrite"         <> help "Recompute and overwrite tables even when they exist already" )   <*> switch ( long "no-files"         <> help "Do not read or write any files \                 \(recompute tables for this session)" )-  <*> switch ( long "strict"-        <> help "Force loading tables before doing anything else" )+  <*> switch ( long "timed" <> short 't'+        <> help "Print time taken to solve every cube" )+  <*> (optional . strOption) ( long "ts-dir" <> short 'd'+        <> metavar "DIR"+        <> help "Location of precomputed tables" )   <*> switch ( long "debug" )  main :: IO ()@@ -84,13 +82,16 @@   _ -> faceletList s p  -- A sequence of moves, e.g., "URF".+moveSequence :: String -> IO () moveSequence s = putStrLn $   case stringToMove s of     Left c -> "Unexpected '" ++ [c] ++ "'."     Right ms -> stringOfCubeColors . moveToCube . reduceMove $ ms +faceletList :: [Char] -> Parameters -> IO () faceletList = either (const . putStrLn) justSolve . readCube +readCube :: (Eq b, Show b) => [b] -> Either String Cube readCube s   = case colorFacelets'' s of       Nothing -> Left "Expected string of length 54 of a set of (any) 6 \@@ -111,21 +112,13 @@ justSolve c p = do   let solved = solve p c       solStr = moveToString solved-  flip vPutStrLn p . toString =<< clock (evaluate solved)+  time <- clock (evaluate (solved `seq` ()))+  when (timed p) (putStrLn (showFFloat (Just 2) time "s"))   if c <> moveToCube solved == iden   then putStrLn solStr   else fail $ "Incorrect solver: " ++ solStr-  where-    toString d = showFFloat (Just 2) d "s" -unlessQuiet' :: IO () -> Parameters -> IO ()-unlessQuiet' a = unlessQuiet (const a) ()---- Strict in its second argument-unlessQuiet :: (a -> IO ()) -> a -> Parameters -> IO ()-unlessQuiet f a p = evaluate a >> when (verbose p) (f a)--clock :: IO a -> IO Double+clock :: IO () -> IO Double clock a = do   t <- getCurrentTime   a@@ -133,12 +126,3 @@   return (diffTimeToSeconds (diffUTCTime t' t))   where     diffTimeToSeconds = fromRational . toRational--listSeq' :: [a] -> [a]-listSeq' s = s `listSeq` s--vPutStrLn :: String -> Parameters -> IO ()-vPutStrLn s = unlessQuiet putStrLn (listSeq' s)--vPutStr :: String -> Parameters -> IO ()-vPutStr s = unlessQuiet putStrLn (listSeq' s)
src/Data/Tuple/Template.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE TemplateHaskell, ViewPatterns #-}+{-# LANGUAGE CPP, TemplateHaskell, ViewPatterns #-} module Data.Tuple.Template where  import Control.Monad@@ -31,7 +31,11 @@     [typeD aas, consInlD, consD, splitInlD, splitD]   where     typeD aas@(a : as) =+#if MIN_VERSION_template_haskell(2, 15, 0)+      TySynInstD <$> tySynEqn Nothing (conT (mkName ":|") `appT` a `appT` tupleT as) (tupleT aas)+#else       TySynInstD (mkName ":|") <$> tySynEqn [a, tupleT as] (tupleT aas)+#endif     consInlD = pragInlD (mkName "|:|") Inline FunLike AllPhases     splitInlD = pragInlD (mkName "split") Inline FunLike AllPhases     consD = do
src/Data/Vector/HalfByte.hs view
@@ -4,7 +4,7 @@  {-# LANGUAGE FlexibleContexts, FlexibleInstances, GeneralizedNewtypeDeriving,     MagicHash, MultiParamTypeClasses, RankNTypes, ScopedTypeVariables,-    TypeFamilies #-}+    TypeFamilies, UndecidableInstances #-} module Data.Vector.HalfByte where  import Control.DeepSeq
src/Rubik/Cube/Cubie/Internal.hs view
@@ -11,7 +11,6 @@ import Data.Function ( on ) import Data.List import Data.Maybe-import Data.Monoid import qualified Data.Vector.Unboxed as U import qualified Data.Vector.Unboxed.Mutable as MU @@ -209,20 +208,28 @@  -- +instance Semigroup CornerPermu where+  CornerPermu b <> CornerPermu c = CornerPermu $ composeVector b c+ instance Monoid CornerPermu where   mempty = CornerPermu $ idVector numCorners-  mappend (CornerPermu b) (CornerPermu c) = CornerPermu $ composeVector b c+  mappend = (<>)  instance Group CornerPermu where   inverse (CornerPermu a) = CornerPermu $ inverseVector a ++instance Semigroup EdgePermu where+  EdgePermu b <> EdgePermu c = EdgePermu $ composeVector b c+ instance Monoid EdgePermu where   mempty = EdgePermu $ idVector numEdges-  mappend (EdgePermu b) (EdgePermu c) = EdgePermu $ composeVector b c+  mappend = (<>)  instance Group EdgePermu where   inverse (EdgePermu a) = EdgePermu $ inverseVector a + instance CubeAction CornerPermu where   cubeAction cp_ = (cp_ <>) . fromCube @@ -257,32 +264,36 @@  -- -instance Monoid Corner where-  mempty = Corner iden idCornerO-    where idCornerO = CornerOrien $ U.replicate numCorners 0--  mappend (Corner bp_ bo_)+instance Semigroup Corner where+  (<>)    (Corner bp_ bo_)         c@(Corner cp_ co_)     =      Corner dp_ do_     where dp_ = bp_ <>             cp_           do_ = bo_ `actionCorner` c +instance Monoid Corner where+  mempty = Corner iden idCornerO+    where idCornerO = CornerOrien $ U.replicate numCorners 0+  mappend = (<>)+ instance Group Corner where   inverse (Corner ap_  (CornerOrien ao))     =      Corner ap_' (CornerOrien ao')     where ap_'@(CornerPermu ap') = inverse ap_           ao'                    = U.map oInv . U.backpermute ao $ ap' -instance Monoid Edge where-  mempty = Edge iden idEdgeO-    where idEdgeO = EdgeOrien $ U.replicate numEdges 0--  mappend (Edge bp_ bo_)+instance Semigroup Edge where+  (<>)    (Edge bp_ bo_)         c@(Edge cp_ co_)     =      Edge dp_ do_     where dp_ = bp_ <>           cp_           do_ = bo_ `actionEdge` c +instance Monoid Edge where+  mempty = Edge iden idEdgeO+    where idEdgeO = EdgeOrien $ U.replicate numEdges 0+  mappend = (<>)+ instance Group Edge where   inverse (Edge ap_  (EdgeOrien ao))     =      Edge ap_' (EdgeOrien ao')@@ -291,9 +302,12 @@  -- +instance Semigroup Cube where+  Cube cA eA <> Cube cB eB = Cube (cA <> cB) (eA <> eB)+ instance Monoid Cube where   mempty = Cube iden iden-  mappend (Cube cA eA) (Cube cB eB) = Cube (cA <> cB) (eA <> eB)+  mappend = (<>)  instance Group Cube where   inverse (Cube c e) = Cube (inverse c) (inverse e)
src/Rubik/Cube/Facelet/Internal.hs view
@@ -20,9 +20,12 @@     fromFacelets :: Vector Int   } deriving (Eq, Show) +instance Semigroup Facelets where+  Facelets b <> Facelets c = Facelets $ composeVector b c+ instance Monoid Facelets where   mempty = Facelets $ idVector numFacelets-  mappend (Facelets b) (Facelets c) = Facelets $ composeVector b c+  mappend = (<>)  instance Group Facelets where   inverse (Facelets a) = Facelets $ inverseVector a
src/Rubik/Cube/Moves/Internal.hs view
@@ -16,7 +16,6 @@ import Data.Function ( on ) import Data.List import Data.Maybe-import Data.Monoid import qualified Data.Vector as V import qualified Data.Vector.Unboxed as U 
src/Rubik/IDA.hs view
@@ -23,15 +23,19 @@ type Result a l = Maybe [l] data SearchResult a l = Next !a | Found [l] | Stop +instance Ord a => Semigroup (SearchResult a l) where+  {-# INLINE (<>) #-}+  (<>) f@(Found _) _ = f+  (<>) _ f@(Found _) = f+  (<>) (Next a) (Next b) = Next (min a b)+  (<>) Stop x = x+  (<>) x Stop = x+ instance Ord a => Monoid (SearchResult a l) where   {-# INLINE mempty #-}   mempty = Stop   {-# INLINE mappend #-}-  mappend f@(Found _) _ = f-  mappend _ f@(Found _) = f-  mappend (Next a) (Next b) = Next (min a b)-  mappend Stop x = x-  mappend x Stop = x+  mappend = (<>)  -- | Depth-first search up to depth @bound@, -- and reduce results from the leaves.
src/Rubik/Misc.hs view
@@ -9,7 +9,6 @@ import Control.Applicative  import Data.Maybe-import Data.Monoid import Data.Proxy (Proxy(..)) import Data.List import qualified Data.Vector.Unboxed as U
src/Rubik/Solver/TwoPhase.hs view
@@ -10,7 +10,6 @@ import Rubik.Tables.Distances  import Data.Function ( on )-import Data.Monoid  {-# INLINE phase1Proj #-} phase1Proj
src/Rubik/Tables/Moves.hs view
@@ -10,7 +10,6 @@ import Data.Bifunctor import Data.Bits import Data.Maybe-import Data.Monoid import qualified Data.Vector as V import qualified Data.Vector.Unboxed as U import qualified Data.Vector.Storable.Allocated as S
− stack.yaml
@@ -1,8 +0,0 @@-flags: {}-packages:-  - '.'-extra-deps: []-  # These deps are for tests and the first one needs fixing anyway.-  #- HUnit-Plus-0.1.0-  #- cabal-test-quickcheck-0.1.6-resolver: lts-5.8
test/Test.hs view
@@ -1,33 +1,50 @@ {-# LANGUAGE LambdaCase, RecordWildCards, ScopedTypeVariables, ViewPatterns #-}-module Test where  import Rubik.Cube import Rubik.Cube.Facelet.Internal import Rubik.Cube.Cubie.Internal-import Rubik.Cube.Moves.Internal+import Rubik.Tables.Internal (setPrecompute) import Rubik.Tables.Moves import Rubik.Misc import Rubik.Symmetry  import Control.Applicative import Control.Monad-import Data.List import Data.List.Split (chunksOf) import Data.Maybe-import Data.Monoid+import Data.Proxy (Proxy(..))+import Data.Tagged (Tagged(..)) import qualified Data.Vector.Generic as G-import qualified Data.Vector.Primitive.Pinned as P-import Distribution.TestSuite-import Distribution.TestSuite.QuickCheck-import Test.HUnitPlus-import Test.QuickCheck+import qualified Data.Vector.Storable.Allocated as P+import Test.Tasty (TestTree, askOption, defaultIngredients, defaultMainWithIngredients, includingOptions, testGroup)+import Test.Tasty.Options (IsOption(..), OptionDescription(..), flagCLParser, safeReadBool)+import Test.Tasty.Runners (TestTree(SingleTest, TestGroup))+import Test.Tasty.HUnit+import Test.Tasty.QuickCheck import qualified Test.QuickCheck as Gen-import System.Environment +type Test = TestTree++main :: IO ()+main = do+  setPrecompute True+  defaultMainWithIngredients ingredients tests+  where+    ingredients = includingOptions [Option (Proxy :: Proxy EnableOptimal)] : defaultIngredients++newtype EnableOptimal = EnableOptimal Bool++instance IsOption EnableOptimal where+  defaultValue = EnableOptimal False+  parseValue = (fmap . fmap) EnableOptimal safeReadBool+  optionName = Tagged "enable-optimal"+  optionHelp = Tagged "Run tests involving optimal solver"+  optionCLParser = flagCLParser Nothing (EnableOptimal True)+ -- If the test suite receives some command line arguments, only tests whose -- fully qualified name has a prefix among them are run.-tests :: IO [Test]-tests = (filterTests . rename)+tests :: Test+tests = testGroup "twentyseven"   [ testGroup "Cube"     [ testGroup "Facelets"       [ testProperty "permutation-to-facelet" $@@ -149,7 +166,7 @@           move18 move18CornerOrien       , testMoveTables "move18EdgeOrien"           move18 move18EdgeOrien-      , testMoveTables "move18UDSlicePermu"+      , requiresOptimal $ testMoveTables "move18UDSlicePermu"           move18 move18UDSlicePermu       , testMoveTables "move18UDSlice"           move18 move18UDSlice@@ -160,13 +177,26 @@       ]     , testUDSlicePermu     , testFlipUDSlicePermu-    , testRawToSymFlipUDSlicePermu-    , testSymReprTable "srFUDSP"+    , requiresOptimal testRawToSymFlipUDSlicePermu+    , requiresOptimal $ testSymReprTable "srFUDSP"         reprFlipUDSlicePermu conjugateFlipUDSlicePermu-    , testMoveSymTables "msFUDSP" move18 move18SymFlipUDSlicePermu+    , requiresOptimal $ testMoveSymTables "msFUDSP" move18SymFlipUDSlicePermu     ]   ] +-- Argument must be SingleTest or TestGroup+requiresOptimal :: TestTree -> TestTree+requiresOptimal t =+  askOption $ \(EnableOptimal enable) ->+      if enable then+        t+      else+        let name = case t of+              SingleTest name _ -> name+              TestGroup name _ -> name+              _ -> error "expected named test" in+        testCaseInfo name (pure "Skipped (requires --enable-optimal)")+ -- * Facelets  genFacelets = unsafeFacelets' <$> shuffle [0 .. 53]@@ -247,11 +277,11 @@ -- * Moves  testMoves :: String -> String -> Test-testMoves moves result = '.' : moves ~:-  (stringOfCubeColors . moveToCube <$> stringToMove moves) ~?= Right result+testMoves moves result = testCase ('_' : moves) $+  (stringOfCubeColors . moveToCube <$> stringToMove moves) @?= Right result  testCube :: String -> Cube -> String -> Test-testCube name c result = name ~: stringOfCubeColors c ~?= result+testCube name c result = testCase name $ stringOfCubeColors c @?= result  -- * Move tables @@ -283,13 +313,13 @@     genCoordFUDSP = RawCoord <$> Gen.choose (0, range ([] :: [FlipUDSlicePermu]) -1)  testMoveSymTables :: ()-  => String -> MoveTag m [Cube] -> MoveTag m [SymMove UDFix FlipUDSlicePermu]+  => String -> MoveTag m [SymMove UDFix FlipUDSlicePermu]   -> Test-testMoveSymTables name (MoveTag cubes) (MoveTag moves)+testMoveSymTables name (MoveTag moves)   = testProperty name $-      conjoin $ zipWith propMoveSymTable1 cubes moves+      conjoin $ propMoveSymTable1 <$> moves -propMoveSymTable1 c (SymMove m)+propMoveSymTable1 (SymMove m)   -- = forAll (Gen.choose (0, P.length m-1)) $ \x ->   = case G.find (\x -> x >= 16 * P.length m) m of       Nothing -> property True@@ -306,9 +336,8 @@ -- * Typeclass laws  testMonoid0 :: (Monoid a, Eq a, Show a) => proxy a -> Test-testMonoid0 proxy =-  "mempty-mappend-mempty" ~:-    mempty <> mempty ~?= mempty `asProxyTypeOf` proxy+testMonoid0 proxy = testCase "mempty-mappend-mempty" $+    mempty <> mempty @?= mempty `asProxyTypeOf` proxy  testMonoid :: (Monoid a, Eq a, Show a) => Gen a -> Test testMonoid gen = testGroup "Monoid"@@ -323,9 +352,8 @@   ]  testGroup0 :: (Group a, Eq a, Show a) => proxy a -> Test-testGroup0 proxy =-  "inverse-mempty" ~:-    inverse mempty ~?= mempty `asProxyTypeOf` proxy+testGroup0 proxy = testCase "inverse-mempty" $+    inverse mempty @?= mempty `asProxyTypeOf` proxy  testGroupInstance :: (Group a, Eq a, Show a) => Gen a -> Test testGroupInstance gen = testGroup "Group"@@ -340,7 +368,7 @@ testMonoidMorphism :: (Monoid a, Monoid b, Eq a, Eq b, Show a, Show b)   => Gen a -> (a -> b) -> Test testMonoidMorphism gen f = testGroup "MonoidM"-  [ "morphism-iden" ~: f mempty ~?= mempty+  [ testCase "morphism-iden" $ f mempty @?= mempty   , testProperty "morphism-compose" $       forAll gen $ \x -> forAll gen $ \y ->         f (x <> y) === f x <> f y@@ -367,29 +395,3 @@  testGenerator :: (Eq a, Show a) => Gen a -> (a -> Maybe b) -> Test testGenerator gen p = testProperty "generator" $ forAll gen (isJust . p)---- * Utilities---- Qualify test names-rename :: [Test] -> [Test]-rename = fmap (rename' "")--rename' :: String -> Test -> Test-rename' pfx (Test t) = Test t{ name = pfx ++ name t }-rename' pfx (Group name conc tests)-  = Group name conc (fmap (rename' (pfx ++ name ++ "/")) tests)-rename' pfx (ExtraOptions opts test) = ExtraOptions opts (rename' pfx test)--filterTests :: [Test] -> IO [Test]-filterTests tests = do-  getArgs <&> \case-    [] -> tests-    pfxs -> filterTests' pfxs tests--filterTests' pfxs = (>>= filterTest pfxs)--filterTest pfxs test@(Test t) = [test | any (`isPrefixOf` name t) pfxs]-filterTest pfxs (Group name conc tests)-  = let tests' = filterTests' pfxs tests-    in [Group name conc tests' | (not . null) tests']-filterTest pfxs (ExtraOptions opts test) = ExtraOptions opts <$> filterTest pfxs test
twentyseven.cabal view
@@ -1,5 +1,5 @@ name:                twentyseven-version:             0.0.0+version:             1.0.0 synopsis:            Rubik's cube solver description:   Solve 3×3×3 Rubik's cubes in the fewest possible moves. Or, if you can't@@ -8,12 +8,18 @@ license:             MIT license-file:        LICENSE author:              Li-yao Xia-maintainer:          li-yao.xia@ens.fr+maintainer:          lysxia@gmail.com category:            Algorithms build-type:          Simple-extra-source-files:  README.md stack.yaml-cabal-version:       >=1.10+extra-doc-files:     README.md, CHANGELOG.md+cabal-version:       2.0+tested-with:+  GHC == 8.6.5, GHC == 9.12.2 +source-repository head+  type:     git+  location: https://github.com/lysxia/twentyseven+ library   exposed-modules:     Data.Binary.Storable@@ -56,19 +62,19 @@     ViewPatterns   build-depends:     base >=4.8 && <5,-    deepseq,-    directory,-    filepath,-    heap >=1.0,-    primitive >=0.6,-    vector >=0.10,-    containers >=0.5,-    monad-loops,-    MonadRandom,-    mtl >= 2.1,-    newtype >= 0.2,-    ref-fd >=0.4,-    template-haskell+    deepseq < 1.7,+    directory < 1.4,+    filepath < 1.6,+    heap >=1.0 && < 1.1,+    primitive >=0.6 && < 0.10,+    vector >=0.10 && < 0.14,+    containers >=0.5 && < 0.9,+    monad-loops >= 0.4 && < 0.5,+    MonadRandom >= 0.4.2.2 && < 0.7,+    mtl >= 2.1 && < 2.4,+    newtype >= 0.2 && < 0.3,+    ref-fd >=0.4 && < 0.6,+    template-haskell < 2.24   hs-source-dirs:      src   default-language:    Haskell2010   ghc-options:         -fwarn-unused-imports@@ -81,25 +87,28 @@     RecordWildCards   build-depends:     base >=4.8 && <5,-    optparse-applicative,-    time <1.6,-    transformers,+    optparse-applicative < 0.20,+    time <1.15,+    transformers < 0.7,     twentyseven   default-language:    Haskell2010+  ghc-options: -Wall  Test-Suite test-twentyseven-  type: detailed-0.9+  type: exitcode-stdio-1.0   hs-source-dirs: test-  test-module: Test+  main-is: Test.hs   other-extensions:     LambdaCase   build-depends:     base >=4.8,-    Cabal >=1.9.3,-    cabal-test-quickcheck >=0.1.6,-    HUnit-Plus >=1.1.0,     QuickCheck >=2.8,+    tagged >= 0.8.9,+    tasty,+    tasty-quickcheck,+    tasty-hunit,     split,     vector,     twentyseven   default-language: Haskell2010+  ghc-options: -Wall -Wno-name-shadowing -Wno-missing-signatures -Wno-unused-top-binds