不应用提升精神解析规则

Boost spirit parse rule is not applied

本文关键字:规则 应用      更新时间:2023-10-16

我在这里看不到我的错误..这个规则解析了一些东西,但最后两个样本不能。 有人可以给我一个提示..

目标是可以识别成员属性访问和成员函数调用的解析器。也以某种方式链接

a()
a(para)
x.a()
x.a(para)
x.a(para).g(para).j()
x.y
x.y.z
x.y.z()    <---fail
y.z.z(para) <--- fail
lvalue =
iter_pos >> name[_val = _1]
>> *(lit('(') > paralistopt  > lit(')') >> iter_pos)[_val = construct<common_node>(type_cmd_fnc_call, LOCATION_NODE_ITER(_val, _2), key_this, construct<common_node>(_val), key_parameter, construct<std::vector<common_node> >(_1))]        
>> *(lit('.') >> name_pure >> lit('(') > paralistopt > lit(')') >> iter_pos)[_val = construct<common_node>(type_cmd_fnc_call, LOCATION_NODE_ITER(_val, _3), key_this, construct<common_node>(_val), key_callname, construct<std::wstring>(_1), key_parameter, construct<std::vector<common_node> >(_2))]
>> *(lit('.') >> name_pure >> iter_pos)[_val = construct<common_node>(type_cmd_dot_call, LOCATION_NODE_ITER(_val, _2), key_this, construct<common_node>(_val), key_propname, construct<std::wstring>(_1))]
;

谢谢 马库斯

您提供的信息很少。让我幽默你进入这个猜谜游戏:

假设您要解析一种仅允许成员表达式和函数调用但链接的简单"语言"。

现在,你的语法没有说明参数(尽管很明显参数列表可以是空的),所以让我走下一英里,假设你想在那里接受相同类型的表达式(所以foo(a)没关系,但也bar(foo(a))bar(b.foo(a)))。

由于您接受函数调用的链接,因此函数似乎是一等对象(并且函数可以返回函数),因此也应该接受foo(a)(b, c, d)

你没有提到它,但参数通常包括文字(sqrt(9)想到的,或者println("hello world"))。

其他项目:

  • 你没有说,但你可能想忽略某些地方的空格
  • iter_pos(滥用)使用来看,您似乎有兴趣跟踪生成的 AST 内的原始源位置。

1.定义 AST

我们应该一如既往地保持简单:

namespace Ast {
using Identifier = boost::iterator_range<It>;
struct MemberExpression;
struct FunctionCall;
using Expression = boost::variant<
double,       // some literal types
std::string,
// non-literals
Identifier,
boost::recursive_wrapper<MemberExpression>,
boost::recursive_wrapper<FunctionCall>
>;
struct MemberExpression {
Expression object; // antecedent
Identifier member; // function or field
};
using Parameter  = Expression;
using Parameters = std::vector<Parameter>;
struct FunctionCall {
Expression function; // could be a member function
Parameters parameters;
};
}

注意我们不会重点显示源位置,但已经进行了一个预配,将标识符存储为迭代器范围。

注意融合适应唯一不直接由精神支持的类型:

BOOST_FUSION_ADAPT_STRUCT(Ast::MemberExpression, object, member)
BOOST_FUSION_ADAPT_STRUCT(Ast::FunctionCall, function, parameters)

我们会发现我们不使用这些,因为语义操作在这里更方便。

阿拉伯数字。匹配语法

Grammar() : Grammar::base_type(start) {
using namespace qi;
start = skip(space) [expression];
identifier = raw [ (alpha|'_') >> *(alnum|'_') ];
parameters = -(expression % ',');
expression 
= literal 
| identifier  >> *(
('.' >> identifier)        
| ('(' >> parameters >> ')') 
);
literal = double_ | string_;
string_ = '"' >> *('' >> char_ | ~char_('"')) >> '"';
BOOST_SPIRIT_DEBUG_NODES(
(identifier)(start)(parameters)(expression)(literal)(string_)
);
}

在此框架中,大多数规则受益于自动属性传播。没有的expression

qi::rule<It, Expression()> start;
using Skipper = qi::space_type;
qi::rule<It, Expression(), Skipper> expression, literal;
qi::rule<It, Parameters(), Skipper> parameters;
// lexemes
qi::rule<It, Identifier()> identifier;
qi::rule<It, std::string()> string_;

因此,让我们为语义操作创建一些帮助程序。

注意:这里的一个重要要点是创建自己的更高级别的构建块,而不是辛苦地使用boost::phoenix::construct<>等。

定义两个简单的构造函数:

struct mme_f { MemberExpression operator()(Expression lhs, Identifier rhs) const { return { lhs, rhs }; } };
struct mfc_f { FunctionCall operator()(Expression f, Parameters params) const { return { f, params }; } };
phx::function<mme_f> make_member_expression;
phx::function<mfc_f> make_function_call;

然后使用它们:

expression 
= literal [_val=_1]
| identifier [_val=_1] >> *(
('.' >> identifier)        [ _val = make_member_expression(_val, _1)]
| ('(' >> parameters >> ')') [ _val = make_function_call(_val, _1) ]
);

就这样。我们准备出发了!

3. 演示

住在科里鲁

我创建了一个看起来像这样的测试平台:

