Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions common/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,15 @@ licenses(["notice"])

cc_library(
name = "ast",
srcs = ["ast.cc"],
hdrs = ["ast.h"],
deps = [
":expr",
"//common/ast:metadata",
"@com_google_absl//absl/base:no_destructor",
"@com_google_absl//absl/base:nullability",
"@com_google_absl//absl/container:flat_hash_map",
"@com_google_absl//absl/strings:string_view",
],
)

Expand Down
21 changes: 10 additions & 11 deletions common/ast/ast_impl.cc → common/ast.cc
Original file line number Diff line number Diff line change
Expand Up @@ -12,50 +12,49 @@
// See the License for the specific language governing permissions and
// limitations under the License.

#include "common/ast/ast_impl.h"
#include "common/ast.h"

#include <cstdint>

#include "absl/base/no_destructor.h"
#include "absl/base/nullability.h"
#include "absl/container/flat_hash_map.h"
#include "common/ast/expr.h"
#include "common/ast/metadata.h"

namespace cel::ast_internal {
namespace cel {
namespace {

const Type& DynSingleton() {
static auto* singleton = new TypeSpec(TypeKind(DynamicType()));
const TypeSpec& DynSingleton() {
static absl::NoDestructor<TypeSpec> singleton{TypeSpecKind(DynTypeSpec())};
return *singleton;
}

} // namespace

const TypeSpec* absl_nullable AstImpl::GetType(int64_t expr_id) const {
const TypeSpec* absl_nullable Ast::GetType(int64_t expr_id) const {
auto iter = type_map_.find(expr_id);
if (iter == type_map_.end()) {
return nullptr;
}
return &iter->second;
}

const TypeSpec& AstImpl::GetTypeOrDyn(int64_t expr_id) const {
const TypeSpec& Ast::GetTypeOrDyn(int64_t expr_id) const {
if (const TypeSpec* type = GetType(expr_id); type != nullptr) {
return *type;
}
return DynSingleton();
}

const TypeSpec& AstImpl::GetReturnType() const {
const TypeSpec& Ast::GetReturnType() const {
return GetTypeOrDyn(root_expr().id());
}

const Reference* AstImpl::GetReference(int64_t expr_id) const {
const Reference* absl_nullable Ast::GetReference(int64_t expr_id) const {
auto iter = reference_map_.find(expr_id);
if (iter == reference_map_.end()) {
return nullptr;
}
return &iter->second;
}

} // namespace cel::ast_internal
} // namespace cel
134 changes: 119 additions & 15 deletions common/ast.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,38 +15,142 @@
#ifndef THIRD_PARTY_CEL_CPP_COMMON_AST_H_
#define THIRD_PARTY_CEL_CPP_COMMON_AST_H_

#include <cstdint>
#include <string>
#include <utility>

#include "absl/base/nullability.h"
#include "absl/container/flat_hash_map.h"
#include "absl/strings/string_view.h"
#include "common/ast/metadata.h" // IWYU pragma: export
#include "common/expr.h"

namespace cel {

namespace ast_internal {
// Forward declare supported implementations.
class AstImpl;
} // namespace ast_internal

// Runtime representation of a CEL expression's Abstract Syntax Tree.
// In memory representation of a CEL abstract syntax tree.
//
// If AST inspection or manipulation is needed, prefer to use an existing tool
// or traverse the protobuf representation rather than directly manipulating
// through this class. See `cel::NavigableAst` and `cel::AstTraverse`.
//
// This class provides public APIs for CEL users and allows for clients to
// manage lifecycle.
// Type and reference maps are only populated if the AST is checked. Any changes
// to the AST are not automatically reflected in the type or reference maps.
//
// Implementations are intentionally opaque to prevent dependencies on the
// details of the runtime representation. To create a new instance, from a
// protobuf representation, use the conversion utilities in
// `extensions/protobuf/ast_converters.h`.
// To create a new instance from a protobuf representation, use the conversion
// utilities in `common/ast_proto.h`.
class Ast {
public:
using ReferenceMap = absl::flat_hash_map<int64_t, Reference>;
using TypeMap = absl::flat_hash_map<int64_t, TypeSpec>;

virtual ~Ast() = default;

// Whether the AST includes type check information.
// If false, the runtime assumes all types are dyn, and that qualified names
// have not been resolved.
virtual bool IsChecked() const = 0;
Ast() : is_checked_(false) {}

Ast(Expr expr, SourceInfo source_info)
: root_expr_(std::move(expr)),
source_info_(std::move(source_info)),
is_checked_(false) {}

Ast(Expr expr, SourceInfo source_info, ReferenceMap reference_map,
TypeMap type_map, std::string expr_version)
: root_expr_(std::move(expr)),
source_info_(std::move(source_info)),
reference_map_(std::move(reference_map)),
type_map_(std::move(type_map)),
expr_version_(std::move(expr_version)),
is_checked_(true) {}

// Move-only
Ast(const Ast& other) = delete;
Ast& operator=(const Ast& other) = delete;
Ast(Ast&& other) = default;
Ast& operator=(Ast&& other) = default;

// Deprecated. Use `is_checked()` instead.
bool IsChecked() const { return is_checked_; }

bool is_checked() const { return is_checked_; }
void set_is_checked(bool is_checked) { is_checked_ = is_checked; }

// The root expression of the AST.
//
// This is the entry point for evaluation and determines the overall result
// of the expression given a context.
const Expr& root_expr() const { return root_expr_; }
Expr& mutable_root_expr() { return root_expr_; }

// Metadata about the source expression.
const SourceInfo& source_info() const { return source_info_; }
SourceInfo& mutable_source_info() { return source_info_; }

// Returns the type of the expression with the given `expr_id`.
//
// Returns `nullptr` if the expression node is not found or has dynamic type.
const TypeSpec* absl_nullable GetType(int64_t expr_id) const;
const TypeSpec& GetTypeOrDyn(int64_t expr_id) const;
const TypeSpec& GetReturnType() const;

// Returns the resolved reference for the expression with the given `expr_id`.
//
// Returns `nullptr` if the expression node is not found or no reference was
// resolved.
const Reference* absl_nullable GetReference(int64_t expr_id) const;

// A map from expression ids to resolved references.
//
// The following entries are in this table:
//
// - An Ident or Select expression is represented here if it resolves to a
// declaration. For instance, if `a.b.c` is represented by
// `select(select(id(a), b), c)`, and `a.b` resolves to a declaration,
// while `c` is a field selection, then the reference is attached to the
// nested select expression (but not to the id or or the outer select).
// In turn, if `a` resolves to a declaration and `b.c` are field selections,
// the reference is attached to the ident expression.
// - Every Call expression has an entry here, identifying the function being
// called.
// - Every CreateStruct expression for a message has an entry, identifying
// the message.
//
// Unpopulated if the AST is not checked.
const ReferenceMap& reference_map() const { return reference_map_; }
ReferenceMap& mutable_reference_map() { return reference_map_; }

// A map from expression ids to types.
//
// Every expression node which has a type different than DYN has a mapping
// here. If an expression has type DYN, it is omitted from this map to save
// space.
//
// Unpopulated if the AST is not checked.
const TypeMap& type_map() const { return type_map_; }
TypeMap& mutable_type_map() { return type_map_; }

// The expr version indicates the major / minor version number of the `expr`
// representation.
//
// The most common reason for a version change will be to indicate to the CEL
// runtimes that transformations have been performed on the expr during static
// analysis.
absl::string_view expr_version() const { return expr_version_; }
void set_expr_version(absl::string_view expr_version) {
expr_version_ = expr_version;
}

private:
// This interface should only be implemented by friend-visibility allowed
// subclasses.
Ast() = default;
friend class ast_internal::AstImpl;

Expr root_expr_;
SourceInfo source_info_;
ReferenceMap reference_map_;
TypeMap type_map_;
std::string expr_version_;
bool is_checked_;
};

} // namespace cel
Expand Down
4 changes: 0 additions & 4 deletions common/ast/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -75,17 +75,13 @@ cc_test(

cc_library(
name = "ast_impl",
srcs = ["ast_impl.cc"],
hdrs = ["ast_impl.h"],
deps = [
":expr",
":metadata",
"//common:ast",
"//common:expr",
"//internal:casts",
"@com_google_absl//absl/base:nullability",
"@com_google_absl//absl/container:flat_hash_map",
"@com_google_absl//absl/strings:string_view",
],
)

Expand Down
109 changes: 8 additions & 101 deletions common/ast/ast_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,9 @@
#ifndef THIRD_PARTY_CEL_CPP_BASE_AST_INTERNAL_AST_IMPL_H_
#define THIRD_PARTY_CEL_CPP_BASE_AST_INTERNAL_AST_IMPL_H_

#include <cstdint>
#include <string>
#include <utility>

#include "absl/container/flat_hash_map.h"
#include "absl/strings/string_view.h"
#include "common/ast.h"
#include "common/ast/expr.h"
#include "common/ast/metadata.h" // IWYU pragma: export
Expand All @@ -29,18 +26,13 @@

namespace cel::ast_internal {

// In memory representation of a CEL abstract syntax tree.
// Trivial subclass of the public Ast.
//
// If AST inspection or manipulation is needed, prefer to use an existing tool
// or traverse the protobuf representation rather than directly manipulating
// through this class. See `cel::NavigableAst` and `cel::AstTraverse`.
//
// Type and reference maps are only populated if the AST is checked. Any changes
// to the AST are not automatically reflected in the type or reference maps.
// Temporarily needed to transition to just using the public Ast.
class AstImpl : public Ast {
public:
using ReferenceMap = absl::flat_hash_map<int64_t, Reference>;
using TypeMap = absl::flat_hash_map<int64_t, Type>;
using ReferenceMap = Ast::ReferenceMap;
using TypeMap = Ast::TypeMap;

// Overloads for down casting from the public interface to the internal
// implementation.
Expand All @@ -60,106 +52,21 @@ class AstImpl : public Ast {
return cel::internal::down_cast<const AstImpl*>(ast);
}

AstImpl() : is_checked_(false) {}
AstImpl() = default;

AstImpl(Expr expr, SourceInfo source_info)
: root_expr_(std::move(expr)),
source_info_(std::move(source_info)),
is_checked_(false) {}
: Ast(std::move(expr), std::move(source_info)) {}

AstImpl(Expr expr, SourceInfo source_info, ReferenceMap reference_map,
TypeMap type_map, std::string expr_version)
: root_expr_(std::move(expr)),
source_info_(std::move(source_info)),
reference_map_(std::move(reference_map)),
type_map_(std::move(type_map)),
expr_version_(std::move(expr_version)),
is_checked_(true) {}
: Ast(std::move(expr), std::move(source_info), std::move(reference_map),
std::move(type_map), std::move(expr_version)) {}

// Move-only
AstImpl(const AstImpl& other) = delete;
AstImpl& operator=(const AstImpl& other) = delete;
AstImpl(AstImpl&& other) = default;
AstImpl& operator=(AstImpl&& other) = default;

// Implement public Ast APIs.
bool IsChecked() const override { return is_checked_; }

bool is_checked() const { return is_checked_; }
void set_is_checked(bool is_checked) { is_checked_ = is_checked; }

// The root expression of the AST.
//
// This is the entry point for evaluation and determines the overall result
// of the expression given a context.
const Expr& root_expr() const { return root_expr_; }
Expr& mutable_root_expr() { return root_expr_; }

// Metadata about the source expression.
const SourceInfo& source_info() const { return source_info_; }
SourceInfo& mutable_source_info() { return source_info_; }

// Returns the type of the expression with the given `expr_id`.
//
// Returns `nullptr` if the expression node is not found or has dynamic type.
const TypeSpec* absl_nullable GetType(int64_t expr_id) const;
const TypeSpec& GetTypeOrDyn(int64_t expr_id) const;
const TypeSpec& GetReturnType() const;

// Returns the resolved reference for the expression with the given `expr_id`.
//
// Returns `nullptr` if the expression node is not found or no reference was
// resolved.
const Reference* absl_nullable GetReference(int64_t expr_id) const;

// A map from expression ids to resolved references.
//
// The following entries are in this table:
//
// - An Ident or Select expression is represented here if it resolves to a
// declaration. For instance, if `a.b.c` is represented by
// `select(select(id(a), b), c)`, and `a.b` resolves to a declaration,
// while `c` is a field selection, then the reference is attached to the
// nested select expression (but not to the id or or the outer select).
// In turn, if `a` resolves to a declaration and `b.c` are field selections,
// the reference is attached to the ident expression.
// - Every Call expression has an entry here, identifying the function being
// called.
// - Every CreateStruct expression for a message has an entry, identifying
// the message.
//
// Unpopulated if the AST is not checked.
const ReferenceMap& reference_map() const { return reference_map_; }
ReferenceMap& mutable_reference_map() { return reference_map_; }

// A map from expression ids to types.
//
// Every expression node which has a type different than DYN has a mapping
// here. If an expression has type DYN, it is omitted from this map to save
// space.
//
// Unpopulated if the AST is not checked.
const TypeMap& type_map() const { return type_map_; }
TypeMap& mutable_type_map() { return type_map_; }

// The expr version indicates the major / minor version number of the `expr`
// representation.
//
// The most common reason for a version change will be to indicate to the CEL
// runtimes that transformations have been performed on the expr during static
// analysis.
absl::string_view expr_version() const { return expr_version_; }
void set_expr_version(absl::string_view expr_version) {
expr_version_ = expr_version;
}

private:
Expr root_expr_;
SourceInfo source_info_;
ReferenceMap reference_map_;
TypeMap type_map_;
std::string expr_version_;
bool is_checked_;
};

} // namespace cel::ast_internal
Expand Down
Loading