Giter VIP home page Giter VIP logo

solidity-stringutils's Introduction

String & slice utility library for Solidity

Overview

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.

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.

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.

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.

Examples

Basic usage

import "github.com/Arachnid/solidity-stringutils/strings.sol";

contract Contract {
    using strings for *;

    // ...
}

Getting the character length of a string

var len = "Unicode snowman ☃".toSlice().len(); // 17

Splitting a string around a delimiter

var s = "foo bar baz".toSlice();
var foo = s.split(" ".toSlice());

After the above code executes, s is now "bar baz", and foo is now "foo".

Splitting a string into an array

var s = "www.google.com".toSlice();
var delim = ".".toSlice();
var parts = new string[](s.count(delim) + 1);
for(uint i = 0; i < parts.length; i++) {
    parts[i] = s.split(delim).toString();
}

Extracting the middle part of a string

var s = "www.google.com".toSlice();
strings.slice memory part;
s.split(".".toSlice(), part); // part and return value is "www"
s.split(".".toSlice(), part); // part and return value is "google"

This approach uses less memory than the above, by reusing the slice part for each section of string extracted.

Converting a slice back to a string

var myString = mySlice.toString();

Finding and returning the first occurrence of a substring

var s = "A B C B D".toSlice();
s.find("B".toSlice()); // "B C B D"

find modifies s to contain the part of the string from the first match onwards.

Finding and returning the last occurrence of a substring

var s = "A B C B D".toSlice();
s.rfind("B".toSlice()); // "A B C B"

rfind modifies s to contain the part of the string from the last match back to the start.

Finding without modifying the original slice.

var s = "A B C B D".toSlice();
var substring = s.copy().rfind("B".toSlice()); // "A B C B"

copy lets you cheaply duplicate a slice so you don't modify the original.

Prefix and suffix matching

var s = "A B C B D".toSlice();
s.startsWith("A".toSlice()); // True
s.endsWith("D".toSlice()); // True
s.startsWith("B".toSlice()); // False

Removing a prefix or suffix

var s = "A B C B D".toSlice();
s.beyond("A ".toSlice()).until(" D".toSlice()); // "B C B"

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.

Finding and returning the string up to the first match

var s = "A B C B D".toSlice();
var needle = "B".toSlice();
var substring = s.until(s.copy().find(needle).beyond(needle));

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.

Concatenating strings

var s = "abc".toSlice().concat("def".toSlice()); // "abcdef"

Reference

toSlice(string self) internal returns (slice)

Returns a slice containing the entire string.

Arguments:

  • self The string to make a slice from.

Returns A newly allocated slice containing the entire string.

copy(slice self) internal returns (slice)

Returns a new slice containing the same data as the current slice.

Arguments:

  • self The slice to copy.

Returns A new slice containing the same data as self.

toString(slice self) internal returns (string)

Copies a slice to a new string.

Arguments:

  • self The slice to copy.

Returns A newly allocated string containing the slice's text.

len(slice self) internal returns (uint)

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.

Arguments:

  • self The slice to operate on.

Returns The length of the slice in runes.

empty(slice self) internal returns (bool)

Returns true if the slice is empty (has a length of 0).

Arguments:

  • self The slice to operate on.

Returns True if the slice is empty, False otherwise.

compare(slice self, slice other) internal returns (int)

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.

Arguments:

  • self The first slice to compare.
  • other The second slice to compare.

Returns The result of the comparison.

equals(slice self, slice other) internal returns (bool)

Returns true if the two slices contain the same text.

Arguments:

  • self The first slice to compare.
  • self The second slice to compare.

Returns True if the slices are equal, false otherwise.

nextRune(slice self, slice rune) internal returns (slice)

Extracts the first rune in the slice into rune, advancing the slice to point to the next rune and returning self.

Arguments:

  • self The slice to operate on.
  • rune The slice that will contain the first rune.

Returns rune.

nextRune(slice self) internal returns (slice ret)

Returns the first rune in the slice, advancing the slice to point to the next rune.

Arguments:

  • self The slice to operate on.

Returns A slice containing only the first rune from self.

ord(slice self) internal returns (uint ret)

Returns the number of the first codepoint in the slice.

Arguments:

  • self The slice to operate on.

Returns The number of the first codepoint in the slice.

keccak(slice self) internal returns (bytes32 ret)

Returns the keccak-256 hash of the slice.

Arguments:

  • self The slice to hash.

Returns The hash of the slice.

startsWith(slice self, slice needle) internal returns (bool)

Returns true if self starts with needle.

Arguments:

  • self The slice to operate on.
  • needle The slice to search for.

Returns True if the slice starts with the provided text, false otherwise.

beyond(slice self, slice needle) internal returns (slice)

If self starts with needle, needle is removed from the beginning of self. Otherwise, self is unmodified.

Arguments:

  • self The slice to operate on.
  • needle The slice to search for.

Returns self

endsWith(slice self, slice needle) internal returns (bool)

Returns true if the slice ends with needle.

Arguments:

  • self The slice to operate on.
  • needle The slice to search for.

Returns True if the slice starts with the provided text, false otherwise.

until(slice self, slice needle) internal returns (slice)

If self ends with needle, needle is removed from the end of self. Otherwise, self is unmodified.

Arguments:

  • self The slice to operate on.
  • needle The slice to search for.

Returns self

find(slice self, slice needle) internal returns (slice)

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.

Arguments:

  • self The slice to search and modify.
  • needle The text to search for.

