Skip to content

Instantly share code, notes, and snippets.

@basvandijk
Created November 6, 2011 21:40
Show Gist options
  • Save basvandijk/1343550 to your computer and use it in GitHub Desktop.
Save basvandijk/1343550 to your computer and use it in GitHub Desktop.
{-# LANGUAGE DefaultSignatures
, EmptyDataDecls
, FlexibleInstances
, FunctionalDependencies
, KindSignatures
, OverlappingInstances
, ScopedTypeVariables
, TypeOperators
, UndecidableInstances
, ViewPatterns
, RankNTypes
, FlexibleContexts
, DeriveDataTypeable
#-}
import Control.Applicative
import Control.DeepSeq (NFData(..))
import Control.Monad.State.Strict
import Data.Attoparsec.Char8 (Number(..))
import Data.Bits (shiftR)
import Data.Data (Data)
import Data.Map (Map)
import Data.Monoid
import Data.Text (Text, pack)
import Data.Text (pack, unpack)
import Data.Typeable (Typeable)
import Data.Vector (Vector)
import GHC.Generics
import qualified Data.Map as M
import qualified Data.Text as T
import qualified Data.Vector as V
--------------------------------------------------------------------------------
-- | The result of running a 'Parser'.
data Result a = Error String
| Success a
deriving (Eq, Show, Typeable)
instance (NFData a) => NFData (Result a) where
rnf (Success a) = rnf a
rnf (Error err) = rnf err
instance Functor Result where
fmap f (Success a) = Success (f a)
fmap _ (Error err) = Error err
{-# INLINE fmap #-}
instance Monad Result where
return = Success
{-# INLINE return #-}
Success a >>= k = k a
Error err >>= _ = Error err
{-# INLINE (>>=) #-}
instance Applicative Result where
pure = return
{-# INLINE pure #-}
(<*>) = ap
{-# INLINE (<*>) #-}
instance MonadPlus Result where
mzero = fail "mzero"
{-# INLINE mzero #-}
mplus a@(Success _) _ = a
mplus _ b = b
{-# INLINE mplus #-}
instance Alternative Result where
empty = mzero
{-# INLINE empty #-}
(<|>) = mplus
{-# INLINE (<|>) #-}
instance Monoid (Result a) where
mempty = fail "mempty"
{-# INLINE mempty #-}
mappend = mplus
{-# INLINE mappend #-}
-- | Failure continuation.
type Failure f r = String -> f r
-- | Success continuation.
type Success a f r = a -> f r
-- | A continuation-based parser type.
newtype Parser a = Parser {
runParser :: forall f r.
Failure f r
-> Success a f r
-> f r
}
instance Monad Parser where
m >>= g = Parser $ \kf ks -> let ks' a = runParser (g a) kf ks
in runParser m kf ks'
{-# INLINE (>>=) #-}
return a = Parser $ \_kf ks -> ks a
{-# INLINE return #-}
fail msg = Parser $ \kf _ks -> kf msg
{-# INLINE fail #-}
instance Functor Parser where
fmap f m = Parser $ \kf ks -> let ks' a = ks (f a)
in runParser m kf ks'
{-# INLINE fmap #-}
instance Applicative Parser where
pure = return
{-# INLINE pure #-}
(<*>) = apP
{-# INLINE (<*>) #-}
instance Alternative Parser where
empty = fail "empty"
{-# INLINE empty #-}
(<|>) = mplus
{-# INLINE (<|>) #-}
instance MonadPlus Parser where
mzero = fail "mzero"
{-# INLINE mzero #-}
mplus a b = Parser $ \kf ks -> let kf' _ = runParser b kf ks
in runParser a kf' ks
{-# INLINE mplus #-}
instance Monoid (Parser a) where
mempty = fail "mempty"
{-# INLINE mempty #-}
mappend = mplus
{-# INLINE mappend #-}
apP :: Parser (a -> b) -> Parser a -> Parser b
apP d e = do
b <- d
a <- e
return (b a)
{-# INLINE apP #-}
--------------------------------------------------------------------------------
-- | A JSON \"object\" (key\/value map).
type Object = Map Text Value
-- | A JSON \"array\" (sequence).
type Array = Vector Value
-- | A JSON value represented as a Haskell value.
data Value = Object Object
| Array Array
| String Text
| Number Number
| Bool !Bool
| Null
deriving (Eq, Show, Typeable, Data)
instance NFData Value where
rnf (Object o) = rnf o
rnf (Array a) = V.foldl' (\x y -> rnf y `seq` x) () a
rnf (String s) = rnf s
rnf (Number n) = case n of I i -> rnf i; D d -> rnf d
rnf (Bool b) = rnf b
rnf Null = ()
-- | A key\/value pair for an 'Object'.
type Pair = (Text, Value)
-- | Create a 'Value' from a list of name\/value 'Pair's. If duplicate
-- keys arise, earlier keys and their associated values win.
object :: [Pair] -> Value
object = Object . M.fromList
{-# INLINE object #-}
-- | Fail parsing due to a type mismatch, with a descriptive message.
typeMismatch :: String -- ^ The name of the type you are trying to parse.
-> Value -- ^ The actual value encountered.
-> Parser a
typeMismatch expected actual =
fail $ "when expecting a " ++ expected ++ ", encountered " ++ name ++
" instead"
where
name = case actual of
Object _ -> "Object"
Array _ -> "Array"
String _ -> "String"
Number _ -> "Number"
Bool _ -> "Boolean"
Null -> "Null"
-- | The empty array.
emptyArray :: Value
emptyArray = Array V.empty
-- | Determines if the 'Value' is an empty 'Array'.
-- Note that: @isEmptyArray 'emptyArray'@.
isEmptyArray :: Value -> Bool
isEmptyArray (Array arr) = V.null arr
isEmptyArray _ = False
instance (ToJSON a) => ToJSON [a] where
toJSON = Array . V.fromList . map toJSON
{-# INLINE toJSON #-}
instance ToJSON Value where
toJSON a = a
{-# INLINE toJSON #-}
--------------------------------------------------------------------------------
class ToJSON a where
toJSON :: a -> Value
default toJSON :: (Generic a, GToJSON (Rep a)) => a -> Value
toJSON = gToJSON . from
class FromJSON a where
parseJSON :: Value -> Parser a
default parseJSON :: (Generic a, GFromJSON (Rep a)) => Value -> Parser a
parseJSON = fmap to . gParseJSON
--------------------------------------------------------------------------------
class GToJSON f where
gToJSON :: f a -> Value
class GFromJSON f where
gParseJSON :: Value -> Parser (f a)
--------------------------------------------------------------------------------
-- Generic toJSON
instance (GToJSON a) => GToJSON (M1 i c a) where
gToJSON = gToJSON . unM1
{-# INLINE gToJSON #-}
instance (ToJSON a) => GToJSON (K1 i a) where
gToJSON = toJSON . unK1
{-# INLINE gToJSON #-}
instance GToJSON U1 where
gToJSON _ = emptyArray
{-# INLINE gToJSON #-}
instance (ConsToJSON a) => GToJSON (C1 c a) where
gToJSON = consToJSON . unM1
{-# INLINE gToJSON #-}
instance (GProductToValues a, GProductToValues b) => GToJSON (a :*: b) where
gToJSON = toJSON . toList . gProductToValues
{-# INLINE gToJSON #-}
instance (GObject a, GObject b) => GToJSON (a :+: b) where
gToJSON (L1 x) = Object $ gObject x
gToJSON (R1 x) = Object $ gObject x
{-# INLINE gToJSON #-}
--------------------------------------------------------------------------------
class ConsToJSON f where consToJSON :: f a -> Value
class ConsToJSON' b f where consToJSON' :: Tagged b (f a -> Value)
newtype Tagged s b = Tagged {unTagged :: b}
instance (IsRecord f b, ConsToJSON' b f) => ConsToJSON f where
consToJSON = unTagged (consToJSON' :: Tagged b (f a -> Value))
{-# INLINE consToJSON #-}
instance (GRecordToPairs f) => ConsToJSON' True f where
consToJSON' = Tagged (object . toList . gRecordToPairs)
{-# INLINE consToJSON' #-}
instance GToJSON f => ConsToJSON' False f where
consToJSON' = Tagged gToJSON
{-# INLINE consToJSON' #-}
--------------------------------------------------------------------------------
class GRecordToPairs f where
gRecordToPairs :: f a -> DList Pair
instance (GRecordToPairs a, GRecordToPairs b) => GRecordToPairs (a :*: b) where
gRecordToPairs (a :*: b) = gRecordToPairs a `append` gRecordToPairs b
{-# INLINE gRecordToPairs #-}
instance (Selector s, GToJSON a) => GRecordToPairs (S1 s a) where
gRecordToPairs m1 = singleton (pack (selName m1), gToJSON (unM1 m1))
{-# INLINE gRecordToPairs #-}
--------------------------------------------------------------------------------
class GProductToValues f where
gProductToValues :: f a -> DList Value
instance (GProductToValues a, GProductToValues b) => GProductToValues (a :*: b) where
gProductToValues (a :*: b) = gProductToValues a `append` gProductToValues b
{-# INLINE gProductToValues #-}
instance (GToJSON a) => GProductToValues a where
gProductToValues = singleton . gToJSON
{-# INLINE gProductToValues #-}
--------------------------------------------------------------------------------
class GObject f where
gObject :: f a -> Object
instance (GObject a, GObject b) => GObject (a :+: b) where
gObject (L1 x) = gObject x
gObject (R1 x) = gObject x
{-# INLINE gObject #-}
instance (Constructor c, GToJSON a, ConsToJSON a) => GObject (C1 c a) where
gObject = M.singleton (pack $ conName (undefined :: t c a p)) . gToJSON
{-# INLINE gObject #-}
--------------------------------------------------------------------------------
-- Generic parseJSON
instance (GFromJSON a) => GFromJSON (M1 i c a) where
gParseJSON = fmap M1 . gParseJSON
{-# INLINE gParseJSON #-}
instance (FromJSON a) => GFromJSON (K1 i a) where
gParseJSON = fmap K1 . parseJSON
{-# INLINE gParseJSON #-}
instance GFromJSON U1 where
gParseJSON v
| isEmptyArray v = pure U1
| otherwise = typeMismatch "unit constructor (U1)" v
{-# INLINE gParseJSON #-}
instance (ConsFromJSON a) => GFromJSON (C1 c a) where
gParseJSON = fmap M1 . consParseJSON
{-# INLINE gParseJSON #-}
instance ( GFromProduct a, GFromProduct b
, ProductSize a, ProductSize b) => GFromJSON (a :*: b) where
gParseJSON (Array arr)
| lenArray == lenProduct = gParseProduct arr 0 lenProduct
| otherwise =
fail $ "When expecting a product of " ++ show lenProduct ++
" values, encountered an Array of " ++ show lenArray ++
" elements instead"
where
lenArray = V.length arr
lenProduct = unTagged2 (productSize :: Tagged2 (a :*: b) Int)
gParseJSON v = typeMismatch "product (:*:)" v
{-# INLINE gParseJSON #-}
instance (GFromSum a, GFromSum b) => GFromJSON (a :+: b) where
gParseJSON (Object (M.toList -> [keyVal@(key, _)])) =
case gParseSum keyVal of
Nothing -> notFound $ unpack key
Just p -> p
gParseJSON v = typeMismatch "sum (:+:)" v
{-# INLINE gParseJSON #-}
notFound :: String -> Parser a
notFound key = fail $ "The key \"" ++ key ++ "\" was not found"
{-# INLINE notFound #-}
--------------------------------------------------------------------------------
class ConsFromJSON f where consParseJSON :: Value -> Parser (f a)
class ConsFromJSON' b f where consParseJSON' :: Tagged b (Value -> Parser (f a))
instance (IsRecord f b, ConsFromJSON' b f) => ConsFromJSON f where
consParseJSON = unTagged (consParseJSON' :: Tagged b (Value -> Parser (f a)))
{-# INLINE consParseJSON #-}
instance (GFromRecord f) => ConsFromJSON' True f where
consParseJSON' = Tagged parseRecord
where
parseRecord (Object obj) = gParseRecord obj
parseRecord v = typeMismatch "record (:*:)" v
{-# INLINE consParseJSON' #-}
instance (GFromJSON f) => ConsFromJSON' False f where
consParseJSON' = Tagged gParseJSON
{-# INLINE consParseJSON' #-}
--------------------------------------------------------------------------------
class GFromRecord f where
gParseRecord :: Object -> Parser (f a)
instance (GFromRecord a, GFromRecord b) => GFromRecord (a :*: b) where
gParseRecord obj = (:*:) <$> gParseRecord obj <*> gParseRecord obj
{-# INLINE gParseRecord #-}
instance (Selector s, GFromJSON a) => GFromRecord (S1 s a) where
gParseRecord = maybe (notFound key) gParseJSON . M.lookup (T.pack key)
where
key = selName (undefined :: t s a p)
{-# INLINE gParseRecord #-}
--------------------------------------------------------------------------------
class ProductSize f where
productSize :: Tagged2 f Int
newtype Tagged2 (s :: * -> *) b = Tagged2 {unTagged2 :: b}
instance (ProductSize a, ProductSize b) => ProductSize (a :*: b) where
productSize = Tagged2 $ unTagged2 (productSize :: Tagged2 a Int) +
unTagged2 (productSize :: Tagged2 b Int)
instance ProductSize (S1 s a) where
productSize = Tagged2 1
--------------------------------------------------------------------------------
class GFromProduct f where
gParseProduct :: Array -> Int -> Int -> Parser (f a)
instance (GFromProduct a, GFromProduct b) => GFromProduct (a :*: b) where
gParseProduct arr ix len = (:*:) <$> gParseProduct arr ix lenL
<*> gParseProduct arr ixR lenR
where
lenL = len `shiftR` 1
ixR = ix + lenL
lenR = len - lenL
{-# INLINE gParseProduct #-}
instance (GFromJSON a) => GFromProduct (S1 s a) where
gParseProduct arr ix _ = gParseJSON $ V.unsafeIndex arr ix
{-# INLINE gParseProduct #-}
--------------------------------------------------------------------------------
class GFromSum f where
gParseSum :: Pair -> Maybe (Parser (f a))
instance (GFromSum a, GFromSum b) => GFromSum (a :+: b) where
gParseSum keyVal = (fmap L1 <$> gParseSum keyVal) <|>
(fmap R1 <$> gParseSum keyVal)
{-# INLINE gParseSum #-}
instance (Constructor c, GFromJSON a, ConsFromJSON a) => GFromSum (C1 c a) where
gParseSum (key, value)
| key == pack (conName (undefined :: t c a p)) = Just $ gParseJSON value
| otherwise = Nothing
{-# INLINE gParseSum #-}
--------------------------------------------------------------------------------
class IsRecord (f :: * -> *) b | f -> b
data True
data False
instance (IsRecord f b) => IsRecord (f :*: g) b
instance IsRecord (M1 S NoSelector f) False
instance (IsRecord f b) => IsRecord (M1 S c f) b
instance IsRecord (K1 i c) True
instance IsRecord U1 False
--------------------------------------------------------------------------------
type DList a = [a] -> [a]
toList :: DList a -> [a]
toList = ($ [])
{-# INLINE toList #-}
singleton :: a -> DList a
singleton = (:)
{-# INLINE singleton #-}
append :: DList a -> DList a -> DList a
append = (.)
{-# INLINE append #-}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment