diff --git a/bower.json b/bower.json index e92fb95..5178621 100644 --- a/bower.json +++ b/bower.json @@ -20,7 +20,9 @@ "purescript-either": "^3.0.0", "purescript-gen": "^1.1.0", "purescript-maybe": "^3.0.0", - "purescript-partial": "^1.2.0" + "purescript-partial": "^1.2.0", + "purescript-unfoldable": "^3.0.0", + "purescript-arrays": "^4.0.1" }, "devDependencies": { "purescript-assert": "^3.0.0", diff --git a/src/Data/String/CodePoints.js b/src/Data/String/CodePoints.js new file mode 100644 index 0000000..1c73483 --- /dev/null +++ b/src/Data/String/CodePoints.js @@ -0,0 +1,108 @@ +"use strict"; +/* global Symbol */ + +var hasArrayFrom = typeof Array.from === "function"; +var hasStringIterator = + typeof Symbol !== "undefined" && + Symbol != null && + typeof Symbol.iterator !== "undefined" && + typeof String.prototype[Symbol.iterator] === "function"; +var hasFromCodePoint = typeof String.prototype.fromCodePoint === "function"; +var hasCodePointAt = typeof String.prototype.codePointAt === "function"; + +exports._unsafeCodePointAt0 = function (fallback) { + return hasCodePointAt + ? function (str) { return str.codePointAt(0); } + : fallback; +}; + +exports._codePointAt = function (fallback) { + return function (Just) { + return function (Nothing) { + return function (unsafeCodePointAt0) { + return function (index) { + return function (str) { + var length = str.length; + if (index < 0 || index >= length) return Nothing; + if (hasStringIterator) { + var iter = str[Symbol.iterator](); + for (var i = index;; --i) { + var o = iter.next(); + if (o.done) return Nothing; + if (i === 0) return Just(unsafeCodePointAt0(o.value)); + } + } + return fallback(index)(str); + }; + }; + }; + }; + }; +}; + +exports._count = function (fallback) { + return function (unsafeCodePointAt0) { + if (hasStringIterator) { + return function (pred) { + return function (str) { + var iter = str[Symbol.iterator](); + for (var cpCount = 0; ; ++cpCount) { + var o = iter.next(); + if (o.done) return cpCount; + var cp = unsafeCodePointAt0(o.value); + if (!pred(cp)) return cpCount; + } + }; + }; + } + return fallback; + }; +}; + +exports._fromCodePointArray = function (singleton) { + return hasFromCodePoint + ? function (cps) { + // Function.prototype.apply will fail for very large second parameters, + // so we don't use it for arrays with 10,000 or more entries. + if (cps.length < 10e3) { + return String.fromCodePoint.apply(String, cps); + } + return cps.map(singleton).join(""); + } + : function (cps) { + return cps.map(singleton).join(""); + }; +}; + +exports._singleton = function (fallback) { + return hasFromCodePoint ? String.fromCodePoint : fallback; +}; + +exports._take = function (fallback) { + return function (n) { + if (hasStringIterator) { + return function (str) { + var accum = ""; + var iter = str[Symbol.iterator](); + for (var i = 0; i < n; ++i) { + var o = iter.next(); + if (o.done) return accum; + accum += o.value; + } + return accum; + }; + } + return fallback(n); + }; +}; + +exports._toCodePointArray = function (fallback) { + return function (unsafeCodePointAt0) { + if (hasArrayFrom) { + return function (str) { + return Array.from(str, unsafeCodePointAt0); + }; + } + return fallback; + }; +}; diff --git a/src/Data/String/CodePoints.purs b/src/Data/String/CodePoints.purs new file mode 100644 index 0000000..7fe51f3 --- /dev/null +++ b/src/Data/String/CodePoints.purs @@ -0,0 +1,284 @@ +-- | These functions allow PureScript strings to be treated as if they were +-- | sequences of Unicode code points instead of their true underlying +-- | implementation (sequences of UTF-16 code units). For nearly all uses of +-- | strings, these functions should be preferred over the ones in Data.String. +module Data.String.CodePoints + ( module StringReExports + , CodePoint() + , codePointAt + , codePointFromInt + , codePointToInt + , count + , drop + , dropWhile + , fromCodePointArray + , indexOf + , indexOf' + , lastIndexOf + , lastIndexOf' + , length + , singleton + , splitAt + , take + , takeWhile + , toCodePointArray + , uncons + ) where + +import Prelude + +import Data.Array as Array +import Data.Char as Char +import Data.Maybe (Maybe(Just, Nothing)) +import Data.String as String +import Data.String.Unsafe as Unsafe +-- WARN: If a new function is added to Data.String, a version of that function +-- should be exported from this module, which should be the same except that it +-- should operate on the code point level rather than the code unit level. If +-- the function's behaviour does not change based on whether we consider +-- strings as sequences of code points or code units, it can simply be +-- re-exported from Data.String. +import Data.String (Pattern(..), Replacement(..), charAt, charCodeAt, contains, fromCharArray, joinWith, localeCompare, null, replace, replaceAll, split, stripPrefix, stripSuffix, toChar, toCharArray, toLower, toUpper, trim) as StringReExports +import Data.Tuple (Tuple(Tuple)) +import Data.Unfoldable (unfoldr) + + +-- | CodePoint is an Int bounded between 0 and 0x10FFFF, corresponding to +-- | Unicode code points. +newtype CodePoint = CodePoint Int + +derive instance eqCodePoint :: Eq CodePoint +derive instance ordCodePoint :: Ord CodePoint + +-- I would prefer that this smart constructor not need to exist and instead +-- CodePoint just implements Enum, but the Enum module already depends on this +-- one. To avoid the circular dependency, we just expose these two functions. +codePointFromInt :: Int -> Maybe CodePoint +codePointFromInt n | 0 <= n && n <= 0x10FFFF = Just (CodePoint n) +codePointFromInt n = Nothing + +codePointToInt :: CodePoint -> Int +codePointToInt (CodePoint n) = n + +unsurrogate :: Int -> Int -> CodePoint +unsurrogate lead trail = CodePoint ((lead - 0xD800) * 0x400 + (trail - 0xDC00) + 0x10000) + +isLead :: Int -> Boolean +isLead cu = 0xD800 <= cu && cu <= 0xDBFF + +isTrail :: Int -> Boolean +isTrail cu = 0xDC00 <= cu && cu <= 0xDFFF + +fromCharCode :: Int -> String +fromCharCode = String.singleton <<< Char.fromCharCode + +-- WARN: this function expects the String parameter to be non-empty +unsafeCodePointAt0 :: String -> CodePoint +unsafeCodePointAt0 = _unsafeCodePointAt0 unsafeCodePointAt0Fallback + +foreign import _unsafeCodePointAt0 + :: (String -> CodePoint) + -> String + -> CodePoint + +unsafeCodePointAt0Fallback :: String -> CodePoint +unsafeCodePointAt0Fallback s = + let cu0 = Unsafe.charCodeAt 0 s in + let cu1 = Unsafe.charCodeAt 1 s in + if isLead cu0 && isTrail cu1 + then unsurrogate cu0 cu1 + else CodePoint cu0 + + +-- | Returns the first code point of the string after dropping the given number +-- | of code points from the beginning, if there is such a code point. Operates +-- | in constant space and in time linear to the given index. +codePointAt :: Int -> String -> Maybe CodePoint +codePointAt n _ | n < 0 = Nothing +codePointAt 0 "" = Nothing +codePointAt 0 s = Just (unsafeCodePointAt0 s) +codePointAt n s = _codePointAt codePointAtFallback Just Nothing unsafeCodePointAt0 n s + +foreign import _codePointAt + :: (Int -> String -> Maybe CodePoint) + -> (forall a. a -> Maybe a) + -> (forall a. Maybe a) + -> (String -> CodePoint) + -> Int + -> String + -> Maybe CodePoint + +codePointAtFallback :: Int -> String -> Maybe CodePoint +codePointAtFallback n s = case uncons s of + Just { head, tail } -> if n == 0 then Just head else codePointAtFallback (n - 1) tail + _ -> Nothing + + +-- | Returns the number of code points in the leading sequence of code points +-- | which all match the given predicate. Operates in constant space and in +-- | time linear to the length of the string. +count :: (CodePoint -> Boolean) -> String -> Int +count = _count countFallback unsafeCodePointAt0 + +foreign import _count + :: ((CodePoint -> Boolean) -> String -> Int) + -> (String -> CodePoint) + -> (CodePoint -> Boolean) + -> String + -> Int + +countFallback :: (CodePoint -> Boolean) -> String -> Int +countFallback p s = countTail p s 0 + +countTail :: (CodePoint -> Boolean) -> String -> Int -> Int +countTail p s accum = case uncons s of + Just { head, tail } -> if p head then countTail p tail (accum + 1) else accum + _ -> accum + + +-- | Drops the given number of code points from the beginning of the string. If +-- | the string does not have that many code points, returns the empty string. +-- | Operates in constant space and in time linear to the given number. +drop :: Int -> String -> String +drop n s = String.drop (String.length (take n s)) s + + +-- | Drops the leading sequence of code points which all match the given +-- | predicate from the string. Operates in constant space and in time linear +-- | to the length of the string. +dropWhile :: (CodePoint -> Boolean) -> String -> String +dropWhile p s = drop (count p s) s + + +-- | Creates a string from an array of code points. Operates in space and time +-- | linear to the length of the array. +fromCodePointArray :: Array CodePoint -> String +fromCodePointArray = _fromCodePointArray singletonFallback + +foreign import _fromCodePointArray + :: (CodePoint -> String) + -> Array CodePoint + -> String + +-- | Returns the number of code points preceding the first match of the given +-- | pattern in the string. Returns Nothing when no matches are found. +indexOf :: String.Pattern -> String -> Maybe Int +indexOf p s = (\i -> length (String.take i s)) <$> String.indexOf p s + + +-- | Returns the number of code points preceding the first match of the given +-- | pattern in the string. Pattern matches preceding the given index will be +-- | ignored. Returns Nothing when no matches are found. +indexOf' :: String.Pattern -> Int -> String -> Maybe Int +indexOf' p i s = + let s' = drop i s in + (\k -> i + length (String.take k s')) <$> String.indexOf p s' + + +-- | Returns the number of code points preceding the last match of the given +-- | pattern in the string. Returns Nothing when no matches are found. +lastIndexOf :: String.Pattern -> String -> Maybe Int +lastIndexOf p s = (\i -> length (String.take i s)) <$> String.lastIndexOf p s + + +-- | Returns the number of code points preceding the first match of the given +-- | pattern in the string. Pattern matches following the given index will be +-- | ignored. Returns Nothing when no matches are found. +lastIndexOf' :: String.Pattern -> Int -> String -> Maybe Int +lastIndexOf' p i s = + let i' = String.length (take i s) in + (\k -> length (String.take k s)) <$> String.lastIndexOf' p i' s + + +-- | Returns the number of code points in the string. Operates in constant +-- | space and in time linear to the length of the string. +length :: String -> Int +length = Array.length <<< toCodePointArray + + +-- | Creates a string containing just the given code point. Operates in +-- | constant space and time. +singleton :: CodePoint -> String +singleton = _singleton singletonFallback + +foreign import _singleton + :: (CodePoint -> String) + -> CodePoint + -> String + +singletonFallback :: CodePoint -> String +singletonFallback (CodePoint cp) | cp <= 0xFFFF = fromCharCode cp +singletonFallback (CodePoint cp) = + let lead = ((cp - 0x10000) / 0x400) + 0xD800 in + let trail = (cp - 0x10000) `mod` 0x400 + 0xDC00 in + fromCharCode lead <> fromCharCode trail + + +-- | Returns a record with strings created from the code points on either side +-- | of the given index. If the index is not within the string, Nothing is +-- | returned. +splitAt :: Int -> String -> Maybe { before :: String, after :: String } +splitAt i s = + let cps = toCodePointArray s in + if i < 0 || Array.length cps < i + then Nothing + else Just { + before: fromCodePointArray (Array.take i cps), + after: fromCodePointArray (Array.drop i cps) + } + + +-- | Returns a string containing the given number of code points from the +-- | beginning of the given string. If the string does not have that many code +-- | points, returns the empty string. Operates in constant space and in time +-- | linear to the given number. +take :: Int -> String -> String +take = _take takeFallback + +foreign import _take :: (Int -> String -> String) -> Int -> String -> String + +takeFallback :: Int -> String -> String +takeFallback n _ | n < 1 = "" +takeFallback n s = case uncons s of + Just { head, tail } -> singleton head <> takeFallback (n - 1) tail + _ -> s + + +-- | Returns a string containing the leading sequence of code points which all +-- | match the given predicate from the string. Operates in constant space and +-- | in time linear to the length of the string. +takeWhile :: (CodePoint -> Boolean) -> String -> String +takeWhile p s = take (count p s) s + + +-- | Creates an array of code points from a string. Operates in space and time +-- | linear to the length of the string. +toCodePointArray :: String -> Array CodePoint +toCodePointArray = _toCodePointArray toCodePointArrayFallback unsafeCodePointAt0 + +foreign import _toCodePointArray + :: (String -> Array CodePoint) + -> (String -> CodePoint) + -> String + -> Array CodePoint + +toCodePointArrayFallback :: String -> Array CodePoint +toCodePointArrayFallback s = unfoldr unconsButWithTuple s + +unconsButWithTuple :: String -> Maybe (Tuple CodePoint String) +unconsButWithTuple s = (\{ head, tail } -> Tuple head tail) <$> uncons s + + +-- | Returns a record with the first code point and the remaining code points +-- | of the string. Returns Nothing if the string is empty. Operates in +-- | constant space and time. +uncons :: String -> Maybe { head :: CodePoint, tail :: String } +uncons s = case String.length s of + 0 -> Nothing + 1 -> Just { head: CodePoint (Unsafe.charCodeAt 0 s), tail: "" } + _ -> + let cu0 = Unsafe.charCodeAt 0 s in + let cu1 = Unsafe.charCodeAt 1 s in + if isLead cu0 && isTrail cu1 + then Just { head: unsurrogate cu0 cu1, tail: String.drop 2 s } + else Just { head: CodePoint cu0, tail: String.drop 1 s } diff --git a/test/Test/Data/String/CodePoints.purs b/test/Test/Data/String/CodePoints.purs new file mode 100644 index 0000000..7a6aef0 --- /dev/null +++ b/test/Test/Data/String/CodePoints.purs @@ -0,0 +1,198 @@ +module Test.Data.String.CodePoints (testStringCodePoints) where + +import Prelude + +import Control.Monad.Eff (Eff) +import Control.Monad.Eff.Console (CONSOLE, log) + +import Data.Maybe (Maybe(..), isNothing, maybe) +import Data.String.CodePoints + +import Test.Assert (ASSERT, assert) + +str :: String +str = "a\xDC00\xD800\xD800\x16805\x16A06\&z" + +testStringCodePoints :: forall eff. Eff (console :: CONSOLE, assert :: ASSERT | eff) Unit +testStringCodePoints = do + log "codePointAt" + assert $ codePointAt (-1) str == Nothing + assert $ codePointAt 0 str == (codePointFromInt 0x61) + assert $ codePointAt 1 str == (codePointFromInt 0xDC00) + assert $ codePointAt 2 str == (codePointFromInt 0xD800) + assert $ codePointAt 3 str == (codePointFromInt 0xD800) + assert $ codePointAt 4 str == (codePointFromInt 0x16805) + assert $ codePointAt 5 str == (codePointFromInt 0x16A06) + assert $ codePointAt 6 str == (codePointFromInt 0x7A) + assert $ codePointAt 7 str == Nothing + + log "count" + assert $ count (\_ -> true) "" == 0 + assert $ count (\_ -> false) str == 0 + assert $ count (\_ -> true) str == 7 + assert $ count (\x -> codePointToInt x < 0xFFFF) str == 4 + assert $ count (\x -> codePointToInt x < 0xDC00) str == 1 + + log "drop" + assert $ drop (-1) str == str + assert $ drop 0 str == str + assert $ drop 1 str == "\xDC00\xD800\xD800\x16805\x16A06\&z" + assert $ drop 2 str == "\xD800\xD800\x16805\x16A06\&z" + assert $ drop 3 str == "\xD800\x16805\x16A06\&z" + assert $ drop 4 str == "\x16805\x16A06\&z" + assert $ drop 5 str == "\x16A06\&z" + assert $ drop 6 str == "z" + assert $ drop 7 str == "" + assert $ drop 8 str == "" + + log "dropWhile" + assert $ dropWhile (\_ -> true) str == "" + assert $ dropWhile (\_ -> false) str == str + assert $ dropWhile (\c -> codePointToInt c < 0xFFFF) str == "\x16805\x16A06\&z" + assert $ dropWhile (\c -> codePointToInt c < 0xDC00) str == "\xDC00\xD800\xD800\x16805\x16A06\&z" + + log "indexOf" + assert $ indexOf (Pattern "") "" == Just 0 + assert $ indexOf (Pattern "") str == Just 0 + assert $ indexOf (Pattern str) str == Just 0 + assert $ indexOf (Pattern "a") str == Just 0 + assert $ indexOf (Pattern "\xDC00\xD800\xD800") str == Just 1 + assert $ indexOf (Pattern "\xD800") str == Just 2 + assert $ indexOf (Pattern "\xD800\xD800") str == Just 2 + assert $ indexOf (Pattern "\xD800\xD81A") str == Just 3 + assert $ indexOf (Pattern "\xD800\x16805") str == Just 3 + assert $ indexOf (Pattern "\x16805") str == Just 4 + assert $ indexOf (Pattern "\x16A06") str == Just 5 + assert $ indexOf (Pattern "z") str == Just 6 + assert $ indexOf (Pattern "\0") str == Nothing + assert $ indexOf (Pattern "\xD81A") str == Just 4 + + log "indexOf'" + assert $ indexOf' (Pattern "") 0 "" == Just 0 + assert $ indexOf' (Pattern str) 0 str == Just 0 + assert $ indexOf' (Pattern str) 1 str == Nothing + assert $ indexOf' (Pattern "a") 0 str == Just 0 + assert $ indexOf' (Pattern "a") 1 str == Nothing + assert $ indexOf' (Pattern "z") 0 str == Just 6 + assert $ indexOf' (Pattern "z") 1 str == Just 6 + assert $ indexOf' (Pattern "z") 2 str == Just 6 + assert $ indexOf' (Pattern "z") 3 str == Just 6 + assert $ indexOf' (Pattern "z") 4 str == Just 6 + assert $ indexOf' (Pattern "z") 5 str == Just 6 + assert $ indexOf' (Pattern "z") 6 str == Just 6 + assert $ indexOf' (Pattern "z") 7 str == Nothing + + log "lastIndexOf" + assert $ lastIndexOf (Pattern "") "" == Just 0 + assert $ lastIndexOf (Pattern "") str == Just 7 + assert $ lastIndexOf (Pattern str) str == Just 0 + assert $ lastIndexOf (Pattern "a") str == Just 0 + assert $ lastIndexOf (Pattern "\xDC00\xD800\xD800") str == Just 1 + assert $ lastIndexOf (Pattern "\xD800") str == Just 3 + assert $ lastIndexOf (Pattern "\xD800\xD800") str == Just 2 + assert $ lastIndexOf (Pattern "\xD800\xD81A") str == Just 3 + assert $ lastIndexOf (Pattern "\xD800\x16805") str == Just 3 + assert $ lastIndexOf (Pattern "\x16805") str == Just 4 + assert $ lastIndexOf (Pattern "\x16A06") str == Just 5 + assert $ lastIndexOf (Pattern "z") str == Just 6 + assert $ lastIndexOf (Pattern "\0") str == Nothing + assert $ lastIndexOf (Pattern "\xD81A") str == Just 5 + + log "lastIndexOf'" + assert $ lastIndexOf' (Pattern "") 0 "" == Just 0 + assert $ lastIndexOf' (Pattern str) 0 str == Just 0 + assert $ lastIndexOf' (Pattern str) 1 str == Just 0 + assert $ lastIndexOf' (Pattern "a") 0 str == Just 0 + assert $ lastIndexOf' (Pattern "a") 7 str == Just 0 + assert $ lastIndexOf' (Pattern "z") 0 str == Nothing + assert $ lastIndexOf' (Pattern "z") 1 str == Nothing + assert $ lastIndexOf' (Pattern "z") 2 str == Nothing + assert $ lastIndexOf' (Pattern "z") 3 str == Nothing + assert $ lastIndexOf' (Pattern "z") 4 str == Nothing + assert $ lastIndexOf' (Pattern "z") 5 str == Nothing + assert $ lastIndexOf' (Pattern "z") 6 str == Just 6 + assert $ lastIndexOf' (Pattern "z") 7 str == Just 6 + assert $ lastIndexOf' (Pattern "\xD800") 7 str == Just 3 + assert $ lastIndexOf' (Pattern "\xD800") 6 str == Just 3 + assert $ lastIndexOf' (Pattern "\xD800") 5 str == Just 3 + assert $ lastIndexOf' (Pattern "\xD800") 4 str == Just 3 + assert $ lastIndexOf' (Pattern "\xD800") 3 str == Just 3 + assert $ lastIndexOf' (Pattern "\xD800") 2 str == Just 2 + assert $ lastIndexOf' (Pattern "\xD800") 1 str == Nothing + assert $ lastIndexOf' (Pattern "\xD800") 0 str == Nothing + assert $ lastIndexOf' (Pattern "\x16A06") 7 str == Just 5 + assert $ lastIndexOf' (Pattern "\x16A06") 6 str == Just 5 + assert $ lastIndexOf' (Pattern "\x16A06") 5 str == Just 5 + assert $ lastIndexOf' (Pattern "\x16A06") 4 str == Nothing + assert $ lastIndexOf' (Pattern "\x16A06") 3 str == Nothing + + log "length" + assert $ length "" == 0 + assert $ length "a" == 1 + assert $ length "ab" == 2 + assert $ length str == 7 + + log "singleton" + assert $ (singleton <$> codePointFromInt 0x30) == Just "0" + assert $ (singleton <$> codePointFromInt 0x16805) == Just "\x16805" + + log "splitAt" + let testSplitAt i s res = + assert $ case splitAt i s of + Nothing -> + isNothing res + Just { before, after } -> + maybe false (\r -> + r.before == before && r.after == after) res + + testSplitAt 0 "" $ Just {before: "", after: ""} + testSplitAt 1 "" Nothing + testSplitAt 0 "a" $ Just {before: "", after: "a"} + testSplitAt 1 "ab" $ Just {before: "a", after: "b"} + testSplitAt 3 "aabcc" $ Just {before: "aab", after: "cc"} + testSplitAt (-1) "abc" $ Nothing + testSplitAt 0 str $ Just {before: "", after: str} + testSplitAt 1 str $ Just {before: "a", after: "\xDC00\xD800\xD800\x16805\x16A06\&z"} + testSplitAt 2 str $ Just {before: "a\xDC00", after: "\xD800\xD800\x16805\x16A06\&z"} + testSplitAt 3 str $ Just {before: "a\xDC00\xD800", after: "\xD800\x16805\x16A06\&z"} + testSplitAt 4 str $ Just {before: "a\xDC00\xD800\xD800", after: "\x16805\x16A06\&z"} + testSplitAt 5 str $ Just {before: "a\xDC00\xD800\xD800\x16805", after: "\x16A06\&z"} + testSplitAt 6 str $ Just {before: "a\xDC00\xD800\xD800\x16805\x16A06", after: "z"} + testSplitAt 7 str $ Just {before: str, after: ""} + testSplitAt 8 str $ Nothing + + log "take" + assert $ take (-1) str == "" + assert $ take 0 str == "" + assert $ take 1 str == "a" + assert $ take 2 str == "a\xDC00" + assert $ take 3 str == "a\xDC00\xD800" + assert $ take 4 str == "a\xDC00\xD800\xD800" + assert $ take 5 str == "a\xDC00\xD800\xD800\x16805" + assert $ take 6 str == "a\xDC00\xD800\xD800\x16805\x16A06" + assert $ take 7 str == str + assert $ take 8 str == str + + log "takeWhile" + assert $ takeWhile (\_ -> true) str == str + assert $ takeWhile (\_ -> false) str == "" + assert $ takeWhile (\c -> codePointToInt c < 0xFFFF) str == "a\xDC00\xD800\xD800" + assert $ takeWhile (\c -> codePointToInt c < 0xDC00) str == "a" + + log "uncons" + let testUncons s res = + assert $ case uncons s of + Nothing -> + isNothing res + Just { head, tail } -> + maybe false (\r -> + r.head == codePointToInt head && r.tail == tail) res + + testUncons str $ Just {head: 0x61, tail: "\xDC00\xD800\xD800\x16805\x16A06\&z"} + testUncons (drop 1 str) $ Just {head: 0xDC00, tail: "\xD800\xD800\x16805\x16A06\&z"} + testUncons (drop 2 str) $ Just {head: 0xD800, tail: "\xD800\x16805\x16A06\&z"} + testUncons (drop 3 str) $ Just {head: 0xD800, tail: "\x16805\x16A06\&z"} + testUncons (drop 4 str) $ Just {head: 0x16805, tail: "\x16A06\&z"} + testUncons (drop 5 str) $ Just {head: 0x16A06, tail: "z"} + testUncons (drop 6 str) $ Just {head: 0x7A, tail: ""} + testUncons "" Nothing diff --git a/test/Test/Main.purs b/test/Test/Main.purs index b2c7f50..8260cc6 100644 --- a/test/Test/Main.purs +++ b/test/Test/Main.purs @@ -8,6 +8,7 @@ import Control.Monad.Eff.Console (CONSOLE) import Test.Assert (ASSERT) import Test.Data.Char (testChar) import Test.Data.String (testString) +import Test.Data.String.CodePoints (testStringCodePoints) import Test.Data.String.Regex (testStringRegex) import Test.Data.String.Unsafe (testStringUnsafe) import Test.Data.String.CaseInsensitive (testCaseInsensitiveString) @@ -16,6 +17,7 @@ main :: Eff (console :: CONSOLE, assert :: ASSERT) Unit main = do testChar testString + testStringCodePoints testStringUnsafe testStringRegex testCaseInsensitiveString