AST:当叶子是不同类型时获取叶子值

AST: get leaf value when leafs are of different types

我需要在 AST 中表示这样的结构:

struct {
  int data;
  double doubleDataArray[10];
  struct {
    int nestedData;
  };
};

我正在创建这样的 AST:

我需要从树叶中检索数据。我遇到的问题是叶子包含异构数据。一片叶子可以表示一个整数值、一个双精度值、一个字符串等。

我可以像 IntValueDoubleValue 那样创建继承自 Value 的 类 并存储各自的数据,执行 dynamic_cast 转换 Value 到其 type 属性中引用的类型。像

switch (value->getType()) {
  case Type::Int: {
   auto iv = dynamic_cast<IntValue>(value);
   int value = iv->getValue();
  } break;
  case Type::Double() {
   auto dv = dynamic_cast<DoubleValue>(value);
   double value = dv->getValue();
  } break;
  //…
}

但我想知道是否有更好的方法,因为像那样的开关不容易维护和阅读。

我看过一些例子,比如 boost::program_options,比如:

int value = value->getValue().as<int>();

这是更好的方法吗?我怎样才能重现这个?

你可以使用 c++17 做这样的事情

struct node {
    //... other stuff
    std::variant</*your types of nodes here*/> type;
}

然后在您的节点上调用此访问者

std::visit([](auto&& node) {
    if constexpr(std::is_same_v<std::decay_t<decltype(node)>, /* your type here */>) {
        // ...
    }
    else if constexpr(/* ... */) {
        // ...
    }
}, node0.type);

为了一个略有不同风格的解决方案继续切线,如何按照 capnproto 的方式来做? Capnproto 自己的模式编译器使用 Capnproto 线编码在内存中表示 AST。该模式支持标记联合。模式的词法分析器和解析器是使用组合器构建的(尽管我认为您已经有一个生成 AST 的良好解析器)。

结构可以使用 capnp 模式表示如下:

# MyAst.capnp

struct Struct {
  fields @0 :List(Field);
}

struct Field {
  name @4 :Text;
  union {
    integer @0 :List(Int32);
    fpoint @1 :List(Double);
    text @2 :List(Text);
    structure @3 :Struct;
  }
}

模式编译器会为此生成 C++ 代码,具有以下重要的 类 Struct::ReaderStruct::BuilderField::ReaderField::Builder。无论是什么,AST 都会使用 Struct::Builder 类型来创建结构实例及其数据。然后,您将按如下方式遍历结构:

void processData(Struct::Reader reader) {
  auto fields = reader.getFields();
  for (auto &field : fields) {
    if (field.hasInteger()) {
      int32_t val = field.getInteger();
      ...
    } else if (field.hasFpoint()) {
      double val = field.getFpoint();
      ...
    } else if (field.hasText()) {
      kj::StringPtr val = field.getText();
      ...
    } else if (field.hasStructure()) {
      processData(field.getStructure());
    }
  }
}

kj 框架(包含在 capnproto 中)有很多 compiler-building 好东西,例如内存竞技场。 Foo::Builder 然后会从 Orphan<Foo> 中获得,孤儿是由孤儿院从竞技场分配器中切出内存产生的。如果你的整个 AST 构建在一个具有一个或几个大的、连续的段的 arena 中,这将比在 general-purpose 堆上分配所有这些类型(假设你的 AST 不小)表现更好。这种表示还可以直接序列化到磁盘或网络,无需转码:您可以对孤儿院的竞技场进行二进制转储,然后直接加载它,您可以零努力和零转码取回所有数据。 Foo::ReaderFoo::Builder 类型提供非常快速的访问器,不进行任何数据解码或转换——这是 capnproto 编码的优势。如果您修改 AST 中的数据,孤儿院可能会增长,但它也提供了一个仅复制引用区域的复制操作(复制 GC,如果您愿意的话)——这也非常快,因为没有完成转码。逐字复制二进制数据块,遍历开销很小。