packages feed

benc-0.1.0.0: src/Data/Bencode/AST.hs

{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
module Data.Bencode.AST
  ( Value(..)
  , KeyValue(..)
  , parseOnly
  ) where

import Data.Char (isDigit)
import Data.List (intercalate)
import qualified Data.ByteString as B
import qualified Data.ByteString.Char8 as BC
import qualified Data.Primitive.Array as A

import Data.Bencode.Util (readKnownNaturalAsInt)

-- | The Bencode AST.
data Value
  = String  {-# UNPACK #-} !B.ByteString
  -- ^ Slice of the input @ByteString@.
  | Integer {-# UNPACK #-} !B.ByteString
  -- ^ Slice of the input @ByteString@, containing a valid integer. Parsing
  -- into an integral type is done later if required.
  | List    {-# UNPACK #-} !(A.Array Value)
  | Dict    {-# UNPACK #-} !(A.Array KeyValue)
  deriving (Eq, Show)

-- | A Bencode dict's key-value pair.
data KeyValue = KeyValue
  {-# UNPACK #-} !B.ByteString-- ^ Slice of the input @ByteString@.
  !Value
  deriving (Eq, Show)

newtype Pos = Pos { unPos :: Int } deriving (Show, Num)

-- | Either an error or the parsed value together with the unparsed
-- section of the input and number of bytes parsed.
type ParseOneResult = Either String (Value, B.ByteString, Int)

data Stack
  = SNil
  | SList {-# UNPACK #-} !Int ![Value] !Stack
  | SDict {-# UNPACK #-} !B.ByteString {-# UNPACK #-} !Int ![KeyValue] !Stack

-- | Parse one Bencode value from the given bytestring. Fails if the string is
-- not fully consumed.
parseOnly :: B.ByteString -> Either String Value
parseOnly s = case parseOne s of
  Left e -> Left e
  Right (v, s', n) ->
    if B.null s'
    then Right v
    else errorAtPos "ExpectedEOF" (Pos n)

-- | Parse one Bencode value from the given bytestring.
parseOne :: B.ByteString -> ParseOneResult
parseOne s = case BC.uncons s of
  Nothing -> errItem Nothing pos
  Just (c,s1) -> case c of
    _ | isDigit c -> do
      (str, s2, pos2) <- parseString s pos
      Right (String str, s2, unPos pos2)
    'i' -> do
      (i, s2, pos2) <- parseInteger s1 (pos+1)
      Right (Integer i, s2, unPos pos2)
    'l' -> parseList SNil 0 [] s1 (pos+1)
    'd' -> parseDict SNil s1 (pos+1)
    _   -> errItem (Just c) pos
  where
    pos = Pos 0

-- | Parse a Bencode list. After the \'l\' marker.
parseList :: Stack -> Int -> [Value] -> B.ByteString -> Pos -> ParseOneResult
parseList stk !n !acc s !pos = case BC.uncons s of
  Nothing -> errItemOrEnd Nothing pos
  Just (c,s1) -> case c of
    _ | isDigit c -> do
      (str, s2, pos2) <- parseString s pos
      parseList stk (n+1) (String str : acc) s2 pos2
    'i' -> do
      (i, s2, pos2) <- parseInteger s1 (pos+1)
      parseList stk (n+1) (Integer i : acc) s2 pos2
    'l' -> parseList (SList n acc stk) 0 [] s1 (pos+1)
    'd' -> parseDict (SList n acc stk) s1 (pos+1)
    'e' -> resumeParse stk (List (arrayFromRevListN n acc)) s1 (pos+1)
    _   -> errItemOrEnd (Just c) pos

-- | Parse a Bencode dict. After the \'d\' marker.
parseDict :: Stack -> B.ByteString -> Pos -> ParseOneResult
parseDict stk s !pos = case BC.uncons s of
  Nothing -> errStringOrEnd Nothing pos
  Just (c1,s1) -> case c1 of
    _ | isDigit c1 -> do
      (key, s2, pos2) <- parseString s pos
      case BC.uncons s2 of
        Nothing -> errItem Nothing pos2
        Just (c3,s3) -> case c3 of
          _ | isDigit c3 -> do
            (str, s4, pos4) <- parseString s2 pos2
            parseDict1 key stk 1 [KeyValue key (String str)] s4 pos4
          'i' -> do
            (i, s4, pos4) <- parseInteger s3 (pos2+1)
            parseDict1 key stk 1 [KeyValue key (Integer i)] s4 pos4
          'l' -> parseList (SDict key 0 [] stk) 0 [] s3 (pos2+1)
          'd' -> parseDict (SDict key 0 [] stk) s3 (pos2+1)
          _   -> errItem (Just c3) pos2
    'e' -> resumeParse stk (Dict (arrayFromRevListN 0 [])) s1 (pos+1)
    _   -> errStringOrEnd (Just c1) pos

-- | Parse a Bencode dict. After the first key-value pair.
parseDict1 :: B.ByteString -> Stack -> Int -> [KeyValue] -> B.ByteString -> Pos
           -> ParseOneResult
parseDict1 !pkey stk !n !acc s !pos = case BC.uncons s of
  Nothing -> errStringOrEnd Nothing pos
  Just (c1,s1) -> case c1 of
    _ | isDigit c1 -> do
      (key, s2, pos2) <- parseString s pos
      if pkey >= key
      then errUnsortedKeys pkey key pos
      else case BC.uncons s2 of
        Nothing -> errItem Nothing pos2
        Just (c3,s3) -> case c3 of
          _ | isDigit c3 -> do
            (str, s4, pos4) <- parseString s2 pos2
            parseDict1 key stk (n+1) (KeyValue key (String str) : acc) s4 pos4
          'i' -> do
            (i, s4, pos4) <- parseInteger s3 (pos2+1)
            parseDict1 key stk (n+1) (KeyValue key (Integer i) : acc) s4 pos4
          'l' -> parseList (SDict key n acc stk) 0 [] s3 (pos2+1)
          'd' -> parseDict (SDict key n acc stk) s3 (pos2+1)
          _   -> errItem (Just c3) pos2
    'e' -> resumeParse stk (Dict (arrayFromRevListN n acc)) s1 (pos+1)
    _   -> errStringOrEnd (Just c1) pos

-- | Add the value to the previously incomplete value on the stack, and resume
-- parsing it.
resumeParse :: Stack -> Value -> B.ByteString -> Pos -> ParseOneResult
resumeParse stk !x s !pos = case stk of
  SNil               -> Right (x, s, unPos pos)
  SList n xs stk1    -> parseList stk1 (n+1) (x:xs) s pos
  SDict k n acc stk1 -> parseDict1 k stk1 (n+1) (KeyValue k x : acc) s pos
{-# INLINE resumeParse #-}

-- | Parse a Bencode integer. After the \'i\' to the \'e\'.
parseInteger :: B.ByteString -> Pos
             -> Either String (B.ByteString, B.ByteString, Pos)
parseInteger s !pos = case BC.uncons s of
  Nothing -> errDigit Nothing pos
  Just (c1,s1) -> case c1 of
    '0' -> end (B.take 1 s) s1 (pos+1)
    '-' -> case BC.span isDigit s1 of
      (x,s2) -> case BC.uncons x of
        Just (c3,_) | c3 /= '0' ->
          let n = B.length x + 1 in end (B.take n s) s2 (pos + Pos n)
        _ -> errNZDigit (fmap fst (BC.uncons s2)) (pos+1)
    _ -> case BC.span isDigit s of
      (x,s2) -> if B.null x
        then errDigitOrNeg (Just c1) pos
        else let n = B.length x in end x s2 (pos + Pos n)
  where
    end x s' !pos' = case BC.uncons s' of
      Nothing -> errEnd Nothing pos'
      Just (c,s'') -> case c of
        'e' -> Right (x, s'', pos'+1)
        _   -> errEnd (Just c) pos'
{-# INLINE parseInteger #-}

-- | Parse a Bencode string. From the length count to the end of the string.
parseString :: B.ByteString -> Pos
            -> Either String (B.ByteString, B.ByteString, Pos)
parseString s !pos = case BC.span isDigit s of
  (digs,s1) -> case readKnownNaturalAsInt False (BC.dropWhile (=='0') digs) of
    Nothing -> errTooLargeStringLength pos
    Just n ->
      let pos2 = pos + Pos (B.length digs)
      in case BC.uncons s1 of
          Nothing -> errColon Nothing pos2
          Just (c3,s3) -> case c3 of
            ':' -> case B.splitAt n s3 of
              (str,s4) | B.length str == n -> Right (str, s4, pos2 + 1 + Pos n)
              _ -> errTooLargeStringLength pos
            _   -> errColon (Just c3) pos2
{-# INLINE parseString #-}

------------------------------
-- Error stuff

errorAtPos :: String -> Pos -> Either String a
errorAtPos e (Pos n) = Left $ "ParseErrorAt " ++ show n ++ ": " ++ e

mismatch :: [String] -> Maybe Char -> Pos -> Either String a
mismatch cs c = errorAtPos $
  "ExpectedOneOfButGot [" ++ intercalate "," cs ++ "] " ++ maybe "EOF" show c

errItem, errItemOrEnd, errStringOrEnd, errEnd, errDigit, errNZDigit,
  errColon, errDigitOrNeg :: Maybe Char -> Pos -> Either String a
errItem        = mismatch ["Digit", show 'i', show 'l', show 'd']
errItemOrEnd   = mismatch ["Digit", show 'i', show 'l', show 'd', show 'e']
errStringOrEnd = mismatch ["Digit", show 'e']
errEnd         = mismatch [show 'e']
errDigit       = mismatch ["Digit"]
errNZDigit     = mismatch ["NonZeroDigit"]
errDigitOrNeg  = mismatch ["Digit", show '-']
errColon       = mismatch [show ':']

errUnsortedKeys :: B.ByteString -> B.ByteString -> Pos -> Either String a
errUnsortedKeys pkey key = errorAtPos $
  "UnsortedKeys " ++ show pkey ++ " " ++ show key

errTooLargeStringLength :: Pos -> Either String a
errTooLargeStringLength = errorAtPos "TooLargeStringLength"

------------------------------
-- Array

-- | Create an array from a list in reverse order.
arrayFromRevListN :: Int -> [a] -> A.Array a
arrayFromRevListN n xs = A.createArray n errorElement $ \a ->
  let f x k = \i ->
        if i == -1
        then pure ()
        else A.writeArray a i x *> k (i-1)
  in foldr f (\ !_ -> pure ()) xs (n-1)
{-# INLINE arrayFromRevListN #-}

errorElement :: a
errorElement = error "errorElement"