diff --git a/libuavcan/dsdl_compiler/data_type_template.hpp b/libuavcan/dsdl_compiler/data_type_template.hpp index 4ff5fb75dd..35cc99e258 100644 --- a/libuavcan/dsdl_compiler/data_type_template.hpp +++ b/libuavcan/dsdl_compiler/data_type_template.hpp @@ -198,8 +198,6 @@ ${define_out_of_line_constants(t.cpp_type_name, t.constants)} UAVCAN_PACKED_END #endif -// TODO Stream operator - % if t.has_default_dtid: const ::uavcan::DefaultDataTypeRegistrator< ${t.cpp_type_name} > _uavcan_gdtr_registrator_${t.cpp_type_name}; % else: @@ -213,3 +211,56 @@ typedef ${t.cpp_type_name} ${t.short_name}; } // Namespace ${nsc} % endfor +namespace uavcan +{ + +<%def name="define_yaml_streamer(type_name, fields)"> +template <> +struct YamlStreamer< ${type_name} > +{ + template + static void stream(Stream& s, ${type_name}::ParameterType obj, const int level) + { +% for idx,a in enumerate(fields): + % if idx == 0: + if (level > 0) + { + s << '\n'; + for (int pos = 0; pos < level; pos++) + s << " "; + } + % else: + s << '\n'; + for (int pos = 0; pos < level; pos++) + s << " "; + % endif + s << "${a.name}: "; + YamlStreamer< ${type_name}::FieldTypes::${a.name} >::stream(s, obj.${a.name}, level + 1); +% endfor + } +}; + +% if t.kind == t.KIND_SERVICE: +${define_yaml_streamer(t.cpp_full_type_name + '::Request', t.request_fields)} +${define_yaml_streamer(t.cpp_full_type_name + '::Response', t.response_fields)} +% else: +${define_yaml_streamer(t.cpp_full_type_name, t.fields)} +% endif + +} + +<%def name="define_streaming_operator(type_name)"> +template +inline Stream& operator<<(Stream& s, ${type_name}::ParameterType obj) +{ + ::uavcan::YamlStreamer< ${type_name} >::stream(s, obj, 0); + return s; +} + +% if t.kind == t.KIND_SERVICE: +${define_streaming_operator(t.cpp_full_type_name + '::Request')} +${define_streaming_operator(t.cpp_full_type_name + '::Response')} +% else: +${define_streaming_operator(t.cpp_full_type_name)} +% endif + diff --git a/libuavcan/include/uavcan/internal/marshal/array.hpp b/libuavcan/include/uavcan/internal/marshal/array.hpp index 497cfe15c4..0697618fe8 100644 --- a/libuavcan/include/uavcan/internal/marshal/array.hpp +++ b/libuavcan/include/uavcan/internal/marshal/array.hpp @@ -115,7 +115,11 @@ class ArrayImpl : public StaticIf >::Result Base; public: - enum { IsStringLike = IsIntegerSpec::Result && (T::MaxBitLen == 8 || T::MaxBitLen == 7) }; + enum + { + IsStringLike = IsIntegerSpec::Result && (T::MaxBitLen == 8 || T::MaxBitLen == 7) + && (ArrayMode == ArrayModeDynamic) + }; private: typedef typename StorageType::Type BufferType[MaxSize + (IsStringLike ? 1 : 0)]; @@ -410,4 +414,114 @@ inline bool operator!=(const R& rhs, const Array& lhs) return lhs.operator!=(rhs); } + +template +class YamlStreamer > +{ + typedef Array ArrayType; + + template + static void streamPrimitives(Stream& s, const ArrayType& array) + { + s << '['; + for (std::size_t i = 0; i < array.size(); i++) + { + YamlStreamer::stream(s, array.at(i), 0); + if ((i + 1) < array.size()) + s << ", "; + } + s << ']'; + } + + template + static void streamCharacters(Stream& s, const ArrayType& array) + { + s << '"'; + for (std::size_t i = 0; i < array.size(); i++) + { + const unsigned int c = array.at(i); + if (c < 32 || c > 126) // Non-printable characters + { + char nibbles[2] = {(c >> 4) & 0xF, c & 0xF}; + for (int i = 0; i < 2; i++) + { + nibbles[i] += '0'; + if (nibbles[i] > '9') + nibbles[i] += 'A' - '9' - 1; + } + s << "\\x" << nibbles[0] << nibbles[1]; + } + else + { + if (c == '"' || c == '\\') // YAML requires to escape these two + s << '\\'; + s << char(c); + } + } + s << '"'; + } + + struct SelectorStringLike { }; + struct SelectorPrimitives { }; + struct SelectorObjects { }; + + template + static void genericStreamImpl(Stream& s, const ArrayType& array, int, SelectorStringLike) + { + bool printable_only = true; + for (int i = 0; i < array.size(); i++) + { + if (array[i] < 32 || array[i] > 126) + { + printable_only = false; + break; + } + } + if (printable_only) + { + streamCharacters(s, array); + } + else + { + streamPrimitives(s, array); + s << " # "; + streamCharacters(s, array); + } + } + + template + static void genericStreamImpl(Stream& s, const ArrayType& array, int, SelectorPrimitives) + { + streamPrimitives(s, array); + } + + template + static void genericStreamImpl(Stream& s, const ArrayType& array, int level, SelectorObjects) + { + if (array.empty()) + { + s << "[]"; + return; + } + for (std::size_t i = 0; i < array.size(); i++) + { + s << '\n'; + for (int pos = 0; pos < level; pos++) + s << " "; + s << "- "; + YamlStreamer::stream(s, array.at(i), level + 1); + } + } + +public: + template + static void stream(Stream& s, const ArrayType& array, int level) + { + typedef typename StaticIf::Result, SelectorPrimitives, + SelectorObjects>::Result >::Result Type; + genericStreamImpl(s, array, level, Type()); + } +}; + } diff --git a/libuavcan/include/uavcan/internal/marshal/float_spec.hpp b/libuavcan/include/uavcan/internal/marshal/float_spec.hpp index 9ff1c84554..265b7fc9e2 100644 --- a/libuavcan/include/uavcan/internal/marshal/float_spec.hpp +++ b/libuavcan/include/uavcan/internal/marshal/float_spec.hpp @@ -102,6 +102,7 @@ public: enum { BitLen = BitLen_ }; enum { MinBitLen = BitLen }; enum { MaxBitLen = BitLen }; + enum { IsPrimitive = 1 }; typedef typename NativeFloatSelector::Type StorageType; @@ -157,4 +158,17 @@ private: } }; + +template +struct YamlStreamer > +{ + typedef typename FloatSpec::StorageType StorageType; + + template + static void stream(Stream& s, const StorageType value, int) + { + s << value; + } +}; + } diff --git a/libuavcan/include/uavcan/internal/marshal/integer_spec.hpp b/libuavcan/include/uavcan/internal/marshal/integer_spec.hpp index b79f94735e..073adf0d78 100644 --- a/libuavcan/include/uavcan/internal/marshal/integer_spec.hpp +++ b/libuavcan/include/uavcan/internal/marshal/integer_spec.hpp @@ -19,14 +19,14 @@ enum Signedness { SignednessUnsigned, SignednessSigned }; template class IntegerSpec { - enum { IsSigned = Signedness == SignednessSigned }; - struct ErrorNoSuchInteger; public: + enum { IsSigned = Signedness == SignednessSigned }; enum { BitLen = BitLen_ }; enum { MinBitLen = BitLen }; enum { MaxBitLen = BitLen }; + enum { IsPrimitive = 1 }; typedef typename StaticIf<(BitLen <= 8), typename StaticIf::Result, typename StaticIf<(BitLen <= 16), typename StaticIf::Result, @@ -116,4 +116,21 @@ struct IsIntegerSpec { enum { Result = 0 }; }; template struct IsIntegerSpec > { enum { Result = 1 }; }; + +template +struct YamlStreamer > +{ + typedef IntegerSpec RawType; + typedef typename RawType::StorageType StorageType; + + template + static void stream(Stream& s, const StorageType value, int) + { + // Get rid of character types - we want its integer representation, not ASCII code + typedef typename StaticIf<(sizeof(StorageType) >= sizeof(int)), StorageType, + typename StaticIf::Result >::Result TempType; + s << TempType(value); + } +}; + } diff --git a/libuavcan/include/uavcan/internal/marshal/type_util.hpp b/libuavcan/include/uavcan/internal/marshal/type_util.hpp index 93515b78bb..ad2c555834 100644 --- a/libuavcan/include/uavcan/internal/marshal/type_util.hpp +++ b/libuavcan/include/uavcan/internal/marshal/type_util.hpp @@ -29,4 +29,25 @@ struct StorageType::Type> typedef typename T::StorageType Type; }; + +template +class IsPrimitiveType +{ + typedef char Yes; + struct No { Yes dummy[8]; }; + + template + static typename EnableIf::Type test(int); + + template + static No test(...); + +public: + enum { Result = sizeof(test(0)) == sizeof(Yes) }; +}; + + +template +struct YamlStreamer; + } diff --git a/libuavcan/test/dsdl_test/dsdl_uavcan_compilability.cpp b/libuavcan/test/dsdl_test/dsdl_uavcan_compilability.cpp index d15307e191..57bed85f3e 100644 --- a/libuavcan/test/dsdl_test/dsdl_uavcan_compilability.cpp +++ b/libuavcan/test/dsdl_test/dsdl_uavcan_compilability.cpp @@ -3,6 +3,7 @@ */ #include + #include #include #include @@ -29,3 +30,92 @@ #include #include #include + +#include + + +TEST(Dsdl, Streaming) +{ + std::ostringstream os; + + uavcan::mavlink::Message mavlink; + os << mavlink << std::endl << "==========" << std::endl; + + mavlink.compid = 12; + mavlink.seq = 42; + mavlink.payload = "Here goes payload"; + os << mavlink << std::endl << "==========" << std::endl; + + uavcan::protocol::GetNodeInfo::Response get_node_info_rsp; + os << get_node_info_rsp << std::endl << "==========" << std::endl; + + root_ns_a::Deep ps; + ps.a.resize(2); + os << ps << std::endl << "==========" << std::endl; + + static const std::string Reference = "seq: 0\n" + "sysid: 0\n" + "compid: 0\n" + "msgid: 0\n" + "payload: \"\"\n" + "==========\n" + "seq: 42\n" + "sysid: 0\n" + "compid: 12\n" + "msgid: 0\n" + "payload: \"Here goes payload\"\n" + "==========\n" + "software_version: \n" + " major: 0\n" + " minor: 0\n" + "hardware_version: \n" + " major: 0\n" + " minor: 0\n" + "uavcan_version: \n" + " major: 0\n" + " minor: 0\n" + "status: \n" + " uptime_sec: 0\n" + " status_code: 0\n" + "name: \"\"\n" + "==========\n" + "c: 0\n" + "str: \"\"\n" + "a: \n" + " - \n" + " scalar: 0\n" + " vector: \n" + " - \n" + " vector: [0, 0, 0, 0]\n" + " bools: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]\n" + " - \n" + " vector: [0, 0, 0, 0]\n" + " bools: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]\n" + " - \n" + " vector: [0, 0, 0, 0]\n" + " bools: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]\n" + " - \n" + " scalar: 0\n" + " vector: \n" + " - \n" + " vector: [0, 0, 0, 0]\n" + " bools: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]\n" + " - \n" + " vector: [0, 0, 0, 0]\n" + " bools: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]\n" + " - \n" + " vector: [0, 0, 0, 0]\n" + " bools: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]\n" + "b: \n" + " - \n" + " vector: [0, 0, 0, 0]\n" + " bools: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]\n" + " - \n" + " vector: [0, 0, 0, 0]\n" + " bools: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]\n" + " - \n" + " vector: [0, 0, 0, 0]\n" + " bools: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]\n" + "==========\n"; + ASSERT_EQ(Reference, os.str()); +} diff --git a/libuavcan/test/dsdl_test/root_ns_a/A.uavcan b/libuavcan/test/dsdl_test/root_ns_a/A.uavcan new file mode 100644 index 0000000000..964e96e285 --- /dev/null +++ b/libuavcan/test/dsdl_test/root_ns_a/A.uavcan @@ -0,0 +1,8 @@ +# +# Nested type. +# Energy and capacity are expressed in watt hours (Joules are infeasible because of range limitations). +# Unknown values should be represented as NAN. +# + +float32 scalar +B[3] vector diff --git a/libuavcan/test/dsdl_test/root_ns_a/B.uavcan b/libuavcan/test/dsdl_test/root_ns_a/B.uavcan new file mode 100644 index 0000000000..751556b581 --- /dev/null +++ b/libuavcan/test/dsdl_test/root_ns_a/B.uavcan @@ -0,0 +1,2 @@ +float64[4] vector +bool[16] bools \ No newline at end of file diff --git a/libuavcan/test/dsdl_test/root_ns_a/Deep.uavcan b/libuavcan/test/dsdl_test/root_ns_a/Deep.uavcan new file mode 100644 index 0000000000..a45b09c3f0 --- /dev/null +++ b/libuavcan/test/dsdl_test/root_ns_a/Deep.uavcan @@ -0,0 +1,8 @@ +# +# Info for all battery packs of the primary power supply. +# + +bool c +uint8[<64] str +A[<3] a +B[3] b diff --git a/libuavcan/test/marshal/array.cpp b/libuavcan/test/marshal/array.cpp index ab8a4d9ae4..5517079ef9 100644 --- a/libuavcan/test/marshal/array.cpp +++ b/libuavcan/test/marshal/array.cpp @@ -710,3 +710,59 @@ TEST(Array, Strings) ASSERT_STREQ("Our sun is dying. 123456-789", a8.c_str()); ASSERT_STREQ("456", a7.c_str()); } + + +TEST(Array, FlatStreaming) +{ + typedef Array, ArrayModeDynamic, 32> A8D; + typedef Array, ArrayModeDynamic, 16> AF16D; + typedef Array, ArrayModeStatic, 3> AF16S; + + A8D a1; + a1 = "12\n3\x44\xa5\xde\xad\x79"; + uavcan::YamlStreamer::stream(std::cout, a1, 0); + std::cout << std::endl; + + A8D a2; + a2 = "Hello"; + uavcan::YamlStreamer::stream(std::cout, a2, 0); + std::cout << std::endl; + + AF16D af16d1; + af16d1.push_back(1.23); + af16d1.push_back(4.56); + uavcan::YamlStreamer::stream(std::cout, af16d1, 0); + std::cout << std::endl; + + AF16D af16d2; + uavcan::YamlStreamer::stream(std::cout, af16d2, 0); + std::cout << std::endl; + + AF16S af16s; + uavcan::YamlStreamer::stream(std::cout, af16s, 0); + std::cout << std::endl; +} + + +TEST(Array, MultidimensionalStreaming) +{ + typedef Array, ArrayModeDynamic, 16> Float16Array; + typedef Array TwoDimensional; + typedef Array ThreeDimensional; + + ThreeDimensional threedee; + threedee.resize(3); + for (int x = 0; x < threedee.size(); x++) + { + threedee[x].resize(3); + for (int y = 0; y < threedee[x].size(); y++) + { + threedee[x][y].resize(3); + for (int z = 0; z < threedee[x][y].size(); z++) + threedee[x][y][z] = 1.0 / (x + y + z + 1.0); + } + } + + uavcan::YamlStreamer::stream(std::cout, threedee, 0); + std::cout << std::endl; +}