libs/kdl/src/eu/kdl/kdl.cc
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #include "eu/kdl/kdl.h" | ||
| 2 | |||
| 3 | #include <kdl/kdl.h> | ||
| 4 | |||
| 5 | namespace eu::kdl { | ||
| 6 | |||
| 7 | // internal helper functions | ||
| 8 | namespace { | ||
| 9 | ✗ | std::string_view to_u8string_view(kdl_str const& s) | |
| 10 | { | ||
| 11 | ✗ | return std::string_view{reinterpret_cast<char const*>(s.data), s.len}; | |
| 12 | } | ||
| 13 | |||
| 14 | ✗ | kdl_str to_kdl_str(std::string_view s) | |
| 15 | { | ||
| 16 | ✗ | return kdl_str{reinterpret_cast<char const*>(s.data()), s.size()}; | |
| 17 | } | ||
| 18 | |||
| 19 | ✗ | std::variant<long long, double, std::string> kdl_number_to_variant(kdl_number const& n) | |
| 20 | { | ||
| 21 | ✗ | switch (n.type) { | |
| 22 | ✗ | case KDL_NUMBER_TYPE_INTEGER: | |
| 23 | ✗ | return n.integer; | |
| 24 | ✗ | case KDL_NUMBER_TYPE_FLOATING_POINT: | |
| 25 | ✗ | return n.floating_point; | |
| 26 | ✗ | case KDL_NUMBER_TYPE_STRING_ENCODED: | |
| 27 | ✗ | return std::string{to_u8string_view(n.string)}; | |
| 28 | ✗ | default: | |
| 29 | ✗ | throw std::logic_error("invalid kdl_number"); | |
| 30 | } | ||
| 31 | } | ||
| 32 | |||
| 33 | ✗ | std::variant<std::monostate, bool, Number, std::string> kdl_value_to_variant(kdl_value const& val) | |
| 34 | { | ||
| 35 | ✗ | switch (val.type) { | |
| 36 | ✗ | case KDL_TYPE_NULL: | |
| 37 | ✗ | return std::monostate{}; | |
| 38 | ✗ | case KDL_TYPE_BOOLEAN: | |
| 39 | ✗ | return val.boolean; | |
| 40 | ✗ | case KDL_TYPE_NUMBER: | |
| 41 | ✗ | return Number{val.number}; | |
| 42 | ✗ | case KDL_TYPE_STRING: | |
| 43 | ✗ | return std::string{to_u8string_view(val.string)}; | |
| 44 | ✗ | default: | |
| 45 | ✗ | throw std::logic_error("invalid kdl_value"); | |
| 46 | } | ||
| 47 | } | ||
| 48 | |||
| 49 | ✗ | void emit_nodes(kdl_emitter* emitter, std::vector<Node> const& nodes) | |
| 50 | { | ||
| 51 | ✗ | for (const auto& node : nodes) { | |
| 52 | ✗ | if (node.type_annotation().has_value()) { | |
| 53 | ✗ | if (!kdl_emit_node_with_type( | |
| 54 | ✗ | emitter, to_kdl_str(*node.type_annotation()), to_kdl_str(node.name()))) | |
| 55 | ✗ | throw EmitterError{}; | |
| 56 | } else { | ||
| 57 | ✗ | if (!kdl_emit_node(emitter, to_kdl_str(node.name()))) throw EmitterError{}; | |
| 58 | } | ||
| 59 | |||
| 60 | ✗ | for (const auto& arg : node.args()) { | |
| 61 | ✗ | auto v = (kdl_value)arg; | |
| 62 | ✗ | if (!kdl_emit_arg(emitter, &v)) throw EmitterError{}; | |
| 63 | } | ||
| 64 | |||
| 65 | ✗ | for (const auto& [key, value] : node.properties()) { | |
| 66 | ✗ | auto v = (kdl_value)value; | |
| 67 | ✗ | if (!kdl_emit_property(emitter, to_kdl_str(key), &v)) throw EmitterError{}; | |
| 68 | } | ||
| 69 | |||
| 70 | ✗ | if (!node.children().empty()) { | |
| 71 | ✗ | if (!kdl_start_emitting_children(emitter)) throw EmitterError{}; | |
| 72 | ✗ | emit_nodes(emitter, node.children()); | |
| 73 | ✗ | if (!kdl_finish_emitting_children(emitter)) throw EmitterError{}; | |
| 74 | } | ||
| 75 | } | ||
| 76 | ✗ | } | |
| 77 | |||
| 78 | } // namespace | ||
| 79 | |||
| 80 | ✗ | ParseError::ParseError(kdl_str const& msg) : m_msg{msg.data, msg.len} {} | |
| 81 | |||
| 82 | ✗ | Number::Number(kdl_number const& n) : m_value{kdl_number_to_variant(n)} {} | |
| 83 | |||
| 84 | ✗ | Number::operator kdl_number() const | |
| 85 | { | ||
| 86 | kdl_number result; | ||
| 87 | ✗ | std::visit( | |
| 88 | ✗ | [&result](const auto& n) { | |
| 89 | using T = std::decay_t<decltype(n)>; | ||
| 90 | if constexpr (std::is_same_v<T, long long>) { | ||
| 91 | ✗ | result.type = KDL_NUMBER_TYPE_INTEGER; | |
| 92 | ✗ | result.integer = n; | |
| 93 | } else if constexpr (std::is_same_v<T, double>) { | ||
| 94 | ✗ | result.type = KDL_NUMBER_TYPE_FLOATING_POINT; | |
| 95 | ✗ | result.floating_point = n; | |
| 96 | } else if constexpr (std::is_same_v<T, std::string>) { | ||
| 97 | ✗ | result.type = KDL_NUMBER_TYPE_STRING_ENCODED; | |
| 98 | ✗ | result.string = to_kdl_str(n); | |
| 99 | } else { | ||
| 100 | throw std::logic_error("incomplete visit"); | ||
| 101 | } | ||
| 102 | ✗ | }, | |
| 103 | ✗ | m_value); | |
| 104 | ✗ | return result; | |
| 105 | } | ||
| 106 | |||
| 107 | ✗ | Value::Value(kdl_value const& val) : m_value(kdl_value_to_variant(val)) | |
| 108 | { | ||
| 109 | ✗ | if (val.type_annotation.data != nullptr) { | |
| 110 | ✗ | set_type_annotation(to_u8string_view(val.type_annotation)); | |
| 111 | } | ||
| 112 | ✗ | } | |
| 113 | |||
| 114 | ✗ | Value Value::from_string(std::string_view s) | |
| 115 | { | ||
| 116 | ✗ | std::string doc_text = "- "; | |
| 117 | ✗ | doc_text.append(s); | |
| 118 | ✗ | auto doc = parse(doc_text); | |
| 119 | ✗ | if (doc.nodes().size() == 1) { | |
| 120 | ✗ | auto const& node = doc.nodes()[0]; | |
| 121 | ✗ | if (node.args().size() == 1 && node.properties().empty() && node.children().empty()) { | |
| 122 | ✗ | return node.args()[0]; | |
| 123 | } | ||
| 124 | } | ||
| 125 | ✗ | throw ParseError("Not a single value"); | |
| 126 | ✗ | } | |
| 127 | |||
| 128 | ✗ | Value::operator kdl_value() const | |
| 129 | { | ||
| 130 | kdl_value result; | ||
| 131 | ✗ | std::visit( | |
| 132 | ✗ | [&result](const auto& v) { | |
| 133 | using T = std::decay_t<decltype(v)>; | ||
| 134 | if constexpr (std::is_same_v<T, bool>) { | ||
| 135 | ✗ | result.type = KDL_TYPE_BOOLEAN; | |
| 136 | ✗ | result.boolean = v; | |
| 137 | } else if constexpr (std::is_same_v<T, Number>) { | ||
| 138 | ✗ | result.type = KDL_TYPE_NUMBER; | |
| 139 | ✗ | result.number = (kdl_number)v; | |
| 140 | } else if constexpr (std::is_same_v<T, std::string>) { | ||
| 141 | ✗ | result.type = KDL_TYPE_STRING; | |
| 142 | ✗ | result.string = to_kdl_str(v); | |
| 143 | } else { | ||
| 144 | ✗ | result.type = KDL_TYPE_NULL; | |
| 145 | } | ||
| 146 | ✗ | }, | |
| 147 | ✗ | m_value); | |
| 148 | ✗ | if (type_annotation().has_value()) { | |
| 149 | ✗ | result.type_annotation = to_kdl_str(*type_annotation()); | |
| 150 | } else { | ||
| 151 | ✗ | result.type_annotation = {nullptr, 0}; | |
| 152 | } | ||
| 153 | ✗ | return result; | |
| 154 | } | ||
| 155 | |||
| 156 | ✗ | Document Document::read_from(kdl_parser* parser) | |
| 157 | { | ||
| 158 | ✗ | Document doc; | |
| 159 | ✗ | auto* node_list = &doc.nodes(); | |
| 160 | ✗ | Node* current_node = nullptr; | |
| 161 | ✗ | std::vector<Node*> stack; | |
| 162 | |||
| 163 | while (true) { | ||
| 164 | ✗ | auto* ev = kdl_parser_next_event(parser); | |
| 165 | |||
| 166 | ✗ | switch (ev->event) { | |
| 167 | ✗ | case KDL_EVENT_EOF: | |
| 168 | ✗ | return doc; | |
| 169 | ✗ | case KDL_EVENT_PARSE_ERROR: | |
| 170 | ✗ | throw ParseError(ev->value.string); | |
| 171 | ✗ | case KDL_EVENT_START_NODE: { | |
| 172 | ✗ | auto name = to_u8string_view(ev->name); | |
| 173 | ✗ | if (ev->value.type_annotation.data != nullptr) { | |
| 174 | ✗ | auto ta = to_u8string_view(ev->value.type_annotation); | |
| 175 | ✗ | current_node = &node_list->emplace_back(ta, name); | |
| 176 | } else { | ||
| 177 | ✗ | current_node = &node_list->emplace_back(name); | |
| 178 | } | ||
| 179 | ✗ | node_list = ¤t_node->children(); | |
| 180 | ✗ | stack.push_back(current_node); | |
| 181 | ✗ | break; | |
| 182 | } | ||
| 183 | ✗ | case KDL_EVENT_END_NODE: | |
| 184 | ✗ | stack.pop_back(); | |
| 185 | ✗ | if (stack.empty()) { | |
| 186 | ✗ | current_node = nullptr; | |
| 187 | ✗ | node_list = &doc.nodes(); | |
| 188 | } else { | ||
| 189 | ✗ | current_node = stack.back(); | |
| 190 | ✗ | node_list = ¤t_node->children(); | |
| 191 | } | ||
| 192 | ✗ | break; | |
| 193 | ✗ | case KDL_EVENT_ARGUMENT: | |
| 194 | ✗ | current_node->args().emplace_back(Value{ev->value}); | |
| 195 | ✗ | break; | |
| 196 | ✗ | case KDL_EVENT_PROPERTY: | |
| 197 | ✗ | current_node->properties()[std::string{to_u8string_view(ev->name)}] = Value{ev->value}; | |
| 198 | ✗ | break; | |
| 199 | ✗ | default: | |
| 200 | ✗ | throw std::logic_error("Invalid event from kdl_parser"); | |
| 201 | } | ||
| 202 | ✗ | } | |
| 203 | ✗ | } | |
| 204 | |||
| 205 | ✗ | std::string Document::to_string() const { return to_string(KdlVersion::Kdl_1); } | |
| 206 | |||
| 207 | ✗ | std::string Document::to_string(KdlVersion version) const | |
| 208 | { | ||
| 209 | ✗ | kdl_emitter_options opts = KDL_DEFAULT_EMITTER_OPTIONS; | |
| 210 | |||
| 211 | ✗ | if (version == KdlVersion::Kdl_1) opts.version = KDL_VERSION_1; | |
| 212 | ✗ | if (version == KdlVersion::Kdl_2) opts.version = KDL_VERSION_2; | |
| 213 | |||
| 214 | ✗ | kdl_emitter* emitter = kdl_create_buffering_emitter(&opts); | |
| 215 | ✗ | if (emitter == nullptr) throw EmitterError{"Error initializing the KDL emitter"}; | |
| 216 | ✗ | emit_nodes(emitter, m_nodes); | |
| 217 | ✗ | auto result = std::string{to_u8string_view(kdl_get_emitter_buffer(emitter))}; | |
| 218 | ✗ | kdl_destroy_emitter(emitter); | |
| 219 | ✗ | return result; | |
| 220 | ✗ | } | |
| 221 | |||
| 222 | ✗ | Document parse(std::string_view kdl_text) { return parse(kdl_text, KdlVersion::Any); } | |
| 223 | |||
| 224 | ✗ | Document parse(std::string_view kdl_text, KdlVersion version) | |
| 225 | { | ||
| 226 | ✗ | kdl_parse_option opts = KDL_DEFAULTS; | |
| 227 | ✗ | if (version == KdlVersion::Kdl_1) opts = KDL_READ_VERSION_1; | |
| 228 | ✗ | else if (version == KdlVersion::Kdl_2) opts = KDL_READ_VERSION_2; | |
| 229 | ✗ | else if (version == KdlVersion::Any) { | |
| 230 | try { | ||
| 231 | ✗ | return parse(kdl_text, KdlVersion::Kdl_2); | |
| 232 | ✗ | } catch (ParseError const&) { | |
| 233 | ✗ | return parse(kdl_text, KdlVersion::Kdl_1); | |
| 234 | ✗ | } | |
| 235 | } | ||
| 236 | |||
| 237 | ✗ | kdl_str text = {reinterpret_cast<char const*>(kdl_text.data()), kdl_text.size()}; | |
| 238 | ✗ | kdl_parser* parser = kdl_create_string_parser(text, opts); | |
| 239 | ✗ | if (parser == nullptr) throw std::runtime_error("Error initializing the KDL parser"); | |
| 240 | ✗ | auto doc = Document::read_from(parser); | |
| 241 | ✗ | kdl_destroy_parser(parser); | |
| 242 | ✗ | return doc; | |
| 243 | ✗ | } | |
| 244 | |||
| 245 | } // namespace kdl | ||
| 246 |