简体   繁体   English

Boost Karma生成器用于类的组成

[英]Boost Karma generator for composition of classes

I've the following class diagram: 我有以下类图:

类图

There's some unused class like BinaryOperator , but my real code needs them so I want to keep them also in the example. 有一些未使用的类,例如BinaryOperator ,但是我的实际代码需要它们,因此我也想在示例中保留它们。

I want to use boost::karma in order to obtain a JSON representation of this. 我想使用boost :: karma以获得此的JSON表示。 The JSON should be like the following one: JSON应该类似于以下内容:

{
  "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. 因为这是一个简单的示例,所以我想对类使用BOOST_FUSION_ADAPT_ADT宏,以便对生成器进行模块化。

I'm new to Karma, I've read the tutorial on boost site but I don't understand how to attack my problem. 我是Karma的新手,我已经在Boost网站上阅读了该教程,但是我不知道如何解决我的问题。 I can't find some good tutorial about that macro. 我找不到有关该宏的一些很好的教程。

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. 我不想将现有的库用于JSON,因为起初我想学习Karma,其次,JSON仅是一个示例,我需要以多种格式导出表达式,并且可以通过简单地更改生成器来实现使用BOOST_FUSION_ADAPT_ADT作为我的类的代码应该相同。

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;
}

I'd advise against using Karma to generate JSON. 我建议不要使用Karma生成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). 强烈建议不要使用ADAPT_ADT(它容易产生非常细微的UB错误,这意味着您正在尝试改编非针对其的内容。请说不)。

Here's my take on it. 这是我的看法。 Let's take the high road and be as unintrusive as possible. 让我们走上一条路,尽可能不打扰。 That means 那意味着

  • We can't just overload operator<< to print json (because you may want to naturally print the expressions instead) 我们不能只是重载operator<<来打印json(因为您可能想自然地打印表达式)
  • It also means that what ever function is responsible for generating the JSON doesn't 这也意味着任何负责生成JSON的函数都不会

    • have to bother with json implementation details 不得不打扰json实现细节
    • have to bother with pretty formatting 必须打扰漂亮的格式
  • Finally, I wouldn't want to intrude on the expression tree with anything JSON specific. 最后,我不想使用任何特定于JSON的内容侵入表达式树。 The most that could be acceptable is an opaque friend declaration. 最不可接受的是不透明的朋友声明。


A simple JSON facility: 一个简单的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): 这很可能是最简单的JSON表示形式,但是它会执行所需的子集并做出许多明智的选择(例如,支持重复属性,保留属性顺序):

#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 让我们证明一下


Pretty Printing JSON 漂亮的打印JSON

Like with the Expression tree itself, let's not hardwire this, but instead create a pretty-printing IO manipulator: 像表达式树本身一样,我们不要硬连接它,而是创建一个漂亮的IO操作器:

#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)}; }
}

The to_json thing dubs as a handy ADL-enabled extension point, you can already us it now: to_json是可方便使用的支持ADL的扩展点,现在您可以使用它了:

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

Connecting it up 连接起来

To make the following work: 要进行以下工作:

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

All we need is the appropriate to_json overload. 我们需要的只是适当的to_json重载。 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). 我们可以在此处进行所有操作,但最终可能需要与名为to_json的函数“交朋友”,更糟糕的是,从json名称空间(至少是json::Value )中转发声明类型。 That's too intrusive. 太过分了。 So, let's add anothe tiny indirection: 因此,让我们添加另一个微小的间接:

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

The trick is to hide the JSON stuff inside an opaque struct that we can then befriend: struct serialization . 诀窍是将JSON内容隐藏在一个不透明的结构中,然后我们可以成为朋友: 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
    }
};

Full Demo 完整演示

See it Live On Coliru 在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"
        }
      ]
    }
  ]
}

Thanks, the fact is that json is only one of many formats that I must to use, some format is proprietary and there are no libraries, so I want to use an uniform way for all. 谢谢,事实是json只是我必须使用的多种格式中的一种,某些格式是专有的,并且没有库,因此我想对所有格式使用统一的方式。 I've decided to use json for the question because is known to community more than, for example, asciimath or other formats created by us – Jepessen 9 hours ago 我决定使用json来解决问题,因为社区对它的了解超过例如asciimath或我们创建的其他格式– Jepessen 9小时前

This changes nothing about my recommendation. 这对我的建议没有任何改变。 If anything, it really emphasizes that you don't want arbitrary restrictions imposed. 如果有的话,它实际上是在强调您不希望强加任意限制。

