使用 Boost.Qi 实现递归语法
Implementing recursive grammars with Boost.Qi
我正在使用 Boost.Spirit Qi 从一些文本数据构建相当复杂的结构。数据结构可能是递归定义的,所以我需要两个语法来相互引用,这就是问题出现的地方。
比如我有一个语法:
element = line | text | circle | box | composite_element
composite_element = 'C', int, int, '[', +element, ']'
显然,我需要这样的东西:
#include <boost/config/warning_disable.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/spirit/include/phoenix_core.hpp>
#include <boost/spirit/include/phoenix_operator.hpp>
#include <boost/spirit/include/phoenix_fusion.hpp>
#include <boost/spirit/include/phoenix_stl.hpp>
#include <tuple>
#include <boost/fusion/adapted/std_pair.hpp>
#include <boost/fusion/include/std_pair.hpp>
#include <boost/spirit/include/qi_eol.hpp>
#include <boost/phoenix.hpp>
#include <vector>
#include <string>
namespace qi = boost::spirit::qi;
namespace ascii = boost::spirit::ascii;
namespace phoenix = boost::phoenix;
struct line {
int x1;
int y1;
int x2;
int y2;
int color;
int width;
int capstyle;
int dashstyle;
int dashlength;
int dashspace;
};
struct box {
int x;
int y;
int width;
int height;
int color;
int line_width;
int capstyle;
int dashstyle;
int dashlength;
int dashspace;
int filltype;
int fillwidth;
int angle1;
int pitch1;
int angle2;
int pitch2;
};
struct circle {
int x;
int y;
int radius;
int color;
int line_width;
int capstyle;
int dashstyle;
int dashlength;
};
struct text {
int x;
int y;
int color;
int size;
int visibility;
int show_name_value;
int angle;
int alignment;
int num_lines;
std::vector<std::string> lines;
};
struct composite_component;
using element_t = boost::variant<line, box, circle, text, boost::recursive_wrapper<composite_component>>;
struct composite_component {
int x;
int y;
std::string basename;
// only used if component is embedded
// i. e. stores its definition within the schematic file
std::vector<element_t> elements;
};
struct element {
// some other fields
// ...
element_t element;
};
struct document {
std::vector<element> elements;
};
BOOST_FUSION_ADAPT_STRUCT(line, x1, y1, x2, y2, color, width, capstyle, dashstyle, dashlength, dashspace)
BOOST_FUSION_ADAPT_STRUCT(box, x, y, width, height, color, line_width, capstyle, dashstyle, dashlength, dashspace, filltype, fillwidth, angle1, pitch1, angle2, pitch2)
BOOST_FUSION_ADAPT_STRUCT(circle, x, y, radius, color, line_width, capstyle, dashstyle, dashlength)
BOOST_FUSION_ADAPT_STRUCT(text, x, y, color, size, visibility, show_name_value, angle, alignment, num_lines, lines)
BOOST_FUSION_ADAPT_STRUCT(composite_component, x, y, basename, elements)
BOOST_FUSION_ADAPT_STRUCT(element, element)
BOOST_FUSION_ADAPT_STRUCT(document, elements)
template <typename Iterator, typename Attribute>
using rule = qi::rule<Iterator, Attribute, qi::blank_type>;
template <typename Iterator>
class composite_element_parser;
template <typename Iterator>
class element_parser : public qi::grammar<Iterator, element(), qi::blank_type> {
public:
element_parser(): element_parser::base_type{start_rule_}
{
using qi::int_;
using qi::repeat;
using phoenix::val;
using phoenix::construct;
/* other definitions except of the 'line' is omitted in sake of simplicity */
line_ = 'L' >> int_ >> int_ >> int_ >> int_ >> int_ >>
int_ >> int_ >> int_ >> int_ >> int_ >> qi::eol;
// box = ...
// circle = ...
// text = ...
start_rule_ = (line_ /* || embedded_component_ */) >> qi::eoi;
}
private:
rule<Iterator, element()> start_rule_;
rule<Iterator, line()> line_;
// here comes the problem - CIRCULAR REFERENCE to incompletely defined template
// composite_element_parser<Iterator> embedded_component_;
};
template <typename Iterator>
class composite_element_parser : public qi::grammar<Iterator, composite_component(), qi::blank_type> {
public:
composite_element_parser() : composite_element_parser::base_type{start_rule_}
{
using phoenix::at_c;
using qi::int_;
using phoenix::push_back;
start_rule_ = "C" >> int_ >> int_ >> qi::lexeme[(qi::char_)[at_c<2>(qi::_val) += qi::_1]]
>> -(
"[" >>
*(element_) [push_back(at_c<3>(qi::_val), qi::_1)] >>
"]"
);
}
private:
rule<Iterator, composite_component()> start_rule_;
element_parser<Iterator> element_;
};
template <typename Iterator>
class document_parser : public qi::grammar<Iterator, document(), qi::blank_type> {
public:
document_parser() : document_parser::base_type{start_rule_}
{
using phoenix::at_c;
using phoenix::push_back;
using qi::_val;
using qi::_0;
using qi::_1;
start_rule_ = +(element_)[push_back(at_c<0>(_val), _1)] >> qi::eoi;
}
private:
rule<Iterator, document()> start_rule_;
element_parser<Iterator> element_;
};
int main(int , char **) {
document_parser<std::string::const_iterator> parser;
document doc;
const std::string text = "v 20180904 2\n"
"L 1 2 3 4 5 6 7 8 9 10\n"
"C 10 10 FOO\n"
"[ "
"L 1 2 3 4 5 6 7 8 9 10\n"
"]\n";
bool r = qi::phrase_parse(text.cbegin(), text.cend(), parser, qi::blank, doc);
std::cout << (r ? "OK" : "FAIL") << std::endl;
return 0;
}
不过,'text'、'circle' 和 'box' 的规则定义被省略了。请注意 element_parser
定义的私有部分中的注释 - 编译器将无法实例化不完整的 class 模板 composite_element_parser<Iterator>
。我该怎么办?显然,我不能将 element_parser
和 composite_element_parser
作为顶级语法的成员(在我的例子中是 document_parser
)并在构造函数中将它们 references/pointers 传递给彼此初始化列表,因为它们目前未初始化。
更新: 这个帖子可能被认为是 Deeply-recursive qi grammars (parsers) with synthesized and inherited attributes 的重复,但我真的无法理解已批准的答案。
通常你不会那样拆分语法。但如果你真的想要,有多种方法:
单独创建语法并在外部将语法分配给 rule
占位符:
#include <boost/spirit/include/qi.hpp>
namespace qi = boost::spirit::qi;
template <typename Iterator>
struct grammar1 : qi::grammar<Iterator, int()>
{
grammar1() : grammar1::base_type{start_}
{
start_ = '[' >> outer >> ']';
}
qi::rule<Iterator, int()> outer;
private:
qi::rule<Iterator, int()> start_;
};
template <typename Iterator>
struct grammar2 : qi::grammar<Iterator, int()>
{
grammar2() : grammar2::base_type{start_}
{
start_ = outer | qi::int_;
}
qi::rule<Iterator, int()> outer;
private:
qi::rule<Iterator, int()> start_;
};
int main()
{
char const* s = "[[123]]", * e = s + std::strlen(s);
grammar2<char const*> g2;
grammar1<char const*> g1;
g2.outer = g1;
g1.outer = g2;
int value = 0;
if (qi::parse(s, e, g1, value))
std::cout << value << '\n';
else
std::cout << "failed\n";
}
在另一个动态创建一个语法并传递给它一个前者的引用:
#include <boost/spirit/include/qi.hpp>
namespace qi = boost::spirit::qi;
template <typename Iterator>
struct grammar2;
template <typename Iterator>
struct grammar1 : qi::grammar<Iterator, int()>
{
grammar1()
: grammar1::base_type{start_}
{
outer_ = std::make_unique<grammar2<Iterator>>(start_);
start_ = '[' >> *outer_ >> ']'; // NOTE: it is not a kleen star!
}
private:
std::unique_ptr<grammar2<Iterator>> outer_;
qi::rule<Iterator, int()> start_;
};
template <typename Iterator>
struct grammar2 : qi::grammar<Iterator, int()>
{
explicit grammar2(qi::rule<Iterator, int()> const& outer)
: grammar2::base_type{start_}
{
start_ = outer | qi::int_;
}
private:
qi::rule<Iterator, int()> start_;
};
int main()
{
char const* s = "[[123]]", * e = s + std::strlen(s);
grammar1<char const*> const g1;
int value = 0;
if (qi::parse(s, e, g1, value))
std::cout << value << '\n';
else
std::cout << "failed\n";
}
我认为在你的例子中没有必要使用语法实例。
规则可以相互引用recursively/cyclically(因为它们通过引用相互引用)。我会利用这个。
You can still separate out grammar classes (e.g. to separate the implementations into compilation units) but you just want to glue them together at a central spot where you have the instances that you can then mutually reference (in other words: classical ownership management: if none of the classes can own objects, have another entity own them both).
或者,您可以简单地在构造函数中传递对互补语法的引用,并保留它们而不是语法实例。
演示
为了方便起见,我采用了第二种方法¹。
我改变了一些东西:
- 将
composite_element::elements
的类型从 std::vector<element_t>
固定为 std::vector<element>
修复了围绕 white-space 跳过的一些问题:
'[' ... ']'
块解析不允许您的输入显示的换行符(参见 skip(qi::space)
)
eol
仅在 line_
之后才需要,但您的输入显示在其他元素之后
- 元素解析器错误地需要
eoi
- 这导致解析在第一个元素之后停止(如果不是 EOI 则失败)。
- 船长end-user无法使用。我的信条是对调用者隐藏它,除非你想让调用者真正能够改变船长
增加了方便 start_rule_
编码船长以及确保每个语法的 top-level 规则在调试输出中有用地显示(如果一切都被调用 start_rule_
,信息所剩无几)
删除了所有内容 phoenix
(参见 Boost Spirit: "Semantic actions are evil"?):
这个例子:
start_rule_ = +(element_)[push_back(at_c<0>(_val), _1)] >> qi::eoi;
这就是自动属性传播已经完成的工作,所以这就足够了:
start_rule_ = +elements_ >> qi::eoi;
这个:
qi::lexeme[(qi::char_)[at_c<2>(qi::_val) += qi::_1]]
有一些问题:它缺少重复(它只解析 1 个字符),它没有指定接受哪些字符,sp 如果重复将读取直到 EOI。我怀疑这就是你想要的:
qi::lexeme[+qi::graph]
另见 Boost spirit skipper issues
已将 ||
更改为 |
(参见 Alternative Parser vs Sequential-Or Parser)。
也许更多但我忘记了?哦对了,我评论了 v
行。
#define BOOST_SPIRIT_DEBUG
#include <boost/fusion/adapted.hpp>
#include <boost/spirit/include/qi.hpp>
namespace qi = boost::spirit::qi;
struct line { int x1, y1, x2, y2, color, width, capstyle, dashstyle, dashlength, dashspace; };
struct box { int x, y, width, height, color, line_width, capstyle, dashstyle, dashlength, dashspace, filltype, fillwidth, angle1, pitch1, angle2, pitch2; };
struct circle { int x, y, radius, color, line_width, capstyle, dashstyle, dashlength; };
struct text { int x, y, color, size, visibility, show_name_value, angle, alignment, num_lines;
std::vector<std::string> lines;
};
struct composite_component;
using element_t = boost::variant<line, box, circle, text, boost::recursive_wrapper<composite_component>>;
struct element {
// ...
element_t element;
};
struct composite_component {
int x;
int y;
std::string basename;
std::vector<element> elements;
};
struct document { std::vector<element> elements; };
BOOST_FUSION_ADAPT_STRUCT(line, x1, y1, x2, y2, color, width, capstyle, dashstyle, dashlength, dashspace)
BOOST_FUSION_ADAPT_STRUCT(box, x, y, width, height, color, line_width, capstyle, dashstyle, dashlength, dashspace, filltype, fillwidth, angle1, pitch1, angle2, pitch2)
BOOST_FUSION_ADAPT_STRUCT(circle, x, y, radius, color, line_width, capstyle, dashstyle, dashlength)
BOOST_FUSION_ADAPT_STRUCT(text, x, y, color, size, visibility, show_name_value, angle, alignment, num_lines, lines)
BOOST_FUSION_ADAPT_STRUCT(composite_component, x, y, basename, elements)
BOOST_FUSION_ADAPT_STRUCT(element, element)
BOOST_FUSION_ADAPT_STRUCT(document, elements)
template <typename Iterator, typename Attribute>
using blank_rule = qi::rule<Iterator, Attribute, qi::blank_type>;
template <typename Iterator>
struct composite_element_parser;
template <typename Iterator>
struct element_parser : qi::grammar<Iterator, element()> {
element_parser(): element_parser::base_type{start_rule_},
embedded_component_(*this)
{
using qi::int_;
/* other definitions except of the 'line' is omitted in sake of simplicity */
line_ = 'L' >> int_ >> int_ >> int_ >> int_ >> int_ >>
int_ >> int_ >> int_ >> int_ >> int_;
// box = ...
// circle = ...
// text = ...
element_rule_ = (line_ | embedded_component_) >> qi::eol;
start_rule_ = qi::skip(qi::blank) [ element_rule_ ];
BOOST_SPIRIT_DEBUG_NODES((element_rule_)(line_));
}
private:
qi::rule<Iterator, element()> start_rule_;
blank_rule<Iterator, element()> element_rule_;
blank_rule<Iterator, line()> line_;
composite_element_parser<Iterator> embedded_component_;
};
template <typename Iterator>
struct composite_element_parser : qi::grammar<Iterator, composite_component()> {
composite_element_parser(element_parser<Iterator> const& ep)
: composite_element_parser::base_type{start_rule_},
element_(ep)
{
using qi::int_;
elements_ = -qi::skip(qi::space) [ '[' >> *element_ >> ']' ];
composite_element_rule_ = 'C' >> int_ >> int_ >> qi::lexeme[+qi::graph] >> elements_;
start_rule_ = qi::skip(qi::blank) [ composite_element_rule_ ];
BOOST_SPIRIT_DEBUG_NODES((composite_element_rule_)(elements_));
}
private:
qi::rule<Iterator, composite_component()> start_rule_;
blank_rule<Iterator, composite_component()> composite_element_rule_;
blank_rule<Iterator, std::vector<element>()> elements_;
element_parser<Iterator> const& element_;
};
template <typename Iterator>
struct document_parser : qi::grammar<Iterator, document()> {
document_parser() : document_parser::base_type{start_rule_}
{
document_rule_ = +element_ >> qi::eoi;
start_rule_ = qi::skip(qi::blank) [ document_rule_ ];
BOOST_SPIRIT_DEBUG_NODES((document_rule_));
}
private:
qi::rule<Iterator, document()> start_rule_;
blank_rule<Iterator, document()> document_rule_;
element_parser<Iterator> element_;
};
int main(int , char **) {
document_parser<std::string::const_iterator> parser;
const std::string text = // "v 20180904 2\n"
"L 1 2 3 4 5 6 7 8 9 10\n"
"C 10 10 FOO\n"
"[ "
" L 10 20 30 40 50 60 70 80 90 100\n"
"]\n";
document doc;
bool r = qi::parse(text.cbegin(), text.cend(), parser, doc);
std::cout << (r ? "OK" : "FAIL") << std::endl;
}
版画
OK
调试输出:
<document_rule_>
<try>L 1 2 3 4 5 6 7 8 9 </try>
<element_rule_>
<try>L 1 2 3 4 5 6 7 8 9 </try>
<line_>
<try>L 1 2 3 4 5 6 7 8 9 </try>
<success>\nC 10 10 FOO\n[ L</success>
<attributes>[[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]]</attributes>
</line_>
<success>C 10 10 FOO\n[ L </success>
<attributes>[[[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]]]</attributes>
</element_rule_>
<element_rule_>
<try>C 10 10 FOO\n[ L </try>
<line_>
<try>C 10 10 FOO\n[ L </try>
<fail/>
</line_>
<composite_element_rule_>
<try>C 10 10 FOO\n[ L </try>
<elements_>
<try>\n[ L 10 20 30 40</try>
<element_rule_>
<try>L 10 20 30 40 50 60 </try>
<line_>
<try>L 10 20 30 40 50 60 </try>
<success>\n]\n</success>
<attributes>[[10, 20, 30, 40, 50, 60, 70, 80, 90, 100]]</attributes>
</line_>
<success>]\n</success>
<attributes>[[[10, 20, 30, 40, 50, 60, 70, 80, 90, 100]]]</attributes>
</element_rule_>
<element_rule_>
<try>]\n</try>
<line_>
<try>]\n</try>
<fail/>
</line_>
<composite_element_rule_>
<try>]\n</try>
<fail/>
</composite_element_rule_>
<fail/>
</element_rule_>
<success>\n</success>
<attributes>[[[[10, 20, 30, 40, 50, 60, 70, 80, 90, 100]]]]</attributes>
</elements_>
<success>\n</success>
<attributes>[[10, 10, [F, O, O], [[[10, 20, 30, 40, 50, 60, 70, 80, 90, 100]]]]]</attributes>
</composite_element_rule_>
<success></success>
<attributes>[[[10, 10, [F, O, O], [[[10, 20, 30, 40, 50, 60, 70, 80, 90, 100]]]]]]</attributes>
</element_rule_>
<element_rule_>
<try></try>
<line_>
<try></try>
<fail/>
</line_>
<composite_element_rule_>
<try></try>
<fail/>
</composite_element_rule_>
<fail/>
</element_rule_>
<success></success>
<attributes>[[[[[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]], [[10, 10, [F, O, O], [[[10, 20, 30, 40, 50, 60, 70, 80, 90, 100]]]]]]]]</attributes>
</document_rule_>
¹ 我通常不这样做,因为它会引发生命周期问题,而且我认为引用成员是一种代码味道,除非 short-lived 仿函数对象
根据我的 我展示了传递引用的方法,我简化了这个答案:
template <typename Iterator>
struct document_parser : qi::grammar<Iterator, document()> {
document_parser() : document_parser::base_type{start_}
{
using namespace qi;
line_ = 'L' >> auto_;
box_ = 'B' >> auto_;
circle_ = 'S' >> auto_;
// text = 'T' >> ...;
element_ = (line_ | box_ | circle_ | composite_element_) >> eol;
elements_ = -skip(space) [ '[' >> skip(blank) [*element_] >> ']' ];
composite_element_ = 'C' >> int_ >> int_ >> lexeme[+graph] >> elements_;
document_ = +element_ >> eoi;
start_ = skip(blank) [ document_ ];
BOOST_SPIRIT_DEBUG_NODES((document_)(element_)(composite_element_)(elements_)(line_));
}
private:
qi::rule<Iterator, document()> start_;
qi::rule<Iterator, document(), qi::blank_type> document_;
qi::rule<Iterator, element(), qi::blank_type> element_;
qi::rule<Iterator, line(), qi::blank_type> line_;
qi::rule<Iterator, box(), qi::blank_type> box_;
qi::rule<Iterator, circle(), qi::blank_type> circle_;
qi::rule<Iterator, composite_component(), qi::blank_type> composite_element_;
qi::rule<Iterator, std::vector<element>(), qi::blank_type> elements_;
};
请注意,它现在也可以解析方框和圆圈。您可能想阅读有关 Auto Parser 的魔法。
#define BOOST_SPIRIT_DEBUG
#include <boost/fusion/adapted.hpp>
#include <boost/spirit/include/qi.hpp>
namespace qi = boost::spirit::qi;
struct line { int x1, y1, x2, y2, color, width, capstyle, dashstyle, dashlength, dashspace; };
struct box { int x, y, width, height, color, line_width, capstyle, dashstyle, dashlength, dashspace, filltype, fillwidth, angle1, pitch1, angle2, pitch2; };
struct circle { int x, y, radius, color, line_width, capstyle, dashstyle, dashlength; };
struct text { int x, y, color, size, visibility, show_name_value, angle, alignment, num_lines;
std::vector<std::string> lines;
};
struct composite_component;
using element_t = boost::variant<line, box, circle, text, boost::recursive_wrapper<composite_component>>;
struct element {
// ...
element_t element;
};
struct composite_component {
int x;
int y;
std::string basename;
std::vector<element> elements;
};
struct document { std::vector<element> elements; };
BOOST_FUSION_ADAPT_STRUCT(line, x1, y1, x2, y2, color, width, capstyle, dashstyle, dashlength, dashspace)
BOOST_FUSION_ADAPT_STRUCT(box, x, y, width, height, color, line_width, capstyle, dashstyle, dashlength, dashspace, filltype, fillwidth, angle1, pitch1, angle2, pitch2)
BOOST_FUSION_ADAPT_STRUCT(circle, x, y, radius, color, line_width, capstyle, dashstyle, dashlength)
BOOST_FUSION_ADAPT_STRUCT(text, x, y, color, size, visibility, show_name_value, angle, alignment, num_lines, lines)
BOOST_FUSION_ADAPT_STRUCT(composite_component, x, y, basename, elements)
BOOST_FUSION_ADAPT_STRUCT(element, element)
BOOST_FUSION_ADAPT_STRUCT(document, elements)
template <typename Iterator>
struct document_parser : qi::grammar<Iterator, document()> {
document_parser() : document_parser::base_type{start_}
{
using namespace qi;
line_ = 'L' >> auto_;
box_ = 'B' >> auto_;
circle_ = 'S' >> auto_;
// text = 'T' >> ...;
element_ = (line_ | box_ | circle_ | composite_element_) >> eol;
elements_ = -skip(space) [ '[' >> skip(blank) [*element_] >> ']' ];
composite_element_ = 'C' >> int_ >> int_ >> lexeme[+graph] >> elements_;
document_ = +element_ >> eoi;
start_ = skip(blank) [ document_ ];
BOOST_SPIRIT_DEBUG_NODES((document_)(element_)(composite_element_)(elements_)(line_)(box_)(circle_));
}
private:
qi::rule<Iterator, document()> start_;
qi::rule<Iterator, document(), qi::blank_type> document_;
qi::rule<Iterator, element(), qi::blank_type> element_;
qi::rule<Iterator, line(), qi::blank_type> line_;
qi::rule<Iterator, box(), qi::blank_type> box_;
qi::rule<Iterator, circle(), qi::blank_type> circle_;
qi::rule<Iterator, composite_component(), qi::blank_type> composite_element_;
qi::rule<Iterator, std::vector<element>(), qi::blank_type> elements_;
};
int main(int , char **) {
document_parser<std::string::const_iterator> parser;
const std::string text = // "v 20180904 2\n"
"L 1 2 3 4 5 6 7 8 9 10\n"
"C 10 10 FOO\n"
"[ "
" L 10 20 30 40 50 60 70 80 90 100\n"
"]\n";
document doc;
bool r = qi::parse(text.cbegin(), text.cend(), parser, doc);
std::cout << (r ? "OK" : "FAIL") << std::endl;
}
版画
OK
调试输出:
<document_>
<try>L 1 2 3 4 5 6 7 8 9 </try>
<element_>
<try>L 1 2 3 4 5 6 7 8 9 </try>
<line_>
<try>L 1 2 3 4 5 6 7 8 9 </try>
<success>\nC 10 10 FOO\n[ L</success>
<attributes>[[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]]</attributes>
</line_>
<success>C 10 10 FOO\n[ L </success>
<attributes>[[[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]]]</attributes>
</element_>
<element_>
<try>C 10 10 FOO\n[ L </try>
<line_>
<try>C 10 10 FOO\n[ L </try>
<fail/>
</line_>
<box_>
<try>C 10 10 FOO\n[ L </try>
<fail/>
</box_>
<circle_>
<try>C 10 10 FOO\n[ L </try>
<fail/>
</circle_>
<composite_element_>
<try>C 10 10 FOO\n[ L </try>
<elements_>
<try>\n[ L 10 20 30 40</try>
<element_>
<try> L 10 20 30 40 5</try>
<line_>
<try> L 10 20 30 40 5</try>
<success>\n]\n</success>
<attributes>[[10, 20, 30, 40, 50, 60, 70, 80, 90, 100]]</attributes>
</line_>
<success>]\n</success>
<attributes>[[[10, 20, 30, 40, 50, 60, 70, 80, 90, 100]]]</attributes>
</element_>
<element_>
<try>]\n</try>
<line_>
<try>]\n</try>
<fail/>
</line_>
<box_>
<try>]\n</try>
<fail/>
</box_>
<circle_>
<try>]\n</try>
<fail/>
</circle_>
<composite_element_>
<try>]\n</try>
<fail/>
</composite_element_>
<fail/>
</element_>
<success>\n</success>
<attributes>[[[[10, 20, 30, 40, 50, 60, 70, 80, 90, 100]]]]</attributes>
</elements_>
<success>\n</success>
<attributes>[[10, 10, [F, O, O], [[[10, 20, 30, 40, 50, 60, 70, 80, 90, 100]]]]]</attributes>
</composite_element_>
<success></success>
<attributes>[[[10, 10, [F, O, O], [[[10, 20, 30, 40, 50, 60, 70, 80, 90, 100]]]]]]</attributes>
</element_>
<element_>
<try></try>
<line_>
<try></try>
<fail/>
</line_>
<box_>
<try></try>
<fail/>
</box_>
<circle_>
<try></try>
<fail/>
</circle_>
<composite_element_>
<try></try>
<fail/>
</composite_element_>
<fail/>
</element_>
<success></success>
<attributes>[[[[[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]], [[10, 10, [F, O, O], [[[10, 20, 30, 40, 50, 60, 70, 80, 90, 100]]]]]]]]</attributes>
</document_>
我正在使用 Boost.Spirit Qi 从一些文本数据构建相当复杂的结构。数据结构可能是递归定义的,所以我需要两个语法来相互引用,这就是问题出现的地方。
比如我有一个语法:
element = line | text | circle | box | composite_element
composite_element = 'C', int, int, '[', +element, ']'
显然,我需要这样的东西:
#include <boost/config/warning_disable.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/spirit/include/phoenix_core.hpp>
#include <boost/spirit/include/phoenix_operator.hpp>
#include <boost/spirit/include/phoenix_fusion.hpp>
#include <boost/spirit/include/phoenix_stl.hpp>
#include <tuple>
#include <boost/fusion/adapted/std_pair.hpp>
#include <boost/fusion/include/std_pair.hpp>
#include <boost/spirit/include/qi_eol.hpp>
#include <boost/phoenix.hpp>
#include <vector>
#include <string>
namespace qi = boost::spirit::qi;
namespace ascii = boost::spirit::ascii;
namespace phoenix = boost::phoenix;
struct line {
int x1;
int y1;
int x2;
int y2;
int color;
int width;
int capstyle;
int dashstyle;
int dashlength;
int dashspace;
};
struct box {
int x;
int y;
int width;
int height;
int color;
int line_width;
int capstyle;
int dashstyle;
int dashlength;
int dashspace;
int filltype;
int fillwidth;
int angle1;
int pitch1;
int angle2;
int pitch2;
};
struct circle {
int x;
int y;
int radius;
int color;
int line_width;
int capstyle;
int dashstyle;
int dashlength;
};
struct text {
int x;
int y;
int color;
int size;
int visibility;
int show_name_value;
int angle;
int alignment;
int num_lines;
std::vector<std::string> lines;
};
struct composite_component;
using element_t = boost::variant<line, box, circle, text, boost::recursive_wrapper<composite_component>>;
struct composite_component {
int x;
int y;
std::string basename;
// only used if component is embedded
// i. e. stores its definition within the schematic file
std::vector<element_t> elements;
};
struct element {
// some other fields
// ...
element_t element;
};
struct document {
std::vector<element> elements;
};
BOOST_FUSION_ADAPT_STRUCT(line, x1, y1, x2, y2, color, width, capstyle, dashstyle, dashlength, dashspace)
BOOST_FUSION_ADAPT_STRUCT(box, x, y, width, height, color, line_width, capstyle, dashstyle, dashlength, dashspace, filltype, fillwidth, angle1, pitch1, angle2, pitch2)
BOOST_FUSION_ADAPT_STRUCT(circle, x, y, radius, color, line_width, capstyle, dashstyle, dashlength)
BOOST_FUSION_ADAPT_STRUCT(text, x, y, color, size, visibility, show_name_value, angle, alignment, num_lines, lines)
BOOST_FUSION_ADAPT_STRUCT(composite_component, x, y, basename, elements)
BOOST_FUSION_ADAPT_STRUCT(element, element)
BOOST_FUSION_ADAPT_STRUCT(document, elements)
template <typename Iterator, typename Attribute>
using rule = qi::rule<Iterator, Attribute, qi::blank_type>;
template <typename Iterator>
class composite_element_parser;
template <typename Iterator>
class element_parser : public qi::grammar<Iterator, element(), qi::blank_type> {
public:
element_parser(): element_parser::base_type{start_rule_}
{
using qi::int_;
using qi::repeat;
using phoenix::val;
using phoenix::construct;
/* other definitions except of the 'line' is omitted in sake of simplicity */
line_ = 'L' >> int_ >> int_ >> int_ >> int_ >> int_ >>
int_ >> int_ >> int_ >> int_ >> int_ >> qi::eol;
// box = ...
// circle = ...
// text = ...
start_rule_ = (line_ /* || embedded_component_ */) >> qi::eoi;
}
private:
rule<Iterator, element()> start_rule_;
rule<Iterator, line()> line_;
// here comes the problem - CIRCULAR REFERENCE to incompletely defined template
// composite_element_parser<Iterator> embedded_component_;
};
template <typename Iterator>
class composite_element_parser : public qi::grammar<Iterator, composite_component(), qi::blank_type> {
public:
composite_element_parser() : composite_element_parser::base_type{start_rule_}
{
using phoenix::at_c;
using qi::int_;
using phoenix::push_back;
start_rule_ = "C" >> int_ >> int_ >> qi::lexeme[(qi::char_)[at_c<2>(qi::_val) += qi::_1]]
>> -(
"[" >>
*(element_) [push_back(at_c<3>(qi::_val), qi::_1)] >>
"]"
);
}
private:
rule<Iterator, composite_component()> start_rule_;
element_parser<Iterator> element_;
};
template <typename Iterator>
class document_parser : public qi::grammar<Iterator, document(), qi::blank_type> {
public:
document_parser() : document_parser::base_type{start_rule_}
{
using phoenix::at_c;
using phoenix::push_back;
using qi::_val;
using qi::_0;
using qi::_1;
start_rule_ = +(element_)[push_back(at_c<0>(_val), _1)] >> qi::eoi;
}
private:
rule<Iterator, document()> start_rule_;
element_parser<Iterator> element_;
};
int main(int , char **) {
document_parser<std::string::const_iterator> parser;
document doc;
const std::string text = "v 20180904 2\n"
"L 1 2 3 4 5 6 7 8 9 10\n"
"C 10 10 FOO\n"
"[ "
"L 1 2 3 4 5 6 7 8 9 10\n"
"]\n";
bool r = qi::phrase_parse(text.cbegin(), text.cend(), parser, qi::blank, doc);
std::cout << (r ? "OK" : "FAIL") << std::endl;
return 0;
}
不过,'text'、'circle' 和 'box' 的规则定义被省略了。请注意 element_parser
定义的私有部分中的注释 - 编译器将无法实例化不完整的 class 模板 composite_element_parser<Iterator>
。我该怎么办?显然,我不能将 element_parser
和 composite_element_parser
作为顶级语法的成员(在我的例子中是 document_parser
)并在构造函数中将它们 references/pointers 传递给彼此初始化列表,因为它们目前未初始化。
更新: 这个帖子可能被认为是 Deeply-recursive qi grammars (parsers) with synthesized and inherited attributes 的重复,但我真的无法理解已批准的答案。
通常你不会那样拆分语法。但如果你真的想要,有多种方法:
单独创建语法并在外部将语法分配给
rule
占位符:#include <boost/spirit/include/qi.hpp> namespace qi = boost::spirit::qi; template <typename Iterator> struct grammar1 : qi::grammar<Iterator, int()> { grammar1() : grammar1::base_type{start_} { start_ = '[' >> outer >> ']'; } qi::rule<Iterator, int()> outer; private: qi::rule<Iterator, int()> start_; }; template <typename Iterator> struct grammar2 : qi::grammar<Iterator, int()> { grammar2() : grammar2::base_type{start_} { start_ = outer | qi::int_; } qi::rule<Iterator, int()> outer; private: qi::rule<Iterator, int()> start_; }; int main() { char const* s = "[[123]]", * e = s + std::strlen(s); grammar2<char const*> g2; grammar1<char const*> g1; g2.outer = g1; g1.outer = g2; int value = 0; if (qi::parse(s, e, g1, value)) std::cout << value << '\n'; else std::cout << "failed\n"; }
在另一个动态创建一个语法并传递给它一个前者的引用:
#include <boost/spirit/include/qi.hpp> namespace qi = boost::spirit::qi; template <typename Iterator> struct grammar2; template <typename Iterator> struct grammar1 : qi::grammar<Iterator, int()> { grammar1() : grammar1::base_type{start_} { outer_ = std::make_unique<grammar2<Iterator>>(start_); start_ = '[' >> *outer_ >> ']'; // NOTE: it is not a kleen star! } private: std::unique_ptr<grammar2<Iterator>> outer_; qi::rule<Iterator, int()> start_; }; template <typename Iterator> struct grammar2 : qi::grammar<Iterator, int()> { explicit grammar2(qi::rule<Iterator, int()> const& outer) : grammar2::base_type{start_} { start_ = outer | qi::int_; } private: qi::rule<Iterator, int()> start_; }; int main() { char const* s = "[[123]]", * e = s + std::strlen(s); grammar1<char const*> const g1; int value = 0; if (qi::parse(s, e, g1, value)) std::cout << value << '\n'; else std::cout << "failed\n"; }
我认为在你的例子中没有必要使用语法实例。
规则可以相互引用recursively/cyclically(因为它们通过引用相互引用)。我会利用这个。
You can still separate out grammar classes (e.g. to separate the implementations into compilation units) but you just want to glue them together at a central spot where you have the instances that you can then mutually reference (in other words: classical ownership management: if none of the classes can own objects, have another entity own them both).
或者,您可以简单地在构造函数中传递对互补语法的引用,并保留它们而不是语法实例。
演示
为了方便起见,我采用了第二种方法¹。
我改变了一些东西:
- 将
composite_element::elements
的类型从std::vector<element_t>
固定为std::vector<element>
修复了围绕 white-space 跳过的一些问题:
'[' ... ']'
块解析不允许您的输入显示的换行符(参见skip(qi::space)
)eol
仅在line_
之后才需要,但您的输入显示在其他元素之后- 元素解析器错误地需要
eoi
- 这导致解析在第一个元素之后停止(如果不是 EOI 则失败)。 - 船长end-user无法使用。我的信条是对调用者隐藏它,除非你想让调用者真正能够改变船长
增加了方便
start_rule_
编码船长以及确保每个语法的 top-level 规则在调试输出中有用地显示(如果一切都被调用start_rule_
,信息所剩无几)删除了所有内容
phoenix
(参见 Boost Spirit: "Semantic actions are evil"?):这个例子:
start_rule_ = +(element_)[push_back(at_c<0>(_val), _1)] >> qi::eoi;
这就是自动属性传播已经完成的工作,所以这就足够了:
start_rule_ = +elements_ >> qi::eoi;
这个:
qi::lexeme[(qi::char_)[at_c<2>(qi::_val) += qi::_1]]
有一些问题:它缺少重复(它只解析 1 个字符),它没有指定接受哪些字符,sp 如果重复将读取直到 EOI。我怀疑这就是你想要的:
qi::lexeme[+qi::graph]
另见 Boost spirit skipper issues
已将
||
更改为|
(参见 Alternative Parser vs Sequential-Or Parser)。也许更多但我忘记了?哦对了,我评论了
v
行。
#define BOOST_SPIRIT_DEBUG
#include <boost/fusion/adapted.hpp>
#include <boost/spirit/include/qi.hpp>
namespace qi = boost::spirit::qi;
struct line { int x1, y1, x2, y2, color, width, capstyle, dashstyle, dashlength, dashspace; };
struct box { int x, y, width, height, color, line_width, capstyle, dashstyle, dashlength, dashspace, filltype, fillwidth, angle1, pitch1, angle2, pitch2; };
struct circle { int x, y, radius, color, line_width, capstyle, dashstyle, dashlength; };
struct text { int x, y, color, size, visibility, show_name_value, angle, alignment, num_lines;
std::vector<std::string> lines;
};
struct composite_component;
using element_t = boost::variant<line, box, circle, text, boost::recursive_wrapper<composite_component>>;
struct element {
// ...
element_t element;
};
struct composite_component {
int x;
int y;
std::string basename;
std::vector<element> elements;
};
struct document { std::vector<element> elements; };
BOOST_FUSION_ADAPT_STRUCT(line, x1, y1, x2, y2, color, width, capstyle, dashstyle, dashlength, dashspace)
BOOST_FUSION_ADAPT_STRUCT(box, x, y, width, height, color, line_width, capstyle, dashstyle, dashlength, dashspace, filltype, fillwidth, angle1, pitch1, angle2, pitch2)
BOOST_FUSION_ADAPT_STRUCT(circle, x, y, radius, color, line_width, capstyle, dashstyle, dashlength)
BOOST_FUSION_ADAPT_STRUCT(text, x, y, color, size, visibility, show_name_value, angle, alignment, num_lines, lines)
BOOST_FUSION_ADAPT_STRUCT(composite_component, x, y, basename, elements)
BOOST_FUSION_ADAPT_STRUCT(element, element)
BOOST_FUSION_ADAPT_STRUCT(document, elements)
template <typename Iterator, typename Attribute>
using blank_rule = qi::rule<Iterator, Attribute, qi::blank_type>;
template <typename Iterator>
struct composite_element_parser;
template <typename Iterator>
struct element_parser : qi::grammar<Iterator, element()> {
element_parser(): element_parser::base_type{start_rule_},
embedded_component_(*this)
{
using qi::int_;
/* other definitions except of the 'line' is omitted in sake of simplicity */
line_ = 'L' >> int_ >> int_ >> int_ >> int_ >> int_ >>
int_ >> int_ >> int_ >> int_ >> int_;
// box = ...
// circle = ...
// text = ...
element_rule_ = (line_ | embedded_component_) >> qi::eol;
start_rule_ = qi::skip(qi::blank) [ element_rule_ ];
BOOST_SPIRIT_DEBUG_NODES((element_rule_)(line_));
}
private:
qi::rule<Iterator, element()> start_rule_;
blank_rule<Iterator, element()> element_rule_;
blank_rule<Iterator, line()> line_;
composite_element_parser<Iterator> embedded_component_;
};
template <typename Iterator>
struct composite_element_parser : qi::grammar<Iterator, composite_component()> {
composite_element_parser(element_parser<Iterator> const& ep)
: composite_element_parser::base_type{start_rule_},
element_(ep)
{
using qi::int_;
elements_ = -qi::skip(qi::space) [ '[' >> *element_ >> ']' ];
composite_element_rule_ = 'C' >> int_ >> int_ >> qi::lexeme[+qi::graph] >> elements_;
start_rule_ = qi::skip(qi::blank) [ composite_element_rule_ ];
BOOST_SPIRIT_DEBUG_NODES((composite_element_rule_)(elements_));
}
private:
qi::rule<Iterator, composite_component()> start_rule_;
blank_rule<Iterator, composite_component()> composite_element_rule_;
blank_rule<Iterator, std::vector<element>()> elements_;
element_parser<Iterator> const& element_;
};
template <typename Iterator>
struct document_parser : qi::grammar<Iterator, document()> {
document_parser() : document_parser::base_type{start_rule_}
{
document_rule_ = +element_ >> qi::eoi;
start_rule_ = qi::skip(qi::blank) [ document_rule_ ];
BOOST_SPIRIT_DEBUG_NODES((document_rule_));
}
private:
qi::rule<Iterator, document()> start_rule_;
blank_rule<Iterator, document()> document_rule_;
element_parser<Iterator> element_;
};
int main(int , char **) {
document_parser<std::string::const_iterator> parser;
const std::string text = // "v 20180904 2\n"
"L 1 2 3 4 5 6 7 8 9 10\n"
"C 10 10 FOO\n"
"[ "
" L 10 20 30 40 50 60 70 80 90 100\n"
"]\n";
document doc;
bool r = qi::parse(text.cbegin(), text.cend(), parser, doc);
std::cout << (r ? "OK" : "FAIL") << std::endl;
}
版画
OK
调试输出:
<document_rule_>
<try>L 1 2 3 4 5 6 7 8 9 </try>
<element_rule_>
<try>L 1 2 3 4 5 6 7 8 9 </try>
<line_>
<try>L 1 2 3 4 5 6 7 8 9 </try>
<success>\nC 10 10 FOO\n[ L</success>
<attributes>[[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]]</attributes>
</line_>
<success>C 10 10 FOO\n[ L </success>
<attributes>[[[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]]]</attributes>
</element_rule_>
<element_rule_>
<try>C 10 10 FOO\n[ L </try>
<line_>
<try>C 10 10 FOO\n[ L </try>
<fail/>
</line_>
<composite_element_rule_>
<try>C 10 10 FOO\n[ L </try>
<elements_>
<try>\n[ L 10 20 30 40</try>
<element_rule_>
<try>L 10 20 30 40 50 60 </try>
<line_>
<try>L 10 20 30 40 50 60 </try>
<success>\n]\n</success>
<attributes>[[10, 20, 30, 40, 50, 60, 70, 80, 90, 100]]</attributes>
</line_>
<success>]\n</success>
<attributes>[[[10, 20, 30, 40, 50, 60, 70, 80, 90, 100]]]</attributes>
</element_rule_>
<element_rule_>
<try>]\n</try>
<line_>
<try>]\n</try>
<fail/>
</line_>
<composite_element_rule_>
<try>]\n</try>
<fail/>
</composite_element_rule_>
<fail/>
</element_rule_>
<success>\n</success>
<attributes>[[[[10, 20, 30, 40, 50, 60, 70, 80, 90, 100]]]]</attributes>
</elements_>
<success>\n</success>
<attributes>[[10, 10, [F, O, O], [[[10, 20, 30, 40, 50, 60, 70, 80, 90, 100]]]]]</attributes>
</composite_element_rule_>
<success></success>
<attributes>[[[10, 10, [F, O, O], [[[10, 20, 30, 40, 50, 60, 70, 80, 90, 100]]]]]]</attributes>
</element_rule_>
<element_rule_>
<try></try>
<line_>
<try></try>
<fail/>
</line_>
<composite_element_rule_>
<try></try>
<fail/>
</composite_element_rule_>
<fail/>
</element_rule_>
<success></success>
<attributes>[[[[[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]], [[10, 10, [F, O, O], [[[10, 20, 30, 40, 50, 60, 70, 80, 90, 100]]]]]]]]</attributes>
</document_rule_>
¹ 我通常不这样做,因为它会引发生命周期问题,而且我认为引用成员是一种代码味道,除非 short-lived 仿函数对象
根据我的
template <typename Iterator>
struct document_parser : qi::grammar<Iterator, document()> {
document_parser() : document_parser::base_type{start_}
{
using namespace qi;
line_ = 'L' >> auto_;
box_ = 'B' >> auto_;
circle_ = 'S' >> auto_;
// text = 'T' >> ...;
element_ = (line_ | box_ | circle_ | composite_element_) >> eol;
elements_ = -skip(space) [ '[' >> skip(blank) [*element_] >> ']' ];
composite_element_ = 'C' >> int_ >> int_ >> lexeme[+graph] >> elements_;
document_ = +element_ >> eoi;
start_ = skip(blank) [ document_ ];
BOOST_SPIRIT_DEBUG_NODES((document_)(element_)(composite_element_)(elements_)(line_));
}
private:
qi::rule<Iterator, document()> start_;
qi::rule<Iterator, document(), qi::blank_type> document_;
qi::rule<Iterator, element(), qi::blank_type> element_;
qi::rule<Iterator, line(), qi::blank_type> line_;
qi::rule<Iterator, box(), qi::blank_type> box_;
qi::rule<Iterator, circle(), qi::blank_type> circle_;
qi::rule<Iterator, composite_component(), qi::blank_type> composite_element_;
qi::rule<Iterator, std::vector<element>(), qi::blank_type> elements_;
};
请注意,它现在也可以解析方框和圆圈。您可能想阅读有关 Auto Parser 的魔法。
#define BOOST_SPIRIT_DEBUG
#include <boost/fusion/adapted.hpp>
#include <boost/spirit/include/qi.hpp>
namespace qi = boost::spirit::qi;
struct line { int x1, y1, x2, y2, color, width, capstyle, dashstyle, dashlength, dashspace; };
struct box { int x, y, width, height, color, line_width, capstyle, dashstyle, dashlength, dashspace, filltype, fillwidth, angle1, pitch1, angle2, pitch2; };
struct circle { int x, y, radius, color, line_width, capstyle, dashstyle, dashlength; };
struct text { int x, y, color, size, visibility, show_name_value, angle, alignment, num_lines;
std::vector<std::string> lines;
};
struct composite_component;
using element_t = boost::variant<line, box, circle, text, boost::recursive_wrapper<composite_component>>;
struct element {
// ...
element_t element;
};
struct composite_component {
int x;
int y;
std::string basename;
std::vector<element> elements;
};
struct document { std::vector<element> elements; };
BOOST_FUSION_ADAPT_STRUCT(line, x1, y1, x2, y2, color, width, capstyle, dashstyle, dashlength, dashspace)
BOOST_FUSION_ADAPT_STRUCT(box, x, y, width, height, color, line_width, capstyle, dashstyle, dashlength, dashspace, filltype, fillwidth, angle1, pitch1, angle2, pitch2)
BOOST_FUSION_ADAPT_STRUCT(circle, x, y, radius, color, line_width, capstyle, dashstyle, dashlength)
BOOST_FUSION_ADAPT_STRUCT(text, x, y, color, size, visibility, show_name_value, angle, alignment, num_lines, lines)
BOOST_FUSION_ADAPT_STRUCT(composite_component, x, y, basename, elements)
BOOST_FUSION_ADAPT_STRUCT(element, element)
BOOST_FUSION_ADAPT_STRUCT(document, elements)
template <typename Iterator>
struct document_parser : qi::grammar<Iterator, document()> {
document_parser() : document_parser::base_type{start_}
{
using namespace qi;
line_ = 'L' >> auto_;
box_ = 'B' >> auto_;
circle_ = 'S' >> auto_;
// text = 'T' >> ...;
element_ = (line_ | box_ | circle_ | composite_element_) >> eol;
elements_ = -skip(space) [ '[' >> skip(blank) [*element_] >> ']' ];
composite_element_ = 'C' >> int_ >> int_ >> lexeme[+graph] >> elements_;
document_ = +element_ >> eoi;
start_ = skip(blank) [ document_ ];
BOOST_SPIRIT_DEBUG_NODES((document_)(element_)(composite_element_)(elements_)(line_)(box_)(circle_));
}
private:
qi::rule<Iterator, document()> start_;
qi::rule<Iterator, document(), qi::blank_type> document_;
qi::rule<Iterator, element(), qi::blank_type> element_;
qi::rule<Iterator, line(), qi::blank_type> line_;
qi::rule<Iterator, box(), qi::blank_type> box_;
qi::rule<Iterator, circle(), qi::blank_type> circle_;
qi::rule<Iterator, composite_component(), qi::blank_type> composite_element_;
qi::rule<Iterator, std::vector<element>(), qi::blank_type> elements_;
};
int main(int , char **) {
document_parser<std::string::const_iterator> parser;
const std::string text = // "v 20180904 2\n"
"L 1 2 3 4 5 6 7 8 9 10\n"
"C 10 10 FOO\n"
"[ "
" L 10 20 30 40 50 60 70 80 90 100\n"
"]\n";
document doc;
bool r = qi::parse(text.cbegin(), text.cend(), parser, doc);
std::cout << (r ? "OK" : "FAIL") << std::endl;
}
版画
OK
调试输出:
<document_>
<try>L 1 2 3 4 5 6 7 8 9 </try>
<element_>
<try>L 1 2 3 4 5 6 7 8 9 </try>
<line_>
<try>L 1 2 3 4 5 6 7 8 9 </try>
<success>\nC 10 10 FOO\n[ L</success>
<attributes>[[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]]</attributes>
</line_>
<success>C 10 10 FOO\n[ L </success>
<attributes>[[[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]]]</attributes>
</element_>
<element_>
<try>C 10 10 FOO\n[ L </try>
<line_>
<try>C 10 10 FOO\n[ L </try>
<fail/>
</line_>
<box_>
<try>C 10 10 FOO\n[ L </try>
<fail/>
</box_>
<circle_>
<try>C 10 10 FOO\n[ L </try>
<fail/>
</circle_>
<composite_element_>
<try>C 10 10 FOO\n[ L </try>
<elements_>
<try>\n[ L 10 20 30 40</try>
<element_>
<try> L 10 20 30 40 5</try>
<line_>
<try> L 10 20 30 40 5</try>
<success>\n]\n</success>
<attributes>[[10, 20, 30, 40, 50, 60, 70, 80, 90, 100]]</attributes>
</line_>
<success>]\n</success>
<attributes>[[[10, 20, 30, 40, 50, 60, 70, 80, 90, 100]]]</attributes>
</element_>
<element_>
<try>]\n</try>
<line_>
<try>]\n</try>
<fail/>
</line_>
<box_>
<try>]\n</try>
<fail/>
</box_>
<circle_>
<try>]\n</try>
<fail/>
</circle_>
<composite_element_>
<try>]\n</try>
<fail/>
</composite_element_>
<fail/>
</element_>
<success>\n</success>
<attributes>[[[[10, 20, 30, 40, 50, 60, 70, 80, 90, 100]]]]</attributes>
</elements_>
<success>\n</success>
<attributes>[[10, 10, [F, O, O], [[[10, 20, 30, 40, 50, 60, 70, 80, 90, 100]]]]]</attributes>
</composite_element_>
<success></success>
<attributes>[[[10, 10, [F, O, O], [[[10, 20, 30, 40, 50, 60, 70, 80, 90, 100]]]]]]</attributes>
</element_>
<element_>
<try></try>
<line_>
<try></try>
<fail/>
</line_>
<box_>
<try></try>
<fail/>
</box_>
<circle_>
<try></try>
<fail/>
</circle_>
<composite_element_>
<try></try>
<fail/>
</composite_element_>
<fail/>
</element_>
<success></success>
<attributes>[[[[[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]], [[10, 10, [F, O, O], [[[10, 20, 30, 40, 50, 60, 70, 80, 90, 100]]]]]]]]</attributes>
</document_>