content: -----------------------------------------------------------------------------
  {-# LANGUAGE CPP                  #-}
  {-# LANGUAGE LambdaCase           #-}
  {-# LANGUAGE FlexibleInstances    #-}
  -----------------------------------------------------------------------------
  {-# OPTIONS_GHC -fno-warn-orphans #-}
  -----------------------------------------------------------------------------
  -- |
  -- Module      :  Miso.String
  -- Copyright   :  (C) 2016-2025 David M. Johnson
  -- License     :  BSD3-style (see the file LICENSE)
  -- Maintainer  :  David M. Johnson <code@dmj.io>
  -- Stability   :  experimental
  -- Portability :  non-portable
  ----------------------------------------------------------------------------
  module Miso.String (
      ToMisoString (..)
    , FromMisoString (..)
    , fromMisoString
    , MisoString
    , module Data.JSString
    , module Data.Monoid
    , ms
    ) where
  ----------------------------------------------------------------------------
  import           Control.Exception (SomeException)
  import           Data.Aeson
  import qualified Data.ByteString         as B
  import qualified Data.ByteString.Builder as B
  import qualified Data.ByteString.Lazy    as BL
  import           Data.Char
  import           Data.JSString
  import qualified Data.JSString as JS
  import           Data.JSString.Text
  import           Data.Monoid
  import qualified Data.Text               as T
  import qualified Data.Text.Encoding      as T
  import qualified Data.Text.Lazy          as LT
  import qualified Data.Text.Lazy.Encoding as LT
  import           Language.Javascript.JSaddle (MakeArgs (..), toJSVal)
  import           Prelude                 hiding (foldr)
  ----------------------------------------------------------------------------
  -- | String type swappable based on compiler
  type MisoString = JS.JSString
  ----------------------------------------------------------------------------
  instance MakeArgs JS.JSString where
    makeArgs arg = (:[]) <$> toJSVal arg
  ----------------------------------------------------------------------------
  instance ToJSONKey MisoString
  instance FromJSONKey MisoString
  ----------------------------------------------------------------------------
  #ifdef GHCJS_BOTH
  -- | `ToJSON` for `MisoString`
  instance ToJSON MisoString where
    toJSON = String . textFromJSString
  ----------------------------------------------------------------------------
  -- | `FromJSON` for `MisoString`
  instance FromJSON MisoString where
    parseJSON =
      withText "Not a valid string" $ \x ->
        pure (toMisoString x)
  #endif
  ----------------------------------------------------------------------------
  -- | Convenience class for creating `MisoString` from other string-like types
  class ToMisoString str where
    toMisoString :: str -> MisoString
  ----------------------------------------------------------------------------
  -- | Class used to parse a 'MisoString'. Like a safe 'Read' for 'MisoString'
  class FromMisoString t where
    fromMisoStringEither :: MisoString -> Either String t
  ----------------------------------------------------------------------------
  -- | Reads a 'MisoString', throws an error when decoding
  -- fails. Use `fromMisoStringEither` for as a safe alternative.
  fromMisoString :: FromMisoString a => MisoString -> a
  fromMisoString s =
    case fromMisoStringEither s of
      Left err -> error err
      Right x  -> x
  ----------------------------------------------------------------------------
  -- | Convenience function, shorthand for `toMisoString`
  ms :: ToMisoString str => str -> MisoString
  ms = toMisoString
  ----------------------------------------------------------------------------
  instance ToMisoString a => ToMisoString (Maybe a) where
    toMisoString Nothing = mempty
    toMisoString (Just x) = toMisoString x
  ----------------------------------------------------------------------------
  instance ToMisoString Char where
    toMisoString = JS.singleton
  ----------------------------------------------------------------------------
  instance ToMisoString MisoString where
    toMisoString = id
  ----------------------------------------------------------------------------
  instance ToMisoString SomeException where
    toMisoString = toMisoString . show
  ----------------------------------------------------------------------------
  instance ToMisoString String where
    toMisoString = JS.pack
  ----------------------------------------------------------------------------
  instance ToMisoString T.Text where
    toMisoString = textToJSString
  ----------------------------------------------------------------------------
  instance ToMisoString LT.Text where
    toMisoString = lazyTextToJSString
  instance ToMisoString B.ByteString where
    toMisoString = toMisoString . T.decodeUtf8
  ----------------------------------------------------------------------------
  instance ToMisoString BL.ByteString where
    toMisoString = toMisoString . LT.decodeUtf8
  ----------------------------------------------------------------------------
  instance ToMisoString B.Builder where
    toMisoString = toMisoString . B.toLazyByteString
  ----------------------------------------------------------------------------
  instance ToMisoString Float where
    toMisoString = JS.pack . show
  ----------------------------------------------------------------------------
  instance ToMisoString Double where
    toMisoString = JS.pack . show
  ----------------------------------------------------------------------------
  instance ToMisoString Int where
    toMisoString = JS.pack . show
  ----------------------------------------------------------------------------
  instance ToMisoString Word where
    toMisoString = JS.pack . show
  ----------------------------------------------------------------------------
  instance FromMisoString MisoString where
    fromMisoStringEither = Right
  ----------------------------------------------------------------------------
  instance FromMisoString String where
    fromMisoStringEither = Right . JS.unpack
  ----------------------------------------------------------------------------
  instance FromMisoString T.Text where
    fromMisoStringEither = Right . textFromJSString
  ----------------------------------------------------------------------------
  instance FromMisoString LT.Text where
    fromMisoStringEither = Right . lazyTextFromJSString
  ----------------------------------------------------------------------------
  instance FromMisoString B.ByteString where
    fromMisoStringEither = fmap T.encodeUtf8 . fromMisoStringEither
  ----------------------------------------------------------------------------
  instance FromMisoString BL.ByteString where
    fromMisoStringEither = fmap LT.encodeUtf8 . fromMisoStringEither
  ----------------------------------------------------------------------------
  instance FromMisoString B.Builder where
    fromMisoStringEither = fmap B.byteString . fromMisoStringEither
  ----------------------------------------------------------------------------
  instance FromMisoString Float where
    fromMisoStringEither = fmap realToFrac . jsStringToDoubleEither
  ----------------------------------------------------------------------------
  instance FromMisoString Double where
    fromMisoStringEither = jsStringToDoubleEither
  ----------------------------------------------------------------------------
  instance FromMisoString Int where
    fromMisoStringEither = parseInt
  ----------------------------------------------------------------------------
  instance FromMisoString Word where
    fromMisoStringEither = parseWord
  ----------------------------------------------------------------------------
  jsStringToDoubleEither :: JS.JSString -> Either String Double
  jsStringToDoubleEither s = let d = read $ JS.unpack s
                             in if isNaN d then Left "jsStringToDoubleEither: parse failed"
                                           else Right d
  ----------------------------------------------------------------------------
  parseWord :: MisoString -> Either String Word
  parseWord s = case JS.uncons s of
                  Nothing     -> Left "parseWord: parse error"
                  Just (c,s') -> JS.foldl' k (pDigit c) s'
    where
      pDigit c | isDigit c = Right . fromIntegral . digitToInt $ c
               | otherwise = Left "parseWord: parse error"
      k ea c = (\a x -> 10*a + x) <$> ea <*> pDigit c
  ----------------------------------------------------------------------------
  parseInt   :: MisoString -> Either String Int
  parseInt s = case JS.uncons s of
                 Just ('-',s') -> ((-1)*) . fromIntegral <$> parseWord s'
                 _             ->           fromIntegral <$> parseWord s
  ----------------------------------------------------------------------------
  