Skip to content

Commit

Permalink
Add std::string_view support. See #196
Browse files Browse the repository at this point in the history
  • Loading branch information
cfis committed Feb 25, 2024
1 parent f6baa4b commit 97886d1
Show file tree
Hide file tree
Showing 10 changed files with 198 additions and 1 deletion.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ Gemfile.lock
*.bundle
README.doxygen
doc/_build

.vs/
cmake*
pkg
*.log
.*.swp
Expand Down
1 change: 1 addition & 0 deletions doc/stl.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ STL
stl/reference_wrapper
stl/smart_pointers
stl/string
stl/string_view
stl/unordered_map
stl/variant
stl/vector
9 changes: 9 additions & 0 deletions doc/stl/string_view.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
.. _std_string_view:

std::string_view
-----------------
``std::string_view`` is a read-only reference to a sequence of `char`s. It provides a way of passing strings without the overhead of copying `std::string`.

On input, Rice treats ``std::string_view`` as a Builtin type which means it copies the portion of the ``char`` sequence that a ``std::string_view`` references in C++ to Ruby. Please refer to the :ref:`_std_string_view` documentation to learn about how Rice handles encodings.my-refere

On output, Rice creates a ``std::string_view`` that references a Ruby string's underlying `char` buffer. Note this is DANGEROUS. Sooner or later the Ruby string will be garbage collected or moved as part of compaction, thus invalidating the `char` buffer. So use this with caution.
3 changes: 3 additions & 0 deletions rice/cpp_api/String.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ namespace Rice
//! Construct a String from an std::string.
String(std::string const& s);

//! Construct a String from an std::string_view.
String(std::string_view const& s);

//! Format a string using printf-style formatting.
template <typename... Arg_Ts>
static inline String format(char const* fmt, Arg_Ts&&...args);
Expand Down
4 changes: 4 additions & 0 deletions rice/cpp_api/String.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ namespace Rice
{
}

inline String::String(std::string_view const& s) : Builtin_Object<T_STRING>(detail::protect(rb_str_new, s.data(), (long)s.length()))
{
}

inline String::String(Identifier id) : Builtin_Object<T_STRING>(detail::protect(rb_str_new2, id.c_str()))
{
}
Expand Down
1 change: 1 addition & 0 deletions rice/stl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#define Rice__stl__hpp_

#include "stl/string.hpp"
#include "stl/string_view.hpp"
#include "stl/complex.hpp"
#include "stl/optional.hpp"
#include "stl/reference_wrapper.hpp"
Expand Down
6 changes: 6 additions & 0 deletions rice/stl/string_view.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#ifndef Rice__stl__string_view__hpp_
#define Rice__stl__string_view__hpp_

#include "string_view.ipp"

#endif // Rice__stl__string_view__hpp_
69 changes: 69 additions & 0 deletions rice/stl/string_view.ipp
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#include <string_view>
#include "../detail/ruby.hpp"
#include "../detail/Type.hpp"
#include "../detail/from_ruby.hpp"
#include "../detail/to_ruby.hpp"

namespace Rice::detail
{
template<>
struct Type<std::string_view>
{
static bool verify()
{
return true;
}
};

template<>
class To_Ruby<std::string_view>
{
public:
VALUE convert(std::string_view const& x)
{
return detail::protect(rb_external_str_new, x.data(), (long)x.size());
}
};

template<>
class To_Ruby<std::string_view&>
{
public:
VALUE convert(std::string_view const& x)
{
return detail::protect(rb_external_str_new, x.data(), (long)x.size());
}
};

template<>
class From_Ruby<std::string_view>
{
public:
From_Ruby() = default;

explicit From_Ruby(Arg* arg) : arg_(arg)
{
}

bool is_convertible(VALUE value)
{
return rb_type(value) == RUBY_T_STRING;
}

std::string_view convert(VALUE value)
{
if (value == Qnil && this->arg_ && this->arg_->hasDefaultValue())
{
return this->arg_->defaultValue<std::string_view>();
}
else
{
detail::protect(rb_check_type, value, (int)T_STRING);
return std::string_view(RSTRING_PTR(value), RSTRING_LEN(value));
}
}

private:
Arg* arg_ = nullptr;
};
}
88 changes: 88 additions & 0 deletions test/test_Stl_String_View.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
#include "unittest.hpp"
#include "embed_ruby.hpp"
#include <rice/rice.hpp>
#include <ruby/encoding.h>
#include <rice/stl.hpp>

#include <optional>

using namespace Rice;

TESTSUITE(StlStringView);

SETUP(StlStringView)
{
embed_ruby();
}

TESTCASE(std_string_view_to_ruby)
{
ASSERT(rb_equal(String("").value(), detail::to_ruby(std::string_view(""))));
ASSERT(rb_equal(String("foo").value(), detail::to_ruby(std::string_view("foo"))));
}

TESTCASE(std_string_view_to_ruby_encoding)
{
VALUE value = detail::to_ruby(std::string_view("Some String"));
String string(value);
Object encoding = string.call("encoding");
String encodingName = encoding.call("name");
std::string_view result = detail::From_Ruby<std::string_view>().convert(encodingName);
if(result != "ASCII-8BIT" && result != "US-ASCII" && result != "UTF-8") {
FAIL("Encoding incorrect", "ASCII-8BIT, US-ASCII, or UTF-8 (Windows)", result);
}
}

TESTCASE(std_string_view_to_ruby_encoding_utf8)
{
rb_encoding* defaultEncoding = rb_default_external_encoding();

VALUE utf8Encoding = rb_enc_from_encoding(rb_utf8_encoding());
rb_enc_set_default_external(utf8Encoding);

VALUE value = detail::to_ruby(std::string_view("Some String"));
Object object(value);
Object encoding = object.call("encoding");
Object encodingName = encoding.call("name");
ASSERT_EQUAL("UTF-8", detail::From_Ruby<std::string_view>().convert(encodingName));

rb_enc_set_default_external(rb_enc_from_encoding(defaultEncoding));
}

TESTCASE(std_string_view_from_ruby)
{
ASSERT_EQUAL(std::string_view(""), detail::From_Ruby<std::string_view>().convert(rb_str_new2("")));
ASSERT_EQUAL(std::string_view("foo"), detail::From_Ruby<std::string_view>().convert(rb_str_new2("foo")));

ASSERT_EXCEPTION_CHECK(
Exception,
detail::From_Ruby<std::string_view>().convert(rb_float_new(15.512)),
ASSERT_EQUAL("wrong argument type Float (expected String)", ex.what())
);
}

TESTCASE(std_string_view_to_ruby_with_binary)
{
Rice::String got = detail::to_ruby(std::string_view("\000test", 5));

ASSERT_EQUAL(String(std::string_view("\000test", 5)), got);
ASSERT_EQUAL(5ul, got.length());
}

TESTCASE(std_string_view_from_ruby_with_binary)
{
std::string_view got = detail::From_Ruby<std::string_view>().convert(rb_str_new("\000test", 5));
ASSERT_EQUAL(5ul, got.length());
ASSERT_EQUAL(std::string_view("\000test", 5), got);
}

TESTCASE(std_string_view_from_ruby_refefence)
{
VALUE value = rb_str_new("test", 4);
std::string_view view = detail::From_Ruby<std::string_view>().convert(value);
ASSERT_EQUAL("test", view);

String string(value);
string.instance_eval("self[1] = 'a'");
ASSERT_EQUAL("tast", view);
}
15 changes: 15 additions & 0 deletions test/test_String.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,21 @@ TESTCASE(construct_from_std_string)
ASSERT_EQUAL("foo", RSTRING_PTR(s.value()));
}

TESTCASE(construct_from_std_string_view)
{
std::string_view foo("foo");
String s(foo);
ASSERT_EQUAL(T_STRING, rb_type(s));
ASSERT_EQUAL("foo", RSTRING_PTR(s.value()));

{
foo = "foo 2";
s = foo;
}
ASSERT_EQUAL(T_STRING, rb_type(s));
ASSERT_EQUAL("foo 2", RSTRING_PTR(s.value()));
}

TESTCASE(format)
{
String s(String::format("%s %d", "foo", 42));
Expand Down

0 comments on commit 97886d1

Please sign in to comment.