-- | An outout buffer for ByteStrings that keeps track of line and
-- column numbers.
module Floskell.Buffer
    ( Buffer
    , empty
    , newline
    , write
    , line
    , column
    , toLazyByteString
    ) where

import qualified Data.ByteString         as BS
import           Data.ByteString.Builder ( Builder )
import qualified Data.ByteString.Builder as BB
import qualified Data.ByteString.Lazy    as BL

data Buffer =
    Buffer { Buffer -> Builder
bufferData        :: !Builder -- ^ The current output.
           , Buffer -> Builder
bufferDataNoSpace :: !Builder -- ^ The current output without trailing spaces.
           , Buffer -> Int
bufferLine        :: !Int     -- ^ Current line number.
           , Buffer -> Int
bufferColumn      :: !Int     -- ^ Current column number.
           }

-- | An empty output buffer.
empty :: Buffer
empty :: Buffer
empty = Buffer :: Builder -> Builder -> Int -> Int -> Buffer
Buffer { bufferData :: Builder
bufferData        = Builder
forall a. Monoid a => a
mempty
               , bufferDataNoSpace :: Builder
bufferDataNoSpace = Builder
forall a. Monoid a => a
mempty
               , bufferLine :: Int
bufferLine        = Int
0
               , bufferColumn :: Int
bufferColumn      = Int
0
               }

-- | Append a ByteString to the output buffer.  It is an error for the
-- string to contain newlines.
write :: BS.ByteString -> Buffer -> Buffer
write :: ByteString -> Buffer -> Buffer
write ByteString
str Buffer
buf =
    Buffer
buf { bufferData :: Builder
bufferData        = Builder
newBufferData
        , bufferDataNoSpace :: Builder
bufferDataNoSpace =
              if (Word8 -> Bool) -> ByteString -> Bool
BS.all (Word8 -> Word8 -> Bool
forall a. Eq a => a -> a -> Bool
== Word8
32) ByteString
str then Buffer -> Builder
bufferData Buffer
buf else Builder
newBufferData
        , bufferColumn :: Int
bufferColumn      = Buffer -> Int
bufferColumn Buffer
buf Int -> Int -> Int
forall a. Num a => a -> a -> a
+ ByteString -> Int
BS.length ByteString
str
        }
  where
    newBufferData :: Builder
newBufferData = Buffer -> Builder
bufferData Buffer
buf Builder -> Builder -> Builder
forall a. Monoid a => a -> a -> a
`mappend` ByteString -> Builder
BB.byteString ByteString
str

-- | Append a newline to the output buffer.
newline :: Buffer -> Buffer
newline :: Buffer -> Buffer
newline Buffer
buf = Buffer
buf { bufferData :: Builder
bufferData        = Builder
newBufferData
                  , bufferDataNoSpace :: Builder
bufferDataNoSpace = Builder
newBufferData
                  , bufferLine :: Int
bufferLine        = Buffer -> Int
bufferLine Buffer
buf Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
1
                  , bufferColumn :: Int
bufferColumn      = Int
0
                  }
  where
    newBufferData :: Builder
newBufferData = Buffer -> Builder
bufferDataNoSpace Buffer
buf Builder -> Builder -> Builder
forall a. Monoid a => a -> a -> a
`mappend` Char -> Builder
BB.char7 Char
'\n'

-- | Return the current line number, counting from 0.
line :: Buffer -> Int
line :: Buffer -> Int
line = Buffer -> Int
bufferLine

-- | Return the column number, counting from 0.
column :: Buffer -> Int
column :: Buffer -> Int
column = Buffer -> Int
bufferColumn

-- | Return the contents of the output buffer as a lazy ByteString.
toLazyByteString :: Buffer -> BL.ByteString
toLazyByteString :: Buffer -> ByteString
toLazyByteString = Builder -> ByteString
BB.toLazyByteString (Builder -> ByteString)
-> (Buffer -> Builder) -> Buffer -> ByteString
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Buffer -> Builder
bufferData