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
44 changes: 44 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,50 @@ Common generated operations include:
- `clone()`
- field accessors, `has_*()`, `set_*()`, `mutable_*()`, and `ensure_*()` where applicable

### String Views

Generated `string` field accessors return `::protocyte::Span<const char>` by
default. Protocyte does not return `std::string_view` by default because the
runtime is designed for freestanding and kernel-style builds that avoid
standard-library exception surfaces. `std::string_view` includes checked APIs
such as `at()` and some `substr()` overloads whose standard contract can throw
`std::out_of_range`; `::protocyte::Span<const char>` keeps the default string
view API in Protocyte's no-exceptions runtime surface.

Hosted users who want standard-library interoperability can opt in:

```cmake
target_compile_definitions(my_target PRIVATE PROTOCYTE_ENABLE_STD_STRING_VIEW=1)
```

When `PROTOCYTE_ENABLE_STD_STRING_VIEW` is defined, the runtime includes
`<string_view>` and both `::protocyte::Span<char>` / `Span<const char>` and
`::protocyte::String` are implicitly convertible to `std::string_view`. The
default accessor return type remains `Span<const char>`, so code that does not
enable the option keeps the smaller no-exception surface.

In a Windows kernel driver, one technically possible MSVC/STL-specific escape
hatch is to provide the STL's internal out-of-range throw helper yourself so
`std::string_view::at()` can link even though exceptions are unavailable. This
should be treated as a last-resort compatibility shim, not as a recommended
Protocyte configuration: any accidental checked access would bugcheck the
system.

```cpp
#include <ntddk.h>

namespace std {
[[noreturn]] void __cdecl _Xout_of_range(char const*) {
KeBugCheckEx(MANUALLY_INITIATED_CRASH, 'svat', 0, 0, 0);
__assume(0);
}
} // namespace std
```

Prefer the default `::protocyte::Span<const char>` API in kernel and
freestanding builds. It avoids depending on implementation-private STL symbols
and keeps checked string access out of the generated-code runtime surface.

### Parse Atomicity

`merge_from(reader)` commits parsed data per wire field occurrence. If a field
Expand Down
8 changes: 7 additions & 1 deletion smoke/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,12 @@ add_executable(protocyte_host_smoke
"${PROTOCYTE_GENERATED_DIR}/cross_package.protocyte.cpp"
)
target_include_directories(protocyte_host_smoke PRIVATE "${PROTOCYTE_GENERATED_DIR}")
target_compile_definitions(protocyte_host_smoke PRIVATE PROTOCYTE_ENABLE_HOSTED_ALLOCATOR=1)
target_compile_definitions(
protocyte_host_smoke
PRIVATE
PROTOCYTE_ENABLE_HOSTED_ALLOCATOR=1
PROTOCYTE_ENABLE_STD_STRING_VIEW=1
)
target_compile_features(protocyte_host_smoke PRIVATE cxx_std_20)
target_link_libraries(protocyte_host_smoke PRIVATE Catch2::Catch2WithMain)

Expand All @@ -150,6 +155,7 @@ if(PROTOCYTE_SMOKE_BUILD_DRIVER)
"${PROTOCYTE_GENERATED_DIR}/example.protocyte.cpp"
)
target_include_directories(protocyte_kernel_smoke PRIVATE "${PROTOCYTE_GENERATED_DIR}")
target_compile_definitions(protocyte_kernel_smoke PRIVATE PROTOCYTE_ENABLE_STD_STRING_VIEW=1)
target_compile_features(protocyte_kernel_smoke PRIVATE cxx_std_20)
target_compile_options(protocyte_kernel_smoke PRIVATE /std:c++20 /permissive- /W4)

Expand Down
10 changes: 5 additions & 5 deletions smoke/generated/compat.protocyte.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ namespace protocyte_smoke::test::compat {
}
constexpr void clear_value() noexcept { value_ = {}; }

