增强精神解析器规则以检测语句中的特殊结尾

Boost Spirit parser rule to detect special endings in a statement

本文关键字:语句 结尾 检测 规则 增强      更新时间:2023-10-16

这个小示例语法只是解析这个语句

a        <--special (but ok because rule in grammer)
a()
a.b      <--special
a.b()
a.b().c  <--special
a().b.c()
a().b    <--special

所有末尾带有 non (( 的情况都是特殊的,在精神上应该是单独的规则。到目前为止,只有规则(特例 1(是正确的。如何定义一个规则来捕获所有其他末尾没有 (( 的情况?

lvalue_statement =
(
name >> +(
(lit('(') >> paralistopt  >> lit(')')[_normal_action_call]
| (lit('.') >> name)                   [_normal_action_dot]
)

| name                                   [_special_action_]  // special case 1
)

另一个示例来解释"特殊"的含义,您可以看到 ROOT 节点应该具有特殊的 AST 节点或操作

a.b         -> SPECIAL_DOT(a,b)
a.b.c       -> SPECIAL_DOT(a,NORMAL_DOT(b,c))
a(para).b.c -> SEPCIAL_DOT(NORMAL_DOT(CALL(a,para),c)

我非常反感这么多语义操作¹。

我也认为这不是你的问题。

在语言方面,您希望a.b是成员取消引用,a()是调用,因此a.b()成员取消引用后调用a.b

从这个意义上说,a.b是正常情况,因为它不执行调用。a.b()将是"更特殊的",因为它是相同的 PLUS 调用。

我会用我的表达式语法来反映这一点:

lvalue = name >> *(
'.' >> name
| '(' >> paralistopt >> ')'
);

这将解析所有内容。现在你可以使用语义操作或属性传播

语义操作 #1

auto lvalue = name [ action("normal") ] >> *(
'.' >> name [ action("member_access") ]
| ('(' >> paralistopt >> ')') [ action("call") ]
);

给你。让我们想出一个记录内容的通用操作:

auto action = [](auto type) {
return [=](auto& ctx){
auto& attr = _attr(ctx);
using A = std::decay_t<decltype(attr)>;
std::cout << type << ":";
if constexpr(boost::fusion::traits::is_sequence<A>::value) {
std::cout << boost::fusion::as_vector(attr);
} else if constexpr(x3::traits::is_container<A>::value && not std::is_same_v<std::string, A>) {
std::string_view sep;
std::cout << "{";
for (auto& el : attr) { std::cout << sep << el; sep = ", "; }
std::cout << "}";
} else {
std::cout << attr;
}
std::cout << "n";
};
};

现在我们可以解析所有样本(加上更多(:

Live On Coliru版画:

=== "a"
normal:a
Ok
=== "a()"
normal:a
call:{}
Ok
=== "a.b"
normal:a
member_access:b
Ok
=== "a.b()"
normal:a
member_access:b
call:{}
Ok
=== "a.b().c"
normal:a
member_access:b
call:{}
member_access:c
Ok
=== "a().b.c()"
normal:a
call:{}
member_access:b
member_access:c
call:{}
Ok
=== "a().b.c()"
normal:a
call:{}
member_access:b
member_access:c
call:{}
Ok
=== "a(q,r,s).b"
normal:a
call:{q, r, s}
member_access:b
Ok

SA #2:构建 AST

让我们对 AST 进行建模:

namespace Ast {
using name   = std::string;
using params = std::vector<name>;
struct member_access;
struct call;
using lvalue = boost::variant<
name,
boost::recursive_wrapper<member_access>,
boost::recursive_wrapper<call>
>;
using params = std::vector<name>;
struct member_access { lvalue obj; name member; } ;
struct call          { lvalue f; params args;   } ;
}

现在我们可以替换操作:

auto lvalue
= rule<struct lvalue_, Ast::lvalue> {"lvalue"}
= name [ ([](auto& ctx){ _val(ctx) = _attr(ctx); }) ] >> *(
'.' >> name [ ([](auto& ctx){ _val(ctx) = Ast::member_access{ _val(ctx), _attr(ctx) }; }) ]
| ('(' >> paralistopt >> ')') [ ([](auto& ctx){ _val(ctx) = Ast::call{ _val(ctx), _attr(ctx) }; }) ]
);

很丑陋- 我不建议以这种方式编写代码,但至少它说明了涉及的步骤很少。

还添加了一些输出运算符:

namespace Ast { // debug output
static inline std::ostream& operator<<(std::ostream& os, Ast::member_access const& ma) {
return os << ma.obj << "." << ma.member;
}
static inline std::ostream& operator<<(std::ostream& os, Ast::call const& c) {
std::string_view sep;
os << c.f << "(";
for (auto& arg: c.args) { os << sep << arg; sep = ", "; }
return os << ")";
}
}

现在可以使用完整的 AST 解析所有内容:Live On Coliru,打印:

"a" -> a
"a()" -> a()
"a.b" -> a.b
"a.b()" -> a.b()
"a.b().c" -> a.b().c
"a().b.c()" -> a().b.c()
"a().b" -> a().b
"a(q,r,s).b" -> a(q, r, s).b

自动传播

实际上,我这样做有点陷入困境。我花了太长时间才把它弄对并以有用的方式解析关联性,所以我停止了尝试。相反,让我们通过清理第二个 SA 镜头来总结:

总结

使操作更具可读性:

auto passthrough =
[](auto& ctx) { _val(ctx) = _attr(ctx); };
template <typename T> auto binary_ =
[](auto& ctx) { _val(ctx) = T { _val(ctx), _attr(ctx) }; };
auto lvalue
= rule<struct lvalue_, Ast::lvalue> {"lvalue"}
= name [ passthrough ] >> *(
'.' >> name                 [ binary_<Ast::member_access> ]
| ('(' >> paralistopt >> ')') [ binary_<Ast::call> ]
);

现在还剩下许多问题:

您可能需要一个更通用的表达式语法,而不仅仅是解析左值表达式(例如f(foo, 42)可能应该解析,len("foo") + 17也应该解析?

为此,左值/右值的区别不属于语法:它主要是语义上的区别。

我碰巧创建了一个扩展解析器,它针对适当的 LValues 执行所有这些 + 评估(同时支持一般值(。我建议查看此答案的[扩展聊天][3]以及github上的结果代码:https://github.com/sehe/qi-extended-parser-evaluator。

完整列表

住在科里鲁

#include <boost/spirit/home/x3.hpp>
#include <iostream>
#include <iomanip>
namespace x3 = boost::spirit::x3;
namespace Ast {
using name   = std::string;
using params = std::vector<name>;
struct member_access;
struct call;
using lvalue = boost::variant<
name,
boost::recursive_wrapper<member_access>,
boost::recursive_wrapper<call>
>;
using params = std::vector<name>;
struct member_access { lvalue obj; name member; } ;
struct call          { lvalue f; params args;   } ;
}
namespace Ast { // debug output
static inline std::ostream& operator<<(std::ostream& os, Ast::member_access const& ma) {
return os << ma.obj << "." << ma.member;
}
static inline std::ostream& operator<<(std::ostream& os, Ast::call const& c) {
std::string_view sep;
os << c.f << "(";
for (auto& arg: c.args) { os << sep << arg; sep = ", "; }
return os << ")";
}
}
namespace Parser {
using namespace x3;
auto name
= rule<struct string_, Ast::name> {"name"}
= lexeme[alpha >> *(alnum|char_("_"))];
auto paralistopt
= rule<struct params_, Ast::params> {"params"}
= -(name % ',');
auto passthrough =
[](auto& ctx) { _val(ctx) = _attr(ctx); };
template <typename T> auto binary_ =
[](auto& ctx) { _val(ctx) = T { _val(ctx), _attr(ctx) }; };
auto lvalue
= rule<struct lvalue_, Ast::lvalue> {"lvalue"}
= name [ passthrough ] >> *(
'.' >> name                 [ binary_<Ast::member_access> ]
| ('(' >> paralistopt >> ')') [ binary_<Ast::call> ]
);
auto start = skip(space) [ lvalue ];
}
int main() {
for (std::string const input: {
"a",       // special (but ok because rule in grammer)
"a()",
"a.b",     // special
"a.b()",
"a.b().c", // special
"a().b.c()",
"a().b",   // special
"a(q,r,s).b",
})
{
std::cout << std::quoted(input) << " -> ";
auto f = begin(input), l = end(input);
Ast::lvalue parsed;
if (parse(f, l, Parser::start, parsed)) {
std::cout << parsed << "n";;
} else {
std::cout << "Failedn";
}
if (f!=l) {
std::cout << " -- Remainig: " << std::quoted(std::string(f,l)) << "n";
}
}
}

指纹

"a" -> a
"a()" -> a()
"a.b" -> a.b
"a.b()" -> a.b()
"a.b().c" -> a.b().c
"a().b.c()" -> a().b.c()
"a().b" -> a().b
"a(q,r,s).b" -> a(q, r, s).b

¹(在回溯的情况下会导致混乱,请参阅Boost Spirit:"语义行为是邪恶的"?