{-# LANGUAGE DeriveFoldable #-} {-# LANGUAGE DeriveFunctor #-} {-# LANGUAGE DeriveTraversable #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE FunctionalDependencies #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE NoImplicitPrelude #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE TypeFamilies #-} -- | Types and functions for handling characters in JSON. module Waargonaut.Types.JChar ( -- * Types JChar (..) , AsJChar (..) , HasJChar (..) -- * Parser , parseJChar -- * Conversion , utf8CharToJChar , jCharToUtf8Char , jCharToChar , charToJChar ) where import Prelude (Char, Eq, Ord, Show, otherwise, (/=)) import Control.Category (id, (.)) import Control.Lens (Lens', Prism', preview, prism, review) import Control.Applicative ((<$>), (<|>)) import Data.Bits ((.&.)) import Data.Char (ord) import Data.Either (Either (..)) import Data.Foldable (Foldable, asum) import Data.Function (($)) import Data.Functor (Functor) import Data.Maybe (Maybe (..), fromMaybe) import Data.Traversable (Traversable) import qualified Data.Text.Internal as Text import Data.Digit (HeXDigit, HeXaDeCiMaL) import qualified Data.Digit as D import Text.Parser.Char (CharParsing) import Waargonaut.Types.JChar.HexDigit4 (HexDigit4 (..)) import Waargonaut.Types.JChar.Escaped (AsEscaped (..), Escaped (..), charToEscaped, escapedToChar, parseEscaped) import Waargonaut.Types.JChar.Unescaped (AsUnescaped (..), Unescaped, parseUnescaped) -- $setup -- >>> :set -XOverloadedStrings -- >>> import Data.Function (($)) -- >>> import Data.Either(Either (..), isLeft) -- >>> import Data.Digit (HeXDigit(..)) -- >>> import Utils -- >>> import Waargonaut.Decode.Error (DecodeError) -- >>> import Waargonaut.Types.Whitespace -- >>> import Waargonaut.Types.JChar.Unescaped -- >>> import Waargonaut.Types.JChar.Escaped -- >>> import Waargonaut.Types.JChar ---- -- | A JChar may be unescaped or escaped. data JChar digit = EscapedJChar ( Escaped digit ) | UnescapedJChar Unescaped deriving (Eq, Ord, Show, Functor, Foldable, Traversable) -- | Typeclass for things that have a 'JChar'. class HasJChar c digit | c -> digit where jChar :: Lens' c (JChar digit) instance HasJChar (JChar digit) digit where jChar = id -- | Typeclass for things that be used as a 'JChar'. class AsJChar r digit | r -> digit where _JChar :: Prism' r (JChar digit) _EscapedJChar :: Prism' r (Escaped digit) _UnescapedJChar :: Prism' r Unescaped _EscapedJChar = _JChar . _EscapedJChar _UnescapedJChar = _JChar . _UnescapedJChar instance AsJChar (JChar digit) digit where _JChar = id _EscapedJChar = prism EscapedJChar (\ x -> case x of EscapedJChar y1 -> Right y1 _ -> Left x ) _UnescapedJChar = prism UnescapedJChar (\ x -> case x of UnescapedJChar y1 -> Right y1 _ -> Left x ) instance AsEscaped (JChar digit) digit where _Escaped = _JChar . _Escaped instance AsUnescaped (JChar digit) where _Unescaped = _JChar . _Unescaped -- instance AsJChar Char HeXDigit where -- Don't implement this, it's not a lawful prism. -- | Convert a 'JChar' to a Haskell 'Char' jCharToChar :: JChar HeXDigit -> Char jCharToChar (UnescapedJChar uejc) = review _Unescaped uejc jCharToChar (EscapedJChar ejc) = escapedToChar ejc -- | Attempt to convert a Haskell 'Char' to a JSON acceptable 'JChar' charToJChar :: Char -> Maybe (JChar HeXDigit) charToJChar c = (UnescapedJChar <$> preview _Unescaped c) <|> (EscapedJChar <$> charToEscaped c) utf8SafeChar :: Char -> Maybe Char utf8SafeChar c | ord c .&. 0x1ff800 /= 0xd800 = Just c | otherwise = Nothing -- | Convert a 'Char' to 'JChar HexDigit' and replace any invalid values with -- @U+FFFD@ as per the 'Data.Text.Text' documentation. -- -- Refer to documentation for more info. -- utf8CharToJChar :: Char -> JChar HeXDigit utf8CharToJChar c = fromMaybe scalarReplacement (charToJChar $ Text.safe c) where scalarReplacement = EscapedJChar (Hex (HexDigit4 D.xf D.xf D.xf D.xd)) {-# INLINE utf8CharToJChar #-} -- | Try to convert a 'JChar' to a 'Data.Text.Text' safe 'Char' value. Refer to the link for more info: -- https://hackage.haskell.org/package/text-1.2.3.0/docs/Data-Text-Internal.html#v:safe jCharToUtf8Char :: JChar HeXDigit -> Maybe Char jCharToUtf8Char = utf8SafeChar . jCharToChar {-# INLINE jCharToUtf8Char #-} -- | Parse a JSON character. -- -- >>> testparse parseJChar "\\u1234" :: Either DecodeError (JChar HeXDigit) -- Right (EscapedJChar (Hex (HexDigit4 HeXDigit1 HeXDigit2 HeXDigit3 HeXDigit4))) -- -- >>> testparse parseJChar "\\\\" :: Either DecodeError (JChar HeXDigit) -- Right (EscapedJChar ReverseSolidus) -- -- >>> testparse parseJChar "\\r" -- Right (EscapedJChar (WhiteSpace CarriageReturn)) -- -- >>> testparsetheneof parseJChar "a" -- Right (UnescapedJChar (Unescaped 'a')) -- -- >>> testparsethennoteof parseJChar "ax" -- Right (UnescapedJChar (Unescaped 'a')) parseJChar :: (CharParsing f, HeXaDeCiMaL digit) => f ( JChar digit ) parseJChar = asum [ EscapedJChar <$> parseEscaped , UnescapedJChar <$> parseUnescaped ]