int main() {
using It = std::string::const_iterator;
Parser::Grammar<It> const g;
for (std::string const input : {
"a()", "a(para)", "x.a()", "x.a(para)", "x.a(para).g(para).j()", "x.y", "x.y.z",
"x.y.z()",
"y.z.z(para)",
// now let's add some funkyness that you didn't mention
"bar(foo(a))",
"bar(b.foo(a))",
"foo(a)(b, c, d)", // first class functions
"sqrt(9)",
"println("hello world")",
"allocate(strlen("aaaaa"))",
"3.14",
"object.rotate(180)",
"object.rotate(event.getAngle(), "torque")",
"app.mainwindow().find_child("InputBox").font().size(12)",
"app.mainwindow().find_child("InputBox").font(config().preferences.baseFont(style.PROPORTIONAL))"
}) {
std::cout << " =========== '" << input << "' ========================n";
It f(input.begin()), l(input.end());
Ast::Expression parsed;
bool ok = parse(f, l, g, parsed);
if (ok) {
std::cout << "Parsed: " << parsed << "n";
}
else
std::cout << "Parse failedn";
if (f != l)
std::cout << "Remaining unparsed input: '" << std::string(f, l) << "'n";
}
}

尽管看起来令人难以置信,但这已经解析了所有测试用例并打印:

=========== 'a()' ========================
Parsed: a()
=========== 'a(para)' ========================
Parsed: a(para)
=========== 'x.a()' ========================
Parsed: x.a()
=========== 'x.a(para)' ========================
Parsed: x.a(para)
=========== 'x.a(para).g(para).j()' ========================
Parsed: x.a(para).g(para).j()
=========== 'x.y' ========================
Parsed: x.y
=========== 'x.y.z' ========================
Parsed: x.y.z
=========== 'x.y.z()' ========================
Parsed: x.y.z()
=========== 'y.z.z(para)' ========================
Parsed: y.z.z(para)
=========== 'bar(foo(a))' ========================
Parsed: bar(foo(a))
=========== 'bar(b.foo(a))' ========================
Parsed: bar(b.foo(a))
=========== 'foo(a)(b, c, d)' ========================
Parsed: foo(a)(b, c, d)
=========== 'sqrt(9)' ========================
Parsed: sqrt(9)
=========== 'println("hello world")' ========================
Parsed: println(hello world)
=========== 'allocate(strlen("aaaaa"))' ========================
Parsed: allocate(strlen(aaaaa))
=========== '3.14' ========================
Parsed: 3.14
=========== 'object.rotate(180)' ========================
Parsed: object.rotate(180)
=========== 'object.rotate(event.getAngle(), "torque")' ========================
Parsed: object.rotate(event.getAngle(), torque)
=========== 'app.mainwindow().find_child("InputBox").font().size(12)' ========================
Parsed: app.mainwindow().find_child(InputBox).font().size(12)
=========== 'app.mainwindow().find_child("InputBox").font(config().preferences.baseFont(style.PROPORTIONAL))' ========================
Parsed: app.mainwindow().find_child(InputBox).font(config().preferences.baseFont(style.PROPORTIONAL))

4. 好得令人难以置信?

你是对的。我作弊了。我没有向您展示调试打印解析后的 AST 所需的代码:

namespace Ast {
static inline std::ostream& operator<<(std::ostream& os, MemberExpression const& me) {
return os << me.object << "." << me.member;
}
static inline std::ostream& operator<<(std::ostream& os, FunctionCall const& fc) {
os << fc.function << "(";
bool first = true;
for (auto& p : fc.parameters) { if (!first) os << ", "; first = false; os << p; }
return os << ")";
}
}

这只是调试打印,因为字符串文本未正确往返。但它只有 10 行代码,这是一个奖励。

5. 完整的蒙蒂:来源位置

这引起了您的兴趣,所以让我们展示它的工作原理。让我们添加一个简单的循环来打印标识符的所有位置:

using IOManip::showpos;
for (auto& id : all_identifiers(parsed)) {
std::cout << " - " << id << " at " << showpos(id, input) << "n";
}

当然,这就引出了一个问题,什么是showposall_identifiers

namespace IOManip {
struct showpos_t {
boost::iterator_range<It> fragment;
std::string const& source;
friend std::ostream& operator<<(std::ostream& os, showpos_t const& manip) {
auto ofs = [&](It it) { return it - manip.source.begin(); };
return os << "[" << ofs(manip.fragment.begin()) << ".." << ofs(manip.fragment.end()) << ")";
}
};
showpos_t showpos(boost::iterator_range<It> fragment, std::string const& source) {
return {fragment, source};
}
}

至于标识符提取:

std::vector<Identifier> all_identifiers(Expression const& expr) {
std::vector<Identifier> result;
struct Harvest {
using result_type = void;
std::back_insert_iterator<std::vector<Identifier> > out;
void operator()(Identifier const& id)       { *out++ = id; }
void operator()(MemberExpression const& me) { apply_visitor(*this, me.object); *out++ = me.member; }
void operator()(FunctionCall const& fc)     {
apply_visitor(*this, fc.function); 
for (auto& p : fc.parameters) apply_visitor(*this, p);
}
// non-identifier expressions
void operator()(std::string const&) { }
void operator()(double) { }
} harvest { back_inserter(result) };
boost::apply_visitor(harvest, expr);
return result;
}

这是一个树访客,它递归地收集所有标识符,将它们插入容器的背面。

住在科里鲁

其中输出如下所示(摘录):

=========== 'app.mainwindow().find_child("InputBox").font(config().preferences.baseFont(style.PROPORTIONAL))' ========================
Parsed: app.mainwindow().find_child(InputBox).font(config().preferences.baseFont(style.PROPORTIONAL))
- app at [0..3)
- mainwindow at [4..14)
- find_child at [17..27)
- font at [40..44)
- config at [45..51)
- preferences at [54..65)
- baseFont at [66..74)
- style at [75..80)
- PROPORTIONAL at [81..93)

尝试更改

>> *(lit('.') >> name_pure >> lit('(') > paralistopt > lit(')'))

>> *(*(lit('.') >> name_pure) >> lit('(') > paralistopt > lit(')'))