shrub/pkg/hair/lib/Data/Noun/Jam.hs
2019-05-20 16:04:28 -07:00

219 lines
7.4 KiB
Haskell

module Data.Noun.Jam where
import ClassyPrelude
import Data.Noun
import Data.Noun.Atom
import Data.Noun.Poet
import Data.Bits
import Control.Lens
import Text.Printf
import Data.Map (Map)
import Control.Monad (guard)
import Test.Tasty
import Test.Tasty.TH
import Test.Tasty.QuickCheck as QC
import Test.QuickCheck
-- Length-Encoded Atoms --------------------------------------------------------
bex :: (Num a, Bits a) => Int -> a
bex = shiftL 1
mat' :: Atom -> Buf
mat' 0 = Buf 1 1
mat' atm = Buf bufWid buffer
where
atmWid = bitWidth atm
preWid = bitWidth (toAtom atmWid)
bufWid = preWid + preWid + atmWid - 1
prefix = bex preWid
extras = takeBits (preWid-1) (toAtom atmWid)
suffix = xor extras (shiftL (takeBits (atmWid-1) atm) (preWid-1))
buffer = bitConcat prefix suffix
rub' :: Cursor -> Maybe Buf
rub' slc@(Cursor idx buf) =
leadingZeros slc >>= \case
0 -> pure (Buf 1 0)
prefix -> pure (Buf sz val)
where
widIdx = idx + 1 + prefix
width = fromSlice (Slice widIdx (prefix - 1) buf)
datIdx = widIdx + (prefix-1)
datWid = fromIntegral (2^(prefix-1) + width) - 1
sz = datWid + (2*prefix)
val = bex datWid .|. fromSlice (Slice datIdx datWid buf)
jam' :: Noun -> Atom
jam' = toAtom . fst . go 0 mempty
where
insertNoun :: Noun -> Int -> Map Noun Int -> Map Noun Int
insertNoun n i tbl = lookup n tbl
& maybe tbl (const $ insertMap n i tbl)
go :: Int -> Map Noun Int -> Noun -> (Buf, Map Noun Int)
go off oldTbl noun =
let tbl = insertNoun noun off oldTbl in
case (lookup noun oldTbl, noun) of
(Just ref, Atom atm) | bitWidth atm <= bitWidth (toAtom ref) ->
(Buf (1+sz) (shiftL res 1), tbl)
where Buf sz res = mat' atm
(Just ref, _) ->
(Buf (2+sz) (xor 3 (shiftL res 2)), tbl)
where Buf sz res = mat' (toAtom ref)
(Nothing, Atom atm) ->
(Buf (1+sz) (shiftL res 1), tbl)
where Buf sz res = mat' atm
(Nothing, Cell lef rit) ->
(Buf (2+lSz+rSz) (xor 1 (shiftL (lRes .|. shiftL rRes lSz) 2)), rTbl)
where (Buf lSz lRes, lTbl) = go (off+2) tbl lef
(Buf rSz rRes, rTbl) = go (off+lSz) lTbl rit
cue' :: Atom -> Maybe Noun
cue' buf = view _2 <$> go mempty 0
where
go :: Map Int Noun -> Int -> Maybe (Int, Noun, Map Int Noun)
go tbl i =
case (bitIdx i buf, bitIdx (i+1) buf) of
(False, _ ) -> do Buf wid at <- rub' (Cursor (i+1) buf)
let r = toNoun at
pure (wid+1, r, insertMap i r tbl)
(True, False ) -> do (lSz,lef,tbl) <- go tbl (i+2)
(rSz,rit,tbl) <- go tbl (i+2+fromIntegral lSz)
let r = Cell lef rit
pure (2+lSz+rSz, r, insertMap i r tbl)
(True, True ) -> do Buf wid at <- rub' (Cursor (i+2) buf)
r <- lookup (fromIntegral at) tbl & \case
Nothing -> error ("bad-ref-" <> show at)
Just ix -> Just ix
pure (2+wid, r, tbl)
--------------------------------------------------------------------------------
mat :: Atom -> Buf
mat 0 = Buf 1 1
mat atm = Buf bufWid buffer
where
atmWid = bitWidth atm
preWid = bitWidth (toAtom atmWid)
bufWid = preWid + preWid + atmWid
prefix = shiftL 1 preWid
extras = takeBits (preWid-1) (toAtom atmWid)
suffix = xor extras (shiftL atm (preWid-1))
buffer = bitConcat prefix suffix
bufVal Nothing = "<nil>"
bufVal (Just (Buf sz v)) = show v <> " [" <> show sz <> "]"
rub :: Cursor -> Maybe Buf
rub slc@(Cursor idx buf) =
leadingZeros slc >>= \case
0 -> pure (Buf 1 0)
prefix -> pure (Buf sz val)
where
widIdx = idx + 1 + prefix
width = fromSlice (Slice widIdx (prefix - 1) buf)
datIdx = widIdx + (prefix-1)
datWid = fromIntegral $ 2^(prefix-1) + width
sz = datWid + (2*prefix)
val = fromSlice (Slice datIdx datWid buf)
-- Noun Serialization ----------------------------------------------------------
-- bex can be implemented using
-- `mpz_mul_2exp(a_mp, a_mp, a);
jam :: Noun -> Atom
jam = toAtom . fst . go 0 mempty
where
insertNoun :: Noun -> Int -> Map Noun Int -> Map Noun Int
insertNoun n i tbl = lookup n tbl
& maybe tbl (const $ insertMap n i tbl)
go :: Int -> Map Noun Int -> Noun -> (Buf, Map Noun Int)
go off oldTbl noun =
let tbl = insertNoun noun off oldTbl in
case (lookup noun oldTbl, noun) of
(Just ref, Atom atm) | bitWidth atm <= bitWidth (toAtom ref) ->
(Buf (1+sz) (shiftL res 1), tbl)
where Buf sz res = mat atm
(Just ref, _) ->
(Buf (2+sz) (xor 3 (shiftL res 2)), tbl)
where Buf sz res = mat (toAtom ref)
(Nothing, Atom atm) ->
(Buf (1+sz) (shiftL res 1), tbl)
where Buf sz res = mat atm
(Nothing, Cell lef rit) ->
(Buf (2+lSz+rSz) (xor 1 (shiftL (bitConcat lRes rRes) 2)), rTbl)
where (Buf lSz lRes, lTbl) = go (off+2) tbl lef
(Buf rSz rRes, rTbl) = go (off+lSz) lTbl rit
leadingZeros :: Cursor -> Maybe Int
leadingZeros (Cursor idx buf) = go 0
where wid = bitWidth buf
go n = do () <- if (n < wid) then pure ()
else error "infinite-atom"
guard (n < wid)
if bitIdx (idx+n) buf then pure n else go (n+1)
cue :: Atom -> Maybe Noun
cue buf = view _2 <$> go mempty 0
where
go :: Map Int Noun -> Int -> Maybe (Int, Noun, Map Int Noun)
go tbl i =
-- trace ("go-" <> show i)
case (bitIdx i buf, bitIdx (i+1) buf) of
(False, _ ) -> do Buf wid at <- rub (Cursor (i+1) buf)
let r = toNoun at
pure (wid+1, r, insertMap i r tbl)
(True, False ) -> do (lSz,lef,tbl) <- go tbl (i+2)
(rSz,rit,tbl) <- go tbl (i+2+fromIntegral lSz)
let r = Cell lef rit
pure (2+lSz+rSz, r, insertMap i r tbl)
(True, True ) -> do Buf wid at <- rub (Cursor (i+2) buf)
-- traceM ("ref-" <> show at)
r <- lookup (fromIntegral at) tbl & \case
Nothing -> error ("bad-ref-" <> show at)
Just ix -> Just ix
pure (2+wid, r, tbl)
-- Tests -----------------------------------------------------------------------
pills :: [Atom]
pills = [ 0x2, 0xc, 0x48, 0x29, 0xc9, 0x299
, 0x3170_c7c1, 0x93_c7c1, 0xa_72e0, 0x1bd5_b7dd_e080
]
cueTest :: Maybe [Noun]
cueTest = traverse cue pills
jamTest :: Maybe [Atom]
jamTest = fmap jam <$> cueTest
prop_jamCue :: Noun -> Bool
prop_jamCue n = Just n == cue (jam n)
prop_matRub :: Atom -> Bool
prop_matRub atm = matSz==rubSz && rubRes==atm
where
Buf matSz matBuf = mat atm
Buf rubSz rubRes = fromMaybe mempty (rub $ Cursor 0 matBuf)
prop_jamCue' :: Noun -> Bool
prop_jamCue' n = Just n == cue' (jam' n)
prop_matRub' :: Atom -> Bool
prop_matRub' atm = matSz==rubSz && rubRes==atm
where
Buf matSz matBuf = mat' atm
Buf rubSz rubRes = fromMaybe mempty (rub' $ Cursor 0 matBuf)
main :: IO ()
main = $(defaultMainGenerator)