mirror of
https://github.com/ChaiScript/ChaiScript.git
synced 2026-06-15 00:16:17 +08:00
* Fix #458: Support std::string_view interop with ChaiScript strings Register std::string_view as the "string_view" user type with a built-in implicit conversion from std::string, so a ChaiScript string can be passed directly to a C++ function taking std::string_view. Adds the reverse explicit conversion via string(sv) / to_string(sv), plus basic queries (size, length, empty, data) and comparison operators on string_view. String-style methods that take size_t (substr, find, ...) are intentionally not duplicated on string_view: with the implicit conversion in place they would create dispatch ambiguity for calls like string.substr(int, int). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Address review: expand string_view binding with substr/find/[] Adds substr, find/rfind/find_first_of/find_last_of/find_first_not_of/ find_last_not_of, starts_with/ends_with, and operator[] to string_view_type, mirroring string_type's search/substring surface so script authors can traverse a buffer through string_view without allocating. Sharing method names with string_type creates an ambiguity in dispatch_with_conversions when the script call needs arithmetic conversion (e.g. myString.substr(int, int)): both string::substr and string_view::substr match-except-for-arithmetic, and the existing const/non-const tiebreaker doesn't apply. Extend dispatch_with_conversions to prefer the candidate whose first/receiver parameter type exactly matches the actual receiver, mirroring the deprioritization already in dispatch(). With this in place, myString.substr(1, 2) still resolves to string::substr while mySV.substr(1, 2) resolves to string_view::substr (returning a string_view). Updates the doc comment on string_view_type and adds a compiled test that locks in both dispatch directions plus the new search/substring methods. Requested by @lefticus in PR #697 review. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: leftibot <leftibot@users.noreply.github.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
d60f8fed73
commit
46cf4cdef0
@ -10,6 +10,7 @@
|
|||||||
#include <map>
|
#include <map>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@ -52,9 +53,14 @@ namespace chaiscript {
|
|||||||
|
|
||||||
bootstrap::standard_library::vector_type<std::vector<Boxed_Value>>("Vector", *lib);
|
bootstrap::standard_library::vector_type<std::vector<Boxed_Value>>("Vector", *lib);
|
||||||
bootstrap::standard_library::string_type<std::string>("string", *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::map_type<std::map<std::string, Boxed_Value>>("Map", *lib);
|
||||||
bootstrap::standard_library::pair_type<std::pair<Boxed_Value, Boxed_Value>>("Pair", *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
|
#ifndef CHAISCRIPT_NO_THREADS
|
||||||
bootstrap::standard_library::future_type<std::future<chaiscript::Boxed_Value>>("future", *lib);
|
bootstrap::standard_library::future_type<std::future<chaiscript::Boxed_Value>>("future", *lib);
|
||||||
// Note: async() is registered in ChaiScript_Basic::build_eval_system()
|
// Note: async() is registered in ChaiScript_Basic::build_eval_system()
|
||||||
|
|||||||
@ -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");
|
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
|
/// Add a MapType container
|
||||||
/// http://www.sgi.com/tech/stl/Map.html
|
/// http://www.sgi.com/tech/stl/Map.html
|
||||||
template<typename FutureType>
|
template<typename FutureType>
|
||||||
|
|||||||
@ -737,11 +737,24 @@ namespace chaiscript {
|
|||||||
if (matching_func == end) {
|
if (matching_func == end) {
|
||||||
matching_func = begin;
|
matching_func = begin;
|
||||||
} else {
|
} 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 &mat_fun_param_types = matching_func->second->get_param_types();
|
||||||
const auto &next_fun_param_types = begin->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
|
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()) {
|
} 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
|
// keep the old one, it has a better const/non-const matchup
|
||||||
|
|||||||
@ -2299,3 +2299,73 @@ TEST_CASE("Exception from C++ [] operator is catchable in ChaiScript") {
|
|||||||
caught
|
caught
|
||||||
)") == true);
|
)") == 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'
|
||||||
|
}()
|
||||||
|
)"));
|
||||||
|
}
|
||||||
|
|||||||
@ -39,14 +39,15 @@ assert_equal(true, test_fun_types[1].bare_equal(int_type));
|
|||||||
|
|
||||||
assert_equal(2, `==`.get_arity());
|
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
|
// we want to peel it apart and make sure that's true
|
||||||
auto types = `<`.get_param_types();
|
auto types = `<`.get_param_types();
|
||||||
assert_equal(3, types.size());
|
assert_equal(3, types.size());
|
||||||
assert_equal(true, types[0].bare_equal(bool_type));
|
assert_equal(true, types[0].bare_equal(bool_type));
|
||||||
assert_equal(true, types[1].bare_equal(Object_type));
|
assert_equal(true, types[1].bare_equal(Object_type));
|
||||||
assert_equal(true, types[2].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
|
// guard existence tests
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user