Boost Karma生成器,用于类的组成 [英] Boost Karma generator for composition of classes

查看:116
本文介绍了Boost Karma生成器,用于类的组成的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有以下类图:

有一些未使用的类,例如BinaryOperator,但是我的实际代码需要它们,因此我想在示例中也保留它们.

There's some unused class like BinaryOperator, but my real code needs them so I want to keep them also in the example.

我想使用 boost :: karma ,以获取此内容的JSON表示形式. JSON应该类似于以下内容:

I want to use boost::karma in order to obtain a JSON representation of this. The JSON should be like the following one:

{
  "name": "Plus",
  "type": "Function",
  "arguments": [
    {
      "name": "IntegerValue",
      "type": "Value",
      "value": "4"
    },
    {
      "name": "Plus",
      "type": "Function",
      "arguments": [
        {
          "name": "IntegerValue",
          "type": "Value",
          "value": "5"
        },
        {
          "name": "IntegerValue",
          "type": "Value",
          "value": "6"
        }
      ]
    }
  ]
}

由于这是一个简单的示例,因此我想使用

Since it's a simple example, I'd like to use BOOST_FUSION_ADAPT_ADT macro for my classes in order to modularize the generator.

我是Karma的新手,我已经阅读了Boost网站上的教程,但是我不知道如何解决我的问题.我找不到有关该宏的一些很好的教程.

I'm new to Karma, I've read the tutorial on boost site but I don't understand how to attack my problem. I can't find some good tutorial about that macro.

我不想使用现有的JSON库,因为起初我想学习Karma,其次,JSON只是一个例子,我需要以多种格式导出表达式,我可以通过简单的方式来实现更改生成器,而将BOOST_FUSION_ADAPT_ADT用于我的类的代码应该相同.

I don't want to use existing libraries for JSON because at first I want to learn Karma, and in second place JSON is only an example, I need to export my expression in many formats, and I can do it by simply changing generators while the code that uses BOOST_FUSION_ADAPT_ADT for my classes should be the same.

您可以找到用于创建示例表达式的代码.为了解决我的问题,我应该从哪里开始?

You can find the code for creating a sample expression. Where I need to start in order to solve my problem?

#include <boost/lexical_cast.hpp>
#include <iostream>
#include <vector>

class Expression {
public:

  virtual std::string getName() const = 0;
};

class Value : public Expression {
public:

  virtual std::string getValue() const = 0;
};

class IntegerValue : public Value {
public:

  IntegerValue(int value) : m_value(value) {}
  virtual std::string getName() const override { return "IntegerValue"; }
  virtual std::string getValue() const override { return boost::lexical_cast<std::string>(m_value); }

private:

  int m_value;
};

class Function : public Expression {
public:

  void addArgument(Expression* expression) { m_arguments.push_back(expression); }
  virtual std::string getName() const override { return m_name; }

protected:

  std::vector<Expression*> m_arguments;
  std::string m_name;
};

class Plus : public Function {
public:

  Plus() : Function() { m_name = "Plus"; }
};

///////////////////////////////////////////////////////////////////////////////

int main(int argc, char **argv) {

  // Build expression 4 + 5 + 6 as 4 + (5 + 6)
  Function* plus1 = new Plus();
  Function* plus2 = new Plus();
  Value* iv4   = new IntegerValue(4);
  Value* iv5   = new IntegerValue(5);
  Value* iv6   = new IntegerValue(6);
  plus2->addArgument(iv5);
  plus2->addArgument(iv6);
  plus1->addArgument(iv4);
  plus1->addArgument(plus2);

  // Generate json string here, but how?

  return 0;
}

推荐答案

我建议不要使用Karma生成JSON.我建议强烈反对ADAPT_ADT(它易于出现非常细微的UB错误,这意味着您正在尝试改编非针对其的内容.请说不).

