tricu

An interpreted language for exploring Tree Calculus
Log | Files | Refs | README | LICENSE

Arboricx.hs (5575B)


      1 {-# LANGUAGE BangPatterns #-}
      2 
      3 module ContentStore.Arboricx
      4   ( merkleNodeDomain
      5   , putNode
      6   , getNode
      7   , treeTermDomain
      8   , encodeTreeTerm
      9   , decodeTreeTerm
     10   , decodeTreeTermShared
     11   , putTreeTerm
     12   , getTreeTerm
     13   , putTree
     14   , getTree
     15   ) where
     16 
     17 import ContentStore.Filesystem
     18 import ContentStore.Object
     19 import Runtime
     20 
     21 import qualified Data.ByteString as BS
     22 import qualified Data.ByteString.Lazy as BL
     23 import Data.ByteString.Builder (word8, toLazyByteString)
     24 import qualified NodeTable as Nodes
     25 
     26 merkleNodeDomain :: Domain
     27 merkleNodeDomain = Domain "arboricx.merkle.node.v1"
     28 
     29 treeTermDomain :: Domain
     30 treeTermDomain = Domain "arboricx.tree-term.v1"
     31 
     32 putNode :: StorePath -> Node -> IO ObjectHash
     33 putNode store node = putObject store merkleNodeDomain (serializeNode node)
     34 
     35 getNode :: StorePath -> ObjectHash -> IO (Maybe Node)
     36 getNode store h = fmap deserializeNode <$> getVerifiedObject store merkleNodeDomain h
     37 
     38 -- | Store a complete normal tree as one content object. Merkle nodes remain
     39 -- available for DAG use cases, but module executable exports use this object
     40 -- kind to avoid filesystem writes for every subtree of large normal forms.
     41 encodeTreeTerm :: T -> BS.ByteString
     42 encodeTreeTerm tree = BL.toStrict (toLazyByteString (go [tree]))
     43   where
     44     -- Canonical preorder over logical occurrences, without repeatedly copying
     45     -- encoded subtrees. The explicit work stack does not intern or apply trees.
     46     go [] = mempty
     47     go (Leaf : rest) = word8 0 <> go rest
     48     go (Stem child : rest) = word8 1 <> go (child : rest)
     49     go (Fork left right : rest) = word8 2 <> go (left : right : rest)
     50 
     51 decodeTreeTerm :: BS.ByteString -> Either String T
     52 decodeTreeTerm payload = do
     53   (term, rest) <- getTerm payload
     54   if BS.null rest
     55     then Right term
     56     else Left "trailing bytes after tree term"
     57   where
     58     getTerm bs = case BS.uncons bs of
     59       Nothing -> Left "unexpected end of tree term"
     60       Just (0x00, rest) -> Right (Leaf, rest)
     61       Just (0x01, rest) -> do
     62         (child, afterChild) <- getTerm rest
     63         Right (Stem child, afterChild)
     64       Just (0x02, rest) -> do
     65         (left, afterLeft) <- getTerm rest
     66         (right, afterRight) <- getTerm afterLeft
     67         Right (Fork left right, afterRight)
     68       Just (tag, _) -> Left $ "unknown tree term tag: " ++ show tag
     69 
     70 -- ReadTreeTerm transports executable libraries. Raw canonical bytes repeat
     71 -- logical subtrees; allocating each occurrence loses the sharing that keeps
     72 -- large stable compiler libraries practical. Intern only within this decode,
     73 -- using exact constructor/child IDs (never tree equality, hashes or evaluation).
     74 -- Other raw data readers keep the ordinary decoder: a mostly unique data tree
     75 -- can cost more with an intern table. Space is O(unique nodes + input + depth),
     76 -- time O(logical bytes * min(word bits, unique nodes)); the table is not globally retained.
     77 data SharedDecodeFrame
     78   = SharedStem
     79   | SharedForkLeft
     80   | SharedForkRight !(Int, T)
     81 
     82 decodeTreeTermShared :: BS.ByteString -> Either String T
     83 decodeTreeTermShared payload = readNode 0 [] Nodes.empty 1
     84   where
     85     !lengthBytes = BS.length payload
     86     readNode !offset frames !table !next
     87       | offset >= lengthBytes = Left "unexpected end of tree term"
     88       | otherwise = case BS.index payload offset of
     89           0 -> deliver (0, Leaf) (offset + 1) frames table next
     90           1 -> readNode (offset + 1) (SharedStem : frames) table next
     91           2 -> readNode (offset + 1) (SharedForkLeft : frames) table next
     92           tag -> Left $ "unknown tree term tag: " ++ show tag
     93     deliver (_, tree) !offset [] _ _
     94       | offset == lengthBytes = Right tree
     95       | otherwise = Left "trailing bytes after tree term"
     96     deliver (childId, child) !offset (SharedStem : frames) table next =
     97       intern (Nodes.StemKey childId) (Stem child) offset frames table next
     98     deliver left !offset (SharedForkLeft : frames) table next =
     99       readNode offset (SharedForkRight left : frames) table next
    100     deliver (rightId, right) !offset (SharedForkRight (leftId, left) : frames) table next =
    101       intern (Nodes.ForkKey leftId rightId) (Fork left right) offset frames table next
    102     intern key tree offset frames table next = case Nodes.lookup key table of
    103       Just node -> deliver node offset frames table next
    104       Nothing ->
    105         let !node = (next, tree)
    106             !updated = Nodes.insert key node table
    107         in deliver node offset frames updated (next + 1)
    108 
    109 putTreeTerm :: StorePath -> T -> IO ObjectHash
    110 putTreeTerm store = putObject store treeTermDomain . encodeTreeTerm
    111 
    112 getTreeTerm :: StorePath -> ObjectHash -> IO (Maybe T)
    113 getTreeTerm store h = do
    114   mPayload <- getVerifiedObject store treeTermDomain h
    115   case mPayload of
    116     Nothing -> pure Nothing
    117     Just payload -> case decodeTreeTerm payload of
    118       Left err -> fail $ "invalid tree term " ++ show h ++ ": " ++ err
    119       Right term -> pure (Just term)
    120 
    121 putTree :: StorePath -> T -> IO ObjectHash
    122 putTree store = go
    123   where
    124     go Leaf = putNode store NLeaf
    125     go (Stem t) = do
    126       child <- go t
    127       putNode store (NStem child)
    128     go (Fork l r) = do
    129       left <- go l
    130       right <- go r
    131       putNode store (NFork left right)
    132 
    133 getTree :: StorePath -> ObjectHash -> IO (Maybe T)
    134 getTree store root = do
    135   mNode <- getNode store root
    136   case mNode of
    137     Nothing -> return Nothing
    138     Just node -> case node of
    139       NLeaf -> return (Just Leaf)
    140       NStem child -> fmap Stem <$> getTree store child
    141       NFork left right -> do
    142         ml <- getTree store left
    143         mr <- getTree store right
    144         return $ Fork <$> ml <*> mr