flp-0.1.0.0: src/Language/Rust/Data/InputStream.hs
{-|
Module : Language.Rust.Data.InputStream
Description : Interface to the underlying input of parsing
Copyright : (c) Alec Theriault, 2017-2018
License : BSD-style
Maintainer : alec.theriault@gmail.com
Stability : experimental
Portability : portable
These are the only functions that need to be implemented in order to use the parser. Whether this
wraps 'BS.ByteString' or 'String' depends on whether the @useByteStrings@ option is on or not (it is
by default). Using 'BS.ByteString' means better handling of weird characters ('takeByte' for plain
'String' fails badly if you try to take a byte that doesn't fall on a character boundary), but it
means incurring a dependency on the [utf8-string](https://hackage.haskell.org/package/utf8-string)
package.
-}
{-# LANGUAGE CPP #-}
module Language.Rust.Data.InputStream (
-- * InputStream type
InputStream,
countLines,
inputStreamEmpty,
-- * Introduction forms
readInputStream,
hReadInputStream,
inputStreamFromString,
-- * Elimination forms
inputStreamToString,
takeByte,
takeChar,
peekChars,
) where
import Data.Word ( Word8 )
import Data.Coerce ( coerce )
import Data.String ( IsString(..) )
import System.IO
#ifdef USE_BYTESTRING
import qualified Data.ByteString as BS
import qualified Data.ByteString.UTF8 as BE
#else
import qualified Data.Char as Char
#endif
-- | Read an encoded file into an 'InputStream'
readInputStream :: FilePath -> IO InputStream
{-# INLINE readInputStream #-}
-- | Read an 'InputStream' from a 'Handle'
hReadInputStream :: Handle -> IO InputStream
{-# INLINE hReadInputStream #-}
-- | Convert 'InputStream' to 'String'.
inputStreamToString :: InputStream -> String
{-# INLINE inputStreamToString #-}
-- | Convert a 'String' to an 'InputStream'.
inputStreamFromString :: String -> InputStream
{-# INLINE inputStreamFromString #-}
-- | Uses 'inputStreamFromString'
instance IsString InputStream where fromString = inputStreamFromString
-- | Read the first byte from an 'InputStream' and return that byte with what remains of the
-- 'InputStream'. Behaviour is undefined when 'inputStreamEmpty' returns 'True'.
--
-- >>> takeByte "foo bar"
-- (102, "oo bar")
--
-- >>> takeByte "Ĥăƨĸëļļ"
-- (196, "\ETX\168\&8\235<<")
--
takeByte :: InputStream -> (Word8, InputStream)
{-# INLINE takeByte #-}
-- | Read the first character from an 'InputStream' and return that 'Char' with what remains of the
-- 'InputStream'. Behaviour is undefined when 'inputStreamEmpty' returns 'True'.
--
-- >>> takeChar "foo bar"
-- ('f', "oo bar")
--
-- >>> takeChar "Ĥăƨĸëļļ"
-- ('Ĥ', "ăƨĸëļļ")
--
takeChar :: InputStream -> (Char, InputStream)
{-# INLINE takeChar #-}
-- | Return @True@ if the given input stream is empty.
--
-- >>> inputStreamEmpty ""
-- True
--
-- >>> inputStreamEmpty "foo"
-- False
--
inputStreamEmpty :: InputStream -> Bool
{-# INLINE inputStreamEmpty #-}
-- | Returns the first @n@ characters of the given input stream, without removing them.
--
-- >>> peekChars 5 "foo bar"
-- "foo ba"
--
-- >>> peekChars 5 "foo"
-- "foo"
--
-- >>> peekChars 3 "Ĥăƨĸëļļ"
-- "Ĥăƨ"
--
peekChars :: Int -> InputStream -> String
{-# INLINE peekChars #-}
-- | Returns the number of text lines in the given 'InputStream'
--
-- >>> countLines ""
-- 0
--
-- >>> countLines "foo"
-- 1
--
-- >>> countLines "foo\n\nbar"
-- 3
--
-- >>> countLines "foo\n\nbar\n"
-- 3
--
countLines :: InputStream -> Int
{-# INLINE countLines #-}
#ifdef USE_BYTESTRING
-- | Opaque input type.
newtype InputStream = IS BS.ByteString deriving (Eq, Ord)
takeByte bs = (BS.head (coerce bs), coerce (BS.tail (coerce bs)))
takeChar bs = maybe (error "takeChar: no char left") coerce (BE.uncons (coerce bs))
inputStreamEmpty = BS.null . coerce
peekChars n = BE.toString . BE.take n . coerce
readInputStream f = coerce <$> BS.readFile f
hReadInputStream h = coerce <$> BS.hGetContents h
inputStreamToString = BE.toString . coerce
inputStreamFromString = IS . BE.fromString
countLines = length . BE.lines . coerce
instance Show InputStream where
show (IS bs) = show bs
#else
-- | Opaque input type.
newtype InputStream = IS String deriving (Eq, Ord)
takeByte (IS ~(c:str))
| Char.isLatin1 c = let b = fromIntegral (Char.ord c) in b `seq` (b, IS str)
| otherwise = error "takeByte: not a latin-1 character"
takeChar (IS ~(c:str)) = (c, IS str)
inputStreamEmpty (IS str) = null str
peekChars n (IS str) = take n str
readInputStream f = IS <$> readFile f
hReadInputStream h = IS <$> hGetContents h
inputStreamToString = coerce
inputStreamFromString = IS
countLines (IS str) = length . lines $ str
instance Show InputStream where
show (IS bs) = show bs
#endif