Returns self.

rfind(slice self, slice needle) internal returns (slice)

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.

Arguments:

  • self The slice to search and modify.
  • needle The text to search for.

Returns self.

split(slice self, slice needle, slice token) internal returns (slice)

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.

Arguments:

  • self The slice to split.
  • needle The text to search for in self.
  • token An output parameter to which the first token is written.

Returns token.

split(slice self, slice needle) internal returns (slice token)

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.

Arguments:

  • self The slice to split.
  • needle The text to search for in self.

Returns The part of self up to the first occurrence of delim.

rsplit(slice self, slice needle, slice token) internal returns (slice)

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.

Arguments:

  • self The slice to split.
  • needle The text to search for in self.
  • token An output parameter to which the first token is written.

Returns token.

rsplit(slice self, slice needle) internal returns (slice token)

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.

Arguments:

  • self The slice to split.
  • needle The text to search for in self.

Returns The part of self after the last occurrence of delim.

count(slice self, slice needle) internal returns (uint count)

Counts the number of nonoverlapping occurrences of needle in self.

Arguments:

  • self The slice to search.
  • needle The text to search for in self.

Returns The number of occurrences of needle found in self.

contains(slice self, slice needle) internal returns (bool)

Returns True if self contains needle.

Arguments:

  • self The slice to search.
  • needle The text to search for in self.

Returns True if needle is found in self, false otherwise.

concat(slice self, slice other) internal returns (string)

Returns a newly allocated string containing the concatenation of self and other.

Arguments:

  • self The first slice to concatenate.
  • other The second slice to concatenate.

Returns The concatenation of the two strings.

join(slice self, slice[] parts) internal returns (string)

Joins an array of slices, using self as a delimiter, returning a newly allocated string.

Arguments:

  • self The delimiter to use.
  • parts A list of slices to join.

Returns A newly allocated string containing all the slices in parts, joined with self.

solidity-stringutils's People

Contributors

0xrin1 avatar anudit avatar arachnid avatar axic avatar chriseth avatar dexaran avatar minaminao avatar ovangle avatar robertzk avatar ryanrhall avatar xeroblood-io avatar zynis avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

solidity-stringutils's Issues

tests fail

Hello :D, I've noticed that one test fails testCompare after updating to solc 0.4.3/dapple 0.8.15. Also I'm getting a lot of warnings like these.

