簡體   English   中英

Boost Karma生成器用於類的組成

[英]Boost Karma generator for composition of classes

我有以下類圖:

類圖

有一些未使用的類,例如BinaryOperator ,但是我的實際代碼需要它們,因此我也想在示例中保留它們。

我想使用boost :: karma以獲得此的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"
        }
      ]
    }
  ]
}

因為這是一個簡單的示例,所以我想對類使用BOOST_FUSION_ADAPT_ADT宏,以便對生成器進行模塊化。

我是Karma的新手,我已經在Boost網站上閱讀了該教程,但是我不知道如何解決我的問題。 我找不到有關該宏的一些很好的教程。

我不想將現有的庫用於JSON,因為起初我想學習Karma,其次,JSON僅是一個示例,我需要以多種格式導出表達式,並且可以通過簡單地更改生成器來實現使用BOOST_FUSION_ADAPT_ADT作為我的類的代碼應該相同。

您可以找到用於創建示例表達式的代碼。 為了解決我的問題,我應該從哪里開始?

#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錯誤,這意味着您正在嘗試改編非針對其的內容。請說不)。

這是我的看法。 讓我們走上一條路,盡可能不打擾。 那意味着

  • 我們不能只是重載operator<<來打印json(因為您可能想自然地打印表達式)
  • 這也意味着任何負責生成JSON的函數都不會

    • 不得不打擾json實現細節
    • 必須打擾漂亮的格式
  • 最后,我不想使用任何特定於JSON的內容侵入表達式樹。 最不可接受的是不透明的朋友聲明。


一個簡單的JSON工具:

這很可能是最簡單的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>;
}

就這樣。 這是全部功能。 讓我們證明一下


漂亮的打印JSON

像表達式樹本身一樣,我們不要硬連接它,而是創建一個漂亮的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)}; }
}

to_json是可方便使用的支持ADL的擴展點,現在您可以使用它了:

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

連接起來

要進行以下工作:

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

我們需要的只是適當的to_json重載。 我們可以在此處進行所有操作,但最終可能需要與名為to_json的函數“交朋友”,更糟糕的是,從json名稱空間(至少是json::Value )中轉發聲明類型。 太過分了。 因此,讓我們添加另一個微小的間接:

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

訣竅是將JSON內容隱藏在一個不透明的結構中,然后我們可以成為朋友: struct serialization 其余的很簡單:

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上實時觀看

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

從您的問題來看輸出是完美的:

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

謝謝,事實是json只是我必須使用的多種格式中的一種,某些格式是專有的,並且沒有庫,因此我想對所有格式使用統一的方式。 我決定使用json來解決問題,因為社區對它的了解超過例如asciimath或我們創建的其他格式– Jepessen 9小時前

這對我的建議沒有任何改變。 如果有的話,它實際上是在強調您不希望強加任意限制。

因果報應的問題

  • 業力是靜態生成器的“內聯” DSL。 它們適用於靜態類型的東西。 您的AST使用動態多態性。

    這樣就避免了編寫簡潔的生成器的機會,除非使用許多復雜的語義動作。 我不記得寫過很多與業力有關的明確答案,但是動態多態性和語義動作的問題在齊方面都差不多:

    所有的關鍵缺點都適用,除了顯然沒有創建AST之外,因此分配的性能影響不如使用Qi解析器嚴重。

    但是,仍然保持着相同的邏輯:業力生成器被靜態組合以提高效率。 但是,您的動態類型層次結構會妨礙大多數效率。 換句話說,您不是Karma的目標受眾。

  • 無論您的AST的設計方式如何,業力都有另一個結構上的局限性,無論它是如何設計的:要使用有狀態的規則進行漂亮的打印非常困難。

    對我來說,這是實際上從不使用Karma的關鍵原因。 即使不是漂亮的打印目標,您也可以直接通過Boost Fusion生成訪問AST的輸出,從而獲得相似的里程(我們在我們的項目中使用它來生成不同版本的OData XML和API類型的JSON表示形式,以用於靜態API )。

    當然,有一些狀態生成任務具有Karma內置的自定義指令,有時它們會達到快速原型制作的最佳效果,例如

無論如何都要做

因為我不是受虐狂,所以我會從另一個答案中借鑒一個概念:創建一個中間表述,以更好地促進業力。

在此示例中,中間表示可能非常簡單,但是我懷疑您的其他要求(例如“ 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; };
}

首先,因為我們要使用業力,所以我們確實需要調整中間表示形式:

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

發電機

這是我能想到的最簡單的生成器,給出和接受兩件事:

  • 我已經對其進行了相當多的調整,以獲得一些“可讀”的格式。 如果刪除所有無關緊要的空格,它將變得更加簡單。
  • 我選擇不存儲冗余信息(例如中間表示形式中的靜態“類型”表示形式)。 這樣做會稍微復雜一些,主要是通過使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;
    };
}

聖經后

我只是為了簡化起見。 並遇到了完全不明顯的屬性處理怪癖的出色演示。 下面的(剛剛剝離空白處理) 工作:

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

如果您喜歡戲劇,可以在這里閱讀錯誤小說 問題在於delimit[]塊神奇地將屬性合並為單個字符串(呵呵)。 錯誤消息反映出例如啟動arguments生成器時尚未使用字符串屬性。

治療症狀的最直接方法是破壞屬性,但是沒有真正的方法:

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

沒有不同

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

如果它真的有效的話會很好。 無需添加或替換諸如ka::as<std::string>()[...]之類的咒語,就可以避免編譯錯誤。²

因此,為了結束這個悲慘的故事,我們將彎腰麻木的乏味:

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

有關實時演示,請參見下面標有“簡化版本”的部分。

使用它

使用該語法生成​​的最短方法是創建中間表示形式:

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

並使用:

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

完整演示

在魔盒上直播¹

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

輸出

生成器具有我所希望的可讀性/魯棒性/功能(存在與定界符有關的怪癖,當類型包含需要加引號的字符時會出現問題,沒有有狀態的縮進)。

結果看起來不像預期的那樣,盡管它是有效的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"
}]
}]
}

修復它是...如果您想嘗試,將是一個不錯的挑戰。

簡化版

簡化版本,帶有上面記錄的屬性處理變通辦法:

生活在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_;
    };
}

產生以下輸出:

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

我傾向於認為這比失敗的“漂亮”格式嘗試好得多。 但是,這里的真實故事是,維護成本無論如何都是通過屋頂進行的。


¹有趣的是,Coliru超出了編譯時間...這也可能是指導您進行設計決斷的論點

²讓您想知道每天有多少人實際使用Karma

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM