我正在尝试为参数列表编写解析器,这将允许以下内容:
myFunc( arg0, arg1, namedArg0 = valueA, namedArg1 = valueB )
在上面的例子中,我希望前两个参数解析为TypeA的实体,然后由std :: vector< TypeA>。后两个参数将解析为TypeB,它将由std :: vector< TypeB>。所有TypeA参数都应该在所有TypeB参数之前。但是我想从一个以逗号分隔的列表中解析所有内容。应该可以只有TypeA参数,只有TypeB参数或TypeA元素序列,后跟一系列TypeB元素。
我无法定义规则,因此将最终的TypeA参数与第一个TypeB参数分开的逗号不会被误认为是另一个TypeA参数的期望。
我目前的实施情况如下。任何人都可以就如何解决这个问题提出任何建议吗?
这里的关键区别是TypeA参数应该是单个符号,而TypeB参数应采用以下形式:symbol = symbol。
问题似乎与TypeA参数等同于TypeB参数的第一部分这一事实相关,因此使得TypeA序列的结尾不清楚?
谢谢!
struct Params
{
std::vector<TypeA> a_elements;
std::vector<TypeB> b_elements;
Params(const std::vector<TypeA>& a_vec, const std::vector<TypeB>& b_vec)
: a_elements( a_vec ), b_elements( b_vec ) {}
static Params create(const std::vector<TypeA>& a_vec, const std::vector<TypeB>& b_vec)
{
return Params( a_vec, b_vec );
}
};
struct ParamsParser : qi::grammar<Iterator, Params(), Skipper>
{
qi::rule<Iterator, Params(), Skipper> start_rule;
qi::rule<Iterator, std::vector<TypeA>(), Skipper> type_a_vec_opt_rule;
qi::rule<Iterator, std::vector<TypeB>(), Skipper> type_b_vec_opt_rule;
qi::rule<Iterator, std::vector<TypeA>(), Skipper> type_a_vec_rule;
qi::rule<Iterator, std::vector<TypeB>(), Skipper> type_b_vec_rule;
qi::rule<Iterator, TypeA(), Skipper> type_a_rule;
qi::rule<Iterator, TypeB(), Skipper> type_b_rule;
qi::rule<Iterator, std::string(), Skipper> symbol_rule;
ParamsParser() : ParamsParser::base_type( start_rule, "params_parser" )
{
start_rule =
// version 1:
( ( '(' >> type_a_vec_rule >> ',' >> type_b_vec_rule >> ')' )
[ qi::_val = boost::phoenix::bind( Params::create, qi::_1, qi::_2 ) ] )
// version 2:
| ( ( '(' >> type_a_vec_opt_rule >> ')' )
[ qi::_val = boost::phoenix::bind( Params::create, qi::_1, std::vector<TypeB>() ) ] )
// version 3:
| ( ( '(' >> type_b_vec_opt_rule >> ')' )
[ qi::_val = boost::phoenix::bind( Params::create, std::vector<TypeA>(), qi::_1 ) ] )
;
type_a_vec_opt_rule = -type_a_vec_rule;
type_b_vec_opt_rule = -type_b_vec_rule;
type_a_vec_rule = ( type_a_rule % ',' );
type_b_vec_rule = ( type_b_rule % ',' );
type_a_rule = ( symbol_rule );
type_b_rule = ( symbol_rule >> '=' >> symbol_rule );
symbol_rule = qi::char_( "a-zA-Z_" ) >> *qi::char_( "a-zA-Z_0-9" );
}
};
答案 0 :(得分:5)
两个问题。首先,您要确保不匹配名称参数可以匹配的位置参数¹。
第二:你希望他们分开收藏。
在上面的例子中,我希望前两个参数解析为
TypeA
的实体,然后由std::vector< TypeA >
包含。TypeB
。后两个参数将解析为std::vector< TypeB >
,TypeA
将包含TypeB
。所有argument_list = '(' >> -positional_args >> -named_args >> ')';
参数都应该出现在所有positional_args = expression % ','; named_args = named_arg % ','; named_arg = identifier >> '=' > expression)
个参数之前。
所以,你天真地写了
positional_args = (!named_arg >> expression) % ',';
其中
','
当然你已经注意到这会因位置和命名args之间的可选插值而出错。但首先要做的事情。
让我们阻止位置匹配命名匹配的位置:
)
这是相当生硬的。根据您精确的表达式/标识符产生,您可以使用更有效的区分器,但这是最简单的方法。
现在,为了继续本着相同的精神,关于定位/命名之间的,
可以起作用的最简单的事情是......只需检查如果有位置那么必须遵循argument_list = '(' >> positional_args >> -named_args >> ')';
positional_args = *(expression >> (&lit(')') | ','));
或positional_args
(应该随后使用)。很快:
argument_list
请注意
中不再是可选的vector<TypeA>
现在如何允许空匹配,因此vector<TypeB>
规则
positional_args
, argument_list = '(' >> -(argument % ',') >> ')';
你还想要什么?
我几乎可以感受到回应:我想要更优雅!
毕竟,现在TypeAOrB
被“阻止”了解有关命名args的知识以及期望参数列表的结束。
是。这是一个有效的问题。我同意更多涉及的语法,我更愿意实际写
arguments %=
eps [ found_named_arg = false ] // initialize the local (_a)
>> '(' >> -(argument(found_named_arg) % ',') >> ')';
argument %= (named_arg > eps [named_only=true])
| (eps(!named_only) >> positional);
然后这会自然地解析为boost::variant<TypeA, TypeB>
²的容器,并且你会做一些语义检查以确保位置参数之后没有位置参数:
<强> Live On Coliru 强>³
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>
namespace qi = boost::spirit::qi;
struct Positional { };
struct Named { };
using Arg = boost::variant<Positional, Named>;
using Args = std::vector<Arg>;
template <typename Iterator>
struct ArgsParser : qi::grammar<Iterator, Args()>
{
ArgsParser() : ArgsParser::base_type( start_rule, "params_parser" )
{
using namespace qi;
start_rule = skip(space) [ arguments ];
arguments %=
eps [ found_named_arg = false ] // initialize the local (_a)
>> '(' >> -(argument(found_named_arg) % ',') >> ')';
argument %= (named_arg > eps [named_only=true])
| (eps(!named_only) >> positional);
named_arg = "n" >> attr(Named{});
positional = "p" >> attr(Positional{});
}
private:
using Skipper = qi::space_type;
qi::rule<Iterator, Args()> start_rule;
qi::rule<Iterator, Args(), Skipper, qi::locals<bool> > arguments;
qi::rule<Iterator, Arg(bool&), Skipper> argument;
qi::rule<Iterator, Named(), Skipper> named_arg;
qi::rule<Iterator, Positional(), Skipper> positional;
qi::_a_type found_named_arg;
qi::_r1_type named_only;
};
// for debug output
static inline std::ostream& operator<<(std::ostream& os, Named) { return os << "named"; }
static inline std::ostream& operator<<(std::ostream& os, Positional) { return os << "positional"; }
int main() {
using It = std::string::const_iterator;
ArgsParser<It> const p;
for (std::string const input : {
"()",
"(p)",
"(p,p)",
"(p,n)",
"(n,n)",
"(n)",
// start the failing
"(n,p)",
"(p,p,n,p,n)",
"(p,p,n,p)",
})
{
std::cout << " ======== " << input << " ========\n";
It f(input.begin()), l(input.end());
Args parsed;
if (parse(f,l,p,parsed)) {
std::cout << "Parsed " << parsed.size() << " arguments in list: ";
std::copy(parsed.begin(), parsed.end(), std::ostream_iterator<Arg>(std::cout, " "));
std::cout << "\n";
} else {
std::cout << "Parse failed\n";
}
if (f!=l) {
std::cout << "Remaining input unparsed: '" << std::string(f,l) << "'\n";
}
}
}
我认为这和上面一样笨拙,所以如果你能拥有你想要的自然载体,为什么会使AST复杂化呢?
是的,您可以将所有这些与凤凰魔法 OR 结合起来使用自定义属性传播来对AST“桶”中的参数类型进行排序。 SO上有几个答案,说明如何做那样的诡计。
但是我认为提出的问题并没有充分的理由来介绍其中任何一个。
¹OT rant:为什么不使用正确的术语而不是像“typeA”和“typeB”这样的烟雾和镜子。如果它真的是一个秘密,不要在SO上发布问题。如果不是,请不要隐藏上下文,因为99%的时间问题和解决方案都来自上下文
² ======== () ========
Parsed 0 arguments in list:
======== (p) ========
Parsed 1 arguments in list: positional
======== (p,p) ========
Parsed 2 arguments in list: positional positional
======== (p,n) ========
Parsed 2 arguments in list: positional named
======== (n,n) ========
Parsed 2 arguments in list: named named
======== (n) ========
Parsed 1 arguments in list: named
======== (n,p) ========
Parse failed
Remaining input unparsed: '(n,p)'
======== (p,p,n,p,n) ========
Parse failed
Remaining input unparsed: '(p,p,n,p,n)'
======== (p,p,n,p) ========
Parse failed
Remaining input unparsed: '(p,p,n,p)'
³用于反比特目的 Live On Coliru 的完整来源:
variable1
Strongly Agree count1
Agree count2
Subtotal count1 + count2
Neutral count3
Disagree count4
Strongly Disagree count6
打印
Strongly Agree Agree Neutral Disagree Strongly Disagree Subtotal
variable1 count1 count2 count3 count4 count5 count1+count2