我正在 Spirit X3 中编写一个解析器,以便熟悉它,尽管我非常熟悉 Qi,但在 X3 中我仍然遇到了一些障碍。
例如,Qi 示例包括一个基本的 XML 解析器,您可以通过该解析器了解如何使用 Phoenix 占位符来匹配先前匹配的值。然而,我只能在 X3 中弄清楚这一点:
#include <iostream>
#include <boost/spirit/home/x3.hpp>
#include <boost/fusion/include/adapt_struct.hpp>
namespace x3 = boost::spirit::x3;
namespace mytest
{
struct SimpleElement
{
std::string tag;
std::string content;
};
} // namespace bbspirit
BOOST_FUSION_ADAPT_STRUCT
(
mytest::SimpleElement, tag, content
)
namespace mytest
{
namespace x3 = boost::spirit::x3;
namespace ascii = boost::spirit::x3::ascii;
using x3::lit;
using x3::lexeme;
using ascii::char_;
const x3::rule<class SimpleElementID, SimpleElement> simpleTag = "simpleTag";
auto assignTag = [](auto& ctx)
{
x3::_val(ctx).tag = x3::_attr(ctx);
};
auto testTag = [](auto& ctx)
{
x3::_pass(ctx) =
(x3::_val(ctx).tag == x3::_attr(ctx));
};
auto assignContent = [](auto& ctx)
{
x3::_val(ctx).content = x3::_attr(ctx);
};
auto const simpleTag_def
= '['
>> x3::lexeme[+(char_ - ']')][assignTag]
>> ']'
>> x3::lexeme[
+(char_ - x3::lit("[/"))]
[assignContent]
>> "[/"
>> x3::lexeme[+(char_ - ']')][testTag]
>> ']'
;
BOOST_SPIRIT_DEFINE(simpleTag);
} // namespace bbspirit
int main()
{
const std::string text = "[test]Hello World![/test]";
std::string::const_iterator start = std::begin(text);
const std::string::const_iterator stop = std::end(text);
mytest::SimpleElement element{};
bool result =
phrase_parse(start, stop, mytest::simpleTag, x3::ascii::space, element);
if (!result)
{
std::cout << "failed to parse!\n";
}
else
{
std::cout << "tag : " << element.tag << '\n';
std::cout << "content: " << element.content << '\n';
}
}
(链接:https://wandbox.org/permlink/xLZN9plcOwkSKCrD)
这是可行的,但是如果我尝试解析类似 [test]Hello [/World[/test]
的内容,它就不起作用,因为我没有在此处指定正确的省略:
>> x3::lexeme[
+(char_ - x3::lit("[/"))]
[assignContent]
本质上我想告诉解析器类似的事情:
>> x3::lexeme[
+(char_ - (x3::lit("[/") << *the start tag* << ']') )]
[assignContent]
我怎样才能做到这一点?另外,我引用开始标记并随后将其匹配的方式是在 X3 中执行此操作的“最佳”方式还是有更好/更优选的方式?
谢谢!
最佳答案
好问题。
最好的答案就是按照 XML 的方式去做:outlaw [/
在标签数据内。事实上,XML 是非法的 <
(因为它可能打开一个嵌套标签,并且您不希望必须提前读取整个流来查找它是否是有效的子标签)。
XML uses character entities ("escapes" like
<
and>
) or unparsed character data (CDATA[]
) to encode contents that requires these characters.
接下来,您当然可以使用 !closeTag
进行否定前瞻断言( -closeTag
或 tag
)属性成员就像你已经做的那样。
重新调整拼写规则,这还不算太糟糕
Note I removed the need for manual propagation of the tag/contents using the
, true>
template argument onsimpleTag
rule. See Boost Spirit: "Semantic actions are evil"?
const x3::rule<class SimpleElementID, SimpleElement, true> simpleTag = "simpleTag";
auto testTag = [](auto& ctx) { _pass(ctx) = (_val(ctx).tag == _attr(ctx)); };
auto openTag = '[' >> x3::lexeme[+(char_ - ']')] >> ']';
auto closeTag = "[/" >> x3::lexeme[+(char_ - ']')] [testTag] >> ']';
auto tagContents = x3::lexeme[ +(char_ - closeTag) ];
auto const simpleTag_def
= openTag
>> tagContents
>> x3::omit [ closeTag ]
;
背景
这可行,但最终变得非常笨拙,因为这意味着到处使用语义操作,并且也违背了属性引用的自然绑定(bind)。
跳出框框思考:
在 Qi 中你可以使用 qi::locals
或inherited attributes为此(请参阅文档中非常相似的示例: MiniXML )。
这两者都会产生用您的信息扩展解析器上下文的最终效果。
X3没有这样的“高级”功能。但它确实具有扩展您的上下文的构建 block :x3::witt<>(data) [ p ]
.
x3::with
在这个简单的示例中,这似乎有点矫枉过正,但在某些时候,您会欣赏如何在规则中使用额外的上下文,而不会将属性类型作为人质:
struct TagName{};
auto openTag
= x3::rule<struct openTagID, std::string, true> {"openTag"}
= ('[' >> x3::lexeme[+(char_ - ']')] >> ']')
[([](auto& ctx) { x3::get<TagName>(ctx) = _attr(ctx); })]
;
auto closeTag
= x3::rule<struct closeTagID, std::string, true> {"closeTag"}
= ("[/" >> x3::lexeme[+(char_ - ']')] >> ']')
[([](auto& ctx) { _pass(ctx) = (x3::get<TagName>(ctx) == _attr(ctx)); })]
;
auto tagContents
= x3::rule<struct openTagID, std::string> {"tagContents"}
= x3::lexeme[ +(char_ - closeTag) ];
auto const simpleTag
= x3::rule<class SimpleElementID, SimpleElement, true> {"simpleTag"}
= x3::with<TagName>(std::string()) [
openTag
>> tagContents
>> x3::omit [ closeTag ]
];
关于c++ - Spirit X3,指的是之前匹配的值,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62143841/