玩具编译器符号表

Toy Compiler Symbol Table

本文关键字:符号 编译器 玩具      更新时间:2023-10-16

我正在创建一个编译器(用于Cool语言)作为个人项目,我在设计符号表时遇到了麻烦。出于上下文的考虑,我使用了一个类的层次结构作为AST。

class NodeAST {
public:
  virtual void accept(Visitor&) = 0;  
};
class ProgramAST : public NodeAST {
private:
  const std::vector<ClassPtr> vClasses;
public:
  ProgramAST(std::vector<ClassPtr> vClasses);
  auto class_cbegin() const { 
    return std::cbegin(vClasses); 
  }
  auto class_cend() const { 
    return std::cend(vClasses); 
  }
  virtual void accept(Visitor& v) override;
};
class ClassAST : public NodeAST {
private:
  const std::string name;
  const std::vector<FeaturePtr> vFeatures; 
public:
  ClassAST(std::string name, std::vector<FeaturePtr> vFeatures);
  auto getName() const { 
    return name; 
  }
  auto feature_cbegin() const { 
    return std::cbegin(vFeatures);
  }
  auto feature_cend() const { 
    return std::cend(vFeatures); 
  }
  virtual void accept(Visitor& v) override;
};

目前,我的符号表的核心是一个map,定义如下:

std::unordered_map<std::string, NodeAST*> table

它将声明的名称映射到AST中相应的节点,这样,例如,我可以使用我在AST节点中设置的类型填充松散标识符的类型。

然而,问题是,当我查询符号表中的节点时,我得到的是一个NodeAST*。因此,我必须将其降为ClassAST*, MethodAST*VarDecAST*等来实际使用它。

我如何设计我的符号表,以避免需要向下转换?

我不知道您正在实现的编程语言,但我认为您确实不可能完全避免动态强制转换。例如,在f(1)中,如果您的语言有lambdas, f可以是函数或变量。你需要通过在符号表中查找它来找出它。

如果可以绝对排除这种可能性,理论上可以为每种类型创建单独的符号表。但是请记住,如果您需要检测名称冲突,那么这显然会使查找名称冲突变得更加困难,并且可能意味着您的编程语言以后更难以扩展。我不推荐这个解决方案。

就我个人而言,我只会添加像to_class(), to_var(), is_class(), is_var()等方法到你的NodeAST类,这样动态强制转换被封装,而不是在整个代码库中传播。你也可以为你的符号表创建一个类,这样你就可以访问get_class(), get_var()等元素。

如果你担心c++中RTTI的运行时成本,你可以看看其他编译器正在使用的解决方案。对于LLVM,如下所示:http://llvm.org/docs/HowToSetUpLLVMStyleRTTI.html

我在过去非常成功地使用访问者模式访问具有公共基的指针容器。在一个实现中,我经历了比dynamic_cast<>实现大约40%的加速,这在我正在编写的数据库抽象层中很重要。

在这里的一个答案中有更多的解释....处理对象的多态集合的正确设计模式

关于访问者模式的Wikipedia页面也给出了一个很好的简短的c++示例,该示例展示了Dispatcher类访问的基指针集合。

你已经在你的问题中显示了一个"可访问"类的源代码。我们需要查看"访问者"的实现,以了解为什么需要dynamic_cast<>。这应该是不必要的。应该通过函数参数重载来选择要使用的正确的visit()函数。

欢呼