memorable-bits (empty) → 0.1.0.0
raw patch · 13 files changed
+6944/−0 lines, 13 filesdep +HUnitdep +basedep +binarysetup-changed
Dependencies added: HUnit, base, binary, bits, bytes, bytestring, criterion, cryptonite, data-dword, doctest, hashable, memorable-bits, memory, mtl, network-ip, optparse-applicative, random, split, tasty, tasty-hunit, tasty-quickcheck, tasty-smallcheck
Files
- LICENSE +26/−0
- Setup.hs +2/−0
- app/Membits.hs +147/−0
- bench/Main.hs +28/−0
- memorable-bits.cabal +95/−0
- src/Data/Memorable.hs +31/−0
- src/Data/Memorable/Internal.hs +891/−0
- src/Data/Memorable/Theme/Fantasy.hs +287/−0
- src/Data/Memorable/Theme/Food.hs +54/−0
- src/Data/Memorable/Theme/Metric.hs +63/−0
- src/Data/Memorable/Theme/Science.hs +16/−0
- src/Data/Memorable/Theme/Words.hs +5233/−0
- test/Main.hs +71/−0
+ LICENSE view
@@ -0,0 +1,26 @@+Copyright (c) 2016, Luke Clifton+All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are+met:++1. Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++2. Redistributions in binary form must reproduce the above copyright+ notice, this list of conditions and the following disclaimer in the+ documentation and/or other materials provided with the+ distribution.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ app/Membits.hs view
@@ -0,0 +1,147 @@+{-# LANGUAGE ApplicativeDo #-}+{-# LANGUAGE ExistentialQuantification #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+module Main where++import Unsafe.Coerce+import Control.Exception+import Crypto.Hash+import qualified Data.ByteString.Lazy as B+import Data.Char+import Data.Maybe+import Data.Memorable+import Data.Memorable.Theme.Words+import Data.Monoid+import Data.Type.Equality+import GHC.TypeLits+import Options.Applicative+import System.Environment+import System.IO++type Algorithm = B.ByteString -> String++data Opt+ = HashOpt+ { algorithm :: Algorithm+ , files :: [FilePath]+ }+ | Convert++algOption ::+ ( Show a+ , 40 <= MemLen (Digest a)+ , KnownNat (MemLen (Digest a))+ , Memorable (Digest a)+ , HashAlgorithm a+ ) => a -> Parser Algorithm+algOption a = flag' (goAlg a) (long (map toLower $ show a))++algs :: [Parser Algorithm]+algs =+ [ algOption Blake2b_512+ , algOption Blake2bp_512+ , algOption Blake2s_224+ , algOption Blake2s_256+ , algOption Blake2sp_224+ , algOption Blake2sp_256+ , algOption Keccak_224+ , algOption Keccak_256+ , algOption Keccak_384+ , algOption Keccak_512+ , algOption MD2+ , algOption MD4+ , algOption MD5+ , algOption RIPEMD160+ , algOption SHA1+ , algOption SHA224+ , algOption SHA256+ , algOption SHA384+ , algOption SHA3_224+ , algOption SHA3_256+ , algOption SHA3_384+ , algOption SHA3_512+ , algOption SHA512+ , algOption SHA512t_224+ , algOption SHA512t_256+ , algOption Skein256_224+ , algOption Skein256_256+ , algOption Skein512_224+ , algOption Skein512_256+ , algOption Skein512_384+ , algOption Skein512_512+ , algOption Tiger+ , algOption Whirlpool+ ]++choice :: Alternative f => f a -> [f a] -> f a+choice = foldr (<|>)++opts :: Parser Opt+opts = parseHashOpts <|> parseConv+ where+ parseHashOpts = do+ algorithm <- parseAlgorithm+ files <- parseFiles+ pure HashOpt{..}+ parseAlgorithm = choice empty algs+ parseFiles = many $ strArgument (metavar "FILE" <> help "Files to hash" <> action "file")+ parseConv = flag' Convert (long "conv" <> help "Convert hex lines on stdin to a memorable pattern")++main :: IO ()+main = do+ opts <- execParser (info opts fullDesc)+ case opts of+ Convert -> do+ ls <- lines <$> getContents+ mapM_ (either (hPutStrLn stderr) putStrLn . doConv) ls+ HashOpt{..} -> mapM_ (run algorithm) files++run :: Algorithm -> FilePath -> IO ()+run a f = do+ res <- try $ do+ bs <- B.readFile f+ return $! a bs+ case res of+ Right h -> do+ putStr h+ putStr "\t"+ putStrLn f+ Left (e :: SomeException) -> hPutStrLn stderr (displayException e)++goAlg :: forall a.+ ( 40 <= MemLen (Digest a)+ , KnownNat (MemLen (Digest a))+ , Memorable (Digest a)+ , HashAlgorithm a+ ) => a -> Algorithm+goAlg a b =+ let+ h = hashlazy b :: Digest a+ in+ renderMemorable (padHex $ four flw10) h++moreThan40 :: KnownNat n => Proxy (n :: Nat) -> Maybe ((40 <=? n) :~: True)+moreThan40 p = if natVal p > 40 then Just $ unsafeCoerce Refl else Nothing++doConv :: String -> Either String String+doConv i =+ let+ Just bitsIn = someNatVal $ fromIntegral $ length i * 4+ in+ case bitsIn of+ SomeNat (p :: Proxy n) ->+ case moreThan40 p of+ Just Refl -> + let+ s = rerender (hex @n) (padHex $ four flw10) i+ in+ maybe (Left $ "failed to convert '" ++ i ++ "'") Right s+ Nothing -> Left $ "failed to convert '" ++ i ++ "'"+
+ bench/Main.hs view
@@ -0,0 +1,28 @@+module Main where++import Control.Monad+import Criterion.Main+import Data.ByteString.Lazy (ByteString, pack)+import Data.Memorable+import Data.Memorable.Theme.Words+import System.Random+import Data.Word++benchmarks :: Word8 -> Word32 -> Word64 -> [Benchmark]+benchmarks w8 w32 w64 =+ [ bgroup "words"+ [ bench "word8" $ nf (renderMemorable words8) w8+ , bench "threeWordsFor32Bits" $ nf (renderMemorable threeWordsFor32Bits) w32+ , bench "fourWordsFor32Bits" $ nf (renderMemorable fourEqualWordsFor32Bits) w32+ , bench "sixWordsFor64Bits" $ nf (renderMemorable sixWordsFor64Bits) w64+ , bench "eightWordsFor64Bits" $ nf (renderMemorable eightEqualWordsFor64Bits) w64+ , bench "big tuple" $ nf (renderMemorable (four eightEqualWordsFor64Bits)) (w64,w64,w64,w64)+ ]+ ]++main :: IO ()+main = do+ w8 <- randomIO+ w32 <- randomIO+ w64 <- randomIO+ defaultMain $ benchmarks w8 w32 w64
+ memorable-bits.cabal view
@@ -0,0 +1,95 @@+-- Initial memorable-bits.cabal generated by cabal init. For further +-- documentation, see http://haskell.org/cabal/users-guide/++name: memorable-bits+version: 0.1.0.0+synopsis: Generate human memorable strings from binary data.+license: BSD2+license-file: LICENSE+author: Luke Clifton+maintainer: lukec@themk.net+category: Data+build-type: Simple+cabal-version: >=1.10++flag data-dword-inst+ description: Include data-dword and provide instances for its types.+ default: True++flag network-ip-inst+ description: Include network-ip and provide instances for its types.+ default: True++flag cryptonite-inst+ description: Include cryptonite and provide instances for its types.+ default: True++flag exes+ description: Build a set of executables for printing memorable hashes of files.+ default: True++library+ exposed-modules: Data.Memorable+ , Data.Memorable.Internal+ , Data.Memorable.Theme.Food+ , Data.Memorable.Theme.Fantasy+ , Data.Memorable.Theme.Metric+ , Data.Memorable.Theme.Science+ , Data.Memorable.Theme.Words+ build-depends: base >=4.8 && <5+ , bits >=0.5+ , bytes >=0.15+ , binary >=0.8+ , bytestring >=0.10+ , hashable >=1.2+ , split >=0.2+ , random >=1.1+ , mtl >=2.2.1+ hs-source-dirs: src+ default-language: Haskell2010+ if flag(network-ip-inst) {+ cpp-options: -DNETWORK_IP -DDATA_DWORD+ build-depends: network-ip, data-dword+ }+ if flag(data-dword-inst) {+ cpp-options: -DDATA_DWORD+ build-depends: data-dword+ }+ if flag(cryptonite-inst) {+ cpp-options: -DCRYPTONITE+ build-depends: cryptonite, memory+ }++test-suite test-memorable-bits+ type: exitcode-stdio-1.0+ main-is: test/Main.hs+ build-depends: base >=4.8 && <4.10+ , memorable-bits+ , tasty+ , tasty-hunit+ , tasty-quickcheck+ , tasty-smallcheck+ , doctest+ , HUnit++benchmark bench-memorable-bits+ type: exitcode-stdio-1.0+ main-is: bench/Main.hs+ build-depends: base >=4.8 && <4.10+ , criterion+ , memorable-bits+ , random+ , bytestring++executable membits+ main-is: app/Membits.hs+ default-language: Haskell2010+ build-depends: base >=4.8 && <4.10+ , memorable-bits+ , bytestring+ , cryptonite+ , optparse-applicative++ if !flag(exes) {+ buildable: False+ }
+ src/Data/Memorable.hs view
@@ -0,0 +1,31 @@+module Data.Memorable+ ( renderMemorable+ , parseMemorable+ , rerender+ , Memorable(..)+ -- * Pattern Building+ , (.-), (.|)+ , two, three, four, five+ , padHex, padDec+ , hex, hex4, hex8, hex16, hex32, dec, dec4, dec8, dec16, dec32+ , ToTree+ , leftSide+ , rightSide+ -- * Pattern Types+ , (:-)+ , MemRender()+ , Number+ , NumberWithOffset+ , PadTo+ , Dec+ , Hex+ , Depth+ , getDepth+ , LeftSide+ , RightSide+ -- * Re-export+ , Proxy(..)+ ) where++import Data.Memorable.Internal+import Data.Proxy
+ src/Data/Memorable/Internal.hs view
@@ -0,0 +1,891 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE UndecidableSuperClasses #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE UndecidableInstances #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE PolyKinds #-}+{-# LANGUAGE TypeOperators #-}+module Data.Memorable.Internal where++import Control.Arrow (first)+import Text.Printf+import Control.Applicative+import Control.Monad.Except+import Data.Maybe+import Data.List.Split+import Control.Monad.State+import Control.Monad.Writer+import Data.Hashable+import Data.Binary.Get+import Data.Binary.Put+import qualified Data.Binary+import Data.Bits+import Data.Bits.Coding hiding (putUnaligned)+import Data.Bytes.Put+import Data.Bytes.Get+import Data.Type.Equality+import Data.Type.Bool+import Data.ByteString.Lazy (ByteString, pack, unpack)+import qualified Data.ByteString.Lazy as BL+import qualified Data.ByteString as B+import Data.List+import Data.Proxy+import Data.Word+import Data.Int+import GHC.TypeLits+import GHC.Exts+import Numeric+import System.Random (randomIO)+#ifdef DATA_DWORD+import Data.DoubleWord+#endif+#ifdef NETWORK_IP+import Network.IP.Addr+#endif+#ifdef CRYPTONITE+import Data.ByteArray (convert)+import Crypto.Hash hiding (hash)+#endif+#ifdef HASHABLE+import Data.Hashable+#endif++-- | Choice between two sub patterns. It's not safe to use this directly.+-- Use `.|` instead.+--+-- Also, if you are parsing back rendered phrases, you must make sure that+-- the selected word is enough to choose a side. That is, `a` and `b` must+-- have unique first words. This is NOT checked, as it causes a HUGE+-- compile-time performance hit. If we can make it performant it may be+-- checked one day.+data a :| b++-- | Append two patterns together by doing the first, then the second. See+-- also `.-`+data a :- b++-- | Proxy version of `:|`. It also constraints the two subpatterns to+-- being the same depth. Use this to add an extra bit to the pattern depth,+-- where the bit chooses to proceed down either the left or right side.+--+-- >>> :set -XTypeApplications+-- >>> :set -XDataKinds+-- >>> import Data.Word+-- >>> let myPattern = padHex (Proxy @"foo" .| Proxy @"bar")+-- >>> renderMemorable myPattern (0x00 :: Word8)+-- "bar-00"+-- >>> renderMemorable myPattern (0xff :: Word8)+-- "foo-7f"+--+-- See also 'ToTree'+--+-- WARNING: Each side of the split must be unique. See the warning about `:|`.+(.|) :: (Depth a ~ Depth b) => Proxy a -> Proxy b -> Proxy (a :| b)+_ .| _ = Proxy++-- | Proxy version of `:-`.+-- The new pattern depth is the sum of the two parts.+-- >>> import Data.Word+-- >>> import Data.Memorable.Theme.Words+-- >>> let myPattern = words8 .- words8+-- >>> renderMemorable myPattern (0xabcd :: Word16)+-- "ages-old"+(.-) :: Proxy a -> Proxy b -> Proxy (a :- b)+_ .- _ = Proxy++-- | Captures `n` bits and converts them to a string via the `nt` ("number type")+-- argument. See `Dec`, `Hex`.+type Number nt n = NumberWithOffset nt n 0++-- | Captures `n` bits and convertes them to a string via the `nt` ("number type")+-- argument after adding the offset. See `Dec`, `Hex`.+data NumberWithOffset nt (n :: Nat) (o :: Nat)++-- | Pad the `a` argument out to length `n` by taking the remaining bits+-- and converting them via `nt` (see `Dec` and `Hex`). If padding is required,+-- it is separated by a dash.+--+-- See `padHex` and `padDec` for convinence functions.+data PadTo nt (n :: Nat) a++---------------------------------------------------------------------+-- Utility type functions+---------------------------------------------------------------------++-- Helper for `ToTree`+type family ToTreeH (a :: [k]) :: [*] where+ ToTreeH '[] = '[]+ ToTreeH (x1 ': x2 ': x3 ': x4 ': x5 ': x6 ': x7 ': x8 ': x9 ': x10 ': x11 ': x12 ': x13 ': x14 ': x15 ': x16 ': x17 ': x18 ': x19 ': x20 ': x21 ': x22 ': x23 ': x24 ': x25 ': x26 ': x27 ': x28 ': x29 ': x30 ': x31 ': x32 ': x33 ': x34 ': x35 ': x36 ': x37 ': x38 ': x39 ': x40 ': x41 ': x42 ': x43 ': x44 ': x45 ': x46 ': x47 ': x48 ': x49 ': x50 ': x51 ': x52 ': x53 ': x54 ': x55 ': x56 ': x57 ': x58 ': x59 ': x60 ': x61 ': x62 ': x63 ': x64 ': xs) = ToTree64 (x1 ': x2 ': x3 ': x4 ': x5 ': x6 ': x7 ': x8 ': x9 ': x10 ': x11 ': x12 ': x13 ': x14 ': x15 ': x16 ': x17 ': x18 ': x19 ': x20 ': x21 ': x22 ': x23 ': x24 ': x25 ': x26 ': x27 ': x28 ': x29 ': x30 ': x31 ': x32 ': x33 ': x34 ': x35 ': x36 ': x37 ': x38 ': x39 ': x40 ': x41 ': x42 ': x43 ': x44 ': x45 ': x46 ': x47 ': x48 ': x49 ': x50 ': x51 ': x52 ': x53 ': x54 ': x55 ': x56 ': x57 ': x58 ': x59 ': x60 ': x61 ': x62 ': x63 ': x64 ': xs)+ ToTreeH as = ToTree2 as++type family ToTree2 (as :: [k]) :: [*] where + ToTree2 '[] = '[]+ ToTree2 (a ': b ': bs) = (a :| b) ': ToTree2 bs++type family ToTree64 (as :: [k]) :: [*] where + ToTree64 '[] = '[]+ ToTree64 (x1 ': x2 ': x3 ': x4 ': x5 ': x6 ': x7 ': x8 ': x9 ': x10 ': x11 ': x12 ': x13 ': x14 ': x15 ': x16 ': x17 ': x18 ': x19 ': x20 ': x21 ': x22 ': x23 ': x24 ': x25 ': x26 ': x27 ': x28 ': x29 ': x30 ': x31 ': x32 ': x33 ': x34 ': x35 ': x36 ': x37 ': x38 ': x39 ': x40 ': x41 ': x42 ': x43 ': x44 ': x45 ': x46 ': x47 ': x48 ': x49 ': x50 ': x51 ': x52 ': x53 ': x54 ': x55 ': x56 ': x57 ': x58 ': x59 ': x60 ': x61 ': x62 ': x63 ': x64 ': xs) =+ (+ (+ (+ (+ (+ (x1 :| x2) :| (x3 :| x4)+ ) :|+ (+ (x5 :| x6) :| (x7 :| x8)+ )+ ) :|+ (+ (+ (x9 :| x10) :| (x11 :| x12)+ ) :|+ (+ (x13 :| x14) :| (x15 :| x16)+ )+ )+ ) :|+ (+ (+ (+ (x17 :| x18) :| (x19 :| x20)+ ) :|+ (+ (x21 :| x22) :| (x23 :| x24)+ )+ ) :|+ (+ (+ (x25 :| x26) :| (x27 :| x28)+ ) :|+ (+ (x29 :| x30) :| (x31 :| x32)+ )+ )+ )+ ) :|+ (+ (+ (+ (+ (x33 :| x34) :| (x35 :| x36)+ ) :|+ (+ (x37 :| x38) :| (x39 :| x40)+ )+ ) :|+ (+ (+ (x41 :| x42) :| (x43 :| x44)+ ) :|+ (+ (x45 :| x46) :| (x47 :| x48)+ )+ )+ ) :|+ (+ (+ (+ (x49 :| x50) :| (x51 :| x52)+ ) :|+ (+ (x53 :| x54) :| (x55 :| x56)+ )+ ) :|+ (+ (+ (x57 :| x58) :| (x59 :| x60)+ ) :|+ (+ (x61 :| x62) :| (x63 :| x64)+ )+ )+ )+ ) + ) ': ToTree64 xs++type family Len (a :: [Symbol]) :: Nat where+ Len (a ': b ': c ': d ': e ': f ': g ': h ': i ': j ': k ': l ': m ': n ': o ': p ': q ': r ': s ': t ': u ': v ': w ': x ': y ': z ': as) = Len as + 26+ Len (a ': as) = Len as + 1+ Len '[] = 0++-- | Convert a @'[Symbol]@ to a balanced tree of `:|`. Each result has equal+-- probability of occurring. Length of the list must be a power of two. This+-- is very useful for converting long lists of words into a usable pattern.+--+-- >>> :kind! ToTree '["a", "b", "c", "d"]+-- ToTree '["a", "b", "c", "d"] :: *+-- = ("a" :| "b") :| ("c" :| "d")+type family ToTree (a :: [k]) :: * where+ ToTree (x ': y ': '[] ) = x :| y+ ToTree '[(x :| y)] = x :| y+ ToTree xs = ToTree (ToTreeH xs)++type family Concat (a :: [k]) :: * where+ Concat (a ': b ': '[]) = a :- b+ Concat (a ': b ': cs) = a :- b :- Concat cs++type family Intersperse (a :: k) (b :: [k]) :: [k] where+ Intersperse a '[] = '[]+ Intersperse a (b ': '[]) = b ': '[]+ Intersperse a (b ': cs) = b ': a ': Intersperse a cs+++-- | Useful to prevent haddock from expanding the type.+type family LeftSide (a :: *) :: * where+ LeftSide (a :| b) = a++-- | Useful to prevent haddock from expanding the type.+type family RightSide (a :: *) :: * where+ RightSide (a :| b) = b++-- | Shrink a branching pattern by discarding the right hand side.+leftSide :: Proxy (a :| b) -> Proxy a+leftSide _ = Proxy++-- | Shrink a branching pattern by discarding the left hand side.+rightSide :: Proxy (a :| b) -> Proxy b+rightSide _ = Proxy+++type PowerOfTwo n = (IsPowerOfTwo n ~ True)++type family IsPowerOfTwo (a :: Nat) :: Bool where+ IsPowerOfTwo 1 = True+ IsPowerOfTwo 2 = True+ IsPowerOfTwo 4 = True+ IsPowerOfTwo 8 = True+ IsPowerOfTwo 16 = True+ IsPowerOfTwo 32 = True+ IsPowerOfTwo 64 = True+ IsPowerOfTwo 128 = True+ IsPowerOfTwo 256 = True+ IsPowerOfTwo 512 = True+ IsPowerOfTwo 1024 = True+ IsPowerOfTwo 2048 = True+ IsPowerOfTwo 4096 = True+ IsPowerOfTwo 8192 = True++type family BitsInPowerOfTwo (a :: Nat) :: Nat where+ BitsInPowerOfTwo 1 = 0+ BitsInPowerOfTwo 2 = 1+ BitsInPowerOfTwo 4 = 2+ BitsInPowerOfTwo 8 = 3+ BitsInPowerOfTwo 16 = 4+ BitsInPowerOfTwo 32 = 5+ BitsInPowerOfTwo 64 = 6+ BitsInPowerOfTwo 128 = 7+ BitsInPowerOfTwo 256 = 8+ BitsInPowerOfTwo 512 = 9+ BitsInPowerOfTwo 1024 = 10+ BitsInPowerOfTwo 2048 = 11+ BitsInPowerOfTwo 4096 = 12+ BitsInPowerOfTwo 8192 = 13++type family Find a as :: Bool where+ Find a '[] = 'False+ Find a (a ': as) = 'True+ Find a (b ': a ': as) = 'True+ Find a (c ': b ': a ': as) = 'True+ Find a (d ': c ': b ': a ': as) = 'True+ Find a (e ': d ': c ': b ': a ': as) = 'True+ Find a (f ': e ': d ': c ': b ': a ': as) = 'True+ Find a (g ': f ': e ': d ': c ': b ': a ': as) = 'True+ Find a (h ': g ': f ': e ': d ': c ': b ': a ': as) = 'True+ Find a (i ': h ': g ': f ': e ': d ': c ': b ': a ': as) = 'True+ Find a (j ': i ': h ': g ': f ': e ': d ': c ': b ': a ': as) = 'True+ Find a (k ': j ': i ': h ': g ': f ': e ': d ': c ': b ': a ': as) = 'True+ Find a (l ': k ': j ': i ': h ': g ': f ': e ': d ': c ': b ': a ': as) = 'True+ Find a (m ': l ': k ': j ': i ': h ': g ': f ': e ': d ': c ': b ': a ': as) = 'True+ Find a (n ': m ': l ': k ': j ': i ': h ': g ': f ': e ': d ': c ': b ': a ': as) = 'True+ Find a (o ': n ': m ': l ': k ': j ': i ': h ': g ': f ': e ': d ': c ': b ': a ': as) = 'True+ Find a (p ': o ': n ': m ': l ': k ': j ': i ': h ': g ': f ': e ': d ': c ': b ': a ': as) = 'True+ Find a (q ': p ': o ': n ': m ': l ': k ': j ': i ': h ': g ': f ': e ': d ': c ': b ': a ': as) = 'True+ Find a (r ': q ': p ': o ': n ': m ': l ': k ': j ': i ': h ': g ': f ': e ': d ': c ': b ': a ': as) = 'True+ Find a (s ': r ': q ': p ': o ': n ': m ': l ': k ': j ': i ': h ': g ': f ': e ': d ': c ': b ': a ': as) = 'True+ Find a (t ': s ': r ': q ': p ': o ': n ': m ': l ': k ': j ': i ': h ': g ': f ': e ': d ': c ': b ': a ': as) = 'True+ Find a (u ': t ': s ': r ': q ': p ': o ': n ': m ': l ': k ': j ': i ': h ': g ': f ': e ': d ': c ': b ': a ': as) = 'True+ Find a (v ': u ': t ': s ': r ': q ': p ': o ': n ': m ': l ': k ': j ': i ': h ': g ': f ': e ': d ': c ': b ': a ': as) = 'True+ Find a (w ': v ': u ': t ': s ': r ': q ': p ': o ': n ': m ': l ': k ': j ': i ': h ': g ': f ': e ': d ': c ': b ': a ': as) = 'True+ Find a (x ': w ': v ': u ': t ': s ': r ': q ': p ': o ': n ': m ': l ': k ': j ': i ': h ': g ': f ': e ': d ': c ': b ': a ': as) = 'True+ Find a (y ': x ': w ': v ': u ': t ': s ': r ': q ': p ': o ': n ': m ': l ': k ': j ': i ': h ': g ': f ': e ': d ': c ': b ': a ': as) = 'True+ Find a (z ': y ': x ': w ': v ': u ': t ': s ': r ': q ': p ': o ': n ': m ': l ': k ': j ': i ': h ': g ': f ': e ': d ': c ': b ': a ': as) = 'True+ Find a (z ': y ': x ': w ': v ': u ': t ': s ': r ': q ': p ': o ': n ': m ': l ': k ': j ': i ': h ': g ': f ': e ': d ': c ': b ': aa ': as) = Find a as+ Find a (b ': as ) = Find a as+++type family HasDups (a :: [Symbol]) :: Bool where+ HasDups (a ': as) = Find a as || HasDups as+ HasDups '[] = 'False++type family NoDups (a :: [Symbol]) :: Constraint where+ NoDups (a ': as) = If (Find a as) (TypeError (Text "Pattern is ambiguous because of " :<>: ShowType a)) (NoDups as)+ NoDups '[] = ()++-- | Determines the number of bits that a pattern will consume.+type family Depth (a :: k) :: Nat where+ Depth (a :: Symbol) = 0+ Depth (a :- b) = Depth a + Depth b+ Depth (a :| b) = 1 + Depth a+ Depth (NumberWithOffset nt a o) = a+ Depth (PadTo nt n a) = n++-- | Get the depth of a pattern as a value-level `Integer`.+-- >>> :set -XTypeApplications -XDataKinds+-- >>> getDepth (Proxy @"foo" .| Proxy @"bar")+-- 1+getDepth :: forall a. KnownNat (Depth a) => Proxy a -> Integer+getDepth _ = natVal (Proxy :: Proxy (Depth a))+++---------------------------------------------------------------------+-- Utility functions+---------------------------------------------------------------------++type family NTimes (n :: Nat) (p :: *) where+ NTimes 1 a = a+ NTimes n a = a :- NTimes (n - 1) a++-- | Put five things next to each other.+-- Same as using '.-' repeatedly+five :: Proxy a -> Proxy (a :- a :- a :- a :- a)+five _ = Proxy++-- | Put four things next to each other.+four :: Proxy a -> Proxy (a :- a :- a :- a)+four _ = Proxy++-- | Put three things next to each other.+three :: Proxy a -> Proxy (a :- a :- a)+three _ = Proxy++-- | Put two things next to each other.+two :: Proxy a -> Proxy (a :- a)+two _ = Proxy++-- | Pad this pattern out with hex digits. Useful when you want some human+-- readability, but also want full coverage of the data. See 'Hex' for details.+--+-- >>> import Data.Word+-- >>> import Data.Memorable.Theme.Fantasy+-- >>> renderMemorable (padHex rpgWeapons) (0xdeadbeef01020304 :: Word64)+-- "sacred-club-of-ghoul-charming-eef01020304"+--+-- The depth to pad to is the first type-level argument. If you have+-- `TypeApplications` set, you can use it like `padHex @256` to be+-- explicit.+padHex :: forall n a. Proxy a -> Proxy (PadTo Hex n a)+padHex _ = Proxy++-- | Pad with decimal digits. See 'padHex' and 'Dec' for details. This does+-- not pad with 0's+padDec :: forall n a. Proxy a -> Proxy (PadTo Dec n a)+padDec _ = Proxy++-- | A single hex number consuming 4 bits (with leading 0's).+hex4 :: Proxy (Number Hex 4)+hex4 = Proxy++-- | A single hex number consuming 8 bits (with leading 0's).+hex8 :: Proxy (Number Hex 8)+hex8 = Proxy++-- | A single hex number consuming 16 bits (with leading 0's).+hex16 :: Proxy (Number Hex 16)+hex16 = Proxy++-- | A single hex number consuming 32 bits (with leading 0's).+hex32 :: Proxy (Number Hex 32)+hex32 = Proxy++-- | A single hex number consuming `n` bits, which it will try and figure+-- out from context (with leading 0's). Using `TypeApplications` allows you+-- to specify the size directly, `hex @32 == hex32`.+hex :: Proxy (Number Hex n)+hex = Proxy++-- | A single decimal number consuming 4 bits (no leading 0's)+dec4 :: Proxy (Number Dec 4)+dec4 = Proxy++-- | A single decimal number consuming 8 bits (no leading 0's)+dec8 :: Proxy (Number Dec 8)+dec8 = Proxy++-- | A single decimal number consuming 16 bits (no leading 0's)+dec16 :: Proxy (Number Dec 16)+dec16 = Proxy++-- | A single decimal number consuming 32 bits (no leading 0's)+dec32 :: Proxy (Number Dec 32)+dec32 = Proxy++-- | A single decimal number consuming `n` bits, which it will try and figure+-- out from context (no leading 0's). Using `TypeApplications` allows you+-- to specify the size directly, `dec @32 == hex32`.+dec :: Proxy (Number Dec n)+dec = Proxy++---------------------------------------------------------------------+-- MemRender+---------------------------------------------------------------------++-- | The class that implements the main rendering function.+class MemRender a where+ render :: Proxy a -> Coding Get String+ parser :: Proxy a -> ExceptT String (State ([String], Coding PutM ())) ()++addBits :: Coding PutM () -> ExceptT String (State ([String], Coding PutM ())) ()+addBits c = do+ (s,cs) <- get+ put (s,cs >> c)++symbolString :: KnownSymbol a => Proxy a -> String+symbolString = concatMap tr . symbolVal+ where+ tr '-' = "\\_"+ tr '\\' = "\\\\"+ tr c = [c]++stringSymbol :: String -> String+stringSymbol [] = []+stringSymbol ('\\':'\\':rest) = '\\' : stringSymbol rest+stringSymbol ('\\':'_':rest) = '-' : stringSymbol rest+stringSymbol (a:rest) = a : stringSymbol rest++parsePhrase :: MemRender p => Proxy p -> String -> Maybe ByteString+parsePhrase p input =+ let+ tokens = map stringSymbol $ splitOn "-" input+ stm = runExceptT (parser p)+ (e,(_,cdm)) = runState stm (tokens, pure ())+ ptm = runCoding (cdm <* Data.Bytes.Put.flush) (\a _ _ -> pure a) 0 0+ in+ case e of+ Left _ -> Nothing+ Right () -> Just $ runPutL ptm++-- | Turn a memorable string back into a 'Memorable' value.+parseMemorable :: (Memorable a, MemRender p, MemLen a ~ Depth p) => Proxy p -> String -> Maybe a+parseMemorable p input =+ let+ bs = parsePhrase p input+ in runParser <$> bs++-- | Convert a memorable string into a different memorable string.+--+-- Useful for things like taking an existing md5, and converting it+-- into a memorable one.+--+-- >>> :set -XTypeApplications -XDataKinds+-- >>> import Data.Memorable.Theme.Words+-- >>> rerender hex (padHex @128 $ four words10) "2d4fbe4d5db8748c931b85c551d03360"+-- Just "lurk-lash-atop-hole-b8748c931b85c551d03360"+rerender :: (MemRender a, MemRender b, Depth a ~ Depth b) => Proxy a -> Proxy b -> String -> Maybe String+rerender a b input = renderMemorableByteString b <$> parsePhrase a input++instance (KnownSymbol a) => MemRender (a :: Symbol) where+ render = return . symbolString+ parser p = do+ (ss,cs) <- get+ case ss of+ [] -> empty+ s:ss' ->+ if s == symbolVal p+ then put (ss',cs)+ else empty+ ++instance (MemRender a, MemRender b) => MemRender (a :- b) where+ render _ = do+ sa <- render (Proxy :: Proxy a)+ sb <- render (Proxy :: Proxy b)+ return $ sa ++ "-" ++ sb+ parser _ = do+ parser (Proxy :: Proxy a)+ parser (Proxy :: Proxy b)+ ++instance (MemRender a, MemRender b) => MemRender (a :| b) where+ render _ = do+ b <- getBit+ if b+ then render (Proxy :: Proxy a)+ else render (Proxy :: Proxy b)+ parser _ = do+ s <- get+ catchError (do+ addBits (putBit True)+ parser (Proxy :: Proxy a) + ) (\_ -> do+ put s+ addBits (putBit False)+ parser (Proxy :: Proxy b)+ )++instance (NumberRender nt, KnownNat a, KnownNat o) => MemRender (NumberWithOffset nt a o) where+ render _ = do+ let+ o = natVal (Proxy :: Proxy o)+ b = natVal (Proxy :: Proxy a)+ w <- getBitsFrom (fromIntegral (pred b)) 0+ return $ renderNumber (Proxy :: Proxy nt) b (w + o)++ parser _ = do+ let+ o = natVal (Proxy :: Proxy o)+ b = natVal (Proxy :: Proxy a)+ (ss,cs) <- get+ case ss of+ [] -> empty+ (s:ss') -> do+ let+ n = readNumber (Proxy :: Proxy nt) b s+ case n of+ Nothing -> empty+ Just n' -> do+ let n'' = n' - o+ when (n'' >= 2^b) empty+ put (ss',cs >> putBitsFrom (fromIntegral $ pred b) n'')+ ++instance (MemRender a, Depth a <= n, NumberRender nt, KnownNat n, KnownNat (Depth a)) => MemRender (PadTo nt n a) where+ render _ = do+ s1 <- render (Proxy :: Proxy a)+ let+ diff = natVal (Proxy :: Proxy n) - natVal (Proxy :: Proxy (Depth a))+ ntp = Proxy :: Proxy nt+ case diff of+ 0 -> return s1+ _ -> do+ d <- getBitsFrom (fromIntegral (pred diff)) 0+ return $ s1 ++ "-" ++ renderNumber ntp diff d++ parser _ = do+ let+ nt = Proxy :: Proxy nt+ diff = natVal (Proxy :: Proxy n) - natVal (Proxy :: Proxy (Depth a))+ parser (Proxy :: Proxy a)+ case diff of+ 0 -> return ()+ _ -> do+ (ss,cs) <- get+ when (null ss) empty+ let+ (s:ss') = ss+ n = readNumber nt diff s+ n' <- maybe empty return n+ when (n' >= 2^diff) empty+ put (ss', cs >> putBitsFrom (fromIntegral $ pred diff) n')++---------------------------------------------------------------------+-- NumberRender+---------------------------------------------------------------------++-- | Class for capturing how to render numbers.+class NumberRender n where+ renderNumber :: Proxy n -> Integer -> Integer -> String+ readNumber :: Proxy n -> Integer -> String -> Maybe Integer+++-- | Render numbers as decimal numbers. Does not pad.+data Dec++instance NumberRender Dec where+ renderNumber _ _ = show+ readNumber _ _ input = case readDec input of+ [(v,"")] -> Just v+ _ -> Nothing+++-- | Render numbers as hexadecimal numbers. Pads with 0s.+data Hex++instance NumberRender Hex where+ renderNumber _ b = printf "%0*x" hexDigits+ where+ hexDigits = (b - 1) `div` 4 + 1+ readNumber _ _ input = case readHex input of+ [(v,"")] -> Just v+ _ -> Nothing++---------------------------------------------------------------------+-- Rendering functions for users+---------------------------------------------------------------------++-- | Class for all things that can be converted to memorable strings.+-- See `renderMemorable` for how to use.+class Memorable a where+ -- Do not lie. Use @`testMemLen`@.+ type MemLen a :: Nat+ renderMem :: MonadPut m => a -> Coding m ()+ parserMem :: MonadGet m => Coding m a++memBitSize :: forall a. (KnownNat (MemLen a)) => Proxy a -> Int+memBitSize _ = fromIntegral $ natVal (Proxy :: Proxy (MemLen a))++-- | Use this with tasty-quickcheck (or your prefered testing framework) to+-- make sure you aren't lying about `MemLen`.+-- +-- @+-- testProperty "MemLen Word8" $ forAll (arbitrary :: Gen Word8) `testMemLen`+-- @+testMemLen :: forall a. (KnownNat (MemLen a), Memorable a) => a -> Bool+testMemLen a =+ let+ p :: Coding PutM ()+ p = renderMem a+ (x,bs) = runPutM (runCoding p (\a x _ -> return x) 0 0)+ l = fromIntegral $ natVal (Proxy :: Proxy (MemLen a))+ bl = 8 * fromIntegral (BL.length bs) - x+ in+ l == bl++putUnaligned :: (MonadPut m, FiniteBits b) => b -> Coding m ()+putUnaligned b = putBitsFrom (pred $ finiteBitSize b) b++instance Memorable Word8 where+ type MemLen Word8 = 8+ renderMem = putUnaligned+ parserMem = getBitsFrom (pred $ memBitSize (Proxy :: Proxy Word8)) 0++instance Memorable Word16 where+ type MemLen Word16 = 16+ renderMem = putUnaligned+ parserMem = getBitsFrom (pred $ memBitSize (Proxy :: Proxy Word16)) 0++instance Memorable Word32 where+ type MemLen Word32 = 32+ renderMem = putUnaligned+ parserMem = getBitsFrom (pred $ memBitSize (Proxy :: Proxy Word32)) 0++instance Memorable Word64 where+ type MemLen Word64 = 64+ renderMem = putUnaligned+ parserMem = getBitsFrom (pred $ memBitSize (Proxy :: Proxy Word64)) 0++instance Memorable Int8 where+ type MemLen Int8 = 8+ renderMem = putUnaligned+ parserMem = getBitsFrom (pred $ memBitSize (Proxy :: Proxy Int8)) 0++instance Memorable Int16 where+ type MemLen Int16 = 16+ renderMem = putUnaligned+ parserMem = getBitsFrom (pred $ memBitSize (Proxy :: Proxy Int16)) 0++instance Memorable Int32 where+ type MemLen Int32 = 32+ renderMem = putUnaligned+ parserMem = getBitsFrom (pred $ memBitSize (Proxy :: Proxy Int32)) 0++instance Memorable Int64 where+ type MemLen Int64 = 64+ renderMem = putUnaligned+ parserMem = getBitsFrom (pred $ memBitSize (Proxy :: Proxy Int64)) 0++instance (Memorable a, Memorable b) => Memorable (a,b) where+ type MemLen (a,b) = MemLen a + MemLen b+ renderMem (a,b) = renderMem a >> renderMem b+ parserMem = (,) <$> parserMem <*> parserMem++instance (Memorable a, Memorable b, Memorable c) => Memorable (a,b,c) where+ type MemLen (a,b,c) = MemLen a + MemLen b + MemLen c+ renderMem (a,b,c) = renderMem a >> renderMem b >> renderMem c+ parserMem = (,,) <$> parserMem <*> parserMem <*> parserMem+++instance (Memorable a, Memorable b, Memorable c, Memorable d) => Memorable (a,b,c,d) where+ type MemLen (a,b,c,d) = MemLen a + MemLen b + MemLen c + MemLen d+ renderMem (a,b,c,d) = renderMem a >> renderMem b >> renderMem c >> renderMem d+ parserMem = (,,,) <$> parserMem <*> parserMem <*> parserMem <*> parserMem++instance (Memorable a, Memorable b, Memorable c, Memorable d, Memorable e) => Memorable (a,b,c,d,e) where+ type MemLen (a,b,c,d,e) = MemLen a + MemLen b + MemLen c + MemLen d + MemLen e+ renderMem (a,b,c,d,e) = renderMem a >> renderMem b >> renderMem c >> renderMem d >> renderMem e+ parserMem = (,,,,) <$> parserMem <*> parserMem <*> parserMem <*> parserMem <*> parserMem++#ifdef DATA_DWORD+instance Memorable Word96 where+ type MemLen Word96 = 96+ renderMem (Word96 h l) = renderMem h >> renderMem l+ parserMem = Word96 <$> parserMem <*> parserMem++instance Memorable Word128 where+ type MemLen Word128 = 128+ renderMem (Word128 h l) = renderMem h >> renderMem l+ parserMem = Word128 <$> parserMem <*> parserMem++instance Memorable Word160 where+ type MemLen Word160 = 160+ renderMem (Word160 h l) = renderMem h >> renderMem l+ parserMem = Word160 <$> parserMem <*> parserMem++instance Memorable Word192 where+ type MemLen Word192 = 192+ renderMem (Word192 h l) = renderMem h >> renderMem l+ parserMem = Word192 <$> parserMem <*> parserMem++instance Memorable Word224 where+ type MemLen Word224 = 224+ renderMem (Word224 h l) = renderMem h >> renderMem l+ parserMem = Word224 <$> parserMem <*> parserMem++instance Memorable Word256 where+ type MemLen Word256 = 256+ renderMem (Word256 h l) = renderMem h >> renderMem l+ parserMem = Word256 <$> parserMem <*> parserMem++instance Memorable Int96 where+ type MemLen Int96 = 96+ renderMem (Int96 h l) = renderMem h >> renderMem l+ parserMem = Int96 <$> parserMem <*> parserMem++instance Memorable Int128 where+ type MemLen Int128 = 128+ renderMem (Int128 h l) = renderMem h >> renderMem l+ parserMem = Int128 <$> parserMem <*> parserMem++instance Memorable Int160 where+ type MemLen Int160 = 160+ renderMem (Int160 h l) = renderMem h >> renderMem l+ parserMem = Int160 <$> parserMem <*> parserMem++instance Memorable Int192 where+ type MemLen Int192 = 192+ renderMem (Int192 h l) = renderMem h >> renderMem l+ parserMem = Int192 <$> parserMem <*> parserMem++instance Memorable Int224 where+ type MemLen Int224 = 224+ renderMem (Int224 h l) = renderMem h >> renderMem l+ parserMem = Int224 <$> parserMem <*> parserMem++instance Memorable Int256 where+ type MemLen Int256 = 256+ renderMem (Int256 h l) = renderMem h >> renderMem l+ parserMem = Int256 <$> parserMem <*> parserMem+#endif++#ifdef NETWORK_IP+-- | >>> renderMemorable threeWordsFor32Bits (ip4FromOctets 127 0 0 1)+-- "shore-pick-pets"+instance Memorable IP4 where+ type MemLen IP4 = 32+ renderMem (IP4 w) = renderMem w+ parserMem = IP4 <$> parserMem++instance Memorable IP6 where+ type MemLen IP6 = 128+ renderMem (IP6 w) = renderMem w+ parserMem = IP6 <$> parserMem+#endif++#ifdef CRYPTONITE+#define DIGEST_INST(NAME,BITS) \+instance Memorable (Digest NAME) where {\+ type MemLen (Digest NAME) = BITS; \+ renderMem = mapM_ putUnaligned . B.unpack . convert; \+ parserMem = do { \+ let {b = (BITS) `div` 8;}; \+ Just md <- (digestFromByteString . B.pack) <$> replicateM b (getBitsFrom 7 0); \+ return md;}}++DIGEST_INST(Whirlpool,512)+DIGEST_INST(Blake2s_224,224)+DIGEST_INST(Blake2s_256,256)+DIGEST_INST(Blake2sp_224,224)+DIGEST_INST(Blake2sp_256,256)+DIGEST_INST(Blake2b_512,512)+DIGEST_INST(Blake2bp_512,512)+DIGEST_INST(Tiger,192)+DIGEST_INST(Skein512_512,512)+DIGEST_INST(Skein512_384,384)+DIGEST_INST(Skein512_256,256)+DIGEST_INST(Skein512_224,224)+DIGEST_INST(Skein256_224,224)+DIGEST_INST(Skein256_256,256)+DIGEST_INST(SHA512t_256,256)+DIGEST_INST(SHA512t_224,224)+DIGEST_INST(SHA512,512)+DIGEST_INST(SHA384,384)+DIGEST_INST(SHA3_512,512)+DIGEST_INST(SHA3_384,384)+DIGEST_INST(SHA3_256,256)+DIGEST_INST(SHA3_224,224)+DIGEST_INST(SHA256,256)+DIGEST_INST(SHA224,224)+DIGEST_INST(SHA1,160)+DIGEST_INST(RIPEMD160,160)+-- | +-- >>> :set -XOverloadedStrings+-- >>> import Data.ByteString+-- >>> import Crypto.Hash+-- >>> let myPattern = padHex (four flw10)+-- >>> renderMemorable myPattern (hash ("anExample" :: ByteString) :: Digest MD5)+-- "bark-most-gush-tuft-1b7155ab0dce3ecb4195fc"+DIGEST_INST(MD5,128)+DIGEST_INST(MD4,128)+DIGEST_INST(MD2,128)+DIGEST_INST(Keccak_512,512)+DIGEST_INST(Keccak_384,384)+DIGEST_INST(Keccak_256,256)+DIGEST_INST(Keccak_224,224)+#undef DIGEST_INST+#endif++-- | This is the function to use when you want to turn your values into a+-- memorable strings.+--+-- >>> import Data.Word+-- >>> import Data.Memorable.Theme.Words+-- >>> let myPattern = words8 .- words8+-- >>> renderMemorable myPattern (0x0123 :: Word16)+-- "cats-bulk"+renderMemorable :: (MemRender p, Depth p ~ MemLen a, Memorable a) => Proxy p -> a -> String+renderMemorable p a = renderMemorableByteString p (runRender a)++runRender :: Memorable a => a -> ByteString+runRender c = runPutL (runCoding (renderMem c) (\_ _ _ -> pure ()) 0 0)++runParser :: Memorable a => ByteString -> a+runParser = runGet (runCoding parserMem (\a _ _ -> pure a) 0 0)++-- | Render a `ByteString` as a more memorable `String`.+renderMemorableByteString+ :: MemRender a+ => Proxy a -> ByteString -> String+renderMemorableByteString p =+ runGetL (runCoding (render p) (\a _ _ -> return a) 0 0)+ --runGet (runBitGet . flip evalStateT 0 . unBitPull $ render p)++-- | Generate a random string.+renderRandom+ :: forall a. (MemRender a, KnownNat (Depth a))+ => Proxy a -> IO String+renderRandom p = do+ let+ nBits = getDepth p+ nBytes = fromIntegral $ nBits `div` 8 + 1+ bs <- pack <$> replicateM nBytes randomIO+ return $ renderMemorableByteString p bs+++-- | Render any `Hashable` value as a 32 bit pattern.+renderHashable32 :: (MemRender p, Depth p ~ 32, Hashable a) => Proxy p -> a -> String+renderHashable32 p a = renderMemorable p (fromIntegral $ hash a :: Word32)++-- | Render any `Hashable` value as a 16 bit pattern.+renderHashable16 :: (MemRender p, Depth p ~ 16, Hashable a) => Proxy p -> a -> String+renderHashable16 p a = renderMemorable p (fromIntegral $ hash a :: Word16)++-- | Render any `Hashable` value as a 8 bit pattern.+renderHashable8 :: (MemRender p, Depth p ~ 8, Hashable a) => Proxy p -> a -> String+renderHashable8 p a = renderMemorable p (fromIntegral $ hash a :: Word8)
+ src/Data/Memorable/Theme/Fantasy.hs view
@@ -0,0 +1,287 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE PolyKinds #-}+module Data.Memorable.Theme.Fantasy where++import Data.Memorable++type TransitiveVerb = ToTree+ '[ "blending"+ , "breaking"+ , "bullying"+ , "burning"+ , "charming"+ , "dominating"+ , "fighting"+ , "freezing"+ , "growing"+ , "kicking"+ , "killing"+ , "liquefying"+ , "loving"+ , "melting"+ , "paralyzing"+ , "peeling"+ , "petrifying"+ , "piercing"+ , "pinching"+ , "poking"+ , "punching"+ , "singeing"+ , "slicing"+ , "splattering"+ , "squishing"+ , "tearing"+ , "teasing"+ , "throwing"+ , "tormenting"+ , "tossing"+ , "wooing"+ , "zapping"+ ]++type Weapons = ToTree+ '[ "axe"+ , "club"+ , "dagger"+ , "dart"+ , "flail"+ , "halberd"+ , "hammer"+ , "javelin"+ , "lance"+ , "mace"+ , "pick"+ , "sling"+ , "spear"+ , "staff"+ , "sword"+ , "trident"+ ]++type Armour = ToTree+ '[ "helm"+ , "boots"+ , "vest"+ , "gauntlets"+ , "great helm"+ , "cuirasse"+ , "shield"+ , "pants"+ , "shirt"+ , "greaves"+ , "hat"+ , "leggings"+ , "robes"+ , "sandals"+ , "amulet"+ , "ring"+ ]++type Aura = ToTree+ '[ "unholy"+ , "holy"+ , "sacred"+ , "vile"+ , "shimmering"+ , "glowing"+ , "sparkling"+ , "demonic"+ , "angelic"+ , "cold"+ , "terrifying"+ , "ancient"+ , "mouldy"+ , "elvish"+ , "apocryphal"+ , "ghostly"+ ]++type Monster = ToTree+ '[ "batrachian"+ , "afreet"+ , "ankheg"+ , "annis"+ , "assagim"+ , "babau"+ , "babbler"+ , "banshee"+ , "barghest"+ , "basilisk"+ , "behir"+ , "blindheim"+ , "blinkdog"+ , "brownie"+ , "bugbear"+ , "bulette"+ , "carbuncle"+ , "caterwaul"+ , "centaur"+ , "chimaera"+ , "cockatrice"+ , "crabman"+ , "cyclops"+ , "dakon"+ , "demoniac"+ , "devilcat"+ , "disenchanter"+ , "doppelganger"+ , "dracolisk"+ , "dragon"+ , "dretch"+ , "dryad"+ , "elemental"+ , "erinyes"+ , "ettercap"+ , "ettin"+ , "faun"+ , "gargoyle"+ , "genie"+ , "ghast"+ , "ghost"+ , "ghoul"+ , "giant"+ , "gnoll"+ , "goblin"+ , "golem"+ , "gorgon"+ , "griffon"+ , "grimlock"+ , "harpy"+ , "hellhound"+ , "hippogriff"+ , "hobgoblin"+ , "homonculus"+ , "hydra"+ , "imp"+ , "jackalwere"+ , "kobold"+ , "kraken"+ , "lemure"+ , "leprechaun"+ , "lich"+ , "locathah"+ , "manticore"+ , "medusa"+ , "mephit"+ , "merman"+ , "minotaur"+ , "mongrelman"+ , "mould"+ , "mummy"+ , "naga"+ , "necrophidius"+ , "nereid"+ , "nightmare"+ , "nilbog"+ , "nixie"+ , "nymph"+ , "ogre"+ , "orc"+ , "otyugh"+ , "owlbear"+ , "pegasus"+ , "phantom"+ , "phoenix"+ , "piercer"+ , "pixie"+ , "poltergeist"+ , "pseudodragon"+ , "quasit"+ , "quickling"+ , "rakshasa"+ , "remorhaz"+ , "roc"+ , "roper"+ , "sahuagin"+ , "shaitan"+ , "shedu"+ , "shrieker"+ , "shub"+ , "skeleton"+ , "spectre"+ , "sphinx"+ , "sprite"+ , "squealer"+ , "stirge"+ , "stunjelly"+ , "succubus"+ , "sylph"+ , "titan"+ , "treant"+ , "triton"+ , "troglodyte"+ , "troll"+ , "uduk"+ , "unicorn"+ , "vampire"+ , "vilstrak"+ , "volt"+ , "vulchling"+ , "wererat"+ , "werewolf"+ , "wight"+ , "wraith"+ , "wyvern"+ , "xorn"+ , "yeti"+ , "zombie"+ ]++type ArmourMaterial = ToTree+ '[ "bronze"+ , "cloth"+ , "golden"+ , "iron"+ , "leather"+ , "silk"+ , "studded"+ , "wooden"+ , "crystal"+ , "mythril"+ , "bone"+ , "stone"+ , "silver"+ , "glass"+ , "paper"+ , "fur"+ ]++type Buff = ToTree+ '[ "accuracy"+ , "climbing"+ , "constitution"+ , "dexterity"+ , "fear"+ , "fortitude"+ , "hope"+ , "invisibility"+ , "paralysis"+ , "protection"+ , "speed"+ , "swimming"+ , "telekinesis"+ , "warmth"+ , "wisdom"+ , "wizardry"+ ]++type RpgWeapons = Weapons :- "of" :- Monster :- TransitiveVerb++type RpgArmour = ArmourMaterial :- Armour :- "of" :- Buff++{-+rpgThings = aura .- (w .| a)+ where+ w = Proxy :: Proxy RpgWeapons+ a = Proxy :: Proxy RpgArmour+ aura = Proxy :: Proxy Aura+-}++rpgWeapons :: Proxy (Aura :- RpgWeapons)+rpgWeapons = Proxy++rpgArmour :: Proxy (Aura :- RpgArmour)+rpgArmour = Proxy+
+ src/Data/Memorable/Theme/Food.hs view
@@ -0,0 +1,54 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE PolyKinds #-}+module Data.Memorable.Theme.Food where++import Data.Memorable++type DessertFlavours = ToTree+ '[ "strawberry"+ , "chocolate"+ , "vanilla"+ , "blueberry"+ , "raspberry"+ , "apple"+ , "almond"+ , "cherry"+ ]++type DessertTypes = ToTree+ '[ "custard"+ , "parfait"+ , "souffle"+ , "pancakes"+ , "icecream"+ , "tart"+ , "pie"+ , "cupcakes"+ ]++type FoodAdjectives = ToTree+ '[ "delicious"+ , "rancid"+ , "heavenly"+ , "scrumptious"+ , "delightful"+ , "disgusting"+ , "foul"+ , "exotic"+ , "bland"+ , "gourmet"+ , "tasty"+ , "tasteless"+ , "refreshing"+ , "sensational"+ , "crunchy"+ , "creamy"+ ]++type Desserts+ = FoodAdjectives :- DessertFlavours :- DessertTypes++desserts :: Proxy Desserts+desserts = Proxy+
+ src/Data/Memorable/Theme/Metric.hs view
@@ -0,0 +1,63 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE PolyKinds #-}+module Data.Memorable.Theme.Metric where++import Data.Memorable++type UnitsPlural = ToTree+ '[ "meters"+ , "seconds"+ , "grams"+ , "liters"+ , "volts"+ , "ohms"+ , "amps"+ , "henries"+ ]++type Units = ToTree+ '[ "meter"+ , "second"+ , "gram"+ , "liter"+ , "volt"+ , "ohm"+ , "amp"+ , "henry"+ ]++type SIPrefix = ToTree+ '[ "atto"+ , "femto"+ , "pico"+ , "nano"+ , "micro"+ , "milli"+ , "centi"+ , "deci"+ , "deca"+ , "hecto"+ , "kilo"+ , "mega"+ , "giga"+ , "tera"+ , "peta"+ , "exa"+ ]++type Measurements+ = Number Dec 5+ :- "."+ :- Number Dec 3+ :- " "+ :- SIPrefix+ :- UnitsPlural+ :- " per "+ :- SIPrefix+ :- Units+ :- "^"+ :- NumberWithOffset Dec 2 2++measurements :: Proxy Measurements+measurements = Proxy
+ src/Data/Memorable/Theme/Science.hs view
@@ -0,0 +1,16 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE PolyKinds #-}+module Data.Memorable.Theme.Science where++import Data.Memorable+++type NumericPrefix = ToTree '["di", "tri", "4", "5", "9", "mono", "8", "hex"]+type ChemPrefix = ToTree '["propyl", "ethyl", "pyridine", "chloro", "methyl", "benzene", "hydro", "ferro"]+type ChemSuffix = ToTree '["oxide", "carbide", "sulfide", "fluoride"]+type ChemBabble = NumericPrefix :- ChemPrefix :- NumericPrefix :- ChemPrefix :- ChemSuffix++chemBabble :: Proxy ChemBabble+chemBabble = Proxy+
+ src/Data/Memorable/Theme/Words.hs view
@@ -0,0 +1,5233 @@+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE UndecidableInstances #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE PolyKinds #-}++-- | Words is special because GHC can't handle this many Symbols.+-- The `Choose` trick helps, but only to about 1024 words, after+-- which the compiler fails.+module Data.Memorable.Theme.Words where++import Data.Memorable+++type Words12 = Words+type Words11 = LeftSide Words12+type Words10 = LeftSide Words11+type Words9 = LeftSide Words10+type Words8 = LeftSide Words9+type Words7 = LeftSide Words8+type Words6 = LeftSide Words7+type Words5 = LeftSide Words6+type Words4 = LeftSide Words5++-- | A collection of words+words12 :: Proxy Words12+words12 = Proxy++words11 :: Proxy Words11+words11 = leftSide words12++words10 :: Proxy Words10+words10 = leftSide words11++words9 :: Proxy Words9+words9 = leftSide words10++words8 :: Proxy Words8+words8 = leftSide words9++words7 :: Proxy Words7+words7 = leftSide words8++words6 :: Proxy Words6+words6 = leftSide words7++words5 :: Proxy Words5+words5 = leftSide words6++words4 :: Proxy Words4+words4 = leftSide words5++-- | Three words made to fit into 32 bits+threeWordsFor32Bits :: Proxy (Words12 :- Words10 :- Words10)+threeWordsFor32Bits = Proxy++-- | Six words made to fit into 64 bits+sixWordsFor64Bits :: Proxy (Words12 :- Words12 :- Words10 :- Words9 :- Words9 :- Words12)+sixWordsFor64Bits = Proxy++type FLW10 = FourLetterWords+type FLW9 = LeftSide FLW10+type FLW8 = LeftSide FLW9+type FLW7 = LeftSide FLW8+type FLW6 = LeftSide FLW7+type FLW5 = LeftSide FLW6+type FLW4 = LeftSide FLW5++-- | Four letter words (useful for even lengths)+flw10 :: Proxy FLW10+flw10 = Proxy++flw9 :: Proxy FLW9+flw9 = Proxy ++flw8 :: Proxy FLW8+flw8 = leftSide flw9++flw7 :: Proxy FLW7+flw7 = leftSide flw8++flw6 :: Proxy FLW6+flw6 = leftSide flw7++flw5 :: Proxy FLW5+flw5 = leftSide flw6++flw4 :: Proxy FLW4+flw4 = leftSide flw5++-- | Four equal length words made to fit 32 bits+fourEqualWordsFor32Bits :: Proxy (FLW8 :- FLW8 :- FLW8 :- FLW8)+fourEqualWordsFor32Bits = Proxy++-- | Seven equal length words for 64 bits+sevenEqualWordsFor64Bits :: Proxy (FLW10 :- FLW9 :- FLW9 :- FLW9 :- FLW9 :- FLW9 :- FLW9)+sevenEqualWordsFor64Bits = Proxy++-- | Eight equal length words made to fit 64 bits+eightEqualWordsFor64Bits :: Proxy (FLW8 :- FLW8 :- FLW8 :- FLW8 :- FLW8 :- FLW8 :- FLW8 :- FLW8)+eightEqualWordsFor64Bits = Proxy++-- Sorted by word length so that when you `leftSide` it, you keep only the+-- smaller words.+type Words = ToTree+ '[ "act"+ , "ads"+ , "ago"+ , "air"+ , "all"+ , "and"+ , "any"+ , "art"+ , "ash"+ , "ass"+ , "bad"+ , "bag"+ , "big"+ , "bit"+ , "bow"+ , "box"+ , "bus"+ , "but"+ , "cry"+ , "dry"+ , "far"+ , "fat"+ , "few"+ , "fix"+ , "fly"+ , "fog"+ , "for"+ , "fun"+ , "gas"+ , "hey"+ , "him"+ , "his"+ , "hot"+ , "how"+ , "ice"+ , "ill"+ , "its"+ , "joy"+ , "let"+ , "lit"+ , "low"+ , "mad"+ , "man"+ , "may"+ , "mix"+ , "mud"+ , "nor"+ , "not"+ , "now"+ , "off"+ , "old"+ , "one"+ , "out"+ , "per"+ , "raw"+ , "red"+ , "sad"+ , "say"+ , "see"+ , "sex"+ , "she"+ , "shy"+ , "sir"+ , "six"+ , "sky"+ , "spy"+ , "tax"+ , "the"+ , "too"+ , "try"+ , "via"+ , "who"+ , "why"+ , "yes"+ , "yet"+ , "you"+ , "able"+ , "ache"+ , "acid"+ , "acne"+ , "acre"+ , "acts"+ , "adds"+ , "afar"+ , "ages"+ , "ahoy"+ , "aide"+ , "aids"+ , "ails"+ , "aims"+ , "ajar"+ , "akin"+ , "alas"+ , "ally"+ , "also"+ , "amid"+ , "anew"+ , "anti"+ , "apex"+ , "aqua"+ , "arch"+ , "area"+ , "aria"+ , "arms"+ , "army"+ , "arts"+ , "asks"+ , "atom"+ , "atop"+ , "aunt"+ , "aura"+ , "auto"+ , "avid"+ , "avow"+ , "away"+ , "awry"+ , "axis"+ , "axle"+ , "babe"+ , "baby"+ , "back"+ , "bade"+ , "bags"+ , "bail"+ , "bait"+ , "bake"+ , "bald"+ , "bale"+ , "balk"+ , "ball"+ , "balm"+ , "band"+ , "bane"+ , "bang"+ , "bank"+ , "bans"+ , "barb"+ , "bard"+ , "bare"+ , "bark"+ , "barn"+ , "bars"+ , "base"+ , "bash"+ , "bask"+ , "bass"+ , "bath"+ , "bats"+ , "bawl"+ , "bays"+ , "bead"+ , "beak"+ , "beam"+ , "bean"+ , "bear"+ , "beat"+ , "beds"+ , "beef"+ , "been"+ , "beep"+ , "beer"+ , "bees"+ , "begs"+ , "bell"+ , "belt"+ , "bend"+ , "bent"+ , "best"+ , "beta"+ , "bets"+ , "bias"+ , "bids"+ , "bike"+ , "bile"+ , "bill"+ , "bind"+ , "bird"+ , "bite"+ , "bled"+ , "blip"+ , "blob"+ , "blog"+ , "blot"+ , "blow"+ , "blue"+ , "blur"+ , "boar"+ , "boat"+ , "bode"+ , "body"+ , "boil"+ , "bold"+ , "bolt"+ , "bomb"+ , "bond"+ , "bone"+ , "bong"+ , "bonk"+ , "bony"+ , "book"+ , "boom"+ , "boon"+ , "boot"+ , "bore"+ , "born"+ , "boss"+ , "both"+ , "bout"+ , "bowl"+ , "boys"+ , "brag"+ , "bran"+ , "brat"+ , "bred"+ , "brew"+ , "brim"+ , "buck"+ , "buff"+ , "bugs"+ , "bulb"+ , "bulk"+ , "bull"+ , "bump"+ , "bunk"+ , "buoy"+ , "burn"+ , "burp"+ , "bury"+ , "bush"+ , "busk"+ , "bust"+ , "busy"+ , "butt"+ , "buys"+ , "byte"+ , "cabs"+ , "cage"+ , "cake"+ , "call"+ , "calm"+ , "came"+ , "camp"+ , "cane"+ , "cans"+ , "cape"+ , "caps"+ , "card"+ , "care"+ , "cars"+ , "cart"+ , "case"+ , "cash"+ , "cask"+ , "cast"+ , "cats"+ , "cave"+ , "cede"+ , "cell"+ , "cent"+ , "chap"+ , "chat"+ , "chef"+ , "chew"+ , "chin"+ , "chip"+ , "chop"+ , "chug"+ , "cite"+ , "city"+ , "clad"+ , "clam"+ , "clan"+ , "clap"+ , "claw"+ , "clay"+ , "clip"+ , "clog"+ , "clop"+ , "clot"+ , "club"+ , "clue"+ , "coal"+ , "coat"+ , "code"+ , "coil"+ , "coin"+ , "cold"+ , "colt"+ , "coma"+ , "comb"+ , "come"+ , "cone"+ , "cook"+ , "cool"+ , "cope"+ , "cops"+ , "copy"+ , "cord"+ , "core"+ , "cork"+ , "corn"+ , "cost"+ , "cosy"+ , "coup"+ , "cove"+ , "cowl"+ , "cows"+ , "cozy"+ , "crab"+ , "cram"+ , "crew"+ , "crib"+ , "crop"+ , "crow"+ , "crux"+ , "cube"+ , "cues"+ , "cuff"+ , "cull"+ , "cult"+ , "cups"+ , "curb"+ , "curd"+ , "cure"+ , "curl"+ , "cusp"+ , "cuss"+ , "cute"+ , "cuts"+ , "cyan"+ , "cyst"+ , "czar"+ , "dads"+ , "daft"+ , "dame"+ , "damn"+ , "damp"+ , "dams"+ , "dare"+ , "dark"+ , "dart"+ , "dash"+ , "data"+ , "date"+ , "dawn"+ , "days"+ , "daze"+ , "dead"+ , "deaf"+ , "deal"+ , "dean"+ , "dear"+ , "debt"+ , "deck"+ , "deed"+ , "deem"+ , "deep"+ , "deer"+ , "deft"+ , "defy"+ , "deli"+ , "dell"+ , "demo"+ , "dent"+ , "deny"+ , "desk"+ , "dial"+ , "dice"+ , "died"+ , "dies"+ , "diet"+ , "diff"+ , "digs"+ , "dill"+ , "dime"+ , "dine"+ , "dips"+ , "dire"+ , "dirt"+ , "disc"+ , "dish"+ , "disk"+ , "dive"+ , "dock"+ , "does"+ , "dogs"+ , "doll"+ , "dolt"+ , "dome"+ , "done"+ , "doom"+ , "door"+ , "dope"+ , "dork"+ , "dorm"+ , "dose"+ , "dote"+ , "dots"+ , "dove"+ , "down"+ , "drab"+ , "drag"+ , "draw"+ , "drew"+ , "drip"+ , "drop"+ , "drug"+ , "drum"+ , "dual"+ , "duck"+ , "duct"+ , "dude"+ , "duel"+ , "dues"+ , "duet"+ , "duke"+ , "dull"+ , "duly"+ , "dumb"+ , "dump"+ , "dune"+ , "dunk"+ , "dusk"+ , "dust"+ , "duty"+ , "each"+ , "earl"+ , "earn"+ , "ears"+ , "ease"+ , "east"+ , "easy"+ , "eats"+ , "eave"+ , "echo"+ , "edge"+ , "edgy"+ , "edit"+ , "eels"+ , "eggs"+ , "egos"+ , "else"+ , "emit"+ , "ends"+ , "envy"+ , "epic"+ , "eras"+ , "ergo"+ , "etch"+ , "euro"+ , "even"+ , "ever"+ , "eves"+ , "evil"+ , "exam"+ , "exit"+ , "expo"+ , "eyes"+ , "face"+ , "fact"+ , "fade"+ , "fail"+ , "fair"+ , "fake"+ , "fall"+ , "fame"+ , "fang"+ , "fans"+ , "fare"+ , "farm"+ , "fart"+ , "fast"+ , "fate"+ , "faun"+ , "fawn"+ , "fear"+ , "feat"+ , "feed"+ , "feel"+ , "fees"+ , "feet"+ , "fell"+ , "felt"+ , "fend"+ , "fern"+ , "fest"+ , "feud"+ , "file"+ , "fill"+ , "film"+ , "find"+ , "fine"+ , "fire"+ , "firm"+ , "fish"+ , "fist"+ , "fits"+ , "five"+ , "fizz"+ , "flag"+ , "flak"+ , "flap"+ , "flat"+ , "flaw"+ , "flay"+ , "flea"+ , "fled"+ , "flee"+ , "flew"+ , "flex"+ , "flip"+ , "flog"+ , "flow"+ , "flux"+ , "foal"+ , "foam"+ , "foil"+ , "fold"+ , "folk"+ , "fond"+ , "font"+ , "food"+ , "fool"+ , "foot"+ , "ford"+ , "fore"+ , "fork"+ , "form"+ , "fort"+ , "foul"+ , "four"+ , "fowl"+ , "foxy"+ , "frag"+ , "fray"+ , "free"+ , "fret"+ , "frog"+ , "from"+ , "fuel"+ , "full"+ , "fume"+ , "fund"+ , "furs"+ , "fury"+ , "fuse"+ , "fuss"+ , "gain"+ , "gait"+ , "gala"+ , "game"+ , "gang"+ , "gape"+ , "gaps"+ , "garb"+ , "gash"+ , "gasp"+ , "gate"+ , "gave"+ , "gawk"+ , "gaze"+ , "gear"+ , "geek"+ , "gems"+ , "gene"+ , "germ"+ , "gets"+ , "ghee"+ , "gift"+ , "girl"+ , "gist"+ , "give"+ , "glad"+ , "glam"+ , "glee"+ , "glen"+ , "glib"+ , "glow"+ , "glue"+ , "glum"+ , "gnat"+ , "gnaw"+ , "goal"+ , "goat"+ , "goes"+ , "gold"+ , "golf"+ , "gone"+ , "gong"+ , "good"+ , "goof"+ , "gore"+ , "gory"+ , "gosh"+ , "goth"+ , "gout"+ , "gown"+ , "grab"+ , "gram"+ , "gray"+ , "grew"+ , "grid"+ , "grim"+ , "grin"+ , "grip"+ , "grit"+ , "grow"+ , "grub"+ , "gulf"+ , "gull"+ , "gulp"+ , "gunk"+ , "guns"+ , "guru"+ , "gush"+ , "gust"+ , "guts"+ , "guys"+ , "gyms"+ , "hack"+ , "hail"+ , "hair"+ , "half"+ , "hall"+ , "halo"+ , "halt"+ , "hand"+ , "hang"+ , "hard"+ , "hare"+ , "harm"+ , "harp"+ , "hash"+ , "hate"+ , "hats"+ , "haul"+ , "have"+ , "hawk"+ , "hays"+ , "haze"+ , "hazy"+ , "head"+ , "heal"+ , "heap"+ , "hear"+ , "heat"+ , "heed"+ , "heel"+ , "heft"+ , "heir"+ , "held"+ , "hell"+ , "helm"+ , "help"+ , "hemp"+ , "herb"+ , "herd"+ , "here"+ , "hero"+ , "hers"+ , "hide"+ , "high"+ , "hike"+ , "hill"+ , "hilt"+ , "hind"+ , "hint"+ , "hips"+ , "hire"+ , "hiss"+ , "hits"+ , "hive"+ , "hoax"+ , "hold"+ , "hole"+ , "holy"+ , "home"+ , "hone"+ , "honk"+ , "hood"+ , "hoof"+ , "hook"+ , "hoop"+ , "hoot"+ , "hope"+ , "horn"+ , "hose"+ , "host"+ , "hour"+ , "howl"+ , "huge"+ , "hugs"+ , "hulk"+ , "hull"+ , "hump"+ , "hung"+ , "hunt"+ , "hurl"+ , "hurt"+ , "hush"+ , "husk"+ , "hymn"+ , "hype"+ , "icon"+ , "idea"+ , "idle"+ , "idol"+ , "inch"+ , "info"+ , "into"+ , "iris"+ , "iron"+ , "isle"+ , "itch"+ , "item"+ , "jack"+ , "jade"+ , "jail"+ , "jars"+ , "java"+ , "jaws"+ , "jazz"+ , "jean"+ , "jeer"+ , "jest"+ , "jets"+ , "jinx"+ , "jobs"+ , "john"+ , "join"+ , "joke"+ , "jolt"+ , "jump"+ , "junk"+ , "jury"+ , "just"+ , "keen"+ , "keep"+ , "kelp"+ , "kept"+ , "keys"+ , "kick"+ , "kids"+ , "kill"+ , "kiln"+ , "kilt"+ , "kind"+ , "king"+ , "kink"+ , "kiss"+ , "kite"+ , "kits"+ , "knee"+ , "knew"+ , "knit"+ , "knob"+ , "knot"+ , "know"+ , "labs"+ , "lace"+ , "lack"+ , "lady"+ , "laid"+ , "lair"+ , "lake"+ , "lamb"+ , "lame"+ , "lamp"+ , "land"+ , "lane"+ , "laps"+ , "lard"+ , "lash"+ , "last"+ , "late"+ , "lava"+ , "lawn"+ , "laws"+ , "lays"+ , "laze"+ , "lazy"+ , "lead"+ , "leaf"+ , "leak"+ , "lean"+ , "leap"+ , "leek"+ , "leer"+ , "left"+ , "legs"+ , "lend"+ , "lens"+ , "less"+ , "lest"+ , "levy"+ , "lewd"+ , "liar"+ , "lice"+ , "lick"+ , "lids"+ , "lies"+ , "life"+ , "lift"+ , "like"+ , "limb"+ , "lime"+ , "limp"+ , "line"+ , "link"+ , "lint"+ , "lion"+ , "lips"+ , "lisp"+ , "list"+ , "live"+ , "load"+ , "loaf"+ , "loan"+ , "lobe"+ , "lock"+ , "loft"+ , "logo"+ , "logs"+ , "lone"+ , "long"+ , "look"+ , "loom"+ , "loop"+ , "loot"+ , "lord"+ , "lore"+ , "lose"+ , "loss"+ , "lost"+ , "lots"+ , "loud"+ , "love"+ , "luck"+ , "lull"+ , "lump"+ , "lung"+ , "lure"+ , "lurk"+ , "lush"+ , "lust"+ , "lute"+ , "mace"+ , "made"+ , "maid"+ , "mail"+ , "maim"+ , "main"+ , "make"+ , "male"+ , "mall"+ , "malt"+ , "many"+ , "maps"+ , "mare"+ , "mark"+ , "mash"+ , "mask"+ , "mass"+ , "mast"+ , "mate"+ , "math"+ , "matt"+ , "maul"+ , "maze"+ , "mead"+ , "meal"+ , "mean"+ , "meat"+ , "meek"+ , "meet"+ , "meld"+ , "melt"+ , "meme"+ , "memo"+ , "mend"+ , "menu"+ , "meow"+ , "mere"+ , "mesh"+ , "mess"+ , "mice"+ , "mike"+ , "mild"+ , "mile"+ , "milk"+ , "mill"+ , "mime"+ , "mind"+ , "mine"+ , "mini"+ , "mint"+ , "miss"+ , "mist"+ , "mite"+ , "moan"+ , "moat"+ , "mock"+ , "mode"+ , "mold"+ , "mole"+ , "molt"+ , "monk"+ , "mood"+ , "moon"+ , "mope"+ , "more"+ , "moss"+ , "most"+ , "moth"+ , "move"+ , "much"+ , "mule"+ , "mull"+ , "muse"+ , "mush"+ , "musk"+ , "must"+ , "mute"+ , "mutt"+ , "myth"+ , "nail"+ , "name"+ , "navy"+ , "near"+ , "neat"+ , "neck"+ , "need"+ , "neon"+ , "nerd"+ , "nest"+ , "nets"+ , "news"+ , "newt"+ , "next"+ , "nice"+ , "nick"+ , "nine"+ , "node"+ , "nods"+ , "none"+ , "nook"+ , "noon"+ , "nope"+ , "norm"+ , "nose"+ , "note"+ , "noun"+ , "nude"+ , "nuke"+ , "null"+ , "numb"+ , "nuts"+ , "oaks"+ , "oath"+ , "obey"+ , "oboe"+ , "odds"+ , "odor"+ , "oils"+ , "okay"+ , "omen"+ , "omit"+ , "once"+ , "ones"+ , "only"+ , "onto"+ , "onus"+ , "ooze"+ , "opal"+ , "open"+ , "opts"+ , "oral"+ , "ouch"+ , "ours"+ , "oust"+ , "oval"+ , "oven"+ , "over"+ , "owes"+ , "owns"+ , "pace"+ , "pack"+ , "pact"+ , "pads"+ , "page"+ , "paid"+ , "pail"+ , "pain"+ , "pair"+ , "pale"+ , "pall"+ , "palm"+ , "pane"+ , "pans"+ , "pant"+ , "park"+ , "part"+ , "pass"+ , "past"+ , "path"+ , "pats"+ , "pave"+ , "pawn"+ , "pays"+ , "peak"+ , "pear"+ , "peas"+ , "peck"+ , "peek"+ , "peel"+ , "peep"+ , "peer"+ , "pelt"+ , "pens"+ , "peon"+ , "perk"+ , "pest"+ , "pets"+ , "pick"+ , "pics"+ , "pier"+ , "pies"+ , "pigs"+ , "pike"+ , "pile"+ , "pill"+ , "pine"+ , "pink"+ , "pins"+ , "pint"+ , "pipe"+ , "pits"+ , "pity"+ , "plan"+ , "play"+ , "plea"+ , "plot"+ , "plow"+ , "ploy"+ , "plug"+ , "plum"+ , "plus"+ , "poem"+ , "poet"+ , "poke"+ , "pole"+ , "poll"+ , "pomp"+ , "pond"+ , "pony"+ , "pool"+ , "poor"+ , "pops"+ , "pore"+ , "pork"+ , "port"+ , "pose"+ , "posh"+ , "post"+ , "posy"+ , "pots"+ , "pour"+ , "pout"+ , "pray"+ , "prev"+ , "prey"+ , "prod"+ , "prop"+ , "puck"+ , "puff"+ , "pull"+ , "pulp"+ , "pump"+ , "punk"+ , "punt"+ , "puny"+ , "pure"+ , "push"+ , "puts"+ , "pyre"+ , "quay"+ , "quip"+ , "quit"+ , "quiz"+ , "race"+ , "rack"+ , "raft"+ , "rage"+ , "raid"+ , "rail"+ , "rain"+ , "rake"+ , "ramp"+ , "rang"+ , "rank"+ , "rant"+ , "rape"+ , "rare"+ , "rash"+ , "rate"+ , "rats"+ , "rave"+ , "raze"+ , "read"+ , "real"+ , "reap"+ , "rear"+ , "redo"+ , "reed"+ , "reef"+ , "reek"+ , "reel"+ , "rely"+ , "rent"+ , "rest"+ , "ribs"+ , "rice"+ , "rich"+ , "ride"+ , "rids"+ , "riff"+ , "rift"+ , "rims"+ , "rind"+ , "ring"+ , "rink"+ , "riot"+ , "ripe"+ , "rips"+ , "rise"+ , "risk"+ , "road"+ , "roam"+ , "roar"+ , "robe"+ , "rock"+ , "rods"+ , "role"+ , "roll"+ , "romp"+ , "roof"+ , "rook"+ , "room"+ , "root"+ , "rope"+ , "rose"+ , "rows"+ , "rubs"+ , "ruby"+ , "rude"+ , "ruin"+ , "rule"+ , "rump"+ , "rune"+ , "rung"+ , "runs"+ , "runt"+ , "ruse"+ , "rush"+ , "rusk"+ , "rust"+ , "ruts"+ , "sack"+ , "safe"+ , "saga"+ , "sage"+ , "said"+ , "sail"+ , "sake"+ , "sale"+ , "salt"+ , "same"+ , "sand"+ , "sane"+ , "sang"+ , "sank"+ , "sash"+ , "save"+ , "says"+ , "scab"+ , "scam"+ , "scan"+ , "scar"+ , "scum"+ , "seal"+ , "seam"+ , "sear"+ , "seas"+ , "seat"+ , "seed"+ , "seek"+ , "seem"+ , "seen"+ , "seep"+ , "seer"+ , "self"+ , "sell"+ , "semi"+ , "send"+ , "sent"+ , "serf"+ , "sets"+ , "sewn"+ , "sexy"+ , "shed"+ , "shim"+ , "shin"+ , "ship"+ , "shit"+ , "shod"+ , "shoe"+ , "shop"+ , "shot"+ , "show"+ , "shun"+ , "shut"+ , "sick"+ , "side"+ , "sift"+ , "sigh"+ , "sign"+ , "silk"+ , "sill"+ , "silo"+ , "silt"+ , "sing"+ , "sink"+ , "sins"+ , "sire"+ , "site"+ , "sits"+ , "size"+ , "skew"+ , "skid"+ , "skim"+ , "skin"+ , "skip"+ , "skis"+ , "skit"+ , "slab"+ , "slam"+ , "slap"+ , "slat"+ , "slay"+ , "sled"+ , "slew"+ , "slid"+ , "slim"+ , "slip"+ , "slit"+ , "slob"+ , "slot"+ , "slow"+ , "slug"+ , "slum"+ , "slur"+ , "smog"+ , "smug"+ , "snag"+ , "snap"+ , "snip"+ , "snob"+ , "snot"+ , "snow"+ , "snub"+ , "snug"+ , "soak"+ , "soap"+ , "soar"+ , "sock"+ , "soda"+ , "sofa"+ , "soft"+ , "soil"+ , "sold"+ , "sole"+ , "solo"+ , "some"+ , "song"+ , "sons"+ , "soon"+ , "soot"+ , "sore"+ , "sort"+ , "soul"+ , "soup"+ , "sour"+ , "sown"+ , "sows"+ , "spam"+ , "span"+ , "spar"+ , "spat"+ , "spay"+ , "sped"+ , "spew"+ , "spin"+ , "spit"+ , "spot"+ , "spry"+ , "spun"+ , "spur"+ , "stab"+ , "stag"+ , "star"+ , "stay"+ , "stem"+ , "step"+ , "stew"+ , "stir"+ , "stop"+ , "stow"+ , "stub"+ , "stun"+ , "such"+ , "suck"+ , "sues"+ , "suit"+ , "sulk"+ , "sums"+ , "sung"+ , "sunk"+ , "suns"+ , "sure"+ , "surf"+ , "suss"+ , "swab"+ , "swag"+ , "swam"+ , "swan"+ , "swap"+ , "swat"+ , "sway"+ , "swig"+ , "swim"+ , "swum"+ , "sync"+ , "tack"+ , "tact"+ , "tags"+ , "tail"+ , "take"+ , "tale"+ , "talk"+ , "tall"+ , "tame"+ , "tank"+ , "tape"+ , "taps"+ , "tart"+ , "task"+ , "taut"+ , "taxi"+ , "teal"+ , "team"+ , "tear"+ , "teas"+ , "tech"+ , "teen"+ , "tell"+ , "tend"+ , "tens"+ , "tent"+ , "term"+ , "test"+ , "text"+ , "than"+ , "that"+ , "thaw"+ , "them"+ , "then"+ , "they"+ , "thin"+ , "this"+ , "thud"+ , "thug"+ , "thus"+ , "tick"+ , "tide"+ , "tidy"+ , "tied"+ , "tier"+ , "ties"+ , "tile"+ , "till"+ , "tilt"+ , "time"+ , "tint"+ , "tiny"+ , "tips"+ , "tire"+ , "toad"+ , "toes"+ , "toil"+ , "told"+ , "toll"+ , "tomb"+ , "tone"+ , "tony"+ , "took"+ , "tool"+ , "toot"+ , "tops"+ , "tore"+ , "torn"+ , "toss"+ , "tour"+ , "town"+ , "toys"+ , "tram"+ , "trap"+ , "tray"+ , "tree"+ , "trim"+ , "trip"+ , "trod"+ , "trot"+ , "true"+ , "tuba"+ , "tube"+ , "tuck"+ , "tuft"+ , "tuna"+ , "tune"+ , "turf"+ , "turn"+ , "tusk"+ , "twig"+ , "twin"+ , "twos"+ , "type"+ , "tyre"+ , "ugly"+ , "undo"+ , "unit"+ , "unto"+ , "upon"+ , "urge"+ , "used"+ , "user"+ , "uses"+ , "vain"+ , "vale"+ , "vans"+ , "vary"+ , "vase"+ , "vast"+ , "veal"+ , "veer"+ , "veil"+ , "vein"+ , "vend"+ , "vent"+ , "verb"+ , "very"+ , "vest"+ , "vial"+ , "vibe"+ , "vice"+ , "view"+ , "vile"+ , "vine"+ , "visa"+ , "void"+ , "volt"+ , "vote"+ , "wade"+ , "waft"+ , "wage"+ , "wail"+ , "wait"+ , "wake"+ , "walk"+ , "wall"+ , "wand"+ , "wane"+ , "want"+ , "ward"+ , "ware"+ , "warm"+ , "warn"+ , "warp"+ , "wars"+ , "wart"+ , "wary"+ , "wash"+ , "wasp"+ , "wave"+ , "ways"+ , "weak"+ , "wean"+ , "wear"+ , "webs"+ , "weed"+ , "week"+ , "weep"+ , "weld"+ , "well"+ , "welt"+ , "went"+ , "wept"+ , "were"+ , "west"+ , "wets"+ , "what"+ , "when"+ , "whet"+ , "whey"+ , "whim"+ , "whip"+ , "whom"+ , "wick"+ , "wide"+ , "wife"+ , "wild"+ , "will"+ , "wilt"+ , "wimp"+ , "wind"+ , "wine"+ , "wing"+ , "wink"+ , "wins"+ , "wipe"+ , "wire"+ , "wiry"+ , "wise"+ , "wish"+ , "with"+ , "wits"+ , "woke"+ , "wolf"+ , "womb"+ , "wood"+ , "wool"+ , "word"+ , "wore"+ , "work"+ , "worm"+ , "worn"+ , "wrap"+ , "wren"+ , "wuss"+ , "yank"+ , "yard"+ , "yarn"+ , "yawn"+ , "yeah"+ , "year"+ , "yell"+ , "yelp"+ , "yeti"+ , "yoga"+ , "yolk"+ , "york"+ , "your"+ , "yowl"+ , "zeal"+ , "zero"+ , "zest"+ , "zone"+ , "zoom"+ , "about"+ , "above"+ , "abuse"+ , "actor"+ , "adapt"+ , "added"+ , "admit"+ , "adopt"+ , "adult"+ , "after"+ , "again"+ , "agent"+ , "agree"+ , "ahead"+ , "aisle"+ , "alarm"+ , "album"+ , "alien"+ , "alike"+ , "alive"+ , "alley"+ , "allow"+ , "alone"+ , "along"+ , "alter"+ , "amaze"+ , "among"+ , "angel"+ , "anger"+ , "angle"+ , "angry"+ , "ankle"+ , "apart"+ , "apple"+ , "apply"+ , "arena"+ , "argue"+ , "arise"+ , "armed"+ , "array"+ , "arrow"+ , "aside"+ , "asset"+ , "avoid"+ , "await"+ , "awake"+ , "award"+ , "aware"+ , "awful"+ , "badly"+ , "basic"+ , "basis"+ , "beach"+ , "beard"+ , "beast"+ , "begin"+ , "being"+ , "belly"+ , "below"+ , "bench"+ , "birth"+ , "black"+ , "blade"+ , "blame"+ , "blank"+ , "blast"+ , "blend"+ , "bless"+ , "blind"+ , "blink"+ , "block"+ , "blond"+ , "blood"+ , "board"+ , "boast"+ , "bonus"+ , "boost"+ , "booth"+ , "brain"+ , "brake"+ , "brand"+ , "brave"+ , "bread"+ , "break"+ , "brick"+ , "bride"+ , "brief"+ , "bring"+ , "broad"+ , "brown"+ , "brush"+ , "buddy"+ , "build"+ , "bunch"+ , "burst"+ , "buyer"+ , "cabin"+ , "cable"+ , "candy"+ , "cargo"+ , "carry"+ , "carve"+ , "catch"+ , "cause"+ , "cease"+ , "chain"+ , "chair"+ , "chaos"+ , "charm"+ , "chart"+ , "chase"+ , "cheap"+ , "cheat"+ , "check"+ , "cheek"+ , "cheer"+ , "chest"+ , "chief"+ , "child"+ , "chill"+ , "chunk"+ , "civic"+ , "civil"+ , "claim"+ , "class"+ , "clean"+ , "clear"+ , "clerk"+ , "click"+ , "cliff"+ , "climb"+ , "cling"+ , "clock"+ , "close"+ , "cloth"+ , "cloud"+ , "coach"+ , "coast"+ , "color"+ , "couch"+ , "could"+ , "count"+ , "court"+ , "cover"+ , "crack"+ , "craft"+ , "crash"+ , "crawl"+ , "crazy"+ , "cream"+ , "crest"+ , "crime"+ , "cross"+ , "crowd"+ , "cruel"+ , "crush"+ , "curve"+ , "cycle"+ , "daily"+ , "dance"+ , "death"+ , "debut"+ , "delay"+ , "dense"+ , "depth"+ , "devil"+ , "diary"+ , "dirty"+ , "donor"+ , "doubt"+ , "dough"+ , "dozen"+ , "draft"+ , "drain"+ , "drama"+ , "dream"+ , "dress"+ , "dried"+ , "drift"+ , "drill"+ , "drink"+ , "drive"+ , "drown"+ , "drunk"+ , "dying"+ , "eager"+ , "early"+ , "earth"+ , "eight"+ , "elbow"+ , "elder"+ , "elect"+ , "elite"+ , "empty"+ , "enact"+ , "enemy"+ , "enjoy"+ , "enter"+ , "entry"+ , "equal"+ , "equip"+ , "error"+ , "essay"+ , "event"+ , "every"+ , "exact"+ , "exist"+ , "extra"+ , "faint"+ , "faith"+ , "false"+ , "fatal"+ , "fault"+ , "favor"+ , "fence"+ , "fever"+ , "fewer"+ , "fiber"+ , "field"+ , "fifth"+ , "fifty"+ , "fight"+ , "final"+ , "first"+ , "fixed"+ , "flame"+ , "flash"+ , "fleet"+ , "flesh"+ , "float"+ , "flood"+ , "floor"+ , "flour"+ , "fluid"+ , "focus"+ , "force"+ , "forth"+ , "forty"+ , "forum"+ , "found"+ , "frame"+ , "fraud"+ , "fresh"+ , "front"+ , "frown"+ , "fruit"+ , "fully"+ , "funny"+ , "genre"+ , "ghost"+ , "giant"+ , "given"+ , "glass"+ , "globe"+ , "glory"+ , "glove"+ , "grace"+ , "grade"+ , "grain"+ , "grand"+ , "grant"+ , "grape"+ , "grasp"+ , "grass"+ , "grave"+ , "great"+ , "green"+ , "greet"+ , "grief"+ , "gross"+ , "group"+ , "guard"+ , "guess"+ , "guest"+ , "guide"+ , "guilt"+ , "habit"+ , "happy"+ , "harsh"+ , "heart"+ , "heavy"+ , "hello"+ , "hence"+ , "honey"+ , "honor"+ , "horse"+ , "hotel"+ , "house"+ , "human"+ , "humor"+ , "hurry"+ , "ideal"+ , "image"+ , "imply"+ , "index"+ , "inner"+ , "input"+ , "irony"+ , "issue"+ , "jeans"+ , "jewel"+ , "joint"+ , "judge"+ , "juice"+ , "juror"+ , "kneel"+ , "knife"+ , "knock"+ , "known"+ , "label"+ , "labor"+ , "large"+ , "laser"+ , "later"+ , "laugh"+ , "layer"+ , "learn"+ , "least"+ , "leave"+ , "legal"+ , "lemon"+ , "level"+ , "light"+ , "limit"+ , "liver"+ , "lobby"+ , "local"+ , "logic"+ , "loose"+ , "lover"+ , "lower"+ , "loyal"+ , "lucky"+ , "lunch"+ , "magic"+ , "major"+ , "maker"+ , "march"+ , "marry"+ , "match"+ , "maybe"+ , "mayor"+ , "medal"+ , "media"+ , "merit"+ , "metal"+ , "meter"+ , "midst"+ , "might"+ , "minor"+ , "mixed"+ , "model"+ , "money"+ , "month"+ , "moral"+ , "motor"+ , "mount"+ , "mouse"+ , "mouth"+ , "movie"+ , "music"+ , "naked"+ , "nasty"+ , "naval"+ , "nerve"+ , "never"+ , "newly"+ , "night"+ , "noise"+ , "north"+ , "novel"+ , "nurse"+ , "occur"+ , "ocean"+ , "offer"+ , "often"+ , "onion"+ , "opera"+ , "orbit"+ , "order"+ , "organ"+ , "other"+ , "ought"+ , "outer"+ , "owner"+ , "paint"+ , "panel"+ , "panic"+ , "paper"+ , "party"+ , "pasta"+ , "patch"+ , "pause"+ , "peace"+ , "pearl"+ , "phase"+ , "phone"+ , "photo"+ , "piano"+ , "piece"+ , "pilot"+ , "pitch"+ , "pizza"+ , "place"+ , "plain"+ , "plane"+ , "plant"+ , "plate"+ , "plead"+ , "point"+ , "porch"+ , "pound"+ , "power"+ , "press"+ , "price"+ , "pride"+ , "prime"+ , "print"+ , "prior"+ , "prize"+ , "proof"+ , "proud"+ , "prove"+ , "pulse"+ , "punch"+ , "purse"+ , "queen"+ , "quest"+ , "quick"+ , "quiet"+ , "quite"+ , "quote"+ , "radar"+ , "radio"+ , "raise"+ , "rally"+ , "ranch"+ , "range"+ , "rapid"+ , "ratio"+ , "reach"+ , "react"+ , "ready"+ , "realm"+ , "rebel"+ , "refer"+ , "relax"+ , "reply"+ , "rhyme"+ , "rider"+ , "ridge"+ , "rifle"+ , "right"+ , "risky"+ , "rival"+ , "river"+ , "robot"+ , "rough"+ , "round"+ , "route"+ , "royal"+ , "rumor"+ , "rural"+ , "salad"+ , "sales"+ , "sauce"+ , "scale"+ , "scare"+ , "scary"+ , "scene"+ , "scent"+ , "scope"+ , "score"+ , "screw"+ , "seize"+ , "sense"+ , "serve"+ , "seven"+ , "shade"+ , "shake"+ , "shall"+ , "shame"+ , "shape"+ , "share"+ , "shark"+ , "sharp"+ , "sheep"+ , "sheer"+ , "sheet"+ , "shelf"+ , "shell"+ , "shift"+ , "shine"+ , "shirt"+ , "shock"+ , "shoot"+ , "shore"+ , "short"+ , "shout"+ , "shove"+ , "shrug"+ , "sight"+ , "silly"+ , "since"+ , "sixth"+ , "skill"+ , "skirt"+ , "skull"+ , "slave"+ , "sleep"+ , "slice"+ , "slide"+ , "slope"+ , "small"+ , "smart"+ , "smell"+ , "smile"+ , "smoke"+ , "snake"+ , "sneak"+ , "solar"+ , "solid"+ , "solve"+ , "sorry"+ , "sound"+ , "south"+ , "space"+ , "spare"+ , "spark"+ , "speak"+ , "speed"+ , "spell"+ , "spend"+ , "spill"+ , "spine"+ , "spite"+ , "split"+ , "spoon"+ , "sport"+ , "spray"+ , "squad"+ , "stack"+ , "staff"+ , "stage"+ , "stair"+ , "stake"+ , "stand"+ , "stare"+ , "start"+ , "state"+ , "steak"+ , "steal"+ , "steam"+ , "steel"+ , "steep"+ , "steer"+ , "stick"+ , "stiff"+ , "still"+ , "stock"+ , "stone"+ , "stoop"+ , "store"+ , "storm"+ , "story"+ , "stove"+ , "straw"+ , "strip"+ , "study"+ , "stuff"+ , "style"+ , "sugar"+ , "suite"+ , "sunny"+ , "super"+ , "swear"+ , "sweat"+ , "sweep"+ , "sweet"+ , "swell"+ , "swing"+ , "sword"+ , "table"+ , "taste"+ , "teach"+ , "terms"+ , "thank"+ , "their"+ , "theme"+ , "there"+ , "these"+ , "thick"+ , "thigh"+ , "thing"+ , "think"+ , "third"+ , "those"+ , "three"+ , "throw"+ , "thumb"+ , "tight"+ , "tired"+ , "title"+ , "today"+ , "tooth"+ , "topic"+ , "total"+ , "touch"+ , "tough"+ , "towel"+ , "tower"+ , "toxic"+ , "trace"+ , "track"+ , "trade"+ , "trail"+ , "train"+ , "trait"+ , "trash"+ , "treat"+ , "trend"+ , "trial"+ , "tribe"+ , "trick"+ , "troop"+ , "truck"+ , "truly"+ , "trunk"+ , "trust"+ , "truth"+ , "tumor"+ , "twice"+ , "twist"+ , "uncle"+ , "under"+ , "union"+ , "unite"+ , "unity"+ , "until"+ , "upper"+ , "upset"+ , "urban"+ , "usual"+ , "valid"+ , "value"+ , "video"+ , "virus"+ , "visit"+ , "vital"+ , "vocal"+ , "voice"+ , "voter"+ , "wagon"+ , "waist"+ , "waste"+ , "watch"+ , "water"+ , "weave"+ , "weigh"+ , "weird"+ , "whale"+ , "wheat"+ , "wheel"+ , "where"+ , "which"+ , "while"+ , "white"+ , "whole"+ , "whose"+ , "widow"+ , "woman"+ , "works"+ , "world"+ , "worry"+ , "worth"+ , "would"+ , "wound"+ , "wrist"+ , "write"+ , "wrong"+ , "yield"+ , "young"+ , "yours"+ , "youth"+ , "abroad"+ , "absorb"+ , "accent"+ , "accept"+ , "access"+ , "accuse"+ , "across"+ , "action"+ , "active"+ , "actual"+ , "adjust"+ , "admire"+ , "advice"+ , "advise"+ , "affair"+ , "affect"+ , "afford"+ , "afraid"+ , "agency"+ , "agenda"+ , "almost"+ , "always"+ , "amount"+ , "animal"+ , "annual"+ , "answer"+ , "anyone"+ , "anyway"+ , "appeal"+ , "appear"+ , "around"+ , "arrest"+ , "arrive"+ , "artist"+ , "asleep"+ , "aspect"+ , "assert"+ , "assess"+ , "assign"+ , "assist"+ , "assume"+ , "assure"+ , "attach"+ , "attack"+ , "attend"+ , "author"+ , "ballot"+ , "banana"+ , "banker"+ , "barely"+ , "barrel"+ , "basket"+ , "battle"+ , "beauty"+ , "become"+ , "before"+ , "behalf"+ , "behave"+ , "behind"+ , "belief"+ , "belong"+ , "beside"+ , "better"+ , "beyond"+ , "bishop"+ , "bitter"+ , "bloody"+ , "border"+ , "boring"+ , "borrow"+ , "bother"+ , "bottle"+ , "bottom"+ , "bounce"+ , "branch"+ , "breast"+ , "breath"+ , "breeze"+ , "bridge"+ , "bright"+ , "broken"+ , "broker"+ , "bronze"+ , "brutal"+ , "bubble"+ , "bucket"+ , "budget"+ , "bullet"+ , "burden"+ , "bureau"+ , "butter"+ , "button"+ , "camera"+ , "campus"+ , "cancel"+ , "cancer"+ , "candle"+ , "canvas"+ , "carbon"+ , "career"+ , "carpet"+ , "carrot"+ , "casino"+ , "casual"+ , "cattle"+ , "center"+ , "chance"+ , "change"+ , "charge"+ , "cheese"+ , "choice"+ , "choose"+ , "church"+ , "circle"+ , "client"+ , "clinic"+ , "closed"+ , "closer"+ , "closet"+ , "coffee"+ , "collar"+ , "colony"+ , "column"+ , "combat"+ , "comedy"+ , "coming"+ , "commit"+ , "common"+ , "compel"+ , "comply"+ , "convey"+ , "cookie"+ , "corner"+ , "costly"+ , "cotton"+ , "county"+ , "couple"+ , "course"+ , "cousin"+ , "create"+ , "credit"+ , "crisis"+ , "critic"+ , "cruise"+ , "custom"+ , "damage"+ , "dancer"+ , "danger"+ , "deadly"+ , "dealer"+ , "debate"+ , "debris"+ , "decade"+ , "decent"+ , "decide"+ , "deeply"+ , "defeat"+ , "defend"+ , "define"+ , "degree"+ , "demand"+ , "denial"+ , "depart"+ , "depend"+ , "depict"+ , "deploy"+ , "deputy"+ , "derive"+ , "desert"+ , "design"+ , "desire"+ , "detail"+ , "detect"+ , "device"+ , "devote"+ , "differ"+ , "dining"+ , "dinner"+ , "direct"+ , "divide"+ , "divine"+ , "doctor"+ , "domain"+ , "donate"+ , "double"+ , "drawer"+ , "driver"+ , "during"+ , "easily"+ , "eating"+ , "editor"+ , "effect"+ , "effort"+ , "eighth"+ , "either"+ , "emerge"+ , "empire"+ , "employ"+ , "enable"+ , "endure"+ , "energy"+ , "engage"+ , "engine"+ , "enough"+ , "enroll"+ , "ensure"+ , "entire"+ , "entity"+ , "equity"+ , "escape"+ , "estate"+ , "ethics"+ , "ethnic"+ , "evolve"+ , "exceed"+ , "except"+ , "excuse"+ , "exotic"+ , "expand"+ , "expect"+ , "expert"+ , "export"+ , "expose"+ , "extend"+ , "extent"+ , "fabric"+ , "factor"+ , "fairly"+ , "family"+ , "famous"+ , "farmer"+ , "faster"+ , "father"+ , "fellow"+ , "female"+ , "fierce"+ , "figure"+ , "filter"+ , "finger"+ , "finish"+ , "firmly"+ , "fiscal"+ , "flavor"+ , "flight"+ , "flower"+ , "flying"+ , "follow"+ , "forbid"+ , "forest"+ , "forget"+ , "formal"+ , "format"+ , "former"+ , "foster"+ , "fourth"+ , "freely"+ , "freeze"+ , "friend"+ , "frozen"+ , "future"+ , "galaxy"+ , "garage"+ , "garden"+ , "garlic"+ , "gather"+ , "gender"+ , "genius"+ , "gentle"+ , "gently"+ , "gifted"+ , "glance"+ , "global"+ , "golden"+ , "govern"+ , "ground"+ , "growth"+ , "guilty"+ , "guitar"+ , "handle"+ , "happen"+ , "hardly"+ , "hazard"+ , "health"+ , "heaven"+ , "height"+ , "helmet"+ , "hidden"+ , "highly"+ , "hockey"+ , "honest"+ , "horror"+ , "hunger"+ , "hungry"+ , "hunter"+ , "ignore"+ , "immune"+ , "impact"+ , "import"+ , "impose"+ , "income"+ , "indeed"+ , "infant"+ , "inform"+ , "injure"+ , "injury"+ , "inmate"+ , "insect"+ , "insert"+ , "inside"+ , "insist"+ , "intact"+ , "intend"+ , "intent"+ , "invade"+ , "invent"+ , "invest"+ , "invite"+ , "island"+ , "itself"+ , "jacket"+ , "jungle"+ , "junior"+ , "killer"+ , "ladder"+ , "lately"+ , "latter"+ , "launch"+ , "lawyer"+ , "leader"+ , "league"+ , "legacy"+ , "legend"+ , "length"+ , "lesson"+ , "letter"+ , "likely"+ , "liquid"+ , "listen"+ , "little"+ , "living"+ , "locate"+ , "lonely"+ , "lovely"+ , "mainly"+ , "makeup"+ , "manage"+ , "manner"+ , "manual"+ , "marble"+ , "margin"+ , "marine"+ , "marker"+ , "market"+ , "master"+ , "matter"+ , "medium"+ , "member"+ , "memory"+ , "mental"+ , "mentor"+ , "merely"+ , "method"+ , "middle"+ , "minute"+ , "mirror"+ , "mobile"+ , "modern"+ , "modest"+ , "modify"+ , "moment"+ , "monkey"+ , "mostly"+ , "mother"+ , "motion"+ , "motive"+ , "murder"+ , "muscle"+ , "museum"+ , "mutter"+ , "mutual"+ , "myself"+ , "narrow"+ , "nation"+ , "native"+ , "nature"+ , "nearby"+ , "nearly"+ , "needle"+ , "nobody"+ , "normal"+ , "notice"+ , "notion"+ , "number"+ , "object"+ , "obtain"+ , "occupy"+ , "office"+ , "online"+ , "openly"+ , "oppose"+ , "option"+ , "orange"+ , "origin"+ , "others"+ , "outfit"+ , "outlet"+ , "output"+ , "oxygen"+ , "palace"+ , "parade"+ , "parent"+ , "parish"+ , "partly"+ , "pastor"+ , "patent"+ , "patrol"+ , "patron"+ , "peanut"+ , "pencil"+ , "people"+ , "pepper"+ , "period"+ , "permit"+ , "person"+ , "phrase"+ , "pickup"+ , "pillow"+ , "pistol"+ , "planet"+ , "player"+ , "please"+ , "plenty"+ , "plunge"+ , "pocket"+ , "poetry"+ , "police"+ , "policy"+ , "poster"+ , "potato"+ , "powder"+ , "praise"+ , "prayer"+ , "preach"+ , "prefer"+ , "pretty"+ , "priest"+ , "prison"+ , "profit"+ , "prompt"+ , "proper"+ , "public"+ , "punish"+ , "purple"+ , "pursue"+ , "puzzle"+ , "rabbit"+ , "racial"+ , "racism"+ , "random"+ , "rarely"+ , "rather"+ , "rating"+ , "reader"+ , "really"+ , "reason"+ , "recall"+ , "recent"+ , "recipe"+ , "record"+ , "reduce"+ , "reform"+ , "refuge"+ , "refuse"+ , "regain"+ , "regard"+ , "regime"+ , "region"+ , "regret"+ , "reject"+ , "relate"+ , "relief"+ , "remain"+ , "remark"+ , "remind"+ , "remote"+ , "remove"+ , "render"+ , "rental"+ , "repair"+ , "repeat"+ , "report"+ , "rescue"+ , "resign"+ , "resist"+ , "resort"+ , "result"+ , "resume"+ , "retail"+ , "retain"+ , "retire"+ , "return"+ , "reveal"+ , "review"+ , "reward"+ , "rhythm"+ , "ribbon"+ , "ritual"+ , "rocket"+ , "rubber"+ , "ruling"+ , "runner"+ , "sacred"+ , "safely"+ , "safety"+ , "salary"+ , "salmon"+ , "sample"+ , "saving"+ , "scared"+ , "scheme"+ , "school"+ , "scream"+ , "screen"+ , "script"+ , "search"+ , "season"+ , "second"+ , "secret"+ , "sector"+ , "secure"+ , "seldom"+ , "select"+ , "seller"+ , "senior"+ , "sensor"+ , "series"+ , "settle"+ , "severe"+ , "sexual"+ , "shadow"+ , "shared"+ , "shorts"+ , "should"+ , "shower"+ , "shrimp"+ , "shrink"+ , "signal"+ , "silent"+ , "silver"+ , "simple"+ , "simply"+ , "singer"+ , "single"+ , "sister"+ , "sleeve"+ , "slight"+ , "slowly"+ , "smooth"+ , "soccer"+ , "social"+ , "sodium"+ , "soften"+ , "softly"+ , "solely"+ , "source"+ , "speech"+ , "sphere"+ , "spirit"+ , "spouse"+ , "spread"+ , "spring"+ , "square"+ , "stable"+ , "stance"+ , "statue"+ , "status"+ , "steady"+ , "strain"+ , "streak"+ , "stream"+ , "street"+ , "stress"+ , "strict"+ , "strike"+ , "string"+ , "stroke"+ , "strong"+ , "studio"+ , "stupid"+ , "submit"+ , "subtle"+ , "suburb"+ , "sudden"+ , "suffer"+ , "summer"+ , "summit"+ , "supply"+ , "surely"+ , "survey"+ , "switch"+ , "symbol"+ , "system"+ , "tackle"+ , "tactic"+ , "talent"+ , "target"+ , "temple"+ , "tender"+ , "tennis"+ , "terror"+ , "thanks"+ , "theory"+ , "thirty"+ , "though"+ , "thread"+ , "threat"+ , "thrive"+ , "throat"+ , "ticket"+ , "timber"+ , "timing"+ , "tissue"+ , "toilet"+ , "tomato"+ , "tongue"+ , "toward"+ , "tragic"+ , "trauma"+ , "travel"+ , "treaty"+ , "tribal"+ , "tunnel"+ , "turkey"+ , "twelve"+ , "twenty"+ , "unable"+ , "unfair"+ , "unfold"+ , "unique"+ , "unless"+ , "unlike"+ , "update"+ , "useful"+ , "vacuum"+ , "valley"+ , "vanish"+ , "vendor"+ , "verbal"+ , "versus"+ , "vessel"+ , "victim"+ , "viewer"+ , "virtue"+ , "vision"+ , "visual"+ , "volume"+ , "voting"+ , "wander"+ , "warmth"+ , "weaken"+ , "wealth"+ , "weapon"+ , "weekly"+ , "weight"+ , "widely"+ , "window"+ , "winner"+ , "winter"+ , "wisdom"+ , "within"+ , "wonder"+ , "wooden"+ , "worker"+ , "writer"+ , "yellow"+ , "abandon"+ , "ability"+ , "absence"+ , "account"+ , "achieve"+ , "acquire"+ , "actress"+ , "address"+ , "advance"+ , "adviser"+ , "against"+ , "airline"+ , "airport"+ , "alcohol"+ , "alleged"+ , "already"+ , "analyst"+ , "analyze"+ , "ancient"+ , "another"+ , "anxiety"+ , "anxious"+ , "anybody"+ , "anymore"+ , "apology"+ , "appoint"+ , "approve"+ , "arrange"+ , "arrival"+ , "article"+ , "assault"+ , "athlete"+ , "attempt"+ , "attract"+ , "auction"+ , "average"+ , "balance"+ , "balloon"+ , "banking"+ , "barrier"+ , "battery"+ , "because"+ , "bedroom"+ , "believe"+ , "beneath"+ , "benefit"+ , "besides"+ , "between"+ , "bicycle"+ , "billion"+ , "biology"+ , "blanket"+ , "bombing"+ , "breathe"+ , "briefly"+ , "brother"+ , "builder"+ , "burning"+ , "cabinet"+ , "capable"+ , "capital"+ , "captain"+ , "capture"+ , "careful"+ , "carrier"+ , "cartoon"+ , "catalog"+ , "ceiling"+ , "central"+ , "century"+ , "certain"+ , "chamber"+ , "channel"+ , "chapter"+ , "charity"+ , "charter"+ , "chicken"+ , "chronic"+ , "circuit"+ , "citizen"+ , "classic"+ , "clearly"+ , "climate"+ , "closely"+ , "closest"+ , "clothes"+ , "cluster"+ , "coastal"+ , "cocaine"+ , "collect"+ , "college"+ , "combine"+ , "comfort"+ , "command"+ , "comment"+ , "company"+ , "compare"+ , "compete"+ , "complex"+ , "compose"+ , "concede"+ , "concept"+ , "concern"+ , "concert"+ , "condemn"+ , "conduct"+ , "confess"+ , "confirm"+ , "confuse"+ , "connect"+ , "consent"+ , "consist"+ , "consult"+ , "consume"+ , "contact"+ , "contain"+ , "contend"+ , "content"+ , "contest"+ , "context"+ , "control"+ , "convert"+ , "convict"+ , "cooking"+ , "correct"+ , "costume"+ , "cottage"+ , "council"+ , "counsel"+ , "counter"+ , "country"+ , "courage"+ , "crowded"+ , "crucial"+ , "crystal"+ , "culture"+ , "curious"+ , "current"+ , "curtain"+ , "custody"+ , "dancing"+ , "declare"+ , "decline"+ , "defense"+ , "deficit"+ , "delight"+ , "deliver"+ , "density"+ , "deposit"+ , "descend"+ , "deserve"+ , "despite"+ , "dessert"+ , "destroy"+ , "develop"+ , "diamond"+ , "dictate"+ , "digital"+ , "dignity"+ , "dilemma"+ , "discuss"+ , "disease"+ , "dismiss"+ , "display"+ , "dispute"+ , "distant"+ , "disturb"+ , "diverse"+ , "divorce"+ , "doorway"+ , "drawing"+ , "driving"+ , "dynamic"+ , "eastern"+ , "economy"+ , "edition"+ , "educate"+ , "elderly"+ , "elegant"+ , "element"+ , "embrace"+ , "emotion"+ , "endless"+ , "endorse"+ , "enforce"+ , "enhance"+ , "entitle"+ , "episode"+ , "equally"+ , "essence"+ , "ethical"+ , "evening"+ , "evident"+ , "exactly"+ , "examine"+ , "example"+ , "excited"+ , "exclude"+ , "execute"+ , "exhaust"+ , "exhibit"+ , "expense"+ , "explain"+ , "explode"+ , "exploit"+ , "explore"+ , "express"+ , "extreme"+ , "eyebrow"+ , "factory"+ , "faculty"+ , "failure"+ , "fantasy"+ , "fashion"+ , "fatigue"+ , "feather"+ , "feature"+ , "federal"+ , "feeling"+ , "fiction"+ , "fifteen"+ , "fighter"+ , "finally"+ , "finance"+ , "finding"+ , "fishing"+ , "fitness"+ , "foreign"+ , "forever"+ , "forgive"+ , "formula"+ , "fortune"+ , "forward"+ , "founder"+ , "fragile"+ , "frankly"+ , "freedom"+ , "fuchsia"+ , "fucking"+ , "funding"+ , "funeral"+ , "gallery"+ , "garbage"+ , "general"+ , "genetic"+ , "genuine"+ , "gesture"+ , "glimpse"+ , "gravity"+ , "greatly"+ , "grimace"+ , "grocery"+ , "growing"+ , "habitat"+ , "halfway"+ , "hallway"+ , "handful"+ , "happily"+ , "harmony"+ , "harvest"+ , "healthy"+ , "hearing"+ , "heavily"+ , "helpful"+ , "herself"+ , "highway"+ , "himself"+ , "history"+ , "holiday"+ , "horizon"+ , "hormone"+ , "hostage"+ , "hostile"+ , "housing"+ , "however"+ , "hundred"+ , "hunting"+ , "husband"+ , "illegal"+ , "illness"+ , "imagine"+ , "impress"+ , "improve"+ , "impulse"+ , "include"+ , "inherit"+ , "initial"+ , "inquiry"+ , "insight"+ , "inspire"+ , "install"+ , "instant"+ , "instead"+ , "intense"+ , "involve"+ , "isolate"+ , "jewelry"+ , "journal"+ , "journey"+ , "justice"+ , "justify"+ , "killing"+ , "kingdom"+ , "kitchen"+ , "landing"+ , "largely"+ , "laundry"+ , "lawsuit"+ , "leading"+ , "leather"+ , "lecture"+ , "legally"+ , "liberal"+ , "liberty"+ , "library"+ , "license"+ , "lightly"+ , "limited"+ , "logical"+ , "loyalty"+ , "machine"+ , "manager"+ , "mandate"+ , "mansion"+ , "married"+ , "massive"+ , "maximum"+ , "meaning"+ , "measure"+ , "medical"+ , "meeting"+ , "mention"+ , "message"+ , "million"+ , "mineral"+ , "minimal"+ , "minimum"+ , "miracle"+ , "missile"+ , "missing"+ , "mission"+ , "mistake"+ , "mixture"+ , "monitor"+ , "monster"+ , "monthly"+ , "morning"+ , "musical"+ , "mystery"+ , "natural"+ , "neither"+ , "nervous"+ , "network"+ , "neutral"+ , "nominee"+ , "nothing"+ , "nowhere"+ , "nuclear"+ , "observe"+ , "obvious"+ , "offense"+ , "officer"+ , "ongoing"+ , "opening"+ , "operate"+ , "opinion"+ , "opposed"+ , "organic"+ , "outcome"+ , "outdoor"+ , "outline"+ , "outside"+ , "overall"+ , "oversee"+ , "package"+ , "painful"+ , "painter"+ , "parking"+ , "partial"+ , "partner"+ , "passage"+ , "passing"+ , "passion"+ , "patient"+ , "pattern"+ , "payment"+ , "peasant"+ , "penalty"+ , "pension"+ , "perfect"+ , "perform"+ , "perhaps"+ , "persist"+ , "physics"+ , "picture"+ , "pioneer"+ , "pitcher"+ , "planner"+ , "plastic"+ , "playoff"+ , "pleased"+ , "popular"+ , "portion"+ , "portray"+ , "possess"+ , "poverty"+ , "precise"+ , "predict"+ , "premise"+ , "premium"+ , "prepare"+ , "present"+ , "pretend"+ , "prevail"+ , "prevent"+ , "primary"+ , "privacy"+ , "private"+ , "problem"+ , "proceed"+ , "process"+ , "produce"+ , "product"+ , "profile"+ , "program"+ , "project"+ , "promise"+ , "promote"+ , "propose"+ , "protect"+ , "protein"+ , "protest"+ , "provide"+ , "provoke"+ , "publish"+ , "purpose"+ , "pursuit"+ , "qualify"+ , "quality"+ , "quarter"+ , "quickly"+ , "quietly"+ , "radical"+ , "rapidly"+ , "readily"+ , "reading"+ , "reality"+ , "realize"+ , "rebuild"+ , "receive"+ , "recover"+ , "recruit"+ , "reflect"+ , "refugee"+ , "regular"+ , "related"+ , "release"+ , "relieve"+ , "removal"+ , "replace"+ , "request"+ , "require"+ , "reserve"+ , "resolve"+ , "respect"+ , "respond"+ , "restore"+ , "retired"+ , "retreat"+ , "revenue"+ , "reverse"+ , "rolling"+ , "romance"+ , "roughly"+ , "routine"+ , "running"+ , "satisfy"+ , "scandal"+ , "scatter"+ , "scholar"+ , "science"+ , "scratch"+ , "section"+ , "secular"+ , "segment"+ , "seminar"+ , "senator"+ , "serious"+ , "servant"+ , "service"+ , "serving"+ , "session"+ , "setting"+ , "seventh"+ , "several"+ , "shallow"+ , "sharply"+ , "shelter"+ , "shortly"+ , "shuttle"+ , "sibling"+ , "silence"+ , "similar"+ , "skilled"+ , "slavery"+ , "society"+ , "soldier"+ , "someday"+ , "somehow"+ , "someone"+ , "speaker"+ , "special"+ , "species"+ , "specify"+ , "sponsor"+ , "squeeze"+ , "stadium"+ , "starter"+ , "station"+ , "statute"+ , "stomach"+ , "storage"+ , "strange"+ , "stretch"+ , "student"+ , "stumble"+ , "subject"+ , "subsidy"+ , "succeed"+ , "success"+ , "suggest"+ , "suicide"+ , "summary"+ , "support"+ , "suppose"+ , "surface"+ , "surgeon"+ , "surgery"+ , "survive"+ , "suspect"+ , "suspend"+ , "sustain"+ , "swallow"+ , "sweater"+ , "symptom"+ , "teacher"+ , "teenage"+ , "tension"+ , "terrain"+ , "testify"+ , "testing"+ , "texture"+ , "theater"+ , "therapy"+ , "thereby"+ , "thought"+ , "through"+ , "tighten"+ , "tightly"+ , "tobacco"+ , "tonight"+ , "totally"+ , "tourism"+ , "tourist"+ , "towards"+ , "trading"+ , "traffic"+ , "tragedy"+ , "trailer"+ , "trainer"+ , "transit"+ , "trigger"+ , "triumph"+ , "trouble"+ , "typical"+ , "uncover"+ , "undergo"+ , "unhappy"+ , "uniform"+ , "unknown"+ , "unusual"+ , "usually"+ , "utility"+ , "utilize"+ , "vaccine"+ , "variety"+ , "various"+ , "vehicle"+ , "venture"+ , "verdict"+ , "version"+ , "veteran"+ , "victory"+ , "village"+ , "violate"+ , "violent"+ , "virtual"+ , "visible"+ , "visitor"+ , "vitamin"+ , "walking"+ , "warning"+ , "warrior"+ , "wealthy"+ , "weather"+ , "wedding"+ , "weekend"+ , "welcome"+ , "welfare"+ , "western"+ , "whereas"+ , "whether"+ , "whisper"+ , "whoever"+ , "willing"+ , "without"+ , "witness"+ , "working"+ , "workout"+ , "worried"+ , "writing"+ , "written"+ , "abortion"+ , "absolute"+ , "abstract"+ , "academic"+ , "accident"+ , "accuracy"+ , "accurate"+ , "actively"+ , "activist"+ , "activity"+ , "actually"+ , "addition"+ , "adequate"+ , "adoption"+ , "advanced"+ , "advocate"+ , "aircraft"+ , "airplane"+ , "alliance"+ , "although"+ , "aluminum"+ , "ambition"+ , "analysis"+ , "ancestor"+ , "announce"+ , "annually"+ , "anything"+ , "anywhere"+ , "apparent"+ , "approach"+ , "approval"+ , "argument"+ , "artifact"+ , "artistic"+ , "assemble"+ , "assembly"+ , "athletic"+ , "attitude"+ , "attorney"+ , "audience"+ , "autonomy"+ , "backyard"+ , "bacteria"+ , "balanced"+ , "baseball"+ , "basement"+ , "bathroom"+ , "behavior"+ , "birthday"+ , "blessing"+ , "boundary"+ , "building"+ , "business"+ , "calendar"+ , "campaign"+ , "capacity"+ , "casualty"+ , "category"+ , "cemetery"+ , "ceremony"+ , "chairman"+ , "champion"+ , "changing"+ , "chemical"+ , "civilian"+ , "classify"+ , "clinical"+ , "clothing"+ , "collapse"+ , "colonial"+ , "colorful"+ , "combined"+ , "commonly"+ , "complain"+ , "complete"+ , "compound"+ , "comprise"+ , "computer"+ , "conceive"+ , "conclude"+ , "concrete"+ , "conflict"+ , "confront"+ , "consider"+ , "constant"+ , "consumer"+ , "continue"+ , "contract"+ , "contrast"+ , "convince"+ , "corridor"+ , "coverage"+ , "creation"+ , "creative"+ , "creature"+ , "criminal"+ , "criteria"+ , "critical"+ , "cultural"+ , "currency"+ , "customer"+ , "darkness"+ , "database"+ , "daughter"+ , "deadline"+ , "decision"+ , "decorate"+ , "decrease"+ , "dedicate"+ , "defender"+ , "delicate"+ , "delivery"+ , "describe"+ , "designer"+ , "detailed"+ , "diabetes"+ , "diagnose"+ , "dialogue"+ , "diminish"+ , "diplomat"+ , "directly"+ , "director"+ , "disabled"+ , "disagree"+ , "disaster"+ , "disclose"+ , "discount"+ , "discover"+ , "disorder"+ , "dissolve"+ , "distance"+ , "distinct"+ , "distract"+ , "district"+ , "division"+ , "doctrine"+ , "document"+ , "domestic"+ , "dominant"+ , "dominate"+ , "donation"+ , "downtown"+ , "dramatic"+ , "drinking"+ , "driveway"+ , "dynamics"+ , "earnings"+ , "economic"+ , "educator"+ , "election"+ , "electric"+ , "elephant"+ , "elevator"+ , "eligible"+ , "emerging"+ , "emission"+ , "emphasis"+ , "employee"+ , "employer"+ , "engineer"+ , "enormous"+ , "entirely"+ , "entrance"+ , "envelope"+ , "envision"+ , "epidemic"+ , "equality"+ , "equation"+ , "estimate"+ , "evaluate"+ , "everyday"+ , "everyone"+ , "evidence"+ , "exchange"+ , "exciting"+ , "exercise"+ , "existing"+ , "expected"+ , "explicit"+ , "exposure"+ , "extended"+ , "external"+ , "facility"+ , "familiar"+ , "favorite"+ , "feedback"+ , "feminist"+ , "festival"+ , "fighting"+ , "flexible"+ , "football"+ , "forehead"+ , "formerly"+ , "fraction"+ , "fragment"+ , "frequent"+ , "freshman"+ , "friendly"+ , "frontier"+ , "function"+ , "gasoline"+ , "generate"+ , "generous"+ , "governor"+ , "graduate"+ , "grateful"+ , "greatest"+ , "guidance"+ , "handsome"+ , "hardware"+ , "headache"+ , "headline"+ , "heritage"+ , "hesitate"+ , "historic"+ , "homeland"+ , "homeless"+ , "homework"+ , "honestly"+ , "horrible"+ , "hospital"+ , "humanity"+ , "identify"+ , "identity"+ , "ideology"+ , "illusion"+ , "improved"+ , "incident"+ , "increase"+ , "indicate"+ , "industry"+ , "informal"+ , "inherent"+ , "initiate"+ , "innocent"+ , "instance"+ , "instinct"+ , "instruct"+ , "interact"+ , "interest"+ , "interior"+ , "internal"+ , "interval"+ , "intimate"+ , "invasion"+ , "investor"+ , "involved"+ , "isolated"+ , "judgment"+ , "judicial"+ , "landmark"+ , "language"+ , "laughter"+ , "lawmaker"+ , "learning"+ , "lifetime"+ , "lighting"+ , "likewise"+ , "listener"+ , "literary"+ , "location"+ , "longtime"+ , "magazine"+ , "magnetic"+ , "maintain"+ , "majority"+ , "managing"+ , "marriage"+ , "material"+ , "meantime"+ , "mechanic"+ , "medicine"+ , "mentally"+ , "merchant"+ , "metaphor"+ , "midnight"+ , "military"+ , "minimize"+ , "minister"+ , "ministry"+ , "minority"+ , "moderate"+ , "molecule"+ , "momentum"+ , "monument"+ , "moreover"+ , "mortgage"+ , "motivate"+ , "mountain"+ , "movement"+ , "multiple"+ , "mushroom"+ , "musician"+ , "national"+ , "negative"+ , "neighbor"+ , "normally"+ , "northern"+ , "notebook"+ , "numerous"+ , "nutrient"+ , "observer"+ , "obstacle"+ , "occasion"+ , "offender"+ , "offering"+ , "official"+ , "operator"+ , "opponent"+ , "opposite"+ , "ordinary"+ , "organism"+ , "organize"+ , "original"+ , "outsider"+ , "overcome"+ , "overlook"+ , "painting"+ , "parental"+ , "particle"+ , "patience"+ , "peaceful"+ , "perceive"+ , "personal"+ , "persuade"+ , "physical"+ , "planning"+ , "platform"+ , "pleasant"+ , "pleasure"+ , "politics"+ , "portrait"+ , "position"+ , "positive"+ , "possible"+ , "possibly"+ , "powerful"+ , "practice"+ , "precious"+ , "predator"+ , "pregnant"+ , "presence"+ , "preserve"+ , "pressure"+ , "previous"+ , "priority"+ , "prisoner"+ , "probably"+ , "proclaim"+ , "producer"+ , "profound"+ , "progress"+ , "prohibit"+ , "properly"+ , "property"+ , "proposal"+ , "proposed"+ , "prospect"+ , "protocol"+ , "provided"+ , "provider"+ , "province"+ , "publicly"+ , "purchase"+ , "quantity"+ , "question"+ , "railroad"+ , "rational"+ , "reaction"+ , "receiver"+ , "recently"+ , "recovery"+ , "regional"+ , "register"+ , "regulate"+ , "relation"+ , "relative"+ , "relevant"+ , "reliable"+ , "religion"+ , "remember"+ , "reminder"+ , "reporter"+ , "republic"+ , "required"+ , "research"+ , "resemble"+ , "resident"+ , "resource"+ , "response"+ , "restrict"+ , "retailer"+ , "rhetoric"+ , "romantic"+ , "sanction"+ , "sandwich"+ , "scenario"+ , "schedule"+ , "scramble"+ , "security"+ , "selected"+ , "sentence"+ , "separate"+ , "sequence"+ , "severely"+ , "sexually"+ , "shooting"+ , "shopping"+ , "shortage"+ , "shoulder"+ , "sidewalk"+ , "slightly"+ , "socially"+ , "software"+ , "solution"+ , "somebody"+ , "sometime"+ , "somewhat"+ , "southern"+ , "specific"+ , "spectrum"+ , "spending"+ , "sprinkle"+ , "standard"+ , "standing"+ , "starting"+ , "steadily"+ , "stimulus"+ , "straight"+ , "stranger"+ , "strategy"+ , "strength"+ , "strictly"+ , "striking"+ , "strongly"+ , "struggle"+ , "suburban"+ , "suddenly"+ , "suitable"+ , "sunlight"+ , "superior"+ , "supplier"+ , "supposed"+ , "surprise"+ , "surround"+ , "survival"+ , "survivor"+ , "swimming"+ , "symbolic"+ , "sympathy"+ , "syndrome"+ , "talented"+ , "taxpayer"+ , "teaching"+ , "teammate"+ , "teaspoon"+ , "teenager"+ , "tendency"+ , "terrible"+ , "terribly"+ , "terrific"+ , "textbook"+ , "theology"+ , "thinking"+ , "thousand"+ , "threaten"+ , "together"+ , "tolerate"+ , "tomorrow"+ , "training"+ , "transfer"+ , "transmit"+ , "traveler"+ , "treasure"+ , "tropical"+ , "troubled"+ , "ultimate"+ , "universe"+ , "unlikely"+ , "upstairs"+ , "vacation"+ , "validity"+ , "valuable"+ , "variable"+ , "vertical"+ , "violence"+ , "weakness"+ , "whatever"+ , "whenever"+ , "wherever"+ , "wildlife"+ , "withdraw"+ , "workshop"+ , "yourself"+ , "accompany"+ , "according"+ , "admission"+ , "advantage"+ , "adventure"+ , "advertise"+ , "aesthetic"+ , "afternoon"+ , "afterward"+ , "agreement"+ , "allegedly"+ , "alongside"+ , "ambitious"+ , "amendment"+ , "anonymous"+ , "apartment"+ , "apologize"+ , "architect"+ , "assistant"+ , "associate"+ , "attention"+ , "attribute"+ , "authority"+ , "authorize"+ , "automatic"+ , "available"+ , "awareness"+ , "basically"+ , "beautiful"+ , "beginning"+ , "biography"+ , "boyfriend"+ , "breakfast"+ , "breathing"+ , "brilliant"+ , "broadcast"+ , "butterfly"+ , "calculate"+ , "candidate"+ , "carefully"+ , "celebrate"+ , "celebrity"+ , "certainly"+ , "challenge"+ , "character"+ , "chemistry"+ , "childhood"+ , "chocolate"+ , "cigarette"+ , "classical"+ , "classroom"+ , "coalition"+ , "cognitive"+ , "colleague"+ ]++-- Shuffled so that you get an even distribution of words+-- when taking `leftSide`+type FourLetterWords = ToTree+ '[ "ergo"+ , "blur"+ , "gaze"+ , "said"+ , "cone"+ , "thaw"+ , "undo"+ , "than"+ , "vile"+ , "rods"+ , "thug"+ , "hurt"+ , "matt"+ , "spun"+ , "eggs"+ , "tail"+ , "copy"+ , "says"+ , "gust"+ , "rims"+ , "mild"+ , "feed"+ , "hawk"+ , "pelt"+ , "pawn"+ , "suit"+ , "rain"+ , "trap"+ , "bulk"+ , "slug"+ , "spay"+ , "ring"+ , "hate"+ , "time"+ , "mole"+ , "skip"+ , "cart"+ , "pall"+ , "full"+ , "glue"+ , "fate"+ , "fast"+ , "keen"+ , "mead"+ , "flux"+ , "jets"+ , "part"+ , "bone"+ , "wept"+ , "apex"+ , "lace"+ , "very"+ , "dawn"+ , "lies"+ , "ware"+ , "bead"+ , "anew"+ , "lint"+ , "deal"+ , "slur"+ , "lung"+ , "came"+ , "mace"+ , "bonk"+ , "text"+ , "save"+ , "dell"+ , "left"+ , "clip"+ , "mute"+ , "glib"+ , "gala"+ , "arts"+ , "rush"+ , "raid"+ , "asks"+ , "pray"+ , "amid"+ , "cram"+ , "leap"+ , "best"+ , "prey"+ , "laws"+ , "jazz"+ , "redo"+ , "eave"+ , "fore"+ , "shoe"+ , "nice"+ , "grit"+ , "paid"+ , "boom"+ , "whim"+ , "used"+ , "puck"+ , "smog"+ , "sack"+ , "year"+ , "pens"+ , "flex"+ , "hash"+ , "suss"+ , "sums"+ , "bait"+ , "cyst"+ , "hilt"+ , "warp"+ , "cave"+ , "fuss"+ , "spry"+ , "bunk"+ , "firm"+ , "lend"+ , "pulp"+ , "port"+ , "lick"+ , "aria"+ , "body"+ , "sway"+ , "edgy"+ , "lewd"+ , "wuss"+ , "horn"+ , "yowl"+ , "link"+ , "chop"+ , "kiln"+ , "moss"+ , "dome"+ , "halo"+ , "lull"+ , "fits"+ , "foul"+ , "cask"+ , "bred"+ , "wink"+ , "grow"+ , "helm"+ , "cage"+ , "lime"+ , "lump"+ , "take"+ , "wall"+ , "hoop"+ , "pore"+ , "lazy"+ , "poet"+ , "sure"+ , "root"+ , "nods"+ , "buck"+ , "vine"+ , "seam"+ , "died"+ , "toss"+ , "oven"+ , "palm"+ , "rusk"+ , "bomb"+ , "tore"+ , "gash"+ , "glad"+ , "maim"+ , "hips"+ , "arch"+ , "skit"+ , "tree"+ , "snot"+ , "bees"+ , "toll"+ , "step"+ , "prod"+ , "food"+ , "pits"+ , "gape"+ , "welt"+ , "diet"+ , "saga"+ , "limb"+ , "bent"+ , "bark"+ , "tuck"+ , "gasp"+ , "heat"+ , "hind"+ , "tune"+ , "buoy"+ , "buff"+ , "gaps"+ , "womb"+ , "ball"+ , "hats"+ , "clog"+ , "ages"+ , "cuff"+ , "lips"+ , "were"+ , "pump"+ , "rate"+ , "cues"+ , "damn"+ , "knit"+ , "lust"+ , "seer"+ , "ford"+ , "swat"+ , "dunk"+ , "iris"+ , "hack"+ , "grid"+ , "tied"+ , "rats"+ , "brag"+ , "mesh"+ , "lock"+ , "stir"+ , "snub"+ , "bake"+ , "baby"+ , "call"+ , "club"+ , "cyan"+ , "boar"+ , "high"+ , "tear"+ , "safe"+ , "wood"+ , "hemp"+ , "play"+ , "pool"+ , "rest"+ , "sail"+ , "tire"+ , "corn"+ , "sold"+ , "plum"+ , "webs"+ , "talk"+ , "pale"+ , "plea"+ , "luck"+ , "into"+ , "thud"+ , "drew"+ , "seem"+ , "hump"+ , "belt"+ , "kiss"+ , "each"+ , "cash"+ , "jean"+ , "hill"+ , "digs"+ , "null"+ , "when"+ , "chap"+ , "wake"+ , "rear"+ , "pike"+ , "farm"+ , "mold"+ , "lean"+ , "pins"+ , "gulf"+ , "buys"+ , "tool"+ , "demo"+ , "made"+ , "plug"+ , "bard"+ , "ears"+ , "oaks"+ , "lest"+ , "plus"+ , "nuke"+ , "legs"+ , "plan"+ , "dash"+ , "hair"+ , "lute"+ , "army"+ , "evil"+ , "look"+ , "wipe"+ , "gist"+ , "meow"+ , "bail"+ , "knew"+ , "nail"+ , "deck"+ , "ever"+ , "snag"+ , "desk"+ , "heel"+ , "some"+ , "pans"+ , "germ"+ , "mind"+ , "will"+ , "vans"+ , "guys"+ , "dorm"+ , "bids"+ , "lays"+ , "code"+ , "pays"+ , "shod"+ , "lose"+ , "sash"+ , "clap"+ , "grab"+ , "oral"+ , "taut"+ , "cull"+ , "toys"+ , "slow"+ , "molt"+ , "ties"+ , "bane"+ , "salt"+ , "hike"+ , "idol"+ , "uses"+ , "maid"+ , "pear"+ , "gray"+ , "melt"+ , "most"+ , "news"+ , "cope"+ , "bawl"+ , "rank"+ , "mock"+ , "clay"+ , "lard"+ , "crib"+ , "noun"+ , "peel"+ , "sore"+ , "nick"+ , "meld"+ , "main"+ , "jars"+ , "mist"+ , "skim"+ , "easy"+ , "road"+ , "gosh"+ , "kite"+ , "wise"+ , "foot"+ , "puff"+ , "cusp"+ , "lush"+ , "boon"+ , "fray"+ , "gunk"+ , "cult"+ , "garb"+ , "cent"+ , "flee"+ , "rung"+ , "math"+ , "dire"+ , "yard"+ , "loom"+ , "kill"+ , "dice"+ , "ajar"+ , "vial"+ , "rise"+ , "hone"+ , "whet"+ , "skin"+ , "vase"+ , "silo"+ , "flap"+ , "fork"+ , "pick"+ , "meme"+ , "boat"+ , "jump"+ , "sewn"+ , "memo"+ , "page"+ , "chin"+ , "zeal"+ , "fees"+ , "peek"+ , "furs"+ , "guts"+ , "euro"+ , "tidy"+ , "site"+ , "veil"+ , "labs"+ , "wilt"+ , "busk"+ , "stag"+ , "ploy"+ , "tart"+ , "beer"+ , "data"+ , "heap"+ , "beds"+ , "tusk"+ , "heed"+ , "deem"+ , "shut"+ , "clan"+ , "burn"+ , "stay"+ , "size"+ , "hers"+ , "glow"+ , "dope"+ , "gong"+ , "pour"+ , "wave"+ , "here"+ , "hiss"+ , "dial"+ , "bled"+ , "prev"+ , "diff"+ , "rave"+ , "loud"+ , "king"+ , "fume"+ , "bare"+ , "comb"+ , "claw"+ , "bars"+ , "mush"+ , "dads"+ , "idea"+ , "twos"+ , "glam"+ , "song"+ , "tend"+ , "rust"+ , "tray"+ , "over"+ , "ours"+ , "caps"+ , "duet"+ , "what"+ , "dote"+ , "dock"+ , "case"+ , "dude"+ , "bull"+ , "tuna"+ , "opts"+ , "weld"+ , "west"+ , "skid"+ , "upon"+ , "raft"+ , "dine"+ , "stew"+ , "wren"+ , "lady"+ , "slip"+ , "wine"+ , "prop"+ , "fall"+ , "rule"+ , "junk"+ , "pain"+ , "newt"+ , "tank"+ , "game"+ , "must"+ , "jaws"+ , "riot"+ , "room"+ , "cook"+ , "risk"+ , "race"+ , "wrap"+ , "soap"+ , "dump"+ , "spur"+ , "thin"+ , "duke"+ , "scan"+ , "etch"+ , "mime"+ , "seen"+ , "rook"+ , "gets"+ , "care"+ , "neck"+ , "teen"+ , "punk"+ , "post"+ , "dove"+ , "brim"+ , "sole"+ , "silk"+ , "bold"+ , "bale"+ , "vary"+ , "lash"+ , "vein"+ , "down"+ , "pave"+ , "burp"+ , "does"+ , "dead"+ , "also"+ , "veal"+ , "snap"+ , "punt"+ , "gate"+ , "john"+ , "toad"+ , "swig"+ , "teas"+ , "soot"+ , "drag"+ , "real"+ , "fake"+ , "vale"+ , "tame"+ , "swim"+ , "cake"+ , "fine"+ , "balk"+ , "gone"+ , "mend"+ , "oboe"+ , "grew"+ , "pond"+ , "peer"+ , "view"+ , "bird"+ , "gull"+ , "meet"+ , "flip"+ , "seal"+ , "fail"+ , "mass"+ , "team"+ , "pole"+ , "hope"+ , "path"+ , "yelp"+ , "mull"+ , "eels"+ , "coal"+ , "wire"+ , "wade"+ , "send"+ , "aids"+ , "cord"+ , "bang"+ , "life"+ , "push"+ , "rang"+ , "slot"+ , "jail"+ , "exam"+ , "raze"+ , "ramp"+ , "name"+ , "rail"+ , "card"+ , "boil"+ , "find"+ , "pact"+ , "duck"+ , "host"+ , "dogs"+ , "self"+ , "tyre"+ , "sunk"+ , "butt"+ , "home"+ , "bald"+ , "chat"+ , "kink"+ , "show"+ , "skis"+ , "jinx"+ , "tick"+ , "leaf"+ , "aqua"+ , "roar"+ , "mail"+ , "your"+ , "kits"+ , "fire"+ , "them"+ , "colt"+ , "foxy"+ , "list"+ , "serf"+ , "loft"+ , "joke"+ , "type"+ , "park"+ , "pail"+ , "wide"+ , "male"+ , "cans"+ , "ones"+ , "veer"+ , "deft"+ , "cuts"+ , "tens"+ , "icon"+ , "hear"+ , "pyre"+ , "wing"+ , "wets"+ , "core"+ , "well"+ , "perk"+ , "brat"+ , "deaf"+ , "tale"+ , "mint"+ , "sang"+ , "soda"+ , "girl"+ , "cops"+ , "tram"+ , "pack"+ , "fuse"+ , "rent"+ , "camp"+ , "acid"+ , "suck"+ , "hero"+ , "kick"+ , "fowl"+ , "loss"+ , "peep"+ , "week"+ , "yank"+ , "feet"+ , "onto"+ , "free"+ , "runs"+ , "runt"+ , "mike"+ , "only"+ , "hulk"+ , "turn"+ , "rude"+ , "wart"+ , "faun"+ , "balm"+ , "obey"+ , "czar"+ , "dams"+ , "fold"+ , "slid"+ , "rich"+ , "snip"+ , "scum"+ , "mean"+ , "meek"+ , "ruse"+ , "hugs"+ , "term"+ , "fish"+ , "tier"+ , "gyms"+ , "tony"+ , "atop"+ , "ally"+ , "crow"+ , "jest"+ , "ahoy"+ , "gram"+ , "hoax"+ , "dies"+ , "pout"+ , "yolk"+ , "iron"+ , "nose"+ , "jade"+ , "feud"+ , "bean"+ , "poor"+ , "area"+ , "shot"+ , "ooze"+ , "file"+ , "less"+ , "sake"+ , "weep"+ , "such"+ , "york"+ , "puny"+ , "hole"+ , "spam"+ , "fort"+ , "once"+ , "foam"+ , "hazy"+ , "tall"+ , "rake"+ , "lids"+ , "sane"+ , "mess"+ , "nest"+ , "omit"+ , "dolt"+ , "cane"+ , "warn"+ , "rage"+ , "seed"+ , "lead"+ , "told"+ , "knob"+ , "drop"+ , "gush"+ , "riff"+ , "wear"+ , "folk"+ , "dots"+ , "scab"+ , "ruby"+ , "tape"+ , "gift"+ , "soak"+ , "laps"+ , "quip"+ , "side"+ , "vibe"+ , "rips"+ , "lots"+ , "boot"+ , "ugly"+ , "hood"+ , "clue"+ , "wage"+ , "sick"+ , "ride"+ , "cosy"+ , "numb"+ , "pats"+ , "bans"+ , "glum"+ , "read"+ , "haze"+ , "taps"+ , "cold"+ , "vote"+ , "mice"+ , "coin"+ , "gait"+ , "idle"+ , "fend"+ , "onus"+ , "born"+ , "ends"+ , "help"+ , "gain"+ , "spar"+ , "doom"+ , "opal"+ , "tube"+ , "band"+ , "tuft"+ , "rubs"+ , "sofa"+ , "soup"+ , "robe"+ , "peas"+ , "spot"+ , "hush"+ , "wins"+ , "hare"+ , "zoom"+ , "harm"+ , "hull"+ , "come"+ , "verb"+ , "lost"+ , "sigh"+ , "rump"+ , "bowl"+ , "fond"+ , "warm"+ , "scam"+ , "dear"+ , "jolt"+ , "swab"+ , "seek"+ , "bony"+ , "laze"+ , "late"+ , "aims"+ , "ouch"+ , "lava"+ , "zone"+ , "whey"+ , "land"+ , "nerd"+ , "eras"+ , "chef"+ , "heft"+ , "turf"+ , "past"+ , "mark"+ , "yeti"+ , "clop"+ , "dent"+ , "epic"+ , "whom"+ , "mast"+ , "dust"+ , "mall"+ , "from"+ , "deer"+ , "drip"+ , "deep"+ , "rows"+ , "owns"+ , "base"+ , "duct"+ , "woke"+ , "bays"+ , "bask"+ , "tags"+ , "feel"+ , "maul"+ , "logs"+ , "silt"+ , "feat"+ , "herb"+ , "next"+ , "sues"+ , "mite"+ , "with"+ , "blob"+ , "roof"+ , "geek"+ , "dull"+ , "meat"+ , "pant"+ , "dual"+ , "dive"+ , "open"+ , "flog"+ , "spit"+ , "unto"+ , "pile"+ , "acre"+ , "dusk"+ , "sank"+ , "rape"+ , "pics"+ , "rink"+ , "guns"+ , "oath"+ , "slam"+ , "sled"+ , "okay"+ , "mope"+ , "bend"+ , "trot"+ , "shim"+ , "town"+ , "guru"+ , "weak"+ , "fair"+ , "shit"+ , "gave"+ , "deny"+ , "deed"+ , "fans"+ , "hymn"+ , "soil"+ , "lice"+ , "nets"+ , "zest"+ , "stop"+ , "yoga"+ , "rely"+ , "mode"+ , "pots"+ , "form"+ , "kelp"+ , "half"+ , "coil"+ , "swap"+ , "city"+ , "fern"+ , "edit"+ , "fill"+ , "blip"+ , "hoof"+ , "rift"+ , "spat"+ , "coup"+ , "cats"+ , "yawn"+ , "leer"+ , "shed"+ , "pies"+ , "malt"+ , "pint"+ , "bags"+ , "gulp"+ , "leek"+ , "glen"+ , "wind"+ , "stun"+ , "clam"+ , "cube"+ , "bass"+ , "nope"+ , "know"+ , "holy"+ , "chug"+ , "herd"+ , "spin"+ , "cool"+ , "word"+ , "ways"+ , "that"+ , "hide"+ , "eats"+ , "drab"+ , "tech"+ , "took"+ , "grim"+ , "book"+ , "work"+ , "akin"+ , "pass"+ , "bank"+ , "rare"+ , "ripe"+ , "itch"+ , "mule"+ , "poke"+ , "bolt"+ , "snug"+ , "wimp"+ , "loot"+ , "ward"+ , "ghee"+ , "beta"+ , "dean"+ , "dime"+ , "film"+ , "wane"+ , "tile"+ , "muse"+ , "went"+ , "fool"+ , "navy"+ , "huge"+ , "even"+ , "heal"+ , "visa"+ , "earn"+ , "fart"+ , "pull"+ , "dart"+ , "howl"+ , "debt"+ , "awry"+ , "mare"+ , "auto"+ , "dark"+ , "crux"+ , "isle"+ , "cozy"+ , "bump"+ , "vend"+ , "font"+ , "wand"+ , "sour"+ , "babe"+ , "bran"+ , "bode"+ , "goal"+ , "foil"+ , "knee"+ , "bade"+ , "bash"+ , "aide"+ , "neat"+ , "swag"+ , "hurl"+ , "rope"+ , "quit"+ , "reef"+ , "bong"+ , "note"+ , "fund"+ , "thus"+ , "hose"+ , "sexy"+ , "grin"+ , "beep"+ , "duty"+ , "fled"+ , "mill"+ , "near"+ , "quay"+ , "fare"+ , "lack"+ , "earl"+ , "gory"+ , "fuel"+ , "tuba"+ , "sand"+ , "fear"+ , "sign"+ , "live"+ , "hype"+ , "door"+ , "much"+ , "node"+ , "snow"+ ]
+ test/Main.hs view
@@ -0,0 +1,71 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE PolyKinds #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE FlexibleContexts #-}+module Main where++import Data.Memorable.Internal+import Data.Proxy+import Data.Memorable.Theme.Words+import Data.Word+import Data.Int+import Data.List+import GHC.TypeLits+import Text.Printf+import Test.Tasty+import Test.Tasty.QuickCheck as QC+import qualified Test.Tasty.SmallCheck as SC+import Test.Tasty.HUnit+import Test.DocTest++main :: IO ()+main = defaultMain tests++tests :: TestTree+tests = testGroup "Test"+ [ properties+ , unitTests+ ]++type TestType = (Int8, Int32, Word8, Word64)+testPat = padHex @112 (words8 .- words10)+testPat2 = padDec @112 (hex8 .- words4 .- dec4)++properties :: TestTree+properties = testGroup "Properties"+ [ QC.testProperty "is show" (\x -> show (x :: Word8) == renderMemorable dec8 x)+ , QC.testProperty "is show tuple" (\(a,b) -> show (a :: Word8) ++ "-" ++ show (b :: Word8) == renderMemorable (two dec8) (a,b))+ , QC.testProperty "is %02x" (\x -> printf "%02x" (x :: Word8) == renderMemorable hex8 x)+ , QC.testProperty "is %04x" (\x -> printf "%04x" (x :: Word16) == renderMemorable hex16 x)+ , QC.testProperty "is %08x" (\x -> printf "%08x" (x :: Word32) == renderMemorable hex32 x)+ , QC.testProperty "tuples" (\(a,b) -> printf "%02x-%02x" (a :: Word8) (b :: Word8) == renderMemorable (hex8 .- hex8) (a,b))+ , QC.testProperty "render/parse" (\a -> runParser (runRender (a :: TestType)) == a)+ , QC.testProperty "rMem/pMem" (\a -> parseMemorable testPat (renderMemorable testPat a) == Just (a :: TestType))+ , QC.testProperty "rMem/pMem" (\a -> parseMemorable testPat2 (renderMemorable testPat2 a) == Just (a :: TestType))+ , QC.testProperty "rerender" (\a -> let x = renderMemorable testPat (a :: TestType) in Just x == (rerender testPat2 testPat =<< rerender testPat testPat2 x))+ ]++scProps = testGroup "(checked by SmallCheck)"+ [+ ]++unitTests :: TestTree+unitTests = testGroup "Unit tests"+ [ testCase "DocTest" $ doctest ["./src/"]+ , testGroup "Unique"+ [ testCase "padHex words4" $ assert (uniqueRenderings (padHex words4))+ , testCase "padDec words4" $ assert (uniqueRenderings (padDec words4))+ , testCase "words4 .- words4" $ assert (uniqueRenderings (words4 .- words4))+ , testCase "Hex" $ assert (uniqueRenderings (Proxy :: Proxy (Number Dec 8)))+ , testCase "Dec" $ assert (uniqueRenderings (Proxy :: Proxy (Number Hex 8)))+ ]+ ]++uniqueRenderings :: (Depth p ~ 8, MemRender p) => Proxy p -> Bool+uniqueRenderings p =+ let+ ls = map (renderMemorable p) [minBound .. maxBound :: Word8]+ in+ ls == nub ls