_0xb3668356078994a35ee131f8ba3e76d3441e5f52056a912f94278c22ff495034.sol:55:18: Warning: Inline assembly block is not balanced. It leaves 2 item(s) on the stack.
        assembly {
                 ^
Spanning multiple lines.
_0xb3668356078994a35ee131f8ba3e76d3441e5f52056a912f94278c22ff495034.sol:115:18: Warning: Inline assembly block is not balanced. It leaves 1 item(s) on the stack.
        assembly {
                 ^
...
StringsTest
  test compare
  Error at `e only h'

Not supported for progma solidity >=0.4.22 <0.6.2;

Source "github.com/Arachnid/solidity-stringutils/strings.sol" not found: File import callback not supported

pragma solidity >=0.4.22 <0.6.2;
import "github.com/Arachnid/solidity-stringutils/strings.sol"

Member "toSlice" not found or not visible after argument-dependent lookup

I cannot use the library. Even with your own example:

    var len = "Unicode snowman ☃".toSlice().len();                                                                         

The compiler complains:

 % solc --abi registry.sol
registry.sol:26:15: Error: Member "toSlice" not found or not visible after argument-dependent lookup in literal_string "Unicode snowman ☃"
   uint len = "Unicode snowman ☃".toSlice().len(); 
              ^---------------------------^
% solc --v
solc, the solidity compiler commandline interface
Version: 0.3.2-0/Release-Linux/g++/Interpreter

support for solidity 0.5.x

Does the library support solidity version 0.5.x? I am getting this error "Source file requires different compiler version" for solc version 0.5.4

Function to convert Integer to String

Something along the lines of:

function uintToString(uint v) constant returns (string str) {
        uint maxlength = 100;
        bytes memory reversed = new bytes(maxlength);
        uint i = 0;
        while (v != 0) {
            uint remainder = v % 10;
            v = v / 10;
            reversed[i++] = byte(48 + remainder);
        }
        bytes memory s = new bytes(i);
        for (uint j = 0; j < i; j++) {
            s[j] = reversed[i - 1 - j];
        }
        str = string(s);
    }

Source: https://ethereum.stackexchange.com/questions/10811/solidity-concatenate-uint-into-a-string

Unable to get "join" working

Getting the following error.

Error: Invalid type for argument in function call. Invalid implicit conversion from struct strings.slice memory[2] memory to struct strings.slice memory[] memory requested.
                var book = ";".toSlice().join([catalog[i].title.toSlice(), catalog[i].publisher.toSlice()]);
                                              ^----------------------------------------------------------^

Reference doc for "rfind" is misleading

in the reference section for rfind, it says:
"Modifies self to contain the part of the string from the start of self to the end of the first occurrence of needle."

it should better read:
"Modifies self to contain the part of the string from the start of self to the end of the last occurrence of needle."

Mark eligible functions as pure/view

Nearly all of the string utility functions can be declared as view or even pure, since they do not modify any state but only meddle with the arguments passed to them.

Please add these keywords where possible in order to enable users to keep their functions declared as view or pure without the compiler complaining that one of the called methods in the function may modify the state.

strings.slice stored in structs when retrieved and converted into slice yields gibberish when console.logged

I have a struct like this

struct Question {
        strings.slice question;
        strings.slice answer;
    }

And a mapping mapping(uint256 => QA) public questions; and a proposeQuestion function that looks like this

        public
        returns (bool done)
    {
        questions[questionId].question = _question.toSlice();
        questions[questionId].answer = _answer.toSlice();
        questionId++;
        return true;
    }

When I retrieve the question and the answer stored in the struct which is stored in a mapping, and console log it, using foundry

function testQuestion() public {

        string memory q = "What?";
        console.log(q, q.toSlice().toString());
        myContract.proposeQuestion(q, unicode"すみません");
        assertTrue(
            keccak256(abi.encodePacked(q)) ==
                keccak256(abi.encodePacked(q.toSlice().toString()))
        );
        (          
            strings.slice memory question,
            strings.slice memory answer
        ) = myContract.questions(0);
        console.log(question.toString());

The console prints out gibberish ,.��

However, if I just do this console.log(q, q.toSlice().toString()) they print out the same thing! What's going on here?

PS:

Also, when I tried this out, this test fails

 assertEq(
            keccak256(abi.encodePacked(q)),
            keccak256(abi.encodePacked(question.toString()))
        );

and yields this error

    Expected: 0xc649b6993c4fe2cdb223c885f0145101f4d5ef046eebceed76b6431c34626612
      Actual: 0xf8a49a8b34d42855db8ed1c016fa2fe0354abc43323b8d67be08e3837aae8b60

Test result: FAILED. 0 passed; 1 failed; finished in 5.19ms```
 

var alternative for "Splitting a string into an array"?

I tried this function in remix and it works fine but when creating a contract it gives var keyword error is deprecated. Could you please give an example?

It is for :

var s = "www.google.com".toSlice();
var delim = ".".toSlice();
var parts = new string[](s.count(delim) + 1);
for(uint i = 0; i < parts.length; i++) {
    parts[i] = s.split(delim).toString();
}

but I can not replace var correctly

Support for solidity 0.4.1

When trying to include the library with solidity 0.4.1 compiler I get following error:

Internal compiler error during compilation:
/tmp/solidity-20160916-54771-1tkfwpz/solidity-0.4.1/libsolidity/interface/Version.cpp(68): Throw in function bytes dev::solidity::binaryVersion()
Dynamic exception type: boost::exception_detail::clone_impl<dev::solidity::InternalCompilerError>
std::exception::what: std::exception
[dev::tag_comment*] = 

Solc version:

solc, the solidity compiler commandline interface
Version: 0.4.1+commit.4fc6fc2.Darwin.appleclang

Warnings when compiling

Compilation warnings encountered:

StringUtil.sol:250:5: Warning: This declaration shadows an existing declaration.
    uint len;
    ^------^
StringUtil.sol:157:3: The shadowed declaration is here:
  function len(slice self) internal returns (uint l) {
  ^
Spanning multiple lines.



StringUtil.sol:250:5: Warning: This declaration shadows an existing declaration.
    uint len;
    ^------^
StringUtil.sol:157:3: The shadowed declaration is here:
  function len(slice self) internal returns (uint l) {
  ^

Solidity: 0.4.18
Compiler: Truffle 4.0.7

Error in compare

This line should be "if(shortest - idx < 32){" otherwise we don't mask out the bits of the shortest string which aren't part of the string. Eg if we have two slices one with 33 bytes and one with 34 bytes the algorithm will compare the first 32 bytes then move on to the next 32 bytes, but shortest variable will be 33 so the current code will not mask out the last 31 bytes of this next chunk of memory.

if(shortest < 32) {

How to Apply the Apache2 license to a Contract

The stringutils library is using the Apache2 license. Under that license it says this,

  1. Redistribution. You may reproduce and distribute copies of the
    Work or Derivative Works thereof in any medium, with or without
    modifications, and in Source or Object form, provided that You
    meet the following conditions:
  (a) You must give any other recipients of the Work or
      Derivative Works a copy of this License; and

If I include a copy of the stringutils library in my contract, compile it and then deploy it, how can I send each user who uses my contract a copy of the apache2 license? How can I comply with this license if I use the stringutils library in my contract?

Thanks for any clarification of this.

publish to ethpm?

Could you please publish this to ethpm so it can be more easily integrated into projects? Thanks!

concatenate three strings

How would concatenate three strings?

var 2 = "abc".toSlice().concat("def".toSlice().concat("ghi".toSlice()));

What am I missing?

Issue appeared in remix

Warning: This declaration shadows an existing declaration.
--> .deps/github/Arachnid/solidity-stringutils/src/strings.sol:46:42:
|
46 | function memcpy(uint dest, uint src, uint len) private pure {
| ^^^^^^^^
Note: The shadowed declaration is here:
--> .deps/github/Arachnid/solidity-stringutils/src/strings.sol:86:5:
|
86 | function len(bytes32 self) internal pure returns (uint) {
| ^ (Relevant source part starts here and spans across multiple lines).
Note: The shadowed declaration is here:
--> .deps/github/Arachnid/solidity-stringutils/src/strings.sol:161:5:
|
161 | function len(slice memory self) internal pure returns (uint l) {
| ^ (Relevant source part starts here and spans across multiple lines).

Looks like the issue is completely inside this repo?

Solidity 0.8 btw.

Tx silently fails if payload is too large but shows up as succesful

Hi there,

I'm attempting to jam a ton of text into a smart contract on the rinkeby network. (~12kb).
The contract in essence takes text supplied and concats it to its internal data variable.
When I try to shove the whole 12kb payload in a single TX it fails silently while showing a succesfully mined TX.
But if I break it up into smaller TXs it works till 4605 bytes at least.
I chunked the whole text blob into 3. Attempting to send the TX with the s3 sized payload fails silently, but shows up in metamask as succesful. The other two work fine.

s3.length
4794
s2.length
3031
s1.length
4605

Here is the contract code:

import "github.com/Arachnid/solidity-stringutils/strings.sol";

contract PermanentDataStore {
    using strings for *;
    string public data;
    function PermanentDataStore(string _data) public {
        concat(data, _data);
    }

    function get() constant returns (string) {
        return data;
    }

    function add(string _data) public {
        concat(data, _data);
    }

    function clear() public {
        data = "";
    }

    function concat(string s1, string s2) {
        data = s1.toSlice().concat(s2.toSlice());
    }
}

I'm wondering what the limit is and if it is documented anywhere?
Also would it be possible to alert when it fails?

Expected behaviour would be either an alert that there was an issue, or a block that prevents initiating a tx with a payload too large.

Browser is chrome 59. OS is macOS 10.12.

Steps to reproduce:

  • Deploy the contract on rinkeby.
  • Generate a 12kb blob of text.
  • Try calling contract.add(string) from the browsers console with various sized chunks of text.

Same issue: MetaMask/metamask-extension#1809

compile warn

msg:
Warning: This declaration shadows an existing declaration.
--> contracts/CRUD.sol:125:42:
|
125 | function memcpy(uint dest, uint src, uint len) private pure {
| ^^^^^^^^
Note: The shadowed declaration is here:
--> contracts/CRUD.sol:165:5:
|
165 | function len(bytes32 self) internal pure returns (uint) {
| ^ (Relevant source part starts here and spans across multiple lines).
Note: The shadowed declaration is here:
--> contracts/CRUD.sol:240:5:
|
240 | function len(slice memory self) internal pure returns (uint l) {
| ^ (Relevant source part starts here and spans across multiple lines).

code:

//SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.7;
contract CRUD {
    using strings for *;
    struct User {
        string account;
        string password;
        uint status;
        uint createdAt;
        uint updatedAt;
    }

    mapping(string => User) public users;
    //for iter
    string[] public accounts;

    function createUser(string memory _account, string memory _password) public {
        //        確認還沒創造
        require(indexOf(accounts, _account) == 2 ^ 256 - 1, "already exist");
        users[_account] = User(_account, _password, 1, block.timestamp, block.timestamp);
        accounts.push(_account);
    }

    function updateUser(string memory _account, string memory _password) public {
        //        確認存在
        require(indexOf(accounts, _account) != 2 ^ 256 - 1, "not exit");
        users[_account].password = _password;
        //        users[_account].status = _status;
        users[_account].updatedAt = block.timestamp;
    }

    function deleteUser(string memory _account) public {
        //        確認存在
        require(indexOf(accounts, _account) != 2 ^ 256 - 1, "not exit");

        //        delete users[_account];
        users[_account].status = 0;
        //        delete accounts[indexOf(accounts,_account)];
    }

    function readUsers(string memory search_word) public view returns (User[] memory){
        User[] memory result = new User[](accounts.length);
        // search_word='' return all
        if (search_word.toSlice()._len == 0) {
            // get all user status = 1
            for (uint256 i = 0; i < accounts.length; i++) {
//                中文
                if (users[accounts[i]].status == 1) {
                    result[i] = users[accounts[i]];
                }
            }
        }
        return result;
    }


    //    function getAccountLength() public view returns (uint256){
    //        return accounts.length;
    //    }
    //
        function indexOf(string[] memory arr, string memory searchFor) private pure returns (uint256) {
            for (uint256 i = 0; i < arr.length; i++) {
                //            if (arr[i] == searchFor) {
                if (arr[i].toSlice().equals(searchFor.toSlice())) {
//                if (equal(arr[i], searchFor)) {
                    return i;
                }
            }
            return 2 ^ 256 - 1;
            // not found
        }
    //
    //    function equal(string memory a, string memory b) private pure returns (bool) {
    //        return keccak256(bytes(a)) == keccak256(bytes(b));
    //    }
    //

}


/*
 * @title String & slice utility library for Solidity contracts.
 * @author Nick Johnson <[email protected]>
 *
 * @dev 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.
 *
 *      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.
 *
 *      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.
 *
 *      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.
 *
 *      For convenience, some functions are provided with non-modifying
 *      variants that create a new slice and return both; for instance,
 *      `s.splitNew('.')` leaves s unmodified, and returns two values
 *      corresponding to the left and right parts of the string.
 */

pragma solidity ^0.8.0;

library strings {
    struct slice {
        uint _len;
        uint _ptr;
    }

    function memcpy(uint dest, uint src, uint len) private pure {
        // Copy word-length chunks while possible
        for (; len >= 32; len -= 32) {
            assembly {
                mstore(dest, mload(src))
            }
            dest += 32;
            src += 32;
        }

        // Copy remaining bytes
        uint mask = type(uint).max;
        if (len > 0) {
            mask = 256 ** (32 - len) - 1;
        }
        assembly {
            let srcpart := and(mload(src), not(mask))
            let destpart := and(mload(dest), mask)
            mstore(dest, or(destpart, srcpart))
        }
    }

    /*
     * @dev Returns a slice containing the entire string.
     * @param self The string to make a slice from.
     * @return A newly allocated slice containing the entire string.
     */
    function toSlice(string memory self) internal pure returns (slice memory) {
        uint ptr;
        assembly {
            ptr := add(self, 0x20)
        }
        return slice(bytes(self).length, ptr);
    }

    /*
     * @dev Returns the length of a null-terminated bytes32 string.
     * @param self The value to find the length of.
     * @return The length of the string, from 0 to 32.
     */
    function len(bytes32 self) internal pure returns (uint) {
        uint ret;
        if (self == 0)
            return 0;
        if (uint(self) & type(uint128).max == 0) {
            ret += 16;
            self = bytes32(uint(self) / 0x100000000000000000000000000000000);
        }
        if (uint(self) & type(uint64).max == 0) {
            ret += 8;
            self = bytes32(uint(self) / 0x10000000000000000);
        }
        if (uint(self) & type(uint32).max == 0) {
            ret += 4;
            self = bytes32(uint(self) / 0x100000000);
        }
        if (uint(self) & type(uint16).max == 0) {
            ret += 2;
            self = bytes32(uint(self) / 0x10000);
        }
        if (uint(self) & type(uint8).max == 0) {
            ret += 1;
        }
        return 32 - ret;
    }

    /*
     * @dev Returns a slice containing the entire bytes32, interpreted as a
     *      null-terminated utf-8 string.
     * @param self The bytes32 value to convert to a slice.
     * @return A new slice containing the value of the input argument up to the
     *         first null.
     */
    function toSliceB32(bytes32 self) internal pure returns (slice memory ret) {
        // Allocate space for `self` in memory, copy it there, and point ret at it
        assembly {
            let ptr := mload(0x40)
            mstore(0x40, add(ptr, 0x20))
            mstore(ptr, self)
            mstore(add(ret, 0x20), ptr)
        }
        ret._len = len(self);
    }

    /*
     * @dev Returns a new slice containing the same data as the current slice.
     * @param self The slice to copy.
     * @return A new slice containing the same data as `self`.
     */
    function copy(slice memory self) internal pure returns (slice memory) {
        return slice(self._len, self._ptr);
    }

    /*
     * @dev Copies a slice to a new string.
     * @param self The slice to copy.
     * @return A newly allocated string containing the slice's text.
     */
    function toString(slice memory self) internal pure returns (string memory) {
        string memory ret = new string(self._len);
        uint retptr;
        assembly {retptr := add(ret, 32)}

        memcpy(retptr, self._ptr, self._len);
        return ret;
    }

    /*
     * @dev 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.
     * @param self The slice to operate on.
     * @return The length of the slice in runes.
     */
    function len(slice memory self) internal pure returns (uint l) {
        // Starting at ptr-31 means the LSB will be the byte we care about
        uint ptr = self._ptr - 31;
        uint end = ptr + self._len;
        for (l = 0; ptr < end; l++) {
            uint8 b;
            assembly {b := and(mload(ptr), 0xFF)}
            if (b < 0x80) {
                ptr += 1;
            } else if (b < 0xE0) {
                ptr += 2;
            } else if (b < 0xF0) {
                ptr += 3;
            } else if (b < 0xF8) {
                ptr += 4;
            } else if (b < 0xFC) {
                ptr += 5;
            } else {
                ptr += 6;
            }
        }
    }

    /*
     * @dev Returns true if the slice is empty (has a length of 0).
     * @param self The slice to operate on.
     * @return True if the slice is empty, False otherwise.
     */
    function empty(slice memory self) internal pure returns (bool) {
        return self._len == 0;
    }

    /*
     * @dev 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.
     * @param self The first slice to compare.
     * @param other The second slice to compare.
     * @return The result of the comparison.
     */
    function compare(slice memory self, slice memory other) internal pure returns (int) {
        uint shortest = self._len;
        if (other._len < self._len)
            shortest = other._len;

        uint selfptr = self._ptr;
        uint otherptr = other._ptr;
        for (uint idx = 0; idx < shortest; idx += 32) {
            uint a;
            uint b;
            assembly {
                a := mload(selfptr)
                b := mload(otherptr)
            }
            if (a != b) {
                // Mask out irrelevant bytes and check again
                uint mask = type(uint).max;
                // 0xffff...
                if (shortest < 32) {
                    mask = ~(2 ** (8 * (32 - shortest + idx)) - 1);
                }
            unchecked {
                uint diff = (a & mask) - (b & mask);
                if (diff != 0)
                    return int(diff);
            }
            }
            selfptr += 32;
            otherptr += 32;
        }
        return int(self._len) - int(other._len);
    }

    /*
     * @dev Returns true if the two slices contain the same text.
     * @param self The first slice to compare.
     * @param self The second slice to compare.
     * @return True if the slices are equal, false otherwise.
     */
    function equals(slice memory self, slice memory other) internal pure returns (bool) {
        return compare(self, other) == 0;
    }

    /*
     * @dev Extracts the first rune in the slice into `rune`, advancing the
     *      slice to point to the next rune and returning `self`.
     * @param self The slice to operate on.
     * @param rune The slice that will contain the first rune.
     * @return `rune`.
     */
    function nextRune(slice memory self, slice memory rune) internal pure returns (slice memory) {
        rune._ptr = self._ptr;

        if (self._len == 0) {
            rune._len = 0;
            return rune;
        }

        uint l;
        uint b;
        // Load the first byte of the rune into the LSBs of b
        assembly {b := and(mload(sub(mload(add(self, 32)), 31)), 0xFF)}
        if (b < 0x80) {
            l = 1;
        } else if (b < 0xE0) {
            l = 2;
        } else if (b < 0xF0) {
            l = 3;
        } else {
            l = 4;
        }

        // Check for truncated codepoints
        if (l > self._len) {
            rune._len = self._len;
            self._ptr += self._len;
            self._len = 0;
            return rune;
        }

        self._ptr += l;
        self._len -= l;
        rune._len = l;
        return rune;
    }

    /*
     * @dev Returns the first rune in the slice, advancing the slice to point
     *      to the next rune.
     * @param self The slice to operate on.
     * @return A slice containing only the first rune from `self`.
     */
    function nextRune(slice memory self) internal pure returns (slice memory ret) {
        nextRune(self, ret);
    }

    /*
     * @dev Returns the number of the first codepoint in the slice.
     * @param self The slice to operate on.
     * @return The number of the first codepoint in the slice.
     */
    function ord(slice memory self) internal pure returns (uint ret) {
        if (self._len == 0) {
            return 0;
        }

        uint word;
        uint length;
        uint divisor = 2 ** 248;

        // Load the rune into the MSBs of b
        assembly {word := mload(mload(add(self, 32)))}
        uint b = word / divisor;
        if (b < 0x80) {
            ret = b;
            length = 1;
        } else if (b < 0xE0) {
            ret = b & 0x1F;
            length = 2;
        } else if (b < 0xF0) {
            ret = b & 0x0F;
            length = 3;
        } else {
            ret = b & 0x07;
            length = 4;
        }

        // Check for truncated codepoints
        if (length > self._len) {
            return 0;
        }

        for (uint i = 1; i < length; i++) {
            divisor = divisor / 256;
            b = (word / divisor) & 0xFF;
            if (b & 0xC0 != 0x80) {
                // Invalid UTF-8 sequence
                return 0;
            }
            ret = (ret * 64) | (b & 0x3F);
        }

        return ret;
    }

    /*
     * @dev Returns the keccak-256 hash of the slice.
     * @param self The slice to hash.
     * @return The hash of the slice.
     */
    function keccak(slice memory self) internal pure returns (bytes32 ret) {
        assembly {
            ret := keccak256(mload(add(self, 32)), mload(self))
        }
    }

    /*
     * @dev Returns true if `self` starts with `needle`.
     * @param self The slice to operate on.
     * @param needle The slice to search for.
     * @return True if the slice starts with the provided text, false otherwise.
     */
    function startsWith(slice memory self, slice memory needle) internal pure returns (bool) {
        if (self._len < needle._len) {
            return false;
        }

        if (self._ptr == needle._ptr) {
            return true;
        }

        bool equal;
        assembly {
            let length := mload(needle)
            let selfptr := mload(add(self, 0x20))
            let needleptr := mload(add(needle, 0x20))
            equal := eq(keccak256(selfptr, length), keccak256(needleptr, length))
        }
        return equal;
    }

    /*
     * @dev If `self` starts with `needle`, `needle` is removed from the
     *      beginning of `self`. Otherwise, `self` is unmodified.
     * @param self The slice to operate on.
     * @param needle The slice to search for.
     * @return `self`
     */
    function beyond(slice memory self, slice memory needle) internal pure returns (slice memory) {
        if (self._len < needle._len) {
            return self;
        }

        bool equal = true;
        if (self._ptr != needle._ptr) {
            assembly {
                let length := mload(needle)
                let selfptr := mload(add(self, 0x20))
                let needleptr := mload(add(needle, 0x20))
                equal := eq(keccak256(selfptr, length), keccak256(needleptr, length))
            }
        }

        if (equal) {
            self._len -= needle._len;
            self._ptr += needle._len;
        }

        return self;
    }

    /*
     * @dev Returns true if the slice ends with `needle`.
     * @param self The slice to operate on.
     * @param needle The slice to search for.
     * @return True if the slice starts with the provided text, false otherwise.
     */
    function endsWith(slice memory self, slice memory needle) internal pure returns (bool) {
        if (self._len < needle._len) {
            return false;
        }

        uint selfptr = self._ptr + self._len - needle._len;

        if (selfptr == needle._ptr) {
            return true;
        }

        bool equal;
        assembly {
            let length := mload(needle)
            let needleptr := mload(add(needle, 0x20))
            equal := eq(keccak256(selfptr, length), keccak256(needleptr, length))
        }

        return equal;
    }

    /*
     * @dev If `self` ends with `needle`, `needle` is removed from the
     *      end of `self`. Otherwise, `self` is unmodified.
     * @param self The slice to operate on.
     * @param needle The slice to search for.
     * @return `self`
     */
    function until(slice memory self, slice memory needle) internal pure returns (slice memory) {
        if (self._len < needle._len) {
            return self;
        }

        uint selfptr = self._ptr + self._len - needle._len;
        bool equal = true;
        if (selfptr != needle._ptr) {
            assembly {
                let length := mload(needle)
                let needleptr := mload(add(needle, 0x20))
                equal := eq(keccak256(selfptr, length), keccak256(needleptr, length))
            }
        }

        if (equal) {
            self._len -= needle._len;
        }

        return self;
    }

    // Returns the memory address of the first byte of the first occurrence of
    // `needle` in `self`, or the first byte after `self` if not found.
    function findPtr(uint selflen, uint selfptr, uint needlelen, uint needleptr) private pure returns (uint) {
        uint ptr = selfptr;
        uint idx;

        if (needlelen <= selflen) {
            if (needlelen <= 32) {
                bytes32 mask;
                if (needlelen > 0) {
                    mask = bytes32(~(2 ** (8 * (32 - needlelen)) - 1));
                }

                bytes32 needledata;
                assembly {needledata := and(mload(needleptr), mask)}

                uint end = selfptr + selflen - needlelen;
                bytes32 ptrdata;
                assembly {ptrdata := and(mload(ptr), mask)}

                while (ptrdata != needledata) {
                    if (ptr >= end)
                        return selfptr + selflen;
                    ptr++;
                    assembly {ptrdata := and(mload(ptr), mask)}
                }
                return ptr;
            } else {
                // For long needles, use hashing
                bytes32 hash;
                assembly {hash := keccak256(needleptr, needlelen)}

                for (idx = 0; idx <= selflen - needlelen; idx++) {
                    bytes32 testHash;
                    assembly {testHash := keccak256(ptr, needlelen)}
                    if (hash == testHash)
                        return ptr;
                    ptr += 1;
                }
            }
        }
        return selfptr + selflen;
    }

    // Returns the memory address of the first byte after the last occurrence of
    // `needle` in `self`, or the address of `self` if not found.
    function rfindPtr(uint selflen, uint selfptr, uint needlelen, uint needleptr) private pure returns (uint) {
        uint ptr;

        if (needlelen <= selflen) {
            if (needlelen <= 32) {
                bytes32 mask;
                if (needlelen > 0) {
                    mask = bytes32(~(2 ** (8 * (32 - needlelen)) - 1));
                }

                bytes32 needledata;
                assembly {needledata := and(mload(needleptr), mask)}

                ptr = selfptr + selflen - needlelen;
                bytes32 ptrdata;
                assembly {ptrdata := and(mload(ptr), mask)}

                while (ptrdata != needledata) {
                    if (ptr <= selfptr)
                        return selfptr;
                    ptr--;
                    assembly {ptrdata := and(mload(ptr), mask)}
                }
                return ptr + needlelen;
            } else {
                // For long needles, use hashing
                bytes32 hash;
                assembly {hash := keccak256(needleptr, needlelen)}
                ptr = selfptr + (selflen - needlelen);
                while (ptr >= selfptr) {
                    bytes32 testHash;
                    assembly {testHash := keccak256(ptr, needlelen)}
                    if (hash == testHash)
                        return ptr + needlelen;
                    ptr -= 1;
                }
            }
        }
        return selfptr;
    }

    /*
     * @dev 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.
     * @param self The slice to search and modify.
     * @param needle The text to search for.
     * @return `self`.
     */
    function find(slice memory self, slice memory needle) internal pure returns (slice memory) {
        uint ptr = findPtr(self._len, self._ptr, needle._len, needle._ptr);
        self._len -= ptr - self._ptr;
        self._ptr = ptr;
        return self;
    }

    /*
     * @dev 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.
     * @param self The slice to search and modify.
     * @param needle The text to search for.
     * @return `self`.
     */
    function rfind(slice memory self, slice memory needle) internal pure returns (slice memory) {
        uint ptr = rfindPtr(self._len, self._ptr, needle._len, needle._ptr);
        self._len = ptr - self._ptr;
        return self;
    }

    /*
     * @dev 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`.
     * @param self The slice to split.
     * @param needle The text to search for in `self`.
     * @param token An output parameter to which the first token is written.
     * @return `token`.
     */
    function split(slice memory self, slice memory needle, slice memory token) internal pure returns (slice memory) {
        uint ptr = findPtr(self._len, self._ptr, needle._len, needle._ptr);
        token._ptr = self._ptr;
        token._len = ptr - self._ptr;
        if (ptr == self._ptr + self._len) {
            // Not found
            self._len = 0;
        } else {
            self._len -= token._len + needle._len;
            self._ptr = ptr + needle._len;
        }
        return token;
    }

    /*
     * @dev 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.
     * @param self The slice to split.
     * @param needle The text to search for in `self`.
     * @return The part of `self` up to the first occurrence of `delim`.
     */
    function split(slice memory self, slice memory needle) internal pure returns (slice memory token) {
        split(self, needle, token);
    }

    /*
     * @dev 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`.
     * @param self The slice to split.
     * @param needle The text to search for in `self`.
     * @param token An output parameter to which the first token is written.
     * @return `token`.
     */
    function rsplit(slice memory self, slice memory needle, slice memory token) internal pure returns (slice memory) {
        uint ptr = rfindPtr(self._len, self._ptr, needle._len, needle._ptr);
        token._ptr = ptr;
        token._len = self._len - (ptr - self._ptr);
        if (ptr == self._ptr) {
            // Not found
            self._len = 0;
        } else {
            self._len -= token._len + needle._len;
        }
        return token;
    }

    /*
     * @dev 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.
     * @param self The slice to split.
     * @param needle The text to search for in `self`.
     * @return The part of `self` after the last occurrence of `delim`.
     */
    function rsplit(slice memory self, slice memory needle) internal pure returns (slice memory token) {
        rsplit(self, needle, token);
    }

    /*
     * @dev Counts the number of nonoverlapping occurrences of `needle` in `self`.
     * @param self The slice to search.
     * @param needle The text to search for in `self`.
     * @return The number of occurrences of `needle` found in `self`.
     */
    function count(slice memory self, slice memory needle) internal pure returns (uint cnt) {
        uint ptr = findPtr(self._len, self._ptr, needle._len, needle._ptr) + needle._len;
        while (ptr <= self._ptr + self._len) {
            cnt++;
            ptr = findPtr(self._len - (ptr - self._ptr), ptr, needle._len, needle._ptr) + needle._len;
        }
    }

    /*
     * @dev Returns True if `self` contains `needle`.
     * @param self The slice to search.
     * @param needle The text to search for in `self`.
     * @return True if `needle` is found in `self`, false otherwise.
     */
    function contains(slice memory self, slice memory needle) internal pure returns (bool) {
        return rfindPtr(self._len, self._ptr, needle._len, needle._ptr) != self._ptr;
    }

    /*
     * @dev Returns a newly allocated string containing the concatenation of
     *      `self` and `other`.
     * @param self The first slice to concatenate.
     * @param other The second slice to concatenate.
     * @return The concatenation of the two strings.
     */
    function concat(slice memory self, slice memory other) internal pure returns (string memory) {
        string memory ret = new string(self._len + other._len);
        uint retptr;
        assembly {retptr := add(ret, 32)}
        memcpy(retptr, self._ptr, self._len);
        memcpy(retptr + self._len, other._ptr, other._len);
        return ret;
    }

    /*
     * @dev Joins an array of slices, using `self` as a delimiter, returning a
     *      newly allocated string.
     * @param self The delimiter to use.
     * @param parts A list of slices to join.
     * @return A newly allocated string containing all the slices in `parts`,
     *         joined with `self`.
     */
    function join(slice memory self, slice[] memory parts) internal pure returns (string memory) {
        if (parts.length == 0)
            return "";

        uint length = self._len * (parts.length - 1);
        for (uint i = 0; i < parts.length; i++)
            length += parts[i]._len;

        string memory ret = new string(length);
        uint retptr;
        assembly {retptr := add(ret, 32)}

        for (uint i = 0; i < parts.length; i++) {
            memcpy(retptr, parts[i]._ptr, parts[i]._len);
            retptr += parts[i]._len;
            if (i < parts.length - 1) {
                memcpy(retptr, self._ptr, self._len);
                retptr += self._len;
            }
        }

        return ret;
    }
}

Question: Importing the Library

I try to import it like in the Readme

import "github.com/Arachnid/solidity-stringutils/strings.sol";

contract Mortal {
   ...

But when I try to build it with truffle I get this:

Cannot find import strings.sol from Test.sol. If it's a relative path, ensure it starts with `./` or `../`.
Compilation failed. See above.

How to do import it?

I read something here, that the compiler reads during dev from /usr/local/dapp-bin/library
http://ethereum.stackexchange.com/questions/2137/how-should-we-set-a-import-path-in-solidity
So do I need to download the repo into that folder?

importing issue

Hi and tanx to your grate library
i have created a file(string.sol) and copy paste the contents into it
the file is in the main solidity directory
i am importing via import "./strings.sol";
but i get an error for this line using strings for *;
error : AttributeError: 'UsingForDirective' object has no attribute 'typeName'
i am using brownie

Error: Library "strings" declared twice

while I imported the library and compiled, it return warning

strings.sol:37:1: Warning: Source file does not specify required compiler version!Consider adding "pragma solidity ^0.4.7
library strings {

then, added the "pragma solidity ^0.4.7" and compiled again, it returns error

[root@localhost contracts]# truffle compile
Compiling ./contracts/Ballot.sol...
Compiling ./contracts/strings.sol...
Compiling strings.sol...
Error compiling:

/home/xuhuan/contract/c2/contracts/strings.sol:39:1: Error: Library "strings" declared twice (will create ambiguities during linking).
library strings {
^
Spanning multiple lines.
The other declaration is here: strings.sol:39:1: 
library strings {
^
Spanning multiple lines.

Unwanted empty string in split function

Hi
I'm trying to split a string like this: hellow-world-how-are-you-doing with the code below.

var s = c.toSlice();
var delim = "-".toSlice();
var parts = new string[](s.count(delim) + 1);
string[] memory arr = new string[](parts.length);
for (uint i = 0; i < parts.length; i++) {
      arr[i] = (s.split(delim).toString());
}

But when running a test with truffle there is always an unwanted extra empty string in the result array. This is weird but it's working just fine in remix-ide.

Unable to import "undefined": Not Found

when I compile the smart contract it throws this error : Unable to import "undefined": Not Found*

this is my smart contract:

pragma solidity ^0.4.0;
import "github.com/Arachnid/solidity-stringutils/strings.sol";
contract Ballot {

       using strings for *;
       
       
       function splitString (string result) public constant returns (string){
           var s = "www.google.com".toSlice();
           var delim = ".".toSlice();
           var parts = new string[](s.count(delim) + 1);
           for(uint i = 0; i < parts.length; i++) {
              parts[i] = s.split(delim).toString();
            }
            
            return parts[0];

       }
}

Feature request: substring with index

Thank you very much for this helpful library. It would be great if there was a way to substring with indexes like this method in Java
public String substring(int startIndex, int endIndex)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.