Virtual 类 作为具有 Spirit 的 AST 节点
Virtual classes as AST nodes with Spirit
我和一个朋友一起研究一种语言的解释器,我们从一个我猜不是那么明智的决定开始:我们首先制作所有要执行的元素(实际上是一棵由不同的 类);但现在看着提升示例,我对如何合并两者感到很困惑。我知道从什么开始(语法),我知道要达到什么(实例化 类 拥有彼此),我不知道如何达到它。
我们从没有变量的表达式开始,因此我们查看了精神计算器示例;但是我不明白什么时候实例化元素。
表达式项示例:
namespace exp
{
class op
{
private:
public:
virtual double exec(function_scope &fs);
};
class operand : public op
{
private:
double value;
public:
operand(double value);
double exec(function_scope &fs);
};
class op_bin : public op
{
private:
public:
op * ll;
op* rr;
op_bin(op* ll, op* rr);
~op_bin();
};
namespace bin
{
class sum : public op_bin
{
public:
sum(op* ll, op* rr);
double exec(function_scope &fs);
};
}
}
忽略 exec 函数,它在运行时使用。
例如,代码 5 + (2 + 1) 的最终结果应为:
new exp::bin::sum(new exp::operand(5), new exp::bin::sum(new exp::operand(2), new exp::operand(1))
一旦我了解了该怎么做,我就几乎做到了。
好吧,我本来打算写下你的问题有什么问题,但我却去证明自己,做出你想要的东西并不难。
几个要点:
- 我稍微修改、重命名并扩展了你的 ast 以使其工作并实际显示一些东西。
Spirit rules 出于某种原因复制了一个属性(我认为这是一个错误),所以我用一个特征解决了这个问题 unique_ptr
。(固定在 1.70)
- 我不确定那里是否真的需要
x3::omit
(你可以删除除最后一个以外的所有内容,它会编译),但它看起来像是 Spirit 中的另一个错误。
make_node
看起来不可靠并且可能会以令人惊讶的方式崩溃,如果您愿意,可以将其拆分为单独的 unary/binary 节点创建者。
- 在某些时候你会想要使用有状态分配器来创建 ast 节点,通过将分配器注入解析器上下文应该非常简单。我把它留给你作为练习。
解析器:
#include <boost/spirit/home/x3.hpp>
#include <memory>
#include <iostream>
namespace ast
{
class expression
{
protected:
expression() = default;
public:
virtual ~expression() = default;
expression(expression&& other) = delete;
expression& operator=(expression&& other) = delete;
virtual void print(std::ostream&) const = 0;
friend std::ostream& operator<<(std::ostream& os, expression const& node)
{
node.print(os);
return os;
}
};
class operand : public expression
{
double value_;
public:
constexpr operand(double value) : value_{value} {}
void print(std::ostream& os) const override { os << value_; }
};
class op_bin : public expression
{
protected:
std::unique_ptr<expression> left_, right_;
public:
op_bin(std::unique_ptr<expression> left, std::unique_ptr<expression> right)
: left_{ std::move(left) }, right_{ std::move(right) }
{}
op_bin(expression * left, expression * right)
: left_{ left }, right_{ right }
{}
};
class plus : public op_bin
{
public:
using op_bin::op_bin;
void print(std::ostream& os) const override
{ os << '(' << *left_ << " + " << *right_ << ')'; }
};
class minus : public op_bin
{
public:
using op_bin::op_bin;
void print(std::ostream& os) const override
{ os << '(' << *left_ << " - " << *right_ << ')'; }
};
class mul : public op_bin
{
public:
using op_bin::op_bin;
void print(std::ostream& os) const override
{ os << '(' << *left_ << " * " << *right_ << ')'; }
};
class div : public op_bin
{
public:
using op_bin::op_bin;
void print(std::ostream& os) const override
{ os << '(' << *left_ << " / " << *right_ << ')'; }
};
} // namespace ast
namespace grammar
{
namespace x3 = boost::spirit::x3;
template <typename T>
struct make_node_
{
template <typename Context>
void operator()(Context const& ctx) const
{
if constexpr (std::is_convertible_v<decltype(x3::_attr(ctx)), T>) {
x3::_val(ctx) = std::make_unique<T>(std::move(x3::_attr(ctx)));
}
else {
x3::_val(ctx) = std::make_unique<T>(std::move(x3::_val(ctx)), std::move(x3::_attr(ctx)));
}
}
};
template <typename T>
constexpr make_node_<T> make_node{};
using x3::double_;
using x3::char_;
x3::rule<class expression_r, std::unique_ptr<ast::expression>, true> const expression;
x3::rule<class prec1_r, std::unique_ptr<ast::expression>, true> const prec1;
x3::rule<class prec0_r, std::unique_ptr<ast::expression>, true> const prec0;
auto const expression_def =
prec1
>> *( x3::omit[('+' > prec1)[make_node<ast::plus>]]
| x3::omit[('-' > prec1)[make_node<ast::minus>]]
)
;
auto const prec1_def =
prec0
>> *( x3::omit[('*' > prec0)[make_node<ast::mul>]]
| x3::omit[('/' > prec0)[make_node<ast::div>]]
)
;
auto const prec0_def =
x3::omit[double_[make_node<ast::operand>]]
| '(' > expression > ')'
;
BOOST_SPIRIT_DEFINE(
expression
, prec1
, prec0
);
} // namespace grammar
#if BOOST_VERSION < 107000
namespace boost::spirit::x3::traits {
template <typename Attribute>
struct make_attribute<std::unique_ptr<Attribute>, std::unique_ptr<Attribute>>
: make_attribute_base<std::unique_ptr<Attribute>>
{
typedef std::unique_ptr<Attribute>& type;
typedef std::unique_ptr<Attribute>& value_type;
};
} // namespace boost::spirit::x3::traits
#endif
int main()
{
namespace x3 = boost::spirit::x3;
std::string s = "1 + 2 * (3 - 4) / 5";
std::unique_ptr<ast::expression> expr;
if (auto iter = s.cbegin(); !phrase_parse(iter, s.cend(), grammar::expression, x3::space, expr)) {
std::cout << "parsing failed";
}
else {
if (iter != s.cend())
std::cout << "partially parsed\n";
std::cout << *expr << '\n';
}
}
输出:
(1 + ((2 * (3 - 4)) / 5))
我和一个朋友一起研究一种语言的解释器,我们从一个我猜不是那么明智的决定开始:我们首先制作所有要执行的元素(实际上是一棵由不同的 类);但现在看着提升示例,我对如何合并两者感到很困惑。我知道从什么开始(语法),我知道要达到什么(实例化 类 拥有彼此),我不知道如何达到它。
我们从没有变量的表达式开始,因此我们查看了精神计算器示例;但是我不明白什么时候实例化元素。
表达式项示例:
namespace exp
{
class op
{
private:
public:
virtual double exec(function_scope &fs);
};
class operand : public op
{
private:
double value;
public:
operand(double value);
double exec(function_scope &fs);
};
class op_bin : public op
{
private:
public:
op * ll;
op* rr;
op_bin(op* ll, op* rr);
~op_bin();
};
namespace bin
{
class sum : public op_bin
{
public:
sum(op* ll, op* rr);
double exec(function_scope &fs);
};
}
}
忽略 exec 函数,它在运行时使用。
例如,代码 5 + (2 + 1) 的最终结果应为:
new exp::bin::sum(new exp::operand(5), new exp::bin::sum(new exp::operand(2), new exp::operand(1))
一旦我了解了该怎么做,我就几乎做到了。
好吧,我本来打算写下你的问题有什么问题,但我却去证明自己,做出你想要的东西并不难。
几个要点:
- 我稍微修改、重命名并扩展了你的 ast 以使其工作并实际显示一些东西。
Spirit rules 出于某种原因复制了一个属性(我认为这是一个错误),所以我用一个特征解决了这个问题(固定在 1.70)unique_ptr
。- 我不确定那里是否真的需要
x3::omit
(你可以删除除最后一个以外的所有内容,它会编译),但它看起来像是 Spirit 中的另一个错误。 make_node
看起来不可靠并且可能会以令人惊讶的方式崩溃,如果您愿意,可以将其拆分为单独的 unary/binary 节点创建者。- 在某些时候你会想要使用有状态分配器来创建 ast 节点,通过将分配器注入解析器上下文应该非常简单。我把它留给你作为练习。
解析器:
#include <boost/spirit/home/x3.hpp>
#include <memory>
#include <iostream>
namespace ast
{
class expression
{
protected:
expression() = default;
public:
virtual ~expression() = default;
expression(expression&& other) = delete;
expression& operator=(expression&& other) = delete;
virtual void print(std::ostream&) const = 0;
friend std::ostream& operator<<(std::ostream& os, expression const& node)
{
node.print(os);
return os;
}
};
class operand : public expression
{
double value_;
public:
constexpr operand(double value) : value_{value} {}
void print(std::ostream& os) const override { os << value_; }
};
class op_bin : public expression
{
protected:
std::unique_ptr<expression> left_, right_;
public:
op_bin(std::unique_ptr<expression> left, std::unique_ptr<expression> right)
: left_{ std::move(left) }, right_{ std::move(right) }
{}
op_bin(expression * left, expression * right)
: left_{ left }, right_{ right }
{}
};
class plus : public op_bin
{
public:
using op_bin::op_bin;
void print(std::ostream& os) const override
{ os << '(' << *left_ << " + " << *right_ << ')'; }
};
class minus : public op_bin
{
public:
using op_bin::op_bin;
void print(std::ostream& os) const override
{ os << '(' << *left_ << " - " << *right_ << ')'; }
};
class mul : public op_bin
{
public:
using op_bin::op_bin;
void print(std::ostream& os) const override
{ os << '(' << *left_ << " * " << *right_ << ')'; }
};
class div : public op_bin
{
public:
using op_bin::op_bin;
void print(std::ostream& os) const override
{ os << '(' << *left_ << " / " << *right_ << ')'; }
};
} // namespace ast
namespace grammar
{
namespace x3 = boost::spirit::x3;
template <typename T>
struct make_node_
{
template <typename Context>
void operator()(Context const& ctx) const
{
if constexpr (std::is_convertible_v<decltype(x3::_attr(ctx)), T>) {
x3::_val(ctx) = std::make_unique<T>(std::move(x3::_attr(ctx)));
}
else {
x3::_val(ctx) = std::make_unique<T>(std::move(x3::_val(ctx)), std::move(x3::_attr(ctx)));
}
}
};
template <typename T>
constexpr make_node_<T> make_node{};
using x3::double_;
using x3::char_;
x3::rule<class expression_r, std::unique_ptr<ast::expression>, true> const expression;
x3::rule<class prec1_r, std::unique_ptr<ast::expression>, true> const prec1;
x3::rule<class prec0_r, std::unique_ptr<ast::expression>, true> const prec0;
auto const expression_def =
prec1
>> *( x3::omit[('+' > prec1)[make_node<ast::plus>]]
| x3::omit[('-' > prec1)[make_node<ast::minus>]]
)
;
auto const prec1_def =
prec0
>> *( x3::omit[('*' > prec0)[make_node<ast::mul>]]
| x3::omit[('/' > prec0)[make_node<ast::div>]]
)
;
auto const prec0_def =
x3::omit[double_[make_node<ast::operand>]]
| '(' > expression > ')'
;
BOOST_SPIRIT_DEFINE(
expression
, prec1
, prec0
);
} // namespace grammar
#if BOOST_VERSION < 107000
namespace boost::spirit::x3::traits {
template <typename Attribute>
struct make_attribute<std::unique_ptr<Attribute>, std::unique_ptr<Attribute>>
: make_attribute_base<std::unique_ptr<Attribute>>
{
typedef std::unique_ptr<Attribute>& type;
typedef std::unique_ptr<Attribute>& value_type;
};
} // namespace boost::spirit::x3::traits
#endif
int main()
{
namespace x3 = boost::spirit::x3;
std::string s = "1 + 2 * (3 - 4) / 5";
std::unique_ptr<ast::expression> expr;
if (auto iter = s.cbegin(); !phrase_parse(iter, s.cend(), grammar::expression, x3::space, expr)) {
std::cout << "parsing failed";
}
else {
if (iter != s.cend())
std::cout << "partially parsed\n";
std::cout << *expr << '\n';
}
}
输出:
(1 + ((2 * (3 - 4)) / 5))