{-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE EmptyDataDecls #-} {-# LANGUAGE NoImplicitPrelude #-} {-# LANGUAGE NoRebindableSyntax #-} {-# LANGUAGE PackageImports #-} {-# LANGUAGE StandaloneDeriving #-} -- | Compatible API with the `text' package. module Data.Text ( Text -- * Creation and elimination , pack , unpack , fromString , empty -- * Conversions , showInt , toShortest -- * I/O , putStrLn -- * Breaking into many substrings , splitOn , stripSuffix -- * Basic interface , cons , snoc , append , (<>) , uncons , head , init , last , tail , null , length -- * Special folds , maximum , all , any , concatMap , concat , minimum -- * Case conversion , toLower , toUpper -- * Transformations , map , intercalate , intersperse , reverse -- * Predicates , isPrefixOf -- * Substrings , drop , take -- * Breaking into lines and words , unlines , lines ) where import Data.Data import FFI import Data.Nullable (fromNullable) import Prelude (Eq,String,Int,Bool,Char,Maybe,Double,error) import qualified "base" Data.String as B (IsString (..)) -- | A space efficient, packed, unboxed Unicode text type. data Text deriving instance Eq Text deriving instance Data Text deriving instance Typeable Text instance B.IsString Text where fromString = error "the method fromString can never be called" -- | O(n) The intercalate function takes a Text and a list of Texts and -- concatenates the list after interspersing the first argument -- between each element of the list. intercalate :: Text -> [Text] -> Text intercalate = ffi "%2.join(%1)" -- | Convert from a string to text. fromString :: String -> Text fromString = ffi "%1" -- | O(n) Adds a character to the end of a Text. This copies the -- entire array in the process, unless fused. Subject to -- fusion. Performs replacement on invalid scalar values. snoc :: Text -> Char -> Text snoc = ffi "%1 + %2" -- | O(n) Adds a character to the front of a Text. This function is -- more costly than its List counterpart because it requires copying a -- new array. Subject to fusion. Performs replacement on invalid -- scalar values. cons :: Char -> Text -> Text cons = ffi "%1 + %2" -- | O(n) Convert a String into a Text. Subject to fusion. Performs -- replacement on invalid scalar values. pack :: String -> Text pack = ffi "%1" -- | O(n) Convert a Text into a String. Subject to fusion. unpack :: Text -> String unpack = ffi "%1" -- | O(n) Appends one Text to the other by copying both of them into a -- new Text. Subject to fusion. append :: Text -> Text -> Text append = ffi "%1 + %2" -- | Append two texts. (<>) :: Text -> Text -> Text (<>) = ffi "%1 + %2" -- | O(n) Returns the number of characters in a Text. Subject to -- fusion. length :: Text -> Int length = ffi "%1.length" -- | O(1) Tests whether a Text is empty or not. Subject to fusion. null :: Text -> Bool null = ffi "%1.length == 0" -- | O(n) take n, applied to a Text, returns the prefix of the Text of -- length n, or the Text itself if n is greater than the length of the -- Text. Subject to fusion. take :: Int -> Text -> Text take = ffi "%2.substring(0,%1)" -- | O(n) drop n, applied to a Text, returns the suffix of the Text -- after the first n characters, or the empty Text if n is greater -- than the length of the Text. Subject to fusion. drop :: Int -> Text -> Text drop = ffi "%2.substring(%1)" -- | O(1) The empty Text. -- Basic interface empty :: Text empty = ffi "\"\"" -- | O(n) Breaks a Text up into a list of Texts at newline Chars. The -- resulting strings do not contain newlines. lines :: Text -> [Text] lines = ffi "%1.split('\\n')" -- | O(n) Joins lines, after appending a terminating newline to each. unlines :: [Text] -> Text unlines = ffi "%1.join('\\n')" -- | O(n) The isPrefixOf function takes two Texts and returns True iff -- the first is a prefix of the second. Subject to fusion. -- http://docs.closure-library.googlecode.com/git/closure_goog_string_string.js.source.html isPrefixOf :: Text -> Text -> Bool isPrefixOf = ffi "%2.lastIndexOf(%1, 0) == 0" -- | O(n) The intersperse function takes a character and places it -- between the characters of a Text. Subject to fusion. Performs -- replacement on invalid scalar values. intersperse :: Char -> Text -> Text intersperse = ffi "%2.split('').join(%1)" -- | O(n) Reverse the characters of a string. Subject to fusion. reverse :: Text -> Text reverse = ffi "%1.split('').reverse().join('')" -- | O(n) Return the prefix of the second string if its suffix matches -- the entire first string. stripSuffix :: Text -- ^ Suffix. -> Text -- ^ Text. -> Maybe Text stripSuffix prefix text = fromNullable (extract prefix text) where extract :: Text -> Text -> Nullable Text extract = ffi "(function(suffix,text){ return text.substring(text.length - suffix.length) == suffix? text.substring(0,text.length - suffix.length) : null; })(%1,%2)" -- | O(m+n) Break a Text into pieces separated by the first Text -- argument, consuming the delimiter. An empty delimiter is -- invalid, and will cause an error to be raised. splitOn :: Char -> Text -> [Text] splitOn = ffi "%2.split(%1)" -- | putStrLn :: Text -> Fay () putStrLn = ffi "console.log('%%s',%1)" -- | toShortest :: Double -> Text toShortest = ffi "%1.toString()" -- | showInt :: Int -> Text showInt = ffi "%1.toString()" -- | O(1) Returns the first character and rest of a Text, or Nothing -- if empty. Subject to fusion. uncons :: Text -> Maybe (Char, Text) uncons = ffi "%1[0] ? { instance: 'Just', slot1 : [%1[0],%1.slice(1)] } : { instance : 'Nothing' }" -- | O(1) Returns the first character of a Text, which must be -- non-empty. Subject to fusion. head :: Text -> Char head = ffi "%1[0] || (function () {throw new Error('Data.Text.head: empty Text'); }())" -- | O(1) Returns the last character of a Text, which must be -- non-empty. Subject to fusion. last :: Text -> Char last = ffi "%1.length ? %1[%1.length-1] : (function() { throw new Error('Data.Text.last: empty Text') })()" -- | O(1) Returns all characters after the head of a Text, which must -- be non-empty. Subject to fusion. tail :: Text -> Text tail = ffi "%1.length ? %1.slice(1) : (function () { throw new Error('Data.Text.tail: empty Text') })()" -- | O(1) Returns all but the last character of a Text, which must be -- non-empty. Subject to fusion. init :: Text -> Text init = ffi "%1.length ? %1.slice(0,-1) : (function () { throw new Error('Data.Text.init: empty Text') })()" -- | O(n) map f t is the Text obtained by applying f to each element -- of t. Subject to fusion. Performs replacement on invalid scalar -- values. map :: (Char -> Char) -> Text -> Text map = ffi "[].map.call(%2, %1).join('')" -- | O(n) Convert a string to lower case, using simple case -- conversion. The result string may be longer than the input -- string. For instance, "İ" (Latin capital letter I with dot above, -- U+0130) maps to the sequence "i" (Latin small letter i, U+0069) -- followed by " ̇" (combining dot above, U+0307). toLower :: Text -> Text toLower = ffi "%1.toLowerCase()" -- | O(n) Convert a string to upper case, using simple case -- conversion. The result string may be longer than the input -- string. For instance, the German "ß" (eszett, U+00DF) maps to the -- two-letter sequence "SS". toUpper :: Text -> Text toUpper = ffi "%1.toUpperCase()" -- | O(n) Concatenate a list of Texts. concat :: [Text] -> Text concat = ffi "%1.join('')" -- | O(n) Map a function over a Text that results in a Text, and -- concatenate the results. concatMap :: (Char -> Text) -> Text -> Text concatMap = ffi "[].map.call(%2, %1).join('')" -- | O(n) any p t determines whether any character in the Text t -- satisifes the predicate p. Subject to fusion. any :: (Char -> Bool) -> Text -> Bool any = ffi "[].filter.call(%2, %1).length > 0" -- | O(n) all p t determines whether all characters in the Text t -- satisify the predicate p. Subject to fusion. all :: (Char -> Bool) -> Text -> Bool all = ffi "[].filter.call(%2, %1).length == %1.length" -- | O(n) maximum returns the maximum value from a Text, which must be -- non-empty. Subject to fusion. maximum :: Text -> Char maximum = ffi "(function (s) { \ \ if (s === '') { throw new Error('Data.Text.maximum: empty string'); } \ \ var max = s[0]; \ \ for (var i = 1; i < s.length; s++) { \ \ if (s[i] > max) { max = s[i]; } \ \ } \ \ return max; \ \ })(%1)" -- | O(n) minimum returns the minimum value from a Text, which must be -- non-empty. Subject to fusion. minimum :: Text -> Char minimum = ffi "(function (s) { \ \ if (s === '') { throw new Error('Data.Text.maximum: empty string'); } \ \ var min = s[0]; \ \ for (var i = 1; i < s.length; s++) { \ \ if (s[i] < min) { min = s[i]; } \ \ } \ \ return min; \ \ })(%1)"