module Text.JSON.String
(
GetJSON
, runGetJSON
, readJSNull
, readJSBool
, readJSString
, readJSRational
, readJSArray
, readJSObject
, readJSValue
, readJSTopType
, showJSNull
, showJSBool
, showJSArray
, showJSObject
, showJSRational
, showJSRational'
, showJSValue
, showJSTopType
) where
import Text.JSON.Types (JSValue(..),
JSString, toJSString, fromJSString,
JSObject, toJSObject, fromJSObject)
import Control.Monad (liftM, ap)
import Control.Applicative((<$>))
import qualified Control.Applicative as A
import Data.Char (isSpace, isDigit, digitToInt)
import Data.Ratio (numerator, denominator, (%))
import Numeric (readHex, readDec, showHex)
newtype GetJSON a = GetJSON { un :: String -> Either String (a,String) }
instance Functor GetJSON where fmap = liftM
instance A.Applicative GetJSON where
pure = return
(<*>) = ap
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)
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))
context :: String -> String
context s = take 8 s
readJSNull :: GetJSON JSValue
readJSNull = do
xs <- getInput
case xs of
'n':'u':'l':'l':xs1 -> setInput xs1 >> return JSNull
_ -> fail $ "Unable to parse JSON null: " ++ context xs
tryJSNull :: GetJSON JSValue -> GetJSON JSValue
tryJSNull k = do
xs <- getInput
case xs of
'n':'u':'l':'l':xs1 -> setInput xs1 >> return JSNull
_ -> k
readJSBool :: GetJSON JSValue
readJSBool = do
xs <- getInput
case xs of
't':'r':'u':'e':xs1 -> setInput xs1 >> return (JSBool True)
'f':'a':'l':'s':'e':xs1 -> setInput xs1 >> return (JSBool False)
_ -> fail $ "Unable to parse JSON Bool: " ++ context xs
readJSString :: GetJSON JSValue
readJSString = do
x <- getInput
case x of
'"' : cs -> parse [] cs
_ -> fail $ "Malformed JSON: expecting string: " ++ context x
where
parse rs cs =
case cs of
'\\' : c : ds -> esc rs c ds
'"' : ds -> do setInput ds
return (JSString (toJSString (reverse rs)))
c : ds
| c >= '\x20' && c <= '\xff' -> parse (c:rs) ds
| c < '\x20' -> fail $ "Illegal unescaped character in string: " ++ context cs
| i <= 0x10ffff -> parse (c:rs) ds
| otherwise -> fail $ "Illegal unescaped character in string: " ++ context cs
where
i = (fromIntegral (fromEnum c) :: Integer)
_ -> 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
readJSRational :: GetJSON Rational
readJSRational = do
cs <- getInput
case cs of
'-' : ds -> negate <$> pos ds
_ -> pos cs
where
pos [] = fail $ "Unable to parse JSON Rational: " ++ context []
pos (c:cs) =
case c of
'0' -> frac 0 cs
_
| not (isDigit c) -> fail $ "Unable to parse JSON Rational: " ++ context cs
| otherwise -> readDigits (digitToIntI c) cs
readDigits acc [] = frac (fromInteger acc) []
readDigits acc (x:xs)
| isDigit x = let acc' = 10*acc + digitToIntI x in
acc' `seq` readDigits acc' xs
| otherwise = frac (fromInteger acc) (x:xs)
frac n ('.' : ds) =
case span isDigit ds of
([],_) -> setInput ds >> return n
(as,bs) -> let x = read as :: Integer
y = 10 ^ (fromIntegral (length as) :: Integer)
in exponent' (n + (x % y)) bs
frac n cs = 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
digitToIntI :: Char -> Integer
digitToIntI ch = fromIntegral (digitToInt ch)
readJSArray :: GetJSON JSValue
readJSArray = readSequence '[' ']' ',' >>= return . JSArray
readJSObject :: GetJSON JSValue
readJSObject = readAssocs '{' '}' ',' >>= return . JSObject . toJSObject
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
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 (fromJSString 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 (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
readJSValue :: GetJSON JSValue
readJSValue = do
cs <- getInput
case cs of
'"' : _ -> readJSString
'[' : _ -> readJSArray
'{' : _ -> readJSObject
't' : _ -> readJSBool
'f' : _ -> readJSBool
(x:_) | isDigit x || x == '-' -> JSRational False <$> readJSRational
xs -> tryJSNull
(fail $ "Malformed JSON: invalid token in this context " ++ context xs)
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."
showJSTopType :: JSValue -> ShowS
showJSTopType (JSArray a) = showJSArray a
showJSTopType (JSObject o) = showJSObject o
showJSTopType x = showJSTopType $ JSArray [x]
showJSValue :: JSValue -> ShowS
showJSValue jv =
case jv of
JSNull{} -> showJSNull
JSBool b -> showJSBool b
JSRational asF r -> showJSRational' asF r
JSArray a -> showJSArray a
JSString s -> showJSString s
JSObject o -> showJSObject o
showJSNull :: ShowS
showJSNull = showString "null"
showJSBool :: Bool -> ShowS
showJSBool True = showString "true"
showJSBool False = showString "false"
showJSString :: JSString -> ShowS
showJSString x xs = quote (encJSString x (quote xs))
where
quote = showChar '"'
showJSRational :: Rational -> ShowS
showJSRational r = showJSRational' False r
showJSRational' :: Bool -> Rational -> ShowS
showJSRational' asFloat r
| denominator r == 1 = shows $ numerator r
| isInfinite x || isNaN x = showJSNull
| asFloat = shows xf
| otherwise = shows x
where
x :: Double
x = realToFrac r
xf :: Float
xf = realToFrac r
showJSArray :: [JSValue] -> ShowS
showJSArray = showSequence '[' ']' ','
showJSObject :: JSObject JSValue -> ShowS
showJSObject = showAssocs '{' '}' ',' . fromJSObject
showAssocs :: Char -> Char -> Char -> [(String,JSValue)] -> ShowS
showAssocs start end sep xs rest = start : go xs
where
go [(k,v)] = '"' : encJSString (toJSString k)
('"' : ':' : showJSValue v (go []))
go ((k,v):kvs) = '"' : encJSString (toJSString k)
('"' : ':' : showJSValue v (sep : go kvs))
go [] = end : rest
showSequence :: Char -> Char -> Char -> [JSValue] -> ShowS
showSequence start end sep xs rest = start : go xs
where
go [y] = showJSValue y (go [])
go (y:ys) = showJSValue y (sep : go ys)
go [] = end : rest
encJSString :: JSString -> ShowS
encJSString jss ss = go (fromJSString jss)
where
go s1 =
case s1 of
(x :xs) | x < '\x20' -> '\\' : encControl x (go xs)
('"' :xs) -> '\\' : '"' : go xs
('\\':xs) -> '\\' : '\\' : go xs
(x :xs) -> x : go xs
"" -> ss
encControl x xs = case x of
'\b' -> 'b' : xs
'\f' -> 'f' : xs
'\n' -> 'n' : xs
'\r' -> 'r' : xs
'\t' -> 't' : xs
_ | x < '\x10' -> 'u' : '0' : '0' : '0' : hexxs
| x < '\x100' -> 'u' : '0' : '0' : hexxs
| x < '\x1000' -> 'u' : '0' : hexxs
| otherwise -> 'u' : hexxs
where hexxs = showHex (fromEnum x) xs