【问题标题】:AST: get leaf value when leafs are of different typesAST:当叶子类型不同时获取叶子值
【发布时间】:2019-07-12 06:45:24
【问题描述】:

我需要在 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>();

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

【问题讨论】:

  • 关于你的最后一段代码,你可以使用 std::any
  • 不完全确定我是否遵循您的设计,但看起来visitor pattern 可能有用。
  • 您的问题不清楚,类型转换有多种解决方案,静态和动态!请添加更多详细信息。

标签: c++ abstract-syntax-tree


【解决方案1】:

你可以用 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);

【讨论】:

    【解决方案2】:

    为了一个稍微不同的解决方案风格而切线,像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 中)有很多编译器构建的好东西,例如内存领域。然后将从Orphan&lt;Foo&gt; 获得Foo::Builder,并且孤儿由从竞技场分配器中分配内存的孤儿院产生。将您的整个 AST 构建在一个具有一个或几个大的、连续的段的竞技场中,这将比在通用堆上分配所有这些类型(假设您的 AST 不小)执行得更好。这种表示还可以直接序列化到磁盘或网络而无需转码:您可以对孤儿院的竞技场进行二进制转储,然后直接加载它,您可以零努力和零转码获取所有数据。 Foo::ReaderFoo::Builder 类型提供了非常快速的访问器,它们不进行任何数据解码或翻译——这就是 capnproto 编码的优势。如果您修改 AST 中的数据,孤儿院可能会增长,但它还提供了一个仅复制引用区域的复制操作(如果您愿意,可以复制 GC)——而且这也非常快,因为没有进行转码。复制逐字二进制数据块时,遍历开销很小。

    【讨论】:

    • 看起来很不错,我会花一些精力研究它。谢谢。
    • 我想说,既然您对实时系统感兴趣,那么 Capnproto 会很有趣。它的转码开销为零,内存开销很小,而且它的 RPC 协议也旨在最大限度地减少往返次数。我正在使用 Capnproto 定义实时系统的接口,这非常有用。生成的代码很小,与例如相比可以忽略不计。消息包或 protobufs。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-04-15
    • 2020-03-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多