| // Part of the Crubit project, under the Apache License v2.0 with LLVM |
| // Exceptions. See /LICENSE for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| |
| #include "rs_bindings_from_cc/collect_namespaces.h" |
| |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "absl/container/btree_map.h" |
| #include "absl/container/flat_hash_map.h" |
| #include "absl/strings/string_view.h" |
| #include "rs_bindings_from_cc/bazel_types.h" |
| #include "rs_bindings_from_cc/ir.h" |
| #include "llvm/Support/JSON.h" |
| |
| namespace crubit { |
| namespace { |
| |
| // A Trie that stores the namespace hierarchy. |
| // |
| // Consider the following namespace structure: |
| // namespace top_level_one { |
| // namespace internal {} |
| // } // namespace A |
| // |
| // namespace top_level_two { |
| // namespace internal {} |
| // } |
| // |
| // namespace top_level_one { |
| // namespace inner {} |
| // } |
| // |
| // A Namespace trie allows us to convert the above to: |
| // |
| // top_level_one -> [internal, inner] |
| // top_level_two -> [internal] |
| // |
| // Which we can then serialize to JSON. |
| class NamespaceTrie { |
| private: |
| struct Node { |
| absl::string_view name; |
| // A map of child namespace name to the index of the namespace node in the |
| // trie_nodes_ vector. We use a btree_map so that at conversion time we get |
| // deterministic JSON output. |
| absl::btree_map<absl::string_view, int> child_name_to_idx; |
| }; |
| |
| std::vector<Node> trie_nodes_; |
| // A map of top level namespace name to the index of the namespace node in the |
| // trie_nodes_ vector. We use a btree_map so that at conversion time we get |
| // deterministic JSON output. |
| absl::btree_map<absl::string_view, int> top_level_name_to_idx_; |
| // The current target's label. |
| BazelLabel label_; |
| // A map of item id to the IR Namespace item. It allows us to look up the |
| // children namespace items. |
| absl::flat_hash_map<ItemId, const Namespace*>& id_to_namespace_; |
| |
| // Creates a node from a Namespace and inserts it into the trie. |
| void InsertNode(int parent_idx, const Namespace* ns) { |
| auto name = ns->name.Ident(); |
| auto parent = &trie_nodes_[parent_idx]; |
| int child_idx; |
| if (parent->child_name_to_idx.find(name) == |
| parent->child_name_to_idx.end()) { |
| child_idx = trie_nodes_.size(); |
| parent->child_name_to_idx.insert({name, child_idx}); |
| // The following line potentially invalidates the "parent" pointer. |
| trie_nodes_.push_back({name, {}}); |
| } else { |
| child_idx = parent->child_name_to_idx[name]; |
| } |
| |
| for (auto ns_child_id : ns->child_item_ids) { |
| if (id_to_namespace_.find(ns_child_id) == id_to_namespace_.end()) { |
| continue; |
| } |
| auto ns_child = id_to_namespace_[ns_child_id]; |
| InsertNode(child_idx, ns_child); |
| } |
| } |
| |
| // Converts a trie node into the JSON serializable NamespaceNode. |
| NamespaceNode NodeToNamespaceNode(const Node* node) const { |
| std::vector<NamespaceNode> namespaces; |
| namespaces.reserve(node->child_name_to_idx.size()); |
| for (const auto& [_, idx] : node->child_name_to_idx) { |
| namespaces.push_back(NodeToNamespaceNode(&trie_nodes_[idx])); |
| } |
| return NamespaceNode{std::string(node->name), std::move(namespaces)}; |
| } |
| |
| public: |
| NamespaceTrie(BazelLabel label, |
| absl::flat_hash_map<ItemId, const Namespace*>& id_to_namespace) |
| : label_(label), id_to_namespace_(id_to_namespace) {} |
| |
| NamespaceTrie(NamespaceTrie&) = delete; |
| NamespaceTrie& operator=(NamespaceTrie&) = delete; |
| |
| // Creates a trie node from the top level namespace and inserts it into the |
| // trie. |
| void InsertTopLevel(const Namespace* ns) { |
| auto name = ns->name.Ident(); |
| int node_idx; |
| if (top_level_name_to_idx_.find(name) == top_level_name_to_idx_.end()) { |
| node_idx = trie_nodes_.size(); |
| top_level_name_to_idx_.insert({name, node_idx}); |
| trie_nodes_.push_back({name, {}}); |
| } else { |
| node_idx = top_level_name_to_idx_[name]; |
| } |
| |
| for (auto ns_child_id : ns->child_item_ids) { |
| if (id_to_namespace_.find(ns_child_id) == id_to_namespace_.end()) { |
| continue; |
| } |
| auto ns_child = id_to_namespace_[ns_child_id]; |
| InsertNode(node_idx, ns_child); |
| } |
| } |
| |
| // Converts the trie into the JSON serializable NamespacesHierarchy. |
| NamespacesHierarchy ToNamespacesHierarchy() { |
| std::vector<NamespaceNode> namespaces; |
| namespaces.reserve(this->top_level_name_to_idx_.size()); |
| for (auto& [_, idx] : this->top_level_name_to_idx_) { |
| namespaces.push_back(NodeToNamespaceNode(&trie_nodes_[idx])); |
| } |
| return NamespacesHierarchy{label_, std::move(namespaces)}; |
| } |
| }; |
| |
| } // namespace |
| |
| // Returns the current target's namespace hierarchy in JSON serializable format. |
| NamespacesHierarchy CollectNamespaces(const IR& ir) { |
| auto all_namespaces = ir.get_items_if<Namespace>(); |
| absl::flat_hash_map<ItemId, const Namespace*> id_to_namespace; |
| for (auto ns : all_namespaces) { |
| // We are not interested in namespaces from different targets. |
| if (ns->owning_target != ir.current_target) { |
| continue; |
| } |
| id_to_namespace.insert({ns->id, ns}); |
| } |
| |
| NamespaceTrie trie(ir.current_target, id_to_namespace); |
| for (auto namespace_id : ir.top_level_item_ids) { |
| if (id_to_namespace.count(namespace_id) == 0) { |
| continue; |
| } |
| auto ns = id_to_namespace[namespace_id]; |
| trie.InsertTopLevel(ns); |
| } |
| |
| return trie.ToNamespacesHierarchy(); |
| } |
| |
| llvm::json::Value NamespaceNode::ToJson() const { |
| std::vector<llvm::json::Value> json_children; |
| json_children.reserve(children.size()); |
| for (const auto& child : children) { |
| json_children.push_back(child.ToJson()); |
| } |
| |
| return llvm::json::Object{ |
| {"name", name}, |
| {"children", std::move(json_children)}, |
| }; |
| } |
| |
| llvm::json::Value NamespacesHierarchy::ToJson() const { |
| std::vector<llvm::json::Value> json_namespaces; |
| json_namespaces.reserve(namespaces.size()); |
| for (const auto& ns : namespaces) { |
| json_namespaces.push_back(ns.ToJson()); |
| } |
| |
| return llvm::json::Object{ |
| {"label", label.value()}, |
| {"namespaces", std::move(json_namespaces)}, |
| }; |
| } |
| |
| } // namespace crubit |