2019-05-20 04:53:32 +03:00
|
|
|
{-# LANGUAGE MagicHash #-}
|
|
|
|
|
2019-05-21 10:25:58 +03:00
|
|
|
{-
|
|
|
|
TODO Handle 32-bit architectures
|
|
|
|
TODO A faster version of this is possible:
|
|
|
|
|
|
|
|
- Get the byte-length of a file.
|
|
|
|
- Round up to a multiple of 8 (or 4 if 32bit cpu)
|
|
|
|
- Allocate a mutable vector of Word8 with that size.
|
|
|
|
- Read the file into the array.
|
|
|
|
- Manually cast to an array of Word.
|
|
|
|
- On big endian, update each words with `System.Endian.fromLE64`.
|
|
|
|
- If there are trailing 0 words, adjust the vector size to delete them.
|
|
|
|
- unsafeFreeze the vector.
|
|
|
|
- Run `byteArrayToBigNat#` on the underlying byte array.
|
|
|
|
- Convert the BigNat to a Natural, to an Atom.
|
|
|
|
- The whole thing becomes zero-copy for little endian machines, with
|
|
|
|
one zero-copy transformation of the whole structure on big-endian
|
|
|
|
machines.
|
|
|
|
-}
|
2019-05-20 09:14:07 +03:00
|
|
|
|
2019-05-20 04:53:32 +03:00
|
|
|
module Data.Noun.Pill where
|
|
|
|
|
|
|
|
import ClassyPrelude
|
2019-05-20 09:14:07 +03:00
|
|
|
import Data.Noun hiding (toList, fromList)
|
2019-05-20 04:53:32 +03:00
|
|
|
import Data.Noun.Atom
|
2019-05-20 09:14:07 +03:00
|
|
|
import Data.Noun.Jam hiding (main)
|
2019-05-20 04:53:32 +03:00
|
|
|
import Data.Flat
|
|
|
|
import Control.Monad.Except
|
2019-05-20 09:14:07 +03:00
|
|
|
import Control.Lens hiding (index, Index)
|
2019-05-20 04:53:32 +03:00
|
|
|
import Data.Either.Extra (mapLeft)
|
|
|
|
import GHC.Natural
|
|
|
|
import Data.Bits
|
|
|
|
import GHC.Integer.GMP.Internals
|
2019-05-20 09:14:07 +03:00
|
|
|
import GHC.Int
|
|
|
|
import GHC.Word
|
|
|
|
import GHC.Exts (sizeofByteArray#)
|
2019-05-20 04:53:32 +03:00
|
|
|
|
2019-05-21 02:04:28 +03:00
|
|
|
import qualified Data.Vector as V
|
2019-05-20 04:53:32 +03:00
|
|
|
import qualified Data.Primitive.ByteArray as Prim
|
|
|
|
import qualified Data.Vector.Primitive as VP
|
2019-05-21 02:04:28 +03:00
|
|
|
import qualified Data.Vector.Unboxed as VU
|
2019-05-20 04:53:32 +03:00
|
|
|
import qualified Data.ByteString as BS
|
|
|
|
|
2019-05-20 09:14:07 +03:00
|
|
|
import Test.Tasty
|
|
|
|
import Test.Tasty.TH
|
|
|
|
import Test.Tasty.QuickCheck as QC
|
|
|
|
import Test.QuickCheck
|
|
|
|
|
2019-05-20 04:53:32 +03:00
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
|
2019-05-20 09:14:07 +03:00
|
|
|
stripTrailingZeros :: IsSequence seq
|
|
|
|
=> Int ~ Index seq
|
|
|
|
=> (Eq (Element seq), Num (Element seq))
|
|
|
|
=> seq -> seq
|
|
|
|
stripTrailingZeros buf = take (len - go 0 (len - 1)) buf
|
2019-05-20 04:53:32 +03:00
|
|
|
where
|
|
|
|
len = length buf
|
2019-05-20 09:14:07 +03:00
|
|
|
go n i | i < 0 = n
|
|
|
|
| 0 == unsafeIndex buf i = go (n+1) (i-1)
|
|
|
|
| otherwise = n
|
|
|
|
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
|
2019-05-21 02:04:28 +03:00
|
|
|
wordsToBigNat :: VP.Vector Word -> BigNat
|
|
|
|
wordsToBigNat v@(VP.Vector off (I# len) (Prim.ByteArray buf)) =
|
2019-05-20 09:14:07 +03:00
|
|
|
case VP.length v of
|
|
|
|
0 -> zeroBigNat
|
|
|
|
1 -> wordToBigNat (case VP.unsafeIndex v 0 of W# w -> w)
|
|
|
|
n -> if off /= 0 then error "words2Nat: bad-vec" else
|
|
|
|
byteArrayToBigNat# buf len
|
|
|
|
|
2019-05-21 02:04:28 +03:00
|
|
|
bigNatToWords :: BigNat -> VP.Vector Word
|
|
|
|
bigNatToWords (BN# bArr) = VP.Vector 0 (I# (sizeofByteArray# bArr) `div` 8)
|
|
|
|
$ Prim.ByteArray bArr
|
2019-05-20 09:14:07 +03:00
|
|
|
|
2019-05-21 02:04:28 +03:00
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
bigNatToBits :: BigNat -> VU.Vector Bool
|
|
|
|
bigNatToBits = undefined
|
|
|
|
|
|
|
|
bitsToBigNat :: BigNat -> VU.Vector Bool
|
|
|
|
bitsToBigNat = undefined
|
2019-05-20 09:14:07 +03:00
|
|
|
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
naturalToBigNat :: Natural -> BigNat
|
|
|
|
naturalToBigNat (NatS# w) = wordToBigNat w
|
|
|
|
naturalToBigNat (NatJ# bn) = bn
|
|
|
|
|
2019-05-21 02:04:28 +03:00
|
|
|
bigNatToNatural :: BigNat -> Natural
|
|
|
|
bigNatToNatural bn =
|
|
|
|
case sizeofBigNat# bn of
|
|
|
|
0# -> 0
|
|
|
|
1# -> NatS# (bigNatToWord bn)
|
|
|
|
_ -> NatJ# bn
|
|
|
|
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
wordsToNatural :: VP.Vector Word -> Natural
|
|
|
|
wordsToNatural = bigNatToNatural . wordsToBigNat
|
2019-05-20 09:14:07 +03:00
|
|
|
|
2019-05-21 02:04:28 +03:00
|
|
|
naturalToWords :: Natural -> VP.Vector Word
|
2019-05-20 09:14:07 +03:00
|
|
|
naturalToWords = bigNatToWords . naturalToBigNat
|
|
|
|
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
dumbPackWord :: ByteString -> Word
|
|
|
|
dumbPackWord bs = go 0 0 (toList bs)
|
2019-05-20 04:53:32 +03:00
|
|
|
where
|
2019-05-20 09:14:07 +03:00
|
|
|
go acc i [] = acc
|
|
|
|
go acc i (x:xs) = go (acc .|. shiftL (fromIntegral x) (8*i)) (i+1) xs
|
|
|
|
|
|
|
|
-- TODO This assumes 64-bit words
|
|
|
|
packWord :: ByteString -> Word
|
2019-05-21 02:04:28 +03:00
|
|
|
packWord buf = go 0 0
|
2019-05-20 09:14:07 +03:00
|
|
|
where
|
2019-05-21 02:04:28 +03:00
|
|
|
top = min 8 (length buf)
|
|
|
|
i idx off = shiftL (fromIntegral $ BS.index buf idx) off
|
|
|
|
go acc idx = if idx >= top then acc else
|
|
|
|
go (acc .|. i idx (8*idx)) (idx+1)
|
2019-05-20 09:14:07 +03:00
|
|
|
|
2019-05-20 04:53:32 +03:00
|
|
|
|
2019-05-20 09:14:07 +03:00
|
|
|
-- TODO This assumes 64-bit words
|
|
|
|
unpackWord :: Word -> ByteString
|
|
|
|
unpackWord wor = reverse $ fromList $ go 0 []
|
2019-05-20 04:53:32 +03:00
|
|
|
where
|
2019-05-20 09:14:07 +03:00
|
|
|
go i acc | i >= 8 = acc
|
|
|
|
go i acc | otherwise = go (i+1) (fromIntegral (shiftR wor (i*8)) : acc)
|
|
|
|
|
|
|
|
--------------------------------------------------------------------------------
|
2019-05-20 04:53:32 +03:00
|
|
|
|
2019-05-21 02:04:28 +03:00
|
|
|
bytesToWords :: ByteString -> VP.Vector Word
|
|
|
|
bytesToWords bytes =
|
|
|
|
VP.generate (1 + length bytes `div` 8) $ \i ->
|
|
|
|
packWord (BS.drop (i*8) bytes)
|
|
|
|
|
|
|
|
fromPrimVec :: Prim a => VP.Vector a -> V.Vector a
|
|
|
|
fromPrimVec vp = V.generate (VP.length vp) (VP.unsafeIndex vp)
|
2019-05-20 09:14:07 +03:00
|
|
|
|
2019-05-21 02:04:28 +03:00
|
|
|
wordsToBytes :: VP.Vector Word -> ByteString
|
|
|
|
wordsToBytes = stripTrailingZeros . concat . fmap unpackWord . fromPrimVec
|
2019-05-20 09:14:07 +03:00
|
|
|
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
|
2019-05-21 02:04:28 +03:00
|
|
|
dumbPackAtom :: ByteString -> Atom
|
|
|
|
dumbPackAtom bs = go 0 0 (toList bs)
|
2019-05-20 09:14:07 +03:00
|
|
|
where
|
|
|
|
go acc i [] = acc
|
|
|
|
go acc i (x:xs) = go (acc .|. shiftL (fromIntegral x) (8*i)) (i+1) xs
|
2019-05-20 04:53:32 +03:00
|
|
|
|
2019-05-21 02:04:28 +03:00
|
|
|
packAtom :: ByteString -> Atom
|
|
|
|
packAtom = MkAtom . wordsToNatural . bytesToWords . stripTrailingZeros
|
|
|
|
|
|
|
|
unpackAtom :: Atom -> ByteString
|
|
|
|
unpackAtom (MkAtom a) = wordsToBytes (naturalToWords a)
|
|
|
|
|
|
|
|
--------------------------------------------------------------------------------
|
2019-05-20 04:53:32 +03:00
|
|
|
|
|
|
|
loadFile :: FilePath -> IO Atom
|
2019-05-21 02:04:28 +03:00
|
|
|
loadFile = fmap packAtom . readFile
|
2019-05-20 04:53:32 +03:00
|
|
|
|
|
|
|
loadJam :: FilePath -> IO (Maybe Noun)
|
|
|
|
loadJam = fmap cue . loadFile
|
|
|
|
|
2019-05-21 02:04:28 +03:00
|
|
|
dumpJam :: FilePath -> Noun -> IO ()
|
|
|
|
dumpJam pat = writeFile pat . unpackAtom . jam
|
2019-05-20 04:53:32 +03:00
|
|
|
|
|
|
|
dumpFlat :: Flat a => FilePath -> a -> IO ()
|
|
|
|
dumpFlat pat = writeFile pat . flat
|
|
|
|
|
|
|
|
loadFlat :: Flat a => FilePath -> IO (Either Text a)
|
|
|
|
loadFlat pat = do
|
|
|
|
bs <- readFile pat
|
|
|
|
pure $ mapLeft tshow $ unflat bs
|
|
|
|
|
2019-05-20 09:14:07 +03:00
|
|
|
data Pill = Brass | Ivory | Solid
|
|
|
|
|
2019-05-21 02:04:28 +03:00
|
|
|
instance Show Pill where
|
|
|
|
show = \case
|
|
|
|
Brass -> "./bin/brass.pill"
|
|
|
|
Solid -> "./bin/solid.pill"
|
|
|
|
Ivory -> "./bin/ivory.pill"
|
|
|
|
|
|
|
|
tryLoadPill :: Pill -> IO ()
|
|
|
|
tryLoadPill pill = do
|
|
|
|
a@(MkAtom nat) <- loadFile (show pill)
|
|
|
|
putStrLn "loaded"
|
|
|
|
print (a > 0)
|
|
|
|
putStrLn "evaled"
|
|
|
|
print (take 10 $ VP.toList $ naturalToWords nat)
|
|
|
|
|
|
|
|
tryCuePill :: Pill -> IO ()
|
|
|
|
tryCuePill pill =
|
|
|
|
loadJam (show pill) >>= \case Nothing -> print "nil"
|
|
|
|
Just (Atom _) -> print "atom"
|
|
|
|
_ -> print "cell"
|
2019-05-20 09:14:07 +03:00
|
|
|
|
|
|
|
-- Tests -----------------------------------------------------------------------
|
|
|
|
|
2019-05-21 02:04:28 +03:00
|
|
|
instance Arbitrary ByteString where
|
|
|
|
arbitrary = fromList <$> arbitrary
|
|
|
|
|
2019-05-20 09:14:07 +03:00
|
|
|
instance Arbitrary BigNat where
|
|
|
|
arbitrary = naturalToBigNat <$> arbitrary
|
|
|
|
|
|
|
|
instance Show BigNat where
|
|
|
|
show = show . NatJ#
|
|
|
|
|
|
|
|
roundTrip :: Eq a => (a -> b) -> (b -> a) -> (a -> Bool)
|
2019-05-21 02:04:28 +03:00
|
|
|
roundTrip dump load x = x == load (dump x)
|
2019-05-20 09:14:07 +03:00
|
|
|
|
|
|
|
equiv :: Eq b => (a -> b) -> (a -> b) -> (a -> Bool)
|
|
|
|
equiv f g x = f x == g x
|
|
|
|
|
|
|
|
check :: Atom -> Atom
|
|
|
|
check = toAtom . (id :: Integer -> Integer) . fromAtom
|
|
|
|
|
2019-05-21 02:04:28 +03:00
|
|
|
clean :: IsSequence seq
|
|
|
|
=> Int ~ Index seq
|
|
|
|
=> (Eq (Element seq), Num (Element seq))
|
|
|
|
=> seq -> seq
|
|
|
|
clean = stripTrailingZeros
|
|
|
|
|
|
|
|
prop_packWordSane = equiv packWord dumbPackWord . fromList
|
|
|
|
prop_packWord = roundTrip unpackWord packWord
|
|
|
|
prop_unpackWord = roundTrip packWord (clean . unpackWord) . clean . take 8
|
|
|
|
|
|
|
|
prop_unpackBigNat = roundTrip bigNatToWords wordsToBigNat
|
|
|
|
|
|
|
|
prop_packBigNat = roundTrip (wordsToBigNat . VP.fromList)
|
|
|
|
(clean . VP.toList . bigNatToWords)
|
|
|
|
. clean
|
|
|
|
|
|
|
|
prop_implodeBytes = roundTrip bytesToWords wordsToBytes . clean
|
|
|
|
|
|
|
|
prop_explodeBytes = roundTrip (wordsToBytes . VP.fromList)
|
|
|
|
(clean . VP.toList . bytesToWords)
|
|
|
|
. clean
|
|
|
|
|
|
|
|
prop_packAtomSane = equiv packAtom dumbPackAtom . fromList
|
|
|
|
prop_unpackAtom = roundTrip unpackAtom packAtom
|
|
|
|
prop_packAtom = roundTrip packAtom unpackAtom . clean
|
2019-05-20 09:14:07 +03:00
|
|
|
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
main :: IO ()
|
|
|
|
main = $(defaultMainGenerator)
|