Skip to content

v2_0_cpp_visitor

Takatoshi Kondo edited this page Apr 14, 2022 · 13 revisions

Visitor interface

msgpack-c provides unpacking APIs. They convert from msgpack formatted bytes to msgpack::object. And then, using msgpack::object for many purposes. It is similar to DOM(Document Object Model) API for XML. The visitor interface provides more direct access for msgpack formatted bytes like the SAX API for XML. You can define your own visitor and pass it to parse() APIs. During parsing process, an iterator traversing the data. When the iterator meets a msgpack element, a visitor member function that corresponding to the element is called.

Visitor concept

struct visitor {
    bool visit_nil();
    bool visit_boolean(bool v);
    bool visit_positive_integer(uint64_t v);
    bool visit_negative_integer(int64_t v);
    bool visit_float32(float v);
    bool visit_float64(double v);
    bool visit_str(const char* v, uint32_t size);
    bool visit_bin(const char* v, uint32_t size);
    bool visit_ext(const char* v, uint32_t size);
    bool start_array(uint32_t num_elements);
    bool start_array_item();
    bool end_array_item();
    bool end_array();
    bool start_map(uint32_t num_kv_pairs);
    bool start_map_key();
    bool end_map_key();
    bool start_map_value();
    bool end_map_value();
    bool end_map();
    void parse_error(size_t parsed_offset, size_t error_offset);
    void insufficient_bytes(size_t parsed_offset, size_t error_offset);
    bool referenced() const; // Only when you want to use the visitor with a parser
};

A visitor must implement all member functions above. If you want to implement only a few member functions, then you can inherit pre-defined msgpack::null_visitor.

struct null_visitor {
    bool visit_nil() {
        return true;
    }
    bool visit_boolean(bool /*v*/) {
        return true;
    }
    bool visit_positive_integer(uint64_t /*v*/) {
        return true;
    }
    bool visit_negative_integer(int64_t /*v*/) {
        return true;
    }
    bool visit_float32(float /*v*/) {
        return true;
    }
    bool visit_float64(double /*v*/) {
        return true;
    }
    bool visit_str(const char* /*v*/, uint32_t /*size*/) {
        return true;
    }
    bool visit_bin(const char* /*v*/, uint32_t /*size*/) {
        return true;
    }
    bool visit_ext(const char* /*v*/, uint32_t /*size*/) {
        return true;
    }
    bool start_array(uint32_t /*num_elements*/) {
        return true;
    }
    bool start_array_item() {
        return true;
    }
    bool end_array_item() {
        return true;
    }
    bool end_array() {
        return true;
    }
    bool start_map(uint32_t /*num_kv_pairs*/) {
        return true;
    }
    bool start_map_key() {
        return true;
    }
    bool end_map_key() {
        return true;
    }
    bool start_map_value() {
        return true;
    }
    bool end_map_value() {
        return true;
    }
    bool end_map() {
        return true;
    }
    void parse_error(size_t /*parsed_offset*/, size_t /*error_offset*/) {
    }
    void insufficient_bytes(size_t /*parsed_offset*/, size_t /*error_offset*/) {
    }
};

All member function except error notifying one have bool return type. Normally you need to return true. That means continue unpacking. If you return false, unpacking process doesn't continue.

The member functions named visit_xxx() are visitors for leaf elements. start_array()/end_array() is called when array starts/finishes. start_array_item()/end_array_item() is called just before/after each array items.

start_map()/end_map() is called when map starts/finishes. start_map_key()/end_map_key() is called just before/after each map keys. start_map_value()/end_map_value() is called just before/after each map values.

parse_error() is called when a parse error happens. insufficient_bytes() is called when the buffer doesn't contain enough bytes to build complete msgpack.

Example

Here is an example that coverts msgpack formatted bytes to JSON like structure (I omit string escape because that is not a point I want to demonstrate):

struct json_like_visitor : msgpack::null_visitor {
    json_like_visitor(std::string& s):m_s(s) {}

    bool visit_nil() {
        m_s += "null";
        return true;
    }
    bool visit_boolean(bool v) {
        if (v) m_s += "true";
        else m_s += "false";
        return true;
    }
    bool visit_positive_integer(uint64_t v) {
        std::stringstream ss;
        ss << v;
        m_s += ss.str();
        return true;
    }
    bool visit_negative_integer(int64_t v) {
        std::stringstream ss;
        ss << v;
        m_s += ss.str();
        return true;
    }
    bool visit_str(const char* v, uint32_t size) {
        m_s += '"' + std::string(v, size) + '"';
        return true;
    }
    bool start_array(uint32_t /*num_elements*/) {
        m_s += "[";
        return true;
    }
    bool end_array_item() {
        m_s += ",";
        return true;
    }
    bool end_array() {
        m_s.erase(m_s.size() - 1, 1); // remove the last ','
        m_s += "]";
        return true;
    }
    bool start_map(uint32_t /*num_kv_pairs*/) {
        m_s += "{";
        return true;
    }
    bool end_map_key() {
        m_s += ":";
        return true;
    }
    bool end_map_value() {
        m_s += ",";
        return true;
    }
    bool end_map() {
        m_s.erase(m_s.size() - 1, 1); // remove the last ','
        m_s += "}";
        return true;
    }
    void parse_error(size_t /*parsed_offset*/, size_t /*error_offset*/) {
        // report error.
    }
    void insufficient_bytes(size_t /*parsed_offset*/, size_t /*error_offset*/) {
        // report error.
    }
    std::string& m_s;
};

int main()
{
    std::stringstream ss;
    msgpack::packer<std::stringstream> p(ss);
    p.pack_map(1);
    p.pack("key");
    p.pack_array(3);
    p.pack(42);
    p.pack_nil();
    p.pack(true);

    std::string json_like;
    json_like_visitor v(json_like);
    std::size_t off = 0;
    std::string const& str = ss.str();
    bool ret = msgpack::parse(str.data(), str.size(), off, v);
    assert(ret);
    assert("{\"key\":[42,null,true]}" == json_like);
}

Parse API

In order to use visitor interface, you need to call parse APIs. Here are parse APIs:

template <typename Visitor>
bool parse(const char* data, size_t len, size_t& off, Visitor& v);
template <typename Visitor>
bool parse(const char* data, size_t len, Visitor& v);

They are similar to unpack APIs.

When you call a parse function, then the parse function callback the visitor that you passed. If parsing process is successfully finished, then the parse function returns true, otherwise returns false. If the visitor's visit function returns false, then the parse function returns false.

You can use visitors with stream interface like an unpacker.

template <typename VisitorHolder, typename ReferencedBufferHook>
class parser

VisitorHolder need to have a function named visitor() that returns a reference of the visitor object that satisfies the visitor concept.

ReferencedBufferHook need to have an operator()(char *). The operator() is called with the current buffer pointer only when the all following conditions are satisfied:

  1. Called parser::reserve_buffer(std::size_t).
  2. The current buffer doesn't have sufficient size and then new buffer is allocated.
  3. The current buffer is referenced by the visitor. In this case, visitor::referenced() should return true.

If visitor::referenced() returns true, the parserdoesn't free the current buffer but callReferencedBufferHook`. So you can get a chance to set something cleanup function for the current buffer.

See unpacker's memory management mechanism.

parser has the same public member functions as unpacker except bool next(). unpacker has the following member functions named next:

    bool next(msgpack::object_handle& result, bool& referenced);
    bool next(msgpack::object_handle& result);

parser has the follwoing member function named next

    bool next();

When you call next() then parse one MessagePack formatted data. So visitor's visit functions are called. If the parsing process successfully finished, then returns true, otherwise returns false.