mirror of
https://github.com/ChaiScript/ChaiScript.git
synced 2026-04-30 19:09:26 +08:00
Fix #206: Make internal string type a template parameter
Add a StringType template parameter (defaulting to std::string) to ChaiScript_Parser, Bootstrap::bootstrap(), and Std_Lib::library(), flowing through to the ChaiScript_Impl convenience class. This allows users to instantiate ChaiScript with std::wstring (via ChaiScript_WString) or other string types. String literals, escape sequences (including unicode for wide chars via if constexpr), to_string conversions, and string operations all respect the parameterized type. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
d4c5bdb3e4
commit
f1e0fb35da
@ -444,6 +444,10 @@ if(BUILD_TESTING)
|
||||
target_link_libraries(threading_config_test ${LIBS})
|
||||
add_test(NAME Threading_Config_Test COMMAND threading_config_test)
|
||||
|
||||
add_executable(string_type_param_test unittests/string_type_param_test.cpp)
|
||||
target_link_libraries(string_type_param_test ${LIBS})
|
||||
add_test(NAME String_Type_Param_Test COMMAND string_type_param_test)
|
||||
|
||||
install(TARGETS test_module RUNTIME DESTINATION bin LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}/chaiscript")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
@ -820,20 +820,24 @@
|
||||
#include "language/chaiscript_parser.hpp"
|
||||
|
||||
namespace chaiscript {
|
||||
class ChaiScript : public ChaiScript_Basic {
|
||||
template<typename StringType = std::string>
|
||||
class ChaiScript_Impl : public ChaiScript_Basic {
|
||||
public:
|
||||
ChaiScript(std::vector<std::string> t_modulepaths = {},
|
||||
ChaiScript_Impl(std::vector<std::string> t_modulepaths = {},
|
||||
std::vector<std::string> t_usepaths = {},
|
||||
std::vector<Options> t_opts = chaiscript::default_options(),
|
||||
std::vector<Library_Options> t_lib_opts = {})
|
||||
: ChaiScript_Basic(chaiscript::Std_Lib::library(t_lib_opts),
|
||||
std::make_unique<parser::ChaiScript_Parser<eval::Noop_Tracer, optimizer::Optimizer_Default>>(),
|
||||
: ChaiScript_Basic(chaiscript::Std_Lib::library<StringType>(t_lib_opts),
|
||||
std::make_unique<parser::ChaiScript_Parser<eval::Noop_Tracer, optimizer::Optimizer_Default, StringType>>(),
|
||||
std::move(t_modulepaths),
|
||||
std::move(t_usepaths),
|
||||
std::move(t_opts),
|
||||
std::find(t_lib_opts.begin(), t_lib_opts.end(), Library_Options::No_IO) != t_lib_opts.end()) {
|
||||
}
|
||||
};
|
||||
|
||||
using ChaiScript = ChaiScript_Impl<std::string>;
|
||||
using ChaiScript_WString = ChaiScript_Impl<std::wstring>;
|
||||
} // namespace chaiscript
|
||||
|
||||
#endif /* CHAISCRIPT_HPP_ */
|
||||
|
||||
@ -38,6 +38,7 @@
|
||||
namespace chaiscript {
|
||||
class Std_Lib {
|
||||
public:
|
||||
template<typename StringType = std::string>
|
||||
[[nodiscard]] static ModulePtr library(const std::vector<Library_Options> &t_opts = {}) {
|
||||
if (std::find(t_opts.begin(), t_opts.end(), Library_Options::No_Stdlib) != t_opts.end()) {
|
||||
return std::make_shared<Module>();
|
||||
@ -49,10 +50,10 @@ namespace chaiscript {
|
||||
const bool no_prelude = std::find(t_opts.begin(), t_opts.end(), Library_Options::No_Prelude) != t_opts.end();
|
||||
const bool no_json = std::find(t_opts.begin(), t_opts.end(), Library_Options::No_JSON) != t_opts.end();
|
||||
|
||||
bootstrap::Bootstrap::bootstrap(*lib, no_io);
|
||||
bootstrap::Bootstrap::bootstrap<StringType>(*lib, no_io);
|
||||
|
||||
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<StringType>("string", *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);
|
||||
|
||||
|
||||
@ -10,6 +10,8 @@
|
||||
#ifndef CHAISCRIPT_BOOTSTRAP_HPP_
|
||||
#define CHAISCRIPT_BOOTSTRAP_HPP_
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
#include "../utility/utility.hpp"
|
||||
#include "register_function.hpp"
|
||||
|
||||
@ -269,6 +271,7 @@ namespace chaiscript::bootstrap {
|
||||
/// \brief perform all common bootstrap functions for std::string, void and POD types
|
||||
/// \param[in,out] m Module to add bootstrapped functions to
|
||||
/// \param[in] t_no_io If true, skip registering print_string and println_string
|
||||
template<typename StringType = std::string>
|
||||
static void bootstrap(Module &m, const bool t_no_io = false) {
|
||||
m.add(user_type<void>(), "void");
|
||||
m.add(user_type<bool>(), "bool");
|
||||
@ -393,13 +396,27 @@ namespace chaiscript::bootstrap {
|
||||
operators::equal<bool>(m);
|
||||
operators::not_equal<bool>(m);
|
||||
|
||||
m.add(fun([](const std::string &s) { return s; }), "to_string");
|
||||
m.add(fun([](const bool b) { return std::string(b ? "true" : "false"); }), "to_string");
|
||||
m.add(fun([](const StringType &s) { return s; }), "to_string");
|
||||
m.add(fun([](const bool b) -> StringType {
|
||||
if constexpr (std::is_same_v<StringType, std::string>) {
|
||||
return b ? "true" : "false";
|
||||
} else {
|
||||
const auto s = std::string(b ? "true" : "false");
|
||||
return StringType(s.begin(), s.end());
|
||||
}
|
||||
}), "to_string");
|
||||
m.add(fun(&unknown_assign), "=");
|
||||
m.add(fun([](const Boxed_Value &bv) { throw bv; }), "throw");
|
||||
|
||||
m.add(fun([](const char c) { return std::string(1, c); }), "to_string");
|
||||
m.add(fun(&Boxed_Number::to_string), "to_string");
|
||||
m.add(fun([](const typename StringType::value_type c) -> StringType { return StringType(1, c); }), "to_string");
|
||||
if constexpr (std::is_same_v<StringType, std::string>) {
|
||||
m.add(fun(&Boxed_Number::to_string), "to_string");
|
||||
} else {
|
||||
m.add(fun([](const Boxed_Number &n) -> StringType {
|
||||
const auto s = n.to_string();
|
||||
return StringType(s.begin(), s.end());
|
||||
}), "to_string");
|
||||
}
|
||||
|
||||
bootstrap_pod_type<double>("double", m);
|
||||
bootstrap_pod_type<long double>("long_double", m);
|
||||
|
||||
@ -17,6 +17,7 @@
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
#include "../dispatchkit/boxed_value.hpp"
|
||||
@ -101,9 +102,31 @@ namespace chaiscript {
|
||||
return Char_Parser_Helper<std::true_type>::u8str_from_ll(val);
|
||||
}
|
||||
};
|
||||
template<typename S>
|
||||
int stoi_for_string(const S &s, std::size_t *pos, int base) {
|
||||
if constexpr (std::is_same_v<S, std::string>) {
|
||||
return std::stoi(s, pos, base);
|
||||
} else if constexpr (std::is_same_v<S, std::wstring>) {
|
||||
return std::stoi(std::wstring(s.begin(), s.end()), pos, base);
|
||||
} else {
|
||||
return std::stoi(std::string(s.begin(), s.end()), pos, base);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename S>
|
||||
long long stoll_for_string(const S &s, std::size_t *pos, int base) {
|
||||
if constexpr (std::is_same_v<S, std::string>) {
|
||||
return std::stoll(s, pos, base);
|
||||
} else if constexpr (std::is_same_v<S, std::wstring>) {
|
||||
return std::stoll(std::wstring(s.begin(), s.end()), pos, base);
|
||||
} else {
|
||||
return std::stoll(std::string(s.begin(), s.end()), pos, base);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
template<typename Tracer, typename Optimizer, std::size_t Parse_Depth = 512>
|
||||
template<typename Tracer, typename Optimizer, typename StringType = std::string, std::size_t Parse_Depth = 512>
|
||||
class ChaiScript_Parser final : public ChaiScript_Parser_Base {
|
||||
void *get_tracer_ptr() noexcept override { return &m_tracer; }
|
||||
|
||||
@ -812,6 +835,17 @@ namespace chaiscript {
|
||||
#endif
|
||||
}
|
||||
|
||||
template<typename S>
|
||||
static std::string to_narrow(const S &s) {
|
||||
if constexpr (std::is_same_v<S, std::string>) {
|
||||
return s;
|
||||
} else if constexpr (std::is_convertible_v<S, std::string_view>) {
|
||||
return std::string(s);
|
||||
} else {
|
||||
return std::string(s.begin(), s.end());
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T, typename... Param>
|
||||
std::unique_ptr<eval::AST_Node_Impl<Tracer>>
|
||||
make_node(std::string_view t_match, const int t_prev_line, const int t_prev_col, Param &&...param) {
|
||||
@ -1100,8 +1134,8 @@ namespace chaiscript {
|
||||
|
||||
void process_hex() {
|
||||
if (!hex_matches.empty()) {
|
||||
auto val = stoll(hex_matches, nullptr, 16);
|
||||
match.push_back(char_type(val));
|
||||
const auto val = detail::stoll_for_string(hex_matches, nullptr, 16);
|
||||
match.push_back(static_cast<char_type>(val));
|
||||
}
|
||||
hex_matches.clear();
|
||||
is_escaped = false;
|
||||
@ -1110,8 +1144,8 @@ namespace chaiscript {
|
||||
|
||||
void process_octal() {
|
||||
if (!octal_matches.empty()) {
|
||||
auto val = stoll(octal_matches, nullptr, 8);
|
||||
match.push_back(char_type(val));
|
||||
const auto val = detail::stoll_for_string(octal_matches, nullptr, 8);
|
||||
match.push_back(static_cast<char_type>(val));
|
||||
}
|
||||
octal_matches.clear();
|
||||
is_escaped = false;
|
||||
@ -1119,14 +1153,13 @@ namespace chaiscript {
|
||||
}
|
||||
|
||||
void process_unicode() {
|
||||
const auto ch = static_cast<uint32_t>(std::stoi(hex_matches, nullptr, 16));
|
||||
const auto ch = static_cast<uint32_t>(detail::stoi_for_string<string_type>(hex_matches, nullptr, 16));
|
||||
const auto match_size = hex_matches.size();
|
||||
hex_matches.clear();
|
||||
is_escaped = false;
|
||||
const auto u_size = unicode_size;
|
||||
unicode_size = 0;
|
||||
|
||||
char buf[4];
|
||||
if (u_size != match_size) {
|
||||
throw exception::eval_error("Incomplete unicode escape sequence");
|
||||
}
|
||||
@ -1134,26 +1167,44 @@ namespace chaiscript {
|
||||
throw exception::eval_error("Invalid 16 bit universal character");
|
||||
}
|
||||
|
||||
if (ch < 0x80) {
|
||||
match += static_cast<char>(ch);
|
||||
} else if (ch < 0x800) {
|
||||
buf[0] = static_cast<char>(0xC0 | (ch >> 6));
|
||||
buf[1] = static_cast<char>(0x80 | (ch & 0x3F));
|
||||
match.append(buf, 2);
|
||||
} else if (ch < 0x10000) {
|
||||
buf[0] = static_cast<char>(0xE0 | (ch >> 12));
|
||||
buf[1] = static_cast<char>(0x80 | ((ch >> 6) & 0x3F));
|
||||
buf[2] = static_cast<char>(0x80 | (ch & 0x3F));
|
||||
match.append(buf, 3);
|
||||
} else if (ch < 0x200000) {
|
||||
buf[0] = static_cast<char>(0xF0 | (ch >> 18));
|
||||
buf[1] = static_cast<char>(0x80 | ((ch >> 12) & 0x3F));
|
||||
buf[2] = static_cast<char>(0x80 | ((ch >> 6) & 0x3F));
|
||||
buf[3] = static_cast<char>(0x80 | (ch & 0x3F));
|
||||
match.append(buf, 4);
|
||||
if constexpr (sizeof(char_type) >= 4) {
|
||||
if (ch < 0x200000) {
|
||||
match.push_back(static_cast<char_type>(ch));
|
||||
} else {
|
||||
throw exception::eval_error("Invalid 32 bit universal character");
|
||||
}
|
||||
} else if constexpr (sizeof(char_type) >= 2) {
|
||||
if (ch < 0x10000) {
|
||||
match.push_back(static_cast<char_type>(ch));
|
||||
} else if (ch < 0x110000) {
|
||||
const auto adjusted = ch - 0x10000;
|
||||
match.push_back(static_cast<char_type>(0xD800 + (adjusted >> 10)));
|
||||
match.push_back(static_cast<char_type>(0xDC00 + (adjusted & 0x3FF)));
|
||||
} else {
|
||||
throw exception::eval_error("Invalid 32 bit universal character");
|
||||
}
|
||||
} else {
|
||||
// this must be an invalid escape sequence?
|
||||
throw exception::eval_error("Invalid 32 bit universal character");
|
||||
char buf[4];
|
||||
if (ch < 0x80) {
|
||||
match += static_cast<char>(ch);
|
||||
} else if (ch < 0x800) {
|
||||
buf[0] = static_cast<char>(0xC0 | (ch >> 6));
|
||||
buf[1] = static_cast<char>(0x80 | (ch & 0x3F));
|
||||
match.append(buf, 2);
|
||||
} else if (ch < 0x10000) {
|
||||
buf[0] = static_cast<char>(0xE0 | (ch >> 12));
|
||||
buf[1] = static_cast<char>(0x80 | ((ch >> 6) & 0x3F));
|
||||
buf[2] = static_cast<char>(0x80 | (ch & 0x3F));
|
||||
match.append(buf, 3);
|
||||
} else if (ch < 0x200000) {
|
||||
buf[0] = static_cast<char>(0xF0 | (ch >> 18));
|
||||
buf[1] = static_cast<char>(0x80 | ((ch >> 12) & 0x3F));
|
||||
buf[2] = static_cast<char>(0x80 | ((ch >> 6) & 0x3F));
|
||||
buf[3] = static_cast<char>(0x80 | (ch & 0x3F));
|
||||
match.append(buf, 4);
|
||||
} else {
|
||||
throw exception::eval_error("Invalid 32 bit universal character");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1280,11 +1331,11 @@ namespace chaiscript {
|
||||
const auto start = m_position;
|
||||
|
||||
if (Quoted_String_()) {
|
||||
std::string match;
|
||||
StringType match;
|
||||
const auto prev_stack_top = m_match_stack.size();
|
||||
|
||||
bool is_interpolated = [&]() -> bool {
|
||||
Char_Parser<std::string> cparser(match, true);
|
||||
Char_Parser<StringType> cparser(match, true);
|
||||
|
||||
auto s = start + 1, end = m_position - 1;
|
||||
|
||||
@ -1293,7 +1344,7 @@ namespace chaiscript {
|
||||
if (*s == '{') {
|
||||
// We've found an interpolation point
|
||||
|
||||
m_match_stack.push_back(make_node<eval::Constant_AST_Node<Tracer>>(match, start.line, start.col, const_var(match)));
|
||||
m_match_stack.push_back(make_node<eval::Constant_AST_Node<Tracer>>(to_narrow(match), start.line, start.col, const_var(match)));
|
||||
|
||||
if (cparser.is_interpolated) {
|
||||
// If we've seen previous interpolation, add on instead of making a new one
|
||||
@ -1351,7 +1402,7 @@ namespace chaiscript {
|
||||
return cparser.is_interpolated;
|
||||
}();
|
||||
|
||||
m_match_stack.push_back(make_node<eval::Constant_AST_Node<Tracer>>(match, start.line, start.col, const_var(match)));
|
||||
m_match_stack.push_back(make_node<eval::Constant_AST_Node<Tracer>>(to_narrow(match), start.line, start.col, const_var(match)));
|
||||
|
||||
if (is_interpolated) {
|
||||
build_match<eval::Binary_Operator_AST_Node<Tracer>>(prev_stack_top, "+");
|
||||
@ -1439,7 +1490,7 @@ namespace chaiscript {
|
||||
close_seq += '"';
|
||||
|
||||
// Extract raw content up to closing sequence
|
||||
std::string match;
|
||||
StringType match;
|
||||
auto end = m_position; // m_position is already past the closing sequence
|
||||
|
||||
// Content is from s up to (end - close_seq.size())
|
||||
@ -1456,11 +1507,11 @@ namespace chaiscript {
|
||||
break;
|
||||
}
|
||||
}
|
||||
match.push_back(*s);
|
||||
match.push_back(static_cast<typename StringType::value_type>(*s));
|
||||
++s;
|
||||
}
|
||||
|
||||
m_match_stack.push_back(make_node<eval::Constant_AST_Node<Tracer>>(match, start.line, start.col, const_var(match)));
|
||||
m_match_stack.push_back(make_node<eval::Constant_AST_Node<Tracer>>(to_narrow(match), start.line, start.col, const_var(match)));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@ -1501,11 +1552,11 @@ namespace chaiscript {
|
||||
|
||||
const auto start = m_position;
|
||||
if (Single_Quoted_String_()) {
|
||||
std::string match;
|
||||
StringType match;
|
||||
|
||||
{
|
||||
// scope for cparser destructor
|
||||
Char_Parser<std::string> cparser(match, false);
|
||||
Char_Parser<StringType> cparser(match, false);
|
||||
|
||||
for (auto s = start + 1, end = m_position - 1; s != end; ++s) {
|
||||
cparser.parse(*s, start.line, start.col, *m_filename);
|
||||
@ -1518,7 +1569,8 @@ namespace chaiscript {
|
||||
*m_filename);
|
||||
}
|
||||
|
||||
m_match_stack.push_back(make_node<eval::Constant_AST_Node<Tracer>>(match, start.line, start.col, const_var(char(match.at(0)))));
|
||||
m_match_stack.push_back(make_node<eval::Constant_AST_Node<Tracer>>(to_narrow(match), start.line, start.col,
|
||||
const_var(static_cast<typename StringType::value_type>(match.at(0)))));
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
@ -2805,7 +2857,7 @@ namespace chaiscript {
|
||||
}
|
||||
|
||||
AST_NodePtr parse(const std::string &t_input, const std::string &t_fname) override {
|
||||
ChaiScript_Parser<Tracer, Optimizer> parser(m_tracer, m_optimizer);
|
||||
ChaiScript_Parser<Tracer, Optimizer, StringType> parser(m_tracer, m_optimizer);
|
||||
return parser.parse_internal(t_input, t_fname);
|
||||
}
|
||||
|
||||
|
||||
47
unittests/string_type_param_test.cpp
Normal file
47
unittests/string_type_param_test.cpp
Normal file
@ -0,0 +1,47 @@
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 4062 4242 4566 4640 4702 6330 28251)
|
||||
#endif
|
||||
|
||||
#ifdef __GNUC__
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wunknown-pragmas"
|
||||
#pragma GCC diagnostic ignored "-Wparentheses"
|
||||
#pragma GCC diagnostic ignored "-Wignored-qualifiers"
|
||||
#endif
|
||||
|
||||
#include <chaiscript/chaiscript.hpp>
|
||||
#include <chaiscript/chaiscript_basic.hpp>
|
||||
|
||||
#define CATCH_CONFIG_MAIN
|
||||
#include "catch.hpp"
|
||||
|
||||
TEST_CASE("String type can be parameterized to wstring") {
|
||||
chaiscript::ChaiScript_WString chai;
|
||||
|
||||
SECTION("String literals produce std::wstring") {
|
||||
auto result = chai.eval<std::wstring>("\"hello\"");
|
||||
CHECK(result == L"hello");
|
||||
}
|
||||
|
||||
SECTION("String concatenation works with wstring") {
|
||||
auto result = chai.eval<std::wstring>("\"hello\" + \" world\"");
|
||||
CHECK(result == L"hello world");
|
||||
}
|
||||
|
||||
SECTION("to_string works for numbers with wstring") {
|
||||
auto result = chai.eval<std::wstring>("to_string(42)");
|
||||
CHECK(result == L"42");
|
||||
}
|
||||
|
||||
SECTION("String interpolation works with wstring") {
|
||||
auto result = chai.eval<std::wstring>("var x = 5; \"value: ${x}\"");
|
||||
CHECK(result == L"value: 5");
|
||||
}
|
||||
|
||||
SECTION("Default ChaiScript still uses std::string") {
|
||||
chaiscript::ChaiScript default_chai;
|
||||
auto result = default_chai.eval<std::string>("\"hello\"");
|
||||
CHECK(result == "hello");
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user