{-# LANGUAGE TypeFamilies #-} {-# LANGUAGE FlexibleInstances #-} -- | Strings in Javascript. module Language.Sunroof.JS.String ( JSString , string ) where import Data.Boolean ( BooleanOf, IfB(..), EqB(..) ) import Data.Monoid ( Monoid(..) ) import Data.Semigroup ( Semigroup(..) ) import Data.Char ( isAscii, isControl, ord ) import Data.String ( IsString(..) ) import Numeric ( showHex ) import Language.Sunroof.JavaScript ( Expr, showExpr, binOp, literal ) import Language.Sunroof.Classes ( Sunroof(..), SunroofValue(..) ) import Language.Sunroof.JS.Bool ( JSBool, jsIfB ) -- ------------------------------------------------------------- -- JSString Type -- ------------------------------------------------------------- -- | Javascript string type. data JSString = JSString Expr -- | Show the Javascript. instance Show JSString where show (JSString v) = showExpr False v -- | First-class Javascript value. instance Sunroof JSString where box = JSString unbox (JSString e) = e -- | Semigroup under concatination. instance Semigroup JSString where (JSString e1) <> (JSString e2) = box $ binOp "+" e1 e2 -- | Monoid under concatination and empty string. instance Monoid JSString where mempty = fromString "" mappend (JSString e1) (JSString e2) = box $ binOp "+" e1 e2 -- | Create them from Haskell 'String's. instance IsString JSString where fromString = box . literal . jsLiteralString type instance BooleanOf JSString = JSBool instance IfB JSString where ifB = jsIfB -- | Value equality. instance EqB JSString where (==*) e1 e2 = box $ binOp "==" (unbox e1) (unbox e2) (/=*) e1 e2 = box $ binOp "!=" (unbox e1) (unbox e2) -- | Create a 'JSString' from a 'String'. instance SunroofValue [Char] where type ValueOf [Char] = JSString js = fromString -- | Create a single character 'JSString' from a 'Char'. instance SunroofValue Char where type ValueOf Char = JSString js c = fromString [c] -- ------------------------------------------------------------- -- JSString Combinators -- ------------------------------------------------------------- -- | Create a Javascript string from a Haskell string. string :: String -> JSString string = fromString -- ------------------------------------------------------------- -- String Conversion Utilities: Haskell -> JS -- ------------------------------------------------------------- -- | Transform a Haskell string into a string representing a JS string literal. jsLiteralString :: String -> String jsLiteralString = jsQuoteString . jsEscapeString -- | Add quotes to a string. jsQuoteString :: String -> String jsQuoteString s = "\"" ++ s ++ "\"" -- | Transform a character to a string that represents its JS -- unicode escape sequence. jsUnicodeChar :: Char -> String jsUnicodeChar c = let hex = showHex (ord c) "" in ('\\':'u': replicate (4 - length hex) '0') ++ hex -- | Correctly replace Haskell characters by the JS escape sequences. jsEscapeString :: String -> String jsEscapeString [] = [] jsEscapeString (c:cs) = case c of -- Backslash has to remain backslash in JS. '\\' -> '\\' : '\\' : jsEscapeString cs -- Special control sequences. '\0' -> jsUnicodeChar '\0' ++ jsEscapeString cs -- Ambigous with numbers '\a' -> jsUnicodeChar '\a' ++ jsEscapeString cs -- Non JS '\b' -> '\\' : 'b' : jsEscapeString cs '\f' -> '\\' : 'f' : jsEscapeString cs '\n' -> '\\' : 'n' : jsEscapeString cs '\r' -> '\\' : 'r' : jsEscapeString cs '\t' -> '\\' : 't' : jsEscapeString cs '\v' -> '\\' : 'v' : jsEscapeString cs '\"' -> '\\' : '\"' : jsEscapeString cs '\'' -> '\\' : '\'' : jsEscapeString cs -- Non-control ASCII characters can remain as they are. c' | not (isControl c') && isAscii c' -> c' : jsEscapeString cs -- All other non ASCII signs are escaped to unicode. c' -> jsUnicodeChar c' ++ jsEscapeString cs