Created
November 6, 2011 21:40
-
-
Save basvandijk/1343550 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{-# 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