packages feed

sugar-0.0.1: src/Sugar/Types.hs

{-# LANGUAGE TupleSections, DeriveGeneric, OverloadedStrings, CPP #-}
module Sugar.Types
  ( Sugar(..)
  , Wrap(..)
  , Note
  , readSugarMay
  , sugarTextMay
  , sugarMapAsIxMap
  , reservedChars
  , FromSugar(..)
  , ToSugar(..)
  ) where

import Data.Text (Text)
import Data.Map (Map)
import Data.Maybe (mapMaybe)
import Data.Text.Conversions (ToText(..), fromText, unUTF8, decodeConvertText, UTF8(..))
import Data.String (IsString(..))
import Data.Word (Word8,Word16,Word32,Word64)
import Data.Int (Int8,Int16,Int32,Int64)
import GHC.Generics (Generic)
import Safe (readMay)

import qualified Data.Map as Map
import qualified Data.Serialize as Serialize
import qualified Data.Store as Store ()
import qualified Data.ByteString as BS
import qualified Data.Text as T

---

data Sugar
  = Sugar'Unit Note
  | Sugar'Text Text Note
  | Sugar'List [Sugar] Wrap Note
  | Sugar'Map [(Sugar,Sugar)] Note
  deriving (Eq, Show, Generic)

data Wrap
  = Wrap'Square
  | Wrap'Paren
  deriving (Eq, Show, Generic)

type Note = Maybe [Sugar]

--

sugarTextMay :: Sugar -> Maybe Text
sugarTextMay (Sugar'Text t _) = Just t
sugarTextMay _ = Nothing

readSugarMay :: Read a => Sugar -> Maybe a
readSugarMay (Sugar'Text t _) = readMay $ T.unpack t
readSugarMay _ = Nothing

sugarMapAsIxMap :: [(Sugar,Sugar)] -> Map (Int, Sugar) Sugar
sugarMapAsIxMap = Map.fromList . zipWith (\i (k,v) -> ((i,k),v)) [0..]

sugarShow :: Show a => a -> Sugar
sugarShow s = Sugar'Text (T.pack $ show s) Nothing

reservedChars :: [Char]
reservedChars = ['\"','[',']','<','>','(',')','{','}',';']

--

instance Ord Sugar where
  compare (Sugar'Unit x) (Sugar'Unit y) = compare x y
  compare Sugar'Unit{} _ = GT
  compare _ Sugar'Unit{} = LT
  compare (Sugar'Text x0 x1) (Sugar'Text y0 y1) = compare x0 y0 `mappend` compare x1 y1
  compare Sugar'Text{} _ = GT
  compare _ Sugar'Text{} = LT
  compare (Sugar'List x0 _ x1) (Sugar'List y0 _ y1) = compare x0 y0 `mappend` compare x1 y1
  compare Sugar'List{} _ = GT
  compare _ Sugar'List{} = LT
  compare (Sugar'Map x0 x1) (Sugar'Map y0 y1) = compare x0 y0 `mappend` compare x1 y1

instance Serialize.Serialize Sugar where
  get = do
    tag <- Serialize.getWord8
    go tag
    where
      go :: Word8 -> Serialize.Get Sugar
      go 0 = Sugar'Unit <$> Serialize.get
      go 1 = Sugar'Text <$> getSerializedText <*> Serialize.get
      go 2 = Sugar'List <$> Serialize.get <*> Serialize.get <*> Serialize.get
      go 3 = Sugar'Map <$> Serialize.get <*> Serialize.get
      go _ = fail "No matching Sugar value"

      getSerializedText :: Serialize.Get Text
      getSerializedText = do
        txt <- (decodeConvertText . UTF8) <$> (Serialize.get :: Serialize.Get BS.ByteString)
        maybe (fail "Cannot deserialize text as UTF8") pure txt

  put (Sugar'Unit note) = do
    Serialize.put (0 :: Word8)
    Serialize.put note
  put (Sugar'Text txt note) = do
    Serialize.put (1 :: Word8)
    Serialize.put (unUTF8 $ fromText txt :: BS.ByteString)
    Serialize.put note
  put (Sugar'List xs w note) = do
    Serialize.put (2 :: Word8)
    Serialize.put xs
    Serialize.put w
    Serialize.put note
  put (Sugar'Map m note) = do
    Serialize.put (3 :: Word8)
    Serialize.put m
    Serialize.put note

instance Serialize.Serialize Wrap where

instance IsString Sugar where
  fromString str = Sugar'Text (toText str) Nothing


class FromSugar a where
  parseSugar :: Sugar -> Maybe a

instance FromSugar a => FromSugar [a] where
  parseSugar (Sugar'List xs _ _) = mapM parseSugar xs
  parseSugar _ = Nothing

instance FromSugar a => FromSugar (Maybe a) where
  parseSugar (Sugar'Unit _) = Just Nothing
  parseSugar s = (return . Just) =<< parseSugar s

instance (FromSugar a, Ord a, FromSugar b) => FromSugar (Map a b) where
  parseSugar (Sugar'Map m _) = Just $ Map.fromList $
    mapMaybe
      (\(s,v) -> (,) <$> parseSugar s <*> parseSugar v)
      m
  parseSugar _ = Nothing

instance FromSugar Text where
  parseSugar (Sugar'Text t _) = Just t
  parseSugar _ = Nothing

instance FromSugar Bool where
  parseSugar (Sugar'Text "#t" _) = Just True
  parseSugar (Sugar'Text "#f" _) = Just False
  parseSugar _ = Nothing

instance FromSugar Integer where parseSugar = readSugarMay
instance FromSugar Int where parseSugar = readSugarMay
instance FromSugar Int8 where parseSugar = readSugarMay
instance FromSugar Int16 where parseSugar = readSugarMay
instance FromSugar Int32 where parseSugar = readSugarMay
instance FromSugar Int64 where parseSugar = readSugarMay
instance FromSugar Word where parseSugar = readSugarMay
instance FromSugar Word8 where parseSugar = readSugarMay
instance FromSugar Word16 where parseSugar = readSugarMay
instance FromSugar Word32 where parseSugar = readSugarMay
instance FromSugar Word64 where parseSugar = readSugarMay
instance FromSugar Float where parseSugar = readSugarMay
instance FromSugar Double where parseSugar = readSugarMay


class ToSugar a where
  toSugar :: a -> Sugar

instance ToSugar () where
  toSugar () = Sugar'Unit Nothing

instance ToSugar Text where
  toSugar t = Sugar'Text t Nothing

-- TODO: Will conflict with a String instance (aka [Char])
instance ToSugar a => ToSugar [a] where
  toSugar xs = Sugar'List (map toSugar xs) Wrap'Square Nothing

instance ToSugar a => ToSugar (Maybe a) where
  toSugar Nothing = Sugar'Unit Nothing
  toSugar (Just a) = toSugar a

instance (ToSugar a, ToSugar b) => ToSugar (Map a b) where
  toSugar m = Sugar'Map (map (\(k,v) -> (toSugar k, toSugar v)) $ Map.toList m) Nothing

instance (ToSugar a, ToSugar b) => ToSugar (a,b) where
  toSugar (a,b) = Sugar'List [toSugar a, toSugar b] Wrap'Paren Nothing

instance (ToSugar a, ToSugar b, ToSugar c) => ToSugar (a,b,c) where
  toSugar (a,b,c) = Sugar'List [toSugar a, toSugar b, toSugar c] Wrap'Paren Nothing

instance ToSugar Bool where
  toSugar s = toSugar (if s then "#t" else "#f" :: Text)

instance ToSugar Integer where toSugar = sugarShow
instance ToSugar Int where toSugar = sugarShow
instance ToSugar Int8 where toSugar = sugarShow
instance ToSugar Int16 where toSugar = sugarShow
instance ToSugar Int32 where toSugar = sugarShow
instance ToSugar Int64 where toSugar = sugarShow
instance ToSugar Word where toSugar = sugarShow
instance ToSugar Word8 where toSugar = sugarShow
instance ToSugar Word16 where toSugar = sugarShow
instance ToSugar Word32 where toSugar = sugarShow
instance ToSugar Word64 where toSugar = sugarShow
instance ToSugar Float where toSugar = sugarShow
instance ToSugar Double where toSugar = sugarShow