I'd advise against using Karma to generate JSON. I'd advise strongly against ADAPT_ADT (it's prone to very subtle UB bugs and it means you're trying to adapt something that wasn't designed for it. Just say no).

这是我的看法.让我们走上一条路,尽可能不打扰.这意味着

Here's my take on it. Let's take the high road and be as unintrusive as possible. That means

  • 我们不能只重载operator<<来打印json(因为您可能想自然来打印表达式)
  • 这还意味着,负责生成JSON的函数都不会

  • We can't just overload operator<< to print json (because you may want to naturally print the expressions instead)
  • It also means that what ever function is responsible for generating the JSON doesn't

  • 必须烦恼json的实现细节
  • 必须烦恼漂亮的格式

最后,我不想使用任何特定于JSON的内容侵入表达式树.最可接受的是 opaque 朋友声明.

Finally, I wouldn't want to intrude on the expression tree with anything JSON specific. The most that could be acceptable is an opaque friend declaration.

这很可能是最简单的JSON表示形式,但是它执行了必需的子集并做出了许多明智的选择(例如,支持重复属性,保留属性顺序):

This might well be the most simplistic JSON representation, but it does the required subset and makes a number of smart choices (supporting duplicate properties, retaining property order for example):

#include <boost/variant.hpp>
namespace json {
    // adhoc JSON rep
    struct Null {};
    using String = std::string;

    using Value = boost::make_recursive_variant<
        Null,
        String,
        std::vector<boost::recursive_variant_>,
        std::vector<std::pair<String, boost::recursive_variant_> >
    >::type;

    using Property = std::pair<String, Value>;
    using Object = std::vector<Property>;
    using Array = std::vector<Value>;
}

仅此而已.这是全部功能.让我们证明一下

That's all. This is fully functional. Let's prove it

就像表达式树本身一样,我们不要硬连接它,而是创建一个漂亮的IO操作器:

Like with the Expression tree itself, let's not hardwire this, but instead create a pretty-printing IO manipulator:

#include <iomanip>
namespace json {

    // pretty print it
    struct pretty_io {
        using result_type = void;

        template <typename Ref>
        struct manip {
            Ref ref;
            friend std::ostream& operator<<(std::ostream& os, manip const& m) {
                pretty_io{os,""}(m.ref);
                return os;
            }
        };

        std::ostream& _os;
        std::string _indent;

        void operator()(Value const& v) const {
            boost::apply_visitor(*this, v);
        }
        void operator()(Null) const {
            _os << "null";
        }
        void operator()(String const& s) const {
            _os << std::quoted(s);
        }
        void operator()(Property const& p) const {
            _os << '\n' << _indent; operator()(p.first);
            _os << ": ";            operator()(p.second);
        }
        void operator()(Object const& o) const {
            pretty_io nested{_os, _indent+"  "};
            _os << "{";
            bool first = true;
            for (auto& p : o) { first||_os << ","; nested(p); first = false; }
            _os << "\n" << _indent << "}";
        }
        void operator()(Array const& o) const {
            pretty_io nested{_os, _indent+"  "};
            _os << "[\n" << _indent << "  ";
            bool first = true;
            for (auto& p : o) { first||_os << ",\n" << _indent << "  "; nested(p); first = false; }
            _os << "\n" << _indent << "]";
        }
    };

    Value to_json(Value const& v) { return v; }

    template <typename T, typename V = decltype(to_json(std::declval<T const&>()))>
    pretty_io::manip<V> pretty(T const& v) { return {to_json(v)}; }
}

to_json东西可谓是方便的启用ADL的扩展点,您现在就可以使用它:

The to_json thing dubs as a handy ADL-enabled extension point, you can already us it now:

std::cout << json::pretty("hello world"); // prints as a JSON String


将其连接

要进行以下工作:


Connecting it up

To make the following work:

std::cout << json::pretty(plus1);

我们所需要的只是适当的to_json重载.我们可以在此处进行所有操作,但最终可能需要与名为to_json的函数交朋友",更糟糕的是,从json命名空间(至少是json::Value)转发声明类型.太过分了.因此,让我们添加另一种微小的间接作用:

All we need is the appropriate to_json overload. We could jot it all in there, but we might end up needing to "friend" a function named to_json, worse still, forward declare types from the json namespace (json::Value at the very least). That's too intrusive. So, let's add anothe tiny indirection:

auto to_json(Expression const* expression) {
    return serialization::call(expression);
}

诀窍是将JSON内容隐藏在一个我们可以随后成为朋友的不透明结构中:struct serialization.其余的很简单:

The trick is to hide the JSON stuff inside an opaque struct that we can then befriend: struct serialization. The rest is straightforward:

struct serialization {
    static json::Value call(Expression const* e) {
        if (auto* f = dynamic_cast<Function const*>(e)) {
            json::Array args;
            for (auto& a : f->m_arguments)
                args.push_back(call(a));
            return json::Object {
                { "name", f->getName() },
                { "type", "Function" },
                { "arguments", args },
            };
        }

        if (auto* v = dynamic_cast<Value const*>(e)) {
            return json::Object {
                { "name", v->getName() },
                { "type", "Value" },
                { "value", v->getValue() },
            };
        }

        return {}; // Null in case we didn't implement a node type
    }
};


完整演示

查看 在Coliru上直播


Full Demo

See it Live On Coliru

#include <boost/lexical_cast.hpp>
#include <iostream>
#include <iomanip>
#include <vector>

struct Expression {
    virtual std::string getName() const = 0;
};

struct Value : Expression {
    virtual std::string getValue() const = 0;
};

struct IntegerValue : Value {
    IntegerValue(int value) : m_value(value) {}
    virtual std::string getName() const override { return "IntegerValue"; }
    virtual std::string getValue() const override { return boost::lexical_cast<std::string>(m_value); }

  private:
    int m_value;
};

struct Function : Expression {
    void addArgument(Expression *expression) { m_arguments.push_back(expression); }
    virtual std::string getName() const override { return m_name; }

  protected:
    std::vector<Expression *> m_arguments;
    std::string m_name;

    friend struct serialization;
};

struct Plus : Function {
    Plus() : Function() { m_name = "Plus"; }
};

///////////////////////////////////////////////////////////////////////////////
// A simple JSON facility
#include <boost/variant.hpp>
namespace json {
    // adhoc JSON rep
    struct Null {};
    using String = std::string;

    using Value = boost::make_recursive_variant<
        Null,
        String,
        std::vector<boost::recursive_variant_>,
        std::vector<std::pair<String, boost::recursive_variant_> >
    >::type;

    using Property = std::pair<String, Value>;
    using Object = std::vector<Property>;
    using Array = std::vector<Value>;
}

///////////////////////////////////////////////////////////////////////////////
// Pretty Print manipulator
#include <iomanip>
namespace json {

    // pretty print it
    struct pretty_io {
        using result_type = void;

        template <typename Ref>
        struct manip {
            Ref ref;
            friend std::ostream& operator<<(std::ostream& os, manip const& m) {
                pretty_io{os,""}(m.ref);
                return os;
            }
        };

        std::ostream& _os;
        std::string _indent;

        void operator()(Value const& v) const {
            boost::apply_visitor(*this, v);
        }
        void operator()(Null) const {
            _os << "null";
        }
        void operator()(String const& s) const {
            _os << std::quoted(s);
        }
        void operator()(Property const& p) const {
            _os << '\n' << _indent; operator()(p.first);
            _os << ": ";            operator()(p.second);
        }
        void operator()(Object const& o) const {
            pretty_io nested{_os, _indent+"  "};
            _os << "{";
            bool first = true;
            for (auto& p : o) { first||_os << ","; nested(p); first = false; }
            _os << "\n" << _indent << "}";
        }
        void operator()(Array const& o) const {
            pretty_io nested{_os, _indent+"  "};
            _os << "[\n" << _indent << "  ";
            bool first = true;
            for (auto& p : o) { first||_os << ",\n" << _indent << "  "; nested(p); first = false; }
            _os << "\n" << _indent << "]";
        }
    };

    Value to_json(Value const& v) { return v; }

    template <typename T, typename V = decltype(to_json(std::declval<T const&>()))>
    pretty_io::manip<V> pretty(T const& v) { return {to_json(v)}; }
}

///////////////////////////////////////////////////////////////////////////////
// Expression -> JSON
struct serialization {
    static json::Value call(Expression const* e) {
        if (auto* f = dynamic_cast<Function const*>(e)) {
            json::Array args;
            for (auto& a : f->m_arguments)
                args.push_back(call(a));
            return json::Object {
                { "name", f->getName() },
                { "type", "Function" },
                { "arguments", args },
            };
        }

        if (auto* v = dynamic_cast<Value const*>(e)) {
            return json::Object {
                { "name", v->getName() },
                { "type", "Value" },
                { "value", v->getValue() },
            };
        }

        return {};
    }
};

auto to_json(Expression const* expression) {
    return serialization::call(expression);
}

int main() {
    // Build expression 4 + 5 + 6 as 4 + (5 + 6)
    Function *plus1 = new Plus();
    Function *plus2 = new Plus();
    Value *iv4 = new IntegerValue(4);
    Value *iv5 = new IntegerValue(5);
    Value *iv6 = new IntegerValue(6);
    plus2->addArgument(iv5);
    plus2->addArgument(iv6);
    plus1->addArgument(iv4);
    plus1->addArgument(plus2);

    // Generate json string here, but how?

    std::cout << json::pretty(plus1);
}

输出完全符合您的问题:

Output is picture-perfect from your question:

{
  "name": "Plus",
  "type": "Function",
  "arguments": [
    {
      "name": "IntegerValue",
      "type": "Value",
      "value": "4"
    },
    {
      "name": "Plus",
      "type": "Function",
      "arguments": [
        {
          "name": "IntegerValue",
          "type": "Value",
          "value": "5"
        },
        {
          "name": "IntegerValue",
          "type": "Value",
          "value": "6"
        }
      ]
    }
  ]
}

这篇关于Boost Karma生成器,用于类的组成的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