packages feed

json (empty) → 0.3.3

raw patch · 51 files changed

+2038/−0 lines, 51 filesdep +arraydep +basedep +bytestringsetup-changed

Dependencies added: array, base, bytestring, containers, parsec, pretty

Files

+ LICENSE view
@@ -0,0 +1,27 @@+Copyright (c) Galois, Inc. 2007++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions+are met:+1. Redistributions of source code must retain the above copyright+   notice, this list of conditions and the following disclaimer.+2. Redistributions in binary form must reproduce the above copyright+   notice, this list of conditions and the following disclaimer in the+   documentation and/or other materials provided with the distribution.+3. Neither the name of the author nor the names of his contributors+   may be used to endorse or promote products derived from this software+   without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE+ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS+OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY+OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF+SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,4 @@+module Main where++import Distribution.Simple+main = defaultMain
+ Text/JSON.hs view
@@ -0,0 +1,371 @@+--------------------------------------------------------------------+-- |+-- Module    : Text.JSON+-- Copyright : (c) Galois, Inc. 2007+-- License   : BSD3+--+-- Maintainer:  Don Stewart <dons@galois.com>+-- Stability :  provisional+-- Portability: portable+--+--------------------------------------------------------------------+--+-- Serialising Haskell values to and from JSON values.+--++module Text.JSON (+    -- * JSON Types+    JSValue(..)++    -- * Serialization to and from JSValues+  , JSON(..)++    -- * Encoding and Decoding+  , Result(..)+  , encode -- :: JSON a => a -> String+  , decode -- :: JSON a => String -> Either String a+  , encodeStrict -- :: JSON a => a -> String+  , decodeStrict -- :: JSON a => String -> Either String a++    -- * Wrapper Types+  , JSString+  , toJSString+  , fromJSString++  , JSObject+  , toJSObject+  , fromJSObject+  , resultToEither++    -- * Serialization to and from Strings.+    -- ** Reading JSON+  , readJSNull, readJSBool, readJSString, readJSRational+  , readJSArray, readJSObject, readJSValue++    -- ** Writing JSON+  , showJSNull, showJSBool, showJSRational, showJSArray+  , showJSObject, showJSValue+++  ) where++import Text.JSON.Types+import Text.JSON.String++import Data.Char+import Data.List+import Data.Int+import Data.Word+import Data.Either+import Control.Monad(liftM,ap)+import Control.Applicative++import qualified Data.ByteString.Char8 as S+import qualified Data.ByteString.Lazy.Char8 as L+import qualified Data.IntSet as I+import qualified Data.Map as M++------------------------------------------------------------------------++-- | Decode a String representing a JSON value +-- (either an object, array, bool, number, null)+--+-- This is a superset of JSON, as types other than+-- Array and Object are allowed at the top level.+--+decode :: (JSON a) => String -> Result a+decode s = case runGetJSON readJSValue s of+             Right a  -> readJSON a+             Left err -> Error err++-- | Encode a Haskell value into a string, in JSON format.+--+-- This is a superset of JSON, as types other than+-- Array and Object are allowed at the top level.+--+encode :: (JSON a) => a -> String+encode = (flip showJSValue [] . showJSON)++------------------------------------------------------------------------++-- | Decode a String representing a strict JSON value.+-- This follows the spec, and requires top level+-- JSON types to be an Array or Object.+decodeStrict :: (JSON a) => String -> Result a+decodeStrict s = case runGetJSON readJSTopType s of+     Right a  -> readJSON a+     Left err -> Error err++-- | Encode a value as a String in strict JSON format.+-- This follows the spec, and requires all values+-- at the top level to be wrapped in either an Array or Object.+-- JSON types to be an Array or Object.+encodeStrict :: (JSON a) => a -> String+encodeStrict = (flip showJSTopType [] . showJSON)++------------------------------------------------------------------------++-- | The class of types serialisable to and from JSON+class JSON a where+  readJSON  :: JSValue -> Result a+  showJSON  :: a -> JSValue++  readJSONs :: JSValue -> Result [a]+  readJSONs (JSArray as) = mapM readJSON as+  readJSONs _            = mkError "Unable to read list"++  showJSONs :: [a] -> JSValue+  showJSONs = JSArray . map showJSON++-- | A type for parser results+data Result a = Ok a | Error String+  deriving (Eq,Show)++-- | Map Results to Eithers+resultToEither :: Result a -> Either String a+resultToEither (Ok a)    = Right a+resultToEither (Error s) = Left  s++instance Functor Result where fmap = liftM++instance Applicative Result where+  (<*>) = ap+  pure  = return++instance Alternative Result where+  Ok a <|> _    = Ok a+  _    <|> Ok b = Ok b+  err  <|> _    = err+  empty         = Error "empty"++instance Monad Result where+  return x      = Ok x+  fail x        = Error x+  Ok a >>= f    = f a+  Error x >>= _ = Error x++-- | Convenient error generation+mkError :: (JSON a) => String -> Result a+mkError s = Error s++--------------------------------------------------------------------+--+-- | To ensure we generate valid JSON, we map Haskell types to JSValue+-- internally, then pretty print that.+--+instance JSON JSValue where+    showJSON = id+    readJSON = return++second :: (a -> b) -> (x,a) -> (x,b)+second f (a,b) = (a, f b)++--------------------------------------------------------------------+-- Some simple JSON wrapper types, to avoid overlapping instances++instance JSON JSString where+  readJSON (JSString s) = return s+  readJSON _            = mkError "Unable to read JSString"+  showJSON = JSString++instance (JSON a) => JSON (JSObject a) where+  readJSON (JSObject o) =+      let f (x,y) = do y' <- readJSON y; return (x,y')+      in toJSObject `fmap` mapM f (fromJSObject o)+  readJSON _ = mkError "Unable to read JSObject"+  showJSON = JSObject . toJSObject . map (second showJSON) . fromJSObject+++-- -----------------------------------------------------------------+-- Instances+--++instance JSON Bool where+  showJSON = JSBool+  readJSON (JSBool b) = return b+  readJSON _          = mkError "Unable to read Bool"++instance JSON Char where+  showJSON  = JSString . toJSString . (:[])+  showJSONs = JSString . toJSString++  readJSON (JSString s) = case fromJSString s of+                            [c] -> return c+                            _ -> mkError "Unable to read Char"+  readJSON _            = mkError "Unable to read Char"++  readJSONs (JSString s)  = return (fromJSString s)+  readJSONs (JSArray a)   = mapM readJSON a+  readJSONs _             = mkError "Unable to read String"++instance JSON Ordering where+  showJSON LT = JSRational (-1)+  showJSON EQ = JSRational 0+  showJSON GT = JSRational 1+  readJSON (JSRational (-1)) = return LT+  readJSON (JSRational 0) = return EQ+  readJSON (JSRational 1) = return GT+  readJSON _ = mkError "Unable to read Ordering"++-- -----------------------------------------------------------------+-- Integral types++instance JSON Integer where+  showJSON = JSRational . fromIntegral+  readJSON (JSRational i) = return $ round i+  readJSON _             = mkError "Unable to read Integer"++-- constrained:+instance JSON Int where+  showJSON = JSRational . fromIntegral+  readJSON (JSRational i) = return $ round i+  readJSON _              = mkError "Unable to read Int"++-- constrained:+instance JSON Word where+  showJSON = JSRational . toRational+  readJSON (JSRational i) = return $ truncate i+  readJSON _             = mkError "Unable to read Word"++-- -----------------------------------------------------------------++instance JSON Word8 where+  showJSON = JSRational . fromIntegral+  readJSON (JSRational i) = return $ truncate i+  readJSON _             = mkError "Unable to read Word8"++instance JSON Word16 where+  showJSON = JSRational . fromIntegral+  readJSON (JSRational i) = return $ truncate i+  readJSON _             = mkError "Unable to read Word16"++instance JSON Word32 where+  showJSON = JSRational . fromIntegral+  readJSON (JSRational i) = return $ truncate i+  readJSON _             = mkError "Unable to read Word32"++instance JSON Word64 where+  showJSON = JSRational . fromIntegral+  readJSON (JSRational i) = return $ truncate i+  readJSON _             = mkError "Unable to read Word64"++instance JSON Int8 where+  showJSON = JSRational . fromIntegral+  readJSON (JSRational i) = return $ truncate i+  readJSON _             = mkError "Unable to read Int8"++instance JSON Int16 where+  showJSON = JSRational . fromIntegral+  readJSON (JSRational i) = return $ truncate i+  readJSON _             = mkError "Unable to read Int16"++instance JSON Int32 where+  showJSON = JSRational . fromIntegral+  readJSON (JSRational i) = return $ truncate i+  readJSON _             = mkError "Unable to read Int32"++instance JSON Int64 where+  showJSON = JSRational . fromIntegral+  readJSON (JSRational i) = return $ truncate i+  readJSON _             = mkError "Unable to read Int64"++-- -----------------------------------------------------------------++instance JSON Double where+  showJSON = JSRational . toRational+  readJSON (JSRational r) = return $ fromRational r+  readJSON _              = mkError "Unable to read Double"+    -- can't use JSRational here, due to ambiguous '0' parse+    -- it will parse as Integer.++instance JSON Float where+  showJSON = JSRational . toRational+  readJSON (JSRational r) = return $ fromRational r+  readJSON _              = mkError "Unable to read Float"++-- -----------------------------------------------------------------+-- Sums++instance (JSON a) => JSON (Maybe a) where+  readJSON (JSObject o) = case "just" `lookup` as of+      Just x -> Just <$> readJSON x+      _      -> case "nothing" `lookup` as of+          Just JSNull -> return Nothing+          _           -> mkError "Unable to read Maybe"+    where as = fromJSObject o+  readJSON _ = mkError "Unable to read Maybe"+  showJSON (Just x) = JSObject $ toJSObject [("just", showJSON x)]+  showJSON Nothing  = JSObject $ toJSObject [("nothing", JSNull)]++instance (JSON a, JSON b) => JSON (Either a b) where+  readJSON (JSObject o) = case "left" `lookup` as of+      Just a  -> Left <$> readJSON a+      Nothing -> case "right" `lookup` as of+          Just b  -> Right <$> readJSON b+          Nothing -> mkError "Unable to read Either"+    where as = fromJSObject o+  readJSON _ = mkError "Unable to read Either"+  showJSON (Left a)  = JSObject $ toJSObject [("left",  showJSON a)]+  showJSON (Right b) = JSObject $ toJSObject [("right", showJSON b)]++-- -----------------------------------------------------------------+-- Products++instance JSON () where+  showJSON _ = JSArray []+  readJSON (JSArray []) = return ()+  readJSON _      = mkError "Unable to read ()"++instance (JSON a, JSON b) => JSON (a,b) where+  showJSON (a,b) = JSArray [ showJSON a, showJSON b ]+  readJSON (JSArray [a,b]) = (,) `fmap` readJSON a `ap` readJSON b+  readJSON _ = mkError "Unable to read Pair"++instance (JSON a, JSON b, JSON c) => JSON (a,b,c) where+  showJSON (a,b,c) = JSArray [ showJSON a, showJSON b, showJSON c ]+  readJSON (JSArray [a,b,c]) = (,,) `fmap`+                                  readJSON a `ap`+                                  readJSON b `ap`+                                  readJSON c+  readJSON _ = mkError "Unable to read Triple"++instance (JSON a, JSON b, JSON c, JSON d) => JSON (a,b,c,d) where+  showJSON (a,b,c,d) = JSArray [showJSON a, showJSON b, showJSON c, showJSON d]+  readJSON (JSArray [a,b,c,d]) = (,,,) `fmap`+                                  readJSON a `ap`+                                  readJSON b `ap`+                                  readJSON c `ap`+                                  readJSON d++  readJSON _ = mkError "Unable to read 4 tuple"++-- -----------------------------------------------------------------+-- List-like types+++instance JSON a => JSON [a] where+  showJSON = showJSONs+  readJSON = readJSONs++instance (Ord a, JSON a, JSON b) => JSON (M.Map a b) where+  showJSON = showJSON . M.toList+  readJSON a@(JSArray _) = M.fromList <$> readJSON a+  readJSON _ = mkError "Unable to read Map"++instance JSON I.IntSet where+  showJSON = showJSON . I.toList+  readJSON a@(JSArray _) = I.fromList <$> readJSON a+  readJSON _ = mkError "Unable to read IntSet"++-- -----------------------------------------------------------------+-- ByteStrings++instance JSON S.ByteString where+  showJSON = JSString . toJSString . S.unpack+  readJSON (JSString s) = return $ S.pack $ fromJSString s+  readJSON _ = mkError "Unable to read ByteString"++instance JSON L.ByteString where+  showJSON = JSString . toJSString . L.unpack+  readJSON (JSString s) = return $ L.pack $ fromJSString s+  readJSON _ = mkError "Unable to read ByteString"
+ Text/JSON/Parsec.hs view
@@ -0,0 +1,118 @@+--------------------------------------------------------------------+-- |+-- Module    : Text.JSON.Parsec+-- Copyright : (c) Galois, Inc. 2007+--+-- Maintainer:  Don Stewart <dons@galois.com>+-- Stability :  provisional+-- Portability: portable+--+-- Parse JSON values using the Parsec combinators.++module Text.JSON.Parsec+  ( p_value+  , p_null+  , p_boolean+  , p_array+  , p_string+  , p_object+  , p_number+  , p_js_string+  , p_js_object+  , module Text.ParserCombinators.Parsec+  ) where++import Text.JSON.Types+import Text.ParserCombinators.Parsec+import Control.Monad+import Data.Char+import Numeric++tok              :: CharParser () a -> CharParser () a+tok p             = spaces *> p++p_value          :: CharParser () JSValue+p_value           =  (JSNull      <$  p_null)+                 <|> (JSBool      <$> p_boolean)+                 <|> (JSArray     <$> p_array)+                 <|> (JSString    <$> p_js_string)+                 <|> (JSObject    <$> p_js_object)+                 <|> (JSRational  <$> p_number)+                 <?> "JSON value"++p_null           :: CharParser () ()+p_null            = tok (string "null") >> return ()++p_boolean        :: CharParser () Bool+p_boolean         = tok+                      (  (True  <$ string "true")+                     <|> (False <$ string "false")+                      )++p_array          :: CharParser () [JSValue]+p_array           = between (tok (char '[')) (tok (char ']'))+                  $ p_value `sepBy` tok (char ',')++p_string         :: CharParser () String+p_string          = between (tok (char '"')) (char '"') (many p_char)+  where p_char    =  (char '\\' >> p_esc)+                 <|> (satisfy (\x -> x /= '"' && x /= '\\'))++        p_esc     =  ('"'   <$ char '"')+                 <|> ('\\'  <$ char '\\')+                 <|> ('/'   <$ char '/')+                 <|> ('\b'  <$ char 'b')+                 <|> ('\f'  <$ char 'f')+                 <|> ('\n'  <$ char 'n')+                 <|> ('\r'  <$ char 'r')+                 <|> ('\t'  <$ char 't')+                 <|> (char 'u' *> p_uni)+                 <?> "escape character"++        p_uni     = check =<< count 4 (satisfy isHexDigit)+          where check x | code <= max_char  = pure (toEnum code)+                        | otherwise         = empty+                  where code      = fst $ head $ readHex x+                        max_char  = fromEnum (maxBound :: Char)++p_object         :: CharParser () [(String,JSValue)]+p_object          = between (tok (char '{')) (tok (char '}'))+                  $ p_field `sepBy` tok (char ',')+  where p_field   = (,) <$> (p_string <* tok (char ':')) <*> p_value++p_number         :: CharParser () Rational+p_number          = do s <- getInput+                       case readSigned readFloat s of+                         [(n,s1)] -> n <$ setInput s1+                         _        -> empty++p_js_string      :: CharParser () JSString+p_js_string       = toJSString <$> p_string++p_js_object      :: CharParser () (JSObject JSValue)+p_js_object       = toJSObject <$> p_object++--------------------------------------------------------------------------------+-- XXX: Because Parsec is not Applicative yet...++pure   :: a -> CharParser () a+pure    = return++(<*>)  :: CharParser () (a -> b) -> CharParser () a -> CharParser () b+(<*>)   = ap++(*>)   :: CharParser () a -> CharParser () b -> CharParser () b+(*>)    = (>>)++(<*)   :: CharParser () a -> CharParser () b -> CharParser () a+m <* n  = do x <- m; n; return x++empty  :: CharParser () a+empty   = mzero++(<$>)  :: (a -> b) -> CharParser () a -> CharParser () b+(<$>)   = fmap++(<$)   :: a -> CharParser () b -> CharParser () a+x <$ m  = m >> return x+
+ Text/JSON/Pretty.hs view
@@ -0,0 +1,69 @@+--------------------------------------------------------------------+-- |+-- Module    : Text.JSON.Pretty+-- Copyright : (c) Galois, Inc. 2007+-- License   : BSD3+--+-- Maintainer:  Don Stewart <dons@galois.com>+-- Stability :  provisional+-- Portability: portable+--+-- Display JSON values using pretty printing combinators.++module Text.JSON.Pretty+  ( module Text.JSON.Pretty+  , module Text.PrettyPrint.HughesPJ+  ) where++import Text.JSON.Types+import Text.PrettyPrint.HughesPJ+import Data.Ratio+import Data.Char+import Numeric++pp_value         :: JSValue -> Doc+pp_value v        = case v of+    JSNull       -> pp_null+    JSBool x     -> pp_boolean x+    JSRational x -> pp_number x+    JSString x   -> pp_js_string x+    JSArray vs   -> pp_array vs+    JSObject xs  -> pp_js_object xs++pp_null          :: Doc+pp_null           = text "null"++pp_boolean       :: Bool -> Doc+pp_boolean True   = text "true"+pp_boolean False  = text "false"++pp_number        :: Rational -> Doc+pp_number x | denominator x == 1  = integer (numerator x)+pp_number x                       = double (fromRational x)++pp_array         :: [JSValue] -> Doc+pp_array xs       = brackets $ fsep $ punctuate comma $ map pp_value xs++pp_string        :: String -> Doc+pp_string x       = doubleQuotes $ hcat $ map pp_char x+  where pp_char '\\'            = text "\\\\"+        pp_char '"'             = text "\\\""+        pp_char c | isControl c = uni_esc c+        pp_char c               = char c++        uni_esc c = text "\\u" <> text (pad 4 (showHex (fromEnum c) ""))++        pad n cs  | len < n   = replicate (n-len) '0' ++ cs+                  | otherwise = cs+          where len = length cs++pp_object        :: [(String,JSValue)] -> Doc+pp_object xs      = braces $ fsep $ punctuate comma $ map pp_field xs+  where pp_field (k,v) = pp_string k <> colon <+> pp_value v++pp_js_string     :: JSString -> Doc+pp_js_string x    = pp_string (fromJSString x)++pp_js_object     :: JSObject JSValue -> Doc+pp_js_object x    = pp_object (fromJSObject x)+
+ Text/JSON/ReadP.hs view
@@ -0,0 +1,117 @@+--------------------------------------------------------------------+-- |+-- Module    : Text.JSON.ReadP+-- Copyright : (c) Galois, Inc. 2007+-- License   : BSD3+--+-- Maintainer:  Don Stewart <dons@galois.com>+-- Stability :  provisional+-- Portability: portable+--+-- Parse JSON values using the ReadP combinators.++module Text.JSON.ReadP+  ( p_value+  , p_null+  , p_boolean+  , p_array+  , p_string+  , p_object+  , p_number+  , p_js_string+  , p_js_object+  , module Text.ParserCombinators.ReadP+  ) where++import Text.JSON.Types+import Text.ParserCombinators.ReadP+import Control.Monad+import Data.Char+import Numeric++token            :: ReadP a -> ReadP a+token p           = skipSpaces *> p++p_value          :: ReadP JSValue+p_value           =  (JSNull      <$  p_null)+                 <|> (JSBool      <$> p_boolean)+                 <|> (JSArray     <$> p_array)+                 <|> (JSString    <$> p_js_string)+                 <|> (JSObject    <$> p_js_object)+                 <|> (JSRational  <$> p_number)++p_null           :: ReadP ()+p_null            = token (string "null") >> return ()++p_boolean        :: ReadP Bool+p_boolean         = token+                      (  (True  <$ string "true")+                     <|> (False <$ string "false")+                      )++p_array          :: ReadP [JSValue]+p_array           = between (token (char '[')) (token (char ']'))+                  $ p_value `sepBy` token (char ',')++p_string         :: ReadP String+p_string          = between (token (char '"')) (char '"') (many p_char)+  where p_char    =  (char '\\' >> p_esc)+                 <|> (satisfy (\x -> x /= '"' && x /= '\\'))++        p_esc     =  ('"'   <$ char '"')+                 <|> ('\\'  <$ char '\\')+                 <|> ('/'   <$ char '/')+                 <|> ('\b'  <$ char 'b')+                 <|> ('\f'  <$ char 'f')+                 <|> ('\n'  <$ char 'n')+                 <|> ('\r'  <$ char 'r')+                 <|> ('\t'  <$ char 't')+                 <|> (char 'u' *> p_uni)++        p_uni     = check =<< count 4 (satisfy isHexDigit)+          where check x | code <= max_char  = pure (toEnum code)+                        | otherwise         = empty+                  where code      = fst $ head $ readHex x+                        max_char  = fromEnum (maxBound :: Char)++p_object         :: ReadP [(String,JSValue)]+p_object          = between (token (char '{')) (token (char '}'))+                  $ p_field `sepBy` token (char ',')+  where p_field   = (,) <$> (p_string <* token (char ':')) <*> p_value++p_number         :: ReadP Rational+p_number          = readS_to_P (readSigned readFloat)++p_js_string      :: ReadP JSString+p_js_string       = toJSString <$> p_string++p_js_object      :: ReadP (JSObject JSValue)+p_js_object       = toJSObject <$> p_object++--------------------------------------------------------------------------------+-- XXX: Because ReadP is not Applicative yet...++pure   :: a -> ReadP a+pure    = return++(<*>)  :: ReadP (a -> b) -> ReadP a -> ReadP b+(<*>)   = ap++(*>)   :: ReadP a -> ReadP b -> ReadP b+(*>)    = (>>)++(<*)   :: ReadP a -> ReadP b -> ReadP a+m <* n  = do x <- m; n; return x++empty  :: ReadP a+empty   = pfail++(<|>)  :: ReadP a -> ReadP a -> ReadP a+(<|>)   = (+++)++(<$>)  :: (a -> b) -> ReadP a -> ReadP b+(<$>)   = fmap++(<$)   :: a -> ReadP b -> ReadP a+x <$ m  = m >> return x+
+ Text/JSON/String.hs view
@@ -0,0 +1,338 @@+--------------------------------------------------------------------+-- |+-- Module    : Text.JSON.String+-- Copyright : (c) Galois, Inc. 2007+-- License   : BSD3+--+-- Maintainer:  Don Stewart <dons@galois.com>+-- Stability :  provisional+-- Portability: portable+--+--------------------------------------------------------------------+--+-- Basic support for working with JSON values.+--++module Text.JSON.String (+    -- * Parsing+    --+     GetJSON, runGetJSON++    -- ** Reading JSON+  , readJSNull, readJSBool, readJSString, readJSRational+  , readJSArray, readJSObject++  , readJSValue, readJSTopType++    -- ** Writing JSON+  , showJSNull, showJSBool, showJSRational, showJSArray+  , showJSObject++  , showJSValue, showJSTopType++  ) where++import Text.JSON.Types++import Data.Char+import Data.List+import Data.Ratio+import Data.Either+import Control.Monad(liftM)+import Numeric+++-- -----------------------------------------------------------------+-- | Parsing JSON++-- | The type of JSON parsers for String+newtype GetJSON a = GetJSON { un :: String -> Either String (a,String) }++instance Functor GetJSON where fmap = liftM+instance Monad GetJSON where+  return x        = GetJSON (\s -> Right (x,s))+  fail x          = GetJSON (\_ -> Left x)+  GetJSON m >>= f = GetJSON (\s -> case m s of+                                     Left err -> Left err+                                     Right (a,s1) -> un (f a) s1)++-- | Run a JSON reader on an input String, returning some Haskell value.+-- All input will be consumed.+runGetJSON :: GetJSON a -> String -> Either String a+runGetJSON (GetJSON m) s = case m s of+     Left err    -> Left err+     Right (a,t) -> case t of+                        [] -> Right a+                        _  -> Left $ "Invalid tokens at end of JSON string: "++ show (take 10 t)++getInput   :: GetJSON String+getInput    = GetJSON (\s -> Right (s,s))++setInput   :: String -> GetJSON ()+setInput s  = GetJSON (\_ -> Right ((),s))++(<$>) :: Functor f => (a -> b) -> f a -> f b+x <$> y = fmap x y++-------------------------------------------------------------------------++-- | Find 8 chars context, for error messages+context :: String -> String+context s = take 8 s++-- | Read the JSON null type+readJSNull :: GetJSON JSValue+readJSNull = do+  xs <- getInput+  if "null" `isPrefixOf` xs+        then setInput (drop 4 xs) >> return JSNull+        else fail $ "Unable to parse JSON null: " ++ context xs++-- | Read the JSON Bool type+readJSBool :: GetJSON JSValue+readJSBool = do+  xs <- getInput+  case () of {_+      | "true"  `isPrefixOf` xs -> setInput (drop 4 xs) >> return (JSBool True)+      | "false" `isPrefixOf` xs -> setInput (drop 5 xs) >> return (JSBool False)+      | otherwise               -> fail $ "Unable to parse JSON Bool: " ++ context xs+  }++-- | Read the JSON String type+readJSString :: GetJSON JSValue+readJSString = do+  x <- getInput+  case x of+       '"' : cs -> parse [] cs+       _        -> fail $ "Malformed JSON: expecting string: " ++ context x++ where parse rs cs = rs `seq` case cs of+            '\\' : c : ds -> esc rs c ds+            '"'  : ds     -> do setInput ds+                                return . JSString . toJSString . reverse $ rs+            c    : ds+                |  (c `elem` (['\x20'..'\x21'] ++ ['\x23'..'\x5B'])) || (c >='\x5D' && c <= '\x10FFFF')+                          -> parse (c:rs) ds+                | otherwise+                          -> fail $ "Illegal unescaped character in string: " ++ context cs+            _             -> fail $ "Unable to parse JSON String: unterminated String: " ++ context cs++       esc rs c cs = case c of+          '\\' -> parse ('\\' : rs) cs+          '"'  -> parse ('"'  : rs) cs+          'n'  -> parse ('\n' : rs) cs+          'r'  -> parse ('\r' : rs) cs+          't'  -> parse ('\t' : rs) cs+          'f'  -> parse ('\f' : rs) cs+          'b'  -> parse ('\b' : rs) cs+          '/'  -> parse ('/'  : rs) cs+          'u'  -> case cs of+                    d1 : d2 : d3 : d4 : cs' ->+                      case readHex [d1,d2,d3,d4] of+                        [(n,"")] -> parse (toEnum n : rs) cs'++                        x -> fail $ "Unable to parse JSON String: invalid hex: " ++ context (show x)+                    _ -> fail $ "Unable to parse JSON String: invalid hex: " ++ context cs+          _ ->  fail $ "Unable to parse JSON String: invalid escape char: " ++ show c+++-- | Read an Integer or Double in JSON format, returning a Rational+readJSRational :: GetJSON Rational+readJSRational = do+  cs <- getInput+  case cs of+    '-' : ds -> negate <$> pos ds+    _        -> pos cs++  where pos ('0':cs)  = frac 0 cs+        pos cs        = case span isDigit cs of+          ([],_)  -> fail $ "Unable to parse JSON Rational: " ++ context cs+          (xs,ys) -> frac (fromInteger (read xs)) ys++        frac n cs = case cs of+            '.' : ds ->+              case span isDigit ds of+                ([],_) -> setInput cs >> return n+                (as,bs) -> let x = read as :: Integer+                               y = 10 ^ (fromIntegral (length as) :: Integer)+                           in exponent' (n + (x % y)) bs+            _ -> exponent' n cs++        exponent' n (c:cs)+          | c == 'e' || c == 'E' = (n*) <$> exp_num cs+        exponent' n cs = setInput cs >> return n++        exp_num          :: String -> GetJSON Rational+        exp_num ('+':cs)  = exp_digs cs+        exp_num ('-':cs)  = recip <$> exp_digs cs+        exp_num cs        = exp_digs cs++        exp_digs :: String -> GetJSON Rational+        exp_digs cs = case readDec cs of+            [(a,ds)] -> do setInput ds+                           return (fromIntegral ((10::Integer) ^ (a::Integer)))+            _        -> fail $ "Unable to parse JSON exponential: " ++ context cs++-- | Read a list in JSON format+readJSArray  :: GetJSON JSValue+readJSArray  = readSequence '[' ']' ',' >>= return . JSArray++-- | Read an object in JSON format+readJSObject :: GetJSON JSValue+readJSObject = readAssocs '{' '}' ',' >>= return . JSObject . toJSObject+++-- | Read a sequence of items+readSequence :: Char -> Char -> Char -> GetJSON [JSValue]+readSequence start end sep = do+  zs <- getInput+  case dropWhile isSpace zs of+    c : cs | c == start ->+        case dropWhile isSpace cs of+            d : ds | d == end -> setInput (dropWhile isSpace ds) >> return []+            ds                -> setInput ds >> parse []+    _ -> fail $ "Unable to parse JSON sequence: sequence stars with invalid character: " ++ context zs++  where parse rs = rs `seq` do+          a  <- readJSValue+          ds <- getInput+          case dropWhile isSpace ds of+            e : es | e == sep -> do setInput (dropWhile isSpace es)+                                    parse (a:rs)+                   | e == end -> do setInput (dropWhile isSpace es)+                                    return (reverse (a:rs))+            _ -> fail $ "Unable to parse JSON array: unterminated array: " ++ context ds+++-- | Read a sequence of JSON labelled fields+readAssocs :: Char -> Char -> Char -> GetJSON [(String,JSValue)]+readAssocs start end sep = do+  zs <- getInput+  case dropWhile isSpace zs of+    c:cs | c == start -> case dropWhile isSpace cs of+            d:ds | d == end -> setInput (dropWhile isSpace ds) >> return []+            ds              -> setInput ds >> parsePairs []+    _ -> fail "Unable to parse JSON object: unterminated object"++  where parsePairs rs = rs `seq` do+          a  <- do k  <- do x <- readJSString ; case x of+                                JSString s -> return s+                                _          -> fail $ "Malformed JSON field labels: object keys must be quoted strings."+                   ds <- getInput+                   case dropWhile isSpace ds of+                       ':':es -> do setInput (dropWhile isSpace es)+                                    v <- readJSValue+                                    return (fromJSString k,v)+                       _      -> fail $ "Malformed JSON labelled field: " ++ context ds++          ds <- getInput+          case dropWhile isSpace ds of+            e : es | e == sep -> do setInput (dropWhile isSpace es)+                                    parsePairs (a:rs)+                   | e == end -> do setInput (dropWhile isSpace es)+                                    return (reverse (a:rs))+            _ -> fail $ "Unable to parse JSON object: unterminated sequence: "+                            ++ context ds++-- | Read one of several possible JS types+readJSValue :: GetJSON JSValue+readJSValue = do+  cs <- getInput+  case cs of+    '"' : _ -> readJSString+    '[' : _ -> readJSArray+    '{' : _ -> readJSObject+    't' : _ -> readJSBool+    'f' : _ -> readJSBool+    xs    | "null" `isPrefixOf` xs -> readJSNull+    (x:_) | x == '-' || isDigit x  -> JSRational <$> readJSRational++    xs -> fail $ "Malformed JSON: invalid token in this context " ++ context xs++-- | Top level JSON can only be Arrays or Objects+readJSTopType :: GetJSON JSValue+readJSTopType = do+  cs <- getInput+  case cs of+    '[' : _ -> readJSArray+    '{' : _ -> readJSObject+    _       -> fail "Invalid JSON: a JSON text a serialized object or array at the top level."++-- -----------------------------------------------------------------+-- | Writing JSON++-- | Show strict JSON top level types. Values not permitted+-- at the top level are wrapped in a singleton array.+showJSTopType :: JSValue -> ShowS+showJSTopType (JSArray a)    = showJSArray a+showJSTopType (JSObject o)   = showJSObject o+showJSTopType x              = showJSTopType $ JSArray [x]++-- | Show JSON values+showJSValue :: JSValue -> ShowS+showJSValue (JSNull)       = showJSNull+showJSValue (JSBool b)     = showJSBool b+showJSValue (JSRational r) = showJSRational r+showJSValue (JSArray a)    = showJSArray a+showJSValue (JSString s)   = showJSString s+showJSValue (JSObject o)   = showJSObject o++-- | Write the JSON null type+showJSNull :: ShowS+showJSNull = showString "null"++-- | Write the JSON Bool type+showJSBool :: Bool -> ShowS+showJSBool True  = showString "true"+showJSBool False = showString "false"++-- | Write the JSON String type+showJSString :: JSString -> ShowS+showJSString x = quote . foldr (.) quote (map sh (fromJSString x))+  where+        quote = showChar '"'+        sh c  = case c of+                  '"'  -> showString "\\\""+                  '\\' -> showString "\\\\"+                  '\n' -> showString "\\n"+                  '\r' -> showString "\\r"+                  '\t' -> showString "\\t"+                  '\f' -> showString "\\f"+                  '\b' -> showString "\\b"+                  _ | n < 32 -> showString "\\u"+                       . showHex d1 . showHex d2 . showHex d3 . showHex d4+                  _ -> showChar c+          where n = fromEnum c+                (d1,n1) = n  `divMod` 0x1000+                (d2,n2) = n1 `divMod` 0x0100+                (d3,d4) = n2 `divMod` 0x0010++-- | Show a Rational in JSON format+showJSRational :: Rational -> ShowS+showJSRational r | denominator r == 1 = shows $ numerator r+                 | otherwise = if isInfinite x || isNaN x then showJSNull+                                                          else shows x+                     where x :: Double+                           x = realToFrac r++-- | Show a list in JSON format+showJSArray :: [JSValue] -> ShowS+showJSArray = showSequence '[' ']' ','++-- | Show an association list in JSON format+showJSObject :: JSObject JSValue -> ShowS+showJSObject = showAssocs '{' '}' ',' . fromJSObject++-- | Show a generic sequence of pairs in JSON format+showAssocs :: Char -> Char -> Char -> [(String,JSValue)] -> ShowS+showAssocs start end sep xs rest = (start:[])+    ++ concat (intersperse (sep:[]) $ map mkRecord xs)+    ++ (end:[]) ++ rest+  where mkRecord (k,v) = show k ++ ":" ++ showJSValue v []++-- | Show a generic sequence in JSON format+showSequence :: Char -> Char -> Char -> [JSValue] -> ShowS+showSequence start end sep xs rest = (start:[])+  ++ concat (intersperse (sep:[]) $ map (flip showJSValue []) xs)+  ++ (end:[]) ++ rest+
+ Text/JSON/Types.hs view
@@ -0,0 +1,81 @@+--------------------------------------------------------------------+-- |+-- Module    : Text.JSON.Types+-- Copyright : (c) Galois, Inc. 2007+-- License   : BSD3+--+-- Maintainer:  Don Stewart <dons@galois.com>+-- Stability :  provisional+-- Portability: portable+--+--------------------------------------------------------------------+--+-- Basic support for working with JSON values.+--++module Text.JSON.Types (++    -- * JSON Types+    JSValue(..)++    -- * Wrapper Types+  , JSString(..)+  , toJSString+  , fromJSString++  , JSObject(..)+  , toJSObject+  , fromJSObject++  ) where++import Data.Char+import Data.Ratio++--+-- | JSON values+--+-- The type to which we encode Haskell values. There's a set+-- of primitives, and a couple of heterogenous collection types.+--+-- Objects:+--+-- An object structure is represented as a pair of curly brackets+-- surrounding zero or more name\/value pairs (or members).  A name is a+-- string.  A single colon comes after each name, separating the name+-- from the value.  A single comma separates a value from a+-- following name.+--+-- Arrays:+--+-- An array structure is represented as square brackets surrounding+-- zero or more values (or elements).  Elements are separated by commas.+--+-- Only valid JSON can be constructed this way+--+data JSValue+    = JSNull+    | JSBool     !Bool+    | JSRational !Rational+    | JSString   JSString+    | JSArray    [JSValue]+    | JSObject   (JSObject JSValue)+    deriving (Show, Read, Eq, Ord)++-- | Strings can be represented a little more efficiently in JSON+newtype JSString   = JSONString { fromJSString :: String }+    deriving (Eq, Ord, Show, Read)++-- | Turn a Haskell string into a JSON string.+toJSString :: String -> JSString+toJSString = JSONString++-- | As can association lists+newtype JSObject e = JSONObject { fromJSObject :: [(String, e)] }+    deriving (Eq, Ord, Show, Read)++-- | Make JSON object out of an association list.+toJSObject :: [(String,a)] -> JSObject a+toJSObject = JSONObject++
+ json.cabal view
@@ -0,0 +1,91 @@+name:               json+version:            0.3.3+synopsis:           Support for serialising Haskell to and from JSON+description:+    JSON (JavaScript Object Notation) is a lightweight data-interchange+    format. It is easy for humans to read and write. It is easy for+    machines to parse and generate.  It is based on a subset of the+    JavaScript Programming Language, Standard ECMA-262 3rd Edition -+    December 1999.+    .+    This library provides a parser and pretty printer for converting+    between Haskell values and JSON.+category:           Web+license:            BSD3+license-file:       LICENSE+author:             Galois Inc.+maintainer:         dons@galois.com+Copyright:          (c) 2007-2008 Galois Inc.+cabal-version:      >= 1.2.0+build-type: Simple+extra-source-files:+    tests/HUnit.hs+    tests/Makefile+    tests/Parallel.hs+    tests/QC.hs+    tests/QuickCheckUtils.hs+    tests/Unit.hs+    tests/unit/fail1.json+    tests/unit/fail10.json+    tests/unit/fail11.json+    tests/unit/fail12.json+    tests/unit/fail13.json+    tests/unit/fail14.json+    tests/unit/fail15.json+    tests/unit/fail16.json+    tests/unit/fail17.json+    tests/unit/fail18.json+    tests/unit/fail19.json+    tests/unit/fail2.json+    tests/unit/fail20.json+    tests/unit/fail21.json+    tests/unit/fail22.json+    tests/unit/fail23.json+    tests/unit/fail24.json+    tests/unit/fail25.json+    tests/unit/fail26.json+    tests/unit/fail27.json+    tests/unit/fail28.json+    tests/unit/fail29.json+    tests/unit/fail3.json+    tests/unit/fail30.json+    tests/unit/fail31.json+    tests/unit/fail32.json+    tests/unit/fail33.json+    tests/unit/fail4.json+    tests/unit/fail5.json+    tests/unit/fail6.json+    tests/unit/fail7.json+    tests/unit/fail8.json+    tests/unit/fail9.json+    tests/unit/pass1.json+    tests/unit/pass2.json+    tests/unit/pass3.json++flag split-base+  default: True+  description: Use the new split base package.+flag parsec+  default: True+  description: Add support for parsing with Parsec.+flag pretty+  default: True+  description: Add support for using pretty printing combinators.++library+  exposed-modules: Text.JSON,+                   Text.JSON.Types,+                   Text.JSON.String,+                   Text.JSON.ReadP+  ghc-options:     -Wall -O2++  if flag(split-base)+    build-depends:   base >= 3, array, containers, bytestring+    if flag(parsec)+      build-depends:    parsec+      exposed-modules:  Text.JSON.Parsec+    if flag(pretty)+      build-depends:    pretty+      exposed-modules:  Text.JSON.Pretty+  else+    build-depends:    base < 3
+ tests/HUnit.hs view
@@ -0,0 +1,82 @@+{-# OPTIONS -fglasgow-exts #-}++import Text.JSON+import Test.HUnit+import System.Exit (exitFailure)+import Control.Monad (when)+import System.IO+import Data.Either+import qualified Data.Map as M++isError (Error _) = True+isError _        = False+++main = do counts <- runTestTT tests+          when (errors counts > 0 || failures counts > 0) exitFailure++tests = TestList+    [shouldFail "non-array top level"         "fail1" (undefined :: String)+    ,shouldFail "unclosed array"              "fail2" (undefined :: JSValue)+    ,shouldFail "object keys must be quoted"  "fail3" (undefined :: JSValue)+    ,shouldFail "extra comma"                 "fail4" (undefined :: JSValue)+    ,shouldFail "double extra comma"          "fail5" (undefined :: JSValue)+    ,shouldFail "missing value"               "fail6" (undefined :: JSValue)+    ,shouldFail "comma after close"           "fail7" (undefined :: JSValue)+    ,shouldFail "extra close"                 "fail8" (undefined :: JSValue)+    ,shouldFail "extra comma"                 "fail9" (undefined :: JSValue)+    ,shouldFail "extra value"                 "fail10" (undefined :: JSValue)+    ,shouldFail "illegal expression"          "fail11" (undefined :: JSValue)+    ,shouldFail "illegal expression"          "fail12" (undefined :: JSValue)+    ,shouldFail "numbers with leading zeroes" "fail13" (undefined :: JSValue)+    ,shouldFail "numbers in hex"              "fail14" (undefined :: JSValue)+    ,shouldFail "illegal backslash"           "fail15" (undefined :: JSValue)+    ,shouldFail "unquoted char"               "fail16" (undefined :: JSValue)+    ,shouldFail "illegal escape"              "fail17" (undefined :: JSValue)+    ,shouldPass "deep objects"                "fail18" (undefined :: JSValue)  -- depth is allowed to be limited, but why bother?+    ,shouldFail "missing colon"               "fail19" (undefined :: JSValue)+    ,shouldFail "double colon"                "fail20" (undefined :: JSValue)+    ,shouldFail "comma instead of colon"      "fail21" (undefined :: JSValue)+    ,shouldFail "colon intead of comma"       "fail22" (undefined :: JSValue)+    ,shouldFail "invalid token"               "fail23" (undefined :: JSValue)+    ,shouldFail "single quotes"               "fail24" (undefined :: JSValue)+    ,shouldFail "literal tabs"                "fail25" (undefined :: JSValue)+    ,shouldFail "tabs in strings"             "fail26" (undefined :: JSValue)+    ,shouldFail "newline in strings"          "fail27" (undefined :: JSValue)+    ,shouldFail "escaped newline in strings"  "fail28" (undefined :: JSValue)+    ,shouldFail "funny number"                "fail29" (undefined :: JSValue)+    ,shouldFail "funny number 2"              "fail30" (undefined :: JSValue)+    ,shouldFail "funny number 3"              "fail31" (undefined :: JSValue)+    ,shouldFail "unterminated array"          "fail32" (undefined :: JSValue)+    ,shouldFail "unterminated array"          "fail33" (undefined :: JSValue)++    , shouldPass "complex valid input 1"        "pass1"  (undefined :: JSValue)+    , shouldPass "complex valid input 2"        "pass2"  (undefined :: JSValue)+    , shouldPass "complex valid input 3"        "pass3"  (undefined :: JSValue)+    ]++------------------------------------------------------------------------++load n = readFile ("unit/" ++ n ++ ".json")++shouldFail :: JSON a => String -> String -> a -> Test+shouldFail  s n (x :: a) = TestLabel ("Should fail: " ++ s) $+  TestCase $ do+--  hPutStrLn stderr $ ("\t\tShould fail: " ++ s)+    s <- load n+    assert =<< case decodeStrict s :: Result a of+                    Ok _     -> return False+                    Error  s -> -- do hPrint stderr s+                                   return True+++shouldPass :: JSON a => String -> String -> a -> Test+shouldPass  s n (x :: a) = TestLabel ("Should pass: " ++ s) $+  TestCase $ do+--  hPutStrLn stderr $ ("\t\tShould pass: " ++ s)+    s <- load n+    assert =<< case decodeStrict s :: Result a of+                    Ok _     -> return True+                    Error  s -> do hPrint stderr s+                                   return False+
+ tests/Makefile view
@@ -0,0 +1,13 @@+ODIR=.ghc++$(ODIR):+	mkdir $(ODIR)++all: $(ODIR)+	ghc -cpp -O QC.hs --make -o QC -no-recomp -odir=$(ODIR) -hidir=$(ODIR)+	time ./QC+	runhaskell HUnit.hs++clean:+	$(RM) -r $(ODIR)+	$(RM) *.html *.tix QC
+ tests/Parallel.hs view
@@ -0,0 +1,151 @@+-----------------------------------------------------------------------------+-- |+-- Module      :  Test.QuickCheck.Parallel+-- Copyright   :  (c) Don Stewart 2006+-- License     :  BSD-style (see the file LICENSE)+-- +-- Maintainer  :  dons@cse.unsw.edu.au+-- Stability   :  experimental+-- Portability :  non-portable (uses Control.Exception, Control.Concurrent)+--+-- A parallel batch driver for running QuickCheck on threaded or SMP systems.+-- See the /Example.hs/ file for a complete overview.+--++module Parallel (+    pRun,+    pDet,+    pNon+  ) where++import Test.QuickCheck+import Data.List+import Control.Concurrent (forkIO)++import Control.Concurrent.Chan+import Control.Concurrent.MVar++import Control.Exception  hiding (evaluate)+import System.Random+import System.IO          (hFlush,stdout)+import Text.Printf++type Name   = String+type Depth  = Int+type Test   = (Name, Depth -> IO String)++-- | Run a list of QuickCheck properties in parallel chunks, using+-- 'n' Haskell threads (first argument), and test to a depth of 'd'+-- (second argument). Compile your application with '-threaded' and run+-- with the SMP runtime's '-N4' (or however many OS threads you want to+-- donate), for best results.+--+-- > import Test.QuickCheck.Parallel+-- >+-- > do n <- getArgs >>= readIO . head+-- >    pRun n 1000 [ ("sort1", pDet prop_sort1) ]+--+-- Will run 'n' threads over the property list, to depth 1000.+--+pRun :: Int -> Int -> [Test] -> IO ()+pRun n depth tests = do+    chan <- newChan+    ps   <- getChanContents chan+    work <- newMVar tests++    forM_ [1..n] $ forkIO . thread work chan++    let wait xs i+            | i >= n     = return () -- done+            | otherwise = case xs of+                    Nothing : xs -> wait xs $! i+1+                    Just s  : xs -> putStr s >> hFlush stdout >> wait xs i+    wait ps 0++  where+    thread :: MVar [Test] -> Chan (Maybe String) -> Int -> IO ()+    thread work chan me = loop+      where+        loop = do+            job <- modifyMVar work $ \jobs -> return $ case jobs of+                        []     -> ([], Nothing)+                        (j:js) -> (js, Just j)+            case job of+                Nothing          -> writeChan chan Nothing -- done+                Just (name,prop) -> do+                    v <- prop depth+                    writeChan chan . Just $ printf "%d: %-25s: %s" me name v+                    loop+++-- | Wrap a property, and run it on a deterministic set of data+pDet :: Testable a => a -> Int -> IO String+pDet a n = mycheck Det defaultConfig+    { configMaxTest = n+    , configEvery   = \n args -> unlines args } a++-- | Wrap a property, and run it on a non-deterministic set of data+pNon :: Testable a => a -> Int -> IO String+pNon a n = mycheck NonDet defaultConfig+    { configMaxTest = n+    , configEvery   = \n args -> unlines args } a++data Mode = Det | NonDet++------------------------------------------------------------------------++mycheck :: Testable a => Mode -> Config -> a -> IO String+mycheck Det config a = do+     let rnd = mkStdGen 99  -- deterministic+     mytests config (evaluate a) rnd 0 0 []++mycheck NonDet config a = do+    rnd <- newStdGen        -- different each run+    mytests config (evaluate a) rnd 0 0 []++mytests :: Config -> Gen Result -> StdGen -> Int -> Int -> [[String]] -> IO String+mytests config gen rnd0 ntest nfail stamps+  | ntest == configMaxTest config = do done "OK," ntest stamps+  | nfail == configMaxFail config = do done "Arguments exhausted after" ntest stamps+  | otherwise = do+         case ok result of+           Nothing    ->+             mytests config gen rnd1 ntest (nfail+1) stamps+           Just True  ->+             mytests config gen rnd1 (ntest+1) nfail (stamp result:stamps)+           Just False ->+             return ( "Falsifiable after "+                   ++ show ntest+                   ++ " tests:\n"+                   ++ unlines (arguments result)+                    )+     where+      result      = generate (configSize config ntest) rnd2 gen+      (rnd1,rnd2) = split rnd0++done :: String -> Int -> [[String]] -> IO String+done mesg ntest stamps =+    return ( mesg ++ " " ++ show ntest ++ " tests" ++ table )+  where+    table = display+        . map entry+        . reverse+        . sort+        . map pairLength+        . group+        . sort+        . filter (not . null)+        $ stamps++    display []  = ".\n"+    display [x] = " (" ++ x ++ ").\n"+    display xs  = ".\n" ++ unlines (map (++ ".") xs)++    pairLength xss@(xs:_) = (length xss, xs)+    entry (n, xs)         = percentage n ntest+                          ++ " "+                          ++ concat (intersperse ", " xs)++    percentage n m        = show ((100 * n) `div` m) ++ "%"++forM_ = flip mapM_
+ tests/QC.hs view
@@ -0,0 +1,192 @@+{-# OPTIONS_GHC -fglasgow-exts  #-}+module Main where++import Text.JSON++import Parallel++import qualified Data.Map as Map+import qualified Data.Set as Set+import qualified Data.IntMap as IntMap+import qualified Data.IntSet as IntSet++import qualified Control.Exception as C (catch,evaluate)+import Control.Monad+import Foreign+import System.Environment+import System.IO+import System.IO.Unsafe+import Data.Word+import Data.Int++import Test.QuickCheck hiding (test)+import QuickCheckUtils+import Debug.Trace+import Text.Printf++import Data.IntSet          ( IntSet )+import qualified Data.ByteString      as S+import qualified Data.ByteString.Lazy as L+import qualified Data.Sequence as Seq+import qualified Data.Map as M+import qualified Data.IntMap as I++------------------------------------------------------------------------+-- low level ones:++main :: IO ()+main = do+    hSetBuffering stdout NoBuffering+    s <- getArgs+    let n = if null s then 100 else read (head s)+        k = doIt n++    k basics+    k atomicCharacterTypes+    k numbers+    k listlikes+    k containers+    k sumtypes+    k products+++doIt n (s,x) = putStrLn (" *** " ++ s) >> pRun 2 n x+++type T a = a -> Property+type B a = a -> Bool++p :: Testable a => a -> Int -> IO String+p = pNon++test :: forall a. (Show a,Arbitrary a, Eq a, JSON a) => a -> Property+test _ = forAll (arbitrary :: Gen a) $ \a ->+                Ok a == decode (encode a)++instance Arbitrary JSString where+    arbitrary = liftM toJSString arbitrary+    coarbitrary = undefined++instance (Ord e, Arbitrary e) => Arbitrary (JSObject e) where+    arbitrary   = do+            ks <- arbitrary+            vs <- arbitrary+            return . toJSObject . M.toList . M.fromList . zip ks $ vs++    coarbitrary = undefined++------------------------------------------------------------------------++-- tests :: [(String, Int -> IO String)]+basics = ("Basic types",+                [("Bool",        p (test :: T Bool ))+                ,("()",          p (test :: T () ))+                ]+         )++        -- atomic character types++atomicCharacterTypes =+    ("Atomic string types",++    [("String",      p (test :: T JSString  ))+    ,("Strict ByteString",       p (test :: T S.ByteString ))+    ,("Lazy ByteString",         p (test :: T L.ByteString ))+    ,("Char",        p (test :: T Char ))+    ]+    )++        -- basic numeric types+numbers =+    ("Numeric types",+        [("Integer",     p (test :: T Integer ))+        ,("Int",         p (test :: T Int ))+        ,("Word",        p (test :: T Word ))++        -- words++        ,("Word8",        p (test :: T Word8    ))+        ,("Word16",       p (test :: T Word16   ))+        ,("Word32",       p (test :: T Word32   ))+        ,("Word64",       p (test :: T Word64   ))++        -- integers++        ,("Int8",         p (test :: T Int8     ))+        ,("Int16",        p (test :: T Int16    ))+        ,("Int32",        p (test :: T Int32    ))+        ,("Int64",        p (test :: T Int64    ))++        -- rationals++        ,("Double",         p (test :: T Double))+        ,("Float",          p (test :: T Float))+    ])++        -- lists++listlikes =+    ("List like types",+        [("[()]",           p (test :: T [()]))+        ,("[Int]",          p (test :: T [Int]))+        ,("[Bool]",         p (test :: T [Bool]))+        ,("[Integer]",      p (test :: T [Integer]))+        ,("[Int]",          p (test :: T [Int]))+        ,("[Word]",         p (test :: T [Word]))+        ,("[S.ByteString]", p (test :: T [S.ByteString]               ))+        ,("[L.ByteString]", p (test :: T [L.ByteString]               ))++    ])+        -- containers++containers =+    ("Container types",+        [("IntSet",         p (test :: T IntSet ))+        ,("Map String Int", p (test :: T (M.Map String Int)      ))+        ,("Map Int String", p (test :: T (M.Map Int String)      ))++--      ,("Maybe Bool",  p (test :: T (Maybe Bool) ))+--      ,("Rational",   p (test :: T Rational ))++        ]+    )++sumtypes =+    ("Sum types",+        [("Ordering",         p (test :: T Ordering))+        ,("Maybe Int",        p (test :: T (Maybe Int)))+        ,("Maybe String",        p (test :: T (Maybe String)))+        ,("Either Bool String",        p (test :: T (Either Bool String)))+        ,("Either Int (Either Int Word32)",+                    p (test :: T (Either Int (Either Int Word32))))+    ])++products =+    ("Products",+        [("((),())",+            p (test :: T ((),())+         ))++        ,("(Bool,Int)",+            p (test :: T (Bool,Int)+            ))++        ,("(Bool,(Int, String))",+            p (test :: T (Bool,(Int,String))+            ))++        ,("(Maybe String,(Either Int Bool, String))",+            p (test :: T (Bool,(Either Int Bool,String))+            ))++        ,("(Bool,Int,String)",+            p (test :: T (Bool,Int,String)+            ))++        ,("(Bool,Int,String,Char)",+            p (test :: T (Bool,Int,String,Char)+            ))++         ]++    )
+ tests/QuickCheckUtils.hs view
@@ -0,0 +1,272 @@+{-# OPTIONS_GHC -fglasgow-exts #-}+--+-- Uses multi-param type classes+--+module QuickCheckUtils where++import Control.Monad++import Test.QuickCheck.Batch+import Test.QuickCheck+import Text.Show.Functions+import Data.Ratio++import qualified Data.ByteString as B+import qualified Data.ByteString.Lazy as L+import qualified Data.Map as Map+import qualified Data.Set as Set+import qualified Data.IntMap as IntMap+import qualified Data.IntSet as IntSet++import qualified Control.Exception as C (evaluate)++import Control.Monad        ( liftM2 )+import Data.Char+import Data.List+import Data.Word+import Data.Int+import System.Random+import System.IO++-- import Control.Concurrent+import System.Mem+import System.CPUTime+import Text.Printf++import qualified Data.ByteString      as P+import qualified Data.ByteString.Lazy as L++#if __GLASGOW_HASKELL__ >= 608+import qualified Data.ByteString.Lazy.Internal as L+import qualified Data.ByteString.Unsafe as B+import qualified Data.ByteString.Internal as B+#else+import qualified Data.ByteString.Base as B+#endif++-- import qualified Data.Sequence as Seq++-- Enable this to get verbose test output. Including the actual tests.+debug = False++mytest :: Testable a => a -> Int -> IO ()+mytest a n = mycheck defaultConfig+    { configMaxTest=n+    , configEvery= \n args -> if debug then show n ++ ":\n" ++ unlines args else [] } a++mycheck :: Testable a => Config -> a -> IO ()+mycheck config a = do+     rnd <- newStdGen+     performGC -- >> threadDelay 100+     t <- mytests config (evaluate a) rnd 0 0 [] 0 -- 0+     printf " %0.3f seconds\n" (t :: Double)+     hFlush stdout++time :: a -> IO (a , Double)+time a = do+    start <- getCPUTime+    v     <- C.evaluate a+    v `seq` return ()+    end   <- getCPUTime+    return (v,     (      (fromIntegral (end - start)) / (10^12)))++mytests :: Config -> Gen Result -> StdGen -> Int -> Int -> [[String]] -> Double -> IO  Double+mytests config gen rnd0 ntest nfail stamps t0+  | ntest == configMaxTest config = do done "OK," ntest stamps+                                       return t0++  | nfail == configMaxFail config = do done "Arguments exhausted after" ntest stamps+                                       return t0++  | otherwise = do+     (result,t1) <- time (generate (configSize config ntest) rnd2 gen)++     putStr (configEvery config ntest (arguments result)) >> hFlush stdout+     case ok result of+       Nothing    ->+         mytests config gen rnd1 ntest (nfail+1) stamps (t0 + t1)+       Just True  ->+         mytests config gen rnd1 (ntest+1) nfail (stamp result:stamps) (t0 + t1)+       Just False -> do+         putStr ( "Falsifiable after "+               ++ show ntest+               ++ " tests:\n"+               ++ unlines (arguments result)+                ) >> hFlush stdout+         return t0++     where+      (rnd1,rnd2) = split rnd0++done :: String -> Int -> [[String]] -> IO ()+done mesg ntest stamps = putStr ( mesg ++ " " ++ show ntest ++ " tests" ++ table )+ where+  table = display+        . map entry+        . reverse+        . sort+        . map pairLength+        . group+        . sort+        . filter (not . null)+        $ stamps++  display []  = ". "+  display [x] = " (" ++ x ++ "). "+  display xs  = ".\n" ++ unlines (map (++ ".") xs)++  pairLength xss@(xs:_) = (length xss, xs)+  entry (n, xs)         = percentage n ntest+                       ++ " "+                       ++ concat (intersperse ", " xs)++  percentage n m        = show ((100 * n) `div` m) ++ "%"++------------------------------------------------------------------------++instance Random Word8 where+  randomR = integralRandomR+  random = randomR (minBound,maxBound)++instance Random Int8 where+  randomR = integralRandomR+  random = randomR (minBound,maxBound)++instance Random Word16 where+  randomR = integralRandomR+  random = randomR (minBound,maxBound)++instance Random Int16 where+  randomR = integralRandomR+  random = randomR (minBound,maxBound)++instance Random Word where+  randomR = integralRandomR+  random = randomR (minBound,maxBound)++instance Random Word32 where+  randomR = integralRandomR+  random = randomR (minBound,maxBound)++instance Random Int32 where+  randomR = integralRandomR+  random = randomR (minBound,maxBound)++instance Random Word64 where+  randomR = integralRandomR+  random = randomR (minBound,maxBound)++instance Random Int64 where+  randomR = integralRandomR+  random = randomR (minBound,maxBound)++------------------------------------------------------------------------++integralRandomR :: (Integral a, RandomGen g) => (a,a) -> g -> (a,g)+integralRandomR  (a,b) g = case randomR (fromIntegral a :: Integer,+                                         fromIntegral b :: Integer) g of+                            (x,g) -> (fromIntegral x, g)++------------------------------------------------------------------------++instance Arbitrary Word8 where+    arbitrary       = choose (0, 2^8-1)+    coarbitrary w   = variant 0++instance Arbitrary (Ratio Integer) where+    arbitrary       = do n <- arbitrary+                         m <- arbitrary+                         if m /= 0+                                then return (n % m)+                                else arbitrary+    coarbitrary w   = undefined++instance Arbitrary Word16 where+    arbitrary       = choose (0, 2^16-1)+    coarbitrary     = undefined++instance Arbitrary Word32 where+--  arbitrary       = choose (0, 2^32-1)+    arbitrary       = choose (minBound, maxBound)+    coarbitrary     = undefined++instance Arbitrary Word64 where+--  arbitrary       = choose (0, 2^64-1)+    arbitrary       = choose (minBound, maxBound)+    coarbitrary     = undefined++instance Arbitrary Int8 where+--  arbitrary       = choose (0, 2^8-1)+    arbitrary       = choose (minBound, maxBound)+    coarbitrary w   = variant 0++instance Arbitrary Int16 where+--  arbitrary       = choose (0, 2^16-1)+    arbitrary       = choose (minBound, maxBound)+    coarbitrary     = undefined++instance Arbitrary Int32 where+--  arbitrary       = choose (0, 2^32-1)+    arbitrary       = choose (minBound, maxBound)+    coarbitrary     = undefined++instance Arbitrary Int64 where+--  arbitrary       = choose (0, 2^64-1)+    arbitrary       = choose (minBound, maxBound)+    coarbitrary     = undefined++instance Arbitrary Word where+    arbitrary       = choose (minBound, maxBound)+    coarbitrary w   = variant 0++------------------------------------------------------------------------++instance Arbitrary Char where+    arbitrary = choose (maxBound, minBound)+    coarbitrary = undefined++{-+instance Arbitrary a => Arbitrary (Maybe a) where+    arbitrary = oneof [ return Nothing, liftM Just arbitrary]+    coarbitrary = undefined+    -}++instance Arbitrary Ordering where+    arbitrary = oneof [ return LT,return  GT,return  EQ ]+    coarbitrary = undefined++{-+instance (Arbitrary a, Arbitrary b) => Arbitrary (Either a b) where+    arbitrary = oneof [ liftM Left arbitrary, liftM Right arbitrary]+    coarbitrary = undefined+    -}++instance Arbitrary IntSet.IntSet where+    arbitrary = fmap IntSet.fromList arbitrary+    coarbitrary = undefined++instance (Arbitrary e) => Arbitrary (IntMap.IntMap e) where+    arbitrary = fmap IntMap.fromList arbitrary+    coarbitrary = undefined++instance (Arbitrary a, Ord a) => Arbitrary (Set.Set a) where+    arbitrary = fmap Set.fromList arbitrary+    coarbitrary = undefined++instance (Arbitrary a, Ord a, Arbitrary b) => Arbitrary (Map.Map a b) where+    arbitrary = fmap Map.fromList arbitrary+    coarbitrary = undefined++{-+instance (Arbitrary a) => Arbitrary (Seq.Seq a) where+    arbitrary = fmap Seq.fromList arbitrary+    coarbitrary = undefined+-}++instance Arbitrary L.ByteString where+    arbitrary     = arbitrary >>= return . L.fromChunks . filter (not. B.null) -- maintain the invariant.+    coarbitrary s = coarbitrary (L.unpack s)++instance Arbitrary B.ByteString where+  arbitrary = B.pack `fmap` arbitrary+  coarbitrary s = coarbitrary (B.unpack s)
+ tests/Unit.hs view
@@ -0,0 +1,12 @@++import Text.JSON+import Network.RPC.JSON+import System.Exit++main = do+    case (decode test1 :: Result JSRequest) of+         Ok _ -> exitWith ExitSuccess+++test1 = +    "{\"method\":\"feed.add\",\"params\":{\"uri\":\"http://rss.slashdot.org/Slashdot/slashdot\"},\"version\":\"1.1\" }"
+ tests/unit/fail1.json view
@@ -0,0 +1,1 @@+"A JSON payload should be an object or array, not a string."
+ tests/unit/fail10.json view
@@ -0,0 +1,1 @@+{"Extra value after close": true} "misplaced quoted value"
+ tests/unit/fail11.json view
@@ -0,0 +1,1 @@+{"Illegal expression": 1 + 2}
+ tests/unit/fail12.json view
@@ -0,0 +1,1 @@+{"Illegal invocation": alert()}
+ tests/unit/fail13.json view
@@ -0,0 +1,1 @@+{"Numbers cannot have leading zeroes": 013}
+ tests/unit/fail14.json view
@@ -0,0 +1,1 @@+{"Numbers cannot be hex": 0x14}
+ tests/unit/fail15.json view
@@ -0,0 +1,1 @@+["Illegal backslash escape: \x15"]
+ tests/unit/fail16.json view
@@ -0,0 +1,1 @@+[\naked]
+ tests/unit/fail17.json view
@@ -0,0 +1,1 @@+["Illegal backslash escape: \017"]
+ tests/unit/fail18.json view
@@ -0,0 +1,1 @@+[[[[[[[[[[[[[[[[[[[["Too deep"]]]]]]]]]]]]]]]]]]]]
+ tests/unit/fail19.json view
@@ -0,0 +1,1 @@+{"Missing colon" null}
+ tests/unit/fail2.json view
@@ -0,0 +1,1 @@+["Unclosed array"
+ tests/unit/fail20.json view
@@ -0,0 +1,1 @@+{"Double colon":: null}
+ tests/unit/fail21.json view
@@ -0,0 +1,1 @@+{"Comma instead of colon", null}
+ tests/unit/fail22.json view
@@ -0,0 +1,1 @@+["Colon instead of comma": false]
+ tests/unit/fail23.json view
@@ -0,0 +1,1 @@+["Bad value", truth]
+ tests/unit/fail24.json view
@@ -0,0 +1,1 @@+['single quote']
+ tests/unit/fail25.json view
@@ -0,0 +1,1 @@+["	tab	character	in	string	"]
+ tests/unit/fail26.json view
@@ -0,0 +1,1 @@+["tab\   character\   in\  string\  "]
+ tests/unit/fail27.json view
@@ -0,0 +1,2 @@+["line+break"]
+ tests/unit/fail28.json view
@@ -0,0 +1,2 @@+["line\+break"]
+ tests/unit/fail29.json view
@@ -0,0 +1,1 @@+[0e]
+ tests/unit/fail3.json view
@@ -0,0 +1,1 @@+{unquoted_key: "keys must be quoted"}
+ tests/unit/fail30.json view
@@ -0,0 +1,1 @@+[0e+]
+ tests/unit/fail31.json view
@@ -0,0 +1,1 @@+[0e+-1]
+ tests/unit/fail32.json view
@@ -0,0 +1,1 @@+{"Comma instead if closing brace": true,
+ tests/unit/fail33.json view
@@ -0,0 +1,1 @@+["mismatch"}
+ tests/unit/fail4.json view
@@ -0,0 +1,1 @@+["extra comma",]
+ tests/unit/fail5.json view
@@ -0,0 +1,1 @@+["double extra comma",,]
+ tests/unit/fail6.json view
@@ -0,0 +1,1 @@+[   , "<-- missing value"]
+ tests/unit/fail7.json view
@@ -0,0 +1,1 @@+["Comma after the close"],
+ tests/unit/fail8.json view
@@ -0,0 +1,1 @@+["Extra close"]]
+ tests/unit/fail9.json view
@@ -0,0 +1,1 @@+{"Extra comma": true,}
+ tests/unit/pass1.json view
@@ -0,0 +1,58 @@+[+    "JSON Test Pattern pass1",+    {"object with 1 member":["array with 1 element"]},+    {},+    [],+    -42,+    true,+    false,+    null,+    {+        "integer": 1234567890,+        "real": -9876.543210,+        "e": 0.123456789e-12,+        "E": 1.234567890E+34,+        "":  23456789012E66,+        "zero": 0,+        "one": 1,+        "space": " ",+        "quote": "\"",+        "backslash": "\\",+        "controls": "\b\f\n\r\t",+        "slash": "/ & \/",+        "alpha": "abcdefghijklmnopqrstuvwyz",+        "ALPHA": "ABCDEFGHIJKLMNOPQRSTUVWYZ",+        "digit": "0123456789",+        "0123456789": "digit",+        "special": "`1~!@#$%^&*()_+-={':[,]}|;.</>?",+        "hex": "\u0123\u4567\u89AB\uCDEF\uabcd\uef4A",+        "true": true,+        "false": false,+        "null": null,+        "array":[  ],+        "object":{  },+        "address": "50 St. James Street",+        "url": "http://www.JSON.org/",+        "comment": "// /* <!-- --",+        "# -- --> */": " ",+        " s p a c e d " :[1,2 , 3++,++4 , 5        ,          6           ,7        ],"compact":[1,2,3,4,5,6,7],+        "jsontext": "{\"object with 1 member\":[\"array with 1 element\"]}",+        "quotes": "&#34; \u0022 %22 0x22 034 &#x22;",+        "\/\\\"\uCAFE\uBABE\uAB98\uFCDE\ubcda\uef4A\b\f\n\r\t`1~!@#$%^&*()_+-=[]{}|;:',./<>?"+: "A key can be any string"+    },+    0.5 ,98.6+,+99.44+,++1066,+1e1,+0.1e1,+1e-1,+1e00,2e+00,2e-00+,"rosebud"]
+ tests/unit/pass2.json view
@@ -0,0 +1,1 @@+[[[[[[[[[[[[[[[[[[["Not too deep"]]]]]]]]]]]]]]]]]]]
+ tests/unit/pass3.json view
@@ -0,0 +1,6 @@+{+    "JSON Test Pattern pass3": {+        "The outermost value": "must be an object or array.",+        "In this test": "It is an object."+    }+}