The problems with Karma 因果报应的问题

  • Karma is an "inline" DSL for statically generated generators. 业力是静态生成器的“内联” DSL。 They work well for statically typed things. 它们适用于静态类型的东西。 Your AST uses dynamic polymorphism. 您的AST使用动态多态性。

    That removes any chance of writing a succinct generator barring the use of many, complicated semantic actions. 这样就避免了编写简洁的生成器的机会,除非使用许多复杂的语义动作。 I don't remember writing many explicit answers related to Karma, but the problems with both dynamic polymorphism and semantic actions are much the same on the Qi side: 我不记得写过很多与业力有关的明确答案,但是动态多态性和语义动作的问题在齐方面都差不多:

    The key draw backs all apply, except obviously that AST creation is not happening, so the performance effect of allocations is less severe than with Qi parsers. 所有的关键缺点都适用,除了显然没有创建AST之外,因此分配的性能影响不如使用Qi解析器严重。

    However, the same logic still stands: Karma generators are statically combined for efficiency. 但是,仍然保持着相同的逻辑:业力生成器被静态组合以提高效率。 However your dynamic type hierarchy precludes most of that efficiency. 但是,您的动态类型层次结构会妨碍大多数效率。 In other words, you are not the target audience for Karma. 换句话说,您不是Karma的目标受众。

  • Karma has another structural limitation that will bite here, regardless of the way your AST is designed: it's (very) hard to make use of stateful rules to do pretty printing. 无论您的AST的设计方式如何,业力都有另一个结构上的局限性,无论它是如何设计的:要使用有状态的规则进行漂亮的打印非常困难。

    This is, for me, a key reason to practically never use Karma. 对我来说,这是实际上从不使用Karma的关键原因。 Even if pretty printing isn't a goal you can still get similar mileage just generating output visiting the AST using Boost Fusion directly (we used this in our project to generate different versions of OData XML and JSON representations of API types for use in restful APIs). 即使不是漂亮的打印目标,您也可以直接通过Boost Fusion生成访问AST的输出,从而获得相似的里程(我们在我们的项目中使用它来生成不同版本的OData XML和API类型的JSON表示形式,以用于静态API )。

    Granted, there are some stateful generating tasks that have custom directives builtin to Karma, and sometimes they hit the sweet spot for rapid prototyping, eg 当然,有一些状态生成任务具有Karma内置的自定义指令,有时它们会达到快速原型制作的最佳效果,例如

Let's Do It Anyways 无论如何都要做

Because I'm not a masochist, I'll do borrow a concept from the other answer : creating an intermediate representation that facilitates Karma a lot better. 因为我不是受虐狂,所以我会从另一个答案中借鉴一个概念:创建一个中间表述,以更好地促进业力。

In this sample the intermediate representation can be exceedingly simple, but I suspect your other requirements like "for example, asciimath or other formats created by us" will require a more detailed design. 在此示例中,中间表示可能非常简单,但是我怀疑您的其他要求(例如“ asciimath或我们创建的其他格式”)将需要更详细的设计。

///////////////////////////////////////////////////////////////////////////////
// A simple intermediate representation
#include <boost/variant.hpp>
namespace output_ast {
    struct Function;
    struct Value;
    using Expression = boost::variant<Function, Value>;

    using Arguments = std::vector<Expression>;

    struct Value    { std::string name, value; };
    struct Function { std::string name; Arguments args; };
}

Firstly, because we're going to use Karma, we do need to actually adapt the intermediate representation: 首先,因为我们要使用业力,所以我们确实需要调整中间表示形式:

#include <boost/fusion/include/struct.hpp>
BOOST_FUSION_ADAPT_STRUCT(output_ast::Value, name, value)
BOOST_FUSION_ADAPT_STRUCT(output_ast::Function, name, args)

A generator 发电机

Here's the simplest generator I can think of, give and take 2 things: 这是我能想到的最简单的生成器,给出和接受两件事:

  • I have tweaked it for considerable time to get some "readable" format. 我已经对其进行了相当多的调整,以获得一些“可读”的格式。 It gets simpler if you remove all insignificant whitespace. 如果删除所有无关紧要的空格,它将变得更加简单。
  • I opted to not store redundant information (such as the static "type" representation in the intermediate representation). 我选择不存储冗余信息(例如中间表示形式中的静态“类型”表示形式)。 Doing so would slightly uncomplicate, mostly by making the type rule more similar to name and value . 这样做会稍微复杂一些,主要是通过使type规则更类似于namevalue
namespace karma_json {
    namespace ka = boost::spirit::karma;

