Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions include/chaiscript/chaiscript_stdlib.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include <map>
#include <memory>
#include <string>
#include <string_view>
#include <utility>
#include <vector>

Expand Down Expand Up @@ -52,9 +53,14 @@ namespace chaiscript {

bootstrap::standard_library::vector_type<std::vector<Boxed_Value>>("Vector", *lib);
bootstrap::standard_library::string_type<std::string>("string", *lib);
bootstrap::standard_library::string_view_type<std::string_view, std::string>("string_view", *lib);
bootstrap::standard_library::map_type<std::map<std::string, Boxed_Value>>("Map", *lib);
bootstrap::standard_library::pair_type<std::pair<Boxed_Value, Boxed_Value>>("Pair", *lib);

// Allow explicit conversion from std::string_view back to std::string,
// e.g. `string(sv)` in ChaiScript.
lib->add(fun([](const std::string_view sv) { return std::string{sv}; }), "string");

#ifndef CHAISCRIPT_NO_THREADS
bootstrap::standard_library::future_type<std::future<chaiscript::Boxed_Value>>("future", *lib);
// Note: async() is registered in ChaiScript_Basic::build_eval_system()
Expand Down
55 changes: 55 additions & 0 deletions include/chaiscript/dispatchkit/bootstrap_stl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -539,6 +539,61 @@ namespace chaiscript::bootstrap::standard_library {
m.add(fun([](const String *s, size_t pos, size_t len) { return s->substr(pos, len); }), "substr");
}

/// Add a String_View type (e.g. std::string_view), with conversions to and from
/// the matching owning String type (e.g. std::string).
///
/// Mirrors the search/substring surface of string_type so that scripts can
/// traverse a buffer through StringView without allocating. Sharing method
/// names with the owning String type is safe: dispatch deprioritizes any
/// candidate whose first/receiver argument requires a type conversion (see
/// dispatch() in proxy_functions.hpp), so myString.substr(1, 2) still
/// resolves to String::substr while mySV.substr(1, 2) resolves to
/// StringView::substr (returning a StringView).
///
/// \note A String_View is a non-owning reference. Constructing one from a
/// temporary owning String yields a dangling reference once that temporary
/// is destroyed; the same lifetime caveats as in C++ apply here.
template<typename StringView, typename String>
void string_view_type(const std::string &type, Module &m) {
m.add(user_type<StringView>(), type);
m.add(constructor<StringView()>(), type);
m.add(constructor<StringView(const StringView &)>(), type);
m.add(fun([](const String &s) { return StringView{s}; }), type);

opers_comparison<StringView>(m);

m.add(fun([](const StringView *s) { return s->size(); }), "size");
m.add(fun([](const StringView *s) { return s->length(); }), "length");
m.add(fun([](const StringView *s) { return s->empty(); }), "empty");
m.add(fun([](const StringView *s) { return s->data(); }), "data");

// Random-access via [] / at, returning const_reference; StringView is read-only.
m.add(fun([](const StringView &sv, int index) -> typename StringView::const_reference {
return sv.at(static_cast<typename StringView::size_type>(index));
}),
"[]");

m.add(fun([](const StringView *s, const StringView &f, size_t pos) { return s->find(f, pos); }), "find");
m.add(fun([](const StringView *s, const StringView &f, size_t pos) { return s->rfind(f, pos); }), "rfind");
m.add(fun([](const StringView *s, const StringView &f, size_t pos) { return s->find_first_of(f, pos); }), "find_first_of");
m.add(fun([](const StringView *s, const StringView &f, size_t pos) { return s->find_last_of(f, pos); }), "find_last_of");
m.add(fun([](const StringView *s, const StringView &f, size_t pos) { return s->find_last_not_of(f, pos); }), "find_last_not_of");
m.add(fun([](const StringView *s, const StringView &f, size_t pos) { return s->find_first_not_of(f, pos); }), "find_first_not_of");

m.add(fun([](const StringView *s, const StringView &f) { return s->starts_with(f); }), "starts_with");
m.add(fun([](const StringView *s, const StringView &f) { return s->ends_with(f); }), "ends_with");

m.add(fun([](const StringView *s, size_t pos, size_t len) { return s->substr(pos, len); }), "substr");

// Built-in implicit conversion from owning String to non-owning StringView.
m.add(type_conversion<const String &, StringView>());

// Explicit conversion from StringView back to owning String, registered as
// to_string(sv); the call site can also register it under the owning type's
// name (e.g. string(sv)) when desired.
m.add(fun([](const StringView sv) { return String{sv}; }), "to_string");
}

/// Add a MapType container
/// http://www.sgi.com/tech/stl/Map.html
template<typename FutureType>
Expand Down
17 changes: 15 additions & 2 deletions include/chaiscript/dispatchkit/proxy_functions.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -737,11 +737,24 @@ namespace chaiscript {
if (matching_func == end) {
matching_func = begin;
} else {
// handle const members vs non-const member, which is not really ambiguous
const auto &mat_fun_param_types = matching_func->second->get_param_types();
const auto &next_fun_param_types = begin->second->get_param_types();

if (plist[0].is_const() && !mat_fun_param_types[1].is_const() && next_fun_param_types[1].is_const()) {
// Prefer the candidate whose first parameter (receiver) matches the
// actual receiver type exactly over one that needs a type conversion.
// Conversions on the receiver create temporaries, so any mutation
// would be silently lost; this mirrors the deprioritization in
// dispatch() and resolves cases like myString.substr(int, int) when
// both string::substr and string_view::substr are registered.
const bool plist_empty = plist.empty();
const bool mat_receiver_exact = !plist_empty && mat_fun_param_types[1].bare_equal(plist[0].get_type_info());
const bool next_receiver_exact = !plist_empty && next_fun_param_types[1].bare_equal(plist[0].get_type_info());

if (mat_receiver_exact && !next_receiver_exact) {
// keep the old one, it has the better receiver match
} else if (!mat_receiver_exact && next_receiver_exact) {
matching_func = begin; // keep the new one, it has the better receiver match
} else if (plist[0].is_const() && !mat_fun_param_types[1].is_const() && next_fun_param_types[1].is_const()) {
matching_func = begin; // keep the new one, the const/non-const matchup is correct
} else if (!plist[0].is_const() && !mat_fun_param_types[1].is_const() && next_fun_param_types[1].is_const()) {
// keep the old one, it has a better const/non-const matchup
Expand Down
70 changes: 70 additions & 0 deletions unittests/compiled_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2204,3 +2204,73 @@ TEST_CASE("Exception from C++ [] operator is catchable in ChaiScript") {
caught
)") == true);
}

// Issue #458: ChaiScript strings should be passable to C++ functions that
// take std::string_view, std::string_view should be a known type, and
// explicit conversion from std::string_view to std::string should work.
TEST_CASE("Issue #458: std::string_view interop with ChaiScript strings") {
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());

chai.add(chaiscript::fun([](const std::string_view sv) { return std::string(sv); }), "consume_string_view");

CHECK(chai.eval<std::string>(R"(consume_string_view("Hi there"))") == "Hi there");
CHECK(chai.eval<std::string>(R"(var s = "from variable"; consume_string_view(s))") == "from variable");

CHECK(chai.eval<bool>(R"(type_name(string_view("hello")) == "string_view")"));

CHECK(chai.eval<std::string>(R"(string(string_view("round trip")))") == "round trip");
}

// Issue #458 follow-up: string_view should expose the same search/substring
// surface as string, and overload dispatch must keep myString.substr(...) on
// String::substr while mySV.substr(...) goes to StringView::substr.
TEST_CASE("Issue #458: string_view supports string-like search and substring") {
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());

// Receiver-type wins: string keeps String::substr, string_view picks StringView::substr.
CHECK(chai.eval<bool>(R"(
fun(){
var s = "hello world";
var sv = string_view(s);
type_name(sv.substr(6, 5)) == "string_view" && type_name(s.substr(6, 5)) == "string"
}()
)"));

// Round-trip: substr through string_view yields the expected substring.
CHECK(chai.eval<std::string>(R"(
fun(){
var s = "hello world";
var sv = string_view(s);
to_string(sv.substr(6, 5))
}()
)") == "world");

// starts_with / ends_with on string_view.
CHECK(chai.eval<bool>(R"(
fun(){
var s = "hello world";
var sv = string_view(s);
sv.starts_with(string_view("hello")) && sv.ends_with(string_view("world"))
}()
)"));

// find / rfind / find_first_of return the expected positions.
CHECK(chai.eval<bool>(R"(
fun(){
var s = "hello world";
var sv = string_view(s);
sv.find(string_view("world"), 0) == 6 &&
sv.rfind(string_view("o"), sv.size()) == 7 &&
sv.find_first_of(string_view("aeiou"), 0) == 1
}()
)"));

// [] indexed access reads the character at the given position.
CHECK(chai.eval<bool>(R"(
fun(){
var s = "hello";
var sv = string_view(s);
sv[1] == 'e'
}()
)"));
}
5 changes: 3 additions & 2 deletions unittests/function_introspection.chai
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,15 @@ assert_equal(true, test_fun_types[1].bare_equal(int_type));

assert_equal(2, `==`.get_arity());

// < should be the merging of two functions bool <(PODObject, PODObject) and bool <(string, string)
// < should be the merging of three functions bool <(PODObject, PODObject),
// bool <(string, string), and bool <(string_view, string_view)
// we want to peel it apart and make sure that's true
auto types = `<`.get_param_types();
assert_equal(3, types.size());
assert_equal(true, types[0].bare_equal(bool_type));
assert_equal(true, types[1].bare_equal(Object_type));
assert_equal(true, types[2].bare_equal(Object_type));
assert_equal(2, `<`.get_contained_functions().size());
assert_equal(3, `<`.get_contained_functions().size());


// guard existence tests
Expand Down
Loading