首页 > 解决方案 > Clang RecursiveASTVisitor

问题描述

我正在开发一个 c++ 到无类 c 的翻译器。

基于 LLVM 中的 Kaleidoscope 示例,AST 树的每个节点都有一个 codeGen() 函数,该函数将生成适当的代码并将其返回给父节点。

我正在尝试使用访问者模式实现类似的行为,但我需要访问者返回对象。

关于如何使用RecursiveASTVisitor?

作为程序的示例输入:

void DrawToLayout(std::string, double, double, double, double) {}

class PCellRect {

private:
  double bottomX, bottomY, topX, topY;

public:
  PCellRect(double bX, double bY, double tX, double ty)
      : bottomX(bX), bottomY(bX), topX(bX), topY(bX) {}

  void Draw() { DrawToLayout("Rect", bottomX, bottomY, topX, topY); }
};

void Test() {

  PCellRect rectangle(1.0, 1.0, 2.0, 2.0);

  rectangle.Draw();
}

会产生这个输出:

void DrawToLayout(std::string, double, double, double, double) {}

void PCellRect_Constructor(double &bottomX, double &bottomY, double &topX,
                           double &topY, double bX, double bY, double tX,
                           double tY) {
  bottomX = bX;
  bottomY = bY;
  topX = bY;
  topY = bY;
}

void PCellRect_Draw(double &bottomX, double &bottomY, double &topX,
                    double &topY) {

  DrawToLayout("Rect", bottomX, bottomY, topX, topY);
}

void Test() {

  double rectangle_PCellRect_bottomX;
  double rectangle_PCellRect_bottomY;
  double rectangle_PCellRect_topX;
  double rectangle_PCellRect_topY;

  PCellRect_Constructor(rectangle_PCellRect_bottomX,
                        rectangle_PCellRect_bottomY, rectangle_PCellRect_topX,
                        rectangle_PCellRect_topY, 1.0, 1.0, 2.0, 2.0);

  PCellRect_Draw(rectangle_PCellRect_bottomX, rectangle_PCellRect_bottomY,
                 rectangle_PCellRect_topX, rectangle_PCellRect_topY);
}

标签: clang

解决方案


正如您所注意到的,RecursiveASTVisitor访问函数返回bool并且无法更改。这些返回值在用户定义的遍历行为中起着至关重要的作用。因此,在此限制中,我为您提供了两种不同的选择。

解决方案 1

该解决方案基于RecursiveASTVisitor. 您可以将std::stringstream(或任何用于按顺序收集结果的容器)保留为访问者的成员对象,并在遍历树时编写修改后的语句/声明。

class Translator : public RecursiveASTVisitor<Translator> {
public:
  bool VisitCXXConstructExpr(clang::CXXConstructExpr *ConstructorCall) {
    auto *Constructor = ConstructorCall->getConstructor();
    auto *ConstructedClass = Constructor->getParent();

    for (auto &Member : getMembers(ConstructedClass)) {
      SS << declareMember(Member) << "\n";
    }
    SS << "\n";
    SS << callPseudoConstructor(Constructor) << "\n";
  }

  // other visit functions

private:
  std::stringstream SS;
};

解决方案 2

这个解决方案有点复杂,但提供了更多的自由。它基于实现您自己的RecursiveASTVisitor. 您可以通过使用StmtVisitorDeclVisitorTypeVisitorTypeLocVisitor来做到这一点。这些只是访问者,因此它们为一个节点调用正确的访问函数,而不是为其子节点调用正确的访问函数。为了实现您自己的遍历,您需要调用Visit您想要遍历的所有子节点。

在以下代码段中,我并未使用所有Visitor类(const实际上是这些类的版本):

/// Some custom object to be constructed for each AST node
class TranslatedNode {
  // ...
};

class Translator : public ConstStmtVisitor<Translator, TranslatedNode>,
                   public ConstDeclVisitor<Translator, TranslatedNode> {
public:
  TranslatedNode VisitCXXConstructExpr(clang::CXXConstructExpr *ConstructorCall) {
    TranslatedNode Result;

    auto *Constructor = Constructor->getConstructor();
    auto *ConstructedClass = Constructor->getParent();

    for (auto &Member : getMembers(ConstructedClass)) {
      Result.declareMember(Member);
    }

    Result.startCall(getPseudoConstructor(Constructor);
    for (auto &Member : getMembers(ConstructedClass)) {
      Result.addArgument(Member);
    }
    for (auto *Argument : ConstructorCall->arguments()) {
      Result.addArgument(Visit(Argument));
    }

    return Result;
  }

  // other visit functions
};

这在某种程度上是一个伪代码,但我希望你能明白。

第一种方法更容易,只要您可以按照自己的方式实现某些东西,就RecursiveASTVisitor应该这样做。但是,在像您这样的情况下,我确实认为迟早需要对遍历进行更多控制。

我希望这些信息对您的项目有用。与 Clang 一起愉快地黑客攻击!


推荐阅读