From 31d570ec6bfd7f3780e5bcb11df561b9ab1de216 Mon Sep 17 00:00:00 2001 From: John Date: Thu, 6 Feb 2025 21:16:31 -0500 Subject: [PATCH 1/3] Milestone1 basic serde in cmake --- .gitignore | 1 + CMakeLists.txt | 56 ++++++++++++++++++++++++++++ README.md | 3 ++ python/test_types/milestone1.py | 10 +++++ python/test_types/milestone1_main.cc | 17 +++++++++ 5 files changed, 87 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 python/test_types/milestone1.py create mode 100644 python/test_types/milestone1_main.cc diff --git a/.gitignore b/.gitignore index 3a2c844..f47aeab 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ *.swp *__pycache__* +build/ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..554efa1 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,56 @@ +cmake_minimum_required(VERSION 3.11) +project(milestone1 CXX) + +# Set the C++ standard +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# --- Fetch gsl-lite (header-only) --- +include(FetchContent) +FetchContent_Declare( + gsl-lite + GIT_REPOSITORY https://github.com/gsl-lite/gsl-lite.git + GIT_TAG v0.42.0 # Use the tag "v0.42.0" as specified + SOURCE_SUBDIR include # Only fetch the header files from the "include" folder +) +FetchContent_MakeAvailable(gsl-lite) + +# --- Define an interface target for gsl-lite as GSL::GSL --- +if (NOT TARGET GSL::GSL) + add_library(GSL INTERFACE) + # Add the directory that contains gsl.hpp so that the user can simply do #include + target_include_directories(GSL INTERFACE ${gsl-lite_SOURCE_DIR}/include) + add_library(GSL::GSL ALIAS GSL) +endif() + +# --- Fetch tartanllama expected (header-only) --- +FetchContent_Declare( + tl_expected + GIT_REPOSITORY https://github.com/TartanLlama/expected.git + GIT_TAG v1.1.0 # Adjust the version/tag as needed; for example "0.8.0" + SOURCE_SUBDIR include # Only fetch the header files from the "include" folder +) +FetchContent_MakeAvailable(tl_expected) + +# --- Define an interface target for tartanllama expected as TL::expected --- +if (NOT TARGET TL::expected) + add_library(TL_EXPECTED INTERFACE) + # Add the directory that directly contains the "tl" folder so that + # you can include without an extra "include/" prefix. + target_include_directories(TL_EXPECTED INTERFACE ${tl_expected_SOURCE_DIR}/include) + add_library(TL::expected ALIAS TL_EXPECTED) +endif() + +# --- Define source file and executable --- +set(SOURCE_FILE "${CMAKE_SOURCE_DIR}/python/test_types/milestone1_main.cc") +add_executable(milestone1 ${SOURCE_FILE}) + +# --- Use these include directories for milestone1 --- +target_include_directories(milestone1 PRIVATE + "${CMAKE_SOURCE_DIR}/python/test_types" + "${CMAKE_SOURCE_DIR}/python/milestone1/test_types/milestone1" + "${CMAKE_SOURCE_DIR}/c/src/runtime" +) + +# --- Link dependencies --- +target_link_libraries(milestone1 PRIVATE GSL::GSL TL::expected) diff --git a/README.md b/README.md index f48e908..c723c6a 100644 --- a/README.md +++ b/README.md @@ -7,4 +7,7 @@ cd python; nix-shell; python -m tako.main generate bakery_test/ test_types.bakery.Bakery cpp ``` +To build milestone1 +cd build +cmake --build . diff --git a/python/test_types/milestone1.py b/python/test_types/milestone1.py new file mode 100644 index 0000000..c5a8052 --- /dev/null +++ b/python/test_types/milestone1.py @@ -0,0 +1,10 @@ +from tako.core.types import * + +class Milestone1(Protocol): + IntegerType = Struct(contained=i8) + BigIntegerType = Struct(contained=li32) + + # For milestone 1, we want to get to a point where I can get all three, but the second + # and third will return nullopt if the variant received is of unknown type + Two = Variant[i8]({IntegerType: 0, BigIntegerType: 1}) + Packet= Struct(one=li32, two=Two, three=lu32) diff --git a/python/test_types/milestone1_main.cc b/python/test_types/milestone1_main.cc new file mode 100644 index 0000000..2238486 --- /dev/null +++ b/python/test_types/milestone1_main.cc @@ -0,0 +1,17 @@ +#include +#include "core.hh" +#include +#include + +int main() { + // Build a Milestone1 + auto request = test_types::milestone1::Packet { + .one = 5, + .two = test_types::milestone1::BigIntegerType { + .contained = 3, + }, + .three = 6 + }; + std::vector request_bytes = request.serialize(); + +} From 4b083a959dee3b26160846b814c5d00df1090fee Mon Sep 17 00:00:00 2001 From: John Date: Thu, 6 Feb 2025 22:03:13 -0500 Subject: [PATCH 2/3] able to ser and de --- python/tako/generators/cpp/core.py | 89 +++++++++++++++++++++++++++- python/tako/generators/cpp/types.py | 50 ++++++++++++++++ python/test_types/milestone1_main.cc | 8 ++- 3 files changed, 145 insertions(+), 2 deletions(-) diff --git a/python/tako/generators/cpp/core.py b/python/tako/generators/cpp/core.py index f3134ae..b5b399c 100644 --- a/python/tako/generators/cpp/core.py +++ b/python/tako/generators/cpp/core.py @@ -37,6 +37,7 @@ relative_path, wrap_in_namespace, protocol_namespace, + PeekCppType, ViewCppType, OwnedCppType, cint_type, @@ -125,7 +126,7 @@ def visit_string_constant(self, constant: kir.RootStringConstant) -> cg.Node: @dataclasses.dataclass class RootTypeGenerator(tir.RootTypeVisitor[cg.Node]): def visit_struct(self, root: tir.Struct) -> cg.Node: - return cg.Section([gen_owned_class(root), gen_view_class(root)]) + return cg.Section([gen_owned_class(root), gen_view_class(root), gen_peek_class(root)]) def visit_variant(self, root: tir.Variant) -> cg.Node: return cg.Section([gen_owned_variant(root), gen_view_variant(root)]) @@ -249,6 +250,91 @@ def visit_dynamic(self, size: st.Dynamic) -> ClassParts: ) +def gen_peek_class(struct: tir.Struct) -> cg.Node: + class_name = PeekCppType.get_local_struct(struct) + owned_type = OwnedCppType.get_local_struct(struct) + builder = ClassBuilder() + + if isinstance(struct.size, st.Constant): + builder.public.append( + cg.Raw(f"static constexpr size_t SIZE_BYTES = {struct.size.value};") + ) + + builder_info = [ + (fname, field.type_.accept(ViewCppType())) + for fname, field in struct.get_owned() + ] + builder.public.append( + gen_raw( + """\ + using Rendered = {{ class_name }}; + using Built = {{ owned_type }}; + static Built build(const Rendered& rendered) { + return Built { + {%- for fname, fctype in builder_info %} + .{{ fname }} = {{ fctype }}::build(rendered.{{ fname }}()), + {%- endfor %} + }; + } + Built build() const { + return build(*this); + }""", + locals(), + ) + ) + builder.public.append(gen_render(struct)) + builder.public.append(gen_parse(struct)) + builder.public.append( + gen_raw( + """\ + static gsl::span serialize_into(const Built& built, gsl::span buf) { + return built.serialize_into(buf); + } + static size_t size_bytes(const Built& built) { + return built.size_bytes(); + }""", + locals(), + ) + ) + builder.public += [ + gen_raw_getter(fname, field) for fname, field in struct.get_non_virtual() + ] + # Virtual fields get getters, but not raw getters. + builder.public += [ + gen_getter(fname, field) for fname, field in struct.fields.items() + ] + builder.public.append( + gen_raw( + """\ + ::gsl::span backing_buffer() const { + return _buf; + }""", + locals(), + ) + ) + + member_info = [("_buf", "::gsl::span")] + [ + (f"_info_{fname}", f"::tako::ParseInfo<{field.type_.accept(ViewCppType())}>") + for fname, field in struct.get_non_virtual_dynamic() + ] + builder.private += [ + gen_raw( + """\ + explicit {{ class_name }}({%- for name, ctype in member_info -%} + {{ ctype }} _cons{{ name }}{{ ", " if not loop.last }} + {%- endfor -%}) : + {%- for name, _ in member_info -%} + {{ name }}{ _cons{{ name }} }{{ "," if not loop.last }} + {%- endfor -%} {} + {%- for name, ctype in member_info%} + {{ ctype }} {{ name }}; + {%- endfor %}""", + locals(), + ) + ] + + return cg.Class(name=class_name, bases=[], sections=builder.finalize()) + def gen_view_class(struct: tir.Struct) -> cg.Node: class_name = ViewCppType.get_local_struct(struct) owned_type = OwnedCppType.get_local_struct(struct) @@ -595,6 +681,7 @@ def gen_owned_variant(root: tir.Variant) -> cg.Node: def gen_view_variant(root: tir.Variant) -> cg.Node: view_class_name = ViewCppType.get_local_variant(root) + print(f"Generating for: {view_class_name}") owned_ctype = root.accept(OwnedCppType()) tag_ctype = root.tag_type.accept(OwnedCppType()) variants = [ diff --git a/python/tako/generators/cpp/types.py b/python/tako/generators/cpp/types.py index ae3ea3c..01e3137 100644 --- a/python/tako/generators/cpp/types.py +++ b/python/tako/generators/cpp/types.py @@ -105,6 +105,56 @@ def qname_to_cpp(qname: QName) -> str: return to_namespace(qname.parts) +@dataclasses.dataclass +class PeekCppType(tir.TypeVisitor[str]): + @staticmethod + def get_local_struct(type_: tir.Struct) -> str: + return f"{type_.name.name()}Peek" + + @staticmethod + def get_local_enum(type_: tir.Enum) -> str: + return f"{type_.name.name()}" + + @staticmethod + def get_local_variant(type_: tir.Variant) -> str: + return f"{type_.name.name()}Peek" + + def visit_int(self, type_: tir.Int) -> str: + return f"::tako::PrimitivePeek<{cint_type(type_.width, type_.sign)}, {endianness_to_cpp(type_.endianness)}>" + + def visit_float(self, type_: tir.Float) -> str: + return f"::tako::PrimitivePeek<{cfloat_type(type_.width)}, {endianness_to_cpp(type_.endianness)}>" + + def visit_array(self, type_: tir.Array) -> str: + return f"::tako::ArrayPeek<{type_.inner.accept(self)}, {type_.length}>" + + def visit_vector(self, type_: tir.Vector) -> str: + return f"::tako::VectorPeek<{type_.inner.accept(self)}>" + + def visit_list(self, type_: tir.List) -> str: + return f"::tako::ListPeek<{type_.inner.accept(self)}>" + + def visit_detached_variant(self, type_: tir.DetachedVariant) -> str: + return type_.variant.accept(self) + + def visit_virtual(self, type_: tir.Virtual) -> str: + return type_.inner.accept(self) + + def visit_struct(self, root: tir.Struct) -> str: + return self.namespace(root, PeekCppType.get_local_struct(root)) + + def visit_variant(self, root: tir.Variant) -> str: + return self.namespace(root, PeekCppType.get_local_variant(root)) + + def visit_enum(self, root: tir.Enum) -> str: + return self.namespace(root, PeekCppType.get_local_enum(root)) + + def namespace(self, type_: tir.RootType, local_name: str) -> str: + return qname_to_cpp( + protocol_namespace(type_.name.namespace()).with_name(local_name) + ) + + @dataclasses.dataclass class ViewCppType(tir.TypeVisitor[str]): @staticmethod diff --git a/python/test_types/milestone1_main.cc b/python/test_types/milestone1_main.cc index 2238486..ab82d23 100644 --- a/python/test_types/milestone1_main.cc +++ b/python/test_types/milestone1_main.cc @@ -1,5 +1,6 @@ #include -#include "core.hh" +#include +#include #include #include @@ -13,5 +14,10 @@ int main() { .three = 6 }; std::vector request_bytes = request.serialize(); + std::cout << "Hello\n"; + test_types::milestone1::PacketView view = test_types::milestone1::PacketView::render(request_bytes); + std::cout << view.one() << "\n"; + std::cout << view.three() << "\n"; + // parsed. } From 4087d520e1e64e952baae09c94c94d1c3be0f30f Mon Sep 17 00:00:00 2001 From: John Date: Thu, 6 Feb 2025 23:14:26 -0500 Subject: [PATCH 3/3] Pushing up the prototype of peeking --- README.md | 6 + .../test_types/milestone1/core_modified.hh | 505 ++++++++++++++++++ python/test_types/milestone1_main.cc | 29 +- 3 files changed, 536 insertions(+), 4 deletions(-) create mode 100644 python/milestone1/test_types/milestone1/core_modified.hh diff --git a/README.md b/README.md index c723c6a..67275c2 100644 --- a/README.md +++ b/README.md @@ -11,3 +11,9 @@ To build milestone1 cd build cmake --build . + +Purely as a point of interest, it seems that PrimitiveView::parse is only used when it's necessary as the slave key of some bigger type ( struct, etc. ) otherwise it always just renders. + +The parse function on some bigger type doesn't check primitive fields parse, just the complex ones. Although maybe these are functionally equivalent as long as the total size is there + +rendering still tries to parse those, but it just calls .value() diff --git a/python/milestone1/test_types/milestone1/core_modified.hh b/python/milestone1/test_types/milestone1/core_modified.hh new file mode 100644 index 0000000..98e3c1a --- /dev/null +++ b/python/milestone1/test_types/milestone1/core_modified.hh @@ -0,0 +1,505 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "tako/tako.hh" + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-parameter" +namespace test_types { +namespace milestone1 { +class IntegerType { +public: + ::std::int8_t contained; + ::gsl::span<::gsl::byte> serialize_into(::gsl::span<::gsl::byte> buf) const { + buf = ::tako::PrimitiveView<::std::int8_t, ::tako::Endianness::LITTLE>::serialize_into(contained, buf); + return buf; + } + static constexpr size_t SIZE_BYTES = 1; + using Buffer = ::std::array<::gsl::byte, 1>; + constexpr size_t size_bytes() const { + return SIZE_BYTES; + } + Buffer serialize() const { + Buffer result; + serialize_into(result); + return result; + } + bool operator ==(const IntegerType& _other) const { + return contained == _other.contained; + } + bool operator !=(const IntegerType& _other) const { + return !(*this == _other); + } +private: +}; +class IntegerTypeView { +public: + static constexpr size_t SIZE_BYTES = 1; + using Rendered = IntegerTypeView; + using Built = IntegerType; + static Built build(const Rendered& rendered) { + return Built { + .contained = ::tako::PrimitiveView<::std::int8_t, ::tako::Endianness::LITTLE>::build(rendered.contained()), + }; + } + Built build() const { + return build(*this); + } + static Rendered render(::gsl::span _buf) { + return Rendered { + _buf + }; + } + static ::tako::ParseResult parse(::gsl::span _buf) { + if (::tako::unsafe_subspan(_buf, 1).data() > _buf.end()) { + return ::tl::make_unexpected(::tako::ParseError::NOT_ENOUGH_DATA); + } + return ::tako::ParseResult(tl::in_place, + Rendered { + _buf + }, + ::tako::unsafe_subspan(_buf, 1) + ); + } + static gsl::span serialize_into(const Built& built, gsl::span buf) { + return built.serialize_into(buf); + } + static size_t size_bytes(const Built& built) { + return built.size_bytes(); + } + ::gsl::span raw_contained() const { + return ::tako::unsafe_subspan(_buf, 0); + } + ::tako::PrimitiveView<::std::int8_t, ::tako::Endianness::LITTLE>::Rendered contained() const { + return ::tako::PrimitiveView<::std::int8_t, ::tako::Endianness::LITTLE>::render(raw_contained()); + } + ::gsl::span backing_buffer() const { + return _buf; + } +private: + explicit IntegerTypeView(::gsl::span _cons_buf) :_buf{ _cons_buf }{} + ::gsl::span _buf; +}; + +class BigIntegerType { +public: + ::std::int32_t contained; + ::gsl::span<::gsl::byte> serialize_into(::gsl::span<::gsl::byte> buf) const { + buf = ::tako::PrimitiveView<::std::int32_t, ::tako::Endianness::LITTLE>::serialize_into(contained, buf); + return buf; + } + static constexpr size_t SIZE_BYTES = 4; + using Buffer = ::std::array<::gsl::byte, 4>; + constexpr size_t size_bytes() const { + return SIZE_BYTES; + } + Buffer serialize() const { + Buffer result; + serialize_into(result); + return result; + } + bool operator ==(const BigIntegerType& _other) const { + return contained == _other.contained; + } + bool operator !=(const BigIntegerType& _other) const { + return !(*this == _other); + } +private: +}; +class BigIntegerTypeView { +public: + static constexpr size_t SIZE_BYTES = 4; + using Rendered = BigIntegerTypeView; + using Built = BigIntegerType; + static Built build(const Rendered& rendered) { + return Built { + .contained = ::tako::PrimitiveView<::std::int32_t, ::tako::Endianness::LITTLE>::build(rendered.contained()), + }; + } + Built build() const { + return build(*this); + } + static Rendered render(::gsl::span _buf) { + return Rendered { + _buf + }; + } + static ::tako::ParseResult parse(::gsl::span _buf) { + if (::tako::unsafe_subspan(_buf, 4).data() > _buf.end()) { + return ::tl::make_unexpected(::tako::ParseError::NOT_ENOUGH_DATA); + } + return ::tako::ParseResult(tl::in_place, + Rendered { + _buf + }, + ::tako::unsafe_subspan(_buf, 4) + ); + } + static gsl::span serialize_into(const Built& built, gsl::span buf) { + return built.serialize_into(buf); + } + static size_t size_bytes(const Built& built) { + return built.size_bytes(); + } + ::gsl::span raw_contained() const { + return ::tako::unsafe_subspan(_buf, 0); + } + ::tako::PrimitiveView<::std::int32_t, ::tako::Endianness::LITTLE>::Rendered contained() const { + return ::tako::PrimitiveView<::std::int32_t, ::tako::Endianness::LITTLE>::render(raw_contained()); + } + ::gsl::span backing_buffer() const { + return _buf; + } +private: + explicit BigIntegerTypeView(::gsl::span _cons_buf) :_buf{ _cons_buf }{} + ::gsl::span _buf; +}; +class Two { +public: + using V = ::std::variant<::test_types::milestone1::IntegerType, ::test_types::milestone1::BigIntegerType>; + V value; + template ::value, T>::type> + Two(T&& t) : value{::std::forward(t)} {} + template + auto match(F&&... args) { + return accept(::tako::overloaded{::std::forward(args)...}); + } + template + auto match(F&&... args) const { + return accept(::tako::overloaded{::std::forward(args)...}); + } + template + auto accept(T&& visitor) { + return ::std::visit(::std::forward(visitor), value); + } + template + auto accept(T&& visitor) const { + return ::std::visit(::std::forward(visitor), value); + } + template + auto match_unify(F&&... args) { + return accept_unify(::tako::overloaded{::std::forward(args)...}); + } + template + auto match_unify(F&&... args) const { + return accept_unify(::tako::overloaded{::std::forward(args)...}); + } + template + auto accept_unify(T&& visitor) { + return ::std::visit(::tako::unify(::std::forward(visitor)), value); + } + template + auto accept_unify(T&& visitor) const { + return ::std::visit(::tako::unify(::std::forward(visitor)), value); + } + template + ::std::optional get() { + auto x = ::std::get_if(&value); + if (x) { + return x; + } else { + return ::std::nullopt; + } + } + template + ::std::optional get() const { + auto x = ::std::get_if(&value); + if (x) { + return x; + } else { + return ::std::nullopt; + } + } + ::std::int8_t tag() const { + return match( + [](const ::test_types::milestone1::IntegerType&) { return static_cast<::std::int8_t>(UINT8_C(0)); }, + [](const ::test_types::milestone1::BigIntegerType&) { return static_cast<::std::int8_t>(UINT8_C(1)); } + ); + } + bool operator ==(const ::test_types::milestone1::Two& other) const { + return value == other.value; + } + bool operator !=(const ::test_types::milestone1::Two& other) const { + return value != other.value; + } + gsl::span serialize_into(gsl::span buf) const { + return accept([&buf](auto&& x){ + return x.serialize_into(buf); + }); + } + size_t size_bytes() const { + return accept([](auto&& x){ + return x.size_bytes(); + }); + } +}; +class TwoView { +public: + using V = ::std::variant<::test_types::milestone1::IntegerTypeView, ::test_types::milestone1::BigIntegerTypeView>; + V value; + template ::value, T>::type> + TwoView(T&& t) : value{::std::forward(t)} {} + template + auto match(F&&... args) { + return accept(::tako::overloaded{::std::forward(args)...}); + } + template + auto match(F&&... args) const { + return accept(::tako::overloaded{::std::forward(args)...}); + } + template + auto accept(T&& visitor) { + return ::std::visit(::std::forward(visitor), value); + } + template + auto accept(T&& visitor) const { + return ::std::visit(::std::forward(visitor), value); + } + template + auto match_unify(F&&... args) { + return accept_unify(::tako::overloaded{::std::forward(args)...}); + } + template + auto match_unify(F&&... args) const { + return accept_unify(::tako::overloaded{::std::forward(args)...}); + } + template + auto accept_unify(T&& visitor) { + return ::std::visit(::tako::unify(::std::forward(visitor)), value); + } + template + auto accept_unify(T&& visitor) const { + return ::std::visit(::tako::unify(::std::forward(visitor)), value); + } + template + ::std::optional get() { + auto x = ::std::get_if(&value); + if (x) { + return x; + } else { + return ::std::nullopt; + } + } + template + ::std::optional get() const { + auto x = ::std::get_if(&value); + if (x) { + return x; + } else { + return ::std::nullopt; + } + } + ::std::int8_t tag() const { + return match( + [](const ::test_types::milestone1::IntegerTypeView&) { return static_cast<::std::int8_t>(UINT8_C(0)); }, + [](const ::test_types::milestone1::BigIntegerTypeView&) { return static_cast<::std::int8_t>(UINT8_C(1)); } + ); + } + using Rendered = TwoView; + static Rendered render(gsl::span buf, ::std::int8_t tag) { + if (tag == static_cast<::std::int8_t>(UINT8_C(0))) { return ::test_types::milestone1::IntegerTypeView::render(buf); } + if (tag == static_cast<::std::int8_t>(UINT8_C(1))) { return ::test_types::milestone1::BigIntegerTypeView::render(buf); } + throw ::std::domain_error("input had illegal value"); + } + using Built = ::test_types::milestone1::Two; + static Built build(const Rendered& rendered) { + return rendered.match( + [](const ::test_types::milestone1::IntegerTypeView& x) -> Built { return ::test_types::milestone1::IntegerTypeView::build(x); }, + [](const ::test_types::milestone1::BigIntegerTypeView& x) -> Built { return ::test_types::milestone1::BigIntegerTypeView::build(x); } + ); + } + Built build() const { + return build(*this); + } + static ::tako::ParseResult parse(::gsl::span buf, ::std::int8_t tag) { + if (tag == static_cast<::std::int8_t>(UINT8_C(0))) { + auto maybe = ::test_types::milestone1::IntegerTypeView::parse(buf); + if (!maybe) { + return tl::make_unexpected(maybe.error()); + } else { + return ::tako::ParseResult(tl::in_place, + ::std::move(maybe->rendered), + maybe->tail + ); + } + } + if (tag == static_cast<::std::int8_t>(UINT8_C(1))) { + auto maybe = ::test_types::milestone1::BigIntegerTypeView::parse(buf); + if (!maybe) { + return tl::make_unexpected(maybe.error()); + } else { + return ::tako::ParseResult(tl::in_place, + ::std::move(maybe->rendered), + maybe->tail + ); + } + } + return ::tl::make_unexpected(::tako::ParseError::MALFORMED); + } + static gsl::span serialize_into(const Built& built, gsl::span buf) { + return built.serialize_into(buf); + } + static size_t size_bytes(const Built& built) { + return built.size_bytes(); + } +}; +class Packet { +public: + ::std::int32_t one; + ::test_types::milestone1::Two two; + ::std::uint32_t three; + ::gsl::span<::gsl::byte> serialize_into(::gsl::span<::gsl::byte> buf) const { + buf = ::tako::PrimitiveView<::std::int32_t, ::tako::Endianness::LITTLE>::serialize_into(one, buf); + buf = ::tako::PrimitiveView<::std::int8_t, ::tako::Endianness::LITTLE>::serialize_into(two.tag(), buf); + buf = ::test_types::milestone1::TwoView::serialize_into(two, buf); + buf = ::tako::PrimitiveView<::std::uint32_t, ::tako::Endianness::LITTLE>::serialize_into(three, buf); + return buf; + } + using Buffer = ::std::vector<::gsl::byte>; + size_t size_bytes() const { + return 9 + ::test_types::milestone1::TwoView::size_bytes(two); + } + Buffer serialize() const { + Buffer result{size_bytes()}; + serialize_into(result); + return result; + } + bool operator ==(const Packet& _other) const { + return one == _other.one && two == _other.two && three == _other.three; + } + bool operator !=(const Packet& _other) const { + return !(*this == _other); + } +private: +}; +class PacketPeek { +public: + explicit PacketPeek(::gsl::span _cons_buf, ::tako::ParseResult<::test_types::milestone1::TwoView> _cons_info_two, size_t _good_bytes) :_buf{ _cons_buf },_info_two{ _cons_info_two },good_bytes{ _good_bytes }{} + ::gsl::span _buf; + ::tako::ParseResult<::test_types::milestone1::TwoView> _info_two; + size_t good_bytes; +}; +class PacketView { +public: + using Peek = PacketPeek; + using Rendered = PacketView; + using Built = Packet; + static Built build(const Rendered& rendered) { + return Built { + .one = ::tako::PrimitiveView<::std::int32_t, ::tako::Endianness::LITTLE>::build(rendered.one()), + .two = ::test_types::milestone1::TwoView::build(rendered.two()), + .three = ::tako::PrimitiveView<::std::uint32_t, ::tako::Endianness::LITTLE>::build(rendered.three()), + }; + } + Built build() const { + return build(*this); + } + + + // Basically the same as parse so far + static Peek peek(::gsl::span _buf) { + // For every simple field, do something like this: + // Distinct case from parse, we have to check every field. + // parse assumes it's fine to just check the next one because if that one + // is bad this one certainly is, if it's good this one is + if (::tako::unsafe_subspan(_buf, 4).data() > _buf.end()) { + return Peek { _buf, tl::make_unexpected(::tako::ParseError::MALFORMED), 0 }; + } + // For complex fields, we have to try to parse + auto two_injected_key_ = ::tako::PrimitiveView<::std::int8_t, ::tako::Endianness::LITTLE>::parse(::tako::unsafe_subspan(_buf, 4)); + if (!two_injected_key_) { + return Peek { _buf, tl::make_unexpected(::tako::ParseError::MALFORMED), 4 }; + } + // We grabbed the i8 type ID, now we grab the rest + auto two = ::test_types::milestone1::TwoView::parse(::tako::unsafe_subspan(_buf, 5), two_injected_key_->rendered); + if (!two) { + return Peek { _buf, tl::make_unexpected(::tako::ParseError::MALFORMED), 4 }; + } + // Check for data at the end + if (::tako::unsafe_subspan(two->tail, 4).data() > _buf.end()) { + return Peek { _buf, tl::make_unexpected(::tako::ParseError::MALFORMED), two->tail.data() - _buf.begin()}; + } + return Peek { _buf, tl::make_unexpected(::tako::ParseError::MALFORMED), ::tako::unsafe_subspan(two->tail, 4).data() - _buf.begin()}; + } + + + static Rendered render(::gsl::span _buf) { + auto two = ::test_types::milestone1::TwoView::parse(::tako::unsafe_subspan(_buf, 5), ::tako::PrimitiveView<::std::int8_t, ::tako::Endianness::LITTLE>::render(::tako::unsafe_subspan(_buf, 4))).value(); + return Rendered { + _buf, + ::std::move(two) + }; + } + + static ::tako::ParseResult parse(::gsl::span _buf) { + auto two_injected_key_ = ::tako::PrimitiveView<::std::int8_t, ::tako::Endianness::LITTLE>::parse(::tako::unsafe_subspan(_buf, 4)); + if (!two_injected_key_) { + return ::tl::make_unexpected(two_injected_key_.error()); + } + auto two = ::test_types::milestone1::TwoView::parse(::tako::unsafe_subspan(_buf, 5), two_injected_key_->rendered); + if (!two) { + return ::tl::make_unexpected(two.error()); + } + if (::tako::unsafe_subspan(two->tail, 4).data() > _buf.end()) { + return ::tl::make_unexpected(::tako::ParseError::NOT_ENOUGH_DATA); + } + return ::tako::ParseResult(tl::in_place, + Rendered { + _buf, + ::std::move(*two) + }, + ::tako::unsafe_subspan(two->tail, 4) + ); + } + static gsl::span serialize_into(const Built& built, gsl::span buf) { + return built.serialize_into(buf); + } + static size_t size_bytes(const Built& built) { + return built.size_bytes(); + } + ::gsl::span raw_one() const { + return ::tako::unsafe_subspan(_buf, 0); + } + ::gsl::span raw_two_injected_key_() const { + return ::tako::unsafe_subspan(_buf, 4); + } + ::gsl::span raw_two() const { + return ::tako::unsafe_subspan(_buf, 5); + } + ::gsl::span raw_three() const { + return ::tako::unsafe_subspan(_info_two.tail, 0); + } + ::tako::PrimitiveView<::std::int32_t, ::tako::Endianness::LITTLE>::Rendered one() const { + return ::tako::PrimitiveView<::std::int32_t, ::tako::Endianness::LITTLE>::render(raw_one()); + } + ::tako::PrimitiveView<::std::int8_t, ::tako::Endianness::LITTLE>::Rendered two_injected_key_() const { + return ::tako::PrimitiveView<::std::int8_t, ::tako::Endianness::LITTLE>::render(raw_two_injected_key_()); + } + ::test_types::milestone1::TwoView::Rendered two() const { + return ::test_types::milestone1::TwoView::render(raw_two(), two_injected_key_()); + } + ::tako::PrimitiveView<::std::uint32_t, ::tako::Endianness::LITTLE>::Rendered three() const { + return ::tako::PrimitiveView<::std::uint32_t, ::tako::Endianness::LITTLE>::render(raw_three()); + } + ::gsl::span backing_buffer() const { + return _buf; + } +private: + explicit PacketView(::gsl::span _cons_buf, ::tako::ParseInfo<::test_types::milestone1::TwoView> _cons_info_two) :_buf{ _cons_buf },_info_two{ _cons_info_two }{} + ::gsl::span _buf; + ::tako::ParseInfo<::test_types::milestone1::TwoView> _info_two; +}; +} +} +#pragma GCC diagnostic pop diff --git a/python/test_types/milestone1_main.cc b/python/test_types/milestone1_main.cc index ab82d23..3f65080 100644 --- a/python/test_types/milestone1_main.cc +++ b/python/test_types/milestone1_main.cc @@ -1,6 +1,8 @@ +#include #include +#include #include -#include +#include #include #include @@ -13,11 +15,30 @@ int main() { }, .three = 6 }; + // Expected sizing + // one = 4 + // two = 1 ( tag ) + 4 + // three = 4 + // 13 total std::vector request_bytes = request.serialize(); - std::cout << "Hello\n"; test_types::milestone1::PacketView view = test_types::milestone1::PacketView::render(request_bytes); - std::cout << view.one() << "\n"; - std::cout << view.three() << "\n"; // parsed. + test_types::milestone1::PacketPeek peek = test_types::milestone1::PacketView::peek(request_bytes); + assert(13 == peek.good_bytes); + + auto corrupt_request = test_types::milestone1::Packet { + .one = 5, + .two = test_types::milestone1::BigIntegerType { + .contained = 3, + }, + .three = 6 + }; + std::vector corrupt_request_bytes = request.serialize(); + assert(int(corrupt_request_bytes[4]) == 1); + // Bad tag on the wire!! + corrupt_request_bytes[4] = gsl::byte{0x03}; + test_types::milestone1::PacketPeek corrupt_peek = test_types::milestone1::PacketView::peek(corrupt_request_bytes); + assert(4 == corrupt_peek.good_bytes); + std::cout << "Successfully ran test\n"; }