Skip to content

Commit a05bb3d

Browse files
jnthntatumcopybara-github
authored andcommitted
Add accessors for subtree size and height for navigable AST.
PiperOrigin-RevId: 797529699
1 parent 22172ae commit a05bb3d

File tree

7 files changed

+195
-43
lines changed

7 files changed

+195
-43
lines changed

tools/BUILD

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,6 @@ cc_library(
8181
"@com_google_absl//absl/log:absl_check",
8282
"@com_google_absl//absl/memory",
8383
"@com_google_absl//absl/strings",
84-
"@com_google_absl//absl/strings:string_view",
8584
"@com_google_absl//absl/types:span",
8685
"@com_google_cel_spec//proto/cel/expr:checked_cc_proto",
8786
"@com_google_cel_spec//proto/cel/expr:syntax_cc_proto",

tools/internal/BUILD

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
# limitations under the License.
1414

1515
load("@rules_cc//cc:cc_library.bzl", "cc_library")
16+
load("@rules_cc//cc:cc_test.bzl", "cc_test")
1617

1718
package(default_visibility = ["//visibility:public"])
1819

@@ -21,5 +22,18 @@ licenses(["notice"])
2122
cc_library(
2223
name = "navigable_ast_internal",
2324
hdrs = ["navigable_ast_internal.h"],
24-
deps = ["@com_google_absl//absl/types:span"],
25+
deps = [
26+
"@com_google_absl//absl/log:absl_check",
27+
"@com_google_absl//absl/types:span",
28+
],
29+
)
30+
31+
cc_test(
32+
name = "navigable_ast_internal_test",
33+
srcs = ["navigable_ast_internal_test.cc"],
34+
deps = [
35+
":navigable_ast_internal",
36+
"//internal:testing",
37+
"@com_google_absl//absl/types:span",
38+
],
2539
)

tools/internal/navigable_ast_internal.h

Lines changed: 54 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,14 @@
1111
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
14-
1514
#ifndef THIRD_PARTY_CEL_CPP_TOOLS_INTERNAL_NAVIGABLE_AST_INTERNAL_H_
1615
#define THIRD_PARTY_CEL_CPP_TOOLS_INTERNAL_NAVIGABLE_AST_INTERNAL_H_
1716

17+
#include <cstddef>
18+
#include <iterator>
19+
#include <type_traits>
20+
21+
#include "absl/log/absl_check.h"
1822
#include "absl/types/span.h"
1923

2024
namespace cel::tools_internal {
@@ -27,44 +31,76 @@ namespace cel::tools_internal {
2731
// RangeTraits provide type info needed to construct the span and adapt to the
2832
// range element type.
2933
template <class RangeTraits>
30-
class SpanRange {
34+
class NavigableAstRange {
3135
private:
3236
using UnderlyingType = typename RangeTraits::UnderlyingType;
37+
using PtrType = const UnderlyingType*;
3338
using SpanType = absl::Span<const UnderlyingType>;
3439

35-
class SpanForwardIter {
40+
public:
41+
class Iterator {
3642
public:
37-
SpanForwardIter(SpanType span, int i) : i_(i), span_(span) {}
43+
using difference_type = ptrdiff_t;
44+
using value_type = decltype(RangeTraits::Adapt(*PtrType()));
45+
using iterator_category = std::bidirectional_iterator_tag;
46+
47+
Iterator() : ptr_(nullptr), span_() {}
48+
Iterator(SpanType span, size_t i) : ptr_(span.data() + i), span_(span) {}
49+
50+
value_type operator*() const {
51+
ABSL_DCHECK(ptr_ != nullptr);
52+
ABSL_DCHECK(span_.data() != nullptr);
53+
ABSL_DCHECK_GE(ptr_, span_.data());
54+
ABSL_DCHECK_LT(ptr_, span_.data() + span_.size());
55+
return RangeTraits::Adapt(*ptr_);
56+
}
3857

39-
decltype(RangeTraits::Adapt(SpanType()[0])) operator*() const {
40-
ABSL_CHECK(i_ < span_.size());
41-
return RangeTraits::Adapt(span_[i_]);
58+
template <int... Barrier, typename T = value_type>
59+
std::enable_if_t<std::is_lvalue_reference<T>::value,
60+
std::add_pointer_t<std::remove_reference_t<T>>>
61+
operator->() const {
62+
return &operator*();
4263
}
4364

44-
SpanForwardIter& operator++() {
45-
++i_;
65+
Iterator& operator++() {
66+
++ptr_;
4667
return *this;
4768
}
4869

49-
bool operator==(const SpanForwardIter& other) const {
50-
return i_ == other.i_ && span_ == other.span_;
70+
Iterator operator++(int) {
71+
Iterator tmp = *this;
72+
++ptr_;
73+
return tmp;
5174
}
5275

53-
bool operator!=(const SpanForwardIter& other) const {
54-
return !(*this == other);
76+
Iterator& operator--() {
77+
--ptr_;
78+
return *this;
5579
}
5680

81+
Iterator operator--(int) {
82+
Iterator tmp = *this;
83+
--ptr_;
84+
return tmp;
85+
}
86+
87+
bool operator==(const Iterator& other) const {
88+
return ptr_ == other.ptr_ && span_ == other.span_;
89+
}
90+
91+
bool operator!=(const Iterator& other) const { return !(*this == other); }
92+
5793
private:
58-
int i_;
94+
PtrType ptr_;
5995
SpanType span_;
6096
};
6197

62-
public:
63-
explicit SpanRange(SpanType span) : span_(span) {}
98+
explicit NavigableAstRange(SpanType span) : span_(span) {}
6499

65-
SpanForwardIter begin() { return SpanForwardIter(span_, 0); }
100+
Iterator begin() { return Iterator(span_, 0); }
101+
Iterator end() { return Iterator(span_, span_.size()); }
66102

67-
SpanForwardIter end() { return SpanForwardIter(span_, span_.size()); }
103+
explicit operator bool() const { return !span_.empty(); }
68104

69105
private:
70106
SpanType span_;
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
#include "tools/internal/navigable_ast_internal.h"
15+
16+
#include <iterator>
17+
#include <vector>
18+
19+
#include "absl/types/span.h"
20+
#include "internal/testing.h"
21+
22+
namespace cel::tools_internal {
23+
namespace {
24+
25+
struct TestRangeTraits {
26+
using UnderlyingType = int;
27+
static double Adapt(const UnderlyingType& value) {
28+
return static_cast<double>(value) + 0.5;
29+
}
30+
};
31+
32+
TEST(NavigableAstRangeTest, BasicIteration) {
33+
std::vector<int> values{1, 2, 3};
34+
NavigableAstRange<TestRangeTraits> range(absl::MakeConstSpan(values));
35+
absl::Span<const int> span(values);
36+
auto it = range.begin();
37+
EXPECT_EQ(*it, 1.5);
38+
EXPECT_EQ(*++it, 2.5);
39+
EXPECT_EQ(*++it, 3.5);
40+
EXPECT_EQ(++it, range.end());
41+
EXPECT_EQ(*--it, 3.5);
42+
EXPECT_EQ(*--it, 2.5);
43+
EXPECT_EQ(*--it, 1.5);
44+
EXPECT_EQ(it, range.begin());
45+
}
46+
47+
} // namespace
48+
} // namespace cel::tools_internal

tools/navigable_ast.cc

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
#include "tools/navigable_ast.h"
1616

17+
#include <algorithm>
1718
#include <cstddef>
1819
#include <memory>
1920
#include <string>
@@ -143,21 +144,23 @@ class NavigableExprBuilderVisitor
143144
? nullptr
144145
: metadata_->nodes[parent_stack_.back()].get();
145146
size_t index = metadata_->AddNode();
147+
AstNode* node = metadata_->nodes[index].get();
146148
tools_internal::AstNodeData& node_data = metadata_->NodeDataAt(index);
147149
node_data.parent = parent;
148150
node_data.expr = expr;
149151
node_data.parent_relation = ChildKind::kUnspecified;
150152
node_data.node_kind = GetNodeKind(*expr);
151-
node_data.weight = 1;
153+
node_data.tree_size = 1;
154+
node_data.height = 1;
152155
node_data.index = index;
153156
node_data.metadata = metadata_.get();
154157

155-
metadata_->id_to_node.insert({expr->id(), index});
156-
metadata_->expr_to_node.insert({expr, index});
158+
metadata_->id_to_node.insert({expr->id(), node});
159+
metadata_->expr_to_node.insert({expr, node});
157160
if (!parent_stack_.empty()) {
158161
auto& parent_node_data = metadata_->NodeDataAt(parent_stack_.back());
159162
size_t child_index = parent_node_data.children.size();
160-
parent_node_data.children.push_back(metadata_->nodes[index].get());
163+
parent_node_data.children.push_back(node);
161164
node_data.parent_relation = GetChildKind(parent_node_data, child_index);
162165
}
163166
parent_stack_.push_back(index);
@@ -172,7 +175,9 @@ class NavigableExprBuilderVisitor
172175
if (!parent_stack_.empty()) {
173176
tools_internal::AstNodeData& parent_node_data =
174177
metadata_->NodeDataAt(parent_stack_.back());
175-
parent_node_data.weight += node.weight;
178+
parent_node_data.tree_size += node.tree_size;
179+
parent_node_data.height =
180+
std::max(parent_node_data.height, node.height + 1);
176181
}
177182
}
178183

@@ -261,12 +266,12 @@ int AstNode::child_index() const {
261266

262267
AstNode::PreorderRange AstNode::DescendantsPreorder() const {
263268
return AstNode::PreorderRange(absl::MakeConstSpan(data_.metadata->nodes)
264-
.subspan(data_.index, data_.weight));
269+
.subspan(data_.index, data_.tree_size));
265270
}
266271

267272
AstNode::PostorderRange AstNode::DescendantsPostorder() const {
268273
return AstNode::PostorderRange(absl::MakeConstSpan(data_.metadata->postorder)
269-
.subspan(data_.index, data_.weight));
274+
.subspan(data_.index, data_.tree_size));
270275
}
271276

272277
NavigableAst NavigableAst::Build(const Expr& expr) {

tools/navigable_ast.h

Lines changed: 36 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -93,15 +93,21 @@ struct AstNodeData {
9393
NodeKind node_kind;
9494
const AstMetadata* metadata;
9595
size_t index;
96-
size_t weight;
96+
size_t tree_size;
97+
size_t height;
9798
std::vector<AstNode*> children;
9899
};
99100

100101
struct AstMetadata {
102+
// The nodes in the AST in preorder.
103+
//
104+
// unique_ptr is used to guarantee pointer stability in the other tables.
101105
std::vector<std::unique_ptr<AstNode>> nodes;
102-
std::vector<const AstNode*> postorder;
103-
absl::flat_hash_map<int64_t, size_t> id_to_node;
104-
absl::flat_hash_map<const cel::expr::Expr*, size_t> expr_to_node;
106+
std::vector<const AstNode* absl_nonnull> postorder;
107+
absl::flat_hash_map<int64_t, const AstNode* absl_nonnull> id_to_node;
108+
absl::flat_hash_map<const cel::expr::Expr*,
109+
const AstNode* absl_nonnull>
110+
expr_to_node;
105111

106112
AstNodeData& NodeDataAt(size_t index);
107113
size_t AddNode();
@@ -123,13 +129,19 @@ struct PreorderTraits {
123129

124130
// Wrapper around a CEL AST node that exposes traversal information.
125131
class AstNode {
126-
private:
132+
public:
133+
// A const Span like type that provides pre-order traversal for a sub tree.
134+
// provides .begin() and .end() returning bidirectional iterators to
135+
// const AstNode&.
127136
using PreorderRange =
128-
tools_internal::SpanRange<tools_internal::PreorderTraits>;
137+
tools_internal::NavigableAstRange<tools_internal::PreorderTraits>;
138+
139+
// A const Span like type that provides post-order traversal for a sub tree.
140+
// provides .begin() and .end() returning bidirectional iterators to
141+
// const AstNode&.
129142
using PostorderRange =
130-
tools_internal::SpanRange<tools_internal::PostorderTraits>;
143+
tools_internal::NavigableAstRange<tools_internal::PostorderTraits>;
131144

132-
public:
133145
// The parent of this node or nullptr if it is a root.
134146
const AstNode* absl_nullable parent() const { return data_.parent; }
135147

@@ -146,6 +158,13 @@ class AstNode {
146158
// The type of this node, analogous to Expr::ExprKindCase.
147159
NodeKind node_kind() const { return data_.node_kind; }
148160

161+
// The number of nodes in the tree rooted at this node (including self).
162+
size_t tree_size() const { return data_.tree_size; }
163+
164+
// The height of this node in the tree (the number of descendants including
165+
// self on the longest path).
166+
size_t height() const { return data_.height; }
167+
149168
absl::Span<const AstNode* const> children() const {
150169
return absl::MakeConstSpan(data_.children);
151170
}
@@ -164,9 +183,6 @@ class AstNode {
164183
// - maps are traversed in order (alternating key, value per entry)
165184
// - comprehensions are traversed in the order: range, accu_init, condition,
166185
// step, result
167-
//
168-
// Return type is an implementation detail, it should only be used with auto
169-
// or in a range-for loop.
170186
PreorderRange DescendantsPreorder() const;
171187

172188
// Range over the descendants of this node (including self) using postorder
@@ -184,12 +200,16 @@ class AstNode {
184200
};
185201

186202
// NavigableExpr provides a view over a CEL AST that allows for generalized
187-
// traversal.
203+
// traversal. The traversal structures are eagerly built on construction,
204+
// requiring a full traversal of the AST. This is intended for use in tools that
205+
// might require random access or multiple passes over the AST, amortizing the
206+
// cost of building the traversal structures.
188207
//
189208
// Pointers to AstNodes are owned by this instance and must not outlive it.
190209
//
191-
// Note: Assumes ptr stability of the input Expr pb -- this is only guaranteed
192-
// if no mutations take place on the input.
210+
// `NavigableAst` and Navigable nodes are independent of the input Expr and may
211+
// outlive it, but may contain dangling pointers if the input Expr is modified
212+
// or destroyed.
193213
class NavigableAst {
194214
public:
195215
static NavigableAst Build(const cel::expr::Expr& expr);
@@ -217,7 +237,7 @@ class NavigableAst {
217237
if (it == metadata_->id_to_node.end()) {
218238
return nullptr;
219239
}
220-
return metadata_->nodes[it->second].get();
240+
return it->second;
221241
}
222242

223243
// Return ptr to the AST node representing the given Expr protobuf node.
@@ -227,7 +247,7 @@ class NavigableAst {
227247
if (it == metadata_->expr_to_node.end()) {
228248
return nullptr;
229249
}
230-
return metadata_->nodes[it->second].get();
250+
return it->second;
231251
}
232252

233253
// The root of the AST.

0 commit comments

Comments
 (0)