    template <typename It>
    struct Generator : ka::grammar<It, output_ast::Expression()> {
        Generator() : Generator::base_type(expression) {
            expression = function|value;

            function
                = "{\n  " << ka::delimit(",\n  ") 
                   [name << type(+"Function") ]
                << arguments 
                << "\n}"
                ;

            arguments = "\"arguments\": [" << -(("\n  " << expression) % ",") << ']';

            value
                = "{\n  " << ka::delimit(",\n  ") 
                    [name << type(+"Value") ]
                << value_ 
                << "\n}"
                ;

            type   = "\"type\":\"" << ka::string(ka::_r1) << "\"";
            string = '"' << *('\\' << ka::char_("\\\"") | ka::char_) << '"';
            name   = "\"name\":" << string;
            value_ = "\"value\":" << string;
        }

      private:
        ka::rule<It, output_ast::Expression()> expression;
        ka::rule<It, output_ast::Function()> function;
        ka::rule<It, output_ast::Arguments()> arguments;
        ka::rule<It, output_ast::Value()> value;
        ka::rule<It, std::string()> string, name, value_;
        ka::rule<It, void(std::string)> type;
    };
}

Post Scriptum 圣经后

I was making the simplified take for completeness. 我只是为了简化起见。 And ran into this excellent demonstration of completely unobvious attribute handling quirks. 并遇到了完全不明显的属性处理怪癖的出色演示。 The following (just stripping whitespace handling) does not work: 下面的(刚刚剥离空白处理) 工作:

 function = '{' << ka::delimit(',') [name << type] << arguments << '}'; value = '{' << ka::delimit(',') [name << type] << value_ << '}' ; 

You can read the error novel here in case you like drama. 如果您喜欢戏剧,可以在这里阅读错误小说 The problem is that the delimit[] block magically consolidates the attributes into a single string (huh). 问题在于delimit[]块神奇地将属性合并为单个字符串(呵呵)。 The error message reflects that the string attribute has not been consumed when eg starting the arguments generator. 错误消息反映出例如启动arguments生成器时尚未使用字符串属性。

The most direct way to treat the symptom would be to break up the attribute, but there's no real way: 治疗症状的最直接方法是破坏属性,但是没有真正的方法:

 function = '{' << ka::delimit(',') [name << ka::eps << type] << arguments << '}'; value = '{' << ka::delimit(',') [name << ka::eps << type] << value_ << '}' ; 

No difference 没有不同

 function = '{' << ka::delimit(',') [ka::as_string[name] << ka::as_string[type]] << arguments << '}'; value = '{' << ka::delimit(',') [ka::as_string[name] << ka::as_string[type]] << value_ << '}' ; 

Would be nice if it actually worked. 如果它真的有效的话会很好。 No amount of adding includes or replacing with incantations like ka::as<std::string>()[...] made the compilation error go away.² 无需添加或替换诸如ka::as<std::string>()[...]之类的咒语,就可以避免编译错误。²

So, to just end this sob-story, we'll stoop to the mind-numbingly tedious: 因此,为了结束这个悲惨的故事,我们将弯腰麻木的乏味:

 function = '{' << name << ',' << type << ',' << arguments << '}'; arguments = "\\"arguments\\":[" << -(expression % ',') << ']'; 

See the section labeled "Simplified Version" below for the live demo. 有关实时演示,请参见下面标有“简化版本”的部分。

Using it 使用它

The shortest way to generate using that grammar is to create the intermediate representation: 使用该语法生成​​的最短方法是创建中间表示形式:

///////////////////////////////////////////////////////////////////////////////
// Expression -> output_ast
struct serialization {
    static output_ast::Expression call(Expression const* e) {
        if (auto* f = dynamic_cast<Function const*>(e)) {
            output_ast::Arguments args;
            for (auto& a : f->m_arguments) args.push_back(call(a));
            return output_ast::Function { f->getName(), args };
        }

        if (auto* v = dynamic_cast<Value const*>(e)) {
            return output_ast::Value { v->getName(), v->getValue() };
        }

        return {};
    }
};

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

And use that: 并使用:

using It = boost::spirit::ostream_iterator;
std::cout << format(karma_json::Generator<It>{}, to_output(plus1));

Full Demo 完整演示

Live On Wandbox¹ 在魔盒上直播¹

#include <boost/lexical_cast.hpp>
#include <iostream>
#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 intermediate representation
#include <boost/variant.hpp>
namespace output_ast {
    struct Function;
    struct Value;
    using Expression = boost::variant<Function, Value>;

    using Arguments = std::vector<Expression>;

    struct Value    { std::string name, value; };
    struct Function { std::string name; Arguments args; };
}

#include <boost/fusion/include/struct.hpp>
BOOST_FUSION_ADAPT_STRUCT(output_ast::Value, name, value)
BOOST_FUSION_ADAPT_STRUCT(output_ast::Function, name, args)

#include <boost/spirit/include/karma.hpp>
namespace karma_json {
    namespace ka = boost::spirit::karma;

    template <typename It>
    struct Generator : ka::grammar<It, output_ast::Expression()> {
        Generator() : Generator::base_type(expression) {
            expression = function|value;

            function
                = "{\n  " << ka::delimit(",\n  ") 
                   [name << type(+"Function") ]
                << arguments 
                << "\n}"
                ;

            arguments = "\"arguments\": [" << -(("\n  " << expression) % ",") << ']';

            value
                = "{\n  " << ka::delimit(",\n  ") 
                    [name << type(+"Value") ]
                << value_ 
                << "\n}"
                ;

            type   = "\"type\":\"" << ka::string(ka::_r1) << "\"";
            string = '"' << *('\\' << ka::char_("\\\"") | ka::char_) << '"';
            name   = "\"name\":" << string;
            value_ = "\"value\":" << string;
        }

      private:
        ka::rule<It, output_ast::Expression()> expression;
        ka::rule<It, output_ast::Function()> function;
        ka::rule<It, output_ast::Arguments()> arguments;
        ka::rule<It, output_ast::Value()> value;
        ka::rule<It, std::string()> string, name, value_;
        ka::rule<It, void(std::string)> type;
    };
}

///////////////////////////////////////////////////////////////////////////////
// Expression -> output_ast
struct serialization {
    static output_ast::Expression call(Expression const* e) {
        if (auto* f = dynamic_cast<Function const*>(e)) {
            output_ast::Arguments args;
            for (auto& a : f->m_arguments) args.push_back(call(a));
            return output_ast::Function { f->getName(), args };
        }

        if (auto* v = dynamic_cast<Value const*>(e)) {
            return output_ast::Value { v->getName(), v->getValue() };
        }

        return {};
    }
};

auto to_output(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?
    using It = boost::spirit::ostream_iterator;
    std::cout << format(karma_json::Generator<It>{}, to_output(plus1));
}

The Output 输出

The generator is being as as readable/robust/functional as I'd like (there are quirks related to delimiters, there are issues when type contain characters that would need to be quoted, there's no stateful indentation). 生成器具有我所希望的可读性/鲁棒性/功能(存在与定界符有关的怪癖,当类型包含需要加引号的字符时会出现问题,没有有状态的缩进)。

The result doesn't look as expected, though it's valid JSON: 结果看起来不像预期的那样,尽管它是有效的JSON:

{
  "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"
}]
}]
}

Fixing it is... a nice challenge if you want to try it. 修复它是...如果您想尝试,将是一个不错的挑战。

The Simplified Version 简化版

The simplified version, complete with attribute-handling workaround documented above: 简化版本,带有上面记录的属性处理变通办法:

Live On Coliru 生活在Coliru

namespace karma_json {
    namespace ka = boost::spirit::karma;

    template <typename It>
    struct Generator : ka::grammar<It, output_ast::Expression()> {
        Generator() : Generator::base_type(expression) {
            expression = function|value;

            function = '{' << name << ',' << type << ',' << arguments << '}';
            arguments = "\"arguments\":[" << -(expression % ',') << ']';

            value = '{' << name << ',' << type << ',' << value_ << '}' ;

            string = '"' << *('\\' << ka::char_("\\\"") | ka::char_) << '"';
            type   = "\"type\":" << string;
            name   = "\"name\":" << string;
            value_ = "\"value\":" << string;
        }

      private:
        ka::rule<It, output_ast::Expression()> expression;
        ka::rule<It, output_ast::Function()> function;
        ka::rule<It, output_ast::Arguments()> arguments;
        ka::rule<It, output_ast::Value()> value;
        ka::rule<It, std::string()> string, name, type, value_;
    };
}

Yields the following output: 产生以下输出:

{"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"}]}]}

I'm inclined to think this is a much better cost/benefit ratio than the failed attempt at "pretty" formatting. 我倾向于认为这比失败的“漂亮”格式尝试好得多。 But the real story here is that the maintenance cost is through the roof anyways. 但是,这里的真实故事是,维护成本无论如何都是通过屋顶进行的。


¹ Interestingly, Coliru exceeds the compilation time... This too could be an argument guiding your design descisions ¹有趣的是,Coliru超出了编译时间...这也可能是指导您进行设计决断的论点

² makes you wonder how many people actually use Karma day-to-day ²让您想知道每天有多少人实际使用Karma

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM