AST:当叶子是不同类型时获取叶子值
AST: get leaf value when leafs are of different types
我需要在 AST 中表示这样的结构:
struct {
int data;
double doubleDataArray[10];
struct {
int nestedData;
};
};
我正在创建这样的 AST:
我需要从树叶中检索数据。我遇到的问题是叶子包含异构数据。一片叶子可以表示一个整数值、一个双精度值、一个字符串等。
我可以像 IntValue
、DoubleValue
那样创建继承自 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::Reader
、Struct::Builder
、Field::Reader
和 Field::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::Reader
和 Foo::Builder
类型提供非常快速的访问器,不进行任何数据解码或转换——这是 capnproto 编码的优势。如果您修改 AST 中的数据,孤儿院可能会增长,但它也提供了一个仅复制引用区域的复制操作(复制 GC,如果您愿意的话)——这也非常快,因为没有完成转码。逐字复制二进制数据块,遍历开销很小。
我需要在 AST 中表示这样的结构:
struct {
int data;
double doubleDataArray[10];
struct {
int nestedData;
};
};
我正在创建这样的 AST:
我需要从树叶中检索数据。我遇到的问题是叶子包含异构数据。一片叶子可以表示一个整数值、一个双精度值、一个字符串等。
我可以像 IntValue
、DoubleValue
那样创建继承自 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::Reader
、Struct::Builder
、Field::Reader
和 Field::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::Reader
和 Foo::Builder
类型提供非常快速的访问器,不进行任何数据解码或转换——这是 capnproto 编码的优势。如果您修改 AST 中的数据,孤儿院可能会增长,但它也提供了一个仅复制引用区域的复制操作(复制 GC,如果您愿意的话)——这也非常快,因为没有完成转码。逐字复制二进制数据块,遍历开销很小。