{-# LANGUAGE DeriveFoldable #-} {-# LANGUAGE DeriveFunctor #-} {-# LANGUAGE DeriveTraversable #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE FunctionalDependencies #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE NoImplicitPrelude #-} -- | Types and functions for handling escaped characters in JSON. module Waargonaut.Types.JChar.Escaped ( -- * Types Escaped (..) , AsEscaped (..) -- * Parser , parseEscaped -- * Conversion , escapedToChar , charToEscaped ) where import Prelude (Eq, Ord, Show) import Control.Applicative (pure, (*>), (<|>)) import Control.Category (id, (.)) import Control.Lens (Prism', preview, prism, to, _Just) import Data.Foldable (Foldable, asum) import Data.Functor (Functor, (<$>)) import Data.Traversable (Traversable) import Data.Function (const) import Data.Char (Char) import Data.Either (Either (..)) import Data.Maybe (Maybe (..)) import Data.Digit (HeXDigit, HeXaDeCiMaL) import Text.Parser.Char (CharParsing, char) import Waargonaut.Types.JChar.HexDigit4 (HexDigit4, charToHexDigit4, hexDigit4ToChar, parseHexDigit4) import Waargonaut.Types.Whitespace (Whitespace (..), escapedWhitespaceChar, _WhitespaceChar) -- $setup -- >>> :set -XOverloadedStrings -- >>> import Control.Monad (return) -- >>> import Data.Either(Either (..), isLeft) -- >>> import Data.Digit (HeXDigit(..)) -- >>> import qualified Data.Digit as D -- >>> import Waargonaut.Decode.Error (DecodeError) -- >>> import Utils ---- -- | Things that may be escaped in a JSON string. data Escaped digit = QuotationMark | ReverseSolidus | Solidus | Backspace | WhiteSpace Whitespace | Hex ( HexDigit4 digit ) deriving (Eq, Ord, Show, Functor, Foldable, Traversable) -- | Typeclass for things that may be used as an escaped JChar. class AsEscaped r digit | r -> digit where _Escaped :: Prism' r (Escaped digit) _QuotationMark :: Prism' r () _ReverseSolidus :: Prism' r () _Solidus :: Prism' r () _Backspace :: Prism' r () _WhiteSpace :: Prism' r Whitespace _Hex :: Prism' r (HexDigit4 digit) _QuotationMark = _Escaped . _QuotationMark _ReverseSolidus = _Escaped . _ReverseSolidus _Solidus = _Escaped . _Solidus _Backspace = _Escaped . _Backspace _WhiteSpace = _Escaped . _WhiteSpace _Hex = _Escaped . _Hex instance AsEscaped (Escaped digit) digit where _Escaped = id _QuotationMark = prism (const QuotationMark) (\ x -> case x of QuotationMark -> Right () _ -> Left x ) _ReverseSolidus = prism (const ReverseSolidus) (\ x -> case x of ReverseSolidus -> Right () _ -> Left x ) _Solidus = prism (const Solidus) (\ x -> case x of Solidus -> Right () _ -> Left x ) _Backspace = prism (const Backspace) (\ x -> case x of Backspace -> Right () _ -> Left x ) _WhiteSpace = prism WhiteSpace (\ x -> case x of WhiteSpace y1 -> Right y1 _ -> Left x ) _Hex = prism Hex (\ x -> case x of Hex y1 -> Right y1 _ -> Left x ) -- | Parse an escapted JSON character. -- -- >>> testparse parseEscaped "\\\"" -- Right QuotationMark -- -- >>> testparse parseEscaped "\\\\" -- Right ReverseSolidus -- -- >>> testparse parseEscaped "\\/" -- Right Solidus -- -- >>> testparse parseEscaped "\\b" -- Right Backspace -- -- >>> testparse parseEscaped "\\f" -- Right (WhiteSpace LineFeed) -- -- >>> testparse parseEscaped "\\n" -- Right (WhiteSpace NewLine) -- -- >>> testparse parseEscaped "\\r" -- Right (WhiteSpace CarriageReturn) -- -- >>> testparse parseEscaped "\\t" -- Right (WhiteSpace HorizontalTab) -- -- >>> testparse parseEscaped "\\u1234" :: Either DecodeError (Escaped HeXDigit) -- Right (Hex (HexDigit4 HeXDigit1 HeXDigit2 HeXDigit3 HeXDigit4)) -- -- >>> testparsetheneof parseEscaped "\\t" -- Right (WhiteSpace HorizontalTab) -- -- >>> testparsethennoteof parseEscaped "\\tx" -- Right (WhiteSpace HorizontalTab) parseEscaped :: (CharParsing f, HeXaDeCiMaL digit) => f ( Escaped digit ) parseEscaped = let z = asum ((\(c, p) -> char c *> pure p) <$> [ ('"' , QuotationMark) , ('\\', ReverseSolidus) , ('/' , Solidus) , ('b' , Backspace) , (' ' , WhiteSpace Space) , ('f' , WhiteSpace LineFeed) , ('n' , WhiteSpace NewLine) , ('r' , WhiteSpace CarriageReturn) , ('t' , WhiteSpace HorizontalTab) ]) h = Hex <$> (char 'u' *> parseHexDigit4) in char '\\' *> (z <|> h) -- | Convert an 'Escaped' character to a Haskell 'Char' escapedToChar :: Escaped HeXDigit -> Char escapedToChar = \case QuotationMark -> '"' ReverseSolidus -> '\\' Solidus -> '/' Backspace -> '\b' WhiteSpace wc -> escapedWhitespaceChar wc Hex hd -> hexDigit4ToChar hd -- | Attempt to convert a Haskell 'Char' to an 'Escaped' JSON character charToEscaped :: Char -> Maybe (Escaped HeXDigit) charToEscaped c = case c of '"' -> Just QuotationMark '\\' -> Just ReverseSolidus '/' -> Just Solidus '\b' -> Just Backspace _ -> preview asWhitespace c <|> preview asHex c where asWhitespace = _WhitespaceChar . to WhiteSpace asHex = to charToHexDigit4 . _Just . to Hex