如何以激励精神构建默认值语法
How to build grammar of default value with boost spirit?
我正在分析一些科学文本,其格式类似于
Keyword
{ 1.0 22.2 59.6 'cm' 'yes' }
我是第一次使用spirit,经过对文档的学习,我可以使用spirit来解决固定格式的关键字。
但是对于下面的格式,我不知道如何构建语法。我的问题是:在我遇到的科学关键字中,某些数据项可以默认为内置默认值。关键字描述说明何时可以应用缺省值。有两种方法可以将数量设置为默认值。首先,通过使用斜杠'}'提前结束数据记录,将未指定的数量设置为其默认值。其次,可以通过输入n*来默认位于'}'之前的选定数量,其中n为连续默认数量。例如,3*会使关键字data中的后三个数量被赋予其默认值。
例如,Person
{ 'Tom' 188 80 'male' 32 }
表示'male'和'32'是默认值,其等效值可以为:
Person
{ 'Tom' 188 88 2* }
或
Person
{ 'Tom' 188 88 'male' 1* }
或
Person
{ 'Tom' 188 88 }
我搜索了过去的帖子,这给了我一些想法,但是我怎么写n*的规则?
您要求的解析器非常复杂,因为它必须解决几个任务:
- 处理末尾缺失的元素
- 处理"2*"语法作为末尾缺失元素的替换
- 不仅正确解析所有有效的输入,而且用匹配的值填充给定的数据结构
qi::attr
:
为缺少的元素提供默认值:
qi::int_ | qi::attr(180)
。匹配整数或使用默认值
180
为"2*"语法提供所有剩余的值(如@vines建议的):
"2*" >> qi::attr(attr2)
。如果
2*
匹配,则使用默认值attr2(即fusion::vector
)。
总的来说,我想出了这个解决方案,它似乎解析并返回默认值很好(即使它看起来非常复杂):
#include <string>
#include <iostream>
#include <boost/spirit/include/qi.hpp>
#include <boost/fusion/include/vector.hpp>
int main()
{
namespace qi = boost::spirit::qi;
namespace fusion = boost::fusion;
// the attribute passed to the parser has to match (in structure) the
// parser, requiring to create nested fusion::vector's
typedef fusion::vector<std::string, int> attribute1_type;
typedef fusion::vector<int, attribute1_type> attribute2_type;
typedef fusion::vector<int, attribute2_type> attribute3_type;
// overall attribute type
typedef fusion::vector<std::string, attribute3_type> attribute_type;
// initialize attributes with default values
attribute1_type attr1("male", 32);
attribute2_type attr2(80, attr1);
attribute3_type attr3(180, attr2);
qi::rule<std::string::iterator, std::string()> quoted_string =
"'" >> *~qi::char_("'") >> "'";
qi::rule<std::string::iterator, attribute_type(), qi::space_type> data =
qi::lit("Person") >> "{"
>> quoted_string
>> -( ("4*" >> qi::attr(attr3))
| (qi::int_ | qi::attr(180))
>> -( ("3*" >> qi::attr(attr2))
| (qi::int_ | qi::attr(80))
>> -( ("2*" >> qi::attr(attr1))
| (quoted_string | qi::attr("male"))
>> -( "1*"
| qi::int_
| qi::attr(32)
)
)
)
)
>> "}";
std::string in1 = "Personn{ 'Tom' 188 80 'male' 32 }";
attribute_type fullattr1;
if (qi::phrase_parse(in1.begin(), in1.end(), data, qi::space, fullattr1))
std::cout << fullattr1 << std::endl;
std::string in2 = "Personn{ 'Tom' 188 80 'male' }";
attribute_type fullattr2;
if (qi::phrase_parse(in2.begin(), in2.end(), data, qi::space, fullattr2))
std::cout << fullattr2 << std::endl;
std::string in3 = "Personn{ 'Tom' 188 3* }";
attribute_type fullattr3;
if (qi::phrase_parse(in3.begin(), in3.end(), data, qi::space, fullattr3))
std::cout << fullattr3 << std::endl;
return 0;
}
将规则拆分为单独的规则(如@vines所建议的)将需要对输入进行多次解析,这就是为什么我使用这种序列和替代的嵌套结构。
我刚刚提出了一个通用的解决方案,虽然它有点复杂=)
它处理"大括号"和多个任意跳过说明符。这是:
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix_core.hpp>
#include <boost/spirit/include/phoenix_operator.hpp>
#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/fusion/include/io.hpp>
#include <iostream>
#include <string>
namespace qi = boost::spirit::qi;
namespace ph = boost::phoenix;
struct numbers { int i1, i2, i3, i4; };
BOOST_FUSION_ADAPT_STRUCT
(numbers,
(int, i1)
(int, i2)
(int, i3)
(int, i4)
)
template <typename Iterator, typename Skipper>
struct Grammar : public qi::grammar <Iterator, numbers(), Skipper>
{
Grammar() : Grammar::base_type(start, "numbers")
{
using qi::int_;
// This rule resets the skip counter:
init_skip = qi::eps[ph::ref(skp) = 0];
// This rule parses the skip directive ("n*") and sets the skip counter:
skip_spec = qi::omit[ (qi::lexeme[ int_ >> "*" ])[ph::ref(skp) = qi::_1] ];
// This rule checks if we should skip the field, and if so, decrements
// the skip counter and returns the value given to it (the default one).
// If not, it tries to parse the int.
// If int fails to parse, the rule resorts the default value again,
// thus handling the "premature brace" case.
int_dflt %= qi::eps(ph::ref(skp) > 0)[--ph::ref(skp)] >> qi::attr(qi::_r1) | int_ | qi::attr(qi::_r1);
// And this is the grammar:
start %= init_skip >>
"{" >> -skip_spec >> int_dflt(-1)
>> -skip_spec >> int_dflt(-1)
>> -skip_spec >> int_dflt(-1)
>> -skip_spec >> int_dflt(-1)
>> "}";
}
// the skip counter itself:
int skp;
qi::rule <Iterator, numbers(), Skipper> start;
qi::rule <Iterator, Skipper> skip_spec, init_skip;
qi::rule <Iterator, int(int), Skipper> int_dflt;
};
int main (int argc, char **argv)
{
using std::cout;
using std::endl;
std::string s = argv[1];
numbers result;
std::string::iterator ib = s.begin();
std::string::iterator ie = s.end();
bool r = qi::phrase_parse(ib, ie, Grammar<std::string::iterator, qi::space_type>(), qi::space, result );
if (r && ib == ie)
{
cout << boost::fusion::tuple_open('[');
cout << boost::fusion::tuple_close(']');
cout << boost::fusion::tuple_delimiter(", ");
cout << "Parsing succeededn";
cout << "got: " << boost::fusion::as_vector(result) << endl;
}
else
{
cout << "Parsing failedn";
cout << "err: " << std::string(ib, ie) << endl;
}
return 0;
}
PS:请注意,Skipper模板参数与字段跳过无关—它只是语法使用的空白跳过解析器的类型。
我首先想到的是:
如果你的结构没有太多的成员,你可以用某种语法来描述*n,比如:
struct_full = "{" >> a >> b >> c >> "}";
struct_reduced_1 = "{" >> a >> b >> "1*" >> attr(c_default) >> "}"
struct_reduced_2 = "{" >> a >> "2*" >> attr(b_default) >> attr(c_default) >> "}";
struct_reduced_3 = "{" >> "3*" >> attr(a_default) >> attr(b_default) >> attr(c_default) >> "}";
当然,这不是最美丽的方式…
相关文章:
- 如何创建一个CMake变量,除非显式重写,否则使用默认值
- 具有默认值的引用获取函数
- 当给定默认值时,为什么此模板参数推导失败
- 从具有默认值的部分指定模板类继承时发生SWIG错误,具有不带默认值的正向声明
- 格式化浮点值:返回默认值
- 如何将数组部分初始化为某个默认值?
- asn1c 不会从 asn.1 模块中提取八位字节字符串的默认值
- 创建一个包含 c++ 默认值的环境文件
- C++(和 ROS) - 包含与前向声明引用,设置默认值和类型定义
- Makefile g++ 使用命令行中的 -D 变量进行编译,默认值
- Switch 语句(字符串)一直选择默认值,除非其为零
- 如何使用默认值将枚举声明为 extern
- 如何在提升程序选项中设置矢量<矢量>的默认值<string>
- 如何使用默认值为构造函数中的枚举赋值?
- 变量始终在函数中重置为默认值
- 如何在C++中提供模板化函数作为另一个函数的参数,默认值?
- 函数不返回默认值
- 具有公共范围与专用范围的默认值的C++的不同行为
- 在 c++ 中,如果我创建一个接受一个具有默认值的参数的构造函数 - 它会用作默认(空)构造函数吗?
- 如何以激励精神构建默认值语法