├── .eslintrc.json ├── .github ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── ci.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── bench └── Main.purs ├── bower.json ├── package.json ├── src └── Data │ ├── Char.purs │ ├── Char │ └── Gen.purs │ ├── String.purs │ └── String │ ├── CaseInsensitive.purs │ ├── CodePoints.js │ ├── CodePoints.purs │ ├── CodeUnits.js │ ├── CodeUnits.purs │ ├── Common.js │ ├── Common.purs │ ├── Gen.purs │ ├── NonEmpty.purs │ ├── NonEmpty │ ├── CaseInsensitive.purs │ ├── CodePoints.purs │ ├── CodeUnits.purs │ └── Internal.purs │ ├── Pattern.purs │ ├── Regex.js │ ├── Regex.purs │ ├── Regex │ ├── Flags.purs │ └── Unsafe.purs │ ├── Unsafe.js │ └── Unsafe.purs └── test └── Test ├── Data ├── String.purs └── String │ ├── CaseInsensitive.purs │ ├── CodePoints.purs │ ├── CodeUnits.purs │ ├── NonEmpty.purs │ ├── NonEmpty │ └── CodeUnits.purs │ ├── Regex.purs │ └── Unsafe.purs └── Main.purs /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parserOptions": { 3 | "ecmaVersion": 6, 4 | "sourceType": "module" 5 | }, 6 | "extends": "eslint:recommended", 7 | "rules": { 8 | "strict": [2, "global"], 9 | "block-scoped-var": 2, 10 | "consistent-return": 2, 11 | "eqeqeq": [2, "smart"], 12 | "guard-for-in": 2, 13 | "no-caller": 2, 14 | "no-extend-native": 2, 15 | "no-loop-func": 2, 16 | "no-new": 2, 17 | "no-param-reassign": 2, 18 | "no-return-assign": 2, 19 | "no-unused-expressions": 2, 20 | "no-use-before-define": 2, 21 | "radix": [2, "always"], 22 | "indent": [2, 2], 23 | "quotes": [2, "double"], 24 | "semi": [2, "always"] 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | **Description of the change** 2 | 3 | Clearly and concisely describe the purpose of the pull request. If this PR relates to an existing issue or change proposal, please link to it. Include any other background context that would help reviewers understand the motivation for this PR. 4 | 5 | --- 6 | 7 | **Checklist:** 8 | 9 | - [ ] Added the change to the changelog's "Unreleased" section with a reference to this PR (e.g. "- Made a change (#0000)") 10 | - [ ] Linked any existing issues or proposals that this pull request should close 11 | - [ ] Updated or added relevant documentation 12 | - [ ] Added a test for the contribution (if applicable) 13 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | branches: [master] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | 15 | - uses: purescript-contrib/setup-purescript@main 16 | with: 17 | purescript: "unstable" 18 | 19 | - uses: actions/setup-node@v2 20 | with: 21 | node-version: "14.x" 22 | 23 | - name: Install dependencies 24 | run: | 25 | npm install -g bower 26 | npm install 27 | bower install --production 28 | 29 | - name: Build source 30 | run: npm run-script build 31 | 32 | - name: Run tests 33 | run: | 34 | bower install 35 | npm run-script test --if-present 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.* 2 | !/.gitignore 3 | !/.eslintrc.json 4 | !/.github/ 5 | /bower_components/ 6 | /node_modules/ 7 | /output/ 8 | package-lock.json 9 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | Notable changes to this project are documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 4 | 5 | ## [Unreleased] 6 | 7 | Breaking changes: 8 | 9 | New features: 10 | 11 | Bugfixes: 12 | 13 | Other improvements: 14 | - Redefine `Data.String.NonEmpty.CodeUnits.fromFoldable1` in terms of `singleton` (#168 by @postsolar) 15 | 16 | ## [v6.0.1](https://github.com/purescript/purescript-strings/releases/tag/v6.0.1) - 2022-08-16 17 | 18 | Bugfixes: 19 | - Fix `Char`'s `toEnum` implementation (#163 by @JordanMartinez) 20 | 21 | ## [v6.0.0](https://github.com/purescript/purescript-strings/releases/tag/v6.0.0) - 2022-04-27 22 | 23 | Breaking changes: 24 | - Migrate FFI to ES modules (#158 by @kl0tl and @JordanMartinez) 25 | - Replaced polymorphic proxies with monomorphic `Proxy` (#158 by @JordanMartinez) 26 | - In `slice`, drop bounds checking and `Maybe` return type (#145 by Quelklef) 27 | 28 | New features: 29 | 30 | Bugfixes: 31 | 32 | Other improvements: 33 | - Surround code with backticks in documentation (#148) 34 | - Make `RegexFlags` a `newtype` and a `Newtype` instance for it(#159 by @mhmdanas) 35 | 36 | ## [v5.0.0](https://github.com/purescript/purescript-strings/releases/tag/v5.0.0) - 2021-02-26 37 | 38 | Breaking changes: 39 | - Added support for PureScript 0.14 and dropped support for all previous versions (#129) 40 | - Updated `replace'` to reflect the existence of optional capturing groups (#126) 41 | 42 | New features: 43 | - Replaced `unsafeCoerce` with `coerce` where appropriate (#130) 44 | - Replaced monomorphic proxies with `Type.Proxy.Proxy` and polymorphic variables (#134) 45 | - Added a dotAll regexp flag (#133) 46 | 47 | Bugfixes: 48 | - Removed the bounds check from the foreign implementation of `lastIndexOf'` (#137) 49 | 50 | Other improvements: 51 | - Fix line endings to match overall project style (#132) 52 | - Removed references to `codePointToInt`, which no longer exists (#135) 53 | - Migrated CI to GitHub Actions and updated installation instructions to use Spago (#136) 54 | - Added a changelog and pull request template (#140, #141) 55 | 56 | ## [v4.0.2](https://github.com/purescript/purescript-strings/releases/tag/v4.0.2) - 2020-05-13 57 | 58 | - Improved performance for `stripPrefix` / `stripSuffix` (#123, @michaelficarra) 59 | 60 | ## [v4.0.1](https://github.com/purescript/purescript-strings/releases/tag/v4.0.1) - 2018-11-11 61 | 62 | - Fixed out of bounds access in `unsafeCodePointAt0Fallback` (@zyla) 63 | - Fixed `slice` when end index equals string length (@abaco) 64 | 65 | ## [v4.0.0](https://github.com/purescript/purescript-strings/releases/tag/v4.0.0) - 2018-05-23 66 | 67 | - Updated for PureScript 0.12 68 | - `splitAt` now always returns a value (#78, @MonoidMusician) 69 | - Added `slice` (@themattchan) 70 | - Added more `String` `Gen`s to correspond with `Char` `Gen`s (@matthewleon) 71 | - `Regex` `match` now returns `NonEmptyArray` 72 | - All string functions now operate on code points now rather than code units. The old functions are available via the `.CodeUnits` modules 73 | - `fromCharCode` can return `Nothing` now if given a value out of range 74 | 75 | ## [v3.5.0](https://github.com/purescript/purescript-strings/releases/tag/v3.5.0) - 2018-02-12 76 | 77 | - Added `Data.String.NonEmpty` 78 | 79 | ## [v3.4.0](https://github.com/purescript/purescript-strings/releases/tag/v3.4.0) - 2017-12-28 80 | 81 | - Add `Show CodePoint` instance (@csicar) 82 | - Add `codePointFromChar` (@csicar) 83 | - Expanded docs for most functions in `Data.String` and `Data.String.CodePoints` (@csicar) 84 | 85 | ## [v3.3.2](https://github.com/purescript/purescript-strings/releases/tag/v3.3.2) - 2017-11-19 86 | 87 | - Performance improvement in `Data.String.Regex.match` (@fehrenbach) 88 | 89 | ## [v3.3.1](https://github.com/purescript/purescript-strings/releases/tag/v3.3.1) - 2017-08-06 90 | 91 | - Fix some `Show` instances (@Rufflewind) 92 | 93 | ## [v3.3.0](https://github.com/purescript/purescript-strings/releases/tag/v3.3.0) - 2017-07-10 94 | 95 | - Add a new module `Data.String.CodePoints`, which treats strings as sequences of Unicode code points rather than sequences of UTF-16 code units. In the future we may swap this module with `Data.String`. (@michaelficarra) 96 | - Fix a typo in the documentation (@ijks) 97 | 98 | ## [v3.2.1](https://github.com/purescript/purescript-strings/releases/tag/v3.2.1) - 2017-06-06 99 | 100 | - Ensure `genString` behaves the same regardless of the `MonadGen` implementation of `chooseInt` when `max < min` 101 | 102 | ## [v3.2.0](https://github.com/purescript/purescript-strings/releases/tag/v3.2.0) - 2017-06-05 103 | 104 | - Generated strings from `genString` now vary in length 105 | - Added additional `Char` generators 106 | 107 | ## [v3.1.0](https://github.com/purescript/purescript-strings/releases/tag/v3.1.0) - 2017-04-28 108 | 109 | - Added some generator functions - introduced `Data.String.Gen` and `Data.Char.Gen` 110 | 111 | ## [v3.0.0](https://github.com/purescript/purescript-strings/releases/tag/v3.0.0) - 2017-03-26 112 | 113 | - Updated for PureScript 0.11 114 | 115 | ## [v2.1.0](https://github.com/purescript/purescript-strings/releases/tag/v2.1.0) - 2016-12-25 116 | 117 | - Added `unsafeRegex` (@rightfold) 118 | 119 | ## [v2.0.2](https://github.com/purescript/purescript-strings/releases/tag/v2.0.2) - 2016-10-26 120 | 121 | - Documentation fix for `split` #70 (@leighman) 122 | 123 | ## [v2.0.1](https://github.com/purescript/purescript-strings/releases/tag/v2.0.1) - 2016-10-08 124 | 125 | - Improved `null` check implementation (@Risto-Stevcev) 126 | 127 | ## [v2.0.0](https://github.com/purescript/purescript-strings/releases/tag/v2.0.0) - 2016-10-08 128 | 129 | - Updated dependencies 130 | - `Pattern` and `Replacement` newtypes are now used to distinguish between arguments when a function accepts multiple strings 131 | - `RegexFlags` have been reworked as a monoid (@Risto-Stevcev) 132 | 133 | ## [v1.1.0](https://github.com/purescript/purescript-strings/releases/tag/v1.1.0) - 2016-07-20 134 | 135 | - Restored export of the `count` function. 136 | 137 | ## [v1.0.0](https://github.com/purescript/purescript-strings/releases/tag/v1.0.0) - 2016-06-01 138 | 139 | This release is intended for the PureScript 0.9.1 compiler and newer. 140 | 141 | **Note**: The v1.0.0 tag is not meant to indicate the library is “finished”, the core libraries are all being bumped to this for the 0.9 compiler release so as to use semver more correctly. 142 | 143 | ## [v0.7.1](https://github.com/purescript/purescript-strings/releases/tag/v0.7.1) - 2015-11-20 144 | 145 | - Removed unused imports (@tfausak) 146 | 147 | ## [v0.7.0](https://github.com/purescript/purescript-strings/releases/tag/v0.7.0) - 2015-08-13 148 | 149 | - Removed orphan (and incorrect) `Bounded Char` instance 150 | 151 | ## [v0.6.0](https://github.com/purescript/purescript-strings/releases/tag/v0.6.0) - 2015-08-02 152 | 153 | - Added `toLower` and `toUpper` to `Data.Char` 154 | - `search` in `Data.String.Regex` now returns `Maybe` result rather than using -1 for failure 155 | - Added test suite 156 | 157 | All updates by @LiamGoodacre 158 | 159 | ## [v0.5.5](https://github.com/purescript/purescript-strings/releases/tag/v0.5.5) - 2015-07-28 160 | 161 | Add `stripSuffix`. 162 | 163 | ## [v0.5.4](https://github.com/purescript/purescript-strings/releases/tag/v0.5.4) - 2015-07-18 164 | 165 | - Removed duplicate `Show` instance for `Char` (@anttih) 166 | 167 | ## [v0.5.3](https://github.com/purescript/purescript-strings/releases/tag/v0.5.3) - 2015-07-10 168 | 169 | Add `stripPrefix` (@hdgarrood) 170 | 171 | ## [v0.5.2](https://github.com/purescript/purescript-strings/releases/tag/v0.5.2) - 2015-07-07 172 | 173 | - Fixed `char` and `charCodeAt` in `Data.String.Unsafe` #36 (@stkb) 174 | 175 | ## [v0.5.1](https://github.com/purescript/purescript-strings/releases/tag/v0.5.1) - 2015-07-06 176 | 177 | - Fixed missing `count` implementation (@qxjit) 178 | 179 | ## [v0.5.0](https://github.com/purescript/purescript-strings/releases/tag/v0.5.0) - 2015-06-30 180 | 181 | This release works with versions 0.7.\* of the PureScript compiler. It will not work with older versions. If you are using an older version, you should require an older, compatible version of this library. 182 | 183 | - Fixed various FFI exports (@sharkdp) 184 | - Fixed `localeCompare` 185 | 186 | ## [v0.4.5](https://github.com/purescript/purescript-strings/releases/tag/v0.4.5) - 2015-03-23 187 | 188 | - Added `char` to `Data.String.Unsafe` (@brainrape) 189 | - Functions in `Data.String.Unsafe` now throw errors immediately when given unacceptable inputs (@brainrape) 190 | 191 | ## [v0.4.4](https://github.com/purescript/purescript-strings/releases/tag/v0.4.4) - 2015-03-22 192 | 193 | - Updated docs 194 | 195 | ## [v0.4.3](https://github.com/purescript/purescript-strings/releases/tag/v0.4.3) - 2015-02-18 196 | 197 | - Added `noFlags` record for default regex flags (@fresheyeball) 198 | 199 | ## [v0.4.2](https://github.com/purescript/purescript-strings/releases/tag/v0.4.2) - 2014-11-28 200 | 201 | - Added `null`, `singleton`, `uncons`, `takeWhile`, and `dropWhile` to `Data.String` (@NightRa) 202 | 203 | ## [v0.4.1](https://github.com/purescript/purescript-strings/releases/tag/v0.4.1) - 2014-11-06 204 | 205 | - Use ternary operator in JavaScript output (@davidchambers) 206 | 207 | ## [v0.4.0](https://github.com/purescript/purescript-strings/releases/tag/v0.4.0) - 2014-10-27 208 | 209 | - Made `charCodeAt` safe, added unsafe versions of `charAt`, `charCodeAt` (@garyb) 210 | 211 | ## [v0.3.3](https://github.com/purescript/purescript-strings/releases/tag/v0.3.3) - 2014-10-24 212 | 213 | - Added `split` to `Data.String.Regex` (@davidchambers) 214 | 215 | ## [v0.3.2](https://github.com/purescript/purescript-strings/releases/tag/v0.3.2) - 2014-10-16 216 | 217 | - Added essential instances for `Char` (@jdegoes) 218 | 219 | ## [v0.3.1](https://github.com/purescript/purescript-strings/releases/tag/v0.3.1) - 2014-10-15 220 | 221 | - Fixed typo in `fromCharArray` FFI implementation (@jdegoes) 222 | 223 | ## [v0.3.0](https://github.com/purescript/purescript-strings/releases/tag/v0.3.0) - 2014-10-14 224 | 225 | - Introduced `Char` newtype and corresponding functions (@jdegoes) 226 | - Made `charAt` safe - breaking change (@jdegoes) 227 | 228 | ## [v0.2.1](https://github.com/purescript/purescript-strings/releases/tag/v0.2.1) - 2014-07-21 229 | 230 | - Fix typo in FFI definition for `flags` (@garyb) 231 | 232 | ## [v0.2.0](https://github.com/purescript/purescript-strings/releases/tag/v0.2.0) - 2014-07-20 233 | 234 | - `Show` instance for `Regex` (@michaelficarra) 235 | - `Regex` now has `RegexFlags` rather than a string for options (@michaelficarra) 236 | 237 | ## [v0.1.3](https://github.com/purescript/purescript-strings/releases/tag/v0.1.3) - 2014-05-04 238 | 239 | - Renamed `Data.String.Regex.replaceR` to `replace`, added `replace'` which uses a function to construct replacements for matches. 240 | 241 | ## [v0.1.2](https://github.com/purescript/purescript-strings/releases/tag/v0.1.2) - 2014-04-30 242 | 243 | - Added `indexOf'` and `lastIndexOf'` (paf31) 244 | 245 | ## [v0.1.1](https://github.com/purescript/purescript-strings/releases/tag/v0.1.1) - 2014-04-27 246 | 247 | - Swapped `joinWith` arguments for better style 248 | 249 | ## [v0.1.0](https://github.com/purescript/purescript-strings/releases/tag/v0.1.0) - 2014-04-25 250 | 251 | - Initial release 252 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2018 PureScript 2 | 3 | Redistribution and use in source and binary forms, with or without modification, 4 | are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, this 7 | list of conditions and the following disclaimer. 8 | 9 | 2. Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation and/or 11 | other materials provided with the distribution. 12 | 13 | 3. Neither the name of the copyright holder nor the names of its contributors 14 | may be used to endorse or promote products derived from this software without 15 | specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 18 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 19 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 21 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 22 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 23 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 24 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 26 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # purescript-strings 2 | 3 | [![Latest release](http://img.shields.io/github/release/purescript/purescript-strings.svg)](https://github.com/purescript/purescript-strings/releases) 4 | [![Build status](https://github.com/purescript/purescript-strings/workflows/CI/badge.svg?branch=master)](https://github.com/purescript/purescript-strings/actions?query=workflow%3ACI+branch%3Amaster) 5 | [![Pursuit](https://pursuit.purescript.org/packages/purescript-strings/badge)](https://pursuit.purescript.org/packages/purescript-strings) 6 | 7 | String and char utility functions, regular expressions. 8 | 9 | ## Installation 10 | 11 | ``` 12 | spago install strings 13 | ``` 14 | 15 | ## Documentation 16 | 17 | Module documentation is [published on Pursuit](http://pursuit.purescript.org/packages/purescript-strings). 18 | -------------------------------------------------------------------------------- /bench/Main.purs: -------------------------------------------------------------------------------- 1 | module Bench.Main where 2 | 3 | import Prelude 4 | 5 | import Control.Monad.Eff (Eff) 6 | import Control.Monad.Eff.Console (CONSOLE, log) 7 | import Data.Array.NonEmpty (fromArray) 8 | import Data.Maybe (fromJust) 9 | import Data.String (toCharArray) 10 | import Data.String.NonEmpty (fromFoldable1, fromNonEmptyCharArray) 11 | import Partial.Unsafe (unsafePartial) 12 | import Performance.Minibench (benchWith) 13 | 14 | main :: Eff (console :: CONSOLE) Unit 15 | main = do 16 | log "NonEmpty conversions" 17 | log "======" 18 | log "" 19 | benchNonEmptyConversions 20 | 21 | benchNonEmptyConversions :: Eff (console :: CONSOLE) Unit 22 | benchNonEmptyConversions = do 23 | log "fromNonEmptyCharArray: short" 24 | log "---" 25 | benchFromNonEmptyCharArray 26 | log "" 27 | 28 | log "fromFoldable1" 29 | log "---" 30 | benchFromFoldable1 31 | log "" 32 | 33 | where 34 | 35 | benchFromNonEmptyCharArray = do 36 | log "short string" 37 | bench \_ -> fromNonEmptyCharArray shortStringArr 38 | 39 | log "long string" 40 | bench \_ -> fromNonEmptyCharArray longStringArr 41 | 42 | benchFromFoldable1 = do 43 | log "short string" 44 | bench \_ -> fromFoldable1 shortStringArr 45 | 46 | log "long string" 47 | bench \_ -> fromFoldable1 longStringArr 48 | 49 | shortStringArr = unsafePartial fromJust $ fromArray 50 | $ toCharArray "supercalifragilisticexpialidocious" 51 | longStringArr = unsafePartial fromJust $ fromArray 52 | $ toCharArray loremIpsum 53 | 54 | bench = benchWith 100000 55 | 56 | loremIpsum :: String 57 | loremIpsum = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut aliquet euismod ligula, vitae lacinia lorem imperdiet nec. Nulla volutpat ullamcorper mollis. Proin interdum quam a sem auctor, id tempus nisl pretium. Suspendisse potenti. Quisque ut libero consequat, suscipit sem a, malesuada nisi. Aliquam dictum odio mi, eu laoreet felis scelerisque non. Ut in odio vehicula, cursus augue sed, tincidunt lorem. Vestibulum consequat lectus eu commodo vulputate. Nam vitae faucibus ipsum. Curabitur sit amet neque sed est sagittis vehicula nec nec risus. Phasellus consectetur cursus malesuada. Vestibulum commodo lorem ut mauris mollis faucibus. Integer ut massa auctor, scelerisque nisi nec, rutrum nisl. Integer vel ex sem. Sed purus felis, molestie eget cursus vel, maximus ut augue. Curabitur nunc ligula, lobortis vitae vehicula a, volutpat nec sem. Phasellus non sapien ipsum. Mauris dolor justo, mollis at elit a, sollicitudin commodo quam. Curabitur posuere felis at nunc pharetra, eu convallis lectus dapibus. Aliquam ullamcorper porta fermentum. Donec at tellus metus. Donec pharetra tempor odio sit amet viverra. Nam vel metus libero. Vivamus maximus quis lacus id pharetra. Duis sed diam molestie, sodales leo id, pulvinar justo. In non augue tempor risus consectetur hendrerit. In libero nulla, elementum non ultrices eu, vehicula non ipsum. Maecenas in hendrerit tellus, sodales dignissim turpis. Ut odio diam, convallis in elit non, consequat gravida nisi. Cras egestas metus eleifend sapien efficitur, vel vulputate est porta. Aliquam posuere, magna nec bibendum luctus, quam risus efficitur sapien, id volutpat metus ex non lorem. Praesent velit eros, efficitur sed tortor quis, lobortis eleifend ligula. Sed tellus quam, aliquet vitae sagittis a, egestas eget massa. Etiam odio elit, hendrerit vel dui vel, fermentum pharetra neque. Curabitur quis mauris id lacus consectetur rhoncus non nec mauris. Mauris blandit tempor pretium. Donec non nisi finibus, lobortis dolor vitae, euismod arcu. Nullam scelerisque lacus in dolor volutpat mollis. Nunc vitae consectetur ligula, quis laoreet quam.Proin sit amet nisi eu orci hendrerit imperdiet vitae sit amet leo. Donec sodales id ante eget viverra. Nullam vitae elit in mauris accumsan feugiat id a velit. Nulla facilisi. Cras in turpis efficitur, consectetur justo quis, suscipit tortor. Sed tincidunt pellentesque sapien, in ultricies eros rhoncus sit amet. Integer blandit ornare lobortis. Duis dictum sit amet mauris sit amet cursus. Nullam nec nisl mauris. Praesent cursus imperdiet mi mattis luctus. Donec in tortor fermentum, efficitur turpis vel, facilisis augue. Integer egestas nisl et magna volutpat ornare. Donec pulvinar risus elit, eget viverra est feugiat in.Ut nec ante vestibulum neque pulvinar pretium sit amet eu nisi. Aliquam erat volutpat. Maecenas egestas nisi et mi congue, sed ultricies nibh posuere. Suspendisse potenti. Donec a nulla et velit elementum pretium. Pellentesque gravida imperdiet sem et varius. Praesent ac diam diam. Donec iaculis risus ex, ac eleifend sapien luctus ut. Fusce aliquet, lacus tincidunt porta malesuada, massa augue commodo nulla, ac malesuada tortor est sed eros. Praesent mattis, nisi eget ullamcorper vestibulum, lacus ante placerat metus, ac ullamcorper ante tellus vel nulla. Praesent vehicula in est sit amet varius. Sed facilisis felis sed sem porttitor rutrum. Etiam sollicitudin erat neque, id gravida metus scelerisque quis. Proin venenatis pharetra lectus ac auctor." 58 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "purescript-strings", 3 | "homepage": "https://github.com/purescript/purescript-strings", 4 | "license": "BSD-3-Clause", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/purescript/purescript-strings.git" 8 | }, 9 | "ignore": [ 10 | "**/.*", 11 | "bower_components", 12 | "node_modules", 13 | "output", 14 | "test", 15 | "bower.json", 16 | "package.json" 17 | ], 18 | "dependencies": { 19 | "purescript-arrays": "^7.0.0", 20 | "purescript-control": "^6.0.0", 21 | "purescript-either": "^6.0.0", 22 | "purescript-enums": "^6.0.1", 23 | "purescript-foldable-traversable": "^6.0.0", 24 | "purescript-gen": "^4.0.0", 25 | "purescript-integers": "^6.0.0", 26 | "purescript-maybe": "^6.0.0", 27 | "purescript-newtype": "^5.0.0", 28 | "purescript-nonempty": "^7.0.0", 29 | "purescript-partial": "^4.0.0", 30 | "purescript-prelude": "^6.0.0", 31 | "purescript-tailrec": "^6.0.0", 32 | "purescript-tuples": "^7.0.0", 33 | "purescript-unfoldable": "^6.0.0", 34 | "purescript-unsafe-coerce": "^6.0.0" 35 | }, 36 | "devDependencies": { 37 | "purescript-assert": "^6.0.0", 38 | "purescript-console": "^6.0.0", 39 | "purescript-minibench": "^4.0.0" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "clean": "rimraf output && rimraf .pulp-cache", 5 | "build": "eslint src && pulp build -- --censor-lib --strict", 6 | "test": "pulp test && npm run test:run:without_codePointAt", 7 | "test:run:without_codePointAt": "node -e \"delete String.prototype.codePointAt; import('./output/Test.Main/index.js').then(m => m.main());\"", 8 | "bench:build": "purs compile 'bench/**/*.purs' 'src/**/*.purs' 'bower_components/*/src/**/*.purs'", 9 | "bench:run": "node --expose-gc -e 'require(\"./output/Bench.Main/index.js\").main()'", 10 | "bench": "npm run bench:build && npm run bench:run" 11 | }, 12 | "devDependencies": { 13 | "eslint": "^7.15.0", 14 | "pulp": "16.0.0-0", 15 | "purescript-psa": "^0.8.2", 16 | "rimraf": "^3.0.2" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Data/Char.purs: -------------------------------------------------------------------------------- 1 | -- | A type and functions for single characters. 2 | module Data.Char 3 | ( toCharCode 4 | , fromCharCode 5 | ) where 6 | 7 | import Data.Enum (fromEnum, toEnum) 8 | import Data.Maybe (Maybe) 9 | 10 | -- | Returns the numeric Unicode value of the character. 11 | toCharCode :: Char -> Int 12 | toCharCode = fromEnum 13 | 14 | -- | Constructs a character from the given Unicode numeric value. 15 | fromCharCode :: Int -> Maybe Char 16 | fromCharCode = toEnum 17 | -------------------------------------------------------------------------------- /src/Data/Char/Gen.purs: -------------------------------------------------------------------------------- 1 | module Data.Char.Gen where 2 | 3 | import Prelude 4 | 5 | import Control.Monad.Gen (class MonadGen, chooseInt, oneOf) 6 | import Data.Enum (toEnumWithDefaults) 7 | import Data.NonEmpty ((:|)) 8 | 9 | -- | Generates a character of the Unicode basic multilingual plane. 10 | genUnicodeChar :: forall m. MonadGen m => m Char 11 | genUnicodeChar = toEnumWithDefaults bottom top <$> chooseInt 0 65536 12 | 13 | -- | Generates a character in the ASCII character set, excluding control codes. 14 | genAsciiChar :: forall m. MonadGen m => m Char 15 | genAsciiChar = toEnumWithDefaults bottom top <$> chooseInt 32 127 16 | 17 | -- | Generates a character in the ASCII character set. 18 | genAsciiChar' :: forall m. MonadGen m => m Char 19 | genAsciiChar' = toEnumWithDefaults bottom top <$> chooseInt 0 127 20 | 21 | -- | Generates a character that is a numeric digit. 22 | genDigitChar :: forall m. MonadGen m => m Char 23 | genDigitChar = toEnumWithDefaults bottom top <$> chooseInt 48 57 24 | 25 | -- | Generates a character from the basic latin alphabet. 26 | genAlpha :: forall m. MonadGen m => m Char 27 | genAlpha = oneOf (genAlphaLowercase :| [genAlphaUppercase]) 28 | 29 | -- | Generates a lowercase character from the basic latin alphabet. 30 | genAlphaLowercase :: forall m. MonadGen m => m Char 31 | genAlphaLowercase = toEnumWithDefaults bottom top <$> chooseInt 97 122 32 | 33 | -- | Generates an uppercase character from the basic latin alphabet. 34 | genAlphaUppercase :: forall m. MonadGen m => m Char 35 | genAlphaUppercase = toEnumWithDefaults bottom top <$> chooseInt 65 90 36 | -------------------------------------------------------------------------------- /src/Data/String.purs: -------------------------------------------------------------------------------- 1 | module Data.String 2 | ( module Data.String.Common 3 | , module Data.String.CodePoints 4 | , module Data.String.Pattern 5 | ) where 6 | 7 | import Data.String.CodePoints 8 | 9 | import Data.String.Common (joinWith, localeCompare, null, replace, replaceAll, split, toLower, toUpper, trim) 10 | import Data.String.Pattern (Pattern(..), Replacement(..)) 11 | -------------------------------------------------------------------------------- /src/Data/String/CaseInsensitive.purs: -------------------------------------------------------------------------------- 1 | module Data.String.CaseInsensitive where 2 | 3 | import Prelude 4 | 5 | import Data.Newtype (class Newtype) 6 | import Data.String (toLower) 7 | 8 | -- | A newtype for case insensitive string comparisons and ordering. 9 | newtype CaseInsensitiveString = CaseInsensitiveString String 10 | 11 | instance eqCaseInsensitiveString :: Eq CaseInsensitiveString where 12 | eq (CaseInsensitiveString s1) (CaseInsensitiveString s2) = 13 | toLower s1 == toLower s2 14 | 15 | instance ordCaseInsensitiveString :: Ord CaseInsensitiveString where 16 | compare (CaseInsensitiveString s1) (CaseInsensitiveString s2) = 17 | compare (toLower s1) (toLower s2) 18 | 19 | instance showCaseInsensitiveString :: Show CaseInsensitiveString where 20 | show (CaseInsensitiveString s) = "(CaseInsensitiveString " <> show s <> ")" 21 | 22 | derive instance newtypeCaseInsensitiveString :: Newtype CaseInsensitiveString _ 23 | -------------------------------------------------------------------------------- /src/Data/String/CodePoints.js: -------------------------------------------------------------------------------- 1 | /* global Symbol */ 2 | 3 | var hasArrayFrom = typeof Array.from === "function"; 4 | var hasStringIterator = 5 | typeof Symbol !== "undefined" && 6 | Symbol != null && 7 | typeof Symbol.iterator !== "undefined" && 8 | typeof String.prototype[Symbol.iterator] === "function"; 9 | var hasFromCodePoint = typeof String.prototype.fromCodePoint === "function"; 10 | var hasCodePointAt = typeof String.prototype.codePointAt === "function"; 11 | 12 | export const _unsafeCodePointAt0 = function (fallback) { 13 | return hasCodePointAt 14 | ? function (str) { return str.codePointAt(0); } 15 | : fallback; 16 | }; 17 | 18 | export const _codePointAt = function (fallback) { 19 | return function (Just) { 20 | return function (Nothing) { 21 | return function (unsafeCodePointAt0) { 22 | return function (index) { 23 | return function (str) { 24 | var length = str.length; 25 | if (index < 0 || index >= length) return Nothing; 26 | if (hasStringIterator) { 27 | var iter = str[Symbol.iterator](); 28 | for (var i = index;; --i) { 29 | var o = iter.next(); 30 | if (o.done) return Nothing; 31 | if (i === 0) return Just(unsafeCodePointAt0(o.value)); 32 | } 33 | } 34 | return fallback(index)(str); 35 | }; 36 | }; 37 | }; 38 | }; 39 | }; 40 | }; 41 | 42 | export const _countPrefix = function (fallback) { 43 | return function (unsafeCodePointAt0) { 44 | if (hasStringIterator) { 45 | return function (pred) { 46 | return function (str) { 47 | var iter = str[Symbol.iterator](); 48 | for (var cpCount = 0; ; ++cpCount) { 49 | var o = iter.next(); 50 | if (o.done) return cpCount; 51 | var cp = unsafeCodePointAt0(o.value); 52 | if (!pred(cp)) return cpCount; 53 | } 54 | }; 55 | }; 56 | } 57 | return fallback; 58 | }; 59 | }; 60 | 61 | export const _fromCodePointArray = function (singleton) { 62 | return hasFromCodePoint 63 | ? function (cps) { 64 | // Function.prototype.apply will fail for very large second parameters, 65 | // so we don't use it for arrays with 10,000 or more entries. 66 | if (cps.length < 10e3) { 67 | return String.fromCodePoint.apply(String, cps); 68 | } 69 | return cps.map(singleton).join(""); 70 | } 71 | : function (cps) { 72 | return cps.map(singleton).join(""); 73 | }; 74 | }; 75 | 76 | export const _singleton = function (fallback) { 77 | return hasFromCodePoint ? String.fromCodePoint : fallback; 78 | }; 79 | 80 | export const _take = function (fallback) { 81 | return function (n) { 82 | if (hasStringIterator) { 83 | return function (str) { 84 | var accum = ""; 85 | var iter = str[Symbol.iterator](); 86 | for (var i = 0; i < n; ++i) { 87 | var o = iter.next(); 88 | if (o.done) return accum; 89 | accum += o.value; 90 | } 91 | return accum; 92 | }; 93 | } 94 | return fallback(n); 95 | }; 96 | }; 97 | 98 | export const _toCodePointArray = function (fallback) { 99 | return function (unsafeCodePointAt0) { 100 | if (hasArrayFrom) { 101 | return function (str) { 102 | return Array.from(str, unsafeCodePointAt0); 103 | }; 104 | } 105 | return fallback; 106 | }; 107 | }; 108 | -------------------------------------------------------------------------------- /src/Data/String/CodePoints.purs: -------------------------------------------------------------------------------- 1 | -- | These functions allow PureScript strings to be treated as if they were 2 | -- | sequences of Unicode code points instead of their true underlying 3 | -- | implementation (sequences of UTF-16 code units). For nearly all uses of 4 | -- | strings, these functions should be preferred over the ones in 5 | -- | `Data.String.CodeUnits`. 6 | module Data.String.CodePoints 7 | ( module Exports 8 | , CodePoint 9 | , codePointFromChar 10 | , singleton 11 | , fromCodePointArray 12 | , toCodePointArray 13 | , codePointAt 14 | , uncons 15 | , length 16 | , countPrefix 17 | , indexOf 18 | , indexOf' 19 | , lastIndexOf 20 | , lastIndexOf' 21 | , take 22 | -- , takeRight 23 | , takeWhile 24 | , drop 25 | -- , dropRight 26 | , dropWhile 27 | -- , slice 28 | , splitAt 29 | ) where 30 | 31 | import Prelude 32 | 33 | import Data.Array as Array 34 | import Data.Enum (class BoundedEnum, class Enum, Cardinality(..), defaultPred, defaultSucc, fromEnum, toEnum, toEnumWithDefaults) 35 | import Data.Int (hexadecimal, toStringAs) 36 | import Data.Maybe (Maybe(..)) 37 | import Data.String.CodeUnits (contains, stripPrefix, stripSuffix) as Exports 38 | import Data.String.CodeUnits as CU 39 | import Data.String.Common (toUpper) 40 | import Data.String.Pattern (Pattern) 41 | import Data.String.Unsafe as Unsafe 42 | import Data.Tuple (Tuple(..)) 43 | import Data.Unfoldable (unfoldr) 44 | 45 | -- | CodePoint is an `Int` bounded between `0` and `0x10FFFF`, corresponding to 46 | -- | Unicode code points. 47 | newtype CodePoint = CodePoint Int 48 | 49 | derive instance eqCodePoint :: Eq CodePoint 50 | derive instance ordCodePoint :: Ord CodePoint 51 | 52 | instance showCodePoint :: Show CodePoint where 53 | show (CodePoint i) = "(CodePoint 0x" <> toUpper (toStringAs hexadecimal i) <> ")" 54 | 55 | instance boundedCodePoint :: Bounded CodePoint where 56 | bottom = CodePoint 0 57 | top = CodePoint 0x10FFFF 58 | 59 | instance enumCodePoint :: Enum CodePoint where 60 | succ = defaultSucc toEnum fromEnum 61 | pred = defaultPred toEnum fromEnum 62 | 63 | instance boundedEnumCodePoint :: BoundedEnum CodePoint where 64 | cardinality = Cardinality (0x10FFFF + 1) 65 | fromEnum (CodePoint n) = n 66 | toEnum n 67 | | n >= 0 && n <= 0x10FFFF = Just (CodePoint n) 68 | | otherwise = Nothing 69 | 70 | -- | Creates a `CodePoint` from a given `Char`. 71 | -- | 72 | -- | ```purescript 73 | -- | >>> codePointFromChar 'B' 74 | -- | CodePoint 0x42 -- represents 'B' 75 | -- | ``` 76 | -- | 77 | codePointFromChar :: Char -> CodePoint 78 | codePointFromChar = fromEnum >>> CodePoint 79 | 80 | -- | Creates a string containing just the given code point. Operates in 81 | -- | constant space and time. 82 | -- | 83 | -- | ```purescript 84 | -- | >>> map singleton (toEnum 0x1D400) 85 | -- | Just "𝐀" 86 | -- | ``` 87 | -- | 88 | singleton :: CodePoint -> String 89 | singleton = _singleton singletonFallback 90 | 91 | foreign import _singleton 92 | :: (CodePoint -> String) 93 | -> CodePoint 94 | -> String 95 | 96 | singletonFallback :: CodePoint -> String 97 | singletonFallback (CodePoint cp) | cp <= 0xFFFF = fromCharCode cp 98 | singletonFallback (CodePoint cp) = 99 | let lead = ((cp - 0x10000) / 0x400) + 0xD800 in 100 | let trail = (cp - 0x10000) `mod` 0x400 + 0xDC00 in 101 | fromCharCode lead <> fromCharCode trail 102 | 103 | -- | Creates a string from an array of code points. Operates in space and time 104 | -- | linear to the length of the array. 105 | -- | 106 | -- | ```purescript 107 | -- | >>> codePointArray = toCodePointArray "c 𝐀" 108 | -- | >>> codePointArray 109 | -- | [CodePoint 0x63, CodePoint 0x20, CodePoint 0x1D400] 110 | -- | >>> fromCodePointArray codePointArray 111 | -- | "c 𝐀" 112 | -- | ``` 113 | -- | 114 | fromCodePointArray :: Array CodePoint -> String 115 | fromCodePointArray = _fromCodePointArray singletonFallback 116 | 117 | foreign import _fromCodePointArray 118 | :: (CodePoint -> String) 119 | -> Array CodePoint 120 | -> String 121 | 122 | -- | Creates an array of code points from a string. Operates in space and time 123 | -- | linear to the length of the string. 124 | -- | 125 | -- | ```purescript 126 | -- | >>> codePointArray = toCodePointArray "b 𝐀𝐀" 127 | -- | >>> codePointArray 128 | -- | [CodePoint 0x62, CodePoint 0x20, CodePoint 0x1D400, CodePoint 0x1D400] 129 | -- | >>> map singleton codePointArray 130 | -- | ["b", " ", "𝐀", "𝐀"] 131 | -- | ``` 132 | -- | 133 | toCodePointArray :: String -> Array CodePoint 134 | toCodePointArray = _toCodePointArray toCodePointArrayFallback unsafeCodePointAt0 135 | 136 | foreign import _toCodePointArray 137 | :: (String -> Array CodePoint) 138 | -> (String -> CodePoint) 139 | -> String 140 | -> Array CodePoint 141 | 142 | toCodePointArrayFallback :: String -> Array CodePoint 143 | toCodePointArrayFallback s = unfoldr unconsButWithTuple s 144 | 145 | unconsButWithTuple :: String -> Maybe (Tuple CodePoint String) 146 | unconsButWithTuple s = (\{ head, tail } -> Tuple head tail) <$> uncons s 147 | 148 | -- | Returns the first code point of the string after dropping the given number 149 | -- | of code points from the beginning, if there is such a code point. Operates 150 | -- | in constant space and in time linear to the given index. 151 | -- | 152 | -- | ```purescript 153 | -- | >>> codePointAt 1 "𝐀𝐀𝐀𝐀" 154 | -- | Just (CodePoint 0x1D400) -- represents "𝐀" 155 | -- | -- compare to Data.String: 156 | -- | >>> charAt 1 "𝐀𝐀𝐀𝐀" 157 | -- | Just '�' 158 | -- | ``` 159 | -- | 160 | codePointAt :: Int -> String -> Maybe CodePoint 161 | codePointAt n _ | n < 0 = Nothing 162 | codePointAt 0 "" = Nothing 163 | codePointAt 0 s = Just (unsafeCodePointAt0 s) 164 | codePointAt n s = _codePointAt codePointAtFallback Just Nothing unsafeCodePointAt0 n s 165 | 166 | foreign import _codePointAt 167 | :: (Int -> String -> Maybe CodePoint) 168 | -> (forall a. a -> Maybe a) 169 | -> (forall a. Maybe a) 170 | -> (String -> CodePoint) 171 | -> Int 172 | -> String 173 | -> Maybe CodePoint 174 | 175 | codePointAtFallback :: Int -> String -> Maybe CodePoint 176 | codePointAtFallback n s = case uncons s of 177 | Just { head, tail } -> if n == 0 then Just head else codePointAtFallback (n - 1) tail 178 | _ -> Nothing 179 | 180 | -- | Returns a record with the first code point and the remaining code points 181 | -- | of the string. Returns `Nothing` if the string is empty. Operates in 182 | -- | constant space and time. 183 | -- | 184 | -- | ```purescript 185 | -- | >>> uncons "𝐀𝐀 c 𝐀" 186 | -- | Just { head: CodePoint 0x1D400, tail: "𝐀 c 𝐀" } 187 | -- | >>> uncons "" 188 | -- | Nothing 189 | -- | ``` 190 | -- | 191 | uncons :: String -> Maybe { head :: CodePoint, tail :: String } 192 | uncons s = case CU.length s of 193 | 0 -> Nothing 194 | 1 -> Just { head: CodePoint (fromEnum (Unsafe.charAt 0 s)), tail: "" } 195 | _ -> 196 | let 197 | cu0 = fromEnum (Unsafe.charAt 0 s) 198 | cu1 = fromEnum (Unsafe.charAt 1 s) 199 | in 200 | if isLead cu0 && isTrail cu1 201 | then Just { head: unsurrogate cu0 cu1, tail: CU.drop 2 s } 202 | else Just { head: CodePoint cu0, tail: CU.drop 1 s } 203 | 204 | -- | Returns the number of code points in the string. Operates in constant 205 | -- | space and in time linear to the length of the string. 206 | -- | 207 | -- | ```purescript 208 | -- | >>> length "b 𝐀𝐀 c 𝐀" 209 | -- | 8 210 | -- | -- compare to Data.String: 211 | -- | >>> length "b 𝐀𝐀 c 𝐀" 212 | -- | 11 213 | -- | ``` 214 | -- | 215 | length :: String -> Int 216 | length = Array.length <<< toCodePointArray 217 | 218 | -- | Returns the number of code points in the leading sequence of code points 219 | -- | which all match the given predicate. Operates in constant space and in 220 | -- | time linear to the length of the string. 221 | -- | 222 | -- | ```purescript 223 | -- | >>> countPrefix (\c -> fromEnum c == 0x1D400) "𝐀𝐀 b c 𝐀" 224 | -- | 2 225 | -- | ``` 226 | -- | 227 | countPrefix :: (CodePoint -> Boolean) -> String -> Int 228 | countPrefix = _countPrefix countFallback unsafeCodePointAt0 229 | 230 | foreign import _countPrefix 231 | :: ((CodePoint -> Boolean) -> String -> Int) 232 | -> (String -> CodePoint) 233 | -> (CodePoint -> Boolean) 234 | -> String 235 | -> Int 236 | 237 | countFallback :: (CodePoint -> Boolean) -> String -> Int 238 | countFallback p s = countTail p s 0 239 | 240 | countTail :: (CodePoint -> Boolean) -> String -> Int -> Int 241 | countTail p s accum = case uncons s of 242 | Just { head, tail } -> if p head then countTail p tail (accum + 1) else accum 243 | _ -> accum 244 | 245 | -- | Returns the number of code points preceding the first match of the given 246 | -- | pattern in the string. Returns `Nothing` when no matches are found. 247 | -- | 248 | -- | ```purescript 249 | -- | >>> indexOf (Pattern "𝐀") "b 𝐀𝐀 c 𝐀" 250 | -- | Just 2 251 | -- | >>> indexOf (Pattern "o") "b 𝐀𝐀 c 𝐀" 252 | -- | Nothing 253 | -- | ``` 254 | -- | 255 | indexOf :: Pattern -> String -> Maybe Int 256 | indexOf p s = (\i -> length (CU.take i s)) <$> CU.indexOf p s 257 | 258 | -- | Returns the number of code points preceding the first match of the given 259 | -- | pattern in the string. Pattern matches preceding the given index will be 260 | -- | ignored. Returns `Nothing` when no matches are found. 261 | -- | 262 | -- | ```purescript 263 | -- | >>> indexOf' (Pattern "𝐀") 4 "b 𝐀𝐀 c 𝐀" 264 | -- | Just 7 265 | -- | >>> indexOf' (Pattern "o") 4 "b 𝐀𝐀 c 𝐀" 266 | -- | Nothing 267 | -- | ``` 268 | -- | 269 | indexOf' :: Pattern -> Int -> String -> Maybe Int 270 | indexOf' p i s = 271 | let s' = drop i s in 272 | (\k -> i + length (CU.take k s')) <$> CU.indexOf p s' 273 | 274 | -- | Returns the number of code points preceding the last match of the given 275 | -- | pattern in the string. Returns `Nothing` when no matches are found. 276 | -- | 277 | -- | ```purescript 278 | -- | >>> lastIndexOf (Pattern "𝐀") "b 𝐀𝐀 c 𝐀" 279 | -- | Just 7 280 | -- | >>> lastIndexOf (Pattern "o") "b 𝐀𝐀 c 𝐀" 281 | -- | Nothing 282 | -- | ``` 283 | -- | 284 | lastIndexOf :: Pattern -> String -> Maybe Int 285 | lastIndexOf p s = (\i -> length (CU.take i s)) <$> CU.lastIndexOf p s 286 | 287 | -- | Returns the number of code points preceding the first match of the given 288 | -- | pattern in the string. Pattern matches following the given index will be 289 | -- | ignored. 290 | -- | 291 | -- | Giving a negative index is equivalent to giving 0 and giving an index 292 | -- | greater than the number of code points in the string is equivalent to 293 | -- | searching in the whole string. 294 | -- | 295 | -- | Returns `Nothing` when no matches are found. 296 | -- | 297 | -- | ```purescript 298 | -- | >>> lastIndexOf' (Pattern "𝐀") (-1) "b 𝐀𝐀 c 𝐀" 299 | -- | Nothing 300 | -- | >>> lastIndexOf' (Pattern "𝐀") 0 "b 𝐀𝐀 c 𝐀" 301 | -- | Nothing 302 | -- | >>> lastIndexOf' (Pattern "𝐀") 5 "b 𝐀𝐀 c 𝐀" 303 | -- | Just 3 304 | -- | >>> lastIndexOf' (Pattern "𝐀") 8 "b 𝐀𝐀 c 𝐀" 305 | -- | Just 7 306 | -- | >>> lastIndexOf' (Pattern "o") 5 "b 𝐀𝐀 c 𝐀" 307 | -- | Nothing 308 | -- | ``` 309 | -- | 310 | lastIndexOf' :: Pattern -> Int -> String -> Maybe Int 311 | lastIndexOf' p i s = 312 | let i' = CU.length (take i s) in 313 | (\k -> length (CU.take k s)) <$> CU.lastIndexOf' p i' s 314 | 315 | -- | Returns a string containing the given number of code points from the 316 | -- | beginning of the given string. If the string does not have that many code 317 | -- | points, returns the entire string. Operates in constant space and in time 318 | -- | linear to the given number. 319 | -- | 320 | -- | ```purescript 321 | -- | >>> take 3 "b 𝐀𝐀 c 𝐀" 322 | -- | "b 𝐀" 323 | -- | -- compare to Data.String: 324 | -- | >>> take 3 "b 𝐀𝐀 c 𝐀" 325 | -- | "b �" 326 | -- | ``` 327 | -- | 328 | take :: Int -> String -> String 329 | take = _take takeFallback 330 | 331 | foreign import _take :: (Int -> String -> String) -> Int -> String -> String 332 | 333 | takeFallback :: Int -> String -> String 334 | takeFallback n _ | n < 1 = "" 335 | takeFallback n s = case uncons s of 336 | Just { head, tail } -> singleton head <> takeFallback (n - 1) tail 337 | _ -> s 338 | 339 | -- | Returns a string containing the leading sequence of code points which all 340 | -- | match the given predicate from the string. Operates in constant space and 341 | -- | in time linear to the length of the string. 342 | -- | 343 | -- | ```purescript 344 | -- | >>> takeWhile (\c -> fromEnum c == 0x1D400) "𝐀𝐀 b c 𝐀" 345 | -- | "𝐀𝐀" 346 | -- | ``` 347 | -- | 348 | takeWhile :: (CodePoint -> Boolean) -> String -> String 349 | takeWhile p s = take (countPrefix p s) s 350 | 351 | -- | Drops the given number of code points from the beginning of the string. If 352 | -- | the string does not have that many code points, returns the empty string. 353 | -- | Operates in constant space and in time linear to the given number. 354 | -- | 355 | -- | ```purescript 356 | -- | >>> drop 5 "𝐀𝐀 b c" 357 | -- | "c" 358 | -- | -- compared to Data.String: 359 | -- | >>> drop 5 "𝐀𝐀 b c" 360 | -- | "b c" -- because "𝐀" occupies 2 code units 361 | -- | ``` 362 | -- | 363 | drop :: Int -> String -> String 364 | drop n s = CU.drop (CU.length (take n s)) s 365 | 366 | -- | Drops the leading sequence of code points which all match the given 367 | -- | predicate from the string. Operates in constant space and in time linear 368 | -- | to the length of the string. 369 | -- | 370 | -- | ```purescript 371 | -- | >>> dropWhile (\c -> fromEnum c == 0x1D400) "𝐀𝐀 b c 𝐀" 372 | -- | " b c 𝐀" 373 | -- | ``` 374 | -- | 375 | dropWhile :: (CodePoint -> Boolean) -> String -> String 376 | dropWhile p s = drop (countPrefix p s) s 377 | 378 | -- | Splits a string into two substrings, where `before` contains the code 379 | -- | points up to (but not including) the given index, and `after` contains the 380 | -- | rest of the string, from that index on. 381 | -- | 382 | -- | ```purescript 383 | -- | >>> splitAt 3 "b 𝐀𝐀 c 𝐀" 384 | -- | { before: "b 𝐀", after: "𝐀 c 𝐀" } 385 | -- | ``` 386 | -- | 387 | -- | Thus the length of `(splitAt i s).before` will equal either `i` or 388 | -- | `length s`, if that is shorter. (Or if `i` is negative the length will be 389 | -- | 0.) 390 | -- | 391 | -- | In code: 392 | -- | ```purescript 393 | -- | length (splitAt i s).before == min (max i 0) (length s) 394 | -- | (splitAt i s).before <> (splitAt i s).after == s 395 | -- | splitAt i s == {before: take i s, after: drop i s} 396 | -- | ``` 397 | splitAt :: Int -> String -> { before :: String, after :: String } 398 | splitAt i s = 399 | let before = take i s in 400 | { before 401 | -- inline drop i s to reuse the result of take i s 402 | , after: CU.drop (CU.length before) s 403 | } 404 | 405 | unsurrogate :: Int -> Int -> CodePoint 406 | unsurrogate lead trail = CodePoint ((lead - 0xD800) * 0x400 + (trail - 0xDC00) + 0x10000) 407 | 408 | isLead :: Int -> Boolean 409 | isLead cu = 0xD800 <= cu && cu <= 0xDBFF 410 | 411 | isTrail :: Int -> Boolean 412 | isTrail cu = 0xDC00 <= cu && cu <= 0xDFFF 413 | 414 | fromCharCode :: Int -> String 415 | fromCharCode = CU.singleton <<< toEnumWithDefaults bottom top 416 | 417 | -- WARN: this function expects the String parameter to be non-empty 418 | unsafeCodePointAt0 :: String -> CodePoint 419 | unsafeCodePointAt0 = _unsafeCodePointAt0 unsafeCodePointAt0Fallback 420 | 421 | foreign import _unsafeCodePointAt0 422 | :: (String -> CodePoint) 423 | -> String 424 | -> CodePoint 425 | 426 | unsafeCodePointAt0Fallback :: String -> CodePoint 427 | unsafeCodePointAt0Fallback s = 428 | let 429 | cu0 = fromEnum (Unsafe.charAt 0 s) 430 | in 431 | if isLead cu0 && CU.length s > 1 432 | then 433 | let cu1 = fromEnum (Unsafe.charAt 1 s) in 434 | if isTrail cu1 then unsurrogate cu0 cu1 else CodePoint cu0 435 | else 436 | CodePoint cu0 437 | -------------------------------------------------------------------------------- /src/Data/String/CodeUnits.js: -------------------------------------------------------------------------------- 1 | export const fromCharArray = function (a) { 2 | return a.join(""); 3 | }; 4 | 5 | export const toCharArray = function (s) { 6 | return s.split(""); 7 | }; 8 | 9 | export const singleton = function (c) { 10 | return c; 11 | }; 12 | 13 | export const _charAt = function (just) { 14 | return function (nothing) { 15 | return function (i) { 16 | return function (s) { 17 | return i >= 0 && i < s.length ? just(s.charAt(i)) : nothing; 18 | }; 19 | }; 20 | }; 21 | }; 22 | 23 | export const _toChar = function (just) { 24 | return function (nothing) { 25 | return function (s) { 26 | return s.length === 1 ? just(s) : nothing; 27 | }; 28 | }; 29 | }; 30 | 31 | export const length = function (s) { 32 | return s.length; 33 | }; 34 | 35 | export const countPrefix = function (p) { 36 | return function (s) { 37 | var i = 0; 38 | while (i < s.length && p(s.charAt(i))) i++; 39 | return i; 40 | }; 41 | }; 42 | 43 | export const _indexOf = function (just) { 44 | return function (nothing) { 45 | return function (x) { 46 | return function (s) { 47 | var i = s.indexOf(x); 48 | return i === -1 ? nothing : just(i); 49 | }; 50 | }; 51 | }; 52 | }; 53 | 54 | export const _indexOfStartingAt = function (just) { 55 | return function (nothing) { 56 | return function (x) { 57 | return function (startAt) { 58 | return function (s) { 59 | if (startAt < 0 || startAt > s.length) return nothing; 60 | var i = s.indexOf(x, startAt); 61 | return i === -1 ? nothing : just(i); 62 | }; 63 | }; 64 | }; 65 | }; 66 | }; 67 | 68 | export const _lastIndexOf = function (just) { 69 | return function (nothing) { 70 | return function (x) { 71 | return function (s) { 72 | var i = s.lastIndexOf(x); 73 | return i === -1 ? nothing : just(i); 74 | }; 75 | }; 76 | }; 77 | }; 78 | 79 | export const _lastIndexOfStartingAt = function (just) { 80 | return function (nothing) { 81 | return function (x) { 82 | return function (startAt) { 83 | return function (s) { 84 | var i = s.lastIndexOf(x, startAt); 85 | return i === -1 ? nothing : just(i); 86 | }; 87 | }; 88 | }; 89 | }; 90 | }; 91 | 92 | export const take = function (n) { 93 | return function (s) { 94 | return s.substr(0, n); 95 | }; 96 | }; 97 | 98 | export const drop = function (n) { 99 | return function (s) { 100 | return s.substring(n); 101 | }; 102 | }; 103 | 104 | export const slice = function (b) { 105 | return function (e) { 106 | return function (s) { 107 | return s.slice(b,e); 108 | }; 109 | }; 110 | }; 111 | 112 | export const splitAt = function (i) { 113 | return function (s) { 114 | return { before: s.substring(0, i), after: s.substring(i) }; 115 | }; 116 | }; 117 | -------------------------------------------------------------------------------- /src/Data/String/CodeUnits.purs: -------------------------------------------------------------------------------- 1 | module Data.String.CodeUnits 2 | ( stripPrefix 3 | , stripSuffix 4 | , contains 5 | , singleton 6 | , fromCharArray 7 | , toCharArray 8 | , charAt 9 | , toChar 10 | , uncons 11 | , length 12 | , countPrefix 13 | , indexOf 14 | , indexOf' 15 | , lastIndexOf 16 | , lastIndexOf' 17 | , take 18 | , takeRight 19 | , takeWhile 20 | , drop 21 | , dropRight 22 | , dropWhile 23 | , slice 24 | , splitAt 25 | ) where 26 | 27 | import Prelude 28 | 29 | import Data.Maybe (Maybe(..), isJust) 30 | import Data.String.Pattern (Pattern(..)) 31 | import Data.String.Unsafe as U 32 | 33 | ------------------------------------------------------------------------------- 34 | -- `stripPrefix`, `stripSuffix`, and `contains` are CodeUnit/CodePoint agnostic 35 | -- as they are based on patterns rather than lengths/indices, but they need to 36 | -- be defined in here to avoid a circular module dependency 37 | ------------------------------------------------------------------------------- 38 | 39 | -- | If the string starts with the given prefix, return the portion of the 40 | -- | string left after removing it, as a `Just` value. Otherwise, return `Nothing`. 41 | -- | 42 | -- | ```purescript 43 | -- | stripPrefix (Pattern "http:") "http://purescript.org" == Just "//purescript.org" 44 | -- | stripPrefix (Pattern "http:") "https://purescript.org" == Nothing 45 | -- | ``` 46 | stripPrefix :: Pattern -> String -> Maybe String 47 | stripPrefix (Pattern prefix) str = 48 | let { before, after } = splitAt (length prefix) str in 49 | if before == prefix then Just after else Nothing 50 | 51 | -- | If the string ends with the given suffix, return the portion of the 52 | -- | string left after removing it, as a `Just` value. Otherwise, return 53 | -- | `Nothing`. 54 | -- | 55 | -- | ```purescript 56 | -- | stripSuffix (Pattern ".exe") "psc.exe" == Just "psc" 57 | -- | stripSuffix (Pattern ".exe") "psc" == Nothing 58 | -- | ``` 59 | stripSuffix :: Pattern -> String -> Maybe String 60 | stripSuffix (Pattern suffix) str = 61 | let { before, after } = splitAt (length str - length suffix) str in 62 | if after == suffix then Just before else Nothing 63 | 64 | -- | Checks whether the pattern appears in the given string. 65 | -- | 66 | -- | ```purescript 67 | -- | contains (Pattern "needle") "haystack with needle" == true 68 | -- | contains (Pattern "needle") "haystack" == false 69 | -- | ``` 70 | contains :: Pattern -> String -> Boolean 71 | contains pat = isJust <<< indexOf pat 72 | 73 | ------------------------------------------------------------------------------- 74 | -- all functions past this point are CodeUnit specific 75 | ------------------------------------------------------------------------------- 76 | 77 | -- | Returns a string of length `1` containing the given character. 78 | -- | 79 | -- | ```purescript 80 | -- | singleton 'l' == "l" 81 | -- | ``` 82 | -- | 83 | foreign import singleton :: Char -> String 84 | 85 | -- | Converts an array of characters into a string. 86 | -- | 87 | -- | ```purescript 88 | -- | fromCharArray ['H', 'e', 'l', 'l', 'o'] == "Hello" 89 | -- | ``` 90 | foreign import fromCharArray :: Array Char -> String 91 | 92 | -- | Converts the string into an array of characters. 93 | -- | 94 | -- | ```purescript 95 | -- | toCharArray "Hello☺\n" == ['H','e','l','l','o','☺','\n'] 96 | -- | ``` 97 | foreign import toCharArray :: String -> Array Char 98 | 99 | -- | Returns the character at the given index, if the index is within bounds. 100 | -- | 101 | -- | ```purescript 102 | -- | charAt 2 "Hello" == Just 'l' 103 | -- | charAt 10 "Hello" == Nothing 104 | -- | ``` 105 | -- | 106 | charAt :: Int -> String -> Maybe Char 107 | charAt = _charAt Just Nothing 108 | 109 | foreign import _charAt 110 | :: (forall a. a -> Maybe a) 111 | -> (forall a. Maybe a) 112 | -> Int 113 | -> String 114 | -> Maybe Char 115 | 116 | -- | Converts the string to a character, if the length of the string is 117 | -- | exactly `1`. 118 | -- | 119 | -- | ```purescript 120 | -- | toChar "l" == Just 'l' 121 | -- | toChar "Hi" == Nothing -- since length is not 1 122 | -- | ``` 123 | toChar :: String -> Maybe Char 124 | toChar = _toChar Just Nothing 125 | 126 | foreign import _toChar 127 | :: (forall a. a -> Maybe a) 128 | -> (forall a. Maybe a) 129 | -> String 130 | -> Maybe Char 131 | 132 | -- | Returns the first character and the rest of the string, 133 | -- | if the string is not empty. 134 | -- | 135 | -- | ```purescript 136 | -- | uncons "" == Nothing 137 | -- | uncons "Hello World" == Just { head: 'H', tail: "ello World" } 138 | -- | ``` 139 | -- | 140 | uncons :: String -> Maybe { head :: Char, tail :: String } 141 | uncons "" = Nothing 142 | uncons s = Just { head: U.charAt zero s, tail: drop one s } 143 | 144 | -- | Returns the number of characters the string is composed of. 145 | -- | 146 | -- | ```purescript 147 | -- | length "Hello World" == 11 148 | -- | ``` 149 | -- | 150 | foreign import length :: String -> Int 151 | 152 | -- | Returns the number of contiguous characters at the beginning 153 | -- | of the string for which the predicate holds. 154 | -- | 155 | -- | ```purescript 156 | -- | countPrefix (_ /= ' ') "Hello World" == 5 -- since length "Hello" == 5 157 | -- | ``` 158 | -- | 159 | foreign import countPrefix :: (Char -> Boolean) -> String -> Int 160 | 161 | -- | Returns the index of the first occurrence of the pattern in the 162 | -- | given string. Returns `Nothing` if there is no match. 163 | -- | 164 | -- | ```purescript 165 | -- | indexOf (Pattern "c") "abcdc" == Just 2 166 | -- | indexOf (Pattern "c") "aaa" == Nothing 167 | -- | ``` 168 | -- | 169 | indexOf :: Pattern -> String -> Maybe Int 170 | indexOf = _indexOf Just Nothing 171 | 172 | foreign import _indexOf 173 | :: (forall a. a -> Maybe a) 174 | -> (forall a. Maybe a) 175 | -> Pattern 176 | -> String 177 | -> Maybe Int 178 | 179 | -- | Returns the index of the first occurrence of the pattern in the 180 | -- | given string, starting at the specified index. Returns `Nothing` if there is 181 | -- | no match. 182 | -- | 183 | -- | ```purescript 184 | -- | indexOf' (Pattern "a") 2 "ababa" == Just 2 185 | -- | indexOf' (Pattern "a") 3 "ababa" == Just 4 186 | -- | ``` 187 | -- | 188 | indexOf' :: Pattern -> Int -> String -> Maybe Int 189 | indexOf' = _indexOfStartingAt Just Nothing 190 | 191 | foreign import _indexOfStartingAt 192 | :: (forall a. a -> Maybe a) 193 | -> (forall a. Maybe a) 194 | -> Pattern 195 | -> Int 196 | -> String 197 | -> Maybe Int 198 | 199 | -- | Returns the index of the last occurrence of the pattern in the 200 | -- | given string. Returns `Nothing` if there is no match. 201 | -- | 202 | -- | ```purescript 203 | -- | lastIndexOf (Pattern "c") "abcdc" == Just 4 204 | -- | lastIndexOf (Pattern "c") "aaa" == Nothing 205 | -- | ``` 206 | -- | 207 | lastIndexOf :: Pattern -> String -> Maybe Int 208 | lastIndexOf = _lastIndexOf Just Nothing 209 | 210 | foreign import _lastIndexOf 211 | :: (forall a. a -> Maybe a) 212 | -> (forall a. Maybe a) 213 | -> Pattern 214 | -> String 215 | -> Maybe Int 216 | 217 | -- | Returns the index of the last occurrence of the pattern in the 218 | -- | given string, starting at the specified index and searching 219 | -- | backwards towards the beginning of the string. 220 | -- | 221 | -- | Starting at a negative index is equivalent to starting at 0 and 222 | -- | starting at an index greater than the string length is equivalent 223 | -- | to searching in the whole string. 224 | -- | 225 | -- | Returns `Nothing` if there is no match. 226 | -- | 227 | -- | ```purescript 228 | -- | lastIndexOf' (Pattern "a") (-1) "ababa" == Just 0 229 | -- | lastIndexOf' (Pattern "a") 1 "ababa" == Just 0 230 | -- | lastIndexOf' (Pattern "a") 3 "ababa" == Just 2 231 | -- | lastIndexOf' (Pattern "a") 4 "ababa" == Just 4 232 | -- | lastIndexOf' (Pattern "a") 5 "ababa" == Just 4 233 | -- | ``` 234 | -- | 235 | lastIndexOf' :: Pattern -> Int -> String -> Maybe Int 236 | lastIndexOf' = _lastIndexOfStartingAt Just Nothing 237 | 238 | foreign import _lastIndexOfStartingAt 239 | :: (forall a. a -> Maybe a) 240 | -> (forall a. Maybe a) 241 | -> Pattern 242 | -> Int 243 | -> String 244 | -> Maybe Int 245 | 246 | -- | Returns the first `n` characters of the string. 247 | -- | 248 | -- | ```purescript 249 | -- | take 5 "Hello World" == "Hello" 250 | -- | ``` 251 | -- | 252 | foreign import take :: Int -> String -> String 253 | 254 | -- | Returns the last `n` characters of the string. 255 | -- | 256 | -- | ```purescript 257 | -- | takeRight 5 "Hello World" == "World" 258 | -- | ``` 259 | -- | 260 | takeRight :: Int -> String -> String 261 | takeRight i s = drop (length s - i) s 262 | 263 | -- | Returns the longest prefix (possibly empty) of characters that satisfy 264 | -- | the predicate. 265 | -- | 266 | -- | ```purescript 267 | -- | takeWhile (_ /= ':') "http://purescript.org" == "http" 268 | -- | ``` 269 | -- | 270 | takeWhile :: (Char -> Boolean) -> String -> String 271 | takeWhile p s = take (countPrefix p s) s 272 | 273 | -- | Returns the string without the first `n` characters. 274 | -- | 275 | -- | ```purescript 276 | -- | drop 6 "Hello World" == "World" 277 | -- | ``` 278 | -- | 279 | foreign import drop :: Int -> String -> String 280 | 281 | -- | Returns the string without the last `n` characters. 282 | -- | 283 | -- | ```purescript 284 | -- | dropRight 6 "Hello World" == "Hello" 285 | -- | ``` 286 | -- | 287 | dropRight :: Int -> String -> String 288 | dropRight i s = take (length s - i) s 289 | 290 | -- | Returns the suffix remaining after `takeWhile`. 291 | -- | 292 | -- | ```purescript 293 | -- | dropWhile (_ /= '.') "Test.purs" == ".purs" 294 | -- | ``` 295 | -- | 296 | dropWhile :: (Char -> Boolean) -> String -> String 297 | dropWhile p s = drop (countPrefix p s) s 298 | 299 | -- | Returns the substring at indices `[begin, end)`. 300 | -- | If either index is negative, it is normalised to `length s - index`, 301 | -- | where `s` is the input string. `""` is returned if either 302 | -- | index is out of bounds or if `begin > end` after normalisation. 303 | -- | 304 | -- | ```purescript 305 | -- | slice 0 0 "purescript" == "" 306 | -- | slice 0 1 "purescript" == "p" 307 | -- | slice 3 6 "purescript" == "esc" 308 | -- | slice (-4) (-1) "purescript" == "rip" 309 | -- | slice (-4) 3 "purescript" == "" 310 | -- | ``` 311 | foreign import slice :: Int -> Int -> String -> String 312 | 313 | -- | Splits a string into two substrings, where `before` contains the 314 | -- | characters up to (but not including) the given index, and `after` contains 315 | -- | the rest of the string, from that index on. 316 | -- | 317 | -- | ```purescript 318 | -- | splitAt 2 "Hello World" == { before: "He", after: "llo World"} 319 | -- | splitAt 10 "Hi" == { before: "Hi", after: ""} 320 | -- | ``` 321 | -- | 322 | -- | Thus the length of `(splitAt i s).before` will equal either `i` or 323 | -- | `length s`, if that is shorter. (Or if `i` is negative the length will be 324 | -- | 0.) 325 | -- | 326 | -- | In code: 327 | -- | ```purescript 328 | -- | length (splitAt i s).before == min (max i 0) (length s) 329 | -- | (splitAt i s).before <> (splitAt i s).after == s 330 | -- | splitAt i s == {before: take i s, after: drop i s} 331 | -- | ``` 332 | foreign import splitAt :: Int -> String -> { before :: String, after :: String } 333 | -------------------------------------------------------------------------------- /src/Data/String/Common.js: -------------------------------------------------------------------------------- 1 | export const _localeCompare = function (lt) { 2 | return function (eq) { 3 | return function (gt) { 4 | return function (s1) { 5 | return function (s2) { 6 | var result = s1.localeCompare(s2); 7 | return result < 0 ? lt : result > 0 ? gt : eq; 8 | }; 9 | }; 10 | }; 11 | }; 12 | }; 13 | 14 | export const replace = function (s1) { 15 | return function (s2) { 16 | return function (s3) { 17 | return s3.replace(s1, s2); 18 | }; 19 | }; 20 | }; 21 | 22 | export const replaceAll = function (s1) { 23 | return function (s2) { 24 | return function (s3) { 25 | return s3.replace(new RegExp(s1.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&"), "g"), s2); // eslint-disable-line no-useless-escape 26 | }; 27 | }; 28 | }; 29 | 30 | export const split = function (sep) { 31 | return function (s) { 32 | return s.split(sep); 33 | }; 34 | }; 35 | 36 | export const toLower = function (s) { 37 | return s.toLowerCase(); 38 | }; 39 | 40 | export const toUpper = function (s) { 41 | return s.toUpperCase(); 42 | }; 43 | 44 | export const trim = function (s) { 45 | return s.trim(); 46 | }; 47 | 48 | export const joinWith = function (s) { 49 | return function (xs) { 50 | return xs.join(s); 51 | }; 52 | }; 53 | -------------------------------------------------------------------------------- /src/Data/String/Common.purs: -------------------------------------------------------------------------------- 1 | module Data.String.Common 2 | ( null 3 | , localeCompare 4 | , replace 5 | , replaceAll 6 | , split 7 | , toLower 8 | , toUpper 9 | , trim 10 | , joinWith 11 | ) where 12 | 13 | import Prelude 14 | 15 | import Data.String.Pattern (Pattern, Replacement) 16 | 17 | -- | Returns `true` if the given string is empty. 18 | -- | 19 | -- | ```purescript 20 | -- | null "" == true 21 | -- | null "Hi" == false 22 | -- | ``` 23 | null :: String -> Boolean 24 | null s = s == "" 25 | 26 | -- | Compare two strings in a locale-aware fashion. This is in contrast to 27 | -- | the `Ord` instance on `String` which treats strings as arrays of code 28 | -- | units: 29 | -- | 30 | -- | ```purescript 31 | -- | "ä" `localeCompare` "b" == LT 32 | -- | "ä" `compare` "b" == GT 33 | -- | ``` 34 | localeCompare :: String -> String -> Ordering 35 | localeCompare = _localeCompare LT EQ GT 36 | 37 | foreign import _localeCompare 38 | :: Ordering 39 | -> Ordering 40 | -> Ordering 41 | -> String 42 | -> String 43 | -> Ordering 44 | 45 | -- | Replaces the first occurence of the pattern with the replacement string. 46 | -- | 47 | -- | ```purescript 48 | -- | replace (Pattern "<=") (Replacement "≤") "a <= b <= c" == "a ≤ b <= c" 49 | -- | ``` 50 | foreign import replace :: Pattern -> Replacement -> String -> String 51 | 52 | -- | Replaces all occurences of the pattern with the replacement string. 53 | -- | 54 | -- | ```purescript 55 | -- | replaceAll (Pattern "<=") (Replacement "≤") "a <= b <= c" == "a ≤ b ≤ c" 56 | -- | ``` 57 | foreign import replaceAll :: Pattern -> Replacement -> String -> String 58 | 59 | -- | Returns the substrings of the second string separated along occurences 60 | -- | of the first string. 61 | -- | 62 | -- | ```purescript 63 | -- | split (Pattern " ") "hello world" == ["hello", "world"] 64 | -- | ``` 65 | foreign import split :: Pattern -> String -> Array String 66 | 67 | -- | Returns the argument converted to lowercase. 68 | -- | 69 | -- | ```purescript 70 | -- | toLower "hElLo" == "hello" 71 | -- | ``` 72 | foreign import toLower :: String -> String 73 | 74 | -- | Returns the argument converted to uppercase. 75 | -- | 76 | -- | ```purescript 77 | -- | toUpper "Hello" == "HELLO" 78 | -- | ``` 79 | foreign import toUpper :: String -> String 80 | 81 | -- | Removes whitespace from the beginning and end of a string, including 82 | -- | [whitespace characters](http://www.ecma-international.org/ecma-262/5.1/#sec-7.2) 83 | -- | and [line terminators](http://www.ecma-international.org/ecma-262/5.1/#sec-7.3). 84 | -- | 85 | -- | ```purescript 86 | -- | trim " Hello \n World\n\t " == "Hello \n World" 87 | -- | ``` 88 | foreign import trim :: String -> String 89 | 90 | -- | Joins the strings in the array together, inserting the first argument 91 | -- | as separator between them. 92 | -- | 93 | -- | ```purescript 94 | -- | joinWith ", " ["apple", "banana", "orange"] == "apple, banana, orange" 95 | -- | ``` 96 | foreign import joinWith :: String -> Array String -> String 97 | -------------------------------------------------------------------------------- /src/Data/String/Gen.purs: -------------------------------------------------------------------------------- 1 | module Data.String.Gen where 2 | 3 | import Prelude 4 | 5 | import Control.Monad.Gen (class MonadGen, chooseInt, unfoldable, sized, resize) 6 | import Control.Monad.Rec.Class (class MonadRec) 7 | import Data.Char.Gen as CG 8 | import Data.String.CodeUnits as SCU 9 | 10 | -- | Generates a string using the specified character generator. 11 | genString :: forall m. MonadRec m => MonadGen m => m Char -> m String 12 | genString genChar = sized \size -> do 13 | newSize <- chooseInt 1 (max 1 size) 14 | resize (const newSize) $ SCU.fromCharArray <$> unfoldable genChar 15 | 16 | -- | Generates a string using characters from the Unicode basic multilingual 17 | -- | plain. 18 | genUnicodeString :: forall m. MonadRec m => MonadGen m => m String 19 | genUnicodeString = genString CG.genUnicodeChar 20 | 21 | -- | Generates a string using the ASCII character set, excluding control codes. 22 | genAsciiString :: forall m. MonadRec m => MonadGen m => m String 23 | genAsciiString = genString CG.genAsciiChar 24 | 25 | -- | Generates a string using the ASCII character set. 26 | genAsciiString' :: forall m. MonadRec m => MonadGen m => m String 27 | genAsciiString' = genString CG.genAsciiChar' 28 | 29 | -- | Generates a string made up of numeric digits. 30 | genDigitString :: forall m. MonadRec m => MonadGen m => m String 31 | genDigitString = genString CG.genDigitChar 32 | 33 | -- | Generates a string using characters from the basic Latin alphabet. 34 | genAlphaString :: forall m. MonadRec m => MonadGen m => m String 35 | genAlphaString = genString CG.genAlpha 36 | 37 | -- | Generates a string using lowercase characters from the basic Latin alphabet. 38 | genAlphaLowercaseString :: forall m. MonadRec m => MonadGen m => m String 39 | genAlphaLowercaseString = genString CG.genAlphaLowercase 40 | 41 | -- | Generates a string using uppercase characters from the basic Latin alphabet. 42 | genAlphaUppercaseString :: forall m. MonadRec m => MonadGen m => m String 43 | genAlphaUppercaseString = genString CG.genAlphaUppercase 44 | -------------------------------------------------------------------------------- /src/Data/String/NonEmpty.purs: -------------------------------------------------------------------------------- 1 | module Data.String.NonEmpty 2 | ( module Data.String.Pattern 3 | , module Data.String.NonEmpty.Internal 4 | , module Data.String.NonEmpty.CodePoints 5 | ) where 6 | 7 | import Data.String.NonEmpty.Internal (NonEmptyString, class MakeNonEmpty, NonEmptyReplacement(..), appendString, contains, fromString, join1With, joinWith, joinWith1, localeCompare, nes, prependString, replace, replaceAll, stripPrefix, stripSuffix, toLower, toString, toUpper, trim, unsafeFromString) 8 | import Data.String.Pattern (Pattern(..)) 9 | import Data.String.NonEmpty.CodePoints 10 | -------------------------------------------------------------------------------- /src/Data/String/NonEmpty/CaseInsensitive.purs: -------------------------------------------------------------------------------- 1 | module Data.String.NonEmpty.CaseInsensitive where 2 | 3 | import Prelude 4 | 5 | import Data.Newtype (class Newtype) 6 | import Data.String.NonEmpty (NonEmptyString, toLower) 7 | 8 | -- | A newtype for case insensitive string comparisons and ordering. 9 | newtype CaseInsensitiveNonEmptyString = CaseInsensitiveNonEmptyString NonEmptyString 10 | 11 | instance eqCaseInsensitiveNonEmptyString :: Eq CaseInsensitiveNonEmptyString where 12 | eq (CaseInsensitiveNonEmptyString s1) (CaseInsensitiveNonEmptyString s2) = 13 | toLower s1 == toLower s2 14 | 15 | instance ordCaseInsensitiveNonEmptyString :: Ord CaseInsensitiveNonEmptyString where 16 | compare (CaseInsensitiveNonEmptyString s1) (CaseInsensitiveNonEmptyString s2) = 17 | compare (toLower s1) (toLower s2) 18 | 19 | instance showCaseInsensitiveNonEmptyString :: Show CaseInsensitiveNonEmptyString where 20 | show (CaseInsensitiveNonEmptyString s) = "(CaseInsensitiveNonEmptyString " <> show s <> ")" 21 | 22 | derive instance newtypeCaseInsensitiveNonEmptyString :: Newtype CaseInsensitiveNonEmptyString _ 23 | -------------------------------------------------------------------------------- /src/Data/String/NonEmpty/CodePoints.purs: -------------------------------------------------------------------------------- 1 | module Data.String.NonEmpty.CodePoints 2 | ( fromCodePointArray 3 | , fromNonEmptyCodePointArray 4 | , singleton 5 | , cons 6 | , snoc 7 | , fromFoldable1 8 | , toCodePointArray 9 | , toNonEmptyCodePointArray 10 | , codePointAt 11 | , indexOf 12 | , indexOf' 13 | , lastIndexOf 14 | , lastIndexOf' 15 | , uncons 16 | , length 17 | , take 18 | -- takeRight 19 | , takeWhile 20 | , drop 21 | -- dropRight 22 | , dropWhile 23 | , countPrefix 24 | , splitAt 25 | ) where 26 | 27 | import Prelude 28 | 29 | import Data.Array.NonEmpty (NonEmptyArray) 30 | import Data.Array.NonEmpty as NEA 31 | import Data.Maybe (Maybe(..), fromJust) 32 | import Data.Semigroup.Foldable (class Foldable1) 33 | import Data.Semigroup.Foldable as F1 34 | import Data.String.CodePoints (CodePoint) 35 | import Data.String.CodePoints as CP 36 | import Data.String.NonEmpty.Internal (NonEmptyString(..), fromString) 37 | import Data.String.Pattern (Pattern) 38 | import Partial.Unsafe (unsafePartial) 39 | 40 | -- For internal use only. Do not export. 41 | toNonEmptyString :: String -> NonEmptyString 42 | toNonEmptyString = NonEmptyString 43 | 44 | -- For internal use only. Do not export. 45 | fromNonEmptyString :: NonEmptyString -> String 46 | fromNonEmptyString (NonEmptyString s) = s 47 | 48 | -- For internal use only. Do not export. 49 | liftS :: forall r. (String -> r) -> NonEmptyString -> r 50 | liftS f (NonEmptyString s) = f s 51 | 52 | fromCodePointArray :: Array CodePoint -> Maybe NonEmptyString 53 | fromCodePointArray = case _ of 54 | [] -> Nothing 55 | cs -> Just (toNonEmptyString (CP.fromCodePointArray cs)) 56 | 57 | fromNonEmptyCodePointArray :: NonEmptyArray CodePoint -> NonEmptyString 58 | fromNonEmptyCodePointArray = unsafePartial fromJust <<< fromCodePointArray <<< NEA.toArray 59 | 60 | singleton :: CodePoint -> NonEmptyString 61 | singleton = toNonEmptyString <<< CP.singleton 62 | 63 | cons :: CodePoint -> String -> NonEmptyString 64 | cons c s = toNonEmptyString (CP.singleton c <> s) 65 | 66 | snoc :: CodePoint -> String -> NonEmptyString 67 | snoc c s = toNonEmptyString (s <> CP.singleton c) 68 | 69 | fromFoldable1 :: forall f. Foldable1 f => f CodePoint -> NonEmptyString 70 | fromFoldable1 = F1.foldMap1 singleton 71 | 72 | toCodePointArray :: NonEmptyString -> Array CodePoint 73 | toCodePointArray = CP.toCodePointArray <<< fromNonEmptyString 74 | 75 | toNonEmptyCodePointArray :: NonEmptyString -> NonEmptyArray CodePoint 76 | toNonEmptyCodePointArray = unsafePartial fromJust <<< NEA.fromArray <<< toCodePointArray 77 | 78 | codePointAt :: Int -> NonEmptyString -> Maybe CodePoint 79 | codePointAt = liftS <<< CP.codePointAt 80 | 81 | indexOf :: Pattern -> NonEmptyString -> Maybe Int 82 | indexOf = liftS <<< CP.indexOf 83 | 84 | indexOf' :: Pattern -> Int -> NonEmptyString -> Maybe Int 85 | indexOf' pat = liftS <<< CP.indexOf' pat 86 | 87 | lastIndexOf :: Pattern -> NonEmptyString -> Maybe Int 88 | lastIndexOf = liftS <<< CP.lastIndexOf 89 | 90 | lastIndexOf' :: Pattern -> Int -> NonEmptyString -> Maybe Int 91 | lastIndexOf' pat = liftS <<< CP.lastIndexOf' pat 92 | 93 | uncons :: NonEmptyString -> { head :: CodePoint, tail :: Maybe NonEmptyString } 94 | uncons nes = 95 | let 96 | s = fromNonEmptyString nes 97 | in 98 | { head: unsafePartial fromJust (CP.codePointAt 0 s) 99 | , tail: fromString (CP.drop 1 s) 100 | } 101 | 102 | length :: NonEmptyString -> Int 103 | length = CP.length <<< fromNonEmptyString 104 | 105 | take :: Int -> NonEmptyString -> Maybe NonEmptyString 106 | take i nes = 107 | let 108 | s = fromNonEmptyString nes 109 | in 110 | if i < 1 111 | then Nothing 112 | else Just (toNonEmptyString (CP.take i s)) 113 | 114 | takeWhile :: (CodePoint -> Boolean) -> NonEmptyString -> Maybe NonEmptyString 115 | takeWhile f = fromString <<< liftS (CP.takeWhile f) 116 | 117 | drop :: Int -> NonEmptyString -> Maybe NonEmptyString 118 | drop i nes = 119 | let 120 | s = fromNonEmptyString nes 121 | in 122 | if i >= CP.length s 123 | then Nothing 124 | else Just (toNonEmptyString (CP.drop i s)) 125 | 126 | dropWhile :: (CodePoint -> Boolean) -> NonEmptyString -> Maybe NonEmptyString 127 | dropWhile f = fromString <<< liftS (CP.dropWhile f) 128 | 129 | countPrefix :: (CodePoint -> Boolean) -> NonEmptyString -> Int 130 | countPrefix = liftS <<< CP.countPrefix 131 | 132 | splitAt 133 | :: Int 134 | -> NonEmptyString 135 | -> { before :: Maybe NonEmptyString, after :: Maybe NonEmptyString } 136 | splitAt i nes = 137 | case CP.splitAt i (fromNonEmptyString nes) of 138 | { before, after } -> { before: fromString before, after: fromString after } 139 | -------------------------------------------------------------------------------- /src/Data/String/NonEmpty/CodeUnits.purs: -------------------------------------------------------------------------------- 1 | module Data.String.NonEmpty.CodeUnits 2 | ( fromCharArray 3 | , fromNonEmptyCharArray 4 | , singleton 5 | , cons 6 | , snoc 7 | , fromFoldable1 8 | , toCharArray 9 | , toNonEmptyCharArray 10 | , charAt 11 | , toChar 12 | , indexOf 13 | , indexOf' 14 | , lastIndexOf 15 | , lastIndexOf' 16 | , uncons 17 | , length 18 | , take 19 | , takeRight 20 | , takeWhile 21 | , drop 22 | , dropRight 23 | , dropWhile 24 | , countPrefix 25 | , splitAt 26 | ) where 27 | 28 | import Prelude 29 | 30 | import Data.Array.NonEmpty (NonEmptyArray) 31 | import Data.Array.NonEmpty as NEA 32 | import Data.Maybe (Maybe(..), fromJust) 33 | import Data.Semigroup.Foldable (class Foldable1) 34 | import Data.Semigroup.Foldable as F1 35 | import Data.String.CodeUnits as CU 36 | import Data.String.NonEmpty.Internal (NonEmptyString(..), fromString) 37 | import Data.String.Pattern (Pattern) 38 | import Data.String.Unsafe as U 39 | import Partial.Unsafe (unsafePartial) 40 | 41 | -- For internal use only. Do not export. 42 | toNonEmptyString :: String -> NonEmptyString 43 | toNonEmptyString = NonEmptyString 44 | 45 | -- For internal use only. Do not export. 46 | fromNonEmptyString :: NonEmptyString -> String 47 | fromNonEmptyString (NonEmptyString s) = s 48 | 49 | -- For internal use only. Do not export. 50 | liftS :: forall r. (String -> r) -> NonEmptyString -> r 51 | liftS f (NonEmptyString s) = f s 52 | 53 | -- | Creates a `NonEmptyString` from a character array `String`, returning 54 | -- | `Nothing` if the input is empty. 55 | -- | 56 | -- | ```purescript 57 | -- | fromCharArray [] = Nothing 58 | -- | fromCharArray ['a', 'b', 'c'] = Just (NonEmptyString "abc") 59 | -- | ``` 60 | fromCharArray :: Array Char -> Maybe NonEmptyString 61 | fromCharArray = case _ of 62 | [] -> Nothing 63 | cs -> Just (toNonEmptyString (CU.fromCharArray cs)) 64 | 65 | fromNonEmptyCharArray :: NonEmptyArray Char -> NonEmptyString 66 | fromNonEmptyCharArray = unsafePartial fromJust <<< fromCharArray <<< NEA.toArray 67 | 68 | -- | Creates a `NonEmptyString` from a character. 69 | singleton :: Char -> NonEmptyString 70 | singleton = toNonEmptyString <<< CU.singleton 71 | 72 | -- | Creates a `NonEmptyString` from a string by prepending a character. 73 | -- | 74 | -- | ```purescript 75 | -- | cons 'a' "bc" = NonEmptyString "abc" 76 | -- | cons 'a' "" = NonEmptyString "a" 77 | -- | ``` 78 | cons :: Char -> String -> NonEmptyString 79 | cons c s = toNonEmptyString (CU.singleton c <> s) 80 | 81 | -- | Creates a `NonEmptyString` from a string by appending a character. 82 | -- | 83 | -- | ```purescript 84 | -- | snoc 'c' "ab" = NonEmptyString "abc" 85 | -- | snoc 'a' "" = NonEmptyString "a" 86 | -- | ``` 87 | snoc :: Char -> String -> NonEmptyString 88 | snoc c s = toNonEmptyString (s <> CU.singleton c) 89 | 90 | -- | Creates a `NonEmptyString` from a `Foldable1` container carrying 91 | -- | characters. 92 | fromFoldable1 :: forall f. Foldable1 f => f Char -> NonEmptyString 93 | fromFoldable1 = F1.foldMap1 singleton 94 | 95 | -- | Converts the `NonEmptyString` into an array of characters. 96 | -- | 97 | -- | ```purescript 98 | -- | toCharArray (NonEmptyString "Hello☺\n") == ['H','e','l','l','o','☺','\n'] 99 | -- | ``` 100 | toCharArray :: NonEmptyString -> Array Char 101 | toCharArray = CU.toCharArray <<< fromNonEmptyString 102 | 103 | -- | Converts the `NonEmptyString` into a non-empty array of characters. 104 | toNonEmptyCharArray :: NonEmptyString -> NonEmptyArray Char 105 | toNonEmptyCharArray = unsafePartial fromJust <<< NEA.fromArray <<< toCharArray 106 | 107 | -- | Returns the character at the given index, if the index is within bounds. 108 | -- | 109 | -- | ```purescript 110 | -- | charAt 2 (NonEmptyString "Hello") == Just 'l' 111 | -- | charAt 10 (NonEmptyString "Hello") == Nothing 112 | -- | ``` 113 | charAt :: Int -> NonEmptyString -> Maybe Char 114 | charAt = liftS <<< CU.charAt 115 | 116 | -- | Converts the `NonEmptyString` to a character, if the length of the string 117 | -- | is exactly `1`. 118 | -- | 119 | -- | ```purescript 120 | -- | toChar "H" == Just 'H' 121 | -- | toChar "Hi" == Nothing 122 | -- | ``` 123 | toChar :: NonEmptyString -> Maybe Char 124 | toChar = CU.toChar <<< fromNonEmptyString 125 | 126 | -- | Returns the index of the first occurrence of the pattern in the 127 | -- | given string. Returns `Nothing` if there is no match. 128 | -- | 129 | -- | ```purescript 130 | -- | indexOf (Pattern "c") (NonEmptyString "abcdc") == Just 2 131 | -- | indexOf (Pattern "c") (NonEmptyString "aaa") == Nothing 132 | -- | ``` 133 | indexOf :: Pattern -> NonEmptyString -> Maybe Int 134 | indexOf = liftS <<< CU.indexOf 135 | 136 | -- | Returns the index of the first occurrence of the pattern in the 137 | -- | given string, starting at the specified index. Returns `Nothing` if there is 138 | -- | no match. 139 | -- | 140 | -- | ```purescript 141 | -- | indexOf' (Pattern "a") 2 (NonEmptyString "ababa") == Just 2 142 | -- | indexOf' (Pattern "a") 3 (NonEmptyString "ababa") == Just 4 143 | -- | ``` 144 | indexOf' :: Pattern -> Int -> NonEmptyString -> Maybe Int 145 | indexOf' pat = liftS <<< CU.indexOf' pat 146 | 147 | -- | Returns the index of the last occurrence of the pattern in the 148 | -- | given string. Returns `Nothing` if there is no match. 149 | -- | 150 | -- | ```purescript 151 | -- | lastIndexOf (Pattern "c") (NonEmptyString "abcdc") == Just 4 152 | -- | lastIndexOf (Pattern "c") (NonEmptyString "aaa") == Nothing 153 | -- | ``` 154 | lastIndexOf :: Pattern -> NonEmptyString -> Maybe Int 155 | lastIndexOf = liftS <<< CU.lastIndexOf 156 | 157 | -- | Returns the index of the last occurrence of the pattern in the 158 | -- | given string, starting at the specified index and searching 159 | -- | backwards towards the beginning of the string. 160 | -- | 161 | -- | Starting at a negative index is equivalent to starting at 0 and 162 | -- | starting at an index greater than the string length is equivalent 163 | -- | to searching in the whole string. 164 | -- | 165 | -- | Returns `Nothing` if there is no match. 166 | -- | 167 | -- | ```purescript 168 | -- | lastIndexOf' (Pattern "a") (-1) (NonEmptyString "ababa") == Just 0 169 | -- | lastIndexOf' (Pattern "a") 1 (NonEmptyString "ababa") == Just 0 170 | -- | lastIndexOf' (Pattern "a") 3 (NonEmptyString "ababa") == Just 2 171 | -- | lastIndexOf' (Pattern "a") 4 (NonEmptyString "ababa") == Just 4 172 | -- | lastIndexOf' (Pattern "a") 5 (NonEmptyString "ababa") == Just 4 173 | -- | ``` 174 | lastIndexOf' :: Pattern -> Int -> NonEmptyString -> Maybe Int 175 | lastIndexOf' pat = liftS <<< CU.lastIndexOf' pat 176 | 177 | -- | Returns the first character and the rest of the string. 178 | -- | 179 | -- | ```purescript 180 | -- | uncons "a" == { head: 'a', tail: Nothing } 181 | -- | uncons "Hello World" == { head: 'H', tail: Just (NonEmptyString "ello World") } 182 | -- | ``` 183 | uncons :: NonEmptyString -> { head :: Char, tail :: Maybe NonEmptyString } 184 | uncons nes = 185 | let 186 | s = fromNonEmptyString nes 187 | in 188 | { head: U.charAt 0 s 189 | , tail: fromString (CU.drop 1 s) 190 | } 191 | 192 | -- | Returns the number of characters the string is composed of. 193 | -- | 194 | -- | ```purescript 195 | -- | length (NonEmptyString "Hello World") == 11 196 | -- | ``` 197 | length :: NonEmptyString -> Int 198 | length = CU.length <<< fromNonEmptyString 199 | 200 | -- | Returns the first `n` characters of the string. Returns `Nothing` if `n` is 201 | -- | less than 1. 202 | -- | 203 | -- | ```purescript 204 | -- | take 5 (NonEmptyString "Hello World") == Just (NonEmptyString "Hello") 205 | -- | take 0 (NonEmptyString "Hello World") == Nothing 206 | -- | ``` 207 | take :: Int -> NonEmptyString -> Maybe NonEmptyString 208 | take i nes = 209 | let 210 | s = fromNonEmptyString nes 211 | in 212 | if i < 1 213 | then Nothing 214 | else Just (toNonEmptyString (CU.take i s)) 215 | 216 | -- | Returns the last `n` characters of the string. Returns `Nothing` if `n` is 217 | -- | less than 1. 218 | -- | 219 | -- | ```purescript 220 | -- | take 5 (NonEmptyString "Hello World") == Just (NonEmptyString "World") 221 | -- | take 0 (NonEmptyString "Hello World") == Nothing 222 | -- | ``` 223 | takeRight :: Int -> NonEmptyString -> Maybe NonEmptyString 224 | takeRight i nes = 225 | let 226 | s = fromNonEmptyString nes 227 | in 228 | if i < 1 229 | then Nothing 230 | else Just (toNonEmptyString (CU.takeRight i s)) 231 | 232 | -- | Returns the longest prefix of characters that satisfy the predicate. 233 | -- | `Nothing` is returned if there is no matching prefix. 234 | -- | 235 | -- | ```purescript 236 | -- | takeWhile (_ /= ':') (NonEmptyString "http://purescript.org") == Just (NonEmptyString "http") 237 | -- | takeWhile (_ == 'a') (NonEmptyString "xyz") == Nothing 238 | -- | ``` 239 | takeWhile :: (Char -> Boolean) -> NonEmptyString -> Maybe NonEmptyString 240 | takeWhile f = fromString <<< liftS (CU.takeWhile f) 241 | 242 | -- | Returns the string without the first `n` characters. Returns `Nothing` if 243 | -- | more characters are dropped than the string is long. 244 | -- | 245 | -- | ```purescript 246 | -- | drop 6 (NonEmptyString "Hello World") == Just (NonEmptyString "World") 247 | -- | drop 20 (NonEmptyString "Hello World") == Nothing 248 | -- | ``` 249 | drop :: Int -> NonEmptyString -> Maybe NonEmptyString 250 | drop i nes = 251 | let 252 | s = fromNonEmptyString nes 253 | in 254 | if i >= CU.length s 255 | then Nothing 256 | else Just (toNonEmptyString (CU.drop i s)) 257 | 258 | -- | Returns the string without the last `n` characters. Returns `Nothing` if 259 | -- | more characters are dropped than the string is long. 260 | -- | 261 | -- | ```purescript 262 | -- | dropRight 6 (NonEmptyString "Hello World") == Just (NonEmptyString "Hello") 263 | -- | dropRight 20 (NonEmptyString "Hello World") == Nothing 264 | -- | ``` 265 | dropRight :: Int -> NonEmptyString -> Maybe NonEmptyString 266 | dropRight i nes = 267 | let 268 | s = fromNonEmptyString nes 269 | in 270 | if i >= CU.length s 271 | then Nothing 272 | else Just (toNonEmptyString (CU.dropRight i s)) 273 | 274 | -- | Returns the suffix remaining after `takeWhile`. 275 | -- | 276 | -- | ```purescript 277 | -- | dropWhile (_ /= '.') (NonEmptyString "Test.purs") == Just (NonEmptyString ".purs") 278 | -- | ``` 279 | dropWhile :: (Char -> Boolean) -> NonEmptyString -> Maybe NonEmptyString 280 | dropWhile f = fromString <<< liftS (CU.dropWhile f) 281 | 282 | -- | Returns the number of contiguous characters at the beginning of the string 283 | -- | for which the predicate holds. 284 | -- | 285 | -- | ```purescript 286 | -- | countPrefix (_ /= 'o') (NonEmptyString "Hello World") == 4 287 | -- | ``` 288 | countPrefix :: (Char -> Boolean) -> NonEmptyString -> Int 289 | countPrefix = liftS <<< CU.countPrefix 290 | 291 | -- | Returns the substrings of a split at the given index, if the index is 292 | -- | within bounds. 293 | -- | 294 | -- | ```purescript 295 | -- | splitAt 2 (NonEmptyString "Hello World") == Just { before: Just (NonEmptyString "He"), after: Just (NonEmptyString "llo World") } 296 | -- | splitAt 10 (NonEmptyString "Hi") == Nothing 297 | -- | ``` 298 | splitAt 299 | :: Int 300 | -> NonEmptyString 301 | -> { before :: Maybe NonEmptyString, after :: Maybe NonEmptyString } 302 | splitAt i nes = 303 | case CU.splitAt i (fromNonEmptyString nes) of 304 | { before, after } -> { before: fromString before, after: fromString after } 305 | -------------------------------------------------------------------------------- /src/Data/String/NonEmpty/Internal.purs: -------------------------------------------------------------------------------- 1 | -- | While most of the code in this module is safe, this module does 2 | -- | export a few partial functions and the `NonEmptyString` constructor. 3 | -- | While the partial functions are obvious from the `Partial` constraint in 4 | -- | their type signature, the `NonEmptyString` constructor can be overlooked 5 | -- | when searching for issues in one's code. See the constructor's 6 | -- | documentation for more information. 7 | module Data.String.NonEmpty.Internal where 8 | 9 | import Prelude 10 | 11 | import Data.Foldable (class Foldable) 12 | import Data.Foldable as F 13 | import Data.Maybe (Maybe(..), fromJust) 14 | import Data.Semigroup.Foldable (class Foldable1) 15 | import Data.String as String 16 | import Data.String.Pattern (Pattern) 17 | import Data.Symbol (class IsSymbol, reflectSymbol) 18 | import Prim.TypeError as TE 19 | import Type.Proxy (Proxy) 20 | import Unsafe.Coerce (unsafeCoerce) 21 | 22 | -- | A string that is known not to be empty. 23 | -- | 24 | -- | You can use this constructor to create a `NonEmptyString` that isn't 25 | -- | non-empty, breaking the guarantee behind this newtype. It is 26 | -- | provided as an escape hatch mainly for the `Data.NonEmpty.CodeUnits` 27 | -- | and `Data.NonEmpty.CodePoints` modules. Use this at your own risk 28 | -- | when you know what you are doing. 29 | newtype NonEmptyString = NonEmptyString String 30 | 31 | derive newtype instance eqNonEmptyString ∷ Eq NonEmptyString 32 | derive newtype instance ordNonEmptyString ∷ Ord NonEmptyString 33 | derive newtype instance semigroupNonEmptyString ∷ Semigroup NonEmptyString 34 | 35 | instance showNonEmptyString :: Show NonEmptyString where 36 | show (NonEmptyString s) = "(NonEmptyString.unsafeFromString " <> show s <> ")" 37 | 38 | -- | A helper class for defining non-empty string values at compile time. 39 | -- | 40 | -- | ``` purescript 41 | -- | something :: NonEmptyString 42 | -- | something = nes (Proxy :: Proxy "something") 43 | -- | ``` 44 | class MakeNonEmpty (s :: Symbol) where 45 | nes :: Proxy s -> NonEmptyString 46 | 47 | instance makeNonEmptyBad :: TE.Fail (TE.Text "Cannot create an NonEmptyString from an empty Symbol") => MakeNonEmpty "" where 48 | nes _ = NonEmptyString "" 49 | 50 | else instance nonEmptyNonEmpty :: IsSymbol s => MakeNonEmpty s where 51 | nes p = NonEmptyString (reflectSymbol p) 52 | 53 | -- | A newtype used in cases to specify a non-empty replacement for a pattern. 54 | newtype NonEmptyReplacement = NonEmptyReplacement NonEmptyString 55 | 56 | derive newtype instance eqNonEmptyReplacement :: Eq NonEmptyReplacement 57 | derive newtype instance ordNonEmptyReplacement :: Ord NonEmptyReplacement 58 | derive newtype instance semigroupNonEmptyReplacement ∷ Semigroup NonEmptyReplacement 59 | 60 | instance showNonEmptyReplacement :: Show NonEmptyReplacement where 61 | show (NonEmptyReplacement s) = "(NonEmptyReplacement " <> show s <> ")" 62 | 63 | -- | Creates a `NonEmptyString` from a `String`, returning `Nothing` if the 64 | -- | input is empty. 65 | -- | 66 | -- | ```purescript 67 | -- | fromString "" = Nothing 68 | -- | fromString "hello" = Just (NES.unsafeFromString "hello") 69 | -- | ``` 70 | fromString :: String -> Maybe NonEmptyString 71 | fromString = case _ of 72 | "" -> Nothing 73 | s -> Just (NonEmptyString s) 74 | 75 | -- | A partial version of `fromString`. 76 | unsafeFromString :: Partial => String -> NonEmptyString 77 | unsafeFromString = fromJust <<< fromString 78 | 79 | -- | Converts a `NonEmptyString` back into a standard `String`. 80 | toString :: NonEmptyString -> String 81 | toString (NonEmptyString s) = s 82 | 83 | -- | Appends a string to this non-empty string. Since one of the strings is 84 | -- | non-empty we know the result will be too. 85 | -- | 86 | -- | ```purescript 87 | -- | appendString (NonEmptyString "Hello") " world" == NonEmptyString "Hello world" 88 | -- | appendString (NonEmptyString "Hello") "" == NonEmptyString "Hello" 89 | -- | ``` 90 | appendString :: NonEmptyString -> String -> NonEmptyString 91 | appendString (NonEmptyString s1) s2 = NonEmptyString (s1 <> s2) 92 | 93 | -- | Prepends a string to this non-empty string. Since one of the strings is 94 | -- | non-empty we know the result will be too. 95 | -- | 96 | -- | ```purescript 97 | -- | prependString "be" (NonEmptyString "fore") == NonEmptyString "before" 98 | -- | prependString "" (NonEmptyString "fore") == NonEmptyString "fore" 99 | -- | ``` 100 | prependString :: String -> NonEmptyString -> NonEmptyString 101 | prependString s1 (NonEmptyString s2) = NonEmptyString (s1 <> s2) 102 | 103 | -- | If the string starts with the given prefix, return the portion of the 104 | -- | string left after removing it. If the prefix does not match or there is no 105 | -- | remainder, the result will be `Nothing`. 106 | -- | 107 | -- | ```purescript 108 | -- | stripPrefix (Pattern "http:") (NonEmptyString "http://purescript.org") == Just (NonEmptyString "//purescript.org") 109 | -- | stripPrefix (Pattern "http:") (NonEmptyString "https://purescript.org") == Nothing 110 | -- | stripPrefix (Pattern "Hello!") (NonEmptyString "Hello!") == Nothing 111 | -- | ``` 112 | stripPrefix :: Pattern -> NonEmptyString -> Maybe NonEmptyString 113 | stripPrefix pat = fromString <=< liftS (String.stripPrefix pat) 114 | 115 | -- | If the string ends with the given suffix, return the portion of the 116 | -- | string left after removing it. If the suffix does not match or there is no 117 | -- | remainder, the result will be `Nothing`. 118 | -- | 119 | -- | ```purescript 120 | -- | stripSuffix (Pattern ".exe") (NonEmptyString "purs.exe") == Just (NonEmptyString "purs") 121 | -- | stripSuffix (Pattern ".exe") (NonEmptyString "purs") == Nothing 122 | -- | stripSuffix (Pattern "Hello!") (NonEmptyString "Hello!") == Nothing 123 | -- | ``` 124 | stripSuffix :: Pattern -> NonEmptyString -> Maybe NonEmptyString 125 | stripSuffix pat = fromString <=< liftS (String.stripSuffix pat) 126 | 127 | -- | Checks whether the pattern appears in the given string. 128 | -- | 129 | -- | ```purescript 130 | -- | contains (Pattern "needle") (NonEmptyString "haystack with needle") == true 131 | -- | contains (Pattern "needle") (NonEmptyString "haystack") == false 132 | -- | ``` 133 | contains :: Pattern -> NonEmptyString -> Boolean 134 | contains = liftS <<< String.contains 135 | 136 | -- | Compare two strings in a locale-aware fashion. This is in contrast to 137 | -- | the `Ord` instance on `String` which treats strings as arrays of code 138 | -- | units: 139 | -- | 140 | -- | ```purescript 141 | -- | NonEmptyString "ä" `localeCompare` NonEmptyString "b" == LT 142 | -- | NonEmptyString "ä" `compare` NonEmptyString "b" == GT 143 | -- | ``` 144 | localeCompare :: NonEmptyString -> NonEmptyString -> Ordering 145 | localeCompare (NonEmptyString a) (NonEmptyString b) = String.localeCompare a b 146 | 147 | -- | Replaces the first occurence of the pattern with the replacement string. 148 | -- | 149 | -- | ```purescript 150 | -- | replace (Pattern "<=") (NonEmptyReplacement "≤") (NonEmptyString "a <= b <= c") == NonEmptyString "a ≤ b <= c" 151 | -- | ``` 152 | replace :: Pattern -> NonEmptyReplacement -> NonEmptyString -> NonEmptyString 153 | replace pat (NonEmptyReplacement (NonEmptyString rep)) (NonEmptyString s) = 154 | NonEmptyString (String.replace pat (String.Replacement rep) s) 155 | 156 | -- | Replaces all occurences of the pattern with the replacement string. 157 | -- | 158 | -- | ```purescript 159 | -- | replaceAll (Pattern "<=") (NonEmptyReplacement "≤") (NonEmptyString "a <= b <= c") == NonEmptyString "a ≤ b ≤ c" 160 | -- | ``` 161 | replaceAll :: Pattern -> NonEmptyReplacement -> NonEmptyString -> NonEmptyString 162 | replaceAll pat (NonEmptyReplacement (NonEmptyString rep)) (NonEmptyString s) = 163 | NonEmptyString (String.replaceAll pat (String.Replacement rep) s) 164 | 165 | -- | Returns the argument converted to lowercase. 166 | -- | 167 | -- | ```purescript 168 | -- | toLower (NonEmptyString "hElLo") == NonEmptyString "hello" 169 | -- | ``` 170 | toLower :: NonEmptyString -> NonEmptyString 171 | toLower (NonEmptyString s) = NonEmptyString (String.toLower s) 172 | 173 | -- | Returns the argument converted to uppercase. 174 | -- | 175 | -- | ```purescript 176 | -- | toUpper (NonEmptyString "Hello") == NonEmptyString "HELLO" 177 | -- | ``` 178 | toUpper :: NonEmptyString -> NonEmptyString 179 | toUpper (NonEmptyString s) = NonEmptyString (String.toUpper s) 180 | 181 | -- | Removes whitespace from the beginning and end of a string, including 182 | -- | [whitespace characters](http://www.ecma-international.org/ecma-262/5.1/#sec-7.2) 183 | -- | and [line terminators](http://www.ecma-international.org/ecma-262/5.1/#sec-7.3). 184 | -- | If the string is entirely made up of whitespace the result will be Nothing. 185 | -- | 186 | -- | ```purescript 187 | -- | trim (NonEmptyString " Hello \n World\n\t ") == Just (NonEmptyString "Hello \n World") 188 | -- | trim (NonEmptyString " \n") == Nothing 189 | -- | ``` 190 | trim :: NonEmptyString -> Maybe NonEmptyString 191 | trim (NonEmptyString s) = fromString (String.trim s) 192 | 193 | -- | Joins the strings in a container together as a new string, inserting the 194 | -- | first argument as separator between them. The result is not guaranteed to 195 | -- | be non-empty. 196 | -- | 197 | -- | ```purescript 198 | -- | joinWith ", " [NonEmptyString "apple", NonEmptyString "banana"] == "apple, banana" 199 | -- | joinWith ", " [] == "" 200 | -- | ``` 201 | joinWith :: forall f. Foldable f => String -> f NonEmptyString -> String 202 | joinWith splice = F.intercalate splice <<< coe 203 | where 204 | coe :: f NonEmptyString -> f String 205 | coe = unsafeCoerce 206 | 207 | -- | Joins non-empty strings in a non-empty container together as a new 208 | -- | non-empty string, inserting a possibly empty string as separator between 209 | -- | them. The result is guaranteed to be non-empty. 210 | -- | 211 | -- | ```purescript 212 | -- | -- array syntax is used for demonstration here, it would need to be a real `Foldable1` 213 | -- | join1With ", " [NonEmptyString "apple", NonEmptyString "banana"] == NonEmptyString "apple, banana" 214 | -- | join1With "" [NonEmptyString "apple", NonEmptyString "banana"] == NonEmptyString "applebanana" 215 | -- | ``` 216 | join1With :: forall f. Foldable1 f => String -> f NonEmptyString -> NonEmptyString 217 | join1With splice = NonEmptyString <<< joinWith splice 218 | 219 | -- | Joins possibly empty strings in a non-empty container together as a new 220 | -- | non-empty string, inserting a non-empty string as a separator between them. 221 | -- | The result is guaranteed to be non-empty. 222 | -- | 223 | -- | ```purescript 224 | -- | -- array syntax is used for demonstration here, it would need to be a real `Foldable1` 225 | -- | joinWith1 (NonEmptyString ", ") ["apple", "banana"] == NonEmptyString "apple, banana" 226 | -- | joinWith1 (NonEmptyString "/") ["a", "b", "", "c", ""] == NonEmptyString "a/b//c/" 227 | -- | ``` 228 | joinWith1 :: forall f. Foldable1 f => NonEmptyString -> f String -> NonEmptyString 229 | joinWith1 (NonEmptyString splice) = NonEmptyString <<< F.intercalate splice 230 | 231 | liftS :: forall r. (String -> r) -> NonEmptyString -> r 232 | liftS f (NonEmptyString s) = f s 233 | -------------------------------------------------------------------------------- /src/Data/String/Pattern.purs: -------------------------------------------------------------------------------- 1 | module Data.String.Pattern where 2 | 3 | import Prelude 4 | 5 | import Data.Newtype (class Newtype) 6 | 7 | -- | A newtype used in cases where there is a string to be matched. 8 | -- | 9 | -- | ```purescript 10 | -- | pursPattern = Pattern ".purs" 11 | -- | --can be used like this: 12 | -- | contains pursPattern "Test.purs" 13 | -- | == true 14 | -- | ``` 15 | -- | 16 | newtype Pattern = Pattern String 17 | 18 | derive instance eqPattern :: Eq Pattern 19 | derive instance ordPattern :: Ord Pattern 20 | derive instance newtypePattern :: Newtype Pattern _ 21 | 22 | instance showPattern :: Show Pattern where 23 | show (Pattern s) = "(Pattern " <> show s <> ")" 24 | 25 | -- | A newtype used in cases to specify a replacement for a pattern. 26 | newtype Replacement = Replacement String 27 | 28 | derive instance eqReplacement :: Eq Replacement 29 | derive instance ordReplacement :: Ord Replacement 30 | derive instance newtypeReplacement :: Newtype Replacement _ 31 | 32 | instance showReplacement :: Show Replacement where 33 | show (Replacement s) = "(Replacement " <> show s <> ")" 34 | -------------------------------------------------------------------------------- /src/Data/String/Regex.js: -------------------------------------------------------------------------------- 1 | export const showRegexImpl = function (r) { 2 | return "" + r; 3 | }; 4 | 5 | export const regexImpl = function (left) { 6 | return function (right) { 7 | return function (s1) { 8 | return function (s2) { 9 | try { 10 | return right(new RegExp(s1, s2)); 11 | } catch (e) { 12 | return left(e.message); 13 | } 14 | }; 15 | }; 16 | }; 17 | }; 18 | 19 | export const source = function (r) { 20 | return r.source; 21 | }; 22 | 23 | export const flagsImpl = function (r) { 24 | return { 25 | multiline: r.multiline, 26 | ignoreCase: r.ignoreCase, 27 | global: r.global, 28 | dotAll: r.dotAll, 29 | sticky: !!r.sticky, 30 | unicode: !!r.unicode 31 | }; 32 | }; 33 | 34 | export const test = function (r) { 35 | return function (s) { 36 | var lastIndex = r.lastIndex; 37 | var result = r.test(s); 38 | r.lastIndex = lastIndex; 39 | return result; 40 | }; 41 | }; 42 | 43 | export const _match = function (just) { 44 | return function (nothing) { 45 | return function (r) { 46 | return function (s) { 47 | var m = s.match(r); 48 | if (m == null || m.length === 0) { 49 | return nothing; 50 | } else { 51 | for (var i = 0; i < m.length; i++) { 52 | m[i] = m[i] == null ? nothing : just(m[i]); 53 | } 54 | return just(m); 55 | } 56 | }; 57 | }; 58 | }; 59 | }; 60 | 61 | export const replace = function (r) { 62 | return function (s1) { 63 | return function (s2) { 64 | return s2.replace(r, s1); 65 | }; 66 | }; 67 | }; 68 | 69 | export const _replaceBy = function (just) { 70 | return function (nothing) { 71 | return function (r) { 72 | return function (f) { 73 | return function (s) { 74 | return s.replace(r, function (match) { 75 | var groups = []; 76 | var group, i = 1; 77 | while (typeof (group = arguments[i++]) !== "number") { 78 | groups.push(group == null ? nothing : just(group)); 79 | } 80 | return f(match)(groups); 81 | }); 82 | }; 83 | }; 84 | }; 85 | }; 86 | }; 87 | 88 | export const _search = function (just) { 89 | return function (nothing) { 90 | return function (r) { 91 | return function (s) { 92 | var result = s.search(r); 93 | return result === -1 ? nothing : just(result); 94 | }; 95 | }; 96 | }; 97 | }; 98 | 99 | export const split = function (r) { 100 | return function (s) { 101 | return s.split(r); 102 | }; 103 | }; 104 | -------------------------------------------------------------------------------- /src/Data/String/Regex.purs: -------------------------------------------------------------------------------- 1 | -- | Wraps Javascript's `RegExp` object that enables matching strings with 2 | -- | patterns defined by regular expressions. 3 | -- | For details of the underlying implementation, see [RegExp Reference at MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp). 4 | module Data.String.Regex 5 | ( Regex(..) 6 | , regex 7 | , source 8 | , flags 9 | , renderFlags 10 | , parseFlags 11 | , test 12 | , match 13 | , replace 14 | , replace' 15 | , search 16 | , split 17 | ) where 18 | 19 | import Prelude 20 | 21 | import Data.Array.NonEmpty (NonEmptyArray) 22 | import Data.Either (Either(..)) 23 | import Data.Maybe (Maybe(..)) 24 | import Data.String (contains) 25 | import Data.String.Pattern (Pattern(..)) 26 | import Data.String.Regex.Flags (RegexFlags(..), RegexFlagsRec) 27 | 28 | -- | Wraps Javascript `RegExp` objects. 29 | foreign import data Regex :: Type 30 | 31 | foreign import showRegexImpl :: Regex -> String 32 | 33 | instance showRegex :: Show Regex where 34 | show = showRegexImpl 35 | 36 | foreign import regexImpl 37 | :: (String -> Either String Regex) 38 | -> (Regex -> Either String Regex) 39 | -> String 40 | -> String 41 | -> Either String Regex 42 | 43 | -- | Constructs a `Regex` from a pattern string and flags. Fails with 44 | -- | `Left error` if the pattern contains a syntax error. 45 | regex :: String -> RegexFlags -> Either String Regex 46 | regex s f = regexImpl Left Right s $ renderFlags f 47 | 48 | -- | Returns the pattern string used to construct the given `Regex`. 49 | foreign import source :: Regex -> String 50 | 51 | -- | Returns the `RegexFlags` used to construct the given `Regex`. 52 | flags :: Regex -> RegexFlags 53 | flags = RegexFlags <<< flagsImpl 54 | 55 | -- | Returns the `RegexFlags` inner record used to construct the given `Regex`. 56 | foreign import flagsImpl :: Regex -> RegexFlagsRec 57 | 58 | -- | Returns the string representation of the given `RegexFlags`. 59 | renderFlags :: RegexFlags -> String 60 | renderFlags (RegexFlags f) = 61 | (if f.global then "g" else "") <> 62 | (if f.ignoreCase then "i" else "") <> 63 | (if f.multiline then "m" else "") <> 64 | (if f.dotAll then "s" else "") <> 65 | (if f.sticky then "y" else "") <> 66 | (if f.unicode then "u" else "") 67 | 68 | -- | Parses the string representation of `RegexFlags`. 69 | parseFlags :: String -> RegexFlags 70 | parseFlags s = RegexFlags 71 | { global: contains (Pattern "g") s 72 | , ignoreCase: contains (Pattern "i") s 73 | , multiline: contains (Pattern "m") s 74 | , dotAll: contains (Pattern "s") s 75 | , sticky: contains (Pattern "y") s 76 | , unicode: contains (Pattern "u") s 77 | } 78 | 79 | -- | Returns `true` if the `Regex` matches the string. In contrast to 80 | -- | `RegExp.prototype.test()` in JavaScript, `test` does not affect 81 | -- | the `lastIndex` property of the Regex. 82 | foreign import test :: Regex -> String -> Boolean 83 | 84 | foreign import _match 85 | :: (forall r. r -> Maybe r) 86 | -> (forall r. Maybe r) 87 | -> Regex 88 | -> String 89 | -> Maybe (NonEmptyArray (Maybe String)) 90 | 91 | -- | Matches the string against the `Regex` and returns an array of matches 92 | -- | if there were any. Each match has type `Maybe String`, where `Nothing` 93 | -- | represents an unmatched optional capturing group. 94 | -- | See [reference](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/match). 95 | match :: Regex -> String -> Maybe (NonEmptyArray (Maybe String)) 96 | match = _match Just Nothing 97 | 98 | -- | Replaces occurrences of the `Regex` with the first string. The replacement 99 | -- | string can include special replacement patterns escaped with `"$"`. 100 | -- | See [reference](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace). 101 | foreign import replace :: Regex -> String -> String -> String 102 | 103 | foreign import _replaceBy 104 | :: (forall r. r -> Maybe r) 105 | -> (forall r. Maybe r) 106 | -> Regex 107 | -> (String -> Array (Maybe String) -> String) 108 | -> String 109 | -> String 110 | 111 | -- | Transforms occurrences of the `Regex` using a function of the matched 112 | -- | substring and a list of captured substrings of type `Maybe String`, 113 | -- | where `Nothing` represents an unmatched optional capturing group. 114 | -- | See the [reference](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace#Specifying_a_function_as_a_parameter). 115 | replace' :: Regex -> (String -> Array (Maybe String) -> String) -> String -> String 116 | replace' = _replaceBy Just Nothing 117 | 118 | foreign import _search 119 | :: (forall r. r -> Maybe r) 120 | -> (forall r. Maybe r) 121 | -> Regex 122 | -> String 123 | -> Maybe Int 124 | 125 | -- | Returns `Just` the index of the first match of the `Regex` in the string, 126 | -- | or `Nothing` if there is no match. 127 | search :: Regex -> String -> Maybe Int 128 | search = _search Just Nothing 129 | 130 | -- | Split the string into an array of substrings along occurrences of the `Regex`. 131 | foreign import split :: Regex -> String -> Array String 132 | -------------------------------------------------------------------------------- /src/Data/String/Regex/Flags.purs: -------------------------------------------------------------------------------- 1 | module Data.String.Regex.Flags where 2 | 3 | import Prelude 4 | 5 | import Control.MonadPlus (guard) 6 | import Data.Newtype (class Newtype) 7 | import Data.String (joinWith) 8 | 9 | type RegexFlagsRec = 10 | { global :: Boolean 11 | , ignoreCase :: Boolean 12 | , multiline :: Boolean 13 | , dotAll :: Boolean 14 | , sticky :: Boolean 15 | , unicode :: Boolean 16 | } 17 | 18 | -- | Flags that control matching. 19 | newtype RegexFlags = RegexFlags RegexFlagsRec 20 | 21 | derive instance newtypeRegexFlags :: Newtype RegexFlags _ 22 | 23 | -- | All flags set to false. 24 | noFlags :: RegexFlags 25 | noFlags = RegexFlags 26 | { global: false 27 | , ignoreCase: false 28 | , multiline: false 29 | , dotAll: false 30 | , sticky: false 31 | , unicode: false 32 | } 33 | 34 | -- | Only global flag set to true 35 | global :: RegexFlags 36 | global = RegexFlags 37 | { global: true 38 | , ignoreCase: false 39 | , multiline: false 40 | , dotAll: false 41 | , sticky: false 42 | , unicode: false 43 | } 44 | 45 | -- | Only ignoreCase flag set to true 46 | ignoreCase :: RegexFlags 47 | ignoreCase = RegexFlags 48 | { global: false 49 | , ignoreCase: true 50 | , multiline: false 51 | , dotAll: false 52 | , sticky: false 53 | , unicode: false 54 | } 55 | 56 | -- | Only multiline flag set to true 57 | multiline :: RegexFlags 58 | multiline = RegexFlags 59 | { global: false 60 | , ignoreCase: false 61 | , multiline: true 62 | , dotAll: false 63 | , sticky: false 64 | , unicode: false 65 | } 66 | 67 | -- | Only sticky flag set to true 68 | sticky :: RegexFlags 69 | sticky = RegexFlags 70 | { global: false 71 | , ignoreCase: false 72 | , multiline: false 73 | , dotAll: false 74 | , sticky: true 75 | , unicode: false 76 | } 77 | 78 | -- | Only unicode flag set to true 79 | unicode :: RegexFlags 80 | unicode = RegexFlags 81 | { global: false 82 | , ignoreCase: false 83 | , multiline: false 84 | , dotAll: false 85 | , sticky: false 86 | , unicode: true 87 | } 88 | 89 | -- | Only dotAll flag set to true 90 | dotAll :: RegexFlags 91 | dotAll = RegexFlags 92 | { global: false 93 | , ignoreCase: false 94 | , multiline: false 95 | , dotAll: true 96 | , sticky: false 97 | , unicode: false 98 | } 99 | 100 | instance semigroupRegexFlags :: Semigroup RegexFlags where 101 | append (RegexFlags x) (RegexFlags y) = RegexFlags 102 | { global: x.global || y.global 103 | , ignoreCase: x.ignoreCase || y.ignoreCase 104 | , multiline: x.multiline || y.multiline 105 | , dotAll: x.dotAll || y.dotAll 106 | , sticky: x.sticky || y.sticky 107 | , unicode: x.unicode || y.unicode 108 | } 109 | 110 | instance monoidRegexFlags :: Monoid RegexFlags where 111 | mempty = noFlags 112 | 113 | derive newtype instance eqRegexFlags :: Eq RegexFlags 114 | 115 | instance showRegexFlags :: Show RegexFlags where 116 | show (RegexFlags flags) = 117 | let 118 | usedFlags = 119 | [] 120 | <> (guard flags.global $> "global") 121 | <> (guard flags.ignoreCase $> "ignoreCase") 122 | <> (guard flags.multiline $> "multiline") 123 | <> (guard flags.dotAll $> "dotAll") 124 | <> (guard flags.sticky $> "sticky") 125 | <> (guard flags.unicode $> "unicode") 126 | in 127 | if usedFlags == [] 128 | then "noFlags" 129 | else "(" <> joinWith " <> " usedFlags <> ")" 130 | -------------------------------------------------------------------------------- /src/Data/String/Regex/Unsafe.purs: -------------------------------------------------------------------------------- 1 | module Data.String.Regex.Unsafe 2 | ( unsafeRegex 3 | ) where 4 | 5 | import Control.Category (identity) 6 | import Data.Either (either) 7 | import Data.String.Regex (Regex, regex) 8 | import Data.String.Regex.Flags (RegexFlags) 9 | import Partial.Unsafe (unsafeCrashWith) 10 | 11 | -- | Constructs a `Regex` from a pattern string and flags. Fails with 12 | -- | an exception if the pattern contains a syntax error. 13 | unsafeRegex :: String -> RegexFlags -> Regex 14 | unsafeRegex s f = either unsafeCrashWith identity (regex s f) 15 | -------------------------------------------------------------------------------- /src/Data/String/Unsafe.js: -------------------------------------------------------------------------------- 1 | export const charAt = function (i) { 2 | return function (s) { 3 | if (i >= 0 && i < s.length) return s.charAt(i); 4 | throw new Error("Data.String.Unsafe.charAt: Invalid index."); 5 | }; 6 | }; 7 | 8 | export const char = function (s) { 9 | if (s.length === 1) return s.charAt(0); 10 | throw new Error("Data.String.Unsafe.char: Expected string of length 1."); 11 | }; 12 | -------------------------------------------------------------------------------- /src/Data/String/Unsafe.purs: -------------------------------------------------------------------------------- 1 | -- | Unsafe string and character functions. 2 | module Data.String.Unsafe 3 | ( char 4 | , charAt 5 | ) where 6 | 7 | -- | Returns the character at the given index. 8 | -- | 9 | -- | **Unsafe:** throws runtime exception if the index is out of bounds. 10 | foreign import charAt :: Int -> String -> Char 11 | 12 | -- | Converts a string of length `1` to a character. 13 | -- | 14 | -- | **Unsafe:** throws runtime exception if length is not `1`. 15 | foreign import char :: String -> Char 16 | -------------------------------------------------------------------------------- /test/Test/Data/String.purs: -------------------------------------------------------------------------------- 1 | module Test.Data.String (testString) where 2 | 3 | import Prelude 4 | 5 | import Data.Maybe (Maybe(..)) 6 | import Data.String as S 7 | import Data.String.Pattern (Pattern(..), Replacement(..)) 8 | import Effect (Effect) 9 | import Effect.Console (log) 10 | import Test.Assert (assert, assertEqual) 11 | 12 | testString :: Effect Unit 13 | testString = do 14 | 15 | log "null" 16 | assert $ S.null "" 17 | assert $ not (S.null "a") 18 | 19 | log "stripPrefix" 20 | -- this is a re-export from Data.String.CodeUnits, so the majority of tests are in there 21 | assertEqual 22 | { actual: S.stripPrefix (Pattern "𝕒𝕓𝕔") "𝕒𝕓𝕔𝕕𝕖" 23 | , expected: Just "𝕕𝕖" 24 | } 25 | 26 | log "stripSuffix" 27 | -- this is a re-export from Data.String.CodeUnits, so the majority of tests are in there 28 | assertEqual 29 | { actual: S.stripSuffix (Pattern "𝕔𝕕𝕖") "𝕒𝕓𝕔𝕕𝕖" 30 | , expected: Just "𝕒𝕓" 31 | } 32 | 33 | log "contains" 34 | assert $ S.contains (Pattern "") "" 35 | assert $ S.contains (Pattern "") "abcd" 36 | assert $ S.contains (Pattern "bc") "abcd" 37 | assert $ not S.contains (Pattern "cb") "abcd" 38 | 39 | log "localeCompare" 40 | assertEqual 41 | { actual: S.localeCompare "" "" 42 | , expected: EQ 43 | } 44 | assertEqual 45 | { actual: S.localeCompare "a" "a" 46 | , expected: EQ 47 | } 48 | assertEqual 49 | { actual: S.localeCompare "a" "b" 50 | , expected: LT 51 | } 52 | assertEqual 53 | { actual: S.localeCompare "b" "a" 54 | , expected: GT 55 | } 56 | 57 | log "replace" 58 | assertEqual 59 | { actual: S.replace (Pattern "b") (Replacement "") "abc" 60 | , expected: "ac" 61 | } 62 | assertEqual 63 | { actual: S.replace (Pattern "b") (Replacement "!") "abc" 64 | , expected: "a!c" 65 | } 66 | assertEqual 67 | { actual: S.replace (Pattern "d") (Replacement "!") "abc" 68 | , expected: "abc" 69 | } 70 | 71 | log "replaceAll" 72 | assertEqual 73 | { actual: S.replaceAll (Pattern "b") (Replacement "") "abbbbbc" 74 | , expected: "ac" 75 | } 76 | assertEqual 77 | { actual: S.replaceAll (Pattern "[b]") (Replacement "!") "a[b]c" 78 | , expected: "a!c" 79 | } 80 | 81 | log "split" 82 | assertEqual 83 | { actual: S.split (Pattern "") "" 84 | , expected: [] 85 | } 86 | assertEqual 87 | { actual: S.split (Pattern "") "a" 88 | , expected: ["a"] 89 | } 90 | assertEqual 91 | { actual: S.split (Pattern "") "ab" 92 | , expected: ["a", "b"] 93 | } 94 | assertEqual 95 | { actual: S.split (Pattern "b") "aabcc" 96 | , expected: ["aa", "cc"] 97 | } 98 | assertEqual 99 | { actual: S.split (Pattern "d") "abc" 100 | , expected: ["abc"] 101 | } 102 | 103 | log "toLower" 104 | assertEqual 105 | { actual: S.toLower "bAtMaN" 106 | , expected: "batman" 107 | } 108 | 109 | log "toUpper" 110 | assertEqual 111 | { actual: S.toUpper "bAtMaN" 112 | , expected: "BATMAN" 113 | } 114 | 115 | log "trim" 116 | assertEqual 117 | { actual: S.trim " abc " 118 | , expected: "abc" 119 | } 120 | 121 | log "joinWith" 122 | assertEqual 123 | { actual: S.joinWith "" [] 124 | , expected: "" 125 | } 126 | assertEqual 127 | { actual: S.joinWith "" ["a", "b"] 128 | , expected: "ab" 129 | } 130 | assertEqual 131 | { actual: S.joinWith "--" ["a", "b", "c"] 132 | , expected: "a--b--c" 133 | } 134 | -------------------------------------------------------------------------------- /test/Test/Data/String/CaseInsensitive.purs: -------------------------------------------------------------------------------- 1 | module Test.Data.String.CaseInsensitive (testCaseInsensitiveString) where 2 | 3 | import Prelude 4 | 5 | import Data.String.CaseInsensitive (CaseInsensitiveString(..)) 6 | import Effect (Effect) 7 | import Effect.Console (log) 8 | import Test.Assert (assertEqual) 9 | 10 | testCaseInsensitiveString :: Effect Unit 11 | testCaseInsensitiveString = do 12 | log "equality" 13 | assertEqual 14 | { actual: CaseInsensitiveString "aB" 15 | , expected: CaseInsensitiveString "AB" 16 | } 17 | 18 | log "comparison" 19 | assertEqual 20 | { actual: compare (CaseInsensitiveString "qwerty") (CaseInsensitiveString "QWERTY") 21 | , expected: EQ 22 | } 23 | -------------------------------------------------------------------------------- /test/Test/Data/String/CodePoints.purs: -------------------------------------------------------------------------------- 1 | module Test.Data.String.CodePoints (testStringCodePoints) where 2 | 3 | import Prelude 4 | 5 | import Data.Enum (fromEnum, toEnum) 6 | import Data.Maybe (Maybe(..), fromJust) 7 | import Data.String.CodePoints as SCP 8 | import Data.String.Pattern (Pattern(..)) 9 | import Effect (Effect) 10 | import Effect.Console (log) 11 | import Partial.Unsafe (unsafePartial) 12 | import Test.Assert (assertEqual) 13 | 14 | str :: String 15 | str = "a\xDC00\xD800\xD800\x16805\x16A06z" 16 | 17 | testStringCodePoints :: Effect Unit 18 | testStringCodePoints = do 19 | 20 | log "show" 21 | assertEqual 22 | { actual: map show (SCP.codePointAt 0 str) 23 | , expected: Just "(CodePoint 0x61)" 24 | } 25 | assertEqual 26 | { actual: map show (SCP.codePointAt 1 str) 27 | , expected: Just "(CodePoint 0xDC00)" 28 | } 29 | assertEqual 30 | { actual: map show (SCP.codePointAt 2 str) 31 | , expected: Just "(CodePoint 0xD800)" 32 | } 33 | assertEqual 34 | { actual: map show (SCP.codePointAt 3 str) 35 | , expected: Just "(CodePoint 0xD800)" 36 | } 37 | assertEqual 38 | { actual: map show (SCP.codePointAt 4 str) 39 | , expected: Just "(CodePoint 0x16805)" 40 | } 41 | assertEqual 42 | { actual: map show (SCP.codePointAt 5 str) 43 | , expected: Just "(CodePoint 0x16A06)" 44 | } 45 | assertEqual 46 | { actual: map show (SCP.codePointAt 6 str) 47 | , expected: Just "(CodePoint 0x7A)" 48 | } 49 | 50 | log "codePointFromChar" 51 | assertEqual 52 | { actual: Just (SCP.codePointFromChar 'A') 53 | , expected: (toEnum 65) 54 | } 55 | assertEqual 56 | { actual: (SCP.codePointFromChar <$> toEnum 0) 57 | , expected: toEnum 0 58 | } 59 | assertEqual 60 | { actual: (SCP.codePointFromChar <$> toEnum 0xFFFF) 61 | , expected: toEnum 0xFFFF 62 | } 63 | 64 | log "singleton" 65 | assertEqual 66 | { actual: (SCP.singleton <$> toEnum 0x30) 67 | , expected: Just "0" 68 | } 69 | assertEqual 70 | { actual: (SCP.singleton <$> toEnum 0x16805) 71 | , expected: Just "\x16805" 72 | } 73 | 74 | log "codePointAt" 75 | assertEqual 76 | { actual: SCP.codePointAt (-1) str 77 | , expected: Nothing 78 | } 79 | assertEqual 80 | { actual: SCP.codePointAt 0 str 81 | , expected: (toEnum 0x61) 82 | } 83 | assertEqual 84 | { actual: SCP.codePointAt 1 str 85 | , expected: (toEnum 0xDC00) 86 | } 87 | assertEqual 88 | { actual: SCP.codePointAt 2 str 89 | , expected: (toEnum 0xD800) 90 | } 91 | assertEqual 92 | { actual: SCP.codePointAt 3 str 93 | , expected: (toEnum 0xD800) 94 | } 95 | assertEqual 96 | { actual: SCP.codePointAt 4 str 97 | , expected: (toEnum 0x16805) 98 | } 99 | assertEqual 100 | { actual: SCP.codePointAt 5 str 101 | , expected: (toEnum 0x16A06) 102 | } 103 | assertEqual 104 | { actual: SCP.codePointAt 6 str 105 | , expected: (toEnum 0x7A) 106 | } 107 | assertEqual 108 | { actual: SCP.codePointAt 7 str 109 | , expected: Nothing 110 | } 111 | 112 | log "uncons" 113 | assertEqual 114 | { actual: SCP.uncons str 115 | , expected: Just {head: cp 0x61, tail: "\xDC00\xD800\xD800\x16805\x16A06z"} 116 | } 117 | assertEqual 118 | { actual: SCP.uncons (SCP.drop 1 str) 119 | , expected: Just {head: cp 0xDC00, tail: "\xD800\xD800\x16805\x16A06z"} 120 | } 121 | assertEqual 122 | { actual: SCP.uncons (SCP.drop 2 str) 123 | , expected: Just {head: cp 0xD800, tail: "\xD800\x16805\x16A06z"} 124 | } 125 | assertEqual 126 | { actual: SCP.uncons (SCP.drop 3 str) 127 | , expected: Just {head: cp 0xD800, tail: "\x16805\x16A06z"} 128 | } 129 | assertEqual 130 | { actual: SCP.uncons (SCP.drop 4 str) 131 | , expected: Just {head: cp 0x16805, tail: "\x16A06z"} 132 | } 133 | assertEqual 134 | { actual: SCP.uncons (SCP.drop 5 str) 135 | , expected: Just {head: cp 0x16A06, tail: "z"} 136 | } 137 | assertEqual 138 | { actual: SCP.uncons (SCP.drop 6 str) 139 | , expected: Just {head: cp 0x7A, tail: ""} 140 | } 141 | assertEqual 142 | { actual: SCP.uncons "" 143 | , expected: Nothing 144 | } 145 | 146 | log "length" 147 | assertEqual 148 | { actual: SCP.length "" 149 | , expected: 0 150 | } 151 | assertEqual 152 | { actual: SCP.length "a" 153 | , expected: 1 154 | } 155 | assertEqual 156 | { actual: SCP.length "ab" 157 | , expected: 2 158 | } 159 | assertEqual 160 | { actual: SCP.length str 161 | , expected: 7 162 | } 163 | 164 | log "countPrefix" 165 | assertEqual 166 | { actual: SCP.countPrefix (\_ -> true) "" 167 | , expected: 0 168 | } 169 | assertEqual 170 | { actual: SCP.countPrefix (\_ -> false) str 171 | , expected: 0 172 | } 173 | assertEqual 174 | { actual: SCP.countPrefix (\_ -> true) str 175 | , expected: 7 176 | } 177 | assertEqual 178 | { actual: SCP.countPrefix (\x -> fromEnum x < 0xFFFF) str 179 | , expected: 4 180 | } 181 | assertEqual 182 | { actual: SCP.countPrefix (\x -> fromEnum x < 0xDC00) str 183 | , expected: 1 184 | } 185 | 186 | log "indexOf" 187 | assertEqual 188 | { actual: SCP.indexOf (Pattern "") "" 189 | , expected: Just 0 190 | } 191 | assertEqual 192 | { actual: SCP.indexOf (Pattern "") str 193 | , expected: Just 0 194 | } 195 | assertEqual 196 | { actual: SCP.indexOf (Pattern str) str 197 | , expected: Just 0 198 | } 199 | assertEqual 200 | { actual: SCP.indexOf (Pattern "a") str 201 | , expected: Just 0 202 | } 203 | assertEqual 204 | { actual: SCP.indexOf (Pattern "\xDC00\xD800\xD800") str 205 | , expected: Just 1 206 | } 207 | assertEqual 208 | { actual: SCP.indexOf (Pattern "\xD800") str 209 | , expected: Just 2 210 | } 211 | assertEqual 212 | { actual: SCP.indexOf (Pattern "\xD800\xD800") str 213 | , expected: Just 2 214 | } 215 | assertEqual 216 | { actual: SCP.indexOf (Pattern "\xD800\xD81A") str 217 | , expected: Just 3 218 | } 219 | assertEqual 220 | { actual: SCP.indexOf (Pattern "\xD800\x16805") str 221 | , expected: Just 3 222 | } 223 | assertEqual 224 | { actual: SCP.indexOf (Pattern "\x16805") str 225 | , expected: Just 4 226 | } 227 | assertEqual 228 | { actual: SCP.indexOf (Pattern "\x16A06") str 229 | , expected: Just 5 230 | } 231 | assertEqual 232 | { actual: SCP.indexOf (Pattern "z") str 233 | , expected: Just 6 234 | } 235 | assertEqual 236 | { actual: SCP.indexOf (Pattern "\n") str 237 | , expected: Nothing 238 | } 239 | assertEqual 240 | { actual: SCP.indexOf (Pattern "\xD81A") str 241 | , expected: Just 4 242 | } 243 | 244 | log "indexOf'" 245 | assertEqual 246 | { actual: SCP.indexOf' (Pattern "") 0 "" 247 | , expected: Just 0 248 | } 249 | assertEqual 250 | { actual: SCP.indexOf' (Pattern str) 0 str 251 | , expected: Just 0 252 | } 253 | assertEqual 254 | { actual: SCP.indexOf' (Pattern str) 1 str 255 | , expected: Nothing 256 | } 257 | assertEqual 258 | { actual: SCP.indexOf' (Pattern "a") 0 str 259 | , expected: Just 0 260 | } 261 | assertEqual 262 | { actual: SCP.indexOf' (Pattern "a") 1 str 263 | , expected: Nothing 264 | } 265 | assertEqual 266 | { actual: SCP.indexOf' (Pattern "z") 0 str 267 | , expected: Just 6 268 | } 269 | assertEqual 270 | { actual: SCP.indexOf' (Pattern "z") 1 str 271 | , expected: Just 6 272 | } 273 | assertEqual 274 | { actual: SCP.indexOf' (Pattern "z") 2 str 275 | , expected: Just 6 276 | } 277 | assertEqual 278 | { actual: SCP.indexOf' (Pattern "z") 3 str 279 | , expected: Just 6 280 | } 281 | assertEqual 282 | { actual: SCP.indexOf' (Pattern "z") 4 str 283 | , expected: Just 6 284 | } 285 | assertEqual 286 | { actual: SCP.indexOf' (Pattern "z") 5 str 287 | , expected: Just 6 288 | } 289 | assertEqual 290 | { actual: SCP.indexOf' (Pattern "z") 6 str 291 | , expected: Just 6 292 | } 293 | assertEqual 294 | { actual: SCP.indexOf' (Pattern "z") 7 str 295 | , expected: Nothing 296 | } 297 | 298 | log "lastIndexOf" 299 | assertEqual 300 | { actual: SCP.lastIndexOf (Pattern "") "" 301 | , expected: Just 0 302 | } 303 | assertEqual 304 | { actual: SCP.lastIndexOf (Pattern "") str 305 | , expected: Just 7 306 | } 307 | assertEqual 308 | { actual: SCP.lastIndexOf (Pattern str) str 309 | , expected: Just 0 310 | } 311 | assertEqual 312 | { actual: SCP.lastIndexOf (Pattern "a") str 313 | , expected: Just 0 314 | } 315 | assertEqual 316 | { actual: SCP.lastIndexOf (Pattern "\xDC00\xD800\xD800") str 317 | , expected: Just 1 318 | } 319 | assertEqual 320 | { actual: SCP.lastIndexOf (Pattern "\xD800") str 321 | , expected: Just 3 322 | } 323 | assertEqual 324 | { actual: SCP.lastIndexOf (Pattern "\xD800\xD800") str 325 | , expected: Just 2 326 | } 327 | assertEqual 328 | { actual: SCP.lastIndexOf (Pattern "\xD800\xD81A") str 329 | , expected: Just 3 330 | } 331 | assertEqual 332 | { actual: SCP.lastIndexOf (Pattern "\xD800\x16805") str 333 | , expected: Just 3 334 | } 335 | assertEqual 336 | { actual: SCP.lastIndexOf (Pattern "\x16805") str 337 | , expected: Just 4 338 | } 339 | assertEqual 340 | { actual: SCP.lastIndexOf (Pattern "\x16A06") str 341 | , expected: Just 5 342 | } 343 | assertEqual 344 | { actual: SCP.lastIndexOf (Pattern "z") str 345 | , expected: Just 6 346 | } 347 | assertEqual 348 | { actual: SCP.lastIndexOf (Pattern "\n") str 349 | , expected: Nothing 350 | } 351 | assertEqual 352 | { actual: SCP.lastIndexOf (Pattern "\xD81A") str 353 | , expected: Just 5 354 | } 355 | 356 | log "lastIndexOf'" 357 | assertEqual 358 | { actual: SCP.lastIndexOf' (Pattern "") 0 "" 359 | , expected: Just 0 360 | } 361 | assertEqual 362 | { actual: SCP.lastIndexOf' (Pattern str) 0 str 363 | , expected: Just 0 364 | } 365 | assertEqual 366 | { actual: SCP.lastIndexOf' (Pattern str) 1 str 367 | , expected: Just 0 368 | } 369 | assertEqual 370 | { actual: SCP.lastIndexOf' (Pattern "a") (-1) str 371 | , expected: Just 0 372 | } 373 | assertEqual 374 | { actual: SCP.lastIndexOf' (Pattern "a") 0 str 375 | , expected: Just 0 376 | } 377 | assertEqual 378 | { actual: SCP.lastIndexOf' (Pattern "a") 7 str 379 | , expected: Just 0 380 | } 381 | assertEqual 382 | { actual: SCP.lastIndexOf' (Pattern "a") (SCP.length str) str 383 | , expected: Just 0 384 | } 385 | assertEqual 386 | { actual: SCP.lastIndexOf' (Pattern "z") 0 str 387 | , expected: Nothing 388 | } 389 | assertEqual 390 | { actual: SCP.lastIndexOf' (Pattern "z") 1 str 391 | , expected: Nothing 392 | } 393 | assertEqual 394 | { actual: SCP.lastIndexOf' (Pattern "z") 2 str 395 | , expected: Nothing 396 | } 397 | assertEqual 398 | { actual: SCP.lastIndexOf' (Pattern "z") 3 str 399 | , expected: Nothing 400 | } 401 | assertEqual 402 | { actual: SCP.lastIndexOf' (Pattern "z") 4 str 403 | , expected: Nothing 404 | } 405 | assertEqual 406 | { actual: SCP.lastIndexOf' (Pattern "z") 5 str 407 | , expected: Nothing 408 | } 409 | assertEqual 410 | { actual: SCP.lastIndexOf' (Pattern "z") 6 str 411 | , expected: Just 6 412 | } 413 | assertEqual 414 | { actual: SCP.lastIndexOf' (Pattern "z") 7 str 415 | , expected: Just 6 416 | } 417 | assertEqual 418 | { actual: SCP.lastIndexOf' (Pattern "\xD800") 7 str 419 | , expected: Just 3 420 | } 421 | assertEqual 422 | { actual: SCP.lastIndexOf' (Pattern "\xD800") 6 str 423 | , expected: Just 3 424 | } 425 | assertEqual 426 | { actual: SCP.lastIndexOf' (Pattern "\xD800") 5 str 427 | , expected: Just 3 428 | } 429 | assertEqual 430 | { actual: SCP.lastIndexOf' (Pattern "\xD800") 4 str 431 | , expected: Just 3 432 | } 433 | assertEqual 434 | { actual: SCP.lastIndexOf' (Pattern "\xD800") 3 str 435 | , expected: Just 3 436 | } 437 | assertEqual 438 | { actual: SCP.lastIndexOf' (Pattern "\xD800") 2 str 439 | , expected: Just 2 440 | } 441 | assertEqual 442 | { actual: SCP.lastIndexOf' (Pattern "\xD800") 1 str 443 | , expected: Nothing 444 | } 445 | assertEqual 446 | { actual: SCP.lastIndexOf' (Pattern "\xD800") 0 str 447 | , expected: Nothing 448 | } 449 | assertEqual 450 | { actual: SCP.lastIndexOf' (Pattern "\x16A06") 7 str 451 | , expected: Just 5 452 | } 453 | assertEqual 454 | { actual: SCP.lastIndexOf' (Pattern "\x16A06") 6 str 455 | , expected: Just 5 456 | } 457 | assertEqual 458 | { actual: SCP.lastIndexOf' (Pattern "\x16A06") 5 str 459 | , expected: Just 5 460 | } 461 | assertEqual 462 | { actual: SCP.lastIndexOf' (Pattern "\x16A06") 4 str 463 | , expected: Nothing 464 | } 465 | assertEqual 466 | { actual: SCP.lastIndexOf' (Pattern "\x16A06") 3 str 467 | , expected: Nothing 468 | } 469 | 470 | log "take" 471 | assertEqual 472 | { actual: SCP.take (-1) str 473 | , expected: "" 474 | } 475 | assertEqual 476 | { actual: SCP.take 0 str 477 | , expected: "" 478 | } 479 | assertEqual 480 | { actual: SCP.take 1 str 481 | , expected: "a" 482 | } 483 | assertEqual 484 | { actual: SCP.take 2 str 485 | , expected: "a\xDC00" 486 | } 487 | assertEqual 488 | { actual: SCP.take 3 str 489 | , expected: "a\xDC00\xD800" 490 | } 491 | assertEqual 492 | { actual: SCP.take 4 str 493 | , expected: "a\xDC00\xD800\xD800" 494 | } 495 | assertEqual 496 | { actual: SCP.take 5 str 497 | , expected: "a\xDC00\xD800\xD800\x16805" 498 | } 499 | assertEqual 500 | { actual: SCP.take 6 str 501 | , expected: "a\xDC00\xD800\xD800\x16805\x16A06" 502 | } 503 | assertEqual 504 | { actual: SCP.take 7 str 505 | , expected: str 506 | } 507 | assertEqual 508 | { actual: SCP.take 8 str 509 | , expected: str 510 | } 511 | 512 | log "takeWhile" 513 | assertEqual 514 | { actual: SCP.takeWhile (\_ -> true) str 515 | , expected: str 516 | } 517 | assertEqual 518 | { actual: SCP.takeWhile (\_ -> false) str 519 | , expected: "" 520 | } 521 | assertEqual 522 | { actual: SCP.takeWhile (\c -> fromEnum c < 0xFFFF) str 523 | , expected: "a\xDC00\xD800\xD800" 524 | } 525 | assertEqual 526 | { actual: SCP.takeWhile (\c -> fromEnum c < 0xDC00) str 527 | , expected: "a" 528 | } 529 | 530 | log "drop" 531 | assertEqual 532 | { actual: SCP.drop (-1) str 533 | , expected: str 534 | } 535 | assertEqual 536 | { actual: SCP.drop 0 str 537 | , expected: str 538 | } 539 | assertEqual 540 | { actual: SCP.drop 1 str 541 | , expected: "\xDC00\xD800\xD800\x16805\x16A06z" 542 | } 543 | assertEqual 544 | { actual: SCP.drop 2 str 545 | , expected: "\xD800\xD800\x16805\x16A06z" 546 | } 547 | assertEqual 548 | { actual: SCP.drop 3 str 549 | , expected: "\xD800\x16805\x16A06z" 550 | } 551 | assertEqual 552 | { actual: SCP.drop 4 str 553 | , expected: "\x16805\x16A06z" 554 | } 555 | assertEqual 556 | { actual: SCP.drop 5 str 557 | , expected: "\x16A06z" 558 | } 559 | assertEqual 560 | { actual: SCP.drop 6 str 561 | , expected: "z" 562 | } 563 | assertEqual 564 | { actual: SCP.drop 7 str 565 | , expected: "" 566 | } 567 | assertEqual 568 | { actual: SCP.drop 8 str 569 | , expected: "" 570 | } 571 | 572 | log "dropWhile" 573 | assertEqual 574 | { actual: SCP.dropWhile (\_ -> true) str 575 | , expected: "" 576 | } 577 | assertEqual 578 | { actual: SCP.dropWhile (\_ -> false) str 579 | , expected: str 580 | } 581 | assertEqual 582 | { actual: SCP.dropWhile (\c -> fromEnum c < 0xFFFF) str 583 | , expected: "\x16805\x16A06z" 584 | } 585 | assertEqual 586 | { actual: SCP.dropWhile (\c -> fromEnum c < 0xDC00) str 587 | , expected: "\xDC00\xD800\xD800\x16805\x16A06z" 588 | } 589 | 590 | log "splitAt" 591 | assertEqual 592 | { actual: SCP.splitAt 0 "" 593 | , expected: {before: "", after: "" } 594 | } 595 | assertEqual 596 | { actual: SCP.splitAt 1 "" 597 | , expected: {before: "", after: "" } 598 | } 599 | assertEqual 600 | { actual: SCP.splitAt 0 "a" 601 | , expected: {before: "", after: "a"} 602 | } 603 | assertEqual 604 | { actual: SCP.splitAt 1 "ab" 605 | , expected: {before: "a", after: "b"} 606 | } 607 | assertEqual 608 | { actual: SCP.splitAt 3 "aabcc" 609 | , expected: {before: "aab", after: "cc"} 610 | } 611 | assertEqual 612 | { actual: SCP.splitAt (-1) "abc" 613 | , expected: {before: "", after: "abc"} 614 | } 615 | assertEqual 616 | { actual: SCP.splitAt 0 str 617 | , expected: {before: "", after: str} 618 | } 619 | assertEqual 620 | { actual: SCP.splitAt 1 str 621 | , expected: {before: "a", after: "\xDC00\xD800\xD800\x16805\x16A06z"} 622 | } 623 | assertEqual 624 | { actual: SCP.splitAt 2 str 625 | , expected: {before: "a\xDC00", after: "\xD800\xD800\x16805\x16A06z"} 626 | } 627 | assertEqual 628 | { actual: SCP.splitAt 3 str 629 | , expected: {before: "a\xDC00\xD800", after: "\xD800\x16805\x16A06z"} 630 | } 631 | assertEqual 632 | { actual: SCP.splitAt 4 str 633 | , expected: {before: "a\xDC00\xD800\xD800", after: "\x16805\x16A06z"} 634 | } 635 | assertEqual 636 | { actual: SCP.splitAt 5 str 637 | , expected: {before: "a\xDC00\xD800\xD800\x16805", after: "\x16A06z"} 638 | } 639 | assertEqual 640 | { actual: SCP.splitAt 6 str 641 | , expected: {before: "a\xDC00\xD800\xD800\x16805\x16A06", after: "z"} 642 | } 643 | assertEqual 644 | { actual: SCP.splitAt 7 str 645 | , expected: {before: str, after: ""} 646 | } 647 | assertEqual 648 | { actual: SCP.splitAt 8 str 649 | , expected: {before: str, after: ""} 650 | } 651 | 652 | cp :: Int -> SCP.CodePoint 653 | cp = unsafePartial fromJust <<< toEnum 654 | -------------------------------------------------------------------------------- /test/Test/Data/String/CodeUnits.purs: -------------------------------------------------------------------------------- 1 | module Test.Data.String.CodeUnits (testStringCodeUnits) where 2 | 3 | import Prelude 4 | 5 | import Data.Enum (fromEnum) 6 | import Data.Maybe (Maybe(..), isNothing) 7 | import Data.String.CodeUnits as SCU 8 | import Data.String.Pattern (Pattern(..)) 9 | import Effect (Effect) 10 | import Effect.Console (log) 11 | import Test.Assert (assert, assertEqual) 12 | 13 | testStringCodeUnits :: Effect Unit 14 | testStringCodeUnits = do 15 | log "stripPrefix" 16 | assertEqual 17 | { actual: SCU.stripPrefix (Pattern "abc") "abcde" 18 | , expected: Just "de" 19 | } 20 | assertEqual 21 | { actual: SCU.stripPrefix (Pattern "xyz") "abcde" 22 | , expected: Nothing 23 | } 24 | assertEqual 25 | { actual: SCU.stripPrefix (Pattern "abcd") "ab" 26 | , expected: Nothing 27 | } 28 | assertEqual 29 | { actual: SCU.stripPrefix (Pattern "abc") "abc" 30 | , expected: Just "" 31 | } 32 | assertEqual 33 | { actual: SCU.stripPrefix (Pattern "") "abc" 34 | , expected: Just "abc" 35 | } 36 | assertEqual 37 | { actual: SCU.stripPrefix (Pattern "") "" 38 | , expected: Just "" 39 | } 40 | 41 | log "stripSuffix" 42 | assertEqual 43 | { actual: SCU.stripSuffix (Pattern "cde") "abcde" 44 | , expected: Just "ab" 45 | } 46 | assertEqual 47 | { actual: SCU.stripSuffix (Pattern "xyz") "abcde" 48 | , expected: Nothing 49 | } 50 | assertEqual 51 | { actual: SCU.stripSuffix (Pattern "abcd") "cd" 52 | , expected: Nothing 53 | } 54 | assertEqual 55 | { actual: SCU.stripSuffix (Pattern "abc") "abc" 56 | , expected: Just "" 57 | } 58 | assertEqual 59 | { actual: SCU.stripSuffix (Pattern "") "abc" 60 | , expected: Just "abc" 61 | } 62 | assertEqual 63 | { actual: SCU.stripSuffix (Pattern "") "" 64 | , expected: Just "" 65 | } 66 | 67 | log "charAt" 68 | assertEqual 69 | { actual: SCU.charAt 0 "" 70 | , expected: Nothing 71 | } 72 | assertEqual 73 | { actual: SCU.charAt 0 "a" 74 | , expected: Just 'a' 75 | } 76 | assertEqual 77 | { actual: SCU.charAt 1 "a" 78 | , expected: Nothing 79 | } 80 | assertEqual 81 | { actual: SCU.charAt 0 "ab" 82 | , expected: Just 'a' 83 | } 84 | assertEqual 85 | { actual: SCU.charAt 1 "ab" 86 | , expected: Just 'b' 87 | } 88 | assertEqual 89 | { actual: SCU.charAt 2 "ab" 90 | , expected: Nothing 91 | } 92 | 93 | log "singleton" 94 | assertEqual 95 | { actual: SCU.singleton 'a' 96 | , expected: "a" 97 | } 98 | 99 | log "charCodeAt" 100 | assertEqual 101 | { actual: (fromEnum <$> SCU.charAt 0 "") 102 | , expected: Nothing 103 | } 104 | assertEqual 105 | { actual: (fromEnum <$> SCU.charAt 0 "a") 106 | , expected: Just 97 107 | } 108 | assertEqual 109 | { actual: (fromEnum <$> SCU.charAt 1 "a") 110 | , expected: Nothing 111 | } 112 | assertEqual 113 | { actual: (fromEnum <$> SCU.charAt 0 "ab") 114 | , expected: Just 97 115 | } 116 | assertEqual 117 | { actual: (fromEnum <$> SCU.charAt 1 "ab") 118 | , expected: Just 98 119 | } 120 | assertEqual 121 | { actual: (fromEnum <$> SCU.charAt 2 "ab") 122 | , expected: Nothing 123 | } 124 | 125 | log "toChar" 126 | assertEqual 127 | { actual: SCU.toChar "" 128 | , expected: Nothing 129 | } 130 | assertEqual 131 | { actual: SCU.toChar "a" 132 | , expected: Just 'a' 133 | } 134 | assertEqual 135 | { actual: SCU.toChar "ab" 136 | , expected: Nothing 137 | } 138 | 139 | log "uncons" 140 | assert $ isNothing (SCU.uncons "") 141 | assertEqual 142 | { actual: SCU.uncons "a" 143 | , expected: Just { head: 'a', tail: "" } 144 | } 145 | assertEqual 146 | { actual: SCU.uncons "ab" 147 | , expected: Just { head: 'a', tail: "b" } 148 | } 149 | 150 | log "takeWhile" 151 | assertEqual 152 | { actual: SCU.takeWhile (\c -> true) "abc" 153 | , expected: "abc" 154 | } 155 | assertEqual 156 | { actual: SCU.takeWhile (\c -> false) "abc" 157 | , expected: "" 158 | } 159 | assertEqual 160 | { actual: SCU.takeWhile (\c -> c /= 'b') "aabbcc" 161 | , expected: "aa" 162 | } 163 | 164 | log "dropWhile" 165 | assertEqual 166 | { actual: SCU.dropWhile (\c -> true) "abc" 167 | , expected: "" 168 | } 169 | assertEqual 170 | { actual: SCU.dropWhile (\c -> false) "abc" 171 | , expected: "abc" 172 | } 173 | assertEqual 174 | { actual: SCU.dropWhile (\c -> c /= 'b') "aabbcc" 175 | , expected: "bbcc" 176 | } 177 | 178 | log "fromCharArray" 179 | assertEqual 180 | { actual: SCU.fromCharArray [] 181 | , expected: "" 182 | } 183 | assertEqual 184 | { actual: SCU.fromCharArray ['a', 'b'] 185 | , expected: "ab" 186 | } 187 | 188 | log "indexOf" 189 | assertEqual 190 | { actual: SCU.indexOf (Pattern "") "" 191 | , expected: Just 0 192 | } 193 | assertEqual 194 | { actual: SCU.indexOf (Pattern "") "abcd" 195 | , expected: Just 0 196 | } 197 | assertEqual 198 | { actual: SCU.indexOf (Pattern "bc") "abcd" 199 | , expected: Just 1 200 | } 201 | assertEqual 202 | { actual: SCU.indexOf (Pattern "cb") "abcd" 203 | , expected: Nothing 204 | } 205 | 206 | log "indexOf'" 207 | assertEqual 208 | { actual: SCU.indexOf' (Pattern "") 0 "" 209 | , expected: Just 0 210 | } 211 | assertEqual 212 | { actual: SCU.indexOf' (Pattern "") (-1) "ab" 213 | , expected: Nothing 214 | } 215 | assertEqual 216 | { actual: SCU.indexOf' (Pattern "") 0 "ab" 217 | , expected: Just 0 218 | } 219 | assertEqual 220 | { actual: SCU.indexOf' (Pattern "") 1 "ab" 221 | , expected: Just 1 222 | } 223 | assertEqual 224 | { actual: SCU.indexOf' (Pattern "") 2 "ab" 225 | , expected: Just 2 226 | } 227 | assertEqual 228 | { actual: SCU.indexOf' (Pattern "") 3 "ab" 229 | , expected: Nothing 230 | } 231 | assertEqual 232 | { actual: SCU.indexOf' (Pattern "bc") 0 "abcd" 233 | , expected: Just 1 234 | } 235 | assertEqual 236 | { actual: SCU.indexOf' (Pattern "bc") 1 "abcd" 237 | , expected: Just 1 238 | } 239 | assertEqual 240 | { actual: SCU.indexOf' (Pattern "bc") 2 "abcd" 241 | , expected: Nothing 242 | } 243 | assertEqual 244 | { actual: SCU.indexOf' (Pattern "cb") 0 "abcd" 245 | , expected: Nothing 246 | } 247 | 248 | log "lastIndexOf" 249 | assertEqual 250 | { actual: SCU.lastIndexOf (Pattern "") "" 251 | , expected: Just 0 252 | } 253 | assertEqual 254 | { actual: SCU.lastIndexOf (Pattern "") "abcd" 255 | , expected: Just 4 256 | } 257 | assertEqual 258 | { actual: SCU.lastIndexOf (Pattern "bc") "abcd" 259 | , expected: Just 1 260 | } 261 | assertEqual 262 | { actual: SCU.lastIndexOf (Pattern "cb") "abcd" 263 | , expected: Nothing 264 | } 265 | 266 | log "lastIndexOf'" 267 | assertEqual 268 | { actual: SCU.lastIndexOf' (Pattern "") 0 "" 269 | , expected: Just 0 270 | } 271 | assertEqual 272 | { actual: SCU.lastIndexOf' (Pattern "") (-1) "ab" 273 | , expected: Just 0 274 | } 275 | assertEqual 276 | { actual: SCU.lastIndexOf' (Pattern "") 0 "ab" 277 | , expected: Just 0 278 | } 279 | assertEqual 280 | { actual: SCU.lastIndexOf' (Pattern "") 1 "ab" 281 | , expected: Just 1 282 | } 283 | assertEqual 284 | { actual: SCU.lastIndexOf' (Pattern "") 2 "ab" 285 | , expected: Just 2 286 | } 287 | assertEqual 288 | { actual: SCU.lastIndexOf' (Pattern "") 3 "ab" 289 | , expected: Just 2 290 | } 291 | assertEqual 292 | { actual: SCU.lastIndexOf' (Pattern "bc") 0 "abcd" 293 | , expected: Nothing 294 | } 295 | assertEqual 296 | { actual: SCU.lastIndexOf' (Pattern "bc") 1 "abcd" 297 | , expected: Just 1 298 | } 299 | assertEqual 300 | { actual: SCU.lastIndexOf' (Pattern "bc") 2 "abcd" 301 | , expected: Just 1 302 | } 303 | assertEqual 304 | { actual: SCU.lastIndexOf' (Pattern "cb") 0 "abcd" 305 | , expected: Nothing 306 | } 307 | 308 | log "length" 309 | assertEqual 310 | { actual: SCU.length "" 311 | , expected: 0 312 | } 313 | assertEqual 314 | { actual: SCU.length "a" 315 | , expected: 1 316 | } 317 | assertEqual 318 | { actual: SCU.length "ab" 319 | , expected: 2 320 | } 321 | 322 | log "take" 323 | assertEqual 324 | { actual: SCU.take 0 "ab" 325 | , expected: "" 326 | } 327 | assertEqual 328 | { actual: SCU.take 1 "ab" 329 | , expected: "a" 330 | } 331 | assertEqual 332 | { actual: SCU.take 2 "ab" 333 | , expected: "ab" 334 | } 335 | assertEqual 336 | { actual: SCU.take 3 "ab" 337 | , expected: "ab" 338 | } 339 | assertEqual 340 | { actual: SCU.take (-1) "ab" 341 | , expected: "" 342 | } 343 | 344 | log "takeRight" 345 | assertEqual 346 | { actual: SCU.takeRight 0 "ab" 347 | , expected: "" 348 | } 349 | assertEqual 350 | { actual: SCU.takeRight 1 "ab" 351 | , expected: "b" 352 | } 353 | assertEqual 354 | { actual: SCU.takeRight 2 "ab" 355 | , expected: "ab" 356 | } 357 | assertEqual 358 | { actual: SCU.takeRight 3 "ab" 359 | , expected: "ab" 360 | } 361 | assertEqual 362 | { actual: SCU.takeRight (-1) "ab" 363 | , expected: "" 364 | } 365 | 366 | log "drop" 367 | assertEqual 368 | { actual: SCU.drop 0 "ab" 369 | , expected: "ab" 370 | } 371 | assertEqual 372 | { actual: SCU.drop 1 "ab" 373 | , expected: "b" 374 | } 375 | assertEqual 376 | { actual: SCU.drop 2 "ab" 377 | , expected: "" 378 | } 379 | assertEqual 380 | { actual: SCU.drop 3 "ab" 381 | , expected: "" 382 | } 383 | assertEqual 384 | { actual: SCU.drop (-1) "ab" 385 | , expected: "ab" 386 | } 387 | 388 | log "dropRight" 389 | assertEqual 390 | { actual: SCU.dropRight 0 "ab" 391 | , expected: "ab" 392 | } 393 | assertEqual 394 | { actual: SCU.dropRight 1 "ab" 395 | , expected: "a" 396 | } 397 | assertEqual 398 | { actual: SCU.dropRight 2 "ab" 399 | , expected: "" 400 | } 401 | assertEqual 402 | { actual: SCU.dropRight 3 "ab" 403 | , expected: "" 404 | } 405 | assertEqual 406 | { actual: SCU.dropRight (-1) "ab" 407 | , expected: "ab" 408 | } 409 | 410 | log "countPrefix" 411 | assertEqual 412 | { actual: SCU.countPrefix (_ == 'a') "" 413 | , expected: 0 414 | } 415 | assertEqual 416 | { actual: SCU.countPrefix (_ == 'a') "ab" 417 | , expected: 1 418 | } 419 | assertEqual 420 | { actual: SCU.countPrefix (_ == 'a') "aaab" 421 | , expected: 3 422 | } 423 | assertEqual 424 | { actual: SCU.countPrefix (_ == 'a') "abaa" 425 | , expected: 1 426 | } 427 | 428 | log "splitAt" 429 | assertEqual 430 | { actual: SCU.splitAt 1 "" 431 | , expected: {before: "", after: ""} 432 | } 433 | assertEqual 434 | { actual: SCU.splitAt 0 "a" 435 | , expected: {before: "", after: "a"} 436 | } 437 | assertEqual 438 | { actual: SCU.splitAt 1 "a" 439 | , expected: {before: "a", after: ""} 440 | } 441 | assertEqual 442 | { actual: SCU.splitAt 1 "ab" 443 | , expected: {before: "a", after: "b"} 444 | } 445 | assertEqual 446 | { actual: SCU.splitAt 3 "aabcc" 447 | , expected: {before: "aab", after: "cc"} 448 | } 449 | assertEqual 450 | { actual: SCU.splitAt (-1) "abc" 451 | , expected: {before: "", after: "abc"} 452 | } 453 | assertEqual 454 | { actual: SCU.splitAt 10 "Hi" 455 | , expected: {before: "Hi", after: ""} 456 | } 457 | 458 | log "toCharArray" 459 | assertEqual 460 | { actual: SCU.toCharArray "" 461 | , expected: [] 462 | } 463 | assertEqual 464 | { actual: SCU.toCharArray "a" 465 | , expected: ['a'] 466 | } 467 | assertEqual 468 | { actual: SCU.toCharArray "ab" 469 | , expected: ['a', 'b'] 470 | } 471 | 472 | log "slice" 473 | assertEqual 474 | { actual: SCU.slice 0 0 "purescript" 475 | , expected: "" 476 | } 477 | assertEqual 478 | { actual: SCU.slice 0 1 "purescript" 479 | , expected: "p" 480 | } 481 | assertEqual 482 | { actual: SCU.slice 3 6 "purescript" 483 | , expected: "esc" 484 | } 485 | assertEqual 486 | { actual: SCU.slice 3 10 "purescript" 487 | , expected: "escript" 488 | } 489 | assertEqual 490 | { actual: SCU.slice 10 10 "purescript" 491 | , expected: "" 492 | } 493 | assertEqual 494 | { actual: SCU.slice (-4) (-1) "purescript" 495 | , expected: "rip" 496 | } 497 | assertEqual 498 | { actual: SCU.slice (-4) 3 "purescript" 499 | , expected: "" 500 | } 501 | assertEqual 502 | { actual: SCU.slice 1000 3 "purescript" 503 | , expected: "" 504 | } 505 | assertEqual 506 | { actual: SCU.slice 2 (-15) "purescript" 507 | , expected: "" 508 | } 509 | assertEqual 510 | { actual: SCU.slice (-15) 9 "purescript" 511 | , expected: "purescrip" 512 | } 513 | assertEqual 514 | { actual: SCU.slice 3 1000 "purescript" 515 | , expected: "escript" 516 | } 517 | -------------------------------------------------------------------------------- /test/Test/Data/String/NonEmpty.purs: -------------------------------------------------------------------------------- 1 | module Test.Data.String.NonEmpty (testNonEmptyString) where 2 | 3 | import Prelude 4 | 5 | import Data.Array.NonEmpty as NEA 6 | import Data.Maybe (Maybe(..), fromJust) 7 | import Data.String.NonEmpty (Pattern(..), nes) 8 | import Data.String.NonEmpty as NES 9 | import Effect (Effect) 10 | import Effect.Console (log) 11 | import Partial.Unsafe (unsafePartial) 12 | import Test.Assert (assert, assertEqual) 13 | import Type.Proxy (Proxy(..)) 14 | 15 | testNonEmptyString :: Effect Unit 16 | testNonEmptyString = do 17 | 18 | log "fromString" 19 | assertEqual 20 | { actual: NES.fromString "" 21 | , expected: Nothing 22 | } 23 | assertEqual 24 | { actual: NES.fromString "hello" 25 | , expected: Just (nes (Proxy :: Proxy "hello")) 26 | } 27 | 28 | log "toString" 29 | assertEqual 30 | { actual: (NES.toString <$> NES.fromString "hello") 31 | , expected: Just "hello" 32 | } 33 | 34 | log "appendString" 35 | assertEqual 36 | { actual: NES.appendString (nes (Proxy :: Proxy "Hello")) " world" 37 | , expected: nes (Proxy :: Proxy "Hello world") 38 | } 39 | assertEqual 40 | { actual: NES.appendString (nes (Proxy :: Proxy "Hello")) "" 41 | , expected: nes (Proxy :: Proxy "Hello") 42 | } 43 | 44 | log "prependString" 45 | assertEqual 46 | { actual: NES.prependString "be" (nes (Proxy :: Proxy "fore")) 47 | , expected: nes (Proxy :: Proxy "before") 48 | } 49 | assertEqual 50 | { actual: NES.prependString "" (nes (Proxy :: Proxy "fore")) 51 | , expected: nes (Proxy :: Proxy "fore") 52 | } 53 | 54 | log "contains" 55 | assert $ NES.contains (Pattern "") (nes (Proxy :: Proxy "abcd")) 56 | assert $ NES.contains (Pattern "bc") (nes (Proxy :: Proxy "abcd")) 57 | assert $ not NES.contains (Pattern "cb") (nes (Proxy :: Proxy "abcd")) 58 | assert $ NES.contains (Pattern "needle") (nes (Proxy :: Proxy "haystack with needle")) 59 | assert $ not NES.contains (Pattern "needle") (nes (Proxy :: Proxy "haystack")) 60 | 61 | log "localeCompare" 62 | assertEqual 63 | { actual: NES.localeCompare (nes (Proxy :: Proxy "a")) (nes (Proxy :: Proxy "a")) 64 | , expected: EQ 65 | } 66 | assertEqual 67 | { actual: NES.localeCompare (nes (Proxy :: Proxy "a")) (nes (Proxy :: Proxy "b")) 68 | , expected: LT 69 | } 70 | assertEqual 71 | { actual: NES.localeCompare (nes (Proxy :: Proxy "b")) (nes (Proxy :: Proxy "a")) 72 | , expected: GT 73 | } 74 | 75 | log "replace" 76 | assertEqual 77 | { actual: NES.replace (Pattern "b") (NES.NonEmptyReplacement (nes (Proxy :: Proxy "!"))) (nes (Proxy :: Proxy "abc")) 78 | , expected: nes (Proxy :: Proxy "a!c") 79 | } 80 | assertEqual 81 | { actual: NES.replace (Pattern "b") (NES.NonEmptyReplacement (nes (Proxy :: Proxy "!"))) (nes (Proxy :: Proxy "abbc")) 82 | , expected: nes (Proxy :: Proxy "a!bc") 83 | } 84 | assertEqual 85 | { actual: NES.replace (Pattern "d") (NES.NonEmptyReplacement (nes (Proxy :: Proxy "!"))) (nes (Proxy :: Proxy "abc")) 86 | , expected: nes (Proxy :: Proxy "abc") 87 | } 88 | 89 | log "replaceAll" 90 | assertEqual 91 | { actual: NES.replaceAll (Pattern "[b]") (NES.NonEmptyReplacement (nes (Proxy :: Proxy "!"))) (nes (Proxy :: Proxy "a[b]c")) 92 | , expected: nes (Proxy :: Proxy "a!c") 93 | } 94 | assertEqual 95 | { actual: NES.replaceAll (Pattern "[b]") (NES.NonEmptyReplacement (nes (Proxy :: Proxy "!"))) (nes (Proxy :: Proxy "a[b]c[b]")) 96 | , expected: nes (Proxy :: Proxy "a!c!") 97 | } 98 | assertEqual 99 | { actual: NES.replaceAll (Pattern "x") (NES.NonEmptyReplacement (nes (Proxy :: Proxy "!"))) (nes (Proxy :: Proxy "abc")) 100 | , expected: nes (Proxy :: Proxy "abc") 101 | } 102 | 103 | log "stripPrefix" 104 | assertEqual 105 | { actual: NES.stripPrefix (Pattern "") (nes (Proxy :: Proxy "abc")) 106 | , expected: Just (nes (Proxy :: Proxy "abc")) 107 | } 108 | assertEqual 109 | { actual: NES.stripPrefix (Pattern "a") (nes (Proxy :: Proxy "abc")) 110 | , expected: Just (nes (Proxy :: Proxy "bc")) 111 | } 112 | assertEqual 113 | { actual: NES.stripPrefix (Pattern "abc") (nes (Proxy :: Proxy "abc")) 114 | , expected: Nothing 115 | } 116 | assertEqual 117 | { actual: NES.stripPrefix (Pattern "!") (nes (Proxy :: Proxy "abc")) 118 | , expected: Nothing 119 | } 120 | assertEqual 121 | { actual: NES.stripPrefix (Pattern "http:") (nes (Proxy :: Proxy "http://purescript.org")) 122 | , expected: Just (nes (Proxy :: Proxy "//purescript.org")) 123 | } 124 | assertEqual 125 | { actual: NES.stripPrefix (Pattern "http:") (nes (Proxy :: Proxy "https://purescript.org")) 126 | , expected: Nothing 127 | } 128 | assertEqual 129 | { actual: NES.stripPrefix (Pattern "Hello!") (nes (Proxy :: Proxy "Hello!")) 130 | , expected: Nothing 131 | } 132 | 133 | log "stripSuffix" 134 | assertEqual 135 | { actual: NES.stripSuffix (Pattern ".exe") (nes (Proxy :: Proxy "purs.exe")) 136 | , expected: Just (nes (Proxy :: Proxy "purs")) 137 | } 138 | assertEqual 139 | { actual: NES.stripSuffix (Pattern ".exe") (nes (Proxy :: Proxy "purs")) 140 | , expected: Nothing 141 | } 142 | assertEqual 143 | { actual: NES.stripSuffix (Pattern "Hello!") (nes (Proxy :: Proxy "Hello!")) 144 | , expected: Nothing 145 | } 146 | 147 | log "toLower" 148 | assertEqual 149 | { actual: NES.toLower (nes (Proxy :: Proxy "bAtMaN")) 150 | , expected: nes (Proxy :: Proxy "batman") 151 | } 152 | 153 | log "toUpper" 154 | assertEqual 155 | { actual: NES.toUpper (nes (Proxy :: Proxy "bAtMaN")) 156 | , expected: nes (Proxy :: Proxy "BATMAN") 157 | } 158 | 159 | log "trim" 160 | assertEqual 161 | { actual: NES.trim (nes (Proxy :: Proxy " abc ")) 162 | , expected: Just (nes (Proxy :: Proxy "abc")) 163 | } 164 | assertEqual 165 | { actual: NES.trim (nes (Proxy :: Proxy " \n")) 166 | , expected: Nothing 167 | } 168 | 169 | log "joinWith" 170 | assertEqual 171 | { actual: NES.joinWith "" [] 172 | , expected: "" 173 | } 174 | assertEqual 175 | { actual: NES.joinWith "" [nes (Proxy :: Proxy "a"), nes (Proxy :: Proxy "b")] 176 | , expected: "ab" 177 | } 178 | assertEqual 179 | { actual: NES.joinWith "--" [nes (Proxy :: Proxy "a"), nes (Proxy :: Proxy "b"), nes (Proxy :: Proxy "c")] 180 | , expected: "a--b--c" 181 | } 182 | 183 | log "join1With" 184 | assertEqual 185 | { actual: NES.join1With "" (nea [nes (Proxy :: Proxy "a"), nes (Proxy :: Proxy "b")]) 186 | , expected: nes (Proxy :: Proxy "ab") 187 | } 188 | assertEqual 189 | { actual: NES.join1With "--" (nea [nes (Proxy :: Proxy "a"), nes (Proxy :: Proxy "b"), nes (Proxy :: Proxy "c")]) 190 | , expected: nes (Proxy :: Proxy "a--b--c") 191 | } 192 | assertEqual 193 | { actual: NES.join1With ", " (nea [nes (Proxy :: Proxy "apple"), nes (Proxy :: Proxy "banana")]) 194 | , expected: nes (Proxy :: Proxy "apple, banana") 195 | } 196 | assertEqual 197 | { actual: NES.join1With "" (nea [nes (Proxy :: Proxy "apple"), nes (Proxy :: Proxy "banana")]) 198 | , expected: nes (Proxy :: Proxy "applebanana") 199 | } 200 | 201 | log "joinWith1" 202 | assertEqual 203 | { actual: NES.joinWith1 (nes (Proxy :: Proxy " ")) (nea ["a", "b"]) 204 | , expected: nes (Proxy :: Proxy "a b") 205 | } 206 | assertEqual 207 | { actual: NES.joinWith1 (nes (Proxy :: Proxy "--")) (nea ["a", "b", "c"]) 208 | , expected: nes (Proxy :: Proxy "a--b--c") 209 | } 210 | assertEqual 211 | { actual: NES.joinWith1 (nes (Proxy :: Proxy ", ")) (nea ["apple", "banana"]) 212 | , expected: nes (Proxy :: Proxy "apple, banana") 213 | } 214 | assertEqual 215 | { actual: NES.joinWith1 (nes (Proxy :: Proxy "/")) (nea ["a", "b", "", "c", ""]) 216 | , expected: nes (Proxy :: Proxy "a/b//c/") 217 | } 218 | 219 | nea :: Array ~> NEA.NonEmptyArray 220 | nea = unsafePartial fromJust <<< NEA.fromArray 221 | -------------------------------------------------------------------------------- /test/Test/Data/String/NonEmpty/CodeUnits.purs: -------------------------------------------------------------------------------- 1 | module Test.Data.String.NonEmpty.CodeUnits (testNonEmptyStringCodeUnits) where 2 | 3 | import Prelude 4 | 5 | import Data.Array.NonEmpty as NEA 6 | import Data.Enum (fromEnum) 7 | import Data.Maybe (Maybe(..), fromJust) 8 | import Data.String.NonEmpty (Pattern(..), nes) 9 | import Data.String.NonEmpty.CodeUnits as NESCU 10 | import Effect (Effect) 11 | import Effect.Console (log) 12 | import Partial.Unsafe (unsafePartial) 13 | import Test.Assert (assertEqual) 14 | import Type.Proxy (Proxy(..)) 15 | 16 | testNonEmptyStringCodeUnits :: Effect Unit 17 | testNonEmptyStringCodeUnits = do 18 | 19 | log "fromCharArray" 20 | assertEqual 21 | { actual: NESCU.fromCharArray [] 22 | , expected: Nothing 23 | } 24 | assertEqual 25 | { actual: NESCU.fromCharArray ['a', 'b'] 26 | , expected: Just (nes (Proxy :: Proxy "ab")) 27 | } 28 | 29 | log "fromNonEmptyCharArray" 30 | assertEqual 31 | { actual: NESCU.fromNonEmptyCharArray (NEA.singleton 'b') 32 | , expected: NESCU.singleton 'b' 33 | } 34 | 35 | log "singleton" 36 | assertEqual 37 | { actual: NESCU.singleton 'a' 38 | , expected: nes (Proxy :: Proxy "a") 39 | } 40 | 41 | log "cons" 42 | assertEqual 43 | { actual: NESCU.cons 'a' "bc" 44 | , expected: nes (Proxy :: Proxy "abc") 45 | } 46 | assertEqual 47 | { actual: NESCU.cons 'a' "" 48 | , expected: nes (Proxy :: Proxy "a") 49 | } 50 | 51 | log "snoc" 52 | assertEqual 53 | { actual: NESCU.snoc 'c' "ab" 54 | , expected: nes (Proxy :: Proxy "abc") 55 | } 56 | assertEqual 57 | { actual: NESCU.snoc 'a' "" 58 | , expected: nes (Proxy :: Proxy "a") 59 | } 60 | 61 | log "fromFoldable1" 62 | assertEqual 63 | { actual: NESCU.fromFoldable1 (nea ['a']) 64 | , expected: nes (Proxy :: Proxy "a") 65 | } 66 | assertEqual 67 | { actual: NESCU.fromFoldable1 (nea ['a', 'b', 'c']) 68 | , expected: nes (Proxy :: Proxy "abc") 69 | } 70 | 71 | log "charAt" 72 | assertEqual 73 | { actual: NESCU.charAt 0 (nes (Proxy :: Proxy "a")) 74 | , expected: Just 'a' 75 | } 76 | assertEqual 77 | { actual: NESCU.charAt 1 (nes (Proxy :: Proxy "a")) 78 | , expected: Nothing 79 | } 80 | assertEqual 81 | { actual: NESCU.charAt 0 (nes (Proxy :: Proxy "ab")) 82 | , expected: Just 'a' 83 | } 84 | assertEqual 85 | { actual: NESCU.charAt 1 (nes (Proxy :: Proxy "ab")) 86 | , expected: Just 'b' 87 | } 88 | assertEqual 89 | { actual: NESCU.charAt 2 (nes (Proxy :: Proxy "ab")) 90 | , expected: Nothing 91 | } 92 | assertEqual 93 | { actual: NESCU.charAt 2 (nes (Proxy :: Proxy "Hello")) 94 | , expected: Just 'l' 95 | } 96 | assertEqual 97 | { actual: NESCU.charAt 10 (nes (Proxy :: Proxy "Hello")) 98 | , expected: Nothing 99 | } 100 | 101 | log "charCodeAt" 102 | assertEqual 103 | { actual: fromEnum <$> NESCU.charAt 0 (nes (Proxy :: Proxy "a")) 104 | , expected: Just 97 105 | } 106 | assertEqual 107 | { actual: fromEnum <$> NESCU.charAt 1 (nes (Proxy :: Proxy "a")) 108 | , expected: Nothing 109 | } 110 | assertEqual 111 | { actual: fromEnum <$> NESCU.charAt 0 (nes (Proxy :: Proxy "ab")) 112 | , expected: Just 97 113 | } 114 | assertEqual 115 | { actual: fromEnum <$> NESCU.charAt 1 (nes (Proxy :: Proxy "ab")) 116 | , expected: Just 98 117 | } 118 | assertEqual 119 | { actual: fromEnum <$> NESCU.charAt 2 (nes (Proxy :: Proxy "ab")) 120 | , expected: Nothing 121 | } 122 | assertEqual 123 | { actual: fromEnum <$> NESCU.charAt 2 (nes (Proxy :: Proxy "5 €")) 124 | , expected: Just 0x20AC 125 | } 126 | assertEqual 127 | { actual: fromEnum <$> NESCU.charAt 10 (nes (Proxy :: Proxy "5 €")) 128 | , expected: Nothing 129 | } 130 | 131 | log "toChar" 132 | assertEqual 133 | { actual: NESCU.toChar (nes (Proxy :: Proxy "a")) 134 | , expected: Just 'a' 135 | } 136 | assertEqual 137 | { actual: NESCU.toChar (nes (Proxy :: Proxy "ab")) 138 | , expected: Nothing 139 | } 140 | 141 | log "toCharArray" 142 | assertEqual 143 | { actual: NESCU.toCharArray (nes (Proxy :: Proxy "a")) 144 | , expected: ['a'] 145 | } 146 | assertEqual 147 | { actual: NESCU.toCharArray (nes (Proxy :: Proxy "ab")) 148 | , expected: ['a', 'b'] 149 | } 150 | assertEqual 151 | { actual: NESCU.toCharArray (nes (Proxy :: Proxy "Hello☺\n")) 152 | , expected: ['H','e','l','l','o','☺','\n'] 153 | } 154 | 155 | log "toNonEmptyCharArray" 156 | assertEqual 157 | { actual: NESCU.toNonEmptyCharArray (nes (Proxy :: Proxy "ab")) 158 | , expected: nea ['a', 'b'] 159 | } 160 | 161 | log "uncons" 162 | assertEqual 163 | { actual: NESCU.uncons (nes (Proxy :: Proxy "a")) 164 | , expected: { head: 'a', tail: Nothing } 165 | } 166 | assertEqual 167 | { actual: NESCU.uncons (nes (Proxy :: Proxy "Hello World")) 168 | , expected: { head: 'H', tail: Just (nes (Proxy :: Proxy "ello World")) } 169 | } 170 | 171 | log "takeWhile" 172 | assertEqual 173 | { actual: NESCU.takeWhile (\_ -> true) (nes (Proxy :: Proxy "abc")) 174 | , expected: Just (nes (Proxy :: Proxy "abc")) 175 | } 176 | assertEqual 177 | { actual: NESCU.takeWhile (\_ -> false) (nes (Proxy :: Proxy "abc")) 178 | , expected: Nothing 179 | } 180 | assertEqual 181 | { actual: NESCU.takeWhile (\c -> c /= 'b') (nes (Proxy :: Proxy "aabbcc")) 182 | , expected: Just (nes (Proxy :: Proxy "aa")) 183 | } 184 | assertEqual 185 | { actual: NESCU.takeWhile (_ /= ':') (nes (Proxy :: Proxy "http://purescript.org")) 186 | , expected: Just (nes (Proxy :: Proxy "http")) 187 | } 188 | assertEqual 189 | { actual: NESCU.takeWhile (_ == 'a') (nes (Proxy :: Proxy "xyz")) 190 | , expected: Nothing 191 | } 192 | 193 | log "dropWhile" 194 | assertEqual 195 | { actual: NESCU.dropWhile (\_ -> true) (nes (Proxy :: Proxy "abc")) 196 | , expected: Nothing 197 | } 198 | assertEqual 199 | { actual: NESCU.dropWhile (\_ -> false) (nes (Proxy :: Proxy "abc")) 200 | , expected: Just (nes (Proxy :: Proxy "abc")) 201 | } 202 | assertEqual 203 | { actual: NESCU.dropWhile (\c -> c /= 'b') (nes (Proxy :: Proxy "aabbcc")) 204 | , expected: Just (nes (Proxy :: Proxy "bbcc")) 205 | } 206 | assertEqual 207 | { actual: NESCU.dropWhile (_ /= '.') (nes (Proxy :: Proxy "Test.purs")) 208 | , expected: Just (nes (Proxy :: Proxy ".purs")) 209 | } 210 | 211 | log "indexOf" 212 | assertEqual 213 | { actual: NESCU.indexOf (Pattern "") (nes (Proxy :: Proxy "abcd")) 214 | , expected: Just 0 215 | } 216 | assertEqual 217 | { actual: NESCU.indexOf (Pattern "bc") (nes (Proxy :: Proxy "abcd")) 218 | , expected: Just 1 219 | } 220 | assertEqual 221 | { actual: NESCU.indexOf (Pattern "cb") (nes (Proxy :: Proxy "abcd")) 222 | , expected: Nothing 223 | } 224 | 225 | log "indexOf'" 226 | assertEqual 227 | { actual: NESCU.indexOf' (Pattern "") (-1) (nes (Proxy :: Proxy "ab")) 228 | , expected: Nothing 229 | } 230 | assertEqual 231 | { actual: NESCU.indexOf' (Pattern "") 0 (nes (Proxy :: Proxy "ab")) 232 | , expected: Just 0 233 | } 234 | assertEqual 235 | { actual: NESCU.indexOf' (Pattern "") 1 (nes (Proxy :: Proxy "ab")) 236 | , expected: Just 1 237 | } 238 | assertEqual 239 | { actual: NESCU.indexOf' (Pattern "") 2 (nes (Proxy :: Proxy "ab")) 240 | , expected: Just 2 241 | } 242 | assertEqual 243 | { actual: NESCU.indexOf' (Pattern "") 3 (nes (Proxy :: Proxy "ab")) 244 | , expected: Nothing 245 | } 246 | assertEqual 247 | { actual: NESCU.indexOf' (Pattern "bc") 0 (nes (Proxy :: Proxy "abcd")) 248 | , expected: Just 1 249 | } 250 | assertEqual 251 | { actual: NESCU.indexOf' (Pattern "bc") 1 (nes (Proxy :: Proxy "abcd")) 252 | , expected: Just 1 253 | } 254 | assertEqual 255 | { actual: NESCU.indexOf' (Pattern "bc") 2 (nes (Proxy :: Proxy "abcd")) 256 | , expected: Nothing 257 | } 258 | assertEqual 259 | { actual: NESCU.indexOf' (Pattern "cb") 0 (nes (Proxy :: Proxy "abcd")) 260 | , expected: Nothing 261 | } 262 | 263 | log "lastIndexOf" 264 | assertEqual 265 | { actual: NESCU.lastIndexOf (Pattern "") (nes (Proxy :: Proxy "abcd")) 266 | , expected: Just 4 267 | } 268 | assertEqual 269 | { actual: NESCU.lastIndexOf (Pattern "bc") (nes (Proxy :: Proxy "abcd")) 270 | , expected: Just 1 271 | } 272 | assertEqual 273 | { actual: NESCU.lastIndexOf (Pattern "cb") (nes (Proxy :: Proxy "abcd")) 274 | , expected: Nothing 275 | } 276 | 277 | log "lastIndexOf'" 278 | assertEqual 279 | { actual: NESCU.lastIndexOf' (Pattern "") (-1) (nes (Proxy :: Proxy "ab")) 280 | , expected: Just 0 281 | } 282 | assertEqual 283 | { actual: NESCU.lastIndexOf' (Pattern "") 0 (nes (Proxy :: Proxy "ab")) 284 | , expected: Just 0 285 | } 286 | assertEqual 287 | { actual: NESCU.lastIndexOf' (Pattern "") 1 (nes (Proxy :: Proxy "ab")) 288 | , expected: Just 1 289 | } 290 | assertEqual 291 | { actual: NESCU.lastIndexOf' (Pattern "") 2 (nes (Proxy :: Proxy "ab")) 292 | , expected: Just 2 293 | } 294 | assertEqual 295 | { actual: NESCU.lastIndexOf' (Pattern "") 3 (nes (Proxy :: Proxy "ab")) 296 | , expected: Just 2 297 | } 298 | assertEqual 299 | { actual: NESCU.lastIndexOf' (Pattern "bc") 0 (nes (Proxy :: Proxy "abcd")) 300 | , expected: Nothing 301 | } 302 | assertEqual 303 | { actual: NESCU.lastIndexOf' (Pattern "bc") 1 (nes (Proxy :: Proxy "abcd")) 304 | , expected: Just 1 305 | } 306 | assertEqual 307 | { actual: NESCU.lastIndexOf' (Pattern "bc") 2 (nes (Proxy :: Proxy "abcd")) 308 | , expected: Just 1 309 | } 310 | assertEqual 311 | { actual: NESCU.lastIndexOf' (Pattern "cb") 0 (nes (Proxy :: Proxy "abcd")) 312 | , expected: Nothing 313 | } 314 | 315 | log "length" 316 | assertEqual 317 | { actual: NESCU.length (nes (Proxy :: Proxy "a")) 318 | , expected: 1 319 | } 320 | assertEqual 321 | { actual: NESCU.length (nes (Proxy :: Proxy "ab")) 322 | , expected: 2 323 | } 324 | 325 | log "take" 326 | assertEqual 327 | { actual: NESCU.take 0 (nes (Proxy :: Proxy "ab")) 328 | , expected: Nothing 329 | } 330 | assertEqual 331 | { actual: NESCU.take 1 (nes (Proxy :: Proxy "ab")) 332 | , expected: Just (nes (Proxy :: Proxy "a")) 333 | } 334 | assertEqual 335 | { actual: NESCU.take 2 (nes (Proxy :: Proxy "ab")) 336 | , expected: Just (nes (Proxy :: Proxy "ab")) 337 | } 338 | assertEqual 339 | { actual: NESCU.take 3 (nes (Proxy :: Proxy "ab")) 340 | , expected: Just (nes (Proxy :: Proxy "ab")) 341 | } 342 | assertEqual 343 | { actual: NESCU.take (-1) (nes (Proxy :: Proxy "ab")) 344 | , expected: Nothing 345 | } 346 | 347 | log "takeRight" 348 | assertEqual 349 | { actual: NESCU.takeRight 0 (nes (Proxy :: Proxy "ab")) 350 | , expected: Nothing 351 | } 352 | assertEqual 353 | { actual: NESCU.takeRight 1 (nes (Proxy :: Proxy "ab")) 354 | , expected: Just (nes (Proxy :: Proxy "b")) 355 | } 356 | assertEqual 357 | { actual: NESCU.takeRight 2 (nes (Proxy :: Proxy "ab")) 358 | , expected: Just (nes (Proxy :: Proxy "ab")) 359 | } 360 | assertEqual 361 | { actual: NESCU.takeRight 3 (nes (Proxy :: Proxy "ab")) 362 | , expected: Just (nes (Proxy :: Proxy "ab")) 363 | } 364 | assertEqual 365 | { actual: NESCU.takeRight (-1) (nes (Proxy :: Proxy "ab")) 366 | , expected: Nothing 367 | } 368 | 369 | log "drop" 370 | assertEqual 371 | { actual: NESCU.drop 0 (nes (Proxy :: Proxy "ab")) 372 | , expected: Just (nes (Proxy :: Proxy "ab")) 373 | } 374 | assertEqual 375 | { actual: NESCU.drop 1 (nes (Proxy :: Proxy "ab")) 376 | , expected: Just (nes (Proxy :: Proxy "b")) 377 | } 378 | assertEqual 379 | { actual: NESCU.drop 2 (nes (Proxy :: Proxy "ab")) 380 | , expected: Nothing 381 | } 382 | assertEqual 383 | { actual: NESCU.drop 3 (nes (Proxy :: Proxy "ab")) 384 | , expected: Nothing 385 | } 386 | assertEqual 387 | { actual: NESCU.drop (-1) (nes (Proxy :: Proxy "ab")) 388 | , expected: Just (nes (Proxy :: Proxy "ab")) 389 | } 390 | 391 | log "dropRight" 392 | assertEqual 393 | { actual: NESCU.dropRight 0 (nes (Proxy :: Proxy "ab")) 394 | , expected: Just (nes (Proxy :: Proxy "ab")) 395 | } 396 | assertEqual 397 | { actual: NESCU.dropRight 1 (nes (Proxy :: Proxy "ab")) 398 | , expected: Just (nes (Proxy :: Proxy "a")) 399 | } 400 | assertEqual 401 | { actual: NESCU.dropRight 2 (nes (Proxy :: Proxy "ab")) 402 | , expected: Nothing 403 | } 404 | assertEqual 405 | { actual: NESCU.dropRight 3 (nes (Proxy :: Proxy "ab")) 406 | , expected: Nothing 407 | } 408 | assertEqual 409 | { actual: NESCU.dropRight (-1) (nes (Proxy :: Proxy "ab")) 410 | , expected: Just (nes (Proxy :: Proxy "ab")) 411 | } 412 | 413 | log "countPrefix" 414 | assertEqual 415 | { actual: NESCU.countPrefix (_ == 'a') (nes (Proxy :: Proxy "ab")) 416 | , expected: 1 417 | } 418 | assertEqual 419 | { actual: NESCU.countPrefix (_ == 'a') (nes (Proxy :: Proxy "aaab")) 420 | , expected: 3 421 | } 422 | assertEqual 423 | { actual: NESCU.countPrefix (_ == 'a') (nes (Proxy :: Proxy "abaa")) 424 | , expected: 1 425 | } 426 | assertEqual 427 | { actual: NESCU.countPrefix (_ == 'c') (nes (Proxy :: Proxy "abaa")) 428 | , expected: 0 429 | } 430 | 431 | log "splitAt" 432 | assertEqual 433 | { actual: NESCU.splitAt 0 (nes (Proxy :: Proxy "a")) 434 | , expected: { before: Nothing, after: Just (nes (Proxy :: Proxy "a")) } 435 | } 436 | assertEqual 437 | { actual: NESCU.splitAt 1 (nes (Proxy :: Proxy "ab")) 438 | , expected: { before: Just (nes (Proxy :: Proxy "a")), after: Just (nes (Proxy :: Proxy "b")) } 439 | } 440 | assertEqual 441 | { actual: NESCU.splitAt 3 (nes (Proxy :: Proxy "aabcc")) 442 | , expected: { before: Just (nes (Proxy :: Proxy "aab")), after: Just (nes (Proxy :: Proxy "cc")) } 443 | } 444 | assertEqual 445 | { actual: NESCU.splitAt (-1) (nes (Proxy :: Proxy "abc")) 446 | , expected: { before: Nothing, after: Just (nes (Proxy :: Proxy "abc")) } 447 | } 448 | 449 | nea :: Array ~> NEA.NonEmptyArray 450 | nea = unsafePartial fromJust <<< NEA.fromArray 451 | -------------------------------------------------------------------------------- /test/Test/Data/String/Regex.purs: -------------------------------------------------------------------------------- 1 | module Test.Data.String.Regex (testStringRegex) where 2 | 3 | import Data.String.Regex 4 | 5 | import Data.Array.NonEmpty (NonEmptyArray, fromArray) 6 | import Data.Either (isLeft) 7 | import Data.Maybe (Maybe(..), fromJust) 8 | import Data.String.Regex.Flags (dotAll, global, ignoreCase, noFlags) 9 | import Data.String.Regex.Unsafe (unsafeRegex) 10 | import Effect (Effect) 11 | import Effect.Console (log) 12 | import Partial.Unsafe (unsafePartial) 13 | import Prelude (type (~>), Unit, discard, not, show, ($), (<<<), (<>), (==)) 14 | import Test.Assert (assert) 15 | 16 | testStringRegex :: Effect Unit 17 | testStringRegex = do 18 | log "regex" 19 | assert $ test (unsafeRegex "^a" noFlags) "abc" 20 | assert $ not (test (unsafeRegex "^b" noFlags) "abc") 21 | assert $ isLeft (regex "+" noFlags) 22 | 23 | log "flags" 24 | assert $ "quxbarfoobaz" == replace (unsafeRegex "foo" noFlags) "qux" "foobarfoobaz" 25 | assert $ "quxbarquxbaz" == replace (unsafeRegex "foo" global) "qux" "foobarfoobaz" 26 | assert $ "quxbarquxbaz" == replace (unsafeRegex "foo" (global <> ignoreCase)) "qux" "foobarFOObaz" 27 | assert $ "quxbarfoobaz" == replace (unsafeRegex ".foo" dotAll) "qux" "\nfoobarfoobaz" 28 | 29 | log "match" 30 | assert $ match (unsafeRegex "^abc$" noFlags) "abc" == Just (nea [Just "abc"]) 31 | assert $ match (unsafeRegex "^abc$" noFlags) "xyz" == Nothing 32 | 33 | log "replace" 34 | assert $ replace (unsafeRegex "-" noFlags) "!" "a-b-c" == "a!b-c" 35 | 36 | log "replace'" 37 | assert $ replace' (unsafeRegex "-" noFlags) (\s xs -> "!") "a-b-c" == "a!b-c" 38 | assert $ replace' (unsafeRegex "(foo)(bar)?" noFlags) (\s xs -> show xs) "<>" == "<>" 39 | assert $ replace' (unsafeRegex "(foo)(bar)?" noFlags) (\s xs -> show xs) "" == "<[(Just \"foo\"),Nothing]>" 40 | assert $ replace' (unsafeRegex "(foo)(bar)?" noFlags) (\s xs -> show xs) "" == "<[(Just \"foo\"),(Just \"bar\")]>" 41 | assert $ replace' (unsafeRegex "@(?\\w+)" noFlags) (\s xs -> show xs) "@purescript" == "[(Just \"purescript\")]" 42 | 43 | log "search" 44 | assert $ search (unsafeRegex "b" noFlags) "abc" == Just 1 45 | assert $ search (unsafeRegex "d" noFlags) "abc" == Nothing 46 | 47 | log "split" 48 | assert $ split (unsafeRegex "" noFlags) "" == [] 49 | assert $ split (unsafeRegex "" noFlags) "abc" == ["a", "b", "c"] 50 | assert $ split (unsafeRegex "b" noFlags) "" == [""] 51 | assert $ split (unsafeRegex "b" noFlags) "abc" == ["a", "c"] 52 | 53 | log "test" 54 | -- Ensure that we have referential transparency for calls to 'test'. No 55 | -- global state should be maintained between these two calls: 56 | let pattern = unsafeRegex "a" (parseFlags "g") 57 | assert $ test pattern "a" 58 | assert $ test pattern "a" 59 | 60 | nea :: Array ~> NonEmptyArray 61 | nea = unsafePartial fromJust <<< fromArray 62 | -------------------------------------------------------------------------------- /test/Test/Data/String/Unsafe.purs: -------------------------------------------------------------------------------- 1 | module Test.Data.String.Unsafe (testStringUnsafe) where 2 | 3 | import Prelude 4 | 5 | import Data.String.Unsafe as SU 6 | import Effect (Effect) 7 | import Effect.Console (log) 8 | import Test.Assert (assertEqual) 9 | 10 | testStringUnsafe :: Effect Unit 11 | testStringUnsafe = do 12 | log "charAt" 13 | assertEqual 14 | { actual: SU.charAt 0 "ab" 15 | , expected: 'a' 16 | } 17 | assertEqual 18 | { actual: SU.charAt 1 "ab" 19 | , expected: 'b' 20 | } 21 | 22 | log "char" 23 | assertEqual 24 | { actual: SU.char "a" 25 | , expected: 'a' 26 | } 27 | -------------------------------------------------------------------------------- /test/Test/Main.purs: -------------------------------------------------------------------------------- 1 | module Test.Main where 2 | 3 | import Prelude 4 | 5 | import Effect (Effect) 6 | import Effect.Console (log) 7 | import Test.Data.String (testString) 8 | import Test.Data.String.CaseInsensitive (testCaseInsensitiveString) 9 | import Test.Data.String.CodePoints (testStringCodePoints) 10 | import Test.Data.String.CodeUnits (testStringCodeUnits) 11 | import Test.Data.String.NonEmpty (testNonEmptyString) 12 | import Test.Data.String.NonEmpty.CodeUnits (testNonEmptyStringCodeUnits) 13 | import Test.Data.String.Regex (testStringRegex) 14 | import Test.Data.String.Unsafe (testStringUnsafe) 15 | 16 | main :: Effect Unit 17 | main = do 18 | log "\n--- Data.String ---\n" 19 | testString 20 | log "\n--- Data.String.CodePoints ---\n" 21 | testStringCodePoints 22 | log "\n--- Data.String.CodeUnits ---\n" 23 | testStringCodeUnits 24 | log "\n--- Data.String.Unsafe ---\n" 25 | testStringUnsafe 26 | log "\n--- Data.String.Regex ---\n" 27 | testStringRegex 28 | log "\n--- Data.String.CaseInsensitive ---\n" 29 | testCaseInsensitiveString 30 | log "\n--- Data.String.NonEmpty ---\n" 31 | testNonEmptyString 32 | log "\n--- Data.String.NonEmpty.CodeUnits ---\n" 33 | testNonEmptyStringCodeUnits 34 | --------------------------------------------------------------------------------