::protocyte::Span<const ::protocyte::u8> label() const noexcept { return label_.view(); }
::protocyte::Span<const char> label() const noexcept { return label_.view(); }
typename Config::String &mutable_label() noexcept { return label_; }
template<class Value>::protocyte::Status set_label(const Value &value) noexcept
requires(::protocyte::ByteSpanSource<Value> && !::protocyte::TextSource<Value>)
Expand Down Expand Up @@ -652,7 +652,7 @@ namespace protocyte_smoke::test::compat {
}
constexpr void clear_f_double() noexcept { f_double_ = {}; }

::protocyte::Span<const ::protocyte::u8> f_string() const noexcept { return f_string_.view(); }
::protocyte::Span<const char> f_string() const noexcept { return f_string_.view(); }
typename Config::String &mutable_f_string() noexcept { return f_string_; }
template<class Value>::protocyte::Status set_f_string(const Value &value) noexcept
requires(::protocyte::ByteSpanSource<Value> && !::protocyte::TextSource<Value>)
Expand Down Expand Up @@ -741,8 +741,8 @@ namespace protocyte_smoke::test::compat {
constexpr bool has_oneof_string() const noexcept {
return special_oneof_case_ == Special_oneofCase::oneof_string;
}
::protocyte::Span<const ::protocyte::u8> oneof_string() const noexcept {
return has_oneof_string() ? special_oneof.oneof_string.view() : ::protocyte::Span<const ::protocyte::u8> {};
::protocyte::Span<const char> oneof_string() const noexcept {
return has_oneof_string() ? special_oneof.oneof_string.view() : ::protocyte::Span<const char> {};
}
template<class Value>::protocyte::Status set_oneof_string(const Value &value) noexcept
requires(::protocyte::ByteSpanSource<Value> && !::protocyte::TextSource<Value>)
Expand Down Expand Up @@ -852,7 +852,7 @@ namespace protocyte_smoke::test::compat {
has_opt_int32_ = false;
}

::protocyte::Span<const ::protocyte::u8> opt_string() const noexcept { return opt_string_.view(); }
::protocyte::Span<const char> opt_string() const noexcept { return opt_string_.view(); }
bool has_opt_string() const noexcept { return has_opt_string_; }
typename Config::String &mutable_opt_string() noexcept {
has_opt_string_ = true;
Expand Down
20 changes: 10 additions & 10 deletions smoke/generated/example.protocyte.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ namespace test::ultimate {
return out;
}

::protocyte::Span<const ::protocyte::u8> description() const noexcept { return description_.view(); }
::protocyte::Span<const char> description() const noexcept { return description_.view(); }
typename Config::String &mutable_description() noexcept { return description_; }
template<class Value>::protocyte::Status set_description(const Value &value) noexcept
requires(::protocyte::ByteSpanSource<Value> && !::protocyte::TextSource<Value>)
Expand Down Expand Up @@ -394,7 +394,7 @@ namespace test::ultimate {
return out;
}

::protocyte::Span<const ::protocyte::u8> name() const noexcept { return name_.view(); }
::protocyte::Span<const char> name() const noexcept { return name_.view(); }
typename Config::String &mutable_name() noexcept { return name_; }
template<class Value>::protocyte::Status set_name(const Value &value) noexcept
requires(::protocyte::ByteSpanSource<Value> && !::protocyte::TextSource<Value>)
Expand Down Expand Up @@ -1118,7 +1118,7 @@ namespace test::ultimate {
deep_oneof_case_ = Deep_oneofCase::none;
}

::protocyte::Span<const ::protocyte::u8> extreme() const noexcept { return extreme_.view(); }
::protocyte::Span<const char> extreme() const noexcept { return extreme_.view(); }
typename Config::String &mutable_extreme() noexcept { return extreme_; }
template<class Value>::protocyte::Status set_extreme(const Value &value) noexcept
requires(::protocyte::ByteSpanSource<Value> && !::protocyte::TextSource<Value>)
Expand Down Expand Up @@ -1168,8 +1168,8 @@ namespace test::ultimate {
}

constexpr bool has_text() const noexcept { return deep_oneof_case_ == Deep_oneofCase::text; }
::protocyte::Span<const ::protocyte::u8> text() const noexcept {
return has_text() ? deep_oneof.text.view() : ::protocyte::Span<const ::protocyte::u8> {};
::protocyte::Span<const char> text() const noexcept {
return has_text() ? deep_oneof.text.view() : ::protocyte::Span<const char> {};
}
template<class Value>::protocyte::Status set_text(const Value &value) noexcept
requires(::protocyte::ByteSpanSource<Value> && !::protocyte::TextSource<Value>)
Expand Down Expand Up @@ -2297,7 +2297,7 @@ namespace test::ultimate {
}
constexpr void clear_f_bool() noexcept { f_bool_ = {}; }

::protocyte::Span<const ::protocyte::u8> f_string() const noexcept { return f_string_.view(); }
::protocyte::Span<const char> f_string() const noexcept { return f_string_.view(); }
typename Config::String &mutable_f_string() noexcept { return f_string_; }
template<class Value>::protocyte::Status set_f_string(const Value &value) noexcept
requires(::protocyte::ByteSpanSource<Value> && !::protocyte::TextSource<Value>)
Expand Down Expand Up @@ -2399,8 +2399,8 @@ namespace test::ultimate {
constexpr bool has_oneof_string() const noexcept {
return special_oneof_case_ == Special_oneofCase::oneof_string;
}
::protocyte::Span<const ::protocyte::u8> oneof_string() const noexcept {
return has_oneof_string() ? special_oneof.oneof_string.view() : ::protocyte::Span<const ::protocyte::u8> {};
::protocyte::Span<const char> oneof_string() const noexcept {
return has_oneof_string() ? special_oneof.oneof_string.view() : ::protocyte::Span<const char> {};
}
template<class Value>::protocyte::Status set_oneof_string(const Value &value) noexcept
requires(::protocyte::ByteSpanSource<Value> && !::protocyte::TextSource<Value>)
Expand Down Expand Up @@ -2749,7 +2749,7 @@ namespace test::ultimate {
has_opt_int32_ = false;
}

::protocyte::Span<const ::protocyte::u8> opt_string() const noexcept { return opt_string_.view(); }
::protocyte::Span<const char> opt_string() const noexcept { return opt_string_.view(); }
bool has_opt_string() const noexcept { return has_opt_string_; }
typename Config::String &mutable_opt_string() noexcept {
has_opt_string_ = true;
Expand Down Expand Up @@ -6063,7 +6063,7 @@ namespace test::ultimate {
return out;
}

::protocyte::Span<const ::protocyte::u8> tag() const noexcept { return tag_.view(); }
::protocyte::Span<const char> tag() const noexcept { return tag_.view(); }
typename Config::String &mutable_tag() noexcept { return tag_; }
template<class Value>::protocyte::Status set_tag(const Value &value) noexcept
requires(::protocyte::ByteSpanSource<Value> && !::protocyte::TextSource<Value>)
Expand Down
78 changes: 63 additions & 15 deletions smoke/generated/protocyte/runtime/runtime.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
#include <new>
#include <type_traits>

#ifdef PROTOCYTE_ENABLE_STD_STRING_VIEW
#include <string_view>
#endif

namespace protocyte {

using i8 = ::std::int8_t;
Expand Down Expand Up @@ -1265,6 +1269,13 @@ namespace protocyte {
constexpr usize size() const noexcept { return size_; }
constexpr usize size_bytes() const noexcept { return size_ * sizeof(T); }
constexpr bool empty() const noexcept { return size_ == 0u; }
#ifdef PROTOCYTE_ENABLE_STD_STRING_VIEW
constexpr operator ::std::string_view() const noexcept
requires(::std::same_as<::std::remove_cv_t<T>, char>)
{
return data_ == nullptr ? ::std::string_view {} : ::std::string_view {data_, size_};
}
#endif
template<usize Count> constexpr Span<T, Count> first() const noexcept
requires(Extent == dynamic_extent || Count <= Extent)
{
Expand Down Expand Up @@ -1560,6 +1571,13 @@ namespace protocyte {
return ::std::memcmp(lhs.data(), rhs.data(), lhs.size()) == 0;
}

template<class L, usize LExtent, class R, usize RExtent>
constexpr bool bytes_equal(const Span<L, LExtent> lhs, const Span<R, RExtent> rhs) noexcept
requires(!(::std::same_as<::std::remove_cv_t<L>, u8> && ::std::same_as<::std::remove_cv_t<R>, u8>) )
{
return bytes_equal(as_bytes(lhs), as_bytes(rhs));
}

constexpr bool bytes_zero(const Span<const u8> view) noexcept {
for (usize i {}; i < view.size(); ++i) {
if (view.data()[i] != 0u) {
Expand All @@ -1578,6 +1596,12 @@ namespace protocyte {
return hash;
}

template<class T, usize Extent> constexpr u64 fnv1a(const Span<T, Extent> view) noexcept
requires(!::std::same_as<::std::remove_cv_t<T>, u8>)
{
return fnv1a(as_bytes(view));
}

template<class T> struct AlwaysFalse {
static constexpr bool value = false;
};
Expand Down Expand Up @@ -2815,11 +2839,11 @@ namespace protocyte {

template<class Config> struct String {
using Context = typename Config::Context;
using value_type = const u8;
using iterator = typename Config::Bytes::const_iterator;
using const_iterator = typename Config::Bytes::const_iterator;
using reverse_iterator = typename Config::Bytes::const_reverse_iterator;
using const_reverse_iterator = typename Config::Bytes::const_reverse_iterator;
using value_type = const char;
using iterator = const char *;
using const_iterator = const char *;
using reverse_iterator = ReverseIterator<const char>;
using const_reverse_iterator = ReverseIterator<const char>;

explicit String(Context *ctx = nullptr) noexcept: bytes_ {ctx} {}
String(String &&other) noexcept: bytes_ {protocyte::move(other.bytes_)} {}
Expand All @@ -2830,19 +2854,24 @@ namespace protocyte {
String(const String &) = delete;
String &operator=(const String &) = delete;

Span<const u8> view() const noexcept { return bytes_.view(); }
const_iterator begin() const noexcept { return bytes_.begin(); }
const_iterator end() const noexcept { return bytes_.end(); }
const_iterator cbegin() const noexcept { return bytes_.cbegin(); }
const_iterator cend() const noexcept { return bytes_.cend(); }
const_reverse_iterator rbegin() const noexcept { return bytes_.rbegin(); }
const_reverse_iterator rend() const noexcept { return bytes_.rend(); }
const_reverse_iterator crbegin() const noexcept { return bytes_.crbegin(); }
const_reverse_iterator crend() const noexcept { return bytes_.crend(); }
const u8 *data() const noexcept { return bytes_.data(); }
Span<const char> view() const noexcept { return {data(), size()}; }
Span<const u8> byte_view() const noexcept { return bytes_.view(); }
const_iterator begin() const noexcept { return data(); }
const_iterator end() const noexcept { return data() + size(); }
const_iterator cbegin() const noexcept { return begin(); }
const_iterator cend() const noexcept { return end(); }
const_reverse_iterator rbegin() const noexcept { return const_reverse_iterator {end()}; }
const_reverse_iterator rend() const noexcept { return const_reverse_iterator {begin()}; }
const_reverse_iterator crbegin() const noexcept { return rbegin(); }
const_reverse_iterator crend() const noexcept { return rend(); }
const char *data() const noexcept { return reinterpret_cast<const char *>(bytes_.data()); }
Context *context() const noexcept { return bytes_.context(); }
usize size() const noexcept { return bytes_.size(); }
usize length() const noexcept { return size(); }
bool empty() const noexcept { return bytes_.empty(); }
#ifdef PROTOCYTE_ENABLE_STD_STRING_VIEW
operator ::std::string_view() const noexcept { return view(); }
#endif
void clear() noexcept { bytes_.clear(); }
Span<u8> mutable_view_for_overwrite() noexcept { return bytes_.mutable_view(); }

Expand All @@ -2853,6 +2882,14 @@ namespace protocyte {
return bytes_.resize_for_overwrite(count);
}

Status assign(const Span<const char> view) noexcept {
const auto bytes = byte_span_of(view);
if (!bytes) {
return bytes.status();
}
return assign(*bytes);
}

Status assign(const Span<const u8> view) noexcept {
if (const auto st = check_size_limit(view.size()); !st) {
return st;
Expand Down Expand Up @@ -4229,6 +4266,17 @@ namespace protocyte {
return write_bytes_field(writer, field_number, view);
}

template<class Writer, class T, usize Extent>
Status write_string_field(Writer &writer, const u32 field_number, const Span<T, Extent> view) noexcept
requires(TextChar<T>)
{
const auto bytes = byte_span_of(view);
if (!bytes) {
return bytes.status();
}
return write_string_field(writer, field_number, *bytes);
}

inline Result<usize> add_size(const usize total, const usize value) noexcept { return checked_add(total, value); }

#ifdef PROTOCYTE_ENABLE_HOSTED_ALLOCATOR
Expand Down
7 changes: 6 additions & 1 deletion smoke/src/host_smoke.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,8 @@ namespace {
};
}

bool view_equal(protocyte::Span<const protocyte::u8> lhs, protocyte::Span<const protocyte::u8> rhs) noexcept {
template<class L, protocyte::usize LExtent, class R, protocyte::usize RExtent>
bool view_equal(protocyte::Span<L, LExtent> lhs, protocyte::Span<R, RExtent> rhs) noexcept {
return protocyte::bytes_equal(lhs, rhs);
}

Expand Down Expand Up @@ -2711,6 +2712,10 @@ TEST_CASE("byte setters accept contiguous byte containers", "[smoke][runtime][by
const auto string_view = protocyte::byte_span_of(string_payload);
REQUIRE(string_view);
CHECK(view_equal(message.f_string(), *string_view));
const std::string_view converted_span = message.f_string();
CHECK(converted_span == std::string_view {"hello"});
const std::string_view converted_string = message.mutable_f_string();
CHECK(converted_string == converted_span);

require_success(message.set_f_string("hello from literal"));
CHECK(message.f_string().size() == std::string_view {"hello from literal"}.size());
Expand Down
6 changes: 4 additions & 2 deletions src/protocyte/cpp.py
Original file line number Diff line number Diff line change
Expand Up @@ -813,7 +813,8 @@ def emit_setter_body() -> None:
return
if item.kind in {"string", "bytes"}:
typ = _field_type(item, options)
w.line(f"::protocyte::Span<const ::protocyte::u8> {item.cpp_name}() const noexcept {{ return {_member(item)}.view(); }}")
view_type = "::protocyte::Span<const char>" if item.kind == "string" else "::protocyte::Span<const ::protocyte::u8>"
w.line(f"{view_type} {item.cpp_name}() const noexcept {{ return {_member(item)}.view(); }}")
if item.proto3_optional:
w.line(f"bool has_{item.cpp_name}() const noexcept {{ return has_{item.cpp_name}_; }}")
w.line(f"{typ}& mutable_{item.cpp_name}() noexcept {{")
Expand Down Expand Up @@ -878,8 +879,9 @@ def _emit_oneof_accessors(w: CppWriter, item: FieldModel, options: GeneratorOpti
f"constexpr bool has_{item.cpp_name}() const noexcept {{ return {case_member} == {case_type}::{item.cpp_name}; }}"
)
if item.kind in {"string", "bytes"}:
view_type = "::protocyte::Span<const char>" if item.kind == "string" else "::protocyte::Span<const ::protocyte::u8>"
w.line(
f"::protocyte::Span<const ::protocyte::u8> {item.cpp_name}() const noexcept {{ return has_{item.cpp_name}() ? {_member(item)}.view() : ::protocyte::Span<const ::protocyte::u8>{{}}; }}"
f"{view_type} {item.cpp_name}() const noexcept {{ return has_{item.cpp_name}() ? {_member(item)}.view() : {view_type}{{}}; }}"
)
def emit_setter_body() -> None:
if item.kind == "bytes" and item.array_enabled:
Expand Down
Loading
Loading