├── .gitattributes ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .gitmodules ├── LICENSE ├── Makefile ├── README ├── README.md ├── dappfile ├── src ├── strings.sol └── strings.t.sol └── strings.sol /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sol linguist-language=Solidity 2 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: "CI" 2 | on: "push" 3 | jobs: 4 | tests: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - uses: actions/checkout@v2.3.4 8 | - uses: cachix/install-nix-action@v13 9 | - name: Install dapp 10 | run: nix-env -iA dapp -f $(curl -sS https://api.github.com/repos/dapphub/dapptools/releases/latest | jq -r .tarball_url) 11 | - name: Fetch submodules 12 | run: git submodule update --init 13 | - name: Run tests 14 | run: make test 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/chain_db//out 2 | build 3 | out 4 | 5 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/ds-test"] 2 | path = lib/ds-test 3 | url = https://github.com/dapphub/ds-test 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2016 Nick Johnson 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all :; dapp build 2 | clean :; dapp clean 3 | test :; dapp test 4 | deploy :; dapp create SolidityStringutils 5 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | Basic string utilities for Solidity, optimized for low gas usage. 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # String & slice utility library for Solidity 3 | ## Overview 4 | Functionality in this library is largely implemented using an abstraction called a 'slice'. A slice represents a part of a string - anything from the entire string to a single character, or even no characters at all (a 0-length slice). Since a slice only has to specify an offset and a length, copying and manipulating slices is a lot less expensive than copying and manipulating the strings they reference. 5 | 6 | To further reduce gas costs, most functions on slice that need to return a slice modify the original one instead of allocating a new one; for instance, `s.split(".")` will return the text up to the first '.', modifying s to only contain the remainder of the string after the '.'. In situations where you do not want to modify the original slice, you can make a copy first with `.copy()`, for example: `s.copy().split(".")`. Try and avoid using this idiom in loops; since Solidity has no memory management, it will result in allocating many short-lived slices that are later discarded. 7 | 8 | Functions that return two slices come in two versions: a non-allocating version that takes the second slice as an argument, modifying it in place, and an allocating version that allocates and returns the second slice; see `nextRune` for example. 9 | 10 | Functions that have to copy string data will return strings rather than slices; these can be cast back to slices for further processing if required. 11 | 12 | ## Examples 13 | ### Basic usage 14 | import "github.com/Arachnid/solidity-stringutils/strings.sol"; 15 | 16 | contract Contract { 17 | using strings for *; 18 | 19 | // ... 20 | } 21 | 22 | ### Getting the character length of a string 23 | var len = "Unicode snowman ☃".toSlice().len(); // 17 24 | 25 | ### Splitting a string around a delimiter 26 | var s = "foo bar baz".toSlice(); 27 | var foo = s.split(" ".toSlice()); 28 | 29 | After the above code executes, `s` is now "bar baz", and `foo` is now "foo". 30 | 31 | ### Splitting a string into an array 32 | var s = "www.google.com".toSlice(); 33 | var delim = ".".toSlice(); 34 | var parts = new string[](s.count(delim) + 1); 35 | for(uint i = 0; i < parts.length; i++) { 36 | parts[i] = s.split(delim).toString(); 37 | } 38 | 39 | ### Extracting the middle part of a string 40 | var s = "www.google.com".toSlice(); 41 | strings.slice memory part; 42 | s.split(".".toSlice(), part); // part and return value is "www" 43 | s.split(".".toSlice(), part); // part and return value is "google" 44 | 45 | This approach uses less memory than the above, by reusing the slice `part` for each section of string extracted. 46 | 47 | ### Converting a slice back to a string 48 | var myString = mySlice.toString(); 49 | 50 | ### Finding and returning the first occurrence of a substring 51 | var s = "A B C B D".toSlice(); 52 | s.find("B".toSlice()); // "B C B D" 53 | 54 | `find` modifies `s` to contain the part of the string from the first match onwards. 55 | 56 | ### Finding and returning the last occurrence of a substring 57 | var s = "A B C B D".toSlice(); 58 | s.rfind("B".toSlice()); // "A B C B" 59 | 60 | `rfind` modifies `s` to contain the part of the string from the last match back to the start. 61 | 62 | ### Finding without modifying the original slice. 63 | var s = "A B C B D".toSlice(); 64 | var substring = s.copy().rfind("B".toSlice()); // "A B C B" 65 | 66 | `copy` lets you cheaply duplicate a slice so you don't modify the original. 67 | 68 | ### Prefix and suffix matching 69 | var s = "A B C B D".toSlice(); 70 | s.startsWith("A".toSlice()); // True 71 | s.endsWith("D".toSlice()); // True 72 | s.startsWith("B".toSlice()); // False 73 | 74 | ### Removing a prefix or suffix 75 | var s = "A B C B D".toSlice(); 76 | s.beyond("A ".toSlice()).until(" D".toSlice()); // "B C B" 77 | 78 | `beyond` modifies `s` to contain the text after its argument; `until` modifies `s` to contain the text up to its argument. If the argument isn't found, `s` is unmodified. 79 | 80 | ### Finding and returning the string up to the first match 81 | var s = "A B C B D".toSlice(); 82 | var needle = "B".toSlice(); 83 | var substring = s.until(s.copy().find(needle).beyond(needle)); 84 | 85 | Calling `find` on a copy of `s` returns the part of the string from `needle` onwards; calling `.beyond(needle)` removes `needle` as a prefix, and finally calling `s.until()` removes the entire end of the string, leaving everything up to and including the first match. 86 | 87 | ### Concatenating strings 88 | var s = "abc".toSlice().concat("def".toSlice()); // "abcdef" 89 | 90 | ## Reference 91 | 92 | ### toSlice(string self) internal returns (slice) 93 | Returns a slice containing the entire string. 94 | 95 | Arguments: 96 | 97 | - self The string to make a slice from. 98 | 99 | Returns A newly allocated slice containing the entire string. 100 | 101 | ### copy(slice self) internal returns (slice) 102 | Returns a new slice containing the same data as the current slice. 103 | 104 | Arguments: 105 | 106 | - self The slice to copy. 107 | 108 | Returns A new slice containing the same data as `self`. 109 | 110 | ### toString(slice self) internal returns (string) 111 | 112 | Copies a slice to a new string. 113 | 114 | Arguments: 115 | 116 | - self The slice to copy. 117 | 118 | Returns A newly allocated string containing the slice's text. 119 | 120 | ### len(slice self) internal returns (uint) 121 | 122 | Returns the length in runes of the slice. Note that this operation takes time proportional to the length of the slice; avoid using it in loops, and call `slice.empty()` if you only need to know whether the slice is empty or not. 123 | 124 | Arguments: 125 | 126 | - self The slice to operate on. 127 | 128 | Returns The length of the slice in runes. 129 | 130 | ### empty(slice self) internal returns (bool) 131 | 132 | Returns true if the slice is empty (has a length of 0). 133 | 134 | Arguments: 135 | 136 | - self The slice to operate on. 137 | 138 | Returns True if the slice is empty, False otherwise. 139 | 140 | ### compare(slice self, slice other) internal returns (int) 141 | 142 | Returns a positive number if `other` comes lexicographically after `self`, a negative number if it comes before, or zero if the contents of the two slices are equal. Comparison is done per-rune, on unicode codepoints. 143 | 144 | Arguments: 145 | 146 | - self The first slice to compare. 147 | - other The second slice to compare. 148 | 149 | Returns The result of the comparison. 150 | 151 | ### equals(slice self, slice other) internal returns (bool) 152 | 153 | Returns true if the two slices contain the same text. 154 | 155 | Arguments: 156 | 157 | - self The first slice to compare. 158 | - self The second slice to compare. 159 | 160 | Returns True if the slices are equal, false otherwise. 161 | 162 | ### nextRune(slice self, slice rune) internal returns (slice) 163 | 164 | Extracts the first rune in the slice into `rune`, advancing the slice to point to the next rune and returning `self`. 165 | 166 | Arguments: 167 | 168 | - self The slice to operate on. 169 | - rune The slice that will contain the first rune. 170 | 171 | Returns `rune`. 172 | 173 | ### nextRune(slice self) internal returns (slice ret) 174 | 175 | Returns the first rune in the slice, advancing the slice to point to the next rune. 176 | 177 | Arguments: 178 | 179 | - self The slice to operate on. 180 | 181 | Returns A slice containing only the first rune from `self`. 182 | 183 | ### ord(slice self) internal returns (uint ret) 184 | 185 | Returns the number of the first codepoint in the slice. 186 | 187 | Arguments: 188 | 189 | - self The slice to operate on. 190 | 191 | Returns The number of the first codepoint in the slice. 192 | 193 | ### keccak(slice self) internal returns (bytes32 ret) 194 | 195 | Returns the keccak-256 hash of the slice. 196 | 197 | Arguments: 198 | 199 | - self The slice to hash. 200 | 201 | Returns The hash of the slice. 202 | 203 | ### startsWith(slice self, slice needle) internal returns (bool) 204 | 205 | Returns true if `self` starts with `needle`. 206 | 207 | Arguments: 208 | 209 | - self The slice to operate on. 210 | - needle The slice to search for. 211 | 212 | Returns True if the slice starts with the provided text, false otherwise. 213 | 214 | ### beyond(slice self, slice needle) internal returns (slice) 215 | 216 | If `self` starts with `needle`, `needle` is removed from the beginning of `self`. Otherwise, `self` is unmodified. 217 | 218 | Arguments: 219 | 220 | - self The slice to operate on. 221 | - needle The slice to search for. 222 | 223 | Returns `self` 224 | 225 | ### endsWith(slice self, slice needle) internal returns (bool) 226 | 227 | Returns true if the slice ends with `needle`. 228 | 229 | Arguments: 230 | 231 | - self The slice to operate on. 232 | - needle The slice to search for. 233 | 234 | Returns True if the slice starts with the provided text, false otherwise. 235 | 236 | ### until(slice self, slice needle) internal returns (slice) 237 | 238 | If `self` ends with `needle`, `needle` is removed from the end of `self`. Otherwise, `self` is unmodified. 239 | 240 | Arguments: 241 | 242 | - self The slice to operate on. 243 | - needle The slice to search for. 244 | 245 | Returns `self` 246 | 247 | ### find(slice self, slice needle) internal returns (slice) 248 | 249 | Modifies `self` to contain everything from the first occurrence of `needle` to the end of the slice. `self` is set to the empty slice if `needle` is not found. 250 | 251 | Arguments: 252 | 253 | - self The slice to search and modify. 254 | - needle The text to search for. 255 | 256 | Returns `self`. 257 | 258 | ### rfind(slice self, slice needle) internal returns (slice) 259 | 260 | Modifies `self` to contain the part of the string from the start of `self` to the end of the first occurrence of `needle`. If `needle` is not found, `self` is set to the empty slice. 261 | 262 | Arguments: 263 | 264 | - self The slice to search and modify. 265 | - needle The text to search for. 266 | 267 | Returns `self`. 268 | 269 | ### split(slice self, slice needle, slice token) internal returns (slice) 270 | 271 | Splits the slice, setting `self` to everything after the first occurrence of `needle`, and `token` to everything before it. If `needle` does not occur in `self`, `self` is set to the empty slice, and `token` is set to the entirety of `self`. 272 | 273 | Arguments: 274 | 275 | - self The slice to split. 276 | - needle The text to search for in `self`. 277 | - token An output parameter to which the first token is written. 278 | 279 | Returns `token`. 280 | 281 | ### split(slice self, slice needle) internal returns (slice token) 282 | 283 | Splits the slice, setting `self` to everything after the first occurrence of `needle`, and returning everything before it. If `needle` does not occur in `self`, `self` is set to the empty slice, and the entirety of `self` is returned. 284 | 285 | Arguments: 286 | 287 | - self The slice to split. 288 | - needle The text to search for in `self`. 289 | 290 | Returns The part of `self` up to the first occurrence of `delim`. 291 | 292 | ### rsplit(slice self, slice needle, slice token) internal returns (slice) 293 | 294 | Splits the slice, setting `self` to everything before the last occurrence of `needle`, and `token` to everything after it. If `needle` does not occur in `self`, `self` is set to the empty slice, and `token` is set to the entirety of `self`. 295 | 296 | Arguments: 297 | 298 | - self The slice to split. 299 | - needle The text to search for in `self`. 300 | - token An output parameter to which the first token is written. 301 | 302 | Returns `token`. 303 | 304 | ### rsplit(slice self, slice needle) internal returns (slice token) 305 | 306 | Splits the slice, setting `self` to everything before the last occurrence of `needle`, and returning everything after it. If `needle` does not occur in `self`, `self` is set to the empty slice, and the entirety of `self` is returned. 307 | 308 | Arguments: 309 | 310 | - self The slice to split. 311 | - needle The text to search for in `self`. 312 | 313 | Returns The part of `self` after the last occurrence of `delim`. 314 | 315 | ### count(slice self, slice needle) internal returns (uint count) 316 | 317 | Counts the number of nonoverlapping occurrences of `needle` in `self`. 318 | 319 | Arguments: 320 | 321 | - self The slice to search. 322 | - needle The text to search for in `self`. 323 | 324 | Returns The number of occurrences of `needle` found in `self`. 325 | 326 | ### contains(slice self, slice needle) internal returns (bool) 327 | 328 | Returns True if `self` contains `needle`. 329 | 330 | Arguments: 331 | 332 | - self The slice to search. 333 | - needle The text to search for in `self`. 334 | 335 | Returns True if `needle` is found in `self`, false otherwise. 336 | 337 | ### concat(slice self, slice other) internal returns (string) 338 | 339 | Returns a newly allocated string containing the concatenation of `self` and `other`. 340 | 341 | Arguments: 342 | 343 | - self The first slice to concatenate. 344 | - other The second slice to concatenate. 345 | 346 | Returns The concatenation of the two strings. 347 | 348 | ### join(slice self, slice[] parts) internal returns (string) 349 | 350 | Joins an array of slices, using `self` as a delimiter, returning a newly allocated string. 351 | 352 | Arguments: 353 | 354 | - self The delimiter to use. 355 | - parts A list of slices to join. 356 | 357 | Returns A newly allocated string containing all the slices in `parts`, joined with `self`. 358 | -------------------------------------------------------------------------------- /dappfile: -------------------------------------------------------------------------------- 1 | version: 2.0.0 2 | tags: [] 3 | layout: 4 | sol_sources: . 5 | build_dir: build 6 | dependencies: {} 7 | ignore: [] 8 | name: ethereum-stringutils 9 | -------------------------------------------------------------------------------- /src/strings.sol: -------------------------------------------------------------------------------- 1 | /* 2 | * @title String & slice utility library for Solidity contracts. 3 | * @author Nick Johnson 4 | * 5 | * @dev Functionality in this library is largely implemented using an 6 | * abstraction called a 'slice'. A slice represents a part of a string - 7 | * anything from the entire string to a single character, or even no 8 | * characters at all (a 0-length slice). Since a slice only has to specify 9 | * an offset and a length, copying and manipulating slices is a lot less 10 | * expensive than copying and manipulating the strings they reference. 11 | * 12 | * To further reduce gas costs, most functions on slice that need to return 13 | * a slice modify the original one instead of allocating a new one; for 14 | * instance, `s.split(".")` will return the text up to the first '.', 15 | * modifying s to only contain the remainder of the string after the '.'. 16 | * In situations where you do not want to modify the original slice, you 17 | * can make a copy first with `.copy()`, for example: 18 | * `s.copy().split(".")`. Try and avoid using this idiom in loops; since 19 | * Solidity has no memory management, it will result in allocating many 20 | * short-lived slices that are later discarded. 21 | * 22 | * Functions that return two slices come in two versions: a non-allocating 23 | * version that takes the second slice as an argument, modifying it in 24 | * place, and an allocating version that allocates and returns the second 25 | * slice; see `nextRune` for example. 26 | * 27 | * Functions that have to copy string data will return strings rather than 28 | * slices; these can be cast back to slices for further processing if 29 | * required. 30 | * 31 | * For convenience, some functions are provided with non-modifying 32 | * variants that create a new slice and return both; for instance, 33 | * `s.splitNew('.')` leaves s unmodified, and returns two values 34 | * corresponding to the left and right parts of the string. 35 | */ 36 | 37 | pragma solidity ^0.8.0; 38 | 39 | library strings { 40 | struct slice { 41 | uint _len; 42 | uint _ptr; 43 | } 44 | 45 | function memcpy(uint dest, uint src, uint length) private pure { 46 | // Copy word-length chunks while possible 47 | for(; length >= 32; length -= 32) { 48 | assembly { 49 | mstore(dest, mload(src)) 50 | } 51 | dest += 32; 52 | src += 32; 53 | } 54 | 55 | // Copy remaining bytes 56 | uint mask = type(uint).max; 57 | if (length > 0) { 58 | mask = 256 ** (32 - length) - 1; 59 | } 60 | assembly { 61 | let srcpart := and(mload(src), not(mask)) 62 | let destpart := and(mload(dest), mask) 63 | mstore(dest, or(destpart, srcpart)) 64 | } 65 | } 66 | 67 | /* 68 | * @dev Returns a slice containing the entire string. 69 | * @param self The string to make a slice from. 70 | * @return A newly allocated slice containing the entire string. 71 | */ 72 | function toSlice(string memory self) internal pure returns (slice memory) { 73 | uint ptr; 74 | assembly { 75 | ptr := add(self, 0x20) 76 | } 77 | return slice(bytes(self).length, ptr); 78 | } 79 | 80 | /* 81 | * @dev Returns the length of a null-terminated bytes32 string. 82 | * @param self The value to find the length of. 83 | * @return The length of the string, from 0 to 32. 84 | */ 85 | function len(bytes32 self) internal pure returns (uint) { 86 | uint ret; 87 | if (self == 0) 88 | return 0; 89 | if (uint(self) & type(uint128).max == 0) { 90 | ret += 16; 91 | self = bytes32(uint(self) / 0x100000000000000000000000000000000); 92 | } 93 | if (uint(self) & type(uint64).max == 0) { 94 | ret += 8; 95 | self = bytes32(uint(self) / 0x10000000000000000); 96 | } 97 | if (uint(self) & type(uint32).max == 0) { 98 | ret += 4; 99 | self = bytes32(uint(self) / 0x100000000); 100 | } 101 | if (uint(self) & type(uint16).max == 0) { 102 | ret += 2; 103 | self = bytes32(uint(self) / 0x10000); 104 | } 105 | if (uint(self) & type(uint8).max == 0) { 106 | ret += 1; 107 | } 108 | return 32 - ret; 109 | } 110 | 111 | /* 112 | * @dev Returns a slice containing the entire bytes32, interpreted as a 113 | * null-terminated utf-8 string. 114 | * @param self The bytes32 value to convert to a slice. 115 | * @return A new slice containing the value of the input argument up to the 116 | * first null. 117 | */ 118 | function toSliceB32(bytes32 self) internal pure returns (slice memory ret) { 119 | // Allocate space for `self` in memory, copy it there, and point ret at it 120 | assembly { 121 | let ptr := mload(0x40) 122 | mstore(0x40, add(ptr, 0x20)) 123 | mstore(ptr, self) 124 | mstore(add(ret, 0x20), ptr) 125 | } 126 | ret._len = len(self); 127 | } 128 | 129 | /* 130 | * @dev Returns a new slice containing the same data as the current slice. 131 | * @param self The slice to copy. 132 | * @return A new slice containing the same data as `self`. 133 | */ 134 | function copy(slice memory self) internal pure returns (slice memory) { 135 | return slice(self._len, self._ptr); 136 | } 137 | 138 | /* 139 | * @dev Copies a slice to a new string. 140 | * @param self The slice to copy. 141 | * @return A newly allocated string containing the slice's text. 142 | */ 143 | function toString(slice memory self) internal pure returns (string memory) { 144 | string memory ret = new string(self._len); 145 | uint retptr; 146 | assembly { retptr := add(ret, 32) } 147 | 148 | memcpy(retptr, self._ptr, self._len); 149 | return ret; 150 | } 151 | 152 | /* 153 | * @dev Returns the length in runes of the slice. Note that this operation 154 | * takes time proportional to the length of the slice; avoid using it 155 | * in loops, and call `slice.empty()` if you only need to know whether 156 | * the slice is empty or not. 157 | * @param self The slice to operate on. 158 | * @return The length of the slice in runes. 159 | */ 160 | function len(slice memory self) internal pure returns (uint l) { 161 | // Starting at ptr-31 means the LSB will be the byte we care about 162 | uint ptr = self._ptr - 31; 163 | uint end = ptr + self._len; 164 | for (l = 0; ptr < end; l++) { 165 | uint8 b; 166 | assembly { b := and(mload(ptr), 0xFF) } 167 | if (b < 0x80) { 168 | ptr += 1; 169 | } else if(b < 0xE0) { 170 | ptr += 2; 171 | } else if(b < 0xF0) { 172 | ptr += 3; 173 | } else if(b < 0xF8) { 174 | ptr += 4; 175 | } else if(b < 0xFC) { 176 | ptr += 5; 177 | } else { 178 | ptr += 6; 179 | } 180 | } 181 | } 182 | 183 | /* 184 | * @dev Returns true if the slice is empty (has a length of 0). 185 | * @param self The slice to operate on. 186 | * @return True if the slice is empty, False otherwise. 187 | */ 188 | function empty(slice memory self) internal pure returns (bool) { 189 | return self._len == 0; 190 | } 191 | 192 | /* 193 | * @dev Returns a positive number if `other` comes lexicographically after 194 | * `self`, a negative number if it comes before, or zero if the 195 | * contents of the two slices are equal. Comparison is done per-rune, 196 | * on unicode codepoints. 197 | * @param self The first slice to compare. 198 | * @param other The second slice to compare. 199 | * @return The result of the comparison. 200 | */ 201 | function compare(slice memory self, slice memory other) internal pure returns (int) { 202 | uint shortest = self._len; 203 | if (other._len < self._len) 204 | shortest = other._len; 205 | 206 | uint selfptr = self._ptr; 207 | uint otherptr = other._ptr; 208 | for (uint idx = 0; idx < shortest; idx += 32) { 209 | uint a; 210 | uint b; 211 | assembly { 212 | a := mload(selfptr) 213 | b := mload(otherptr) 214 | } 215 | if (a != b) { 216 | // Mask out irrelevant bytes and check again 217 | uint mask = type(uint).max; // 0xffff... 218 | if(shortest < 32) { 219 | mask = ~(2 ** (8 * (32 - shortest + idx)) - 1); 220 | } 221 | unchecked { 222 | uint diff = (a & mask) - (b & mask); 223 | if (diff != 0) 224 | return int(diff); 225 | } 226 | } 227 | selfptr += 32; 228 | otherptr += 32; 229 | } 230 | return int(self._len) - int(other._len); 231 | } 232 | 233 | /* 234 | * @dev Returns true if the two slices contain the same text. 235 | * @param self The first slice to compare. 236 | * @param self The second slice to compare. 237 | * @return True if the slices are equal, false otherwise. 238 | */ 239 | function equals(slice memory self, slice memory other) internal pure returns (bool) { 240 | return compare(self, other) == 0; 241 | } 242 | 243 | /* 244 | * @dev Extracts the first rune in the slice into `rune`, advancing the 245 | * slice to point to the next rune and returning `self`. 246 | * @param self The slice to operate on. 247 | * @param rune The slice that will contain the first rune. 248 | * @return `rune`. 249 | */ 250 | function nextRune(slice memory self, slice memory rune) internal pure returns (slice memory) { 251 | rune._ptr = self._ptr; 252 | 253 | if (self._len == 0) { 254 | rune._len = 0; 255 | return rune; 256 | } 257 | 258 | uint l; 259 | uint b; 260 | // Load the first byte of the rune into the LSBs of b 261 | assembly { b := and(mload(sub(mload(add(self, 32)), 31)), 0xFF) } 262 | if (b < 0x80) { 263 | l = 1; 264 | } else if(b < 0xE0) { 265 | l = 2; 266 | } else if(b < 0xF0) { 267 | l = 3; 268 | } else { 269 | l = 4; 270 | } 271 | 272 | // Check for truncated codepoints 273 | if (l > self._len) { 274 | rune._len = self._len; 275 | self._ptr += self._len; 276 | self._len = 0; 277 | return rune; 278 | } 279 | 280 | self._ptr += l; 281 | self._len -= l; 282 | rune._len = l; 283 | return rune; 284 | } 285 | 286 | /* 287 | * @dev Returns the first rune in the slice, advancing the slice to point 288 | * to the next rune. 289 | * @param self The slice to operate on. 290 | * @return A slice containing only the first rune from `self`. 291 | */ 292 | function nextRune(slice memory self) internal pure returns (slice memory ret) { 293 | nextRune(self, ret); 294 | } 295 | 296 | /* 297 | * @dev Returns the number of the first codepoint in the slice. 298 | * @param self The slice to operate on. 299 | * @return The number of the first codepoint in the slice. 300 | */ 301 | function ord(slice memory self) internal pure returns (uint ret) { 302 | if (self._len == 0) { 303 | return 0; 304 | } 305 | 306 | uint word; 307 | uint length; 308 | uint divisor = 2 ** 248; 309 | 310 | // Load the rune into the MSBs of b 311 | assembly { word:= mload(mload(add(self, 32))) } 312 | uint b = word / divisor; 313 | if (b < 0x80) { 314 | ret = b; 315 | length = 1; 316 | } else if(b < 0xE0) { 317 | ret = b & 0x1F; 318 | length = 2; 319 | } else if(b < 0xF0) { 320 | ret = b & 0x0F; 321 | length = 3; 322 | } else { 323 | ret = b & 0x07; 324 | length = 4; 325 | } 326 | 327 | // Check for truncated codepoints 328 | if (length > self._len) { 329 | return 0; 330 | } 331 | 332 | for (uint i = 1; i < length; i++) { 333 | divisor = divisor / 256; 334 | b = (word / divisor) & 0xFF; 335 | if (b & 0xC0 != 0x80) { 336 | // Invalid UTF-8 sequence 337 | return 0; 338 | } 339 | ret = (ret * 64) | (b & 0x3F); 340 | } 341 | 342 | return ret; 343 | } 344 | 345 | /* 346 | * @dev Returns the keccak-256 hash of the slice. 347 | * @param self The slice to hash. 348 | * @return The hash of the slice. 349 | */ 350 | function keccak(slice memory self) internal pure returns (bytes32 ret) { 351 | assembly { 352 | ret := keccak256(mload(add(self, 32)), mload(self)) 353 | } 354 | } 355 | 356 | /* 357 | * @dev Returns true if `self` starts with `needle`. 358 | * @param self The slice to operate on. 359 | * @param needle The slice to search for. 360 | * @return True if the slice starts with the provided text, false otherwise. 361 | */ 362 | function startsWith(slice memory self, slice memory needle) internal pure returns (bool) { 363 | if (self._len < needle._len) { 364 | return false; 365 | } 366 | 367 | if (self._ptr == needle._ptr) { 368 | return true; 369 | } 370 | 371 | bool equal; 372 | assembly { 373 | let length := mload(needle) 374 | let selfptr := mload(add(self, 0x20)) 375 | let needleptr := mload(add(needle, 0x20)) 376 | equal := eq(keccak256(selfptr, length), keccak256(needleptr, length)) 377 | } 378 | return equal; 379 | } 380 | 381 | /* 382 | * @dev If `self` starts with `needle`, `needle` is removed from the 383 | * beginning of `self`. Otherwise, `self` is unmodified. 384 | * @param self The slice to operate on. 385 | * @param needle The slice to search for. 386 | * @return `self` 387 | */ 388 | function beyond(slice memory self, slice memory needle) internal pure returns (slice memory) { 389 | if (self._len < needle._len) { 390 | return self; 391 | } 392 | 393 | bool equal = true; 394 | if (self._ptr != needle._ptr) { 395 | assembly { 396 | let length := mload(needle) 397 | let selfptr := mload(add(self, 0x20)) 398 | let needleptr := mload(add(needle, 0x20)) 399 | equal := eq(keccak256(selfptr, length), keccak256(needleptr, length)) 400 | } 401 | } 402 | 403 | if (equal) { 404 | self._len -= needle._len; 405 | self._ptr += needle._len; 406 | } 407 | 408 | return self; 409 | } 410 | 411 | /* 412 | * @dev Returns true if the slice ends with `needle`. 413 | * @param self The slice to operate on. 414 | * @param needle The slice to search for. 415 | * @return True if the slice starts with the provided text, false otherwise. 416 | */ 417 | function endsWith(slice memory self, slice memory needle) internal pure returns (bool) { 418 | if (self._len < needle._len) { 419 | return false; 420 | } 421 | 422 | uint selfptr = self._ptr + self._len - needle._len; 423 | 424 | if (selfptr == needle._ptr) { 425 | return true; 426 | } 427 | 428 | bool equal; 429 | assembly { 430 | let length := mload(needle) 431 | let needleptr := mload(add(needle, 0x20)) 432 | equal := eq(keccak256(selfptr, length), keccak256(needleptr, length)) 433 | } 434 | 435 | return equal; 436 | } 437 | 438 | /* 439 | * @dev If `self` ends with `needle`, `needle` is removed from the 440 | * end of `self`. Otherwise, `self` is unmodified. 441 | * @param self The slice to operate on. 442 | * @param needle The slice to search for. 443 | * @return `self` 444 | */ 445 | function until(slice memory self, slice memory needle) internal pure returns (slice memory) { 446 | if (self._len < needle._len) { 447 | return self; 448 | } 449 | 450 | uint selfptr = self._ptr + self._len - needle._len; 451 | bool equal = true; 452 | if (selfptr != needle._ptr) { 453 | assembly { 454 | let length := mload(needle) 455 | let needleptr := mload(add(needle, 0x20)) 456 | equal := eq(keccak256(selfptr, length), keccak256(needleptr, length)) 457 | } 458 | } 459 | 460 | if (equal) { 461 | self._len -= needle._len; 462 | } 463 | 464 | return self; 465 | } 466 | 467 | // Returns the memory address of the first byte of the first occurrence of 468 | // `needle` in `self`, or the first byte after `self` if not found. 469 | function findPtr(uint selflen, uint selfptr, uint needlelen, uint needleptr) private pure returns (uint) { 470 | uint ptr = selfptr; 471 | uint idx; 472 | 473 | if (needlelen <= selflen) { 474 | if (needlelen <= 32) { 475 | bytes32 mask; 476 | if (needlelen > 0) { 477 | mask = bytes32(~(2 ** (8 * (32 - needlelen)) - 1)); 478 | } 479 | 480 | bytes32 needledata; 481 | assembly { needledata := and(mload(needleptr), mask) } 482 | 483 | uint end = selfptr + selflen - needlelen; 484 | bytes32 ptrdata; 485 | assembly { ptrdata := and(mload(ptr), mask) } 486 | 487 | while (ptrdata != needledata) { 488 | if (ptr >= end) 489 | return selfptr + selflen; 490 | ptr++; 491 | assembly { ptrdata := and(mload(ptr), mask) } 492 | } 493 | return ptr; 494 | } else { 495 | // For long needles, use hashing 496 | bytes32 hash; 497 | assembly { hash := keccak256(needleptr, needlelen) } 498 | 499 | for (idx = 0; idx <= selflen - needlelen; idx++) { 500 | bytes32 testHash; 501 | assembly { testHash := keccak256(ptr, needlelen) } 502 | if (hash == testHash) 503 | return ptr; 504 | ptr += 1; 505 | } 506 | } 507 | } 508 | return selfptr + selflen; 509 | } 510 | 511 | // Returns the memory address of the first byte after the last occurrence of 512 | // `needle` in `self`, or the address of `self` if not found. 513 | function rfindPtr(uint selflen, uint selfptr, uint needlelen, uint needleptr) private pure returns (uint) { 514 | uint ptr; 515 | 516 | if (needlelen <= selflen) { 517 | if (needlelen <= 32) { 518 | bytes32 mask; 519 | if (needlelen > 0) { 520 | mask = bytes32(~(2 ** (8 * (32 - needlelen)) - 1)); 521 | } 522 | 523 | bytes32 needledata; 524 | assembly { needledata := and(mload(needleptr), mask) } 525 | 526 | ptr = selfptr + selflen - needlelen; 527 | bytes32 ptrdata; 528 | assembly { ptrdata := and(mload(ptr), mask) } 529 | 530 | while (ptrdata != needledata) { 531 | if (ptr <= selfptr) 532 | return selfptr; 533 | ptr--; 534 | assembly { ptrdata := and(mload(ptr), mask) } 535 | } 536 | return ptr + needlelen; 537 | } else { 538 | // For long needles, use hashing 539 | bytes32 hash; 540 | assembly { hash := keccak256(needleptr, needlelen) } 541 | ptr = selfptr + (selflen - needlelen); 542 | while (ptr >= selfptr) { 543 | bytes32 testHash; 544 | assembly { testHash := keccak256(ptr, needlelen) } 545 | if (hash == testHash) 546 | return ptr + needlelen; 547 | ptr -= 1; 548 | } 549 | } 550 | } 551 | return selfptr; 552 | } 553 | 554 | /* 555 | * @dev Modifies `self` to contain everything from the first occurrence of 556 | * `needle` to the end of the slice. `self` is set to the empty slice 557 | * if `needle` is not found. 558 | * @param self The slice to search and modify. 559 | * @param needle The text to search for. 560 | * @return `self`. 561 | */ 562 | function find(slice memory self, slice memory needle) internal pure returns (slice memory) { 563 | uint ptr = findPtr(self._len, self._ptr, needle._len, needle._ptr); 564 | self._len -= ptr - self._ptr; 565 | self._ptr = ptr; 566 | return self; 567 | } 568 | 569 | /* 570 | * @dev Modifies `self` to contain the part of the string from the start of 571 | * `self` to the end of the first occurrence of `needle`. If `needle` 572 | * is not found, `self` is set to the empty slice. 573 | * @param self The slice to search and modify. 574 | * @param needle The text to search for. 575 | * @return `self`. 576 | */ 577 | function rfind(slice memory self, slice memory needle) internal pure returns (slice memory) { 578 | uint ptr = rfindPtr(self._len, self._ptr, needle._len, needle._ptr); 579 | self._len = ptr - self._ptr; 580 | return self; 581 | } 582 | 583 | /* 584 | * @dev Splits the slice, setting `self` to everything after the first 585 | * occurrence of `needle`, and `token` to everything before it. If 586 | * `needle` does not occur in `self`, `self` is set to the empty slice, 587 | * and `token` is set to the entirety of `self`. 588 | * @param self The slice to split. 589 | * @param needle The text to search for in `self`. 590 | * @param token An output parameter to which the first token is written. 591 | * @return `token`. 592 | */ 593 | function split(slice memory self, slice memory needle, slice memory token) internal pure returns (slice memory) { 594 | uint ptr = findPtr(self._len, self._ptr, needle._len, needle._ptr); 595 | token._ptr = self._ptr; 596 | token._len = ptr - self._ptr; 597 | if (ptr == self._ptr + self._len) { 598 | // Not found 599 | self._len = 0; 600 | } else { 601 | self._len -= token._len + needle._len; 602 | self._ptr = ptr + needle._len; 603 | } 604 | return token; 605 | } 606 | 607 | /* 608 | * @dev Splits the slice, setting `self` to everything after the first 609 | * occurrence of `needle`, and returning everything before it. If 610 | * `needle` does not occur in `self`, `self` is set to the empty slice, 611 | * and the entirety of `self` is returned. 612 | * @param self The slice to split. 613 | * @param needle The text to search for in `self`. 614 | * @return The part of `self` up to the first occurrence of `delim`. 615 | */ 616 | function split(slice memory self, slice memory needle) internal pure returns (slice memory token) { 617 | split(self, needle, token); 618 | } 619 | 620 | /* 621 | * @dev Splits the slice, setting `self` to everything before the last 622 | * occurrence of `needle`, and `token` to everything after it. If 623 | * `needle` does not occur in `self`, `self` is set to the empty slice, 624 | * and `token` is set to the entirety of `self`. 625 | * @param self The slice to split. 626 | * @param needle The text to search for in `self`. 627 | * @param token An output parameter to which the first token is written. 628 | * @return `token`. 629 | */ 630 | function rsplit(slice memory self, slice memory needle, slice memory token) internal pure returns (slice memory) { 631 | uint ptr = rfindPtr(self._len, self._ptr, needle._len, needle._ptr); 632 | token._ptr = ptr; 633 | token._len = self._len - (ptr - self._ptr); 634 | if (ptr == self._ptr) { 635 | // Not found 636 | self._len = 0; 637 | } else { 638 | self._len -= token._len + needle._len; 639 | } 640 | return token; 641 | } 642 | 643 | /* 644 | * @dev Splits the slice, setting `self` to everything before the last 645 | * occurrence of `needle`, and returning everything after it. If 646 | * `needle` does not occur in `self`, `self` is set to the empty slice, 647 | * and the entirety of `self` is returned. 648 | * @param self The slice to split. 649 | * @param needle The text to search for in `self`. 650 | * @return The part of `self` after the last occurrence of `delim`. 651 | */ 652 | function rsplit(slice memory self, slice memory needle) internal pure returns (slice memory token) { 653 | rsplit(self, needle, token); 654 | } 655 | 656 | /* 657 | * @dev Counts the number of nonoverlapping occurrences of `needle` in `self`. 658 | * @param self The slice to search. 659 | * @param needle The text to search for in `self`. 660 | * @return The number of occurrences of `needle` found in `self`. 661 | */ 662 | function count(slice memory self, slice memory needle) internal pure returns (uint cnt) { 663 | uint ptr = findPtr(self._len, self._ptr, needle._len, needle._ptr) + needle._len; 664 | while (ptr <= self._ptr + self._len) { 665 | cnt++; 666 | ptr = findPtr(self._len - (ptr - self._ptr), ptr, needle._len, needle._ptr) + needle._len; 667 | } 668 | } 669 | 670 | /* 671 | * @dev Returns True if `self` contains `needle`. 672 | * @param self The slice to search. 673 | * @param needle The text to search for in `self`. 674 | * @return True if `needle` is found in `self`, false otherwise. 675 | */ 676 | function contains(slice memory self, slice memory needle) internal pure returns (bool) { 677 | return rfindPtr(self._len, self._ptr, needle._len, needle._ptr) != self._ptr; 678 | } 679 | 680 | /* 681 | * @dev Returns a newly allocated string containing the concatenation of 682 | * `self` and `other`. 683 | * @param self The first slice to concatenate. 684 | * @param other The second slice to concatenate. 685 | * @return The concatenation of the two strings. 686 | */ 687 | function concat(slice memory self, slice memory other) internal pure returns (string memory) { 688 | string memory ret = new string(self._len + other._len); 689 | uint retptr; 690 | assembly { retptr := add(ret, 32) } 691 | memcpy(retptr, self._ptr, self._len); 692 | memcpy(retptr + self._len, other._ptr, other._len); 693 | return ret; 694 | } 695 | 696 | /* 697 | * @dev Joins an array of slices, using `self` as a delimiter, returning a 698 | * newly allocated string. 699 | * @param self The delimiter to use. 700 | * @param parts A list of slices to join. 701 | * @return A newly allocated string containing all the slices in `parts`, 702 | * joined with `self`. 703 | */ 704 | function join(slice memory self, slice[] memory parts) internal pure returns (string memory) { 705 | if (parts.length == 0) 706 | return ""; 707 | 708 | uint length = self._len * (parts.length - 1); 709 | for(uint i = 0; i < parts.length; i++) 710 | length += parts[i]._len; 711 | 712 | string memory ret = new string(length); 713 | uint retptr; 714 | assembly { retptr := add(ret, 32) } 715 | 716 | for(uint i = 0; i < parts.length; i++) { 717 | memcpy(retptr, parts[i]._ptr, parts[i]._len); 718 | retptr += parts[i]._len; 719 | if (i < parts.length - 1) { 720 | memcpy(retptr, self._ptr, self._len); 721 | retptr += self._len; 722 | } 723 | } 724 | 725 | return ret; 726 | } 727 | } 728 | -------------------------------------------------------------------------------- /src/strings.t.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.0; 2 | 3 | import 'ds-test/test.sol'; 4 | import './strings.sol'; 5 | 6 | contract StringsTest is DSTest { 7 | using strings for *; 8 | 9 | 10 | function abs(int x) private pure returns (int) { 11 | if(x < 0) 12 | return -x; 13 | return x; 14 | } 15 | 16 | function sign(int x) private pure returns (int) { 17 | return x == 0 ? int(0) : (x < 0 ? -1 : int(1)); 18 | } 19 | 20 | function assertEq0(string memory a, string memory b) internal { 21 | assertEq0(bytes(a), bytes(b)); 22 | } 23 | 24 | function assertEq0(strings.slice memory a, strings.slice memory b) internal { 25 | assertEq0(a.toString(), b.toString()); 26 | } 27 | 28 | function assertEq0(strings.slice memory a, string memory b) internal { 29 | assertEq0(a.toString(), b); 30 | } 31 | 32 | function testSliceToString() public { 33 | string memory test = "Hello, world!"; 34 | assertEq0(test, test.toSlice().toString()); 35 | } 36 | 37 | function testBytes32Len() public { 38 | bytes32 test; 39 | for(uint i = 0; i <= 32; i++) { 40 | assertEq(i, test.len()); 41 | test = bytes32((uint(test) / 0x100) | 0x2000000000000000000000000000000000000000000000000000000000000000); 42 | } 43 | } 44 | 45 | 46 | function testToSliceB32() public { 47 | assertEq0(bytes32("foobar").toSliceB32(), "foobar".toSlice()); 48 | } 49 | 50 | function testCopy() public { 51 | string memory test = "Hello, world!"; 52 | strings.slice memory s1 = test.toSlice(); 53 | strings.slice memory s2 = s1.copy(); 54 | s1._len = 0; 55 | assertEq(s2._len, bytes(test).length); 56 | } 57 | 58 | function testLen() public { 59 | assertEq("".toSlice().len(), 0); 60 | assertEq("Hello, world!".toSlice().len(), 13); 61 | assertEq(unicode"naïve".toSlice().len(), 5); 62 | assertEq(unicode"こんにちは".toSlice().len(), 5); 63 | } 64 | 65 | function testEmpty() public { 66 | assertTrue("".toSlice().empty()); 67 | assertTrue(!"x".toSlice().empty()); 68 | } 69 | 70 | function testEquals() public { 71 | assertTrue("".toSlice().equals("".toSlice())); 72 | assertTrue("foo".toSlice().equals("foo".toSlice())); 73 | assertTrue(!"foo".toSlice().equals("bar".toSlice())); 74 | } 75 | 76 | function testNextRune() public { 77 | strings.slice memory s = unicode"a¡ࠀ𐀡".toSlice(); 78 | assertEq0(s.nextRune(), "a"); 79 | assertEq0(s, unicode"¡ࠀ𐀡"); 80 | assertEq0(s.nextRune(), unicode"¡"); 81 | assertEq0(s, unicode"ࠀ𐀡"); 82 | assertEq0(s.nextRune(), unicode"ࠀ"); 83 | assertEq0(s, unicode"𐀡"); 84 | assertEq0(s.nextRune(), unicode"𐀡"); 85 | assertEq0(s, ""); 86 | assertEq0(s.nextRune(), ""); 87 | } 88 | 89 | function testOrd() public { 90 | assertEq("a".toSlice().ord(), 0x61); 91 | assertEq(unicode"¡".toSlice().ord(), 0xA1); 92 | assertEq(unicode"ࠀ".toSlice().ord(), 0x800); 93 | assertEq(unicode"𐀡".toSlice().ord(), 0x10021); 94 | } 95 | 96 | function testCompare() public { 97 | 98 | assertEq(sign("foobie".toSlice().compare("foobie".toSlice())), 0); 99 | assertEq(sign("foobie".toSlice().compare("foobif".toSlice())), -1); 100 | assertEq(sign("foobie".toSlice().compare("foobid".toSlice())), 1); 101 | assertEq(sign("foobie".toSlice().compare("foobies".toSlice())), -1); 102 | assertEq(sign("foobie".toSlice().compare("foobi".toSlice())), 1); 103 | assertEq(sign("foobie".toSlice().compare("doobie".toSlice())), 1); 104 | assertEq(sign("01234567890123456789012345678901".toSlice().compare("012345678901234567890123456789012".toSlice())), -1); 105 | assertEq(sign("0123456789012345678901234567890123".toSlice().compare("1123456789012345678901234567890123".toSlice())), -1); 106 | assertEq(sign("foo.bar".toSlice().split(".".toSlice()).compare("foo".toSlice())), 0); 107 | } 108 | 109 | function testStartsWith() public { 110 | strings.slice memory s = "foobar".toSlice(); 111 | assertTrue(s.startsWith("foo".toSlice())); 112 | assertTrue(!s.startsWith("oob".toSlice())); 113 | assertTrue(s.startsWith("".toSlice())); 114 | assertTrue(s.startsWith(s.copy().rfind("foo".toSlice()))); 115 | } 116 | 117 | function testBeyond() public { 118 | strings.slice memory s = "foobar".toSlice(); 119 | assertEq0(s.beyond("foo".toSlice()), "bar"); 120 | assertEq0(s, "bar"); 121 | assertEq0(s.beyond("foo".toSlice()), "bar"); 122 | assertEq0(s.beyond("bar".toSlice()), ""); 123 | assertEq0(s, ""); 124 | } 125 | 126 | function testEndsWith() public { 127 | strings.slice memory s = "foobar".toSlice(); 128 | assertTrue(s.endsWith("bar".toSlice())); 129 | assertTrue(!s.endsWith("oba".toSlice())); 130 | assertTrue(s.endsWith("".toSlice())); 131 | assertTrue(s.endsWith(s.copy().find("bar".toSlice()))); 132 | } 133 | 134 | function testUntil() public { 135 | strings.slice memory s = "foobar".toSlice(); 136 | assertEq0(s.until("bar".toSlice()), "foo"); 137 | assertEq0(s, "foo"); 138 | assertEq0(s.until("bar".toSlice()), "foo"); 139 | assertEq0(s.until("foo".toSlice()), ""); 140 | assertEq0(s, ""); 141 | } 142 | 143 | function testFind() public { 144 | assertEq0("abracadabra".toSlice().find("abracadabra".toSlice()), "abracadabra"); 145 | assertEq0("abracadabra".toSlice().find("bra".toSlice()), "bracadabra"); 146 | assertTrue("abracadabra".toSlice().find("rab".toSlice()).empty()); 147 | assertTrue("12345".toSlice().find("123456".toSlice()).empty()); 148 | assertEq0("12345".toSlice().find("".toSlice()), "12345"); 149 | assertEq0("12345".toSlice().find("5".toSlice()), "5"); 150 | } 151 | 152 | function testRfind() public { 153 | assertEq0("abracadabra".toSlice().rfind("bra".toSlice()), "abracadabra"); 154 | assertEq0("abracadabra".toSlice().rfind("cad".toSlice()), "abracad"); 155 | assertTrue("12345".toSlice().rfind("123456".toSlice()).empty()); 156 | assertEq0("12345".toSlice().rfind("".toSlice()), "12345"); 157 | assertEq0("12345".toSlice().rfind("1".toSlice()), "1"); 158 | } 159 | 160 | function testSplit() public { 161 | strings.slice memory s = "foo->bar->baz".toSlice(); 162 | strings.slice memory delim = "->".toSlice(); 163 | assertEq0(s.split(delim), "foo"); 164 | assertEq0(s, "bar->baz"); 165 | assertEq0(s.split(delim), "bar"); 166 | assertEq0(s.split(delim), "baz"); 167 | assertTrue(s.empty()); 168 | assertEq0(s.split(delim), ""); 169 | assertEq0(".".toSlice().split(".".toSlice()), ""); 170 | } 171 | 172 | function testRsplit() public { 173 | strings.slice memory s = "foo->bar->baz".toSlice(); 174 | strings.slice memory delim = "->".toSlice(); 175 | assertEq0(s.rsplit(delim), "baz"); 176 | assertEq0(s.rsplit(delim), "bar"); 177 | assertEq0(s.rsplit(delim), "foo"); 178 | assertTrue(s.empty()); 179 | assertEq0(s.rsplit(delim), ""); 180 | } 181 | 182 | function testCount() public { 183 | assertEq("1121123211234321".toSlice().count("1".toSlice()), 7); 184 | assertEq("ababababa".toSlice().count("aba".toSlice()), 2); 185 | } 186 | 187 | function testContains() public { 188 | assertTrue("foobar".toSlice().contains("f".toSlice())); 189 | assertTrue("foobar".toSlice().contains("o".toSlice())); 190 | assertTrue("foobar".toSlice().contains("r".toSlice())); 191 | assertTrue("foobar".toSlice().contains("".toSlice())); 192 | assertTrue("foobar".toSlice().contains("foobar".toSlice())); 193 | assertTrue(!"foobar".toSlice().contains("s".toSlice())); 194 | } 195 | 196 | function testConcat() public { 197 | assertEq0("foo".toSlice().concat("bar".toSlice()), "foobar"); 198 | assertEq0("".toSlice().concat("bar".toSlice()), "bar"); 199 | assertEq0("foo".toSlice().concat("".toSlice()), "foo"); 200 | } 201 | 202 | function testJoin() public { 203 | strings.slice[] memory parts = new strings.slice[](4); 204 | parts[0] = "zero".toSlice(); 205 | parts[1] = "one".toSlice(); 206 | parts[2] = "".toSlice(); 207 | parts[3] = "two".toSlice(); 208 | 209 | assertEq0(" ".toSlice().join(parts), "zero one two"); 210 | assertEq0("".toSlice().join(parts), "zeroonetwo"); 211 | 212 | parts = new strings.slice[](1); 213 | parts[0] = "zero".toSlice(); 214 | assertEq0(" ".toSlice().join(parts), "zero"); 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /strings.sol: -------------------------------------------------------------------------------- 1 | ./src/strings.sol --------------------------------------------------------------------------------