[英]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表示形式,但是它會執行所需的子集並做出許多明智的選擇(例如,支持重復屬性,保留屬性順序):
#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>;
}
就這樣。 這是全部功能。 讓我們證明一下
像表達式樹本身一樣,我們不要硬連接它,而是創建一個漂亮的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
}
};
#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內置的自定義指令,有時它們會達到快速原型制作的最佳效果,例如
- 將Boost ublas矩陣寫入文本文件
- 盡管它很快變得令人困惑,但在Boost因果報應中不一致的Generator指令列行為 。 由於使用
columns[]
指令對子生成器進行“計數”,因此出現了許多怪癖
因為我不是受虐狂,所以我會從另一個答案中借鑒一個概念:創建一個中間表述,以更好地促進業力。
在此示例中,中間表示可能非常簡單,但是我懷疑您的其他要求(例如“ 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
規則更類似於name
和value
。 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"
}]
}]
}
修復它是...如果您想嘗試,將是一個不錯的挑戰。
簡化版本,帶有上面記錄的屬性處理變通辦法:
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.