diff --git a/cel-c/BUILD b/cel-c/BUILD index d4208ed..2f4a0a2 100644 --- a/cel-c/BUILD +++ b/cel-c/BUILD @@ -46,7 +46,7 @@ package_group( cc_library( name = "alloc", srcs = [ - "alloc.c", + "alloc.cc", ], hdrs = [ "alloc.h", @@ -75,7 +75,7 @@ cc_test( cc_library( name = "arena", srcs = [ - "arena.c", + "arena.cc", ], hdrs = [ "arena.h", @@ -106,7 +106,7 @@ cc_test( cc_library( name = "assert", - srcs = ["assert.c"], + srcs = ["assert.cc"], hdrs = ["assert.h"], visibility = [":friends"], deps = [ @@ -127,7 +127,7 @@ cc_test( cc_library( name = "ast", srcs = [ - "ast.c", + "ast.cc", ], hdrs = [ "ast.h", @@ -165,7 +165,7 @@ cc_test( cc_library( name = "ast_proto", srcs = [ - "ast_proto.c", + "ast_proto.cc", ], hdrs = [ "ast_proto.h", @@ -221,7 +221,7 @@ cc_test( cc_library( name = "ast_proto_v1alpha1", srcs = [ - "ast_proto_v1alpha1.c", + "ast_proto_v1alpha1.cc", ], hdrs = [ "ast_proto_v1alpha1.h", @@ -276,7 +276,7 @@ cc_test( cc_library( name = "ast_traverse", srcs = [ - "ast_traverse.c", + "ast_traverse.cc", ], hdrs = [ "ast_traverse.h", @@ -331,7 +331,7 @@ cc_library( cc_library( name = "constant", - srcs = ["constant.c"], + srcs = ["constant.cc"], hdrs = ["constant.h"], visibility = [":friends"], deps = [ @@ -358,7 +358,7 @@ cc_test( cc_library( name = "constant_proto", - srcs = ["constant_proto.c"], + srcs = ["constant_proto.cc"], hdrs = ["constant_proto.h"], visibility = [":friends"], deps = [ @@ -403,7 +403,7 @@ cc_test( cc_library( name = "constant_proto_v1alpha1", - srcs = ["constant_proto_v1alpha1.c"], + srcs = ["constant_proto_v1alpha1.cc"], hdrs = ["constant_proto_v1alpha1.h"], visibility = [":friends"], deps = [ @@ -449,7 +449,7 @@ cc_test( cc_library( name = "cstring_view", srcs = [ - "cstring_view.c", + "cstring_view.cc", ], hdrs = [ "cstring_view.h", @@ -476,7 +476,7 @@ cc_test( cc_library( name = "decl", srcs = [ - "decl.c", + "decl.cc", ], hdrs = [ "decl.h", @@ -514,7 +514,7 @@ cc_test( cc_library( name = "decl_proto", srcs = [ - "decl_proto.c", + "decl_proto.cc", ], hdrs = [ "decl_proto.h", @@ -561,7 +561,7 @@ cc_test( cc_library( name = "decl_proto_v1alpha1", srcs = [ - "decl_proto_v1alpha1.c", + "decl_proto_v1alpha1.cc", ], hdrs = [ "decl_proto_v1alpha1.h", @@ -608,7 +608,7 @@ cc_test( cc_library( name = "duration", srcs = [ - "duration.c", + "duration.cc", ], hdrs = [ "duration.h", @@ -656,7 +656,7 @@ cc_test( cc_library( name = "duration_proto", srcs = [ - "duration_proto.c", + "duration_proto.cc", ], hdrs = [ "duration_proto.h", @@ -686,7 +686,7 @@ cc_test( cc_library( name = "error", srcs = [ - "error.c", + "error.cc", ], hdrs = [ "error.h", @@ -760,7 +760,7 @@ cc_test( cc_library( name = "error_proto", - srcs = ["error_proto.c"], + srcs = ["error_proto.cc"], hdrs = ["error_proto.h"], visibility = [":friends"], deps = [ @@ -800,7 +800,7 @@ cc_test( cc_library( name = "error_code", srcs = [ - "error_code.c", + "error_code.cc", ], hdrs = [ "error_code.h", @@ -869,7 +869,7 @@ cc_test( cc_library( name = "error_space", srcs = [ - "error_space.c", + "error_space.cc", ], hdrs = [ "error_space.h", @@ -901,7 +901,7 @@ cc_test( cc_library( name = "error_status", srcs = [ - "error_status.c", + "error_status.cc", ], hdrs = [ "error_status.h", @@ -947,7 +947,7 @@ cc_library( cc_library( name = "hash", srcs = [ - "hash.c", + "hash.cc", ], hdrs = [ "hash.h", @@ -977,7 +977,7 @@ cc_test( cc_library( name = "operators", srcs = [ - "operators.c", + "operators.cc", ], hdrs = [ "operators.h", @@ -1066,7 +1066,7 @@ cc_library( cc_library( name = "ref", srcs = [ - "ref.c", + "ref.cc", ], hdrs = [ "ref.h", @@ -1084,7 +1084,7 @@ cc_library( cc_library( name = "ref_proto", srcs = [ - "ref_proto.c", + "ref_proto.cc", ], hdrs = [ "ref_proto.h", @@ -1127,7 +1127,7 @@ cc_test( cc_library( name = "ref_proto_v1alpha1", srcs = [ - "ref_proto_v1alpha1.c", + "ref_proto_v1alpha1.cc", ], hdrs = [ "ref_proto_v1alpha1.h", @@ -1171,7 +1171,7 @@ cc_test( cc_library( name = "status", srcs = [ - "status.c", + "status.cc", ], hdrs = [ "status.h", @@ -1242,7 +1242,7 @@ cc_test( cc_library( name = "status_proto", - srcs = ["status_proto.c"], + srcs = ["status_proto.cc"], hdrs = ["status_proto.h"], visibility = [":friends"], deps = [ @@ -1282,7 +1282,7 @@ cc_test( cc_library( name = "status_code", srcs = [ - "status_code.c", + "status_code.cc", ], hdrs = [ "status_code.h", @@ -1352,7 +1352,7 @@ cc_test( cc_library( name = "string_view", srcs = [ - "string_view.c", + "string_view.cc", ], hdrs = [ "string_view.h", @@ -1404,7 +1404,7 @@ cc_test( cc_library( name = "timestamp", srcs = [ - "timestamp.c", + "timestamp.cc", ], hdrs = [ "timestamp.h", @@ -1454,7 +1454,7 @@ cc_test( cc_library( name = "timestamp_proto", srcs = [ - "timestamp_proto.c", + "timestamp_proto.cc", ], hdrs = [ "timestamp_proto.h", @@ -1494,7 +1494,7 @@ cc_library( cc_library( name = "type", srcs = [ - "type.c", + "type.cc", ], hdrs = [ "type.h", @@ -1546,7 +1546,7 @@ cc_library( cc_library( name = "type_proto", srcs = [ - "type_proto.c", + "type_proto.cc", ], hdrs = [ "type_proto.h", @@ -1586,7 +1586,7 @@ cc_test( cc_library( name = "type_proto_v1alpha1", srcs = [ - "type_proto_v1alpha1.c", + "type_proto_v1alpha1.cc", ], hdrs = [ "type_proto_v1alpha1.h", @@ -1681,7 +1681,7 @@ cc_library( cc_library( name = "well_known_types", srcs = [ - "well_known_types.c", + "well_known_types.cc", ], hdrs = [ "well_known_types.h", diff --git a/cel-c/alloc.cc b/cel-c/alloc.cc new file mode 100644 index 0000000..eb75a96 --- /dev/null +++ b/cel-c/alloc.cc @@ -0,0 +1,141 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "cel-c/alloc.h" + +#include +#include +#include +#include +#include + +#include "cel-c/assert.h" +#include "cel-c/config.h" +#include "cel-c/src/ckdint.h" +#include "cel-c/src/malloc.h" +#include "upb/mem/alloc.h" + +extern "C" CEL_NULLABLE(void*) + cel_Allocator_Calloc(CEL_NONNULL(cel_Allocator*) alloc, size_t num, + size_t size, CEL_NULLABLE(size_t*) actual_num) { + CEL_ASSERT_NOT_NULL(alloc); + CEL_ASSERT_NOT_NULL(alloc->func); + + size_t total_size; + if (CEL_UNLIKELY(_cel_ckd_mul(&total_size, num, size))) { + errno = ENOMEM; + return cel_nullptr; + } + void* addr = (*alloc->func)(alloc, cel_nullptr, 0, total_size, actual_num); + if (actual_num != cel_nullptr && addr != cel_nullptr) { + *actual_num /= size; + } + return addr; +} + +static CEL_NULLABILITY_UNKNOWN(void*) _cel_DefaultAllocator_AllocFunc( + CEL_NULLABILITY_UNKNOWN(upb_alloc*) alloc, + CEL_NULLABILITY_UNKNOWN(void*) ptr, size_t oldsize, size_t size, + size_t* cel_nullability_unknown actual_size) { + CEL_ASSERT_NOT_NULL(alloc); + + if (ptr == cel_nullptr) { + CEL_ASSERT_EQ(oldsize, 0); + return _cel_Malloc(size, actual_size); + } + if (size == 0) { + if (oldsize != 0) { + _cel_FreeSized(ptr, oldsize); + } else { + _cel_Free(ptr); + } + if (actual_size != cel_nullptr) { + *actual_size = 0; + } + return cel_nullptr; + } + return _cel_Realloc(ptr, oldsize, size, actual_size); +} + +static cel_Allocator _cel_DefaultAllocator = { + .func = &_cel_DefaultAllocator_AllocFunc, +}; + +extern "C" CEL_NONNULL(cel_Allocator*) const cel_DefaultAllocator = + &_cel_DefaultAllocator; + +extern "C" CEL_NULLABLE(char*) + cel_Allocator_StrDup(CEL_NONNULL(cel_Allocator*) alloc, + CEL_NULLABLE(size_t*) actual_size, + CEL_NONNULL(const char*) str) { + CEL_ASSERT_NOT_NULL(alloc); + CEL_ASSERT_NOT_NULL(str); + size_t len = strlen(str) + 1; + CEL_NULLABLE(char*) + dup = (CEL_NULLABLE(char*))cel_Allocator_Malloc(alloc, len, actual_size); + if (CEL_UNLIKELY(dup == cel_nullptr)) { + return cel_nullptr; + } + memcpy(dup, str, len); + return dup; +} + +extern "C" CEL_NULLABLE(char*) + cel_Allocator_PrintF(CEL_NONNULL(cel_Allocator*) alloc, + CEL_NULLABLE(size_t*) actual_size, + CEL_NONNULL(const char*) fmt, ...) { + va_list args; + va_start(args, fmt); + CEL_NULLABLE(char*) + str = cel_Allocator_VPrintF(alloc, actual_size, fmt, args); + va_end(args); + return str; +} + +extern "C" CEL_NULLABLE(char*) + cel_Allocator_VPrintF(CEL_NONNULL(cel_Allocator*) alloc, + CEL_NULLABLE(size_t*) actual_size, + CEL_NONNULL(const char*) fmt, va_list args) { + CEL_ASSERT_NOT_NULL(alloc); + CEL_ASSERT_NOT_NULL(fmt); + char buf[128]; + int size; + { + va_list args_copy; + va_copy(args_copy, args); + size = vsnprintf(buf, sizeof(buf), fmt, args_copy); + va_end(args_copy); + } + CEL_ASSERT_GE(size, 0); + if (CEL_UNLIKELY(size < 0)) { + errno = EINVAL; + if (actual_size != cel_nullptr) { + *actual_size = 0; + } + return cel_nullptr; + } + CEL_NULLABLE(char*) + str = (CEL_NULLABLE(char*))cel_Allocator_Malloc(alloc, ((size_t)size) + 1, + actual_size); + if (CEL_UNLIKELY(str == cel_nullptr)) { + return cel_nullptr; + } + if (size < sizeof(buf)) { + memcpy(str, buf, ((size_t)size) + 1); + } else { + const int size_copy = vsnprintf(str, ((size_t)size) + 1, fmt, args); + CEL_ASSERT_EQ(size_copy, size); + } + return str; +} diff --git a/cel-c/arena.cc b/cel-c/arena.cc new file mode 100644 index 0000000..eb03b48 --- /dev/null +++ b/cel-c/arena.cc @@ -0,0 +1,128 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "cel-c/arena.h" + +#include +#include +#include +#include +#include + +#include "cel-c/assert.h" +#include "cel-c/config.h" +#include "cel-c/src/align.h" +#include "cel-c/src/ckdint.h" +#include "upb/base/string_view.h" +#include "upb/mem/arena.h" + +extern "C" CEL_NULLABLE(void*) + cel_Arena_Calloc(CEL_NONNULL(cel_Arena*) arena, size_t num, size_t size, + CEL_NULLABLE(size_t*) actual_num) { + CEL_ASSERT_NOT_NULL(arena); + + if (CEL_UNLIKELY(num == 0 || size == 0)) { + if (actual_num != cel_nullptr) { + *actual_num = 0; + } + return cel_nullptr; + } + size_t total_size; + if (CEL_UNLIKELY(_cel_ckd_mul(&total_size, num, size))) { + if (actual_num != cel_nullptr) { + *actual_num = 0; + } + return cel_nullptr; + } + CEL_NULLABLE(void*) addr = upb_Arena_Malloc(arena, total_size); + CEL_ASSERT(addr == cel_nullptr || _cel_is_aligned(addr, cel_Arena_kMaxAlign)); + if (CEL_LIKELY(addr != cel_nullptr)) { + memset(addr, '\0', total_size); + } + if (actual_num != cel_nullptr) { + *actual_num = addr != cel_nullptr ? num : 0; + } + return addr; +} + +extern "C" CEL_NULLABLE(void*) + cel_Arena_Realloc(CEL_NONNULL(cel_Arena*) arena, CEL_NULLABLE(void*) addr, + size_t old_size, size_t new_size, + CEL_NULLABLE(size_t*) actual_size) { + CEL_ASSERT_NOT_NULL(arena); + + if (old_size == 0) { + CEL_ASSERT_NULL(addr); + return cel_Arena_Malloc(arena, new_size, actual_size); + } + CEL_NULLABLE(void*) + new_addr = upb_Arena_Realloc(arena, addr, old_size, new_size); + CEL_ASSERT(addr == cel_nullptr || _cel_is_aligned(addr, cel_Arena_kMaxAlign)); + if (actual_size != cel_nullptr) { + *actual_size = new_addr != cel_nullptr ? new_size : 0; + } + return new_size != 0 ? new_addr : cel_nullptr; +} + +extern "C" void cel_Arena_FreeSized(CEL_NONNULL(cel_Arena*) arena, + CEL_NULLABLE(void*) addr, size_t size) { + CEL_ASSERT_NOT_NULL(arena); + + if (CEL_UNLIKELY(size == 0)) { + CEL_ASSERT_NULL(addr); + return; + } + CEL_ASSERT_NOT_NULL(addr); + CEL_USED(upb_Arena_Realloc(arena, addr, size, 0)); +} + +extern "C" bool cel_Arena_VPrintF(CEL_NONNULL(cel_Arena*) arena, + CEL_NONNULL(upb_StringView*) out, + CEL_NONNULL(const char*) fmt, va_list args) { + CEL_ASSERT_NOT_NULL(arena); + CEL_ASSERT_NOT_NULL(out); + CEL_ASSERT_NOT_NULL(fmt); + + char buf[128]; + int size; + { + va_list args_copy; + va_copy(args_copy, args); + size = vsnprintf(buf, sizeof(buf), fmt, args_copy); + va_end(args_copy); + } + CEL_ASSERT_GE(size, 0); + if (CEL_UNLIKELY(size < 0)) { + return false; + } + if (CEL_UNLIKELY(size == 0)) { + *out = upb_StringView_FromDataAndSize("", 0); + return true; + } + const bool fit = size < sizeof(buf); + CEL_NULLABLE(char*) + str = (CEL_NULLABLE(char*))cel_Arena_Malloc( + arena, ((size_t)size) + (fit ? 0 : 1), cel_nullptr); + if (CEL_UNLIKELY(str == cel_nullptr)) { + return false; + } + if (fit) { + memcpy(str, buf, (size_t)size); + } else { + const int size_copy = vsnprintf(str, ((size_t)size) + 1, fmt, args); + CEL_ASSERT_EQ(size_copy, size); + } + *out = upb_StringView_FromDataAndSize(str, (size_t)size); + return true; +} diff --git a/cel-c/assert.cc b/cel-c/assert.cc new file mode 100644 index 0000000..0ff170e --- /dev/null +++ b/cel-c/assert.cc @@ -0,0 +1,78 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "cel-c/assert.h" + +#include +#include +#include + +#ifdef __ANDROID__ +#include +#if __ANDROID_API__ >= 21 +#include +extern void android_set_abort_message(const char* msg); +#else +#include +#endif +#endif + +#include "cel-c/config.h" + +#if defined(__APPLE__) && CEL_HAVE_INCLUDE() +#include +#endif + +#ifdef _MSC_VER +#include +#endif + +extern "C" void cel_AssertionFailed(CEL_NONNULL(const char*) file, int line, + CEL_NONNULL(const char*) cond) { + CEL_USED(file); + CEL_USED(line); + CEL_USED(cond); +#ifndef NDEBUG + fprintf(stderr, "%s:%d: Assertion `%s' failed\n", file, line, cond); + fflush(stderr); +#ifdef __ANDROID__ + CEL_NULLABLE(char*) amsg = cel_nullptr; + asprintf(&amsg, "%s:%d: Assertion `%s' failed\n", file, line, cond); + if (amsg == cel_nullptr) { + amsg = (CEL_NULLABLE(char*)) ""; + } +#if __ANDROID_API__ >= 21 + android_set_abort_message(amsg); + openlog("cel", 0, 0); + syslog(LOG_CRIT, "%s", amsg); + closelog(); +#else + __assert2(file, line, __func__, amsg); +#endif +#elif defined(__APPLE__) && CEL_HAVE_INCLUDE() + CEL_NULLABLE(char*) amsg = cel_nullptr; + asprintf(&amsg, "%s:%d: Assertion `%s' failed\n", file, line, cond); + if (amsg == cel_nullptr) { + amsg = (CEL_NULLABLE(char*)) ""; + } + CRSetCrashLogMessage(amsg); +#endif +#endif +#if !defined(NDEBUG) && CEL_HAVE_BUILTIN(__builtin_debugtrap) + __builtin_debugtrap(); +#elif !defined(NDEBUG) && defined(_MSC_VER) + __debugbreak(); +#endif + abort(); +} diff --git a/cel-c/ast.cc b/cel-c/ast.cc new file mode 100644 index 0000000..efe87ce --- /dev/null +++ b/cel-c/ast.cc @@ -0,0 +1,1900 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "cel-c/ast.h" + +#include +#include +#include +#include + +#include "cel-c/arena.h" +#include "cel-c/assert.h" +#include "cel-c/config.h" +#include "cel-c/constant.h" +#include "cel-c/operators.h" +#include "cel-c/ref.h" +#include "cel-c/source.h" +#include "cel-c/string_view.h" +#include "cel-c/type.h" + +typedef struct _cel_ExprLink _cel_ExprLink; + +struct _cel_ExprLink { + cel_ExprId id; + CEL_NONNULL(cel_Ast*) ast; + CEL_NULLABLE(cel_Expr*) parent; + cel_ExprKind kind; + cel_SourceRange range; + CEL_NULLABLE(const cel_Ref*) ref; + CEL_NULLABLE(cel_Type*) type; + + CEL_NULLABLE(_cel_ExprLink*) left; + CEL_NULLABLE(_cel_ExprLink*) right; + ptrdiff_t index; +}; + +struct cel_UnspecifiedExpr { + cel_ExprId id; + CEL_NONNULL(cel_Ast*) ast; + CEL_NULLABLE(cel_Expr*) parent; + cel_ExprKind kind; + cel_SourceRange range; + CEL_NULLABLE(const cel_Ref*) ref; + CEL_NULLABLE(cel_Type*) type; +}; + +struct cel_IdentExpr { + cel_ExprId id; + CEL_NONNULL(cel_Ast*) ast; + CEL_NULLABLE(cel_Expr*) parent; + cel_ExprKind kind; + cel_SourceRange range; + CEL_NULLABLE(const cel_Ref*) ref; + CEL_NULLABLE(cel_Type*) type; + + cel_StringView name; +}; + +struct cel_ConstExpr { + cel_ExprId id; + CEL_NONNULL(cel_Ast*) ast; + CEL_NULLABLE(cel_Expr*) parent; + cel_ExprKind kind; + cel_SourceRange range; + CEL_NULLABLE(const cel_Ref*) ref; + CEL_NULLABLE(cel_Type*) type; + + cel_Constant value; +}; + +struct cel_SelectExpr { + cel_ExprId id; + CEL_NONNULL(cel_Ast*) ast; + CEL_NULLABLE(cel_Expr*) parent; + cel_ExprKind kind; + cel_SourceRange range; + CEL_NULLABLE(const cel_Ref*) ref; + CEL_NULLABLE(cel_Type*) type; + + CEL_NULLABLE(cel_Expr*) operand; + cel_StringView field; + bool test_only; +}; + +struct cel_CallExpr { + cel_ExprId id; + CEL_NONNULL(cel_Ast*) ast; + CEL_NULLABLE(cel_Expr*) parent; + cel_ExprKind kind; + cel_SourceRange range; + CEL_NULLABLE(const cel_Ref*) ref; + CEL_NULLABLE(cel_Type*) type; + + cel_StringView function; + CEL_NULLABLE(cel_Expr*) target; + CEL_NULLABLE(_cel_ExprLink*) args_head; + CEL_NULLABLE(_cel_ExprLink*) args_tail; + size_t args; +}; + +struct cel_CallArgExpr { + cel_ExprId id; + CEL_NONNULL(cel_Ast*) ast; + CEL_NULLABLE(cel_Expr*) parent; + cel_ExprKind kind; + cel_SourceRange range; + CEL_NULLABLE(const cel_Ref*) ref; + CEL_NULLABLE(cel_Type*) type; + + CEL_NULLABLE(_cel_ExprLink*) left; + CEL_NULLABLE(_cel_ExprLink*) right; + ptrdiff_t index; + CEL_NULLABLE(cel_Expr*) value; +}; + +struct cel_UnaryExpr { + cel_ExprId id; + CEL_NONNULL(cel_Ast*) ast; + CEL_NULLABLE(cel_Expr*) parent; + cel_ExprKind kind; + cel_SourceRange range; + CEL_NULLABLE(const cel_Ref*) ref; + CEL_NULLABLE(cel_Type*) type; + + cel_UnaryOp op; + CEL_NULLABLE(cel_Expr*) args[1]; +}; + +struct cel_BinaryExpr { + cel_ExprId id; + CEL_NONNULL(cel_Ast*) ast; + CEL_NULLABLE(cel_Expr*) parent; + cel_ExprKind kind; + cel_SourceRange range; + CEL_NULLABLE(const cel_Ref*) ref; + CEL_NULLABLE(cel_Type*) type; + + cel_BinaryOp op; + CEL_NULLABLE(cel_Expr*) args[2]; +}; + +struct cel_TernaryExpr { + cel_ExprId id; + CEL_NONNULL(cel_Ast*) ast; + CEL_NULLABLE(cel_Expr*) parent; + cel_ExprKind kind; + cel_SourceRange range; + CEL_NULLABLE(const cel_Ref*) ref; + CEL_NULLABLE(cel_Type*) type; + + cel_TernaryOp op; + CEL_NULLABLE(cel_Expr*) args[3]; +}; + +struct cel_ListElementExpr { + cel_ExprId id; + CEL_NONNULL(cel_Ast*) ast; + CEL_NULLABLE(cel_Expr*) parent; + cel_ExprKind kind; + cel_SourceRange range; + CEL_NULLABLE(const cel_Ref*) ref; + CEL_NULLABLE(cel_Type*) type; + + CEL_NULLABLE(_cel_ExprLink*) left; + CEL_NULLABLE(_cel_ExprLink*) right; + ptrdiff_t index; + CEL_NULLABLE(cel_Expr*) value; + bool optional; +}; + +struct cel_ListExpr { + cel_ExprId id; + CEL_NONNULL(cel_Ast*) ast; + CEL_NULLABLE(cel_Expr*) parent; + cel_ExprKind kind; + cel_SourceRange range; + CEL_NULLABLE(const cel_Ref*) ref; + CEL_NULLABLE(cel_Type*) type; + + CEL_NULLABLE(_cel_ExprLink*) elems_head; + CEL_NULLABLE(_cel_ExprLink*) elems_tail; + size_t elems; +}; + +struct cel_MapEntryExpr { + cel_ExprId id; + CEL_NONNULL(cel_Ast*) ast; + CEL_NULLABLE(cel_Expr*) parent; + cel_ExprKind kind; + cel_SourceRange range; + CEL_NULLABLE(const cel_Ref*) ref; + CEL_NULLABLE(cel_Type*) type; + + CEL_NULLABLE(_cel_ExprLink*) left; + CEL_NULLABLE(_cel_ExprLink*) right; + ptrdiff_t index; + CEL_NULLABLE(cel_Expr*) key; + CEL_NULLABLE(cel_Expr*) value; + bool optional; +}; + +struct cel_MapExpr { + cel_ExprId id; + CEL_NONNULL(cel_Ast*) ast; + CEL_NULLABLE(cel_Expr*) parent; + cel_ExprKind kind; + cel_SourceRange range; + CEL_NULLABLE(const cel_Ref*) ref; + CEL_NULLABLE(cel_Type*) type; + + CEL_NULLABLE(_cel_ExprLink*) ents_head; + CEL_NULLABLE(_cel_ExprLink*) ents_tail; + size_t ents; +}; + +struct cel_StructFieldExpr { + cel_ExprId id; + CEL_NONNULL(cel_Ast*) ast; + CEL_NULLABLE(cel_Expr*) parent; + cel_ExprKind kind; + cel_SourceRange range; + CEL_NULLABLE(const cel_Ref*) ref; + CEL_NULLABLE(cel_Type*) type; + + CEL_NULLABLE(_cel_ExprLink*) left; + CEL_NULLABLE(_cel_ExprLink*) right; + ptrdiff_t index; + cel_StringView name; + CEL_NULLABLE(cel_Expr*) value; + bool optional; +}; + +struct cel_StructExpr { + cel_ExprId id; + CEL_NONNULL(cel_Ast*) ast; + CEL_NULLABLE(cel_Expr*) parent; + cel_ExprKind kind; + cel_SourceRange range; + CEL_NULLABLE(const cel_Ref*) ref; + CEL_NULLABLE(cel_Type*) type; + + cel_StringView name; + CEL_NULLABLE(_cel_ExprLink*) flds_head; + CEL_NULLABLE(_cel_ExprLink*) flds_tail; + size_t flds; +}; + +struct cel_ComprehensionExpr { + cel_ExprId id; + CEL_NONNULL(cel_Ast*) ast; + CEL_NULLABLE(cel_Expr*) parent; + cel_ExprKind kind; + cel_SourceRange range; + CEL_NULLABLE(const cel_Ref*) ref; + CEL_NULLABLE(cel_Type*) type; + + cel_StringView iter_var; + cel_StringView iter_var2; + CEL_NULLABLE(cel_Expr*) iter_range; + cel_StringView accu_var; + CEL_NULLABLE(cel_Expr*) accu_init; + CEL_NULLABLE(cel_Expr*) loop_condition; + CEL_NULLABLE(cel_Expr*) loop_step; + CEL_NULLABLE(cel_Expr*) result; +}; + +struct cel_Expr { + union { + struct { + cel_ExprId id; + CEL_NONNULL(cel_Ast*) ast; + CEL_NULLABLE(cel_Expr*) parent; + cel_ExprKind kind; + cel_SourceRange range; + CEL_NULLABLE(const cel_Ref*) ref; + CEL_NULLABLE(const cel_Type*) type; + }; + _cel_ExprLink link; + cel_UnspecifiedExpr unspecified_expr; + cel_IdentExpr ident_expr; + cel_ConstExpr const_expr; + cel_SelectExpr select_expr; + cel_CallExpr call_expr; + cel_UnaryExpr unary_expr; + cel_BinaryExpr binary_expr; + cel_TernaryExpr ternary_expr; + cel_ListElementExpr optional_expr; + cel_ListExpr list_expr; + cel_MapEntryExpr map_entry_expr; + cel_MapExpr map_expr; + cel_StructFieldExpr struct_field_expr; + cel_StructExpr struct_expr; + cel_ComprehensionExpr comprehension_expr; + }; +}; + +struct cel_Ast { + CEL_NONNULL(cel_Arena*) arena; + cel_ExprId next_id; + CEL_NULLABLE(cel_Expr*) expr; +}; + +extern "C" cel_ExprId cel_Ast_NextId(CEL_NONNULL(cel_Ast*) ast) { + CEL_ASSERT_NOT_NULL(ast); + + return ++ast->next_id; +} + +extern "C" cel_ExprId cel_Ast_MaxId(CEL_NONNULL(const cel_Ast*) ast) { + CEL_ASSERT_NOT_NULL(ast); + + return ast->next_id; +} + +extern "C" CEL_NULLABLE(cel_Expr*) + cel_Ast_Expr(CEL_NONNULL(const cel_Ast*) ast) { + CEL_ASSERT_NOT_NULL(ast); + CEL_ASSERT(ast->expr == cel_nullptr || + (ast->expr->ast == ast && ast->expr->parent == cel_nullptr)); + + return ast->expr; +} + +extern "C" CEL_NULLABLE(cel_Expr*) + cel_Ast_SetExpr(CEL_NONNULL(cel_Ast*) ast, CEL_NULLABLE(cel_Expr*) expr) { + CEL_ASSERT_NOT_NULL(ast); + CEL_ASSERT(expr == cel_nullptr || + (expr->ast == ast && expr->parent == cel_nullptr)); + CEL_ASSERT(ast->expr == cel_nullptr || + (ast->expr->ast == ast && ast->expr->parent == cel_nullptr)); + + CEL_NULLABLE(cel_Expr*) old_expr = ast->expr; + ast->expr = expr; + return old_expr; +} + +extern "C" CEL_NULLABLE(cel_Expr*) + cel_Ast_ReleaseExpr(CEL_NONNULL(cel_Ast*) ast) { + return cel_Ast_SetExpr(ast, cel_nullptr); +} + +extern "C" CEL_NULLABLE(cel_Ast*) cel_Ast_New(CEL_NONNULL(cel_Arena*) arena) { + CEL_ASSERT_NOT_NULL(arena); + + CEL_NULLABLE(cel_Ast*) + ast = (CEL_NULLABLE(cel_Ast*))cel_Arena_Malloc(arena, sizeof(cel_Ast), + cel_nullptr); + if (ast != cel_nullptr) { + memset(ast, 0, sizeof(*ast)); + ast->arena = arena; + } + return ast; +} + +extern "C" void cel_Ast_Delete(CEL_NULLABLE(cel_Ast*) ast) {} + +CEL_ATTRIBUTE_NODISCARD +static CEL_NULLABLE(cel_Expr*) + _cel_Expr_ReleaseChild(CEL_NONNULL(cel_Expr*) expr, + cel_Expr * cel_nullable * cel_nonnull child_ptr) { + CEL_NULLABLE(cel_Expr*) child = *child_ptr; + if (child != cel_nullptr) { + CEL_ASSERT_EQ(child->parent, expr); + *child_ptr = cel_nullptr; + child->parent = cel_nullptr; + } + return child; +} + +static CEL_NULLABLE(cel_Expr*) + _cel_Expr_SetChild(CEL_NONNULL(cel_Expr*) expr, + cel_Expr * cel_nullable * cel_nonnull child_ptr, + CEL_NULLABLE(cel_Expr*) child) { + CEL_ASSERT(child == cel_nullptr || + (child->ast == expr->ast && child->parent == cel_nullptr)); + + CEL_NULLABLE(cel_Expr*) old_child = _cel_Expr_ReleaseChild(expr, child_ptr); + *child_ptr = child; + if (child != cel_nullptr) { + child->parent = expr; + } + return old_child; +} + +CEL_ATTRIBUTE_NODISCARD +static CEL_NULLABLE(cel_Expr*) + _cel_Expr_Child(CEL_NONNULL(const cel_Expr*) expr, + CEL_NULLABLE(cel_Expr*) child) { + CEL_ASSERT(child == cel_nullptr || + (child->ast == expr->ast && child->parent == expr)); + + return child; +} + +static void _cel_Expr_LinkChild( + CEL_NONNULL(cel_Expr*) expr, + _cel_ExprLink * cel_nullable * cel_nonnull head_ptr, + _cel_ExprLink * cel_nullable * cel_nonnull tail_ptr, + CEL_NONNULL(size_t*) size_ptr, CEL_NULLABLE(_cel_ExprLink*) next, + CEL_NONNULL(_cel_ExprLink*) child) { + CEL_ASSERT(child != cel_nullptr && child->ast == expr->ast && + child->parent == cel_nullptr && child->left == cel_nullptr && + child->right == cel_nullptr && + (next == cel_nullptr || next->parent == expr)); + + if (next == cel_nullptr) { + // nullptr is end + CEL_NULLABLE(_cel_ExprLink*) head = *head_ptr; + CEL_NULLABLE(_cel_ExprLink*) tail = *tail_ptr; + child->left = tail; + if (tail != cel_nullptr) { + CEL_ASSERT_NOT_NULL(head); + tail->right = child; + child->index = tail->index + 1; + } else { + CEL_ASSERT_NULL(head); + *head_ptr = child; + child->index = 0; + } + *tail_ptr = child; + } else { + CEL_NULLABLE(_cel_ExprLink*) prev = next->left; + child->right = next; + child->index = next->index; + child->left = prev; + next->left = child; + if (prev != cel_nullptr) { + prev->right = child; + } else { + CEL_ASSERT_EQ(*head_ptr, next); + *head_ptr = child; + } + // Fixup indices. + do { + ++next->index; + next = next->right; + } while (next != cel_nullptr); + } + + child->parent = expr; + ++(*size_ptr); +} + +static void _cel_Expr_UnlinkChild( + CEL_NONNULL(cel_Expr*) expr, + _cel_ExprLink * cel_nullable * cel_nonnull head_ptr, + _cel_ExprLink * cel_nullable * cel_nonnull tail_ptr, + CEL_NONNULL(size_t*) size_ptr, CEL_NONNULL(_cel_ExprLink*) child) { + CEL_ASSERT(child != cel_nullptr && child->ast == expr->ast && + child->parent == expr); + + if (child->left != cel_nullptr) { + child->left->right = child->right; + } else { + CEL_ASSERT_EQ(*head_ptr, child); + *head_ptr = child->right; + } + + CEL_NULLABLE(_cel_ExprLink*) + right = child->right; + if (right != cel_nullptr) { + right->left = child->left; + // Fixup indices. + do { + --right->index; + right = right->right; + } while (right != cel_nullptr); + } else { + CEL_ASSERT_EQ(*tail_ptr, child); + *tail_ptr = child->left; + } + + --(*size_ptr); + + child->parent = cel_nullptr; + child->left = child->right = cel_nullptr; + child->index = -1; +} + +extern "C" cel_ExprKind cel_Expr_Kind(CEL_NONNULL(const cel_Expr*) expr) { + CEL_ASSERT_NOT_NULL(expr); + + return expr->kind; +} + +extern "C" cel_ExprId cel_Expr_Id(CEL_NONNULL(const cel_Expr*) expr) { + CEL_ASSERT_NOT_NULL(expr); + + return expr->id; +} + +extern "C" void cel_Expr_SetId(CEL_NONNULL(cel_Expr*) expr, cel_ExprId id) { + CEL_ASSERT_NOT_NULL(expr); + CEL_ASSERT_GE(id, 0); + + if (id < 0) { + id = 0; + } + + if (id > expr->ast->next_id) { + expr->ast->next_id = id; + } + + expr->id = id; +} + +extern "C" int32_t cel_Expr_SourcePosition(CEL_NONNULL(const cel_Expr*) expr) { + CEL_ASSERT_NOT_NULL(expr); + + return expr->range.begin; +} + +extern "C" cel_SourceRange cel_Expr_SourceRange(CEL_NONNULL(const cel_Expr*) + expr) { + CEL_ASSERT_NOT_NULL(expr); + + return expr->range; +} + +extern "C" void cel_Expr_SetSourcePosition(CEL_NONNULL(cel_Expr*) expr, + int32_t source_position) { + CEL_ASSERT_NOT_NULL(expr); + CEL_ASSERT_GE(source_position, -1); + + if (source_position < -1) { + source_position = -1; + } + + expr->range.begin = source_position; + expr->range.end = -1; +} + +extern "C" void cel_Expr_SetSourceRange(CEL_NONNULL(cel_Expr*) expr, + cel_SourceRange source_range) { + CEL_ASSERT_NOT_NULL(expr); + CEL_ASSERT_GE(source_range.begin, -1); + CEL_ASSERT_GE(source_range.end, -1); + CEL_ASSERT(source_range.begin <= source_range.end || source_range.end == -1); + + if (source_range.begin < -1) { + source_range.begin = -1; + } + if (source_range.end < -1) { + source_range.end = -1; + } + + expr->range = source_range; +} + +extern "C" CEL_NULLABLE(cel_Expr*) + cel_Expr_Parent(CEL_NONNULL(const cel_Expr*) expr) { + CEL_ASSERT_NOT_NULL(expr); + + return expr->parent; +} + +extern "C" size_t cel_Expr_Depth(CEL_NONNULL(const cel_Expr*) expr) { + CEL_ASSERT_NOT_NULL(expr); + + CEL_NULLABLE(const cel_Expr*) current = expr; + current = cel_Expr_Parent(current); + size_t depth = 0; + while (current != cel_nullptr) { + ++depth; + current = cel_Expr_Parent(current); + } + return depth; +} + +extern "C" CEL_NULLABLE(const cel_Ref*) + cel_Expr_Ref(CEL_NONNULL(const cel_Expr*) expr) { + CEL_ASSERT_NOT_NULL(expr); + + return expr->ref; +} + +extern "C" void cel_Expr_SetRef(CEL_NONNULL(cel_Expr*) expr, + CEL_NULLABLE(const cel_Ref*) ref) { + CEL_ASSERT_NOT_NULL(expr); + + expr->ref = ref; +} + +extern "C" CEL_NONNULL(const cel_Type*) + cel_Expr_Type(CEL_NONNULL(const cel_Expr*) expr) { + CEL_ASSERT_NOT_NULL(expr); + + if (expr->type == cel_nullptr) { + return cel_DynType; + } + return expr->type; +} + +extern "C" void cel_Expr_SetType(CEL_NONNULL(cel_Expr*) expr, + CEL_NONNULL(const cel_Type*) type) { + CEL_ASSERT_NOT_NULL(expr); + CEL_ASSERT_NOT_NULL(type); + + expr->type = type; +} + +extern "C" CEL_NULLABLE(cel_UnspecifiedExpr*) + cel_UnspecifiedExpr_New(CEL_NONNULL(cel_Ast*) ast) { + CEL_ASSERT_NOT_NULL(ast); + + CEL_NULLABLE(cel_UnspecifiedExpr*) + expr = (CEL_NULLABLE(cel_UnspecifiedExpr*))cel_Arena_Malloc( + ast->arena, sizeof(cel_UnspecifiedExpr), cel_nullptr); + if (expr != cel_nullptr) { + memset(expr, 0, sizeof(*expr)); + expr->ast = ast; + expr->kind = cel_ExprKind_kUnspecified; + expr->range = cel_SourceRange(-1, -1); + } + return expr; +} + +extern "C" CEL_NULLABLE(cel_IdentExpr*) + cel_IdentExpr_New(CEL_NONNULL(cel_Ast*) ast) { + CEL_ASSERT_NOT_NULL(ast); + + CEL_NULLABLE(cel_IdentExpr*) + expr = (CEL_NULLABLE(cel_IdentExpr*))cel_Arena_Malloc( + ast->arena, sizeof(cel_IdentExpr), cel_nullptr); + if (expr != cel_nullptr) { + memset(expr, 0, sizeof(*expr)); + expr->ast = ast; + expr->kind = cel_ExprKind_kIdent; + expr->range = cel_SourceRange(-1, -1); + } + return expr; +} + +extern "C" cel_StringView cel_IdentExpr_Name(CEL_NONNULL(const cel_IdentExpr*) + expr) { + CEL_ASSERT_NOT_NULL(expr); + + return expr->name; +} + +extern "C" void cel_IdentExpr_SetName(CEL_NONNULL(cel_IdentExpr*) expr, + cel_StringView name) { + CEL_ASSERT_NOT_NULL(expr); + + expr->name = name; +} + +extern "C" CEL_NULLABLE(cel_ConstExpr*) + cel_ConstExpr_New(CEL_NONNULL(cel_Ast*) ast) { + CEL_ASSERT_NOT_NULL(ast); + + CEL_NULLABLE(cel_ConstExpr*) + expr = (CEL_NULLABLE(cel_ConstExpr*))cel_Arena_Malloc( + ast->arena, sizeof(cel_ConstExpr), cel_nullptr); + if (expr != cel_nullptr) { + memset(expr, 0, sizeof(*expr)); + expr->ast = ast; + expr->kind = cel_ExprKind_kConst; + expr->range = cel_SourceRange(-1, -1); + } + return expr; +} + +extern "C" CEL_NONNULL(const cel_Constant*) + cel_ConstExpr_Value(CEL_NONNULL(const cel_ConstExpr*) expr) { + CEL_ASSERT_NOT_NULL(expr); + + return &expr->value; +} + +extern "C" CEL_NONNULL(cel_Constant*) + cel_ConstExpr_MutableValue(CEL_NONNULL(cel_ConstExpr*) expr) { + CEL_ASSERT_NOT_NULL(expr); + + return &expr->value; +} + +extern "C" CEL_NULLABLE(cel_SelectExpr*) + cel_SelectExpr_New(CEL_NONNULL(cel_Ast*) ast) { + CEL_ASSERT_NOT_NULL(ast); + + CEL_NULLABLE(cel_SelectExpr*) + expr = (CEL_NULLABLE(cel_SelectExpr*))cel_Arena_Malloc( + ast->arena, sizeof(cel_SelectExpr), cel_nullptr); + if (expr != cel_nullptr) { + memset(expr, 0, sizeof(*expr)); + expr->ast = ast; + expr->kind = cel_ExprKind_kSelect; + expr->range = cel_SourceRange(-1, -1); + } + return expr; +} + +extern "C" CEL_NULLABLE(cel_Expr*) + cel_SelectExpr_Operand(CEL_NONNULL(const cel_SelectExpr*) expr) { + CEL_ASSERT_NOT_NULL(expr); + + return _cel_Expr_Child(cel_Expr_UpCast(expr), expr->operand); +} + +extern "C" CEL_NULLABLE(cel_Expr*) + cel_SelectExpr_SetOperand(CEL_NONNULL(cel_SelectExpr*) expr, + CEL_NULLABLE(cel_Expr*) operand) { + CEL_ASSERT_NOT_NULL(expr); + + return _cel_Expr_SetChild(cel_Expr_UpCast(expr), &expr->operand, operand); +} + +extern "C" CEL_NULLABLE(cel_Expr*) + cel_SelectExpr_ReleaseOperand(CEL_NONNULL(cel_SelectExpr*) expr) { + CEL_ASSERT_NOT_NULL(expr); + + return _cel_Expr_ReleaseChild(cel_Expr_UpCast(expr), &expr->operand); +} + +extern "C" cel_StringView cel_SelectExpr_Field( + CEL_NONNULL(const cel_SelectExpr*) expr) { + CEL_ASSERT_NOT_NULL(expr); + + return expr->field; +} + +extern "C" void cel_SelectExpr_SetField(CEL_NONNULL(cel_SelectExpr*) expr, + cel_StringView field) { + CEL_ASSERT_NOT_NULL(expr); + + expr->field = field; +} + +extern "C" bool cel_SelectExpr_TestOnly(CEL_NONNULL(const cel_SelectExpr*) + expr) { + CEL_ASSERT_NOT_NULL(expr); + + return expr->test_only; +} + +extern "C" void cel_SelectExpr_SetTestOnly(CEL_NONNULL(cel_SelectExpr*) expr, + bool test_only) { + CEL_ASSERT_NOT_NULL(expr); + + expr->test_only = test_only; +} + +extern "C" CEL_NULLABLE(cel_CallArgExpr*) + cel_CallArgExpr_New(CEL_NONNULL(cel_Ast*) ast) { + CEL_ASSERT_NOT_NULL(ast); + + CEL_NULLABLE(cel_CallArgExpr*) + expr = (CEL_NULLABLE(cel_CallArgExpr*))cel_Arena_Malloc( + ast->arena, sizeof(cel_CallArgExpr), cel_nullptr); + if (expr != cel_nullptr) { + memset(expr, 0, sizeof(*expr)); + expr->ast = ast; + expr->kind = cel_ExprKind_kCallArg; + expr->range = cel_SourceRange(-1, -1); + expr->index = -1; + } + return expr; +} + +extern "C" CEL_NULLABLE(cel_CallExpr*) + cel_CallArgExpr_Parent(CEL_NONNULL(const cel_CallArgExpr*) expr) { + CEL_NULLABLE(cel_Expr*) parent = cel_Expr_Parent(cel_Expr_UpCast(expr)); + return parent != cel_nullptr ? cel_CallExpr_DownCast(parent) : cel_nullptr; +} + +extern "C" ptrdiff_t cel_CallArgExpr_Index(CEL_NONNULL(const cel_CallArgExpr*) + expr) { + CEL_ASSERT_NOT_NULL(expr); + + return expr->index; +} + +extern "C" CEL_NULLABLE(cel_Expr*) + cel_CallArgExpr_Value(CEL_NONNULL(const cel_CallArgExpr*) expr) { + CEL_ASSERT_NOT_NULL(expr); + + return _cel_Expr_Child(cel_Expr_UpCast(expr), expr->value); +} + +extern "C" CEL_NULLABLE(cel_Expr*) + cel_CallArgExpr_SetValue(CEL_NONNULL(cel_CallArgExpr*) expr, + CEL_NULLABLE(cel_Expr*) value) { + CEL_ASSERT_NOT_NULL(expr); + + return _cel_Expr_SetChild(cel_Expr_UpCast(expr), &expr->value, value); +} + +extern "C" CEL_NULLABLE(cel_Expr*) + cel_CallArgExpr_ReleaseValue(CEL_NONNULL(cel_CallArgExpr*) expr) { + CEL_ASSERT_NOT_NULL(expr); + + return _cel_Expr_ReleaseChild(cel_Expr_UpCast(expr), &expr->value); +} + +extern "C" CEL_NULLABLE(cel_CallExpr*) + cel_CallExpr_New(CEL_NONNULL(cel_Ast*) ast) { + CEL_ASSERT_NOT_NULL(ast); + + CEL_NULLABLE(cel_CallExpr*) + expr = (CEL_NULLABLE(cel_CallExpr*))cel_Arena_Malloc( + ast->arena, sizeof(cel_CallExpr), cel_nullptr); + if (expr != cel_nullptr) { + memset(expr, 0, sizeof(*expr)); + expr->ast = ast; + expr->kind = cel_ExprKind_kCall; + expr->range = cel_SourceRange(-1, -1); + } + return expr; +} + +extern "C" CEL_NULLABLE(cel_Expr*) + cel_CallExpr_Target(CEL_NONNULL(const cel_CallExpr*) expr) { + CEL_ASSERT_NOT_NULL(expr); + + return _cel_Expr_Child(cel_Expr_UpCast(expr), expr->target); +} + +extern "C" CEL_NULLABLE(cel_Expr*) + cel_CallExpr_SetTarget(CEL_NONNULL(cel_CallExpr*) expr, + CEL_NULLABLE(cel_Expr*) target) { + CEL_ASSERT_NOT_NULL(expr); + + return _cel_Expr_SetChild(cel_Expr_UpCast(expr), &expr->target, target); +} + +extern "C" CEL_NULLABLE(cel_Expr*) + cel_CallExpr_ReleaseTarget(CEL_NONNULL(cel_CallExpr*) expr) { + CEL_ASSERT_NOT_NULL(expr); + + return _cel_Expr_ReleaseChild(cel_Expr_UpCast(expr), &expr->target); +} + +extern "C" cel_StringView cel_CallExpr_Function(CEL_NONNULL(const cel_CallExpr*) + expr) { + CEL_ASSERT_NOT_NULL(expr); + + return expr->function; +} + +extern "C" void cel_CallExpr_SetFunction(CEL_NONNULL(cel_CallExpr*) expr, + cel_StringView function) { + CEL_ASSERT_NOT_NULL(expr); + + expr->function = function; +} + +extern "C" void cel_CallExpr_AppendArg(CEL_NONNULL(cel_CallExpr*) expr, + CEL_NONNULL(cel_CallArgExpr*) arg) { + CEL_ASSERT_NOT_NULL(expr); + + _cel_Expr_LinkChild(cel_Expr_UpCast(expr), &expr->args_head, &expr->args_tail, + &expr->args, cel_nullptr, (_cel_ExprLink*)arg); +} + +extern "C" void cel_CallExpr_PrependArg(CEL_NONNULL(cel_CallExpr*) expr, + CEL_NONNULL(cel_CallArgExpr*) arg) { + CEL_ASSERT_NOT_NULL(expr); + + _cel_Expr_LinkChild(cel_Expr_UpCast(expr), &expr->args_head, &expr->args_tail, + &expr->args, expr->args_head, (_cel_ExprLink*)arg); +} + +extern "C" void cel_CallExpr_InsertArg(CEL_NONNULL(cel_CallExpr*) expr, + CEL_NULLABLE(cel_CallArgExpr*) before, + CEL_NONNULL(cel_CallArgExpr*) arg) { + CEL_ASSERT_NOT_NULL(expr); + + _cel_Expr_LinkChild(cel_Expr_UpCast(expr), &expr->args_head, &expr->args_tail, + &expr->args, (_cel_ExprLink*)before, (_cel_ExprLink*)arg); +} + +extern "C" CEL_NONNULL(cel_CallArgExpr*) + cel_CallExpr_ReleaseArg(CEL_NONNULL(cel_CallExpr*) expr, + CEL_NONNULL(cel_CallArgExpr*) arg) { + CEL_ASSERT_NOT_NULL(expr); + + _cel_Expr_UnlinkChild(cel_Expr_UpCast(expr), &expr->args_head, + &expr->args_tail, &expr->args, (_cel_ExprLink*)arg); + return arg; +} + +extern "C" CEL_NONNULL(cel_CallArgExpr*) + cel_CallExpr_Arg(CEL_NONNULL(const cel_CallExpr*) expr, size_t index) { + CEL_ASSERT_NOT_NULL(expr); + CEL_ASSERT_LT(index, expr->args); + + CEL_NULLABLE(_cel_ExprLink*) arg = expr->args_head; + for (; index > 0; --index) { + CEL_ASSERT_NOT_NULL(arg); + arg = arg->right; + } + CEL_ASSERT_NOT_NULL(arg); + CEL_ASSERT(arg->parent == cel_Expr_UpCast(expr)); + return (cel_CallArgExpr*)arg; +} + +extern "C" size_t cel_CallExpr_Args(CEL_NONNULL(const cel_CallExpr*) expr, + CEL_NULLABLE(cel_CallArgExpr*) * + cel_nullable head, + CEL_NULLABLE(cel_CallArgExpr*) * + cel_nullable tail) { + CEL_ASSERT_NOT_NULL(expr); + + if (head != cel_nullptr) { + *head = (cel_CallArgExpr*)expr->args_head; + } + if (tail != cel_nullptr) { + *tail = (cel_CallArgExpr*)expr->args_tail; + } + return expr->args; +} + +extern "C" CEL_NULLABLE(cel_CallArgExpr*) + cel_CallExpr_PrevArg(CEL_NULLABLE(const cel_CallArgExpr*) arg) { + if (arg != cel_nullptr) { + arg = (cel_CallArgExpr*)arg->left; + } + return (cel_CallArgExpr*)arg; +} + +extern "C" CEL_NULLABLE(cel_CallArgExpr*) + cel_CallExpr_NextArg(CEL_NULLABLE(const cel_CallArgExpr*) arg) { + if (arg != cel_nullptr) { + arg = (cel_CallArgExpr*)arg->right; + } + return (cel_CallArgExpr*)arg; +} + +extern "C" CEL_NULLABLE(cel_UnaryExpr*) + cel_UnaryExpr_New(CEL_NONNULL(cel_Ast*) ast) { + CEL_ASSERT_NOT_NULL(ast); + + CEL_NULLABLE(cel_UnaryExpr*) + expr = (CEL_NULLABLE(cel_UnaryExpr*))cel_Arena_Malloc( + ast->arena, sizeof(cel_UnaryExpr), cel_nullptr); + if (expr != cel_nullptr) { + memset(expr, 0, sizeof(*expr)); + expr->ast = ast; + expr->kind = cel_ExprKind_kUnary; + expr->range = cel_SourceRange(-1, -1); + } + return expr; +} + +extern "C" cel_UnaryOp cel_UnaryExpr_Op(CEL_NONNULL(const cel_UnaryExpr*) + expr) { + CEL_ASSERT_NOT_NULL(expr); + + return expr->op; +} + +extern "C" void cel_UnaryExpr_SetOp(CEL_NONNULL(cel_UnaryExpr*) expr, + cel_UnaryOp op) { + CEL_ASSERT_NOT_NULL(expr); + + expr->op = op; +} + +extern "C" CEL_NULLABLE(cel_Expr*) + cel_UnaryExpr_Arg(CEL_NONNULL(const cel_UnaryExpr*) expr) { + CEL_ASSERT_NOT_NULL(expr); + + return _cel_Expr_Child(cel_Expr_UpCast(expr), expr->args[0]); +} + +extern "C" CEL_NULLABLE(cel_Expr*) + cel_UnaryExpr_SetArg(CEL_NONNULL(cel_UnaryExpr*) expr, + CEL_NULLABLE(cel_Expr*) arg) { + CEL_ASSERT_NOT_NULL(expr); + + return _cel_Expr_SetChild(cel_Expr_UpCast(expr), &expr->args[0], arg); +} + +extern "C" CEL_NULLABLE(cel_Expr*) + cel_UnaryExpr_ReleaseArg(CEL_NONNULL(cel_UnaryExpr*) expr) { + CEL_ASSERT_NOT_NULL(expr); + + return _cel_Expr_ReleaseChild(cel_Expr_UpCast(expr), &expr->args[0]); +} + +extern "C" CEL_NULLABLE(cel_BinaryExpr*) + cel_BinaryExpr_New(CEL_NONNULL(cel_Ast*) ast) { + CEL_ASSERT_NOT_NULL(ast); + + CEL_NULLABLE(cel_BinaryExpr*) + expr = (CEL_NULLABLE(cel_BinaryExpr*))cel_Arena_Malloc( + ast->arena, sizeof(cel_BinaryExpr), cel_nullptr); + if (expr != cel_nullptr) { + memset(expr, 0, sizeof(*expr)); + expr->ast = ast; + expr->kind = cel_ExprKind_kBinary; + expr->range = cel_SourceRange(-1, -1); + } + return expr; +} + +extern "C" cel_BinaryOp cel_BinaryExpr_Op(CEL_NONNULL(const cel_BinaryExpr*) + expr) { + CEL_ASSERT_NOT_NULL(expr); + + return expr->op; +} + +extern "C" void cel_BinaryExpr_SetOp(CEL_NONNULL(cel_BinaryExpr*) expr, + cel_BinaryOp op) { + CEL_ASSERT_NOT_NULL(expr); + + expr->op = op; +} + +extern "C" CEL_NULLABLE(cel_Expr*) + cel_BinaryExpr_Left(CEL_NONNULL(const cel_BinaryExpr*) expr) { + CEL_ASSERT_NOT_NULL(expr); + + return _cel_Expr_Child(cel_Expr_UpCast(expr), expr->args[0]); +} + +extern "C" CEL_NULLABLE(cel_Expr*) + cel_BinaryExpr_SetLeft(CEL_NONNULL(cel_BinaryExpr*) expr, + CEL_NULLABLE(cel_Expr*) left) { + CEL_ASSERT_NOT_NULL(expr); + + return _cel_Expr_SetChild(cel_Expr_UpCast(expr), &expr->args[0], left); +} + +extern "C" CEL_NULLABLE(cel_Expr*) + cel_BinaryExpr_ReleaseLeft(CEL_NONNULL(cel_BinaryExpr*) expr) { + CEL_ASSERT_NOT_NULL(expr); + + return _cel_Expr_ReleaseChild(cel_Expr_UpCast(expr), &expr->args[0]); +} + +extern "C" CEL_NULLABLE(cel_Expr*) + cel_BinaryExpr_Right(CEL_NONNULL(const cel_BinaryExpr*) expr) { + CEL_ASSERT_NOT_NULL(expr); + + return _cel_Expr_Child(cel_Expr_UpCast(expr), expr->args[1]); +} + +extern "C" CEL_NULLABLE(cel_Expr*) + cel_BinaryExpr_SetRight(CEL_NONNULL(cel_BinaryExpr*) expr, + CEL_NULLABLE(cel_Expr*) right) { + CEL_ASSERT_NOT_NULL(expr); + + return _cel_Expr_SetChild(cel_Expr_UpCast(expr), &expr->args[1], right); +} + +extern "C" CEL_NULLABLE(cel_Expr*) + cel_BinaryExpr_ReleaseRight(CEL_NONNULL(cel_BinaryExpr*) expr) { + CEL_ASSERT_NOT_NULL(expr); + + return _cel_Expr_ReleaseChild(cel_Expr_UpCast(expr), &expr->args[1]); +} + +extern "C" CEL_NULLABLE(cel_TernaryExpr*) + cel_TernaryExpr_New(CEL_NONNULL(cel_Ast*) ast) { + CEL_ASSERT_NOT_NULL(ast); + + CEL_NULLABLE(cel_TernaryExpr*) + expr = (CEL_NULLABLE(cel_TernaryExpr*))cel_Arena_Malloc( + ast->arena, sizeof(cel_TernaryExpr), cel_nullptr); + if (expr != cel_nullptr) { + memset(expr, 0, sizeof(*expr)); + expr->ast = ast; + expr->kind = cel_ExprKind_kTernary; + expr->range = cel_SourceRange(-1, -1); + } + return expr; +} + +extern "C" cel_TernaryOp cel_TernaryExpr_Op(CEL_NONNULL(const cel_TernaryExpr*) + expr) { + CEL_ASSERT_NOT_NULL(expr); + + return expr->op; +} + +extern "C" void cel_TernaryExpr_SetOp(CEL_NONNULL(cel_TernaryExpr*) expr, + cel_TernaryOp op) { + CEL_ASSERT_NOT_NULL(expr); + + expr->op = op; +} + +extern "C" CEL_NULLABLE(cel_Expr*) + cel_TernaryExpr_Condition(CEL_NONNULL(const cel_TernaryExpr*) expr) { + CEL_ASSERT_NOT_NULL(expr); + + return _cel_Expr_Child(cel_Expr_UpCast(expr), expr->args[0]); +} + +extern "C" CEL_NULLABLE(cel_Expr*) + cel_TernaryExpr_SetCondition(CEL_NONNULL(cel_TernaryExpr*) expr, + CEL_NULLABLE(cel_Expr*) condition) { + CEL_ASSERT_NOT_NULL(expr); + + return _cel_Expr_SetChild(cel_Expr_UpCast(expr), &expr->args[0], condition); +} + +extern "C" CEL_NULLABLE(cel_Expr*) + cel_TernaryExpr_ReleaseCondition(CEL_NONNULL(cel_TernaryExpr*) expr) { + CEL_ASSERT_NOT_NULL(expr); + + return _cel_Expr_ReleaseChild(cel_Expr_UpCast(expr), &expr->args[0]); +} + +extern "C" CEL_NULLABLE(cel_Expr*) + cel_TernaryExpr_IfTrue(CEL_NONNULL(const cel_TernaryExpr*) expr) { + CEL_ASSERT_NOT_NULL(expr); + + return _cel_Expr_Child(cel_Expr_UpCast(expr), expr->args[1]); +} + +extern "C" CEL_NULLABLE(cel_Expr*) + cel_TernaryExpr_SetIfTrue(CEL_NONNULL(cel_TernaryExpr*) expr, + CEL_NULLABLE(cel_Expr*) if_true) { + CEL_ASSERT_NOT_NULL(expr); + + return _cel_Expr_SetChild(cel_Expr_UpCast(expr), &expr->args[1], if_true); +} + +extern "C" CEL_NULLABLE(cel_Expr*) + cel_TernaryExpr_ReleaseIfTrue(CEL_NONNULL(cel_TernaryExpr*) expr) { + CEL_ASSERT_NOT_NULL(expr); + + return _cel_Expr_ReleaseChild(cel_Expr_UpCast(expr), &expr->args[1]); +} + +extern "C" CEL_NULLABLE(cel_Expr*) + cel_TernaryExpr_IfFalse(CEL_NONNULL(const cel_TernaryExpr*) expr) { + CEL_ASSERT_NOT_NULL(expr); + + return _cel_Expr_Child(cel_Expr_UpCast(expr), expr->args[2]); +} + +extern "C" CEL_NULLABLE(cel_Expr*) + cel_TernaryExpr_SetIfFalse(CEL_NONNULL(cel_TernaryExpr*) expr, + CEL_NULLABLE(cel_Expr*) if_false) { + CEL_ASSERT_NOT_NULL(expr); + + return _cel_Expr_SetChild(cel_Expr_UpCast(expr), &expr->args[2], if_false); +} + +extern "C" CEL_NULLABLE(cel_Expr*) + cel_TernaryExpr_ReleaseIfFalse(CEL_NONNULL(cel_TernaryExpr*) expr) { + CEL_ASSERT_NOT_NULL(expr); + + return _cel_Expr_ReleaseChild(cel_Expr_UpCast(expr), &expr->args[2]); +} + +extern "C" CEL_NULLABLE(cel_ListElementExpr*) + cel_ListElementExpr_New(CEL_NONNULL(cel_Ast*) ast) { + CEL_ASSERT_NOT_NULL(ast); + + CEL_NULLABLE(cel_ListElementExpr*) + expr = (CEL_NULLABLE(cel_ListElementExpr*))cel_Arena_Malloc( + ast->arena, sizeof(cel_ListElementExpr), cel_nullptr); + if (expr != cel_nullptr) { + memset(expr, 0, sizeof(*expr)); + expr->ast = ast; + expr->kind = cel_ExprKind_kListElement; + expr->range = cel_SourceRange(-1, -1); + expr->index = -1; + } + return expr; +} + +extern "C" CEL_NULLABLE(cel_ListExpr*) + cel_ListElementExpr_Parent(CEL_NONNULL(const cel_ListElementExpr*) expr) { + CEL_NULLABLE(cel_Expr*) parent = cel_Expr_Parent(cel_Expr_UpCast(expr)); + return parent != cel_nullptr ? cel_ListExpr_DownCast(parent) : cel_nullptr; +} + +extern "C" ptrdiff_t cel_ListElementExpr_Index( + CEL_NONNULL(const cel_ListElementExpr*) expr) { + CEL_ASSERT_NOT_NULL(expr); + + return expr->index; +} + +extern "C" CEL_NULLABLE(cel_Expr*) + cel_ListElementExpr_Value(CEL_NONNULL(const cel_ListElementExpr*) expr) { + CEL_ASSERT_NOT_NULL(expr); + + return _cel_Expr_Child(cel_Expr_UpCast(expr), expr->value); +} + +extern "C" CEL_NULLABLE(cel_Expr*) + cel_ListElementExpr_SetValue(CEL_NONNULL(cel_ListElementExpr*) expr, + CEL_NULLABLE(cel_Expr*) value) { + CEL_ASSERT_NOT_NULL(expr); + + return _cel_Expr_SetChild(cel_Expr_UpCast(expr), &expr->value, value); +} + +extern "C" CEL_NULLABLE(cel_Expr*) + cel_ListElementExpr_ReleaseValue(CEL_NONNULL(cel_ListElementExpr*) expr) { + CEL_ASSERT_NOT_NULL(expr); + + return _cel_Expr_ReleaseChild(cel_Expr_UpCast(expr), &expr->value); +} + +extern "C" bool cel_ListElementExpr_Optional( + CEL_NONNULL(const cel_ListElementExpr*) expr) { + CEL_ASSERT_NOT_NULL(expr); + + return expr->optional; +} + +extern "C" void cel_ListElementExpr_SetOptional( + CEL_NONNULL(cel_ListElementExpr*) expr, bool optional) { + CEL_ASSERT_NOT_NULL(expr); + + expr->optional = optional; +} + +extern "C" CEL_NULLABLE(cel_ListExpr*) + cel_ListExpr_New(CEL_NONNULL(cel_Ast*) ast) { + CEL_ASSERT_NOT_NULL(ast); + + CEL_NULLABLE(cel_ListExpr*) + expr = (CEL_NULLABLE(cel_ListExpr*))cel_Arena_Malloc( + ast->arena, sizeof(cel_ListExpr), cel_nullptr); + if (expr != cel_nullptr) { + memset(expr, 0, sizeof(*expr)); + expr->ast = ast; + expr->kind = cel_ExprKind_kList; + expr->range = cel_SourceRange(-1, -1); + } + return expr; +} + +extern "C" void cel_ListExpr_AppendElement(CEL_NONNULL(cel_ListExpr*) expr, + CEL_NONNULL(cel_ListElementExpr*) + element) { + CEL_ASSERT_NOT_NULL(expr); + + _cel_Expr_LinkChild(cel_Expr_UpCast(expr), &expr->elems_head, + &expr->elems_tail, &expr->elems, cel_nullptr, + (_cel_ExprLink*)element); +} + +extern "C" void cel_ListExpr_PrependElement(CEL_NONNULL(cel_ListExpr*) expr, + CEL_NONNULL(cel_ListElementExpr*) + element) { + CEL_ASSERT_NOT_NULL(expr); + + _cel_Expr_LinkChild(cel_Expr_UpCast(expr), &expr->elems_head, + &expr->elems_tail, &expr->elems, expr->elems_head, + (_cel_ExprLink*)element); +} + +extern "C" void cel_ListExpr_InsertElement(CEL_NONNULL(cel_ListExpr*) expr, + CEL_NULLABLE(cel_ListElementExpr*) + before, + CEL_NONNULL(cel_ListElementExpr*) + element) { + CEL_ASSERT_NOT_NULL(expr); + + _cel_Expr_LinkChild(cel_Expr_UpCast(expr), &expr->elems_head, + &expr->elems_tail, &expr->elems, (_cel_ExprLink*)before, + (_cel_ExprLink*)element); +} + +extern "C" CEL_NONNULL(cel_ListElementExpr*) + cel_ListExpr_ReleaseElement(CEL_NONNULL(cel_ListExpr*) expr, + CEL_NONNULL(cel_ListElementExpr*) element) { + CEL_ASSERT_NOT_NULL(expr); + + _cel_Expr_UnlinkChild(cel_Expr_UpCast(expr), &expr->elems_head, + &expr->elems_tail, &expr->elems, + (_cel_ExprLink*)element); + return element; +} + +extern "C" CEL_NONNULL(cel_ListElementExpr*) + cel_ListExpr_Element(CEL_NONNULL(const cel_ListExpr*) expr, size_t index) { + CEL_ASSERT_NOT_NULL(expr); + CEL_ASSERT_LT(index, expr->elems); + + CEL_NULLABLE(_cel_ExprLink*) element = expr->elems_head; + for (; index > 0; --index) { + CEL_ASSERT_NOT_NULL(element); + element = element->right; + } + CEL_ASSERT_NOT_NULL(element); + CEL_ASSERT(element->parent == cel_Expr_UpCast(expr)); + return (cel_ListElementExpr*)element; +} + +extern "C" size_t cel_ListExpr_Elements(CEL_NONNULL(const cel_ListExpr*) expr, + CEL_NULLABLE(cel_ListElementExpr*) * + cel_nullable head, + CEL_NULLABLE(cel_ListElementExpr*) * + cel_nullable tail) { + CEL_ASSERT_NOT_NULL(expr); + + if (head != cel_nullptr) { + *head = (cel_ListElementExpr*)expr->elems_head; + } + if (tail != cel_nullptr) { + *tail = (cel_ListElementExpr*)expr->elems_tail; + } + return expr->elems; +} + +extern "C" CEL_NULLABLE(cel_ListElementExpr*) + cel_ListExpr_PrevElement(CEL_NULLABLE(const cel_ListElementExpr*) element) { + if (element != cel_nullptr) { + element = (cel_ListElementExpr*)element->left; + } + return (CEL_NULLABLE(cel_ListElementExpr*))element; +} + +extern "C" CEL_NULLABLE(cel_ListElementExpr*) + cel_ListExpr_NextElement(CEL_NULLABLE(const cel_ListElementExpr*) element) { + if (element != cel_nullptr) { + element = (cel_ListElementExpr*)element->right; + } + return (CEL_NULLABLE(cel_ListElementExpr*))element; +} + +extern "C" CEL_NULLABLE(cel_MapEntryExpr*) + cel_MapEntryExpr_New(CEL_NONNULL(cel_Ast*) ast) { + CEL_ASSERT_NOT_NULL(ast); + + CEL_NULLABLE(cel_MapEntryExpr*) + expr = (CEL_NULLABLE(cel_MapEntryExpr*))cel_Arena_Malloc( + ast->arena, sizeof(cel_MapEntryExpr), cel_nullptr); + if (expr != cel_nullptr) { + memset(expr, 0, sizeof(*expr)); + expr->ast = ast; + expr->kind = cel_ExprKind_kMapEntry; + expr->range = cel_SourceRange(-1, -1); + expr->index = -1; + } + return expr; +} + +extern "C" CEL_NULLABLE(cel_MapExpr*) + cel_MapEntryExpr_Parent(CEL_NONNULL(const cel_MapEntryExpr*) expr) { + CEL_NULLABLE(cel_Expr*) parent = cel_Expr_Parent(cel_Expr_UpCast(expr)); + return parent != cel_nullptr ? cel_MapExpr_DownCast(parent) : cel_nullptr; +} + +extern "C" ptrdiff_t cel_MapEntryExpr_Index(CEL_NONNULL(const cel_MapEntryExpr*) + expr) { + CEL_ASSERT_NOT_NULL(expr); + + return expr->index; +} + +extern "C" CEL_NULLABLE(cel_Expr*) + cel_MapEntryExpr_Key(CEL_NONNULL(const cel_MapEntryExpr*) expr) { + CEL_ASSERT_NOT_NULL(expr); + + return _cel_Expr_Child(cel_Expr_UpCast(expr), expr->key); +} + +extern "C" CEL_NULLABLE(cel_Expr*) + cel_MapEntryExpr_SetKey(CEL_NONNULL(cel_MapEntryExpr*) expr, + CEL_NULLABLE(cel_Expr*) key) { + CEL_ASSERT_NOT_NULL(expr); + + return _cel_Expr_SetChild(cel_Expr_UpCast(expr), &expr->key, key); +} + +extern "C" CEL_NULLABLE(cel_Expr*) + cel_MapEntryExpr_ReleaseKey(CEL_NONNULL(cel_MapEntryExpr*) expr) { + CEL_ASSERT_NOT_NULL(expr); + + return _cel_Expr_ReleaseChild(cel_Expr_UpCast(expr), &expr->key); +} + +extern "C" CEL_NULLABLE(cel_Expr*) + cel_MapEntryExpr_Value(CEL_NONNULL(const cel_MapEntryExpr*) expr) { + CEL_ASSERT_NOT_NULL(expr); + + return _cel_Expr_Child(cel_Expr_UpCast(expr), expr->value); +} + +extern "C" CEL_NULLABLE(cel_Expr*) + cel_MapEntryExpr_SetValue(CEL_NONNULL(cel_MapEntryExpr*) expr, + CEL_NULLABLE(cel_Expr*) value) { + CEL_ASSERT_NOT_NULL(expr); + + return _cel_Expr_SetChild(cel_Expr_UpCast(expr), &expr->value, value); +} + +extern "C" CEL_NULLABLE(cel_Expr*) + cel_MapEntryExpr_ReleaseValue(CEL_NONNULL(cel_MapEntryExpr*) expr) { + CEL_ASSERT_NOT_NULL(expr); + + return _cel_Expr_ReleaseChild(cel_Expr_UpCast(expr), &expr->value); +} + +extern "C" bool cel_MapEntryExpr_Optional(CEL_NONNULL(const cel_MapEntryExpr*) + expr) { + CEL_ASSERT_NOT_NULL(expr); + + return expr->optional; +} + +extern "C" void cel_MapEntryExpr_SetOptional(CEL_NONNULL(cel_MapEntryExpr*) + expr, + bool optional) { + CEL_ASSERT_NOT_NULL(expr); + + expr->optional = optional; +} + +extern "C" CEL_NULLABLE(cel_MapExpr*) + cel_MapExpr_New(CEL_NONNULL(cel_Ast*) ast) { + CEL_ASSERT_NOT_NULL(ast); + + CEL_NULLABLE(cel_MapExpr*) + expr = (CEL_NULLABLE(cel_MapExpr*))cel_Arena_Malloc( + ast->arena, sizeof(cel_MapExpr), cel_nullptr); + if (expr != cel_nullptr) { + memset(expr, 0, sizeof(*expr)); + expr->ast = ast; + expr->kind = cel_ExprKind_kMap; + expr->range = cel_SourceRange(-1, -1); + } + return expr; +} + +extern "C" void cel_MapExpr_AppendEntry(CEL_NONNULL(cel_MapExpr*) expr, + CEL_NONNULL(cel_MapEntryExpr*) entry) { + CEL_ASSERT_NOT_NULL(expr); + + _cel_Expr_LinkChild(cel_Expr_UpCast(expr), &expr->ents_head, &expr->ents_tail, + &expr->ents, cel_nullptr, (_cel_ExprLink*)entry); +} + +extern "C" void cel_MapExpr_PrependEntry(CEL_NONNULL(cel_MapExpr*) expr, + CEL_NONNULL(cel_MapEntryExpr*) entry) { + CEL_ASSERT_NOT_NULL(expr); + + _cel_Expr_LinkChild(cel_Expr_UpCast(expr), &expr->ents_head, &expr->ents_tail, + &expr->ents, expr->ents_head, (_cel_ExprLink*)entry); +} + +extern "C" void cel_MapExpr_InsertEntry(CEL_NONNULL(cel_MapExpr*) expr, + CEL_NULLABLE(cel_MapEntryExpr*) before, + CEL_NONNULL(cel_MapEntryExpr*) entry) { + CEL_ASSERT_NOT_NULL(expr); + + _cel_Expr_LinkChild(cel_Expr_UpCast(expr), &expr->ents_head, &expr->ents_tail, + &expr->ents, (_cel_ExprLink*)before, + (_cel_ExprLink*)entry); +} + +extern "C" CEL_NONNULL(cel_MapEntryExpr*) + cel_MapExpr_ReleaseEntry(CEL_NONNULL(cel_MapExpr*) expr, + CEL_NONNULL(cel_MapEntryExpr*) entry) { + CEL_ASSERT_NOT_NULL(expr); + + _cel_Expr_UnlinkChild(cel_Expr_UpCast(expr), &expr->ents_head, + &expr->ents_tail, &expr->ents, (_cel_ExprLink*)entry); + return entry; +} + +extern "C" CEL_NONNULL(cel_MapEntryExpr*) + cel_MapExpr_Entry(CEL_NONNULL(const cel_MapExpr*) expr, size_t index) { + CEL_ASSERT_NOT_NULL(expr); + CEL_ASSERT_LT(index, expr->ents); + + CEL_NULLABLE(_cel_ExprLink*) element = expr->ents_head; + for (; index > 0; --index) { + CEL_ASSERT_NOT_NULL(element); + element = element->right; + } + CEL_ASSERT_NOT_NULL(element); + CEL_ASSERT(element->parent == cel_Expr_UpCast(expr)); + return (cel_MapEntryExpr*)element; +} + +extern "C" size_t cel_MapExpr_Entries(CEL_NONNULL(const cel_MapExpr*) expr, + CEL_NULLABLE(cel_MapEntryExpr*) * + cel_nullable head, + CEL_NULLABLE(cel_MapEntryExpr*) * + cel_nullable tail) { + CEL_ASSERT_NOT_NULL(expr); + + if (head != cel_nullptr) { + *head = (cel_MapEntryExpr*)expr->ents_head; + } + if (tail != cel_nullptr) { + *tail = (cel_MapEntryExpr*)expr->ents_tail; + } + return expr->ents; +} + +extern "C" CEL_NULLABLE(cel_MapEntryExpr*) + cel_MapExpr_PrevEntry(CEL_NULLABLE(const cel_MapEntryExpr*) entry) { + if (entry != cel_nullptr) { + entry = (cel_MapEntryExpr*)entry->left; + } + return (CEL_NULLABLE(cel_MapEntryExpr*))entry; +} + +extern "C" CEL_NULLABLE(cel_MapEntryExpr*) + cel_MapExpr_NextEntry(CEL_NULLABLE(const cel_MapEntryExpr*) entry) { + if (entry != cel_nullptr) { + entry = (cel_MapEntryExpr*)entry->right; + } + return (CEL_NULLABLE(cel_MapEntryExpr*))entry; +} + +extern "C" CEL_NULLABLE(cel_StructFieldExpr*) + cel_StructFieldExpr_New(CEL_NONNULL(cel_Ast*) ast) { + CEL_ASSERT_NOT_NULL(ast); + + CEL_NULLABLE(cel_StructFieldExpr*) + expr = (CEL_NULLABLE(cel_StructFieldExpr*))cel_Arena_Malloc( + ast->arena, sizeof(cel_StructFieldExpr), cel_nullptr); + if (expr != cel_nullptr) { + memset(expr, 0, sizeof(*expr)); + expr->ast = ast; + expr->kind = cel_ExprKind_kStructField; + expr->range = cel_SourceRange(-1, -1); + expr->index = -1; + } + return expr; +} + +extern "C" CEL_NULLABLE(cel_StructExpr*) + cel_StructFieldExpr_Parent(CEL_NONNULL(const cel_StructFieldExpr*) expr) { + CEL_NULLABLE(cel_Expr*) parent = cel_Expr_Parent(cel_Expr_UpCast(expr)); + return parent != cel_nullptr ? cel_StructExpr_DownCast(parent) : cel_nullptr; +} + +extern "C" ptrdiff_t cel_StructFieldExpr_Index( + CEL_NONNULL(const cel_StructFieldExpr*) expr) { + CEL_ASSERT_NOT_NULL(expr); + + return expr->index; +} + +extern "C" cel_StringView cel_StructFieldExpr_Name( + CEL_NONNULL(const cel_StructFieldExpr*) expr) { + CEL_ASSERT_NOT_NULL(expr); + + return expr->name; +} + +extern "C" void cel_StructFieldExpr_SetName(CEL_NONNULL(cel_StructFieldExpr*) + expr, + cel_StringView name) { + CEL_ASSERT_NOT_NULL(expr); + + expr->name = name; +} + +extern "C" CEL_NULLABLE(cel_Expr*) + cel_StructFieldExpr_Value(CEL_NONNULL(const cel_StructFieldExpr*) expr) { + CEL_ASSERT_NOT_NULL(expr); + + return _cel_Expr_Child(cel_Expr_UpCast(expr), expr->value); +} + +extern "C" CEL_NULLABLE(cel_Expr*) + cel_StructFieldExpr_SetValue(CEL_NONNULL(cel_StructFieldExpr*) expr, + CEL_NULLABLE(cel_Expr*) value) { + CEL_ASSERT_NOT_NULL(expr); + + return _cel_Expr_SetChild(cel_Expr_UpCast(expr), &expr->value, value); +} + +extern "C" CEL_NULLABLE(cel_Expr*) + cel_StructFieldExpr_ReleaseValue(CEL_NONNULL(cel_StructFieldExpr*) expr) { + CEL_ASSERT_NOT_NULL(expr); + + return _cel_Expr_ReleaseChild(cel_Expr_UpCast(expr), &expr->value); +} + +extern "C" bool cel_StructFieldExpr_Optional( + CEL_NONNULL(const cel_StructFieldExpr*) expr) { + CEL_ASSERT_NOT_NULL(expr); + + return expr->optional; +} + +extern "C" void cel_StructFieldExpr_SetOptional( + CEL_NONNULL(cel_StructFieldExpr*) expr, bool optional) { + CEL_ASSERT_NOT_NULL(expr); + + expr->optional = optional; +} + +extern "C" CEL_NULLABLE(cel_StructExpr*) + cel_StructExpr_New(CEL_NONNULL(cel_Ast*) ast) { + CEL_ASSERT_NOT_NULL(ast); + + CEL_NULLABLE(cel_StructExpr*) + expr = (CEL_NULLABLE(cel_StructExpr*))cel_Arena_Malloc( + ast->arena, sizeof(cel_StructExpr), cel_nullptr); + if (expr != cel_nullptr) { + memset(expr, 0, sizeof(*expr)); + expr->ast = ast; + expr->kind = cel_ExprKind_kStruct; + expr->range = cel_SourceRange(-1, -1); + } + return expr; +} + +extern "C" cel_StringView cel_StructExpr_Name(CEL_NONNULL(const cel_StructExpr*) + expr) { + CEL_ASSERT_NOT_NULL(expr); + + return expr->name; +} + +extern "C" void cel_StructExpr_SetName(CEL_NONNULL(cel_StructExpr*) expr, + cel_StringView name) { + CEL_ASSERT_NOT_NULL(expr); + + expr->name = name; +} + +extern "C" void cel_StructExpr_AppendField(CEL_NONNULL(cel_StructExpr*) expr, + CEL_NONNULL(cel_StructFieldExpr*) + field) { + CEL_ASSERT_NOT_NULL(expr); + + _cel_Expr_LinkChild(cel_Expr_UpCast(expr), &expr->flds_head, &expr->flds_tail, + &expr->flds, cel_nullptr, (_cel_ExprLink*)field); +} + +extern "C" void cel_StructExpr_PrependField(CEL_NONNULL(cel_StructExpr*) expr, + CEL_NONNULL(cel_StructFieldExpr*) + field) { + CEL_ASSERT_NOT_NULL(expr); + + _cel_Expr_LinkChild(cel_Expr_UpCast(expr), &expr->flds_head, &expr->flds_tail, + &expr->flds, expr->flds_head, (_cel_ExprLink*)field); +} + +extern "C" void cel_StructExpr_InsertField(CEL_NONNULL(cel_StructExpr*) expr, + CEL_NULLABLE(cel_StructFieldExpr*) + before, + CEL_NONNULL(cel_StructFieldExpr*) + field) { + CEL_ASSERT_NOT_NULL(expr); + + _cel_Expr_LinkChild(cel_Expr_UpCast(expr), &expr->flds_head, &expr->flds_tail, + &expr->flds, (_cel_ExprLink*)before, + (_cel_ExprLink*)field); +} + +extern "C" CEL_NONNULL(cel_StructFieldExpr*) + cel_StructExpr_ReleaseField(CEL_NONNULL(cel_StructExpr*) expr, + CEL_NONNULL(cel_StructFieldExpr*) field) { + CEL_ASSERT_NOT_NULL(expr); + + _cel_Expr_UnlinkChild(cel_Expr_UpCast(expr), &expr->flds_head, + &expr->flds_tail, &expr->flds, (_cel_ExprLink*)field); + return field; +} + +extern "C" CEL_NONNULL(cel_StructFieldExpr*) + cel_StructExpr_Field(CEL_NONNULL(const cel_StructExpr*) expr, + size_t index) { + CEL_ASSERT_NOT_NULL(expr); + CEL_ASSERT_LT(index, expr->flds); + + CEL_NULLABLE(_cel_ExprLink*) element = expr->flds_head; + for (; index > 0; --index) { + CEL_ASSERT_NOT_NULL(element); + element = element->right; + } + CEL_ASSERT_NOT_NULL(element); + CEL_ASSERT(element->parent == cel_Expr_UpCast(expr)); + return (cel_StructFieldExpr*)element; +} + +extern "C" size_t cel_StructExpr_Fields(CEL_NONNULL(const cel_StructExpr*) expr, + CEL_NULLABLE(cel_StructFieldExpr*) * + cel_nullable head, + CEL_NULLABLE(cel_StructFieldExpr*) * + cel_nullable tail) { + CEL_ASSERT_NOT_NULL(expr); + + if (head != cel_nullptr) { + *head = (cel_StructFieldExpr*)expr->flds_head; + } + if (tail != cel_nullptr) { + *tail = (cel_StructFieldExpr*)expr->flds_tail; + } + return expr->flds; +} + +extern "C" CEL_NULLABLE(cel_StructFieldExpr*) + cel_StructExpr_PrevField(CEL_NULLABLE(const cel_StructFieldExpr*) field) { + if (field != cel_nullptr) { + field = (cel_StructFieldExpr*)field->left; + } + return (CEL_NULLABLE(cel_StructFieldExpr*))field; +} + +extern "C" CEL_NULLABLE(cel_StructFieldExpr*) + cel_StructExpr_NextField(CEL_NULLABLE(const cel_StructFieldExpr*) field) { + if (field != cel_nullptr) { + field = (cel_StructFieldExpr*)field->right; + } + return (CEL_NULLABLE(cel_StructFieldExpr*))field; +} + +extern "C" CEL_NULLABLE(cel_ComprehensionExpr*) + cel_ComprehensionExpr_New(CEL_NONNULL(cel_Ast*) ast) { + CEL_ASSERT_NOT_NULL(ast); + + CEL_NULLABLE(cel_ComprehensionExpr*) + expr = (CEL_NULLABLE(cel_ComprehensionExpr*))cel_Arena_Malloc( + ast->arena, sizeof(cel_ComprehensionExpr), cel_nullptr); + if (expr != cel_nullptr) { + memset(expr, 0, sizeof(*expr)); + expr->ast = ast; + expr->kind = cel_ExprKind_kComprehension; + expr->range = cel_SourceRange(-1, -1); + } + return expr; +} + +extern "C" cel_StringView cel_ComprehensionExpr_IterVar( + CEL_NONNULL(const cel_ComprehensionExpr*) expr) { + CEL_ASSERT_NOT_NULL(expr); + + return expr->iter_var; +} + +extern "C" void cel_ComprehensionExpr_SetIterVar( + CEL_NONNULL(cel_ComprehensionExpr*) expr, cel_StringView iter_var) { + CEL_ASSERT_NOT_NULL(expr); + + expr->iter_var = iter_var; +} + +extern "C" cel_StringView cel_ComprehensionExpr_IterVar2( + CEL_NONNULL(const cel_ComprehensionExpr*) expr) { + CEL_ASSERT_NOT_NULL(expr); + + return expr->iter_var2; +} + +extern "C" void cel_ComprehensionExpr_SetIterVar2( + CEL_NONNULL(cel_ComprehensionExpr*) expr, cel_StringView iter_var2) { + CEL_ASSERT_NOT_NULL(expr); + + expr->iter_var2 = iter_var2; +} + +extern "C" CEL_NULLABLE(cel_Expr*) + cel_ComprehensionExpr_IterRange(CEL_NONNULL(const cel_ComprehensionExpr*) + expr) { + CEL_ASSERT_NOT_NULL(expr); + + return _cel_Expr_Child(cel_Expr_UpCast(expr), expr->iter_range); +} + +extern "C" CEL_NULLABLE(cel_Expr*) + cel_ComprehensionExpr_SetIterRange(CEL_NONNULL(cel_ComprehensionExpr*) expr, + CEL_NULLABLE(cel_Expr*) iter_range) { + CEL_ASSERT_NOT_NULL(expr); + + return _cel_Expr_SetChild(cel_Expr_UpCast(expr), &expr->iter_range, + iter_range); +} + +extern "C" CEL_NULLABLE(cel_Expr*) + cel_ComprehensionExpr_ReleaseIterRange(CEL_NONNULL(cel_ComprehensionExpr*) + expr) { + CEL_ASSERT_NOT_NULL(expr); + + return _cel_Expr_ReleaseChild(cel_Expr_UpCast(expr), &expr->iter_range); +} + +extern "C" cel_StringView cel_ComprehensionExpr_AccuVar( + CEL_NONNULL(const cel_ComprehensionExpr*) expr) { + CEL_ASSERT_NOT_NULL(expr); + + return expr->accu_var; +} + +extern "C" void cel_ComprehensionExpr_SetAccuVar( + CEL_NONNULL(cel_ComprehensionExpr*) expr, cel_StringView accu_var) { + CEL_ASSERT_NOT_NULL(expr); + + expr->accu_var = accu_var; +} + +extern "C" CEL_NULLABLE(cel_Expr*) + cel_ComprehensionExpr_AccuInit(CEL_NONNULL(const cel_ComprehensionExpr*) + expr) { + CEL_ASSERT_NOT_NULL(expr); + + return _cel_Expr_Child(cel_Expr_UpCast(expr), expr->accu_init); +} + +extern "C" CEL_NULLABLE(cel_Expr*) + cel_ComprehensionExpr_SetAccuInit(CEL_NONNULL(cel_ComprehensionExpr*) expr, + CEL_NULLABLE(cel_Expr*) accu_init) { + CEL_ASSERT_NOT_NULL(expr); + + return _cel_Expr_SetChild(cel_Expr_UpCast(expr), &expr->accu_init, accu_init); +} + +extern "C" CEL_NULLABLE(cel_Expr*) + cel_ComprehensionExpr_ReleaseAccuInit(CEL_NONNULL(cel_ComprehensionExpr*) + expr) { + CEL_ASSERT_NOT_NULL(expr); + + return _cel_Expr_ReleaseChild(cel_Expr_UpCast(expr), &expr->accu_init); +} + +extern "C" CEL_NULLABLE(cel_Expr*) cel_ComprehensionExpr_LoopCondition( + CEL_NONNULL(const cel_ComprehensionExpr*) expr) { + CEL_ASSERT_NOT_NULL(expr); + + return _cel_Expr_Child(cel_Expr_UpCast(expr), expr->loop_condition); +} + +extern "C" CEL_NULLABLE(cel_Expr*) + cel_ComprehensionExpr_SetLoopCondition(CEL_NONNULL(cel_ComprehensionExpr*) + expr, + CEL_NULLABLE(cel_Expr*) + loop_condition) { + CEL_ASSERT_NOT_NULL(expr); + + return _cel_Expr_SetChild(cel_Expr_UpCast(expr), &expr->loop_condition, + loop_condition); +} + +extern "C" CEL_NULLABLE(cel_Expr*) cel_ComprehensionExpr_ReleaseLoopCondition( + CEL_NONNULL(cel_ComprehensionExpr*) expr) { + CEL_ASSERT_NOT_NULL(expr); + + return _cel_Expr_ReleaseChild(cel_Expr_UpCast(expr), &expr->loop_condition); +} + +extern "C" CEL_NULLABLE(cel_Expr*) + cel_ComprehensionExpr_LoopStep(CEL_NONNULL(const cel_ComprehensionExpr*) + expr) { + CEL_ASSERT_NOT_NULL(expr); + + return _cel_Expr_Child(cel_Expr_UpCast(expr), expr->loop_step); +} + +extern "C" CEL_NULLABLE(cel_Expr*) + cel_ComprehensionExpr_SetLoopStep(CEL_NONNULL(cel_ComprehensionExpr*) expr, + CEL_NULLABLE(cel_Expr*) loop_step) { + CEL_ASSERT_NOT_NULL(expr); + + return _cel_Expr_SetChild(cel_Expr_UpCast(expr), &expr->loop_step, loop_step); +} + +extern "C" CEL_NULLABLE(cel_Expr*) + cel_ComprehensionExpr_ReleaseLoopStep(CEL_NONNULL(cel_ComprehensionExpr*) + expr) { + CEL_ASSERT_NOT_NULL(expr); + + return _cel_Expr_ReleaseChild(cel_Expr_UpCast(expr), &expr->loop_step); +} + +extern "C" CEL_NULLABLE(cel_Expr*) + cel_ComprehensionExpr_Result(CEL_NONNULL(const cel_ComprehensionExpr*) + expr) { + CEL_ASSERT_NOT_NULL(expr); + + return _cel_Expr_Child(cel_Expr_UpCast(expr), expr->result); +} + +extern "C" CEL_NULLABLE(cel_Expr*) + cel_ComprehensionExpr_SetResult(CEL_NONNULL(cel_ComprehensionExpr*) expr, + CEL_NULLABLE(cel_Expr*) result) { + CEL_ASSERT_NOT_NULL(expr); + + return _cel_Expr_SetChild(cel_Expr_UpCast(expr), &expr->result, result); +} + +extern "C" CEL_NULLABLE(cel_Expr*) + cel_ComprehensionExpr_ReleaseResult(CEL_NONNULL(cel_ComprehensionExpr*) + expr) { + CEL_ASSERT_NOT_NULL(expr); + + return _cel_Expr_ReleaseChild(cel_Expr_UpCast(expr), &expr->result); +} diff --git a/cel-c/ast_proto.cc b/cel-c/ast_proto.cc new file mode 100644 index 0000000..87eab4f --- /dev/null +++ b/cel-c/ast_proto.cc @@ -0,0 +1,667 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "cel-c/ast_proto.h" + +#include +#include +#include +#include + +#include "cel/expr/checked.upb.h" +#include "cel/expr/syntax.upb.h" +#include "cel-c/arena.h" +#include "cel-c/assert.h" +#include "cel-c/ast.h" +#include "cel-c/config.h" +#include "cel-c/constant_proto.h" +#include "cel-c/operators.h" +#include "cel-c/ref.h" +#include "cel-c/ref_proto.h" +#include "cel-c/src/malloc.h" +#include "cel-c/src/setjmp.h" +#include "cel-c/status.h" +#include "cel-c/string_view.h" +#include "cel-c/type.h" +#include "cel-c/type_proto.h" + +typedef struct { + CEL_NONNULL(cel_Ast*) ast; + CEL_NONNULL(cel_Arena*) arena; + CEL_NONNULL(cel_Status*) status; + CEL_NONNULL(const cel_expr_CheckedExpr*) checked_expr; + CEL_NULLABLE(const cel_expr_SourceInfo*) source_info; + _cel_jmp_buf jmp; +} _cel_AstFromProtoState; + +static int64_t _cel_AstFromProto_CheckId(CEL_NONNULL(_cel_AstFromProtoState*) + state, + cel_ExprId id) { + if (CEL_UNLIKELY(id < 0)) { + cel_InvalidArgumentStatusF(state->status, + "cel: expected google.api.expr.Expr.id to be " + "greater than or equal to 0: %" PRId64, + id); + _cel_longjmp(state->jmp); + } + return id; +} + +static cel_StringView _cel_AstFromProto_StrDup( + CEL_NONNULL(_cel_AstFromProtoState*) state, cel_StringView in) { + cel_StringView out; + if (CEL_UNLIKELY(!cel_Arena_StrDup(state->arena, &out, in))) { + cel_OutOfMemoryStatus(state->status); + _cel_longjmp(state->jmp); + } + return out; +} + +static void _cel_AstFromProto_UpdatePosition( + CEL_NONNULL(_cel_AstFromProtoState*) state, CEL_NONNULL(cel_Expr*) expr) { + if (state->source_info != cel_nullptr) { + int64_t id = cel_Expr_Id(expr); + if (id != 0) { + int32_t position; + if (cel_expr_SourceInfo_positions_get(state->source_info, id, + &position)) { + if (CEL_UNLIKELY(position < -1)) { + cel_InvalidArgumentStatusF( + state->status, + "cel: expected " + "google.api.expr.SourceInfo.positions[%" PRId64 + "] " + "to be greater than or equal to -1: %" PRId32, + id, position); + _cel_longjmp(state->jmp); + } + cel_Expr_SetSourcePosition(expr, position); + } + } + } +} + +static void _cel_AstFromProto_SetType(CEL_NONNULL(_cel_AstFromProtoState*) + state, + CEL_NONNULL(cel_Expr*) expr) { + int64_t id = cel_Expr_Id(expr); + if (id != 0) { + const cel_expr_Type* in_type; + if (cel_expr_CheckedExpr_type_map_get( + state->checked_expr, id, (cel_expr_Type**)&in_type)) { + const cel_Type* type = + cel_Type_FromProto(in_type, state->arena, state->status); + if (CEL_UNLIKELY(type == cel_nullptr)) { + _cel_longjmp(state->jmp); + } + cel_Expr_SetType(expr, type); + } + } +} + +static void _cel_AstFromProto_SetRef(CEL_NONNULL(_cel_AstFromProtoState*) state, + CEL_NONNULL(cel_Expr*) expr) { + int64_t id = cel_Expr_Id(expr); + if (id != 0) { + const cel_expr_Reference* in_ref; + if (cel_expr_CheckedExpr_reference_map_get( + state->checked_expr, id, (cel_expr_Reference**)&in_ref)) { + const cel_Ref* ref = + cel_Ref_FromProto(in_ref, state->arena, state->status); + if (CEL_UNLIKELY(ref == cel_nullptr)) { + _cel_longjmp(state->jmp); + } + cel_Expr_SetRef(expr, ref); + } + } +} + +static CEL_NONNULL(cel_Expr*) + _cel_AstFromProto_Expr(CEL_NONNULL(_cel_AstFromProtoState*) state, + CEL_NONNULL(const cel_expr_Expr*) in); + +static CEL_NONNULL(cel_Expr*) + _cel_AstFromProto_UnspecifiedExpr(CEL_NONNULL(_cel_AstFromProtoState*) + state, + int64_t id) { + _cel_AstFromProto_CheckId(state, id); + CEL_NULLABLE(cel_UnspecifiedExpr*) expr = cel_UnspecifiedExpr_New(state->ast); + if (CEL_UNLIKELY(expr == cel_nullptr)) { + cel_OutOfMemoryStatus(state->status); + _cel_longjmp(state->jmp); + } + cel_Expr_SetId(cel_Expr_UpCast(expr), id); + _cel_AstFromProto_UpdatePosition(state, cel_Expr_UpCast(expr)); + _cel_AstFromProto_SetType(state, cel_Expr_UpCast(expr)); + _cel_AstFromProto_SetRef(state, cel_Expr_UpCast(expr)); + return cel_Expr_UpCast(expr); +} + +static CEL_NONNULL(cel_Expr*) + _cel_AstFromProto_IdentExpr(CEL_NONNULL(_cel_AstFromProtoState*) state, + int64_t id, + CEL_NONNULL(const cel_expr_Expr_Ident*) + in) { + _cel_AstFromProto_CheckId(state, id); + CEL_NULLABLE(cel_IdentExpr*) expr = cel_IdentExpr_New(state->ast); + if (CEL_UNLIKELY(expr == cel_nullptr)) { + cel_OutOfMemoryStatus(state->status); + _cel_longjmp(state->jmp); + } + cel_Expr_SetId(cel_Expr_UpCast(expr), id); + _cel_AstFromProto_UpdatePosition(state, cel_Expr_UpCast(expr)); + _cel_AstFromProto_SetType(state, cel_Expr_UpCast(expr)); + _cel_AstFromProto_SetRef(state, cel_Expr_UpCast(expr)); + cel_IdentExpr_SetName(expr, _cel_AstFromProto_StrDup( + state, cel_expr_Expr_Ident_name(in))); + return cel_Expr_UpCast(expr); +} + +static CEL_NONNULL(cel_Expr*) + _cel_AstFromProto_ConstExpr(CEL_NONNULL(_cel_AstFromProtoState*) state, + int64_t id, + CEL_NONNULL(const cel_expr_Constant*) + in) { + _cel_AstFromProto_CheckId(state, id); + CEL_NULLABLE(cel_ConstExpr*) expr = cel_ConstExpr_New(state->ast); + if (CEL_UNLIKELY(expr == cel_nullptr)) { + cel_OutOfMemoryStatus(state->status); + _cel_longjmp(state->jmp); + } + cel_Expr_SetId(cel_Expr_UpCast(expr), id); + _cel_AstFromProto_UpdatePosition(state, cel_Expr_UpCast(expr)); + _cel_AstFromProto_SetType(state, cel_Expr_UpCast(expr)); + _cel_AstFromProto_SetRef(state, cel_Expr_UpCast(expr)); + if (CEL_UNLIKELY(!cel_Constant_FromProto(cel_ConstExpr_MutableValue(expr), in, + state->arena, state->status))) { + _cel_longjmp(state->jmp); + } + return cel_Expr_UpCast(expr); +} + +static CEL_NONNULL(cel_Expr*) + _cel_AstFromProto_SelectExpr(CEL_NONNULL(_cel_AstFromProtoState*) state, + int64_t id, + CEL_NONNULL(const cel_expr_Expr_Select*) + in) { + _cel_AstFromProto_CheckId(state, id); + CEL_NULLABLE(cel_SelectExpr*) expr = cel_SelectExpr_New(state->ast); + if (CEL_UNLIKELY(expr == cel_nullptr)) { + cel_OutOfMemoryStatus(state->status); + _cel_longjmp(state->jmp); + } + cel_Expr_SetId(cel_Expr_UpCast(expr), id); + _cel_AstFromProto_UpdatePosition(state, cel_Expr_UpCast(expr)); + _cel_AstFromProto_SetType(state, cel_Expr_UpCast(expr)); + _cel_AstFromProto_SetRef(state, cel_Expr_UpCast(expr)); + if (cel_expr_Expr_Select_has_operand(in)) { + cel_SelectExpr_SetOperand( + expr, + _cel_AstFromProto_Expr(state, cel_expr_Expr_Select_operand(in))); + } + cel_SelectExpr_SetField( + expr, + _cel_AstFromProto_StrDup(state, cel_expr_Expr_Select_field(in))); + cel_SelectExpr_SetTestOnly(expr, cel_expr_Expr_Select_test_only(in)); + return cel_Expr_UpCast(expr); +} + +static CEL_NONNULL(cel_Expr*) + _cel_AstFromProto_UnaryExpr(CEL_NONNULL(_cel_AstFromProtoState*) state, + int64_t id, cel_UnaryOp op, + CEL_NONNULL(const cel_expr_Expr*) arg) { + _cel_AstFromProto_CheckId(state, id); + CEL_NULLABLE(cel_UnaryExpr*) expr = cel_UnaryExpr_New(state->ast); + if (CEL_UNLIKELY(expr == cel_nullptr)) { + cel_OutOfMemoryStatus(state->status); + _cel_longjmp(state->jmp); + } + cel_Expr_SetId(cel_Expr_UpCast(expr), id); + _cel_AstFromProto_UpdatePosition(state, cel_Expr_UpCast(expr)); + _cel_AstFromProto_SetType(state, cel_Expr_UpCast(expr)); + _cel_AstFromProto_SetRef(state, cel_Expr_UpCast(expr)); + cel_UnaryExpr_SetOp(expr, op); + cel_UnaryExpr_SetArg(expr, _cel_AstFromProto_Expr(state, arg)); + return cel_Expr_UpCast(expr); +} + +static CEL_NONNULL(cel_Expr*) + _cel_AstFromProto_BinaryExpr(CEL_NONNULL(_cel_AstFromProtoState*) state, + int64_t id, cel_BinaryOp op, + CEL_NONNULL(const cel_expr_Expr*) left, + CEL_NONNULL(const cel_expr_Expr*) + right) { + _cel_AstFromProto_CheckId(state, id); + CEL_NULLABLE(cel_BinaryExpr*) expr = cel_BinaryExpr_New(state->ast); + if (CEL_UNLIKELY(expr == cel_nullptr)) { + cel_OutOfMemoryStatus(state->status); + _cel_longjmp(state->jmp); + } + cel_Expr_SetId(cel_Expr_UpCast(expr), id); + _cel_AstFromProto_UpdatePosition(state, cel_Expr_UpCast(expr)); + _cel_AstFromProto_SetType(state, cel_Expr_UpCast(expr)); + _cel_AstFromProto_SetRef(state, cel_Expr_UpCast(expr)); + cel_BinaryExpr_SetOp(expr, op); + cel_BinaryExpr_SetLeft(expr, _cel_AstFromProto_Expr(state, left)); + cel_BinaryExpr_SetRight(expr, _cel_AstFromProto_Expr(state, right)); + return cel_Expr_UpCast(expr); +} + +static CEL_NONNULL(cel_Expr*) _cel_AstFromProto_TernaryExpr( + CEL_NONNULL(_cel_AstFromProtoState*) state, int64_t id, cel_TernaryOp op, + CEL_NONNULL(const cel_expr_Expr*) condition, + CEL_NONNULL(const cel_expr_Expr*) if_true, + CEL_NONNULL(const cel_expr_Expr*) if_false) { + _cel_AstFromProto_CheckId(state, id); + CEL_NULLABLE(cel_TernaryExpr*) expr = cel_TernaryExpr_New(state->ast); + if (CEL_UNLIKELY(expr == cel_nullptr)) { + cel_OutOfMemoryStatus(state->status); + _cel_longjmp(state->jmp); + } + cel_Expr_SetId(cel_Expr_UpCast(expr), id); + _cel_AstFromProto_UpdatePosition(state, cel_Expr_UpCast(expr)); + _cel_AstFromProto_SetType(state, cel_Expr_UpCast(expr)); + _cel_AstFromProto_SetRef(state, cel_Expr_UpCast(expr)); + cel_TernaryExpr_SetOp(expr, op); + cel_TernaryExpr_SetCondition(expr, _cel_AstFromProto_Expr(state, condition)); + cel_TernaryExpr_SetIfTrue(expr, _cel_AstFromProto_Expr(state, if_true)); + cel_TernaryExpr_SetIfFalse(expr, _cel_AstFromProto_Expr(state, if_false)); + return cel_Expr_UpCast(expr); +} + +static CEL_NONNULL(cel_Expr*) + _cel_AstFromProto_CallExpr(CEL_NONNULL(_cel_AstFromProtoState*) state, + int64_t id, + CEL_NONNULL(const cel_expr_Expr_Call*) + in) { + size_t in_args_len = 0; + const cel_expr_Expr* const* in_args = + cel_expr_Expr_Call_args(in, &in_args_len); + cel_StringView function = cel_expr_Expr_Call_function(in); + if (!cel_StringView_Empty(function) && + !cel_expr_Expr_Call_has_target(in)) { + switch (in_args_len) { + case 1: { + cel_UnaryOp op; + if (cel_UnaryOp_FromString(function, &op)) { + return _cel_AstFromProto_UnaryExpr(state, id, op, in_args[0]); + } + } break; + case 2: { + cel_BinaryOp op; + if (cel_BinaryOp_FromString(function, &op)) { + return _cel_AstFromProto_BinaryExpr(state, id, op, in_args[0], + in_args[1]); + } + } break; + case 3: { + cel_TernaryOp op; + if (cel_TernaryOp_FromString(function, &op)) { + return _cel_AstFromProto_TernaryExpr(state, id, op, in_args[0], + in_args[1], in_args[2]); + } + } break; + default: + break; + } + } + _cel_AstFromProto_CheckId(state, id); + CEL_NULLABLE(cel_CallExpr*) expr = cel_CallExpr_New(state->ast); + if (CEL_UNLIKELY(expr == cel_nullptr)) { + cel_OutOfMemoryStatus(state->status); + _cel_longjmp(state->jmp); + } + cel_Expr_SetId(cel_Expr_UpCast(expr), id); + _cel_AstFromProto_UpdatePosition(state, cel_Expr_UpCast(expr)); + _cel_AstFromProto_SetType(state, cel_Expr_UpCast(expr)); + _cel_AstFromProto_SetRef(state, cel_Expr_UpCast(expr)); + if (cel_expr_Expr_Call_has_target(in)) { + cel_CallExpr_SetTarget( + expr, + _cel_AstFromProto_Expr(state, cel_expr_Expr_Call_target(in))); + } + cel_CallExpr_SetFunction(expr, _cel_AstFromProto_StrDup(state, function)); + for (size_t i = 0; i < in_args_len; ++i) { + CEL_NULLABLE(cel_CallArgExpr*) + arg = cel_CallArgExpr_New(state->ast); + if (CEL_UNLIKELY(arg == cel_nullptr)) { + cel_OutOfMemoryStatus(state->status); + _cel_longjmp(state->jmp); + } + CEL_NONNULL(cel_Expr*) + arg_value = _cel_AstFromProto_Expr(state, in_args[i]); + cel_Expr_SetSourceRange(cel_Expr_UpCast(arg), + cel_Expr_SourceRange(arg_value)); + cel_CallArgExpr_SetValue(arg, arg_value); + cel_CallExpr_AppendArg(expr, arg); + } + return cel_Expr_UpCast(expr); +} + +static CEL_NONNULL(cel_Expr*) _cel_AstFromProto_ListExpr( + CEL_NONNULL(_cel_AstFromProtoState*) state, int64_t id, + CEL_NONNULL(const cel_expr_Expr_CreateList*) in) { + _cel_AstFromProto_CheckId(state, id); + CEL_NULLABLE(cel_ListExpr*) expr = cel_ListExpr_New(state->ast); + if (CEL_UNLIKELY(expr == cel_nullptr)) { + cel_OutOfMemoryStatus(state->status); + _cel_longjmp(state->jmp); + } + cel_Expr_SetId(cel_Expr_UpCast(expr), id); + _cel_AstFromProto_UpdatePosition(state, cel_Expr_UpCast(expr)); + _cel_AstFromProto_SetType(state, cel_Expr_UpCast(expr)); + _cel_AstFromProto_SetRef(state, cel_Expr_UpCast(expr)); + size_t in_opt_indices_len = 0; + const int32_t* in_opt_indices = + cel_expr_Expr_CreateList_optional_indices(in, &in_opt_indices_len); + size_t in_elems_len = 0; + const cel_expr_Expr* const* in_elems = + cel_expr_Expr_CreateList_elements(in, &in_elems_len); + for (size_t i = 0; i < in_elems_len; ++i) { + CEL_NULLABLE(cel_ListElementExpr*) + elem = cel_ListElementExpr_New(state->ast); + if (CEL_UNLIKELY(elem == cel_nullptr)) { + cel_OutOfMemoryStatus(state->status); + _cel_longjmp(state->jmp); + } + CEL_NONNULL(cel_Expr*) + elem_value = _cel_AstFromProto_Expr(state, in_elems[i]); + cel_Expr_SetSourceRange(cel_Expr_UpCast(elem), + cel_Expr_SourceRange(elem_value)); + cel_ListElementExpr_SetValue(elem, elem_value); + bool optional = false; + for (size_t j = 0; j < in_opt_indices_len; ++j) { + int32_t in_opt_index = in_opt_indices[j]; + if (in_opt_index < 0) { + // Just skip for now. In future we should error. + continue; + } + if (((uint32_t)in_opt_index) == i) { + optional = true; + break; + } + } + cel_ListElementExpr_SetOptional(elem, optional); + cel_ListExpr_AppendElement(expr, elem); + } + return cel_Expr_UpCast(expr); +} + +static CEL_NONNULL(cel_Expr*) _cel_AstFromProto_MapExpr( + CEL_NONNULL(_cel_AstFromProtoState*) state, int64_t id, + CEL_NONNULL(const cel_expr_Expr_CreateStruct*) in) { + _cel_AstFromProto_CheckId(state, id); + CEL_NULLABLE(cel_MapExpr*) expr = cel_MapExpr_New(state->ast); + if (CEL_UNLIKELY(expr == cel_nullptr)) { + cel_OutOfMemoryStatus(state->status); + _cel_longjmp(state->jmp); + } + cel_Expr_SetId(cel_Expr_UpCast(expr), id); + _cel_AstFromProto_UpdatePosition(state, cel_Expr_UpCast(expr)); + _cel_AstFromProto_SetType(state, cel_Expr_UpCast(expr)); + _cel_AstFromProto_SetRef(state, cel_Expr_UpCast(expr)); + size_t in_ents_len = 0; + const cel_expr_Expr_CreateStruct_Entry* const* in_ents = + cel_expr_Expr_CreateStruct_entries(in, &in_ents_len); + for (size_t i = 0; i < in_ents_len; ++i) { + const cel_expr_Expr_CreateStruct_Entry* in_ent = in_ents[i]; + const cel_expr_Expr_CreateStruct_Entry_key_kind_oneofcases key_kind = + cel_expr_Expr_CreateStruct_Entry_key_kind_case(in_ent); + if (key_kind != cel_expr_Expr_CreateStruct_Entry_key_kind_map_key) { + cel_InvalidArgumentStatusF( + state->status, + "cel: expected google.api.expr.Expr.CreateStruct.Entry.key_kind to " + "be map_key: %d", + key_kind); + _cel_longjmp(state->jmp); + } + CEL_NONNULL(cel_MapEntryExpr*) ent = cel_MapEntryExpr_New(state->ast); + if (CEL_UNLIKELY(ent == cel_nullptr)) { + cel_OutOfMemoryStatus(state->status); + _cel_longjmp(state->jmp); + } + cel_Expr_SetId( + cel_Expr_UpCast(ent), + _cel_AstFromProto_CheckId( + state, cel_expr_Expr_CreateStruct_Entry_id(in_ent))); + _cel_AstFromProto_UpdatePosition(state, cel_Expr_UpCast(ent)); + _cel_AstFromProto_SetType(state, cel_Expr_UpCast(ent)); + _cel_AstFromProto_SetRef(state, cel_Expr_UpCast(ent)); + cel_MapEntryExpr_SetKey( + ent, + _cel_AstFromProto_Expr( + state, cel_expr_Expr_CreateStruct_Entry_map_key(in_ent))); + if (cel_expr_Expr_CreateStruct_Entry_has_value(in_ent)) { + cel_MapEntryExpr_SetValue( + ent, + _cel_AstFromProto_Expr( + state, cel_expr_Expr_CreateStruct_Entry_value(in_ent))); + } + cel_MapEntryExpr_SetOptional( + ent, cel_expr_Expr_CreateStruct_Entry_optional_entry(in_ent)); + cel_MapExpr_AppendEntry(expr, ent); + } + return cel_Expr_UpCast(expr); +} + +static CEL_NONNULL(cel_Expr*) _cel_AstFromProto_StructExpr( + CEL_NONNULL(_cel_AstFromProtoState*) state, int64_t id, + CEL_NONNULL(const cel_expr_Expr_CreateStruct*) in) { + _cel_AstFromProto_CheckId(state, id); + CEL_NULLABLE(cel_StructExpr*) expr = cel_StructExpr_New(state->ast); + if (CEL_UNLIKELY(expr == cel_nullptr)) { + cel_OutOfMemoryStatus(state->status); + _cel_longjmp(state->jmp); + } + cel_Expr_SetId(cel_Expr_UpCast(expr), id); + _cel_AstFromProto_UpdatePosition(state, cel_Expr_UpCast(expr)); + _cel_AstFromProto_SetType(state, cel_Expr_UpCast(expr)); + _cel_AstFromProto_SetRef(state, cel_Expr_UpCast(expr)); + cel_StructExpr_SetName( + expr, _cel_AstFromProto_StrDup( + state, cel_expr_Expr_CreateStruct_message_name(in))); + size_t in_flds_len = 0; + const cel_expr_Expr_CreateStruct_Entry* const* in_flds = + cel_expr_Expr_CreateStruct_entries(in, &in_flds_len); + for (size_t i = 0; i < in_flds_len; ++i) { + const cel_expr_Expr_CreateStruct_Entry* in_fld = in_flds[i]; + const cel_expr_Expr_CreateStruct_Entry_key_kind_oneofcases key_kind = + cel_expr_Expr_CreateStruct_Entry_key_kind_case(in_fld); + if (key_kind != + cel_expr_Expr_CreateStruct_Entry_key_kind_field_key) { + cel_InvalidArgumentStatusF( + state->status, + "cel: expected google.api.expr.Expr.CreateStruct.Entry.key_kind to " + "be field_key: %d", + key_kind); + _cel_longjmp(state->jmp); + } + CEL_NONNULL(cel_StructFieldExpr*) fld = cel_StructFieldExpr_New(state->ast); + if (CEL_UNLIKELY(fld == cel_nullptr)) { + cel_OutOfMemoryStatus(state->status); + _cel_longjmp(state->jmp); + } + cel_Expr_SetId( + cel_Expr_UpCast(fld), + _cel_AstFromProto_CheckId( + state, cel_expr_Expr_CreateStruct_Entry_id(in_fld))); + _cel_AstFromProto_UpdatePosition(state, cel_Expr_UpCast(fld)); + _cel_AstFromProto_SetType(state, cel_Expr_UpCast(fld)); + _cel_AstFromProto_SetRef(state, cel_Expr_UpCast(fld)); + cel_StructFieldExpr_SetName( + fld, + _cel_AstFromProto_StrDup( + state, cel_expr_Expr_CreateStruct_Entry_field_key(in_fld))); + if (cel_expr_Expr_CreateStruct_Entry_has_value(in_fld)) { + cel_StructFieldExpr_SetValue( + fld, + _cel_AstFromProto_Expr( + state, cel_expr_Expr_CreateStruct_Entry_value(in_fld))); + } + cel_StructFieldExpr_SetOptional( + fld, cel_expr_Expr_CreateStruct_Entry_optional_entry(in_fld)); + cel_StructExpr_AppendField(expr, fld); + } + return cel_Expr_UpCast(expr); +} + +static CEL_NONNULL(cel_Expr*) _cel_AstFromProto_ComprehensionExpr( + CEL_NONNULL(_cel_AstFromProtoState*) state, int64_t id, + CEL_NONNULL(const cel_expr_Expr_Comprehension*) in) { + _cel_AstFromProto_CheckId(state, id); + CEL_NULLABLE(cel_ComprehensionExpr*) + expr = cel_ComprehensionExpr_New(state->ast); + if (CEL_UNLIKELY(expr == cel_nullptr)) { + cel_OutOfMemoryStatus(state->status); + _cel_longjmp(state->jmp); + } + cel_Expr_SetId(cel_Expr_UpCast(expr), id); + _cel_AstFromProto_UpdatePosition(state, cel_Expr_UpCast(expr)); + _cel_AstFromProto_SetType(state, cel_Expr_UpCast(expr)); + _cel_AstFromProto_SetRef(state, cel_Expr_UpCast(expr)); + cel_ComprehensionExpr_SetIterVar( + expr, _cel_AstFromProto_StrDup( + state, cel_expr_Expr_Comprehension_iter_var(in))); + cel_ComprehensionExpr_SetIterVar2( + expr, _cel_AstFromProto_StrDup( + state, cel_expr_Expr_Comprehension_iter_var2(in))); + cel_ComprehensionExpr_SetAccuVar( + expr, _cel_AstFromProto_StrDup( + state, cel_expr_Expr_Comprehension_accu_var(in))); + if (cel_expr_Expr_Comprehension_has_iter_range(in)) { + cel_ComprehensionExpr_SetIterRange( + expr, _cel_AstFromProto_Expr( + state, cel_expr_Expr_Comprehension_iter_range(in))); + } + if (cel_expr_Expr_Comprehension_has_accu_init(in)) { + cel_ComprehensionExpr_SetAccuInit( + expr, _cel_AstFromProto_Expr( + state, cel_expr_Expr_Comprehension_accu_init(in))); + } + if (cel_expr_Expr_Comprehension_has_loop_condition(in)) { + cel_ComprehensionExpr_SetLoopCondition( + expr, + _cel_AstFromProto_Expr( + state, cel_expr_Expr_Comprehension_loop_condition(in))); + } + if (cel_expr_Expr_Comprehension_has_loop_step(in)) { + cel_ComprehensionExpr_SetLoopStep( + expr, _cel_AstFromProto_Expr( + state, cel_expr_Expr_Comprehension_loop_step(in))); + } + if (cel_expr_Expr_Comprehension_has_result(in)) { + cel_ComprehensionExpr_SetResult( + expr, _cel_AstFromProto_Expr( + state, cel_expr_Expr_Comprehension_result(in))); + } + return cel_Expr_UpCast(expr); +} + +static CEL_NONNULL(cel_Expr*) + _cel_AstFromProto_Expr(CEL_NONNULL(_cel_AstFromProtoState*) state, + CEL_NONNULL(const cel_expr_Expr*) in) { + cel_expr_Expr_expr_kind_oneofcases kind = + cel_expr_Expr_expr_kind_case(in); + switch (kind) { + case cel_expr_Expr_expr_kind_NOT_SET: + return _cel_AstFromProto_UnspecifiedExpr(state, + cel_expr_Expr_id(in)); + case cel_expr_Expr_expr_kind_ident_expr: + return _cel_AstFromProto_IdentExpr(state, cel_expr_Expr_id(in), + cel_expr_Expr_ident_expr(in)); + case cel_expr_Expr_expr_kind_const_expr: + return _cel_AstFromProto_ConstExpr(state, cel_expr_Expr_id(in), + cel_expr_Expr_const_expr(in)); + case cel_expr_Expr_expr_kind_select_expr: + return _cel_AstFromProto_SelectExpr(state, cel_expr_Expr_id(in), + cel_expr_Expr_select_expr(in)); + case cel_expr_Expr_expr_kind_call_expr: + return _cel_AstFromProto_CallExpr(state, cel_expr_Expr_id(in), + cel_expr_Expr_call_expr(in)); + case cel_expr_Expr_expr_kind_list_expr: + return _cel_AstFromProto_ListExpr(state, cel_expr_Expr_id(in), + cel_expr_Expr_list_expr(in)); + case cel_expr_Expr_expr_kind_struct_expr: { + CEL_NONNULL(const cel_expr_Expr_CreateStruct*) + struct_expr = cel_expr_Expr_struct_expr(in); + if (cel_StringView_Empty( + cel_expr_Expr_CreateStruct_message_name(struct_expr))) { + return _cel_AstFromProto_MapExpr(state, cel_expr_Expr_id(in), + struct_expr); + } + return _cel_AstFromProto_StructExpr(state, cel_expr_Expr_id(in), + struct_expr); + } + case cel_expr_Expr_expr_kind_comprehension_expr: + return _cel_AstFromProto_ComprehensionExpr( + state, cel_expr_Expr_id(in), + cel_expr_Expr_comprehension_expr(in)); + default: + cel_InvalidArgumentStatusF( + state->status, "cel: unexpected google.api.expr.Expr kind: %d", kind); + _cel_longjmp(state->jmp); + } +} + +extern "C" CEL_NULLABLE(cel_Ast*) + cel_Ast_FromProto(CEL_NONNULL(const cel_expr_CheckedExpr*) in, + CEL_NONNULL(cel_Arena*) arena, + CEL_NONNULL(cel_Status*) status) { + CEL_ASSERT_NOT_NULL(in); + CEL_ASSERT_NOT_NULL(arena); + CEL_ASSERT_NOT_NULL(status); + + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return cel_nullptr; + } + CEL_NULLABLE(_cel_AstFromProtoState*) + volatile state = (CEL_NULLABLE(_cel_AstFromProtoState*))_cel_Malloc( + sizeof(_cel_AstFromProtoState), cel_nullptr); + if (CEL_UNLIKELY(state == cel_nullptr)) { + cel_OutOfMemoryStatus(status); + return cel_nullptr; + } + CEL_NULLABLE(cel_Ast*) ast = cel_Ast_New(arena); + if (CEL_UNLIKELY(ast == cel_nullptr)) { + _cel_FreeSized(state, sizeof(_cel_AstFromProtoState)); + cel_OutOfMemoryStatus(status); + return cel_nullptr; + } + state->ast = ast; + state->arena = arena; + state->status = status; + state->checked_expr = in; + state->source_info = cel_nullptr; + + if (_cel_setjmp(state->jmp)) { + // ERROR + CEL_ASSERT(!cel_Status_Ok(state->status)); + cel_Ast_Delete(state->ast); + _cel_FreeSized(state, sizeof(_cel_AstFromProtoState)); + return cel_nullptr; + } else { + if (cel_expr_CheckedExpr_has_expr(in)) { + if (cel_expr_CheckedExpr_has_source_info(in)) { + state->source_info = cel_expr_CheckedExpr_source_info(in); + } + cel_Ast_SetExpr( + state->ast, + _cel_AstFromProto_Expr(state, cel_expr_CheckedExpr_expr(in))); + } + // OK + CEL_ASSERT(cel_Status_Ok(state->status)); + _cel_FreeSized(state, sizeof(_cel_AstFromProtoState)); + return ast; + } +} diff --git a/cel-c/ast_proto_v1alpha1.cc b/cel-c/ast_proto_v1alpha1.cc new file mode 100644 index 0000000..84aeeb5 --- /dev/null +++ b/cel-c/ast_proto_v1alpha1.cc @@ -0,0 +1,697 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "cel-c/ast_proto_v1alpha1.h" + +#include +#include +#include +#include + +#include "google/api/expr/v1alpha1/checked.upb.h" +#include "google/api/expr/v1alpha1/syntax.upb.h" +#include "cel-c/arena.h" +#include "cel-c/assert.h" +#include "cel-c/ast.h" +#include "cel-c/config.h" +#include "cel-c/constant_proto_v1alpha1.h" +#include "cel-c/operators.h" +#include "cel-c/ref.h" +#include "cel-c/ref_proto_v1alpha1.h" +#include "cel-c/src/malloc.h" +#include "cel-c/src/setjmp.h" +#include "cel-c/status.h" +#include "cel-c/string_view.h" +#include "cel-c/type.h" +#include "cel-c/type_proto_v1alpha1.h" + +typedef struct { + CEL_NONNULL(cel_Ast*) ast; + CEL_NONNULL(cel_Arena*) arena; + CEL_NONNULL(cel_Status*) status; + CEL_NONNULL(const google_api_expr_v1alpha1_CheckedExpr*) checked_expr; + CEL_NULLABLE(const google_api_expr_v1alpha1_SourceInfo*) source_info; + _cel_jmp_buf jmp; +} _cel_AstFromProtoV1Alpha1State; + +static int64_t _cel_AstFromProtoV1Alpha1_CheckId( + CEL_NONNULL(_cel_AstFromProtoV1Alpha1State*) state, cel_ExprId id) { + if (CEL_UNLIKELY(id < 0)) { + cel_InvalidArgumentStatusF(state->status, + "cel: expected google.api.expr.v1alpha1.Expr.id " + "to be greater than or equal to 0: %" PRId64, + id); + _cel_longjmp(state->jmp); + } + return id; +} + +static cel_StringView _cel_AstFromProtoV1Alpha1_StrDup( + CEL_NONNULL(_cel_AstFromProtoV1Alpha1State*) state, cel_StringView in) { + cel_StringView out; + if (CEL_UNLIKELY(!cel_Arena_StrDup(state->arena, &out, in))) { + cel_OutOfMemoryStatus(state->status); + _cel_longjmp(state->jmp); + } + return out; +} + +static void _cel_AstFromProtoV1Alpha1_UpdatePosition( + CEL_NONNULL(_cel_AstFromProtoV1Alpha1State*) state, + CEL_NONNULL(cel_Expr*) expr) { + if (state->source_info != cel_nullptr) { + int64_t id = cel_Expr_Id(expr); + if (id != 0) { + int32_t position; + if (google_api_expr_v1alpha1_SourceInfo_positions_get(state->source_info, + id, &position)) { + if (CEL_UNLIKELY(position < -1)) { + cel_InvalidArgumentStatusF( + state->status, + "cel: expected " + "google.api.expr.v1alpha1.SourceInfo.positions[%" PRId64 + "] " + "to be greater than or equal to -1: %" PRId32, + id, position); + _cel_longjmp(state->jmp); + } + cel_Expr_SetSourcePosition(expr, position); + } + } + } +} + +static void _cel_AstFromProtoV1Alpha1_SetType( + CEL_NONNULL(_cel_AstFromProtoV1Alpha1State*) state, + CEL_NONNULL(cel_Expr*) expr) { + int64_t id = cel_Expr_Id(expr); + if (id != 0) { + const google_api_expr_v1alpha1_Type* in_type; + if (google_api_expr_v1alpha1_CheckedExpr_type_map_get( + state->checked_expr, id, + (google_api_expr_v1alpha1_Type**)&in_type)) { + const cel_Type* type = + cel_Type_FromProtoV1Alpha1(in_type, state->arena, state->status); + if (CEL_UNLIKELY(type == cel_nullptr)) { + _cel_longjmp(state->jmp); + } + cel_Expr_SetType(expr, type); + } + } +} + +static void _cel_AstFromProtoV1Alpha1_SetRef( + CEL_NONNULL(_cel_AstFromProtoV1Alpha1State*) state, + CEL_NONNULL(cel_Expr*) expr) { + int64_t id = cel_Expr_Id(expr); + if (id != 0) { + const google_api_expr_v1alpha1_Reference* in_ref; + if (google_api_expr_v1alpha1_CheckedExpr_reference_map_get( + state->checked_expr, id, + (google_api_expr_v1alpha1_Reference**)&in_ref)) { + const cel_Ref* ref = + cel_Ref_FromProtoV1Alpha1(in_ref, state->arena, state->status); + if (CEL_UNLIKELY(ref == cel_nullptr)) { + _cel_longjmp(state->jmp); + } + cel_Expr_SetRef(expr, ref); + } + } +} + +static CEL_NONNULL(cel_Expr*) _cel_AstFromProtoV1Alpha1_Expr( + CEL_NONNULL(_cel_AstFromProtoV1Alpha1State*) state, + CEL_NONNULL(const google_api_expr_v1alpha1_Expr*) in); + +static CEL_NONNULL(cel_Expr*) _cel_AstFromProtoV1Alpha1_UnspecifiedExpr( + CEL_NONNULL(_cel_AstFromProtoV1Alpha1State*) state, int64_t id) { + _cel_AstFromProtoV1Alpha1_CheckId(state, id); + CEL_NULLABLE(cel_UnspecifiedExpr*) expr = cel_UnspecifiedExpr_New(state->ast); + if (CEL_UNLIKELY(expr == cel_nullptr)) { + cel_OutOfMemoryStatus(state->status); + _cel_longjmp(state->jmp); + } + cel_Expr_SetId(cel_Expr_UpCast(expr), id); + _cel_AstFromProtoV1Alpha1_UpdatePosition(state, cel_Expr_UpCast(expr)); + _cel_AstFromProtoV1Alpha1_SetType(state, cel_Expr_UpCast(expr)); + _cel_AstFromProtoV1Alpha1_SetRef(state, cel_Expr_UpCast(expr)); + return cel_Expr_UpCast(expr); +} + +static CEL_NONNULL(cel_Expr*) _cel_AstFromProtoV1Alpha1_IdentExpr( + CEL_NONNULL(_cel_AstFromProtoV1Alpha1State*) state, int64_t id, + CEL_NONNULL(const google_api_expr_v1alpha1_Expr_Ident*) in) { + _cel_AstFromProtoV1Alpha1_CheckId(state, id); + CEL_NULLABLE(cel_IdentExpr*) expr = cel_IdentExpr_New(state->ast); + if (CEL_UNLIKELY(expr == cel_nullptr)) { + cel_OutOfMemoryStatus(state->status); + _cel_longjmp(state->jmp); + } + cel_Expr_SetId(cel_Expr_UpCast(expr), id); + _cel_AstFromProtoV1Alpha1_UpdatePosition(state, cel_Expr_UpCast(expr)); + _cel_AstFromProtoV1Alpha1_SetType(state, cel_Expr_UpCast(expr)); + _cel_AstFromProtoV1Alpha1_SetRef(state, cel_Expr_UpCast(expr)); + cel_IdentExpr_SetName( + expr, _cel_AstFromProtoV1Alpha1_StrDup( + state, google_api_expr_v1alpha1_Expr_Ident_name(in))); + return cel_Expr_UpCast(expr); +} + +static CEL_NONNULL(cel_Expr*) _cel_AstFromProtoV1Alpha1_ConstExpr( + CEL_NONNULL(_cel_AstFromProtoV1Alpha1State*) state, int64_t id, + CEL_NONNULL(const google_api_expr_v1alpha1_Constant*) in) { + _cel_AstFromProtoV1Alpha1_CheckId(state, id); + CEL_NULLABLE(cel_ConstExpr*) expr = cel_ConstExpr_New(state->ast); + if (CEL_UNLIKELY(expr == cel_nullptr)) { + cel_OutOfMemoryStatus(state->status); + _cel_longjmp(state->jmp); + } + cel_Expr_SetId(cel_Expr_UpCast(expr), id); + _cel_AstFromProtoV1Alpha1_UpdatePosition(state, cel_Expr_UpCast(expr)); + _cel_AstFromProtoV1Alpha1_SetType(state, cel_Expr_UpCast(expr)); + _cel_AstFromProtoV1Alpha1_SetRef(state, cel_Expr_UpCast(expr)); + if (CEL_UNLIKELY(!cel_Constant_FromProtoV1Alpha1( + cel_ConstExpr_MutableValue(expr), in, state->arena, state->status))) { + _cel_longjmp(state->jmp); + } + return cel_Expr_UpCast(expr); +} + +static CEL_NONNULL(cel_Expr*) _cel_AstFromProtoV1Alpha1_SelectExpr( + CEL_NONNULL(_cel_AstFromProtoV1Alpha1State*) state, int64_t id, + CEL_NONNULL(const google_api_expr_v1alpha1_Expr_Select*) in) { + _cel_AstFromProtoV1Alpha1_CheckId(state, id); + CEL_NULLABLE(cel_SelectExpr*) expr = cel_SelectExpr_New(state->ast); + if (CEL_UNLIKELY(expr == cel_nullptr)) { + cel_OutOfMemoryStatus(state->status); + _cel_longjmp(state->jmp); + } + cel_Expr_SetId(cel_Expr_UpCast(expr), id); + _cel_AstFromProtoV1Alpha1_UpdatePosition(state, cel_Expr_UpCast(expr)); + _cel_AstFromProtoV1Alpha1_SetType(state, cel_Expr_UpCast(expr)); + _cel_AstFromProtoV1Alpha1_SetRef(state, cel_Expr_UpCast(expr)); + if (google_api_expr_v1alpha1_Expr_Select_has_operand(in)) { + cel_SelectExpr_SetOperand( + expr, _cel_AstFromProtoV1Alpha1_Expr( + state, google_api_expr_v1alpha1_Expr_Select_operand(in))); + } + cel_SelectExpr_SetField( + expr, _cel_AstFromProtoV1Alpha1_StrDup( + state, google_api_expr_v1alpha1_Expr_Select_field(in))); + cel_SelectExpr_SetTestOnly( + expr, google_api_expr_v1alpha1_Expr_Select_test_only(in)); + return cel_Expr_UpCast(expr); +} + +static CEL_NONNULL(cel_Expr*) _cel_AstFromProtoV1Alpha1_UnaryExpr( + CEL_NONNULL(_cel_AstFromProtoV1Alpha1State*) state, int64_t id, + cel_UnaryOp op, CEL_NONNULL(const google_api_expr_v1alpha1_Expr*) arg) { + _cel_AstFromProtoV1Alpha1_CheckId(state, id); + CEL_NULLABLE(cel_UnaryExpr*) expr = cel_UnaryExpr_New(state->ast); + if (CEL_UNLIKELY(expr == cel_nullptr)) { + cel_OutOfMemoryStatus(state->status); + _cel_longjmp(state->jmp); + } + cel_Expr_SetId(cel_Expr_UpCast(expr), id); + _cel_AstFromProtoV1Alpha1_UpdatePosition(state, cel_Expr_UpCast(expr)); + _cel_AstFromProtoV1Alpha1_SetType(state, cel_Expr_UpCast(expr)); + _cel_AstFromProtoV1Alpha1_SetRef(state, cel_Expr_UpCast(expr)); + cel_UnaryExpr_SetOp(expr, op); + cel_UnaryExpr_SetArg(expr, _cel_AstFromProtoV1Alpha1_Expr(state, arg)); + return cel_Expr_UpCast(expr); +} + +static CEL_NONNULL(cel_Expr*) _cel_AstFromProtoV1Alpha1_BinaryExpr( + CEL_NONNULL(_cel_AstFromProtoV1Alpha1State*) state, int64_t id, + cel_BinaryOp op, CEL_NONNULL(const google_api_expr_v1alpha1_Expr*) left, + CEL_NONNULL(const google_api_expr_v1alpha1_Expr*) right) { + _cel_AstFromProtoV1Alpha1_CheckId(state, id); + CEL_NULLABLE(cel_BinaryExpr*) expr = cel_BinaryExpr_New(state->ast); + if (CEL_UNLIKELY(expr == cel_nullptr)) { + cel_OutOfMemoryStatus(state->status); + _cel_longjmp(state->jmp); + } + cel_Expr_SetId(cel_Expr_UpCast(expr), id); + _cel_AstFromProtoV1Alpha1_UpdatePosition(state, cel_Expr_UpCast(expr)); + _cel_AstFromProtoV1Alpha1_SetType(state, cel_Expr_UpCast(expr)); + _cel_AstFromProtoV1Alpha1_SetRef(state, cel_Expr_UpCast(expr)); + cel_BinaryExpr_SetOp(expr, op); + cel_BinaryExpr_SetLeft(expr, _cel_AstFromProtoV1Alpha1_Expr(state, left)); + cel_BinaryExpr_SetRight(expr, _cel_AstFromProtoV1Alpha1_Expr(state, right)); + return cel_Expr_UpCast(expr); +} + +static CEL_NONNULL(cel_Expr*) _cel_AstFromProtoV1Alpha1_TernaryExpr( + CEL_NONNULL(_cel_AstFromProtoV1Alpha1State*) state, int64_t id, + cel_TernaryOp op, + CEL_NONNULL(const google_api_expr_v1alpha1_Expr*) condition, + CEL_NONNULL(const google_api_expr_v1alpha1_Expr*) if_true, + CEL_NONNULL(const google_api_expr_v1alpha1_Expr*) if_false) { + _cel_AstFromProtoV1Alpha1_CheckId(state, id); + CEL_NULLABLE(cel_TernaryExpr*) expr = cel_TernaryExpr_New(state->ast); + if (CEL_UNLIKELY(expr == cel_nullptr)) { + cel_OutOfMemoryStatus(state->status); + _cel_longjmp(state->jmp); + } + cel_Expr_SetId(cel_Expr_UpCast(expr), id); + _cel_AstFromProtoV1Alpha1_UpdatePosition(state, cel_Expr_UpCast(expr)); + _cel_AstFromProtoV1Alpha1_SetType(state, cel_Expr_UpCast(expr)); + _cel_AstFromProtoV1Alpha1_SetRef(state, cel_Expr_UpCast(expr)); + cel_TernaryExpr_SetOp(expr, op); + cel_TernaryExpr_SetCondition( + expr, _cel_AstFromProtoV1Alpha1_Expr(state, condition)); + cel_TernaryExpr_SetIfTrue(expr, + _cel_AstFromProtoV1Alpha1_Expr(state, if_true)); + cel_TernaryExpr_SetIfFalse(expr, + _cel_AstFromProtoV1Alpha1_Expr(state, if_false)); + return cel_Expr_UpCast(expr); +} + +static CEL_NONNULL(cel_Expr*) _cel_AstFromProtoV1Alpha1_CallExpr( + CEL_NONNULL(_cel_AstFromProtoV1Alpha1State*) state, int64_t id, + CEL_NONNULL(const google_api_expr_v1alpha1_Expr_Call*) in) { + size_t in_args_len = 0; + const google_api_expr_v1alpha1_Expr* const* in_args = + google_api_expr_v1alpha1_Expr_Call_args(in, &in_args_len); + cel_StringView function = google_api_expr_v1alpha1_Expr_Call_function(in); + if (!cel_StringView_Empty(function) && + !google_api_expr_v1alpha1_Expr_Call_has_target(in)) { + switch (in_args_len) { + case 1: { + cel_UnaryOp op; + if (cel_UnaryOp_FromString(function, &op)) { + return _cel_AstFromProtoV1Alpha1_UnaryExpr(state, id, op, in_args[0]); + } + } break; + case 2: { + cel_BinaryOp op; + if (cel_BinaryOp_FromString(function, &op)) { + return _cel_AstFromProtoV1Alpha1_BinaryExpr(state, id, op, in_args[0], + in_args[1]); + } + } break; + case 3: { + cel_TernaryOp op; + if (cel_TernaryOp_FromString(function, &op)) { + return _cel_AstFromProtoV1Alpha1_TernaryExpr( + state, id, op, in_args[0], in_args[1], in_args[2]); + } + } break; + default: + break; + } + } + _cel_AstFromProtoV1Alpha1_CheckId(state, id); + CEL_NULLABLE(cel_CallExpr*) expr = cel_CallExpr_New(state->ast); + if (CEL_UNLIKELY(expr == cel_nullptr)) { + cel_OutOfMemoryStatus(state->status); + _cel_longjmp(state->jmp); + } + cel_Expr_SetId(cel_Expr_UpCast(expr), id); + _cel_AstFromProtoV1Alpha1_UpdatePosition(state, cel_Expr_UpCast(expr)); + _cel_AstFromProtoV1Alpha1_SetType(state, cel_Expr_UpCast(expr)); + _cel_AstFromProtoV1Alpha1_SetRef(state, cel_Expr_UpCast(expr)); + if (google_api_expr_v1alpha1_Expr_Call_has_target(in)) { + cel_CallExpr_SetTarget( + expr, _cel_AstFromProtoV1Alpha1_Expr( + state, google_api_expr_v1alpha1_Expr_Call_target(in))); + } + cel_CallExpr_SetFunction(expr, + _cel_AstFromProtoV1Alpha1_StrDup(state, function)); + for (size_t i = 0; i < in_args_len; ++i) { + CEL_NULLABLE(cel_CallArgExpr*) + arg = cel_CallArgExpr_New(state->ast); + if (CEL_UNLIKELY(arg == cel_nullptr)) { + cel_OutOfMemoryStatus(state->status); + _cel_longjmp(state->jmp); + } + CEL_NONNULL(cel_Expr*) + arg_value = _cel_AstFromProtoV1Alpha1_Expr(state, in_args[i]); + cel_Expr_SetSourceRange(cel_Expr_UpCast(arg), + cel_Expr_SourceRange(arg_value)); + cel_CallArgExpr_SetValue(arg, arg_value); + cel_CallExpr_AppendArg(expr, arg); + } + return cel_Expr_UpCast(expr); +} + +static CEL_NONNULL(cel_Expr*) _cel_AstFromProtoV1Alpha1_ListExpr( + CEL_NONNULL(_cel_AstFromProtoV1Alpha1State*) state, int64_t id, + CEL_NONNULL(const google_api_expr_v1alpha1_Expr_CreateList*) in) { + _cel_AstFromProtoV1Alpha1_CheckId(state, id); + CEL_NULLABLE(cel_ListExpr*) expr = cel_ListExpr_New(state->ast); + if (CEL_UNLIKELY(expr == cel_nullptr)) { + cel_OutOfMemoryStatus(state->status); + _cel_longjmp(state->jmp); + } + cel_Expr_SetId(cel_Expr_UpCast(expr), id); + _cel_AstFromProtoV1Alpha1_UpdatePosition(state, cel_Expr_UpCast(expr)); + _cel_AstFromProtoV1Alpha1_SetType(state, cel_Expr_UpCast(expr)); + _cel_AstFromProtoV1Alpha1_SetRef(state, cel_Expr_UpCast(expr)); + size_t in_opt_indices_len = 0; + const int32_t* in_opt_indices = + google_api_expr_v1alpha1_Expr_CreateList_optional_indices( + in, &in_opt_indices_len); + size_t in_elems_len = 0; + const google_api_expr_v1alpha1_Expr* const* in_elems = + google_api_expr_v1alpha1_Expr_CreateList_elements(in, &in_elems_len); + for (size_t i = 0; i < in_elems_len; ++i) { + CEL_NULLABLE(cel_ListElementExpr*) + elem = cel_ListElementExpr_New(state->ast); + if (CEL_UNLIKELY(elem == cel_nullptr)) { + cel_OutOfMemoryStatus(state->status); + _cel_longjmp(state->jmp); + } + CEL_NONNULL(cel_Expr*) + elem_value = _cel_AstFromProtoV1Alpha1_Expr(state, in_elems[i]); + cel_Expr_SetSourceRange(cel_Expr_UpCast(elem), + cel_Expr_SourceRange(elem_value)); + cel_ListElementExpr_SetValue(elem, elem_value); + bool optional = false; + for (size_t j = 0; j < in_opt_indices_len; ++j) { + int32_t in_opt_index = in_opt_indices[j]; + if (in_opt_index < 0) { + // Just skip for now. In future we should error. + continue; + } + if (((uint32_t)in_opt_index) == i) { + optional = true; + break; + } + } + cel_ListElementExpr_SetOptional(elem, optional); + cel_ListExpr_AppendElement(expr, elem); + } + return cel_Expr_UpCast(expr); +} + +static CEL_NONNULL(cel_Expr*) _cel_AstFromProtoV1Alpha1_MapExpr( + CEL_NONNULL(_cel_AstFromProtoV1Alpha1State*) state, int64_t id, + CEL_NONNULL(const google_api_expr_v1alpha1_Expr_CreateStruct*) in) { + _cel_AstFromProtoV1Alpha1_CheckId(state, id); + CEL_NULLABLE(cel_MapExpr*) expr = cel_MapExpr_New(state->ast); + if (CEL_UNLIKELY(expr == cel_nullptr)) { + cel_OutOfMemoryStatus(state->status); + _cel_longjmp(state->jmp); + } + cel_Expr_SetId(cel_Expr_UpCast(expr), id); + _cel_AstFromProtoV1Alpha1_UpdatePosition(state, cel_Expr_UpCast(expr)); + _cel_AstFromProtoV1Alpha1_SetType(state, cel_Expr_UpCast(expr)); + _cel_AstFromProtoV1Alpha1_SetRef(state, cel_Expr_UpCast(expr)); + size_t in_ents_len = 0; + const google_api_expr_v1alpha1_Expr_CreateStruct_Entry* const* in_ents = + google_api_expr_v1alpha1_Expr_CreateStruct_entries(in, &in_ents_len); + for (size_t i = 0; i < in_ents_len; ++i) { + const google_api_expr_v1alpha1_Expr_CreateStruct_Entry* in_ent = in_ents[i]; + const google_api_expr_v1alpha1_Expr_CreateStruct_Entry_key_kind_oneofcases + key_kind = + google_api_expr_v1alpha1_Expr_CreateStruct_Entry_key_kind_case( + in_ent); + if (key_kind != + google_api_expr_v1alpha1_Expr_CreateStruct_Entry_key_kind_map_key) { + cel_InvalidArgumentStatusF( + state->status, + "cel: expected " + "google.api.expr.v1alpha1.Expr.CreateStruct.Entry.key_kind to " + "be map_key: %d", + key_kind); + _cel_longjmp(state->jmp); + } + CEL_NONNULL(cel_MapEntryExpr*) ent = cel_MapEntryExpr_New(state->ast); + if (CEL_UNLIKELY(ent == cel_nullptr)) { + cel_OutOfMemoryStatus(state->status); + _cel_longjmp(state->jmp); + } + cel_Expr_SetId( + cel_Expr_UpCast(ent), + _cel_AstFromProtoV1Alpha1_CheckId( + state, + google_api_expr_v1alpha1_Expr_CreateStruct_Entry_id(in_ent))); + _cel_AstFromProtoV1Alpha1_UpdatePosition(state, cel_Expr_UpCast(ent)); + _cel_AstFromProtoV1Alpha1_SetType(state, cel_Expr_UpCast(ent)); + _cel_AstFromProtoV1Alpha1_SetRef(state, cel_Expr_UpCast(ent)); + cel_MapEntryExpr_SetKey( + ent, + _cel_AstFromProtoV1Alpha1_Expr( + state, + google_api_expr_v1alpha1_Expr_CreateStruct_Entry_map_key(in_ent))); + if (google_api_expr_v1alpha1_Expr_CreateStruct_Entry_has_value(in_ent)) { + cel_MapEntryExpr_SetValue( + ent, + _cel_AstFromProtoV1Alpha1_Expr( + state, + google_api_expr_v1alpha1_Expr_CreateStruct_Entry_value(in_ent))); + } + cel_MapEntryExpr_SetOptional( + ent, google_api_expr_v1alpha1_Expr_CreateStruct_Entry_optional_entry( + in_ent)); + cel_MapExpr_AppendEntry(expr, ent); + } + return cel_Expr_UpCast(expr); +} + +static CEL_NONNULL(cel_Expr*) _cel_AstFromProtoV1Alpha1_StructExpr( + CEL_NONNULL(_cel_AstFromProtoV1Alpha1State*) state, int64_t id, + CEL_NONNULL(const google_api_expr_v1alpha1_Expr_CreateStruct*) in) { + _cel_AstFromProtoV1Alpha1_CheckId(state, id); + CEL_NULLABLE(cel_StructExpr*) expr = cel_StructExpr_New(state->ast); + if (CEL_UNLIKELY(expr == cel_nullptr)) { + cel_OutOfMemoryStatus(state->status); + _cel_longjmp(state->jmp); + } + cel_Expr_SetId(cel_Expr_UpCast(expr), id); + _cel_AstFromProtoV1Alpha1_UpdatePosition(state, cel_Expr_UpCast(expr)); + _cel_AstFromProtoV1Alpha1_SetType(state, cel_Expr_UpCast(expr)); + _cel_AstFromProtoV1Alpha1_SetRef(state, cel_Expr_UpCast(expr)); + cel_StructExpr_SetName( + expr, + _cel_AstFromProtoV1Alpha1_StrDup( + state, google_api_expr_v1alpha1_Expr_CreateStruct_message_name(in))); + size_t in_flds_len = 0; + const google_api_expr_v1alpha1_Expr_CreateStruct_Entry* const* in_flds = + google_api_expr_v1alpha1_Expr_CreateStruct_entries(in, &in_flds_len); + for (size_t i = 0; i < in_flds_len; ++i) { + const google_api_expr_v1alpha1_Expr_CreateStruct_Entry* in_fld = in_flds[i]; + const google_api_expr_v1alpha1_Expr_CreateStruct_Entry_key_kind_oneofcases + key_kind = + google_api_expr_v1alpha1_Expr_CreateStruct_Entry_key_kind_case( + in_fld); + if (key_kind != + google_api_expr_v1alpha1_Expr_CreateStruct_Entry_key_kind_field_key) { + cel_InvalidArgumentStatusF( + state->status, + "cel: expected " + "google.api.expr.v1alpha1.Expr.CreateStruct.Entry.key_kind to " + "be field_key: %d", + key_kind); + _cel_longjmp(state->jmp); + } + CEL_NONNULL(cel_StructFieldExpr*) fld = cel_StructFieldExpr_New(state->ast); + if (CEL_UNLIKELY(fld == cel_nullptr)) { + cel_OutOfMemoryStatus(state->status); + _cel_longjmp(state->jmp); + } + cel_Expr_SetId( + cel_Expr_UpCast(fld), + _cel_AstFromProtoV1Alpha1_CheckId( + state, + google_api_expr_v1alpha1_Expr_CreateStruct_Entry_id(in_fld))); + _cel_AstFromProtoV1Alpha1_UpdatePosition(state, cel_Expr_UpCast(fld)); + _cel_AstFromProtoV1Alpha1_SetType(state, cel_Expr_UpCast(fld)); + _cel_AstFromProtoV1Alpha1_SetRef(state, cel_Expr_UpCast(fld)); + cel_StructFieldExpr_SetName( + fld, + _cel_AstFromProtoV1Alpha1_StrDup( + state, google_api_expr_v1alpha1_Expr_CreateStruct_Entry_field_key( + in_fld))); + if (google_api_expr_v1alpha1_Expr_CreateStruct_Entry_has_value(in_fld)) { + cel_StructFieldExpr_SetValue( + fld, + _cel_AstFromProtoV1Alpha1_Expr( + state, + google_api_expr_v1alpha1_Expr_CreateStruct_Entry_value(in_fld))); + } + cel_StructFieldExpr_SetOptional( + fld, google_api_expr_v1alpha1_Expr_CreateStruct_Entry_optional_entry( + in_fld)); + cel_StructExpr_AppendField(expr, fld); + } + return cel_Expr_UpCast(expr); +} + +static CEL_NONNULL(cel_Expr*) _cel_AstFromProtoV1Alpha1_ComprehensionExpr( + CEL_NONNULL(_cel_AstFromProtoV1Alpha1State*) state, int64_t id, + CEL_NONNULL(const google_api_expr_v1alpha1_Expr_Comprehension*) in) { + _cel_AstFromProtoV1Alpha1_CheckId(state, id); + CEL_NULLABLE(cel_ComprehensionExpr*) + expr = cel_ComprehensionExpr_New(state->ast); + if (CEL_UNLIKELY(expr == cel_nullptr)) { + cel_OutOfMemoryStatus(state->status); + _cel_longjmp(state->jmp); + } + cel_Expr_SetId(cel_Expr_UpCast(expr), id); + _cel_AstFromProtoV1Alpha1_UpdatePosition(state, cel_Expr_UpCast(expr)); + _cel_AstFromProtoV1Alpha1_SetType(state, cel_Expr_UpCast(expr)); + _cel_AstFromProtoV1Alpha1_SetRef(state, cel_Expr_UpCast(expr)); + cel_ComprehensionExpr_SetIterVar( + expr, + _cel_AstFromProtoV1Alpha1_StrDup( + state, google_api_expr_v1alpha1_Expr_Comprehension_iter_var(in))); + cel_ComprehensionExpr_SetIterVar2( + expr, + _cel_AstFromProtoV1Alpha1_StrDup( + state, google_api_expr_v1alpha1_Expr_Comprehension_iter_var2(in))); + cel_ComprehensionExpr_SetAccuVar( + expr, + _cel_AstFromProtoV1Alpha1_StrDup( + state, google_api_expr_v1alpha1_Expr_Comprehension_accu_var(in))); + if (google_api_expr_v1alpha1_Expr_Comprehension_has_iter_range(in)) { + cel_ComprehensionExpr_SetIterRange( + expr, + _cel_AstFromProtoV1Alpha1_Expr( + state, google_api_expr_v1alpha1_Expr_Comprehension_iter_range(in))); + } + if (google_api_expr_v1alpha1_Expr_Comprehension_has_accu_init(in)) { + cel_ComprehensionExpr_SetAccuInit( + expr, + _cel_AstFromProtoV1Alpha1_Expr( + state, google_api_expr_v1alpha1_Expr_Comprehension_accu_init(in))); + } + if (google_api_expr_v1alpha1_Expr_Comprehension_has_loop_condition(in)) { + cel_ComprehensionExpr_SetLoopCondition( + expr, + _cel_AstFromProtoV1Alpha1_Expr( + state, + google_api_expr_v1alpha1_Expr_Comprehension_loop_condition(in))); + } + if (google_api_expr_v1alpha1_Expr_Comprehension_has_loop_step(in)) { + cel_ComprehensionExpr_SetLoopStep( + expr, + _cel_AstFromProtoV1Alpha1_Expr( + state, google_api_expr_v1alpha1_Expr_Comprehension_loop_step(in))); + } + if (google_api_expr_v1alpha1_Expr_Comprehension_has_result(in)) { + cel_ComprehensionExpr_SetResult( + expr, + _cel_AstFromProtoV1Alpha1_Expr( + state, google_api_expr_v1alpha1_Expr_Comprehension_result(in))); + } + return cel_Expr_UpCast(expr); +} + +static CEL_NONNULL(cel_Expr*) _cel_AstFromProtoV1Alpha1_Expr( + CEL_NONNULL(_cel_AstFromProtoV1Alpha1State*) state, + CEL_NONNULL(const google_api_expr_v1alpha1_Expr*) in) { + google_api_expr_v1alpha1_Expr_expr_kind_oneofcases kind = + google_api_expr_v1alpha1_Expr_expr_kind_case(in); + switch (kind) { + case google_api_expr_v1alpha1_Expr_expr_kind_NOT_SET: + return _cel_AstFromProtoV1Alpha1_UnspecifiedExpr( + state, google_api_expr_v1alpha1_Expr_id(in)); + case google_api_expr_v1alpha1_Expr_expr_kind_ident_expr: + return _cel_AstFromProtoV1Alpha1_IdentExpr( + state, google_api_expr_v1alpha1_Expr_id(in), + google_api_expr_v1alpha1_Expr_ident_expr(in)); + case google_api_expr_v1alpha1_Expr_expr_kind_const_expr: + return _cel_AstFromProtoV1Alpha1_ConstExpr( + state, google_api_expr_v1alpha1_Expr_id(in), + google_api_expr_v1alpha1_Expr_const_expr(in)); + case google_api_expr_v1alpha1_Expr_expr_kind_select_expr: + return _cel_AstFromProtoV1Alpha1_SelectExpr( + state, google_api_expr_v1alpha1_Expr_id(in), + google_api_expr_v1alpha1_Expr_select_expr(in)); + case google_api_expr_v1alpha1_Expr_expr_kind_call_expr: + return _cel_AstFromProtoV1Alpha1_CallExpr( + state, google_api_expr_v1alpha1_Expr_id(in), + google_api_expr_v1alpha1_Expr_call_expr(in)); + case google_api_expr_v1alpha1_Expr_expr_kind_list_expr: + return _cel_AstFromProtoV1Alpha1_ListExpr( + state, google_api_expr_v1alpha1_Expr_id(in), + google_api_expr_v1alpha1_Expr_list_expr(in)); + case google_api_expr_v1alpha1_Expr_expr_kind_struct_expr: { + CEL_NONNULL(const google_api_expr_v1alpha1_Expr_CreateStruct*) + struct_expr = google_api_expr_v1alpha1_Expr_struct_expr(in); + if (cel_StringView_Empty( + google_api_expr_v1alpha1_Expr_CreateStruct_message_name( + struct_expr))) { + return _cel_AstFromProtoV1Alpha1_MapExpr( + state, google_api_expr_v1alpha1_Expr_id(in), struct_expr); + } + return _cel_AstFromProtoV1Alpha1_StructExpr( + state, google_api_expr_v1alpha1_Expr_id(in), struct_expr); + } + case google_api_expr_v1alpha1_Expr_expr_kind_comprehension_expr: + return _cel_AstFromProtoV1Alpha1_ComprehensionExpr( + state, google_api_expr_v1alpha1_Expr_id(in), + google_api_expr_v1alpha1_Expr_comprehension_expr(in)); + default: + cel_InvalidArgumentStatusF( + state->status, + "cel: unexpected google.api.expr.v1alpha1.Expr kind: %d", kind); + _cel_longjmp(state->jmp); + } +} + +extern "C" CEL_NULLABLE(cel_Ast*) cel_Ast_FromProtoV1Alpha1( + CEL_NONNULL(const google_api_expr_v1alpha1_CheckedExpr*) in, + CEL_NONNULL(cel_Arena*) arena, CEL_NONNULL(cel_Status*) status) { + CEL_ASSERT_NOT_NULL(in); + CEL_ASSERT_NOT_NULL(arena); + CEL_ASSERT_NOT_NULL(status); + + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return cel_nullptr; + } + CEL_NULLABLE(_cel_AstFromProtoV1Alpha1State*) + volatile state = (CEL_NULLABLE(_cel_AstFromProtoV1Alpha1State*))_cel_Malloc( + sizeof(_cel_AstFromProtoV1Alpha1State), cel_nullptr); + if (CEL_UNLIKELY(state == cel_nullptr)) { + cel_OutOfMemoryStatus(status); + return cel_nullptr; + } + CEL_NULLABLE(cel_Ast*) ast = cel_Ast_New(arena); + if (CEL_UNLIKELY(ast == cel_nullptr)) { + _cel_FreeSized(state, sizeof(_cel_AstFromProtoV1Alpha1State)); + cel_OutOfMemoryStatus(status); + return cel_nullptr; + } + state->ast = ast; + state->arena = arena; + state->status = status; + state->checked_expr = in; + state->source_info = cel_nullptr; + + if (_cel_setjmp(state->jmp)) { + // ERROR + CEL_ASSERT(!cel_Status_Ok(state->status)); + cel_Ast_Delete(state->ast); + _cel_FreeSized(state, sizeof(_cel_AstFromProtoV1Alpha1State)); + return cel_nullptr; + } else { + if (google_api_expr_v1alpha1_CheckedExpr_has_expr(in)) { + if (google_api_expr_v1alpha1_CheckedExpr_has_source_info(in)) { + state->source_info = + google_api_expr_v1alpha1_CheckedExpr_source_info(in); + } + cel_Ast_SetExpr( + state->ast, + _cel_AstFromProtoV1Alpha1_Expr( + state, google_api_expr_v1alpha1_CheckedExpr_expr(in))); + } + // OK + CEL_ASSERT(cel_Status_Ok(state->status)); + _cel_FreeSized(state, sizeof(_cel_AstFromProtoV1Alpha1State)); + return ast; + } +} diff --git a/cel-c/ast_traverse.cc b/cel-c/ast_traverse.cc new file mode 100644 index 0000000..d08fbae --- /dev/null +++ b/cel-c/ast_traverse.cc @@ -0,0 +1,1431 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "cel-c/ast_traverse.h" + +#include +#include +#include + +#include "cel-c/alloc.h" +#include "cel-c/assert.h" +#include "cel-c/ast.h" +#include "cel-c/ast_visitor.h" +#include "cel-c/config.h" +#include "cel-c/src/deque.h" +#include "cel-c/src/malloc.h" +#include "cel-c/status.h" +#include "cel-c/string_view.h" + +typedef enum CEL_ATTRIBUTE_CLOSED_ENUM { + _cel_AstVisitorControl_kStop = 0, + _cel_AstVisitorControl_kContinue, +} _cel_AstVisitorControl; + +typedef enum CEL_ATTRIBUTE_CLOSED_ENUM { + _cel_AstTraverserStep_kStepIn = 0, + _cel_AstTraverserStep_kStepOver, + _cel_AstTraverserStep_kStepOut, +} _cel_AstTraverserStep; + +#define _cel_AstVisitor_Invoke(visitor, member, ...) \ + ((((visitor)->vtable->member) != cel_nullptr) \ + ? ((*(visitor)->vtable->member)((visitor), ##__VA_ARGS__), \ + _cel_AstVisitorControl_kStop) \ + : _cel_AstVisitorControl_kContinue) + +#define _cel_AstVisitor_HasVisit(visitor, visit) \ + (((visitor)->vtable->PreVisit##visit) != cel_nullptr || \ + ((visitor)->vtable->PostVisit##visit) != cel_nullptr) + +typedef enum CEL_ATTRIBUTE_CLOSED_ENUM { + _cel_AstTraverserRecordKind_kExpr = 1, + _cel_AstTraverserRecordKind_kSelectOperand, + _cel_AstTraverserRecordKind_kCallTarget, + _cel_AstTraverserRecordKind_kCallArgValue, + _cel_AstTraverserRecordKind_kListElementValue, + _cel_AstTraverserRecordKind_kMapEntryKey, + _cel_AstTraverserRecordKind_kMapEntryValue, + _cel_AstTraverserRecordKind_kStructFieldValue, + _cel_AstTraverserRecordKind_kComprehensionIterRange, + _cel_AstTraverserRecordKind_kComprehensionAccuInit, + _cel_AstTraverserRecordKind_kComprehensionLoopCondition, + _cel_AstTraverserRecordKind_kComprehensionLoopStep, + _cel_AstTraverserRecordKind_kComprehensionResult, + _cel_AstTraverserRecordKind_kUnaryArg, + _cel_AstTraverserRecordKind_kBinaryLeft, + _cel_AstTraverserRecordKind_kBinaryRight, + _cel_AstTraverserRecordKind_kTernaryCondition, + _cel_AstTraverserRecordKind_kTernaryIfTrue, + _cel_AstTraverserRecordKind_kTernaryIfFalse, +} _cel_AstTraverserRecordKind; + +typedef struct { + CEL_NONNULL(const cel_Expr*) expr; + bool previsited; + bool pushed_deps; + bool postvisited; + bool previsited_expr; + bool postvisited_expr; +} _cel_AstTraverserRecordData; + +typedef struct { + _cel_AstTraverserRecordData data; + _cel_AstTraverserRecordKind kind; +} _cel_AstTraverserRecord; + +struct cel_AstTraverser { + _cel_Deque(_cel_AstTraverserRecord) records; + CEL_NONNULL(cel_AstVisitor*) visitor; + _cel_AstTraverserStep step; + bool stepped; + // Is the visitor interested in any {Pre,Post}VisitSelectExpr{...} callbacks? + bool select_operand_callbacks; + // Is the visitor interested in any {Pre,Post}VisitUnaryExpr{...} callbacks? + bool unary_arg_callbacks; + // Is the visitor interested in any {Pre,Post}VisitBinaryExpr{...} callbacks? + bool binary_left_callbacks; + bool binary_right_callbacks; + // Is the visitor interested in any {Pre,Post}VisitTernaryExpr{...} callbacks? + bool ternary_condition_callbacks; + bool ternary_if_true_callbacks; + bool ternary_if_false_callbacks; + // Is the visitor interested in any {Pre,Post}VisitCallExpr{...} callbacks? + bool call_target_callbacks; + // Is the visitor interested in any {Pre,Post}VisitCallArgExpr{...} callbacks? + bool call_arg_value_callbacks; + // Is the visitor interested in any {Pre,Post}VisitListElementExpr{...} + // callbacks? + bool list_element_value_callbacks; + // Is the visitor interested in any {Pre,Post}VisitMapEntryExpr{...} + // callbacks? + bool map_entry_key_callbacks; + bool map_entry_value_callbacks; + // Is the visitor interested in any {Pre,Post}VisitStructFieldExpr{...} + // callbacks? + bool struct_field_value_callbacks; + // Is the visitor interested in any {Pre,Post}VisitComprehensionExpr{...} + // callbacks? + bool comprehension_iter_range_callbacks; + bool comprehension_accu_init_callbacks; + bool comprehension_loop_condition_callbacks; + bool comprehension_loop_step_callbacks; + bool comprehension_result_callbacks; +}; + +CEL_ATTRIBUTE_NODISCARD +static CEL_NULLABLE(_cel_AstTraverserRecord*) + _cel_AstTraverser_Push(CEL_NONNULL(cel_AstTraverser*) traverser, + CEL_NULLABLE(cel_Status*) status) { + _cel_AstTraverserRecord* record = + _cel_Deque_PushBack(&traverser->records, cel_DefaultAllocator); + if (CEL_UNLIKELY(record == cel_nullptr)) { + if (status != cel_nullptr) { + cel_OutOfMemoryStatus(status); + } + return cel_nullptr; + } + memset(record, 0, sizeof(*record)); + return record; +} + +CEL_ATTRIBUTE_NODISCARD +static bool _cel_AstTraverser_PushExpr(CEL_NONNULL(cel_AstTraverser*) traverser, + CEL_NONNULL(const cel_Expr*) expr, + CEL_NULLABLE(cel_Status*) status) { + _cel_AstTraverserRecord* record = _cel_AstTraverser_Push(traverser, status); + if (CEL_UNLIKELY(record == cel_nullptr)) { + return false; + } + record->data.expr = expr; + record->kind = _cel_AstTraverserRecordKind_kExpr; + return true; +} + +CEL_ATTRIBUTE_NODISCARD +static CEL_NULLABLE(_cel_AstTraverserRecord*) + _cel_AstTraverser_Peek(CEL_NONNULL(cel_AstTraverser*) traverser) { + if (_cel_Deque_Empty(&traverser->records)) { + return cel_nullptr; + } + return _cel_Deque_MutablePeekBack(&traverser->records); +} + +static void _cel_AstTraverser_Pop(CEL_NONNULL(cel_AstTraverser*) traverser) { + _cel_Deque_PopBack(&traverser->records, cel_DefaultAllocator); +} + +CEL_ATTRIBUTE_NODISCARD +static _cel_AstVisitorControl _cel_AstTraverserRecord_PreVisit( + CEL_NONNULL(_cel_AstTraverserRecord*) record, + CEL_NONNULL(cel_AstTraverser*) traverser, CEL_NONNULL(cel_Status*) status) { + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return _cel_AstVisitorControl_kStop; + } + cel_AstVisitor* visitor = traverser->visitor; + CEL_NONNULL(const cel_Expr*) expr = record->data.expr; + const _cel_AstTraverserRecordKind record_kind = record->kind; + switch (record_kind) { + case _cel_AstTraverserRecordKind_kExpr: { + _cel_AstVisitorControl control = _cel_AstVisitorControl_kContinue; + if (!record->data.previsited_expr) { + control = _cel_AstVisitor_Invoke(visitor, PreVisitExpr, expr); + record->data.previsited_expr = true; + if (traverser->stepped) { + CEL_ASSERT_EQ(control, _cel_AstVisitorControl_kStop); + switch (traverser->step) { + case _cel_AstTraverserStep_kStepIn: + break; + case _cel_AstTraverserStep_kStepOver: + record->data.previsited = true; + record->data.pushed_deps = true; + record->data.postvisited = true; + break; + case _cel_AstTraverserStep_kStepOut: + record->data.previsited = true; + record->data.pushed_deps = true; + record->data.postvisited = true; + record->data.postvisited_expr = true; + break; + default: + CEL_UNREACHABLE(); + } + } + } + if (control != _cel_AstVisitorControl_kContinue) { + return control; + } + if (record->data.previsited) { + return _cel_AstVisitorControl_kContinue; + } + record->data.previsited = true; + switch (cel_Expr_Kind(expr)) { + case cel_ExprKind_kUnspecified: + control = _cel_AstVisitor_Invoke(visitor, VisitUnspecifiedExpr, + cel_UnspecifiedExpr_DownCast(expr)); + break; + case cel_ExprKind_kIdent: + control = _cel_AstVisitor_Invoke(visitor, VisitIdentExpr, + cel_IdentExpr_DownCast(expr)); + break; + case cel_ExprKind_kConst: + control = _cel_AstVisitor_Invoke(visitor, VisitConstExpr, + cel_ConstExpr_DownCast(expr)); + break; + case cel_ExprKind_kSelect: + control = _cel_AstVisitor_Invoke(visitor, PreVisitSelectExpr, + cel_SelectExpr_DownCast(expr)); + break; + case cel_ExprKind_kCallArg: + control = _cel_AstVisitor_Invoke(visitor, PreVisitCallArgExpr, + cel_CallArgExpr_DownCast(expr)); + break; + case cel_ExprKind_kCall: + control = _cel_AstVisitor_Invoke(visitor, PreVisitCallExpr, + cel_CallExpr_DownCast(expr)); + break; + case cel_ExprKind_kListElement: + control = _cel_AstVisitor_Invoke(visitor, PreVisitListElementExpr, + cel_ListElementExpr_DownCast(expr)); + break; + case cel_ExprKind_kList: + control = _cel_AstVisitor_Invoke(visitor, PreVisitListExpr, + cel_ListExpr_DownCast(expr)); + break; + case cel_ExprKind_kMapEntry: + control = _cel_AstVisitor_Invoke(visitor, PreVisitMapEntryExpr, + cel_MapEntryExpr_DownCast(expr)); + break; + case cel_ExprKind_kMap: + control = _cel_AstVisitor_Invoke(visitor, PreVisitMapExpr, + cel_MapExpr_DownCast(expr)); + break; + case cel_ExprKind_kStructField: + control = _cel_AstVisitor_Invoke(visitor, PreVisitStructFieldExpr, + cel_StructFieldExpr_DownCast(expr)); + break; + case cel_ExprKind_kStruct: + control = _cel_AstVisitor_Invoke(visitor, PreVisitStructExpr, + cel_StructExpr_DownCast(expr)); + break; + case cel_ExprKind_kComprehension: + control = + _cel_AstVisitor_Invoke(visitor, PreVisitComprehensionExpr, + cel_ComprehensionExpr_DownCast(expr)); + break; + case cel_ExprKind_kUnary: + control = _cel_AstVisitor_Invoke(visitor, PreVisitUnaryExpr, + cel_UnaryExpr_DownCast(expr)); + break; + case cel_ExprKind_kBinary: + control = _cel_AstVisitor_Invoke(visitor, PreVisitBinaryExpr, + cel_BinaryExpr_DownCast(expr)); + break; + case cel_ExprKind_kTernary: + control = _cel_AstVisitor_Invoke(visitor, PreVisitTernaryExpr, + cel_TernaryExpr_DownCast(expr)); + break; + default: + cel_InternalStatus(status, + cel_StringView_From("unexpected AST node kind")); + control = _cel_AstVisitorControl_kStop; + break; + } + if (traverser->stepped) { + CEL_ASSERT_EQ(control, _cel_AstVisitorControl_kStop); + switch (traverser->step) { + case _cel_AstTraverserStep_kStepIn: + break; + case _cel_AstTraverserStep_kStepOver: + record->data.pushed_deps = true; + break; + case _cel_AstTraverserStep_kStepOut: + record->data.pushed_deps = true; + record->data.postvisited = true; + break; + default: + CEL_UNREACHABLE(); + } + } + return control; + } + default: + record->data.previsited = record->data.previsited_expr = true; + break; + } + _cel_AstVisitorControl control = _cel_AstVisitorControl_kContinue; + switch (record_kind) { + case _cel_AstTraverserRecordKind_kSelectOperand: { + control = + _cel_AstVisitor_Invoke(visitor, PreVisitSelectExprOperand, expr); + break; + } + case _cel_AstTraverserRecordKind_kCallTarget: { + control = _cel_AstVisitor_Invoke(visitor, PreVisitCallExprTarget, expr); + break; + } + case _cel_AstTraverserRecordKind_kCallArgValue: { + control = _cel_AstVisitor_Invoke(visitor, PreVisitCallArgExprValue, expr); + break; + } + case _cel_AstTraverserRecordKind_kListElementValue: { + control = + _cel_AstVisitor_Invoke(visitor, PreVisitListElementExprValue, expr); + break; + } + case _cel_AstTraverserRecordKind_kMapEntryKey: { + control = _cel_AstVisitor_Invoke(visitor, PreVisitMapEntryExprKey, expr); + break; + } + case _cel_AstTraverserRecordKind_kMapEntryValue: { + control = + _cel_AstVisitor_Invoke(visitor, PreVisitMapEntryExprValue, expr); + break; + } + case _cel_AstTraverserRecordKind_kStructFieldValue: { + control = + _cel_AstVisitor_Invoke(visitor, PreVisitStructFieldExprValue, expr); + break; + } + case _cel_AstTraverserRecordKind_kComprehensionIterRange: { + control = _cel_AstVisitor_Invoke( + visitor, PreVisitComprehensionExprIterRange, expr); + break; + } + case _cel_AstTraverserRecordKind_kComprehensionAccuInit: { + control = _cel_AstVisitor_Invoke(visitor, + PreVisitComprehensionExprAccuInit, expr); + break; + } + case _cel_AstTraverserRecordKind_kComprehensionLoopCondition: { + control = _cel_AstVisitor_Invoke( + visitor, PreVisitComprehensionExprLoopCondition, expr); + break; + } + case _cel_AstTraverserRecordKind_kComprehensionLoopStep: { + control = _cel_AstVisitor_Invoke(visitor, + PreVisitComprehensionExprLoopStep, expr); + break; + } + case _cel_AstTraverserRecordKind_kComprehensionResult: { + control = _cel_AstVisitor_Invoke(visitor, PreVisitComprehensionExprResult, + expr); + break; + } + case _cel_AstTraverserRecordKind_kUnaryArg: { + control = _cel_AstVisitor_Invoke(visitor, PreVisitUnaryExprArg, expr); + break; + } + case _cel_AstTraverserRecordKind_kBinaryLeft: { + control = _cel_AstVisitor_Invoke(visitor, PreVisitBinaryExprLeft, expr); + break; + } + case _cel_AstTraverserRecordKind_kBinaryRight: { + control = _cel_AstVisitor_Invoke(visitor, PreVisitBinaryExprRight, expr); + break; + } + case _cel_AstTraverserRecordKind_kTernaryCondition: { + control = + _cel_AstVisitor_Invoke(visitor, PreVisitTernaryExprCondition, expr); + break; + } + case _cel_AstTraverserRecordKind_kTernaryIfTrue: { + control = + _cel_AstVisitor_Invoke(visitor, PreVisitTernaryExprIfTrue, expr); + break; + } + case _cel_AstTraverserRecordKind_kTernaryIfFalse: { + control = + _cel_AstVisitor_Invoke(visitor, PreVisitTernaryExprIfFalse, expr); + break; + } + default: + CEL_UNREACHABLE(); + } + if (traverser->stepped) { + CEL_ASSERT_EQ(control, _cel_AstVisitorControl_kStop); + switch (traverser->step) { + case _cel_AstTraverserStep_kStepIn: + break; + case _cel_AstTraverserStep_kStepOver: + record->data.pushed_deps = true; + break; + case _cel_AstTraverserStep_kStepOut: + record->data.pushed_deps = true; + record->data.postvisited = true; + record->data.postvisited_expr = true; + break; + default: + CEL_UNREACHABLE(); + } + } + return control; +} + +CEL_ATTRIBUTE_NODISCARD +static _cel_AstVisitorControl _cel_AstTraverserRecord_PostVisit( + CEL_NONNULL(_cel_AstTraverserRecord*) record, + CEL_NONNULL(cel_AstTraverser*) traverser, CEL_NONNULL(cel_Status*) status) { + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return _cel_AstVisitorControl_kStop; + } + cel_AstVisitor* visitor = traverser->visitor; + CEL_NONNULL(const cel_Expr*) expr = record->data.expr; + const _cel_AstTraverserRecordKind record_kind = record->kind; + switch (record_kind) { + case _cel_AstTraverserRecordKind_kExpr: { + _cel_AstVisitorControl control = _cel_AstVisitorControl_kContinue; + if (!record->data.postvisited) { + record->data.postvisited = true; + switch (cel_Expr_Kind(expr)) { + case cel_ExprKind_kUnspecified: + CEL_ATTRIBUTE_FALLTHROUGH; + case cel_ExprKind_kIdent: + CEL_ATTRIBUTE_FALLTHROUGH; + case cel_ExprKind_kConst: + break; + case cel_ExprKind_kSelect: + control = _cel_AstVisitor_Invoke(visitor, PostVisitSelectExpr, + cel_SelectExpr_DownCast(expr)); + break; + case cel_ExprKind_kCallArg: + control = _cel_AstVisitor_Invoke(visitor, PostVisitCallArgExpr, + cel_CallArgExpr_DownCast(expr)); + break; + case cel_ExprKind_kCall: + control = _cel_AstVisitor_Invoke(visitor, PostVisitCallExpr, + cel_CallExpr_DownCast(expr)); + break; + case cel_ExprKind_kListElement: + control = + _cel_AstVisitor_Invoke(visitor, PostVisitListElementExpr, + cel_ListElementExpr_DownCast(expr)); + break; + case cel_ExprKind_kList: + control = _cel_AstVisitor_Invoke(visitor, PostVisitListExpr, + cel_ListExpr_DownCast(expr)); + break; + case cel_ExprKind_kMapEntry: + control = _cel_AstVisitor_Invoke(visitor, PostVisitMapEntryExpr, + cel_MapEntryExpr_DownCast(expr)); + break; + case cel_ExprKind_kMap: + control = _cel_AstVisitor_Invoke(visitor, PostVisitMapExpr, + cel_MapExpr_DownCast(expr)); + break; + case cel_ExprKind_kStructField: + control = + _cel_AstVisitor_Invoke(visitor, PostVisitStructFieldExpr, + cel_StructFieldExpr_DownCast(expr)); + break; + case cel_ExprKind_kStruct: + control = _cel_AstVisitor_Invoke(visitor, PostVisitStructExpr, + cel_StructExpr_DownCast(expr)); + break; + case cel_ExprKind_kComprehension: + control = + _cel_AstVisitor_Invoke(visitor, PostVisitComprehensionExpr, + cel_ComprehensionExpr_DownCast(expr)); + break; + case cel_ExprKind_kUnary: + control = _cel_AstVisitor_Invoke(visitor, PostVisitUnaryExpr, + cel_UnaryExpr_DownCast(expr)); + break; + case cel_ExprKind_kBinary: + control = _cel_AstVisitor_Invoke(visitor, PostVisitBinaryExpr, + cel_BinaryExpr_DownCast(expr)); + break; + case cel_ExprKind_kTernary: + control = _cel_AstVisitor_Invoke(visitor, PostVisitTernaryExpr, + cel_TernaryExpr_DownCast(expr)); + break; + default: + record->data.postvisited = false; + cel_InternalStatus(status, + cel_StringView_From("unexpected AST node kind")); + control = _cel_AstVisitorControl_kStop; + break; + } + } + if (control != _cel_AstVisitorControl_kContinue) { + return control; + } + if (record->data.postvisited_expr) { + return _cel_AstVisitorControl_kContinue; + } + record->data.postvisited_expr = true; + return _cel_AstVisitor_Invoke(visitor, PostVisitExpr, expr); + } + default: + if (record->data.postvisited || record->data.postvisited_expr) { + record->data.postvisited = record->data.postvisited_expr = true; + return _cel_AstVisitorControl_kContinue; + } + record->data.postvisited = record->data.postvisited_expr = true; + break; + } + _cel_AstVisitorControl control = _cel_AstVisitorControl_kContinue; + switch (record_kind) { + case _cel_AstTraverserRecordKind_kSelectOperand: { + control = + _cel_AstVisitor_Invoke(visitor, PostVisitSelectExprOperand, expr); + break; + } + case _cel_AstTraverserRecordKind_kCallTarget: { + control = _cel_AstVisitor_Invoke(visitor, PostVisitCallExprTarget, expr); + break; + } + case _cel_AstTraverserRecordKind_kCallArgValue: { + control = + _cel_AstVisitor_Invoke(visitor, PostVisitCallArgExprValue, expr); + break; + } + case _cel_AstTraverserRecordKind_kListElementValue: { + control = + _cel_AstVisitor_Invoke(visitor, PostVisitListElementExprValue, expr); + break; + } + case _cel_AstTraverserRecordKind_kMapEntryKey: { + control = _cel_AstVisitor_Invoke(visitor, PostVisitMapEntryExprKey, expr); + break; + } + case _cel_AstTraverserRecordKind_kMapEntryValue: { + control = + _cel_AstVisitor_Invoke(visitor, PostVisitMapEntryExprValue, expr); + break; + } + case _cel_AstTraverserRecordKind_kStructFieldValue: { + control = + _cel_AstVisitor_Invoke(visitor, PostVisitStructFieldExprValue, expr); + break; + } + case _cel_AstTraverserRecordKind_kComprehensionIterRange: { + control = _cel_AstVisitor_Invoke( + visitor, PostVisitComprehensionExprIterRange, expr); + break; + } + case _cel_AstTraverserRecordKind_kComprehensionAccuInit: { + control = _cel_AstVisitor_Invoke( + visitor, PostVisitComprehensionExprAccuInit, expr); + break; + } + case _cel_AstTraverserRecordKind_kComprehensionLoopCondition: { + control = _cel_AstVisitor_Invoke( + visitor, PostVisitComprehensionExprLoopCondition, expr); + break; + } + case _cel_AstTraverserRecordKind_kComprehensionLoopStep: { + control = _cel_AstVisitor_Invoke( + visitor, PostVisitComprehensionExprLoopStep, expr); + break; + } + case _cel_AstTraverserRecordKind_kComprehensionResult: { + control = _cel_AstVisitor_Invoke(visitor, + PostVisitComprehensionExprResult, expr); + break; + } + case _cel_AstTraverserRecordKind_kUnaryArg: { + control = _cel_AstVisitor_Invoke(visitor, PostVisitUnaryExprArg, expr); + break; + } + case _cel_AstTraverserRecordKind_kBinaryLeft: { + control = _cel_AstVisitor_Invoke(visitor, PostVisitBinaryExprLeft, expr); + break; + } + case _cel_AstTraverserRecordKind_kBinaryRight: { + control = _cel_AstVisitor_Invoke(visitor, PostVisitBinaryExprRight, expr); + break; + } + case _cel_AstTraverserRecordKind_kTernaryCondition: { + control = + _cel_AstVisitor_Invoke(visitor, PostVisitTernaryExprCondition, expr); + break; + } + case _cel_AstTraverserRecordKind_kTernaryIfFalse: { + control = + _cel_AstVisitor_Invoke(visitor, PostVisitTernaryExprIfFalse, expr); + break; + } + case _cel_AstTraverserRecordKind_kTernaryIfTrue: { + control = + _cel_AstVisitor_Invoke(visitor, PostVisitTernaryExprIfTrue, expr); + break; + } + default: + CEL_UNREACHABLE(); + } + if (traverser->stepped) { + CEL_ASSERT_EQ(control, _cel_AstVisitorControl_kStop); + switch (traverser->step) { + case _cel_AstTraverserStep_kStepIn: + break; + case _cel_AstTraverserStep_kStepOver: + CEL_ATTRIBUTE_FALLTHROUGH; + case _cel_AstTraverserStep_kStepOut: + record->data.postvisited = true; + record->data.postvisited_expr = true; + break; + default: + CEL_UNREACHABLE(); + } + } + return control; +} + +CEL_ATTRIBUTE_NODISCARD +static bool _cel_AstTraverser_PushSelectOperand(CEL_NONNULL(cel_AstTraverser*) + traverser, + CEL_NONNULL(const cel_Expr*) + select_operand, + CEL_NONNULL(cel_Status*) + status) { + _cel_AstTraverserRecord* record = _cel_AstTraverser_Push(traverser, status); + if (CEL_UNLIKELY(record == cel_nullptr)) { + return false; + } + record->data.expr = select_operand; + record->kind = traverser->select_operand_callbacks + ? _cel_AstTraverserRecordKind_kSelectOperand + : _cel_AstTraverserRecordKind_kExpr; + return true; +} + +CEL_ATTRIBUTE_NODISCARD +static bool _cel_AstTraverser_PushCallTarget(CEL_NONNULL(cel_AstTraverser*) + traverser, + CEL_NONNULL(const cel_Expr*) + call_target, + CEL_NONNULL(cel_Status*) status) { + _cel_AstTraverserRecord* record = _cel_AstTraverser_Push(traverser, status); + if (CEL_UNLIKELY(record == cel_nullptr)) { + return false; + } + record->data.expr = call_target; + record->kind = traverser->call_target_callbacks + ? _cel_AstTraverserRecordKind_kCallTarget + : _cel_AstTraverserRecordKind_kExpr; + return true; +} + +CEL_ATTRIBUTE_NODISCARD +static bool _cel_AstTraverser_PushCallArgValue(CEL_NONNULL(cel_AstTraverser*) + traverser, + CEL_NONNULL(const cel_Expr*) + call_arg_value, + CEL_NONNULL(cel_Status*) + status) { + _cel_AstTraverserRecord* record = _cel_AstTraverser_Push(traverser, status); + if (CEL_UNLIKELY(record == cel_nullptr)) { + return false; + } + record->data.expr = call_arg_value; + record->kind = traverser->call_arg_value_callbacks + ? _cel_AstTraverserRecordKind_kCallArgValue + : _cel_AstTraverserRecordKind_kExpr; + return true; +} + +CEL_ATTRIBUTE_NODISCARD +static bool _cel_AstTraverser_PushListElementValue( + CEL_NONNULL(cel_AstTraverser*) traverser, + CEL_NONNULL(const cel_Expr*) list_element_value, + CEL_NONNULL(cel_Status*) status) { + _cel_AstTraverserRecord* record = _cel_AstTraverser_Push(traverser, status); + if (CEL_UNLIKELY(record == cel_nullptr)) { + return false; + } + record->data.expr = list_element_value; + record->kind = traverser->list_element_value_callbacks + ? _cel_AstTraverserRecordKind_kListElementValue + : _cel_AstTraverserRecordKind_kExpr; + return true; +} + +CEL_ATTRIBUTE_NODISCARD +static bool _cel_AstTraverser_PushMapEntryKey(CEL_NONNULL(cel_AstTraverser*) + traverser, + CEL_NONNULL(const cel_Expr*) + map_entry_key, + CEL_NONNULL(cel_Status*) status) { + _cel_AstTraverserRecord* record = _cel_AstTraverser_Push(traverser, status); + if (CEL_UNLIKELY(record == cel_nullptr)) { + return false; + } + record->data.expr = map_entry_key; + record->kind = traverser->map_entry_key_callbacks + ? _cel_AstTraverserRecordKind_kMapEntryKey + : _cel_AstTraverserRecordKind_kExpr; + return true; +} + +CEL_ATTRIBUTE_NODISCARD +static bool _cel_AstTraverser_PushMapEntryValue(CEL_NONNULL(cel_AstTraverser*) + traverser, + CEL_NONNULL(const cel_Expr*) + map_entry_value, + CEL_NONNULL(cel_Status*) + status) { + _cel_AstTraverserRecord* record = _cel_AstTraverser_Push(traverser, status); + if (CEL_UNLIKELY(record == cel_nullptr)) { + return false; + } + record->data.expr = map_entry_value; + record->kind = traverser->map_entry_value_callbacks + ? _cel_AstTraverserRecordKind_kMapEntryValue + : _cel_AstTraverserRecordKind_kExpr; + return true; +} + +CEL_ATTRIBUTE_NODISCARD +static bool _cel_AstTraverser_PushStructFieldValue( + CEL_NONNULL(cel_AstTraverser*) traverser, + CEL_NONNULL(const cel_Expr*) struct_field_value, + CEL_NONNULL(cel_Status*) status) { + _cel_AstTraverserRecord* record = _cel_AstTraverser_Push(traverser, status); + if (CEL_UNLIKELY(record == cel_nullptr)) { + return false; + } + record->data.expr = struct_field_value; + record->kind = traverser->struct_field_value_callbacks + ? _cel_AstTraverserRecordKind_kStructFieldValue + : _cel_AstTraverserRecordKind_kExpr; + return true; +} + +CEL_ATTRIBUTE_NODISCARD +static bool _cel_AstTraverser_PushComprehensionIterRange( + CEL_NONNULL(cel_AstTraverser*) traverser, + CEL_NONNULL(const cel_Expr*) iter_range, CEL_NONNULL(cel_Status*) status) { + _cel_AstTraverserRecord* record = _cel_AstTraverser_Push(traverser, status); + if (CEL_UNLIKELY(record == cel_nullptr)) { + return false; + } + record->data.expr = iter_range; + record->kind = traverser->comprehension_iter_range_callbacks + ? _cel_AstTraverserRecordKind_kComprehensionIterRange + : _cel_AstTraverserRecordKind_kExpr; + return true; +} + +CEL_ATTRIBUTE_NODISCARD +static bool _cel_AstTraverser_PushComprehensionAccuInit( + CEL_NONNULL(cel_AstTraverser*) traverser, + CEL_NONNULL(const cel_Expr*) accu_init, CEL_NONNULL(cel_Status*) status) { + _cel_AstTraverserRecord* record = _cel_AstTraverser_Push(traverser, status); + if (CEL_UNLIKELY(record == cel_nullptr)) { + return false; + } + record->data.expr = accu_init; + record->kind = traverser->comprehension_accu_init_callbacks + ? _cel_AstTraverserRecordKind_kComprehensionAccuInit + : _cel_AstTraverserRecordKind_kExpr; + return true; +} + +CEL_ATTRIBUTE_NODISCARD +static bool _cel_AstTraverser_PushComprehensionLoopCondition( + CEL_NONNULL(cel_AstTraverser*) traverser, + CEL_NONNULL(const cel_Expr*) loop_condition, + CEL_NONNULL(cel_Status*) status) { + _cel_AstTraverserRecord* record = _cel_AstTraverser_Push(traverser, status); + if (CEL_UNLIKELY(record == cel_nullptr)) { + return false; + } + record->data.expr = loop_condition; + record->kind = traverser->comprehension_loop_condition_callbacks + ? _cel_AstTraverserRecordKind_kComprehensionLoopCondition + : _cel_AstTraverserRecordKind_kExpr; + return true; +} + +CEL_ATTRIBUTE_NODISCARD +static bool _cel_AstTraverser_PushComprehensionLoopStep( + CEL_NONNULL(cel_AstTraverser*) traverser, + CEL_NONNULL(const cel_Expr*) loop_step, CEL_NONNULL(cel_Status*) status) { + _cel_AstTraverserRecord* record = _cel_AstTraverser_Push(traverser, status); + if (CEL_UNLIKELY(record == cel_nullptr)) { + return false; + } + record->data.expr = loop_step; + record->kind = traverser->comprehension_loop_step_callbacks + ? _cel_AstTraverserRecordKind_kComprehensionLoopStep + : _cel_AstTraverserRecordKind_kExpr; + return true; +} + +CEL_ATTRIBUTE_NODISCARD +static bool _cel_AstTraverser_PushComprehensionResult( + CEL_NONNULL(cel_AstTraverser*) traverser, + CEL_NONNULL(const cel_Expr*) result, CEL_NONNULL(cel_Status*) status) { + _cel_AstTraverserRecord* record = _cel_AstTraverser_Push(traverser, status); + if (CEL_UNLIKELY(record == cel_nullptr)) { + return false; + } + record->data.expr = result; + record->kind = traverser->comprehension_result_callbacks + ? _cel_AstTraverserRecordKind_kComprehensionResult + : _cel_AstTraverserRecordKind_kExpr; + return true; +} + +CEL_ATTRIBUTE_NODISCARD +static bool _cel_AstTraverser_PushUnaryArg(CEL_NONNULL(cel_AstTraverser*) + traverser, + CEL_NONNULL(const cel_Expr*) result, + CEL_NONNULL(cel_Status*) status) { + _cel_AstTraverserRecord* record = _cel_AstTraverser_Push(traverser, status); + if (CEL_UNLIKELY(record == cel_nullptr)) { + return false; + } + record->data.expr = result; + record->kind = traverser->unary_arg_callbacks + ? _cel_AstTraverserRecordKind_kUnaryArg + : _cel_AstTraverserRecordKind_kExpr; + return true; +} + +CEL_ATTRIBUTE_NODISCARD +static bool _cel_AstTraverser_PushBinaryLeft(CEL_NONNULL(cel_AstTraverser*) + traverser, + CEL_NONNULL(const cel_Expr*) + result, + CEL_NONNULL(cel_Status*) status) { + _cel_AstTraverserRecord* record = _cel_AstTraverser_Push(traverser, status); + if (CEL_UNLIKELY(record == cel_nullptr)) { + return false; + } + record->data.expr = result; + record->kind = traverser->binary_left_callbacks + ? _cel_AstTraverserRecordKind_kBinaryLeft + : _cel_AstTraverserRecordKind_kExpr; + return true; +} + +CEL_ATTRIBUTE_NODISCARD +static bool _cel_AstTraverser_PushBinaryRight(CEL_NONNULL(cel_AstTraverser*) + traverser, + CEL_NONNULL(const cel_Expr*) + result, + CEL_NONNULL(cel_Status*) status) { + _cel_AstTraverserRecord* record = _cel_AstTraverser_Push(traverser, status); + if (CEL_UNLIKELY(record == cel_nullptr)) { + return false; + } + record->data.expr = result; + record->kind = traverser->binary_right_callbacks + ? _cel_AstTraverserRecordKind_kBinaryRight + : _cel_AstTraverserRecordKind_kExpr; + return true; +} + +CEL_ATTRIBUTE_NODISCARD +static bool _cel_AstTraverser_PushTernaryCondition( + CEL_NONNULL(cel_AstTraverser*) traverser, + CEL_NONNULL(const cel_Expr*) result, CEL_NONNULL(cel_Status*) status) { + _cel_AstTraverserRecord* record = _cel_AstTraverser_Push(traverser, status); + if (CEL_UNLIKELY(record == cel_nullptr)) { + return false; + } + record->data.expr = result; + record->kind = traverser->ternary_condition_callbacks + ? _cel_AstTraverserRecordKind_kTernaryCondition + : _cel_AstTraverserRecordKind_kExpr; + return true; +} + +CEL_ATTRIBUTE_NODISCARD +static bool _cel_AstTraverser_PushTernaryIfTrue( + CEL_NONNULL(cel_AstTraverser*) traverser, + CEL_NONNULL(const cel_Expr*) result, CEL_NONNULL(cel_Status*) status) { + _cel_AstTraverserRecord* record = _cel_AstTraverser_Push(traverser, status); + if (CEL_UNLIKELY(record == cel_nullptr)) { + return false; + } + record->data.expr = result; + record->kind = traverser->ternary_if_true_callbacks + ? _cel_AstTraverserRecordKind_kTernaryIfTrue + : _cel_AstTraverserRecordKind_kExpr; + return true; +} + +CEL_ATTRIBUTE_NODISCARD +static bool _cel_AstTraverser_PushTernaryIfFalse( + CEL_NONNULL(cel_AstTraverser*) traverser, + CEL_NONNULL(const cel_Expr*) result, CEL_NONNULL(cel_Status*) status) { + _cel_AstTraverserRecord* record = _cel_AstTraverser_Push(traverser, status); + if (CEL_UNLIKELY(record == cel_nullptr)) { + return false; + } + record->data.expr = result; + record->kind = traverser->ternary_if_false_callbacks + ? _cel_AstTraverserRecordKind_kTernaryIfFalse + : _cel_AstTraverserRecordKind_kExpr; + return true; +} + +CEL_ATTRIBUTE_NODISCARD +static bool _cel_AstTraverser_PushSelectDeps(CEL_NONNULL(cel_AstTraverser*) + traverser, + const cel_SelectExpr* select_expr, + CEL_NONNULL(cel_Status*) status) { + CEL_NULLABLE(cel_Expr*) operand = cel_SelectExpr_Operand(select_expr); + if (operand != cel_nullptr) { + if (!_cel_AstTraverser_PushSelectOperand(traverser, operand, status)) { + return false; + } + } + return true; +} + +CEL_ATTRIBUTE_NODISCARD +static bool _cel_AstTraverser_PushCallArgDeps( + CEL_NONNULL(cel_AstTraverser*) traverser, + const cel_CallArgExpr* call_arg_expr, CEL_NONNULL(cel_Status*) status) { + CEL_NULLABLE(cel_Expr*) value = cel_CallArgExpr_Value(call_arg_expr); + if (value != cel_nullptr) { + if (!_cel_AstTraverser_PushCallArgValue(traverser, value, status)) { + return false; + } + } + return true; +} + +CEL_ATTRIBUTE_NODISCARD +static bool _cel_AstTraverser_PushCallDeps(CEL_NONNULL(cel_AstTraverser*) + traverser, + const cel_CallExpr* call_expr, + CEL_NONNULL(cel_Status*) status) { + CEL_NULLABLE(cel_CallArgExpr*) arg; + size_t call_expr_args_size = + cel_CallExpr_Args(call_expr, /*head=*/cel_nullptr, &arg); + for (size_t i = call_expr_args_size; i > 0; --i) { + if (!_cel_AstTraverser_PushExpr(traverser, cel_Expr_UpCast(arg), status)) { + return false; + } + arg = cel_CallExpr_PrevArg(arg); + } + CEL_NULLABLE(cel_Expr*) target = cel_CallExpr_Target(call_expr); + if (target != cel_nullptr) { + if (!_cel_AstTraverser_PushCallTarget(traverser, target, status)) { + return false; + } + } + return true; +} + +CEL_ATTRIBUTE_NODISCARD +static bool _cel_AstTraverser_PushListElementDeps( + CEL_NONNULL(cel_AstTraverser*) traverser, + const cel_ListElementExpr* list_element_expr, + CEL_NONNULL(cel_Status*) status) { + CEL_NULLABLE(cel_Expr*) value = cel_ListElementExpr_Value(list_element_expr); + if (value != cel_nullptr) { + if (!_cel_AstTraverser_PushListElementValue(traverser, value, status)) { + return false; + } + } + return true; +} + +CEL_ATTRIBUTE_NODISCARD +static bool _cel_AstTraverser_PushListDeps(CEL_NONNULL(cel_AstTraverser*) + traverser, + const cel_ListExpr* list_expr, + CEL_NONNULL(cel_Status*) status) { + CEL_NULLABLE(cel_ListElementExpr*) elem; + size_t list_expr_elements_size = + cel_ListExpr_Elements(list_expr, /*head=*/cel_nullptr, &elem); + for (size_t i = list_expr_elements_size; i > 0; --i) { + if (!_cel_AstTraverser_PushExpr(traverser, cel_Expr_UpCast(elem), status)) { + return false; + } + elem = cel_ListExpr_PrevElement(elem); + } + return true; +} + +CEL_ATTRIBUTE_NODISCARD +static bool _cel_AstTraverser_PushMapEntryDeps( + CEL_NONNULL(cel_AstTraverser*) traverser, + const cel_MapEntryExpr* map_entry_expr, CEL_NONNULL(cel_Status*) status) { + CEL_NULLABLE(cel_Expr*) value = cel_MapEntryExpr_Value(map_entry_expr); + if (value != cel_nullptr) { + if (!_cel_AstTraverser_PushMapEntryValue(traverser, value, status)) { + return false; + } + } + CEL_NULLABLE(cel_Expr*) key = cel_MapEntryExpr_Key(map_entry_expr); + if (key != cel_nullptr) { + if (!_cel_AstTraverser_PushMapEntryKey(traverser, key, status)) { + return false; + } + } + return true; +} + +CEL_ATTRIBUTE_NODISCARD +static bool _cel_AstTraverser_PushMapDeps(CEL_NONNULL(cel_AstTraverser*) + traverser, + const cel_MapExpr* map_expr, + CEL_NONNULL(cel_Status*) status) { + CEL_NULLABLE(cel_MapEntryExpr*) ent; + size_t struct_expr_entries_size = + cel_MapExpr_Entries(map_expr, /*head=*/cel_nullptr, &ent); + for (size_t i = struct_expr_entries_size; i > 0; --i) { + if (!_cel_AstTraverser_PushExpr(traverser, cel_Expr_UpCast(ent), status)) { + return false; + } + ent = cel_MapExpr_PrevEntry(ent); + } + return true; +} + +CEL_ATTRIBUTE_NODISCARD +static bool _cel_AstTraverser_PushStructFieldDeps( + CEL_NONNULL(cel_AstTraverser*) traverser, + const cel_StructFieldExpr* struct_field_expr, + CEL_NONNULL(cel_Status*) status) { + CEL_NULLABLE(cel_Expr*) value = cel_StructFieldExpr_Value(struct_field_expr); + if (value != cel_nullptr) { + if (!_cel_AstTraverser_PushStructFieldValue(traverser, value, status)) { + return false; + } + } + return true; +} + +CEL_ATTRIBUTE_NODISCARD +static bool _cel_AstTraverser_PushStructDeps(CEL_NONNULL(cel_AstTraverser*) + traverser, + const cel_StructExpr* struct_expr, + CEL_NONNULL(cel_Status*) status) { + CEL_NULLABLE(cel_StructFieldExpr*) fld; + size_t struct_expr_entries_size = + cel_StructExpr_Fields(struct_expr, /*head=*/cel_nullptr, &fld); + for (size_t i = struct_expr_entries_size; i > 0; --i) { + if (!_cel_AstTraverser_PushExpr(traverser, cel_Expr_UpCast(fld), status)) { + return false; + } + fld = cel_StructExpr_PrevField(fld); + } + return true; +} + +CEL_ATTRIBUTE_NODISCARD +static bool _cel_AstTraverser_PushComprehensionDeps( + CEL_NONNULL(cel_AstTraverser*) traverser, + const cel_ComprehensionExpr* comprehension_expr, + CEL_NONNULL(cel_Status*) status) { + CEL_NULLABLE(cel_Expr*) + result = cel_ComprehensionExpr_Result(comprehension_expr); + if (result != cel_nullptr) { + if (!_cel_AstTraverser_PushComprehensionResult(traverser, result, status)) { + return false; + } + } + CEL_NULLABLE(cel_Expr*) + loop_step = cel_ComprehensionExpr_LoopStep(comprehension_expr); + if (loop_step != cel_nullptr) { + if (!_cel_AstTraverser_PushComprehensionLoopStep(traverser, loop_step, + status)) { + return false; + } + } + CEL_NULLABLE(cel_Expr*) + loop_condition = cel_ComprehensionExpr_LoopCondition(comprehension_expr); + if (loop_condition != cel_nullptr) { + if (!_cel_AstTraverser_PushComprehensionLoopCondition( + traverser, loop_condition, status)) { + return false; + } + } + CEL_NULLABLE(cel_Expr*) + accu_init = cel_ComprehensionExpr_AccuInit(comprehension_expr); + if (accu_init != cel_nullptr) { + if (!_cel_AstTraverser_PushComprehensionAccuInit(traverser, accu_init, + status)) { + return false; + } + } + CEL_NULLABLE(cel_Expr*) + iter_range = cel_ComprehensionExpr_IterRange(comprehension_expr); + if (iter_range != cel_nullptr) { + if (!_cel_AstTraverser_PushComprehensionIterRange(traverser, iter_range, + status)) { + return false; + } + } + return true; +} + +CEL_ATTRIBUTE_NODISCARD +static bool _cel_AstTraverser_PushUnaryDeps(CEL_NONNULL(cel_AstTraverser*) + traverser, + const cel_UnaryExpr* unary_expr, + CEL_NONNULL(cel_Status*) status) { + CEL_NULLABLE(cel_Expr*) arg = cel_UnaryExpr_Arg(unary_expr); + if (arg != cel_nullptr) { + if (!_cel_AstTraverser_PushUnaryArg(traverser, arg, status)) { + return false; + } + } + return true; +} + +CEL_ATTRIBUTE_NODISCARD +static bool _cel_AstTraverser_PushBinaryDeps(CEL_NONNULL(cel_AstTraverser*) + traverser, + const cel_BinaryExpr* binary_expr, + CEL_NONNULL(cel_Status*) status) { + CEL_NULLABLE(cel_Expr*) right = cel_BinaryExpr_Right(binary_expr); + if (right != cel_nullptr) { + if (!_cel_AstTraverser_PushBinaryRight(traverser, right, status)) { + return false; + } + } + CEL_NULLABLE(cel_Expr*) left = cel_BinaryExpr_Left(binary_expr); + if (left != cel_nullptr) { + if (!_cel_AstTraverser_PushBinaryLeft(traverser, left, status)) { + return false; + } + } + return true; +} + +CEL_ATTRIBUTE_NODISCARD +static bool _cel_AstTraverser_PushTernaryDeps( + CEL_NONNULL(cel_AstTraverser*) traverser, + const cel_TernaryExpr* ternary_expr, CEL_NONNULL(cel_Status*) status) { + CEL_NULLABLE(cel_Expr*) if_false = cel_TernaryExpr_IfFalse(ternary_expr); + if (if_false != cel_nullptr) { + if (!_cel_AstTraverser_PushTernaryIfFalse(traverser, if_false, status)) { + return false; + } + } + CEL_NULLABLE(cel_Expr*) if_true = cel_TernaryExpr_IfTrue(ternary_expr); + if (if_true != cel_nullptr) { + if (!_cel_AstTraverser_PushTernaryIfTrue(traverser, if_true, status)) { + return false; + } + } + CEL_NULLABLE(cel_Expr*) condition = cel_TernaryExpr_Condition(ternary_expr); + if (condition != cel_nullptr) { + if (!_cel_AstTraverser_PushTernaryCondition(traverser, condition, status)) { + return false; + } + } + return true; +} + +CEL_ATTRIBUTE_NODISCARD +static bool _cel_AstTraverser_PushDeps(CEL_NONNULL(cel_AstTraverser*) traverser, + CEL_NONNULL(_cel_AstTraverserRecord*) + record, + CEL_NONNULL(cel_Status*) status) { + CEL_NONNULL(const cel_Expr*) expr = record->data.expr; + switch (record->kind) { + case _cel_AstTraverserRecordKind_kExpr: { + switch (cel_Expr_Kind(expr)) { + case cel_ExprKind_kUnspecified: + break; + case cel_ExprKind_kIdent: + break; + case cel_ExprKind_kConst: + break; + case cel_ExprKind_kSelect: + return _cel_AstTraverser_PushSelectDeps( + traverser, cel_SelectExpr_DownCast(expr), status); + case cel_ExprKind_kCallArg: + return _cel_AstTraverser_PushCallArgDeps( + traverser, cel_CallArgExpr_DownCast(expr), status); + case cel_ExprKind_kCall: + return _cel_AstTraverser_PushCallDeps( + traverser, cel_CallExpr_DownCast(expr), status); + case cel_ExprKind_kListElement: + return _cel_AstTraverser_PushListElementDeps( + traverser, cel_ListElementExpr_DownCast(expr), status); + case cel_ExprKind_kList: + return _cel_AstTraverser_PushListDeps( + traverser, cel_ListExpr_DownCast(expr), status); + case cel_ExprKind_kMapEntry: + return _cel_AstTraverser_PushMapEntryDeps( + traverser, cel_MapEntryExpr_DownCast(expr), status); + case cel_ExprKind_kMap: + return _cel_AstTraverser_PushMapDeps( + traverser, cel_MapExpr_DownCast(expr), status); + case cel_ExprKind_kStructField: + return _cel_AstTraverser_PushStructFieldDeps( + traverser, cel_StructFieldExpr_DownCast(expr), status); + case cel_ExprKind_kStruct: + return _cel_AstTraverser_PushStructDeps( + traverser, cel_StructExpr_DownCast(expr), status); + case cel_ExprKind_kComprehension: + return _cel_AstTraverser_PushComprehensionDeps( + traverser, cel_ComprehensionExpr_DownCast(expr), status); + case cel_ExprKind_kUnary: + return _cel_AstTraverser_PushUnaryDeps( + traverser, cel_UnaryExpr_DownCast(expr), status); + case cel_ExprKind_kBinary: + return _cel_AstTraverser_PushBinaryDeps( + traverser, cel_BinaryExpr_DownCast(expr), status); + case cel_ExprKind_kTernary: + return _cel_AstTraverser_PushTernaryDeps( + traverser, cel_TernaryExpr_DownCast(expr), status); + default: + cel_InternalStatus(status, + cel_StringView_From("unexpected AST node kind")); + return false; + } + return true; + } + case _cel_AstTraverserRecordKind_kSelectOperand: + CEL_ATTRIBUTE_FALLTHROUGH; + case _cel_AstTraverserRecordKind_kCallTarget: + CEL_ATTRIBUTE_FALLTHROUGH; + case _cel_AstTraverserRecordKind_kCallArgValue: + CEL_ATTRIBUTE_FALLTHROUGH; + case _cel_AstTraverserRecordKind_kListElementValue: + CEL_ATTRIBUTE_FALLTHROUGH; + case _cel_AstTraverserRecordKind_kMapEntryKey: + CEL_ATTRIBUTE_FALLTHROUGH; + case _cel_AstTraverserRecordKind_kMapEntryValue: + CEL_ATTRIBUTE_FALLTHROUGH; + case _cel_AstTraverserRecordKind_kStructFieldValue: + CEL_ATTRIBUTE_FALLTHROUGH; + case _cel_AstTraverserRecordKind_kComprehensionIterRange: + CEL_ATTRIBUTE_FALLTHROUGH; + case _cel_AstTraverserRecordKind_kComprehensionAccuInit: + CEL_ATTRIBUTE_FALLTHROUGH; + case _cel_AstTraverserRecordKind_kComprehensionLoopCondition: + CEL_ATTRIBUTE_FALLTHROUGH; + case _cel_AstTraverserRecordKind_kComprehensionLoopStep: + CEL_ATTRIBUTE_FALLTHROUGH; + case _cel_AstTraverserRecordKind_kComprehensionResult: + CEL_ATTRIBUTE_FALLTHROUGH; + case _cel_AstTraverserRecordKind_kUnaryArg: + CEL_ATTRIBUTE_FALLTHROUGH; + case _cel_AstTraverserRecordKind_kBinaryLeft: + CEL_ATTRIBUTE_FALLTHROUGH; + case _cel_AstTraverserRecordKind_kBinaryRight: + CEL_ATTRIBUTE_FALLTHROUGH; + case _cel_AstTraverserRecordKind_kTernaryCondition: + CEL_ATTRIBUTE_FALLTHROUGH; + case _cel_AstTraverserRecordKind_kTernaryIfTrue: + CEL_ATTRIBUTE_FALLTHROUGH; + case _cel_AstTraverserRecordKind_kTernaryIfFalse: + return _cel_AstTraverser_PushExpr(traverser, expr, status); + default: + CEL_UNREACHABLE(); + } +} + +static void _cel_AstTraverser_Construct(CEL_NONNULL(cel_AstTraverser*) + traverser, + CEL_NONNULL(cel_AstVisitor*) visitor) { + memset(traverser, '\0', sizeof(*traverser)); + _cel_Deque_Construct(&traverser->records); + traverser->visitor = visitor; + traverser->step = _cel_AstTraverserStep_kStepIn; + traverser->stepped = false; + traverser->select_operand_callbacks = + _cel_AstVisitor_HasVisit(visitor, SelectExprOperand); + traverser->unary_arg_callbacks = + _cel_AstVisitor_HasVisit(visitor, UnaryExprArg); + traverser->binary_left_callbacks = + _cel_AstVisitor_HasVisit(visitor, BinaryExprLeft); + traverser->binary_right_callbacks = + _cel_AstVisitor_HasVisit(visitor, BinaryExprRight); + traverser->ternary_condition_callbacks = + _cel_AstVisitor_HasVisit(visitor, TernaryExprCondition); + traverser->ternary_if_true_callbacks = + _cel_AstVisitor_HasVisit(visitor, TernaryExprIfTrue); + traverser->ternary_if_false_callbacks = + _cel_AstVisitor_HasVisit(visitor, TernaryExprIfFalse); + traverser->call_target_callbacks = + _cel_AstVisitor_HasVisit(visitor, CallExprTarget); + traverser->call_arg_value_callbacks = + _cel_AstVisitor_HasVisit(visitor, CallArgExprValue); + traverser->list_element_value_callbacks = + _cel_AstVisitor_HasVisit(visitor, ListElementExprValue); + traverser->map_entry_key_callbacks = + _cel_AstVisitor_HasVisit(visitor, MapEntryExprKey); + traverser->map_entry_value_callbacks = + _cel_AstVisitor_HasVisit(visitor, MapEntryExprValue); + traverser->struct_field_value_callbacks = + _cel_AstVisitor_HasVisit(visitor, StructFieldExprValue); + traverser->comprehension_iter_range_callbacks = + _cel_AstVisitor_HasVisit(visitor, ComprehensionExprIterRange); + traverser->comprehension_accu_init_callbacks = + _cel_AstVisitor_HasVisit(visitor, ComprehensionExprAccuInit); + traverser->comprehension_loop_condition_callbacks = + _cel_AstVisitor_HasVisit(visitor, ComprehensionExprLoopCondition); + traverser->comprehension_loop_step_callbacks = + _cel_AstVisitor_HasVisit(visitor, ComprehensionExprLoopStep); + traverser->comprehension_result_callbacks = + _cel_AstVisitor_HasVisit(visitor, ComprehensionExprResult); +} + +static void _cel_AstTraverser_Destruct(CEL_NONNULL(cel_AstTraverser*) + traverser) { + _cel_Deque_Destruct(&traverser->records, cel_DefaultAllocator); +} + +extern "C" CEL_NULLABLE(cel_AstTraverser*) + cel_AstTraverser_New(CEL_NONNULL(const cel_Ast*) ast, + CEL_NONNULL(cel_AstVisitor*) visitor) { + CEL_ASSERT_NOT_NULL(ast); + CEL_ASSERT_NOT_NULL(visitor); + + CEL_NULLABLE(cel_AstTraverser*) + ast_traverser = (CEL_NULLABLE(cel_AstTraverser*))_cel_Malloc( + sizeof(cel_AstTraverser), cel_nullptr); + if (ast_traverser == cel_nullptr) { + return cel_nullptr; + } + _cel_AstTraverser_Construct(ast_traverser, visitor); + CEL_NULLABLE(cel_Expr*) expr = cel_Ast_Expr(ast); + if (expr != cel_nullptr) { + if (!_cel_AstTraverser_PushExpr(ast_traverser, expr, + /*status=*/cel_nullptr)) { + cel_AstTraverser_Delete(ast_traverser); + return cel_nullptr; + } + } + return ast_traverser; +} + +extern "C" void cel_AstTraverser_Delete(CEL_NULLABLE(cel_AstTraverser*) + ast_traverser) { + if (ast_traverser == cel_nullptr) { + return; + } + _cel_AstTraverser_Destruct(ast_traverser); + _cel_FreeSized(ast_traverser, sizeof(cel_AstTraverser)); +} + +extern "C" void cel_AstTraverser_StepIn( + cel_AstTraverser* cel_nonnull ast_traverser) { + CEL_ASSERT_NOT_NULL(ast_traverser); + + ast_traverser->step = _cel_AstTraverserStep_kStepIn; + ast_traverser->stepped = true; +} + +extern "C" void cel_AstTraverser_StepOut( + cel_AstTraverser* cel_nonnull ast_traverser) { + CEL_ASSERT_NOT_NULL(ast_traverser); + + ast_traverser->step = _cel_AstTraverserStep_kStepOut; + ast_traverser->stepped = true; +} + +extern "C" void cel_AstTraverser_StepOver( + cel_AstTraverser* cel_nonnull ast_traverser) { + CEL_ASSERT_NOT_NULL(ast_traverser); + + ast_traverser->step = _cel_AstTraverserStep_kStepOver; + ast_traverser->stepped = true; +} + +extern "C" bool cel_AstTraverser_Traverse(CEL_NONNULL(cel_AstTraverser*) + ast_traverser, + CEL_NONNULL(cel_Status*) status) { + CEL_ASSERT_NOT_NULL(ast_traverser); + CEL_ASSERT(cel_Status_Ok(status)); + + while (true) { + _cel_AstTraverserRecord* record = _cel_AstTraverser_Peek(ast_traverser); + if (record == cel_nullptr) { + return false; + } + while (true) { + if (!record->data.previsited || !record->data.previsited_expr) { + ast_traverser->stepped = false; + switch ( + _cel_AstTraverserRecord_PreVisit(record, ast_traverser, status)) { + case _cel_AstVisitorControl_kStop: + return cel_Status_Ok(status); + case _cel_AstVisitorControl_kContinue: + CEL_ASSERT_NOT(ast_traverser->stepped); + CEL_ASSERT(cel_Status_Ok(status)); + break; + default: + CEL_UNREACHABLE(); + } + } else if (!record->data.pushed_deps) { + if (!_cel_AstTraverser_PushDeps(ast_traverser, record, status)) { + return false; + } + record->data.pushed_deps = true; + break; + } else if (!record->data.postvisited || !record->data.postvisited_expr) { + ast_traverser->stepped = false; + switch ( + _cel_AstTraverserRecord_PostVisit(record, ast_traverser, status)) { + case _cel_AstVisitorControl_kStop: + return cel_Status_Ok(status); + case _cel_AstVisitorControl_kContinue: + CEL_ASSERT_NOT(ast_traverser->stepped); + CEL_ASSERT(cel_Status_Ok(status)); + break; + default: + CEL_UNREACHABLE(); + } + } else { + _cel_AstTraverser_Pop(ast_traverser); + break; + } + } + } +} diff --git a/cel-c/constant.cc b/cel-c/constant.cc new file mode 100644 index 0000000..66bb669 --- /dev/null +++ b/cel-c/constant.cc @@ -0,0 +1,67 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "cel-c/constant.h" + +#include + +#include "cel-c/assert.h" +#include "cel-c/config.h" +#include "cel-c/duration.h" +#include "cel-c/string_view.h" +#include "cel-c/timestamp.h" + +extern "C" bool cel_Constant_Equals(CEL_NONNULL(const cel_Constant*) lhs, + CEL_NONNULL(const cel_Constant*) rhs) { + CEL_ASSERT_NOT_NULL(lhs); + CEL_ASSERT_NOT_NULL(rhs); + + if (lhs == rhs) { + return true; + } + + const cel_ConstantKind kind = cel_Constant_Kind(lhs); + if (kind != cel_Constant_Kind(rhs)) { + return false; + } + + switch (kind) { + case cel_ConstantKind_kUnspecified: + return true; + case cel_ConstantKind_kNull: + return true; + case cel_ConstantKind_kBool: + return cel_Constant_GetBool(lhs) == cel_Constant_GetBool(rhs); + case cel_ConstantKind_kInt: + return cel_Constant_GetInt(lhs) == cel_Constant_GetInt(rhs); + case cel_ConstantKind_kUint: + return cel_Constant_GetUint(lhs) == cel_Constant_GetUint(rhs); + case cel_ConstantKind_kDouble: + return cel_Constant_GetDouble(lhs) == cel_Constant_GetDouble(rhs); + case cel_ConstantKind_kBytes: + return cel_StringView_Equals(cel_Constant_GetBytes(lhs), + cel_Constant_GetBytes(rhs)); + case cel_ConstantKind_kString: + return cel_StringView_Equals(cel_Constant_GetString(lhs), + cel_Constant_GetString(rhs)); + case cel_ConstantKind_kDuration: + return cel_Duration_Equals(cel_Constant_GetDuration(lhs), + cel_Constant_GetDuration(rhs)); + case cel_ConstantKind_kTimestamp: + return cel_Timestamp_Equals(cel_Constant_GetTimestamp(lhs), + cel_Constant_GetTimestamp(rhs)); + default: + return false; + } +} diff --git a/cel-c/constant_proto.cc b/cel-c/constant_proto.cc new file mode 100644 index 0000000..686c606 --- /dev/null +++ b/cel-c/constant_proto.cc @@ -0,0 +1,194 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "cel-c/constant_proto.h" + +#include +#include + +#include "cel/expr/syntax.upb.h" +#include "google/protobuf/duration.upb.h" +#include "google/protobuf/struct.upb.h" +#include "google/protobuf/timestamp.upb.h" +#include "cel-c/arena.h" +#include "cel-c/assert.h" +#include "cel-c/config.h" +#include "cel-c/constant.h" +#include "cel-c/duration.h" +#include "cel-c/duration_proto.h" +#include "cel-c/status.h" +#include "cel-c/string_view.h" +#include "cel-c/timestamp.h" +#include "cel-c/timestamp_proto.h" +#include "upb/mem/arena.h" + +extern "C" bool cel_Constant_ToProto(CEL_NONNULL(const cel_Constant*) in, + CEL_NONNULL(upb_Arena*) arena, + CEL_NONNULL(cel_expr_Constant*) out, + CEL_NONNULL(cel_Status*) status) { + CEL_ASSERT_NOT_NULL(in); + CEL_ASSERT_NOT_NULL(arena); + CEL_ASSERT_NOT_NULL(out); + CEL_ASSERT_NOT_NULL(status); + + if (!cel_Status_Ok(status)) { + return false; + } + switch (cel_Constant_Kind(in)) { + case cel_ConstantKind_kUnspecified: + cel_expr_Constant_clear_constant_kind(out); + break; + case cel_ConstantKind_kNull: + cel_expr_Constant_set_null_value(out, google_protobuf_NULL_VALUE); + break; + case cel_ConstantKind_kBool: + cel_expr_Constant_set_bool_value(out, cel_Constant_GetBool(in)); + break; + case cel_ConstantKind_kInt: + cel_expr_Constant_set_int64_value(out, cel_Constant_GetInt(in)); + break; + case cel_ConstantKind_kUint: + cel_expr_Constant_set_uint64_value(out, cel_Constant_GetUint(in)); + break; + case cel_ConstantKind_kDouble: + cel_expr_Constant_set_double_value(out, + cel_Constant_GetDouble(in)); + break; + case cel_ConstantKind_kBytes: + cel_expr_Constant_set_bytes_value(out, cel_Constant_GetBytes(in)); + break; + case cel_ConstantKind_kString: + cel_expr_Constant_set_string_value(out, + cel_Constant_GetString(in)); + break; + case cel_ConstantKind_kDuration: { + CEL_NULLABLE(google_protobuf_Duration*) + out_duration = google_protobuf_Duration_new(arena); + if (out_duration == cel_nullptr) { + cel_OutOfMemoryStatus(status); + return false; + } + cel_Duration_ToProto(cel_Constant_GetDuration(in), out_duration); + cel_expr_Constant_set_duration_value(out, out_duration); + } break; + case cel_ConstantKind_kTimestamp: { + CEL_NULLABLE(google_protobuf_Timestamp*) + out_timestamp = google_protobuf_Timestamp_new(arena); + if (out_timestamp == cel_nullptr) { + cel_OutOfMemoryStatus(status); + return false; + } + cel_Timestamp_ToProto(cel_Constant_GetTimestamp(in), out_timestamp); + cel_expr_Constant_set_timestamp_value(out, out_timestamp); + } break; + default: + cel_InvalidArgumentStatusF(status, "cel: unexpected constant kind: %d", + cel_Constant_Kind(in)); + return false; + } + return true; +} + +extern "C" bool cel_Constant_FromProto( + CEL_NONNULL(cel_Constant*) out, + CEL_NONNULL(const cel_expr_Constant*) in, + CEL_NONNULL(cel_Arena*) arena, CEL_NONNULL(cel_Status*) status) { + CEL_ASSERT_NOT_NULL(out); + CEL_ASSERT_NOT_NULL(in); + CEL_ASSERT_NOT_NULL(status); + + if (!cel_Status_Ok(status)) { + return false; + } + switch (cel_expr_Constant_constant_kind_case(in)) { + case cel_expr_Constant_constant_kind_NOT_SET: + *out = cel_UnspecifiedConstant(); + break; + case cel_expr_Constant_constant_kind_null_value: + cel_Constant_SetNull(out); + break; + case cel_expr_Constant_constant_kind_bool_value: + cel_Constant_SetBool(out, cel_expr_Constant_bool_value(in)); + break; + case cel_expr_Constant_constant_kind_int64_value: + cel_Constant_SetInt(out, cel_expr_Constant_int64_value(in)); + break; + case cel_expr_Constant_constant_kind_uint64_value: + cel_Constant_SetUint(out, cel_expr_Constant_uint64_value(in)); + break; + case cel_expr_Constant_constant_kind_double_value: + cel_Constant_SetDouble(out, cel_expr_Constant_double_value(in)); + break; + case cel_expr_Constant_constant_kind_bytes_value: { + cel_StringView val = cel_expr_Constant_bytes_value(in); + if (!cel_Arena_StrDup(arena, &val, val)) { + cel_OutOfMemoryStatus(status); + return false; + } + cel_Constant_SetBytes(out, val); + } break; + case cel_expr_Constant_constant_kind_string_value: { + cel_StringView val = cel_expr_Constant_string_value(in); + if (!cel_Arena_StrDup(arena, &val, val)) { + cel_OutOfMemoryStatus(status); + return false; + } + cel_Constant_SetString(out, val); + } break; + case cel_expr_Constant_constant_kind_duration_value: { + CEL_NULLABILITY_UNKNOWN(const google_protobuf_Duration*) + in_duration = cel_expr_Constant_duration_value(in); + cel_Duration out_duration; + if (in_duration != cel_nullptr) { + if (!cel_Duration_FromProto(&out_duration, in_duration)) { + cel_InvalidArgumentStatusF( + status, + "cel: invalid google.protobuf.Duration: { seconds: %" PRId64 + " nanos: %" PRId32 " }", + google_protobuf_Duration_seconds(in_duration), + google_protobuf_Duration_nanos(in_duration)); + return false; + } + } else { + out_duration = cel_Duration_kZero; + } + cel_Constant_SetDuration(out, out_duration); + } break; + case cel_expr_Constant_constant_kind_timestamp_value: { + CEL_NULLABILITY_UNKNOWN(const google_protobuf_Timestamp*) + in_timestamp = cel_expr_Constant_timestamp_value(in); + cel_Timestamp out_timestamp; + if (in_timestamp != cel_nullptr) { + if (!cel_Timestamp_FromProto(&out_timestamp, in_timestamp)) { + cel_InvalidArgumentStatusF( + status, + "cel: invalid google.protobuf.Timestamp: { seconds: %" PRId64 + " nanos: %" PRId32 " }", + google_protobuf_Timestamp_seconds(in_timestamp), + google_protobuf_Timestamp_nanos(in_timestamp)); + return false; + } + } else { + out_timestamp = cel_Timestamp_kUnixEpoch; + } + cel_Constant_SetTimestamp(out, out_timestamp); + } break; + default: + cel_InvalidArgumentStatusF( + status, "cel: unexpected constant kind: %d", + cel_expr_Constant_constant_kind_case(in)); + return false; + } + return true; +} diff --git a/cel-c/constant_proto_v1alpha1.cc b/cel-c/constant_proto_v1alpha1.cc new file mode 100644 index 0000000..b63e30c --- /dev/null +++ b/cel-c/constant_proto_v1alpha1.cc @@ -0,0 +1,203 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "cel-c/constant_proto_v1alpha1.h" + +#include +#include + +#include "google/api/expr/v1alpha1/syntax.upb.h" +#include "google/protobuf/duration.upb.h" +#include "google/protobuf/struct.upb.h" +#include "google/protobuf/timestamp.upb.h" +#include "cel-c/arena.h" +#include "cel-c/assert.h" +#include "cel-c/config.h" +#include "cel-c/constant.h" +#include "cel-c/duration.h" +#include "cel-c/duration_proto.h" +#include "cel-c/status.h" +#include "cel-c/string_view.h" +#include "cel-c/timestamp.h" +#include "cel-c/timestamp_proto.h" +#include "upb/mem/arena.h" + +extern "C" bool cel_Constant_ToProtoV1Alpha1( + CEL_NONNULL(const cel_Constant*) in, CEL_NONNULL(upb_Arena*) arena, + CEL_NONNULL(google_api_expr_v1alpha1_Constant*) out, + CEL_NONNULL(cel_Status*) status) { + CEL_ASSERT_NOT_NULL(in); + CEL_ASSERT_NOT_NULL(arena); + CEL_ASSERT_NOT_NULL(out); + CEL_ASSERT_NOT_NULL(status); + + if (!cel_Status_Ok(status)) { + return false; + } + switch (cel_Constant_Kind(in)) { + case cel_ConstantKind_kUnspecified: + google_api_expr_v1alpha1_Constant_clear_constant_kind(out); + break; + case cel_ConstantKind_kNull: + google_api_expr_v1alpha1_Constant_set_null_value( + out, google_protobuf_NULL_VALUE); + break; + case cel_ConstantKind_kBool: + google_api_expr_v1alpha1_Constant_set_bool_value( + out, cel_Constant_GetBool(in)); + break; + case cel_ConstantKind_kInt: + google_api_expr_v1alpha1_Constant_set_int64_value( + out, cel_Constant_GetInt(in)); + break; + case cel_ConstantKind_kUint: + google_api_expr_v1alpha1_Constant_set_uint64_value( + out, cel_Constant_GetUint(in)); + break; + case cel_ConstantKind_kDouble: + google_api_expr_v1alpha1_Constant_set_double_value( + out, cel_Constant_GetDouble(in)); + break; + case cel_ConstantKind_kBytes: + google_api_expr_v1alpha1_Constant_set_bytes_value( + out, cel_Constant_GetBytes(in)); + break; + case cel_ConstantKind_kString: + google_api_expr_v1alpha1_Constant_set_string_value( + out, cel_Constant_GetString(in)); + break; + case cel_ConstantKind_kDuration: { + CEL_NULLABLE(google_protobuf_Duration*) + out_duration = google_protobuf_Duration_new(arena); + if (out_duration == cel_nullptr) { + cel_OutOfMemoryStatus(status); + return false; + } + cel_Duration_ToProto(cel_Constant_GetDuration(in), out_duration); + google_api_expr_v1alpha1_Constant_set_duration_value(out, out_duration); + } break; + case cel_ConstantKind_kTimestamp: { + CEL_NULLABLE(google_protobuf_Timestamp*) + out_timestamp = google_protobuf_Timestamp_new(arena); + if (out_timestamp == cel_nullptr) { + cel_OutOfMemoryStatus(status); + return false; + } + cel_Timestamp_ToProto(cel_Constant_GetTimestamp(in), out_timestamp); + google_api_expr_v1alpha1_Constant_set_timestamp_value(out, out_timestamp); + } break; + default: + cel_InvalidArgumentStatusF(status, "cel: unexpected constant kind: %d", + cel_Constant_Kind(in)); + return false; + } + return true; +} + +extern "C" bool cel_Constant_FromProtoV1Alpha1( + CEL_NONNULL(cel_Constant*) out, + CEL_NONNULL(const google_api_expr_v1alpha1_Constant*) in, + CEL_NONNULL(cel_Arena*) arena, CEL_NONNULL(cel_Status*) status) { + CEL_ASSERT_NOT_NULL(out); + CEL_ASSERT_NOT_NULL(in); + CEL_ASSERT_NOT_NULL(status); + + if (!cel_Status_Ok(status)) { + return false; + } + switch (google_api_expr_v1alpha1_Constant_constant_kind_case(in)) { + case google_api_expr_v1alpha1_Constant_constant_kind_NOT_SET: + *out = cel_UnspecifiedConstant(); + break; + case google_api_expr_v1alpha1_Constant_constant_kind_null_value: + cel_Constant_SetNull(out); + break; + case google_api_expr_v1alpha1_Constant_constant_kind_bool_value: + cel_Constant_SetBool(out, + google_api_expr_v1alpha1_Constant_bool_value(in)); + break; + case google_api_expr_v1alpha1_Constant_constant_kind_int64_value: + cel_Constant_SetInt(out, + google_api_expr_v1alpha1_Constant_int64_value(in)); + break; + case google_api_expr_v1alpha1_Constant_constant_kind_uint64_value: + cel_Constant_SetUint(out, + google_api_expr_v1alpha1_Constant_uint64_value(in)); + break; + case google_api_expr_v1alpha1_Constant_constant_kind_double_value: + cel_Constant_SetDouble( + out, google_api_expr_v1alpha1_Constant_double_value(in)); + break; + case google_api_expr_v1alpha1_Constant_constant_kind_bytes_value: { + cel_StringView val = google_api_expr_v1alpha1_Constant_bytes_value(in); + if (!cel_Arena_StrDup(arena, &val, val)) { + cel_OutOfMemoryStatus(status); + return false; + } + cel_Constant_SetBytes(out, val); + } break; + case google_api_expr_v1alpha1_Constant_constant_kind_string_value: { + cel_StringView val = google_api_expr_v1alpha1_Constant_string_value(in); + if (!cel_Arena_StrDup(arena, &val, val)) { + cel_OutOfMemoryStatus(status); + return false; + } + cel_Constant_SetString(out, val); + } break; + case google_api_expr_v1alpha1_Constant_constant_kind_duration_value: { + CEL_NULLABILITY_UNKNOWN(const google_protobuf_Duration*) + in_duration = google_api_expr_v1alpha1_Constant_duration_value(in); + cel_Duration out_duration; + if (in_duration != cel_nullptr) { + if (!cel_Duration_FromProto(&out_duration, in_duration)) { + cel_InvalidArgumentStatusF( + status, + "cel: invalid google.protobuf.Duration: { seconds: %" PRId64 + " nanos: %" PRId32 " }", + google_protobuf_Duration_seconds(in_duration), + google_protobuf_Duration_nanos(in_duration)); + return false; + } + } else { + out_duration = cel_Duration_kZero; + } + cel_Constant_SetDuration(out, out_duration); + } break; + case google_api_expr_v1alpha1_Constant_constant_kind_timestamp_value: { + CEL_NULLABILITY_UNKNOWN(const google_protobuf_Timestamp*) + in_timestamp = google_api_expr_v1alpha1_Constant_timestamp_value(in); + cel_Timestamp out_timestamp; + if (in_timestamp != cel_nullptr) { + if (!cel_Timestamp_FromProto(&out_timestamp, in_timestamp)) { + cel_InvalidArgumentStatusF( + status, + "cel: invalid google.protobuf.Timestamp: { seconds: %" PRId64 + " nanos: %" PRId32 " }", + google_protobuf_Timestamp_seconds(in_timestamp), + google_protobuf_Timestamp_nanos(in_timestamp)); + return false; + } + } else { + out_timestamp = cel_Timestamp_kUnixEpoch; + } + cel_Constant_SetTimestamp(out, out_timestamp); + } break; + default: + cel_InvalidArgumentStatusF( + status, "cel: unexpected constant kind: %d", + google_api_expr_v1alpha1_Constant_constant_kind_case(in)); + return false; + } + return true; +} diff --git a/cel-c/cstring_view.cc b/cel-c/cstring_view.cc new file mode 100644 index 0000000..3cd5803 --- /dev/null +++ b/cel-c/cstring_view.cc @@ -0,0 +1,91 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "cel-c/cstring_view.h" + +#include +#include + +#include "cel-c/assert.h" +#include "cel-c/config.h" + +extern "C" size_t cel_CStringView_Count(cel_CStringView haystack, + cel_CStringView needle) { + CEL_ASSERT_NOT(cel_CStringView_Empty(needle)); + + size_t count = 0; + const char* pos; + size_t needle_len = 0; + while ((pos = cel_CStringView_FindFirst(haystack, needle)) != cel_nullptr) { + if (needle_len == 0) { + needle_len = cel_CStringView_Size(needle); + } + cel_CStringView_RemovePrefix( + &haystack, (pos - cel_CStringView_Data(haystack)) + needle_len); + ++count; + } + return count; +} + +extern "C" bool cel_CStringViewTokenizer_Next( + cel_CStringViewTokenizer* cel_nonnull tokenizer, + cel_CStringView* cel_nonnull token, size_t* cel_nonnull token_len) { + CEL_ASSERT_NOT_NULL(tokenizer); + CEL_ASSERT_NOT_NULL(token); + CEL_ASSERT_NOT_NULL(token_len); + + if (tokenizer->done) { + return false; + } + const char* next = + cel_CStringView_FindFirst(tokenizer->subject, tokenizer->delim); + if (next == cel_nullptr) { + *token = tokenizer->subject; + *token_len = cel_CStringView_Size(tokenizer->subject); + tokenizer->done = true; + return true; + } + if (tokenizer->delim_len == 0) { + tokenizer->delim_len = cel_CStringView_Size(tokenizer->delim); + } + const char* data = cel_CStringView_Data(tokenizer->subject); + *token = cel_CStringView_FromString(data); + *token_len = next - data; + cel_CStringView_RemovePrefix(&tokenizer->subject, + (next - data) + tokenizer->delim_len); + return true; +} + +extern "C" bool cel_CStringView_EqualsIgnoreCase(cel_CStringView lhs, + cel_CStringView rhs) { + const char* lhs_data = cel_CStringView_Data(lhs); + const char* rhs_data = cel_CStringView_Data(rhs); + if (lhs_data == rhs_data) { + return true; + } + while (true) { + uint8_t lhs_c = (uint8_t)(*lhs_data++); + uint8_t rhs_c = (uint8_t)(*rhs_data++); + if (lhs_c == '\0' || rhs_c == '\0') { + return lhs_c == rhs_c; + } + if (lhs_c != rhs_c) { + lhs_c = lhs_c >= 'A' && lhs_c <= 'Z' ? lhs_c - 'A' + 'a' : lhs_c; + rhs_c = rhs_c >= 'A' && rhs_c <= 'Z' ? rhs_c - 'A' + 'a' : rhs_c; + if (lhs_c != rhs_c) { + return false; + } + } + } +} diff --git a/cel-c/decl.cc b/cel-c/decl.cc new file mode 100644 index 0000000..a1256b0 --- /dev/null +++ b/cel-c/decl.cc @@ -0,0 +1,344 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "cel-c/decl.h" + +#include +#include +#include + +#include "cel-c/arena.h" +#include "cel-c/assert.h" +#include "cel-c/config.h" +#include "cel-c/constant.h" +#include "cel-c/function_scope.h" +#include "cel-c/string_view.h" +#include "cel-c/type.h" +#include "cel-c/type_kind.h" + +struct cel_IdentDecl { + cel_StringView name; + cel_DeclKind kind; + cel_StringView doc; + + const cel_Type* cel_nonnull type; + cel_Constant value; +}; + +struct cel_FunctionDecl { + cel_StringView name; + cel_DeclKind kind; + cel_StringView doc; + + cel_FunctionOverloadDecl* cel_nullable head; + cel_FunctionOverloadDecl* cel_nullable tail; + size_t overloads; +}; + +struct cel_FunctionOverloadDecl { + cel_FunctionOverloadDecl* cel_nullable prev; + cel_FunctionOverloadDecl* cel_nullable next; + cel_FunctionDecl* cel_nullable function; + cel_StringView id; + const cel_FunctionType* cel_nonnull type; + cel_FunctionScope scope; + cel_StringView doc; +}; + +struct cel_Decl { + union { + struct { + cel_StringView name; + cel_DeclKind kind; + cel_StringView doc; + }; + cel_IdentDecl ident_decl; + cel_FunctionDecl function_decl; + }; +}; + +extern "C" cel_DeclKind cel_Decl_Kind(const cel_Decl* cel_nonnull decl) { + CEL_ASSERT_NOT_NULL(decl); + + return decl->kind; +} + +extern "C" cel_StringView cel_Decl_Name(const cel_Decl* cel_nonnull decl) { + CEL_ASSERT_NOT_NULL(decl); + + return decl->name; +} + +extern "C" cel_StringView cel_Decl_Doc(const cel_Decl* cel_nonnull decl) { + CEL_ASSERT_NOT_NULL(decl); + + return decl->doc; +} + +extern "C" void cel_Decl_SetDoc(cel_Decl* cel_nonnull decl, + cel_StringView doc) { + CEL_ASSERT_NOT_NULL(decl); + + decl->doc = doc; +} + +extern "C" cel_IdentDecl* cel_nullable +cel_IdentDecl_New(cel_StringView name, cel_Arena* cel_nonnull arena) { + CEL_ASSERT_NOT_NULL(arena); + + cel_IdentDecl* decl = (cel_IdentDecl*)cel_Arena_Malloc( + arena, sizeof(cel_IdentDecl), cel_nullptr); + if (CEL_LIKELY(decl != cel_nullptr)) { + memset(decl, 0, sizeof(*decl)); + decl->name = name; + decl->kind = cel_DeclKind_kIdent; + decl->type = cel_DynType; + } + return decl; +} + +extern "C" const cel_Type* cel_nonnull +cel_IdentDecl_Type(const cel_IdentDecl* cel_nonnull decl) { + CEL_ASSERT_NOT_NULL(decl); + + return decl->type; +} + +extern "C" void cel_IdentDecl_SetType(cel_IdentDecl* cel_nonnull decl, + const cel_Type* cel_nonnull type) { + CEL_ASSERT_NOT_NULL(decl); + + decl->type = type; +} + +extern "C" const cel_Constant* cel_nonnull +cel_IdentDecl_Value(const cel_IdentDecl* cel_nonnull decl) { + CEL_ASSERT_NOT_NULL(decl); + + return &decl->value; +} + +extern "C" cel_Constant* cel_nonnull +cel_IdentDecl_MutableValue(cel_IdentDecl* cel_nonnull decl) { + CEL_ASSERT_NOT_NULL(decl); + + return &decl->value; +} + +extern "C" cel_FunctionDecl* cel_nullable +cel_FunctionDecl_New(cel_StringView name, cel_Arena* cel_nonnull arena) { + CEL_ASSERT_NOT_NULL(arena); + + cel_FunctionDecl* decl = (cel_FunctionDecl*)cel_Arena_Malloc( + arena, sizeof(cel_FunctionDecl), cel_nullptr); + if (CEL_LIKELY(decl != cel_nullptr)) { + memset(decl, 0, sizeof(*decl)); + decl->name = name; + decl->kind = cel_DeclKind_kFunction; + } + return decl; +} + +extern "C" size_t cel_FunctionDecl_Overloads( + const cel_FunctionDecl* cel_nonnull decl, + cel_FunctionOverloadDecl * cel_nullable * cel_nullable head, + cel_FunctionOverloadDecl * cel_nullable * cel_nullable tail) { + CEL_ASSERT_NOT_NULL(decl); + + if (head != cel_nullptr) { + *head = decl->head; + } + if (tail != cel_nullptr) { + *tail = decl->tail; + } + return decl->overloads; +} + +static bool _cel_IsTypeAssignable(const cel_Type* cel_nonnull to, + const cel_Type* cel_nonnull from) { + if (cel_Type_Equals(to, from)) { + return true; + } + const cel_TypeKind to_kind = cel_Type_Kind(to); + if (to_kind == cel_TypeKind_kDyn) { + return true; + } + switch (to_kind) { + case cel_TypeKind_kBoolWrapper: + return _cel_IsTypeAssignable(cel_NullType, from) || + _cel_IsTypeAssignable(cel_BoolType, from); + case cel_TypeKind_kIntWrapper: + return _cel_IsTypeAssignable(cel_NullType, from) || + _cel_IsTypeAssignable(cel_IntType, from); + case cel_TypeKind_kUintWrapper: + return _cel_IsTypeAssignable(cel_NullType, from) || + _cel_IsTypeAssignable(cel_UintType, from); + case cel_TypeKind_kDoubleWrapper: + return _cel_IsTypeAssignable(cel_NullType, from) || + _cel_IsTypeAssignable(cel_DoubleType, from); + case cel_TypeKind_kBytesWrapper: + return _cel_IsTypeAssignable(cel_NullType, from) || + _cel_IsTypeAssignable(cel_BytesType, from); + case cel_TypeKind_kStringWrapper: + return _cel_IsTypeAssignable(cel_NullType, from) || + _cel_IsTypeAssignable(cel_StringType, from); + default: + break; + } + const cel_TypeKind from_kind = cel_Type_Kind(from); + if (to_kind != from_kind || + !cel_StringView_Equals(cel_Type_Name(to), cel_Type_Name(from))) { + return false; + } + size_t from_params_len; + const cel_Type* const* from_params = cel_Type_Params(from, &from_params_len); + size_t to_params_len; + const cel_Type* const* to_params = cel_Type_Params(to, &to_params_len); + if (from_params_len != to_params_len) { + return false; + } + bool params_overlap = true; + for (size_t i = 0; i < from_params_len && params_overlap; ++i) { + params_overlap = _cel_IsTypeAssignable(from_params[i], to_params[i]); + } + return params_overlap; +} + +static bool _cel_SignaturesOverlap( + const cel_FunctionOverloadDecl* cel_nonnull lhs, + const cel_FunctionOverloadDecl* cel_nonnull rhs) { + if (cel_StringView_Equals(cel_FunctionOverloadDecl_Id(lhs), + cel_FunctionOverloadDecl_Id(rhs))) { + return true; + } + if (cel_FunctionOverloadDecl_Scope(lhs) != + cel_FunctionOverloadDecl_Scope(rhs)) { + return false; + } + const cel_FunctionType* lhs_type = cel_FunctionOverloadDecl_Type(lhs); + const cel_FunctionType* rhs_type = cel_FunctionOverloadDecl_Type(rhs); + size_t lhs_type_args_len; + const cel_Type* const* lhs_type_args = + cel_FunctionType_Args(lhs_type, &lhs_type_args_len); + size_t rhs_type_args_len; + const cel_Type* const* rhs_type_args = + cel_FunctionType_Args(rhs_type, &rhs_type_args_len); + if (lhs_type_args_len != rhs_type_args_len) { + return false; + } + bool args_overlap = true; + for (size_t i = 0; i < lhs_type_args_len && args_overlap; ++i) { + args_overlap = _cel_IsTypeAssignable(lhs_type_args[i], rhs_type_args[i]); + } + return args_overlap; +} + +extern "C" bool cel_FunctionDecl_AddOverload( + cel_FunctionDecl* cel_nonnull decl, + cel_FunctionOverloadDecl* cel_nonnull overload) { + CEL_ASSERT_NOT_NULL(decl); + CEL_ASSERT_NOT_NULL(overload); + CEL_ASSERT_NULL(overload->function); + + cel_FunctionOverloadDecl* head = decl->head; + for (; head != cel_nullptr; head = head->next) { + if (_cel_SignaturesOverlap(head, overload)) { + return false; + } + } + + overload->function = decl; + overload->prev = decl->tail; + if (decl->tail != cel_nullptr) { + decl->tail->next = overload; + } else { + decl->head = overload; + } + decl->tail = overload; + ++decl->overloads; + return true; +} + +extern "C" cel_FunctionOverloadDecl* cel_nullable cel_FunctionOverloadDecl_New( + cel_StringView id, cel_FunctionScope scope, + const cel_FunctionType* cel_nonnull type, cel_Arena* cel_nonnull arena) { + CEL_ASSERT_NOT_NULL(arena); + CEL_ASSERT_NOT_NULL(type); + + cel_FunctionOverloadDecl* decl = (cel_FunctionOverloadDecl*)cel_Arena_Malloc( + arena, sizeof(cel_FunctionOverloadDecl), cel_nullptr); + if (CEL_LIKELY(decl != cel_nullptr)) { + memset(decl, 0, sizeof(*decl)); + decl->id = id; + decl->type = type; + decl->scope = scope; + } + return decl; +} + +extern "C" cel_StringView cel_FunctionOverloadDecl_Id( + const cel_FunctionOverloadDecl* cel_nonnull decl) { + CEL_ASSERT_NOT_NULL(decl); + + return decl->id; +} + +extern "C" cel_StringView cel_FunctionOverloadDecl_Doc( + const cel_FunctionOverloadDecl* cel_nonnull decl) { + CEL_ASSERT_NOT_NULL(decl); + + return decl->doc; +} + +extern "C" void cel_FunctionOverloadDecl_SetDoc( + cel_FunctionOverloadDecl* cel_nonnull decl, cel_StringView doc) { + CEL_ASSERT_NOT_NULL(decl); + + decl->doc = doc; +} + +extern "C" cel_FunctionScope cel_FunctionOverloadDecl_Scope( + const cel_FunctionOverloadDecl* cel_nonnull decl) { + CEL_ASSERT_NOT_NULL(decl); + + return decl->scope; +} + +extern "C" cel_FunctionDecl* cel_nullable cel_FunctionOverloadDecl_Function( + const cel_FunctionOverloadDecl* cel_nonnull decl) { + CEL_ASSERT_NOT_NULL(decl); + + return decl->function; +} + +extern "C" const cel_FunctionType* cel_nonnull cel_FunctionOverloadDecl_Type( + const cel_FunctionOverloadDecl* cel_nonnull decl) { + CEL_ASSERT_NOT_NULL(decl); + + return decl->type; +} + +extern "C" cel_FunctionOverloadDecl* cel_nullable cel_FunctionOverloadDecl_Prev( + const cel_FunctionOverloadDecl* cel_nonnull decl) { + CEL_ASSERT_NOT_NULL(decl); + + return decl->prev; +} + +extern "C" cel_FunctionOverloadDecl* cel_nullable cel_FunctionOverloadDecl_Next( + const cel_FunctionOverloadDecl* cel_nonnull decl) { + CEL_ASSERT_NOT_NULL(decl); + + return decl->next; +} diff --git a/cel-c/decl_proto.cc b/cel-c/decl_proto.cc new file mode 100644 index 0000000..7dbd831 --- /dev/null +++ b/cel-c/decl_proto.cc @@ -0,0 +1,183 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "cel-c/decl_proto.h" + +#include + +#include "cel/expr/checked.upb.h" +#include "cel-c/arena.h" +#include "cel-c/assert.h" +#include "cel-c/config.h" +#include "cel-c/constant_proto.h" +#include "cel-c/decl.h" +#include "cel-c/function_scope.h" +#include "cel-c/status.h" +#include "cel-c/string_view.h" +#include "cel-c/type.h" +#include "cel-c/type_proto.h" + +extern "C" cel_Decl* cel_nullable cel_Decl_FromProto( + const cel_expr_Decl* cel_nonnull in, cel_Arena* cel_nonnull arena, + cel_Status* cel_nonnull status) { + CEL_ASSERT_NOT_NULL(in); + CEL_ASSERT_NOT_NULL(arena); + CEL_ASSERT(cel_Status_Ok(status)); + + const cel_expr_Decl_decl_kind_oneofcases decl_kind = + cel_expr_Decl_decl_kind_case(in); + switch (decl_kind) { + case cel_expr_Decl_decl_kind_ident: { + cel_StringView out_name; + if (CEL_UNLIKELY(!cel_Arena_StrDup(arena, &out_name, + cel_expr_Decl_name(in)))) { + cel_OutOfMemoryStatus(status); + return cel_nullptr; + } + cel_IdentDecl* out = cel_IdentDecl_New(out_name, arena); + if (CEL_UNLIKELY(out == cel_nullptr)) { + cel_OutOfMemoryStatus(status); + return cel_nullptr; + } + const cel_expr_Decl_IdentDecl* in_ident = + cel_expr_Decl_ident(in); + if (cel_expr_Decl_IdentDecl_has_type(in_ident)) { + const cel_Type* out_type = cel_Type_FromProto( + cel_expr_Decl_IdentDecl_type(in_ident), arena, status); + if (CEL_UNLIKELY(out_type == cel_nullptr)) { + return cel_nullptr; + } + cel_IdentDecl_SetType(out, out_type); + } + if (cel_expr_Decl_IdentDecl_has_value(in_ident)) { + if (CEL_UNLIKELY(!cel_Constant_FromProto( + cel_IdentDecl_MutableValue(out), + cel_expr_Decl_IdentDecl_value(in_ident), arena, + status))) { + return cel_nullptr; + } + } + cel_StringView out_doc; + if (CEL_UNLIKELY(!cel_Arena_StrDup( + arena, &out_doc, cel_expr_Decl_IdentDecl_doc(in_ident)))) { + cel_OutOfMemoryStatus(status); + return cel_nullptr; + } + cel_Decl_SetDoc(cel_Decl_UpCast(out), out_doc); + return cel_Decl_UpCast(out); + } + case cel_expr_Decl_decl_kind_function: { + cel_StringView out_name; + if (CEL_UNLIKELY(!cel_Arena_StrDup(arena, &out_name, + cel_expr_Decl_name(in)))) { + cel_OutOfMemoryStatus(status); + return cel_nullptr; + } + cel_FunctionDecl* out = cel_FunctionDecl_New(out_name, arena); + if (CEL_UNLIKELY(out == cel_nullptr)) { + cel_OutOfMemoryStatus(status); + return cel_nullptr; + } + const cel_expr_Decl_FunctionDecl* in_function = + cel_expr_Decl_function(in); + size_t in_overloads_len; + const cel_expr_Decl_FunctionDecl_Overload* const* + in_overloads_ptr = cel_expr_Decl_FunctionDecl_overloads( + in_function, &in_overloads_len); + for (size_t i = 0; i < in_overloads_len; ++i) { + const cel_expr_Decl_FunctionDecl_Overload* in_overload = + in_overloads_ptr[i]; + cel_StringView out_id; + if (CEL_UNLIKELY(!cel_Arena_StrDup( + arena, &out_id, + cel_expr_Decl_FunctionDecl_Overload_overload_id( + in_overload)))) { + cel_OutOfMemoryStatus(status); + return cel_nullptr; + } + const cel_Type* out_result; + if (cel_expr_Decl_FunctionDecl_Overload_has_result_type( + in_overload)) { + out_result = cel_Type_FromProto( + cel_expr_Decl_FunctionDecl_Overload_result_type( + in_overload), + arena, status); + if (CEL_UNLIKELY(out_result == cel_nullptr)) { + return cel_nullptr; + } + } else { + out_result = cel_DynType; + } + size_t in_params_len; + const cel_expr_Type* const* in_params_ptr = + cel_expr_Decl_FunctionDecl_Overload_params(in_overload, + &in_params_len); + const cel_Type** out_params_ptr; + cel_FunctionType* out_type = cel_FunctionType_New( + out_result, in_params_len, &out_params_ptr, arena); + if (CEL_UNLIKELY(out_type == cel_nullptr)) { + cel_OutOfMemoryStatus(status); + return cel_nullptr; + } + for (size_t j = 0; j < in_params_len; ++j) { + const cel_expr_Type* in_param = in_params_ptr[j]; + const cel_Type* out_param; + if (in_param != cel_nullptr) { + out_param = cel_Type_FromProto(in_param, arena, status); + if (CEL_UNLIKELY(out_param == cel_nullptr)) { + return cel_nullptr; + } + } else { + out_param = cel_DynType; + } + out_params_ptr[j] = out_param; + } + cel_FunctionOverloadDecl* out_overload = cel_FunctionOverloadDecl_New( + out_id, + cel_expr_Decl_FunctionDecl_Overload_is_instance_function( + in_overload) + ? cel_FunctionScope_kMember + : cel_FunctionScope_kGlobal, + out_type, arena); + if (CEL_UNLIKELY(out_overload == cel_nullptr)) { + cel_OutOfMemoryStatus(status); + return cel_nullptr; + } + cel_StringView out_doc; + if (CEL_UNLIKELY(!cel_Arena_StrDup( + arena, &out_doc, + cel_expr_Decl_FunctionDecl_Overload_doc(in_overload)))) { + cel_OutOfMemoryStatus(status); + return cel_nullptr; + } + cel_FunctionOverloadDecl_SetDoc(out_overload, out_doc); + CEL_USED(cel_FunctionDecl_AddOverload(out, out_overload)); + } + cel_StringView out_doc; + if (CEL_UNLIKELY(!cel_Arena_StrDup( + arena, &out_doc, + cel_expr_Decl_FunctionDecl_doc(in_function)))) { + cel_OutOfMemoryStatus(status); + return cel_nullptr; + } + cel_Decl_SetDoc(cel_Decl_UpCast(out), out_doc); + return cel_Decl_UpCast(out); + } + default: + cel_InvalidArgumentStatusF( + status, "cel: unexpected google.api.expr.Decl.decl_kind: %d", + decl_kind); + return cel_nullptr; + } +} diff --git a/cel-c/decl_proto_v1alpha1.cc b/cel-c/decl_proto_v1alpha1.cc new file mode 100644 index 0000000..fec5cba --- /dev/null +++ b/cel-c/decl_proto_v1alpha1.cc @@ -0,0 +1,187 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "cel-c/decl_proto_v1alpha1.h" + +#include + +#include "google/api/expr/v1alpha1/checked.upb.h" +#include "cel-c/arena.h" +#include "cel-c/assert.h" +#include "cel-c/config.h" +#include "cel-c/constant_proto_v1alpha1.h" +#include "cel-c/decl.h" +#include "cel-c/function_scope.h" +#include "cel-c/status.h" +#include "cel-c/string_view.h" +#include "cel-c/type.h" +#include "cel-c/type_proto_v1alpha1.h" + +extern "C" cel_Decl* cel_nullable cel_Decl_FromProtoV1Alpha1( + const google_api_expr_v1alpha1_Decl* cel_nonnull in, + cel_Arena* cel_nonnull arena, cel_Status* cel_nonnull status) { + CEL_ASSERT_NOT_NULL(in); + CEL_ASSERT_NOT_NULL(arena); + CEL_ASSERT(cel_Status_Ok(status)); + + const google_api_expr_v1alpha1_Decl_decl_kind_oneofcases decl_kind = + google_api_expr_v1alpha1_Decl_decl_kind_case(in); + switch (decl_kind) { + case google_api_expr_v1alpha1_Decl_decl_kind_ident: { + cel_StringView out_name; + if (CEL_UNLIKELY(!cel_Arena_StrDup( + arena, &out_name, google_api_expr_v1alpha1_Decl_name(in)))) { + cel_OutOfMemoryStatus(status); + return cel_nullptr; + } + cel_IdentDecl* out = cel_IdentDecl_New(out_name, arena); + if (CEL_UNLIKELY(out == cel_nullptr)) { + cel_OutOfMemoryStatus(status); + return cel_nullptr; + } + const google_api_expr_v1alpha1_Decl_IdentDecl* in_ident = + google_api_expr_v1alpha1_Decl_ident(in); + if (google_api_expr_v1alpha1_Decl_IdentDecl_has_type(in_ident)) { + const cel_Type* out_type = cel_Type_FromProtoV1Alpha1( + google_api_expr_v1alpha1_Decl_IdentDecl_type(in_ident), arena, + status); + if (CEL_UNLIKELY(out_type == cel_nullptr)) { + return cel_nullptr; + } + cel_IdentDecl_SetType(out, out_type); + } + if (google_api_expr_v1alpha1_Decl_IdentDecl_has_value(in_ident)) { + if (CEL_UNLIKELY(!cel_Constant_FromProtoV1Alpha1( + cel_IdentDecl_MutableValue(out), + google_api_expr_v1alpha1_Decl_IdentDecl_value(in_ident), arena, + status))) { + return cel_nullptr; + } + } + cel_StringView out_doc; + if (CEL_UNLIKELY(!cel_Arena_StrDup( + arena, &out_doc, + google_api_expr_v1alpha1_Decl_IdentDecl_doc(in_ident)))) { + cel_OutOfMemoryStatus(status); + return cel_nullptr; + } + cel_Decl_SetDoc(cel_Decl_UpCast(out), out_doc); + return cel_Decl_UpCast(out); + } + case google_api_expr_v1alpha1_Decl_decl_kind_function: { + cel_StringView out_name; + if (CEL_UNLIKELY(!cel_Arena_StrDup( + arena, &out_name, google_api_expr_v1alpha1_Decl_name(in)))) { + cel_OutOfMemoryStatus(status); + return cel_nullptr; + } + cel_FunctionDecl* out = cel_FunctionDecl_New(out_name, arena); + if (CEL_UNLIKELY(out == cel_nullptr)) { + cel_OutOfMemoryStatus(status); + return cel_nullptr; + } + const google_api_expr_v1alpha1_Decl_FunctionDecl* in_function = + google_api_expr_v1alpha1_Decl_function(in); + size_t in_overloads_len; + const google_api_expr_v1alpha1_Decl_FunctionDecl_Overload* const* + in_overloads_ptr = + google_api_expr_v1alpha1_Decl_FunctionDecl_overloads( + in_function, &in_overloads_len); + for (size_t i = 0; i < in_overloads_len; ++i) { + const google_api_expr_v1alpha1_Decl_FunctionDecl_Overload* in_overload = + in_overloads_ptr[i]; + cel_StringView out_id; + if (CEL_UNLIKELY(!cel_Arena_StrDup( + arena, &out_id, + google_api_expr_v1alpha1_Decl_FunctionDecl_Overload_overload_id( + in_overload)))) { + cel_OutOfMemoryStatus(status); + return cel_nullptr; + } + const cel_Type* out_result; + if (google_api_expr_v1alpha1_Decl_FunctionDecl_Overload_has_result_type( + in_overload)) { + out_result = cel_Type_FromProtoV1Alpha1( + google_api_expr_v1alpha1_Decl_FunctionDecl_Overload_result_type( + in_overload), + arena, status); + if (CEL_UNLIKELY(out_result == cel_nullptr)) { + return cel_nullptr; + } + } else { + out_result = cel_DynType; + } + size_t in_params_len; + const google_api_expr_v1alpha1_Type* const* in_params_ptr = + google_api_expr_v1alpha1_Decl_FunctionDecl_Overload_params( + in_overload, &in_params_len); + const cel_Type** out_params_ptr; + cel_FunctionType* out_type = cel_FunctionType_New( + out_result, in_params_len, &out_params_ptr, arena); + if (CEL_UNLIKELY(out_type == cel_nullptr)) { + cel_OutOfMemoryStatus(status); + return cel_nullptr; + } + for (size_t j = 0; j < in_params_len; ++j) { + const google_api_expr_v1alpha1_Type* in_param = in_params_ptr[j]; + const cel_Type* out_param; + if (in_param != cel_nullptr) { + out_param = cel_Type_FromProtoV1Alpha1(in_param, arena, status); + if (CEL_UNLIKELY(out_param == cel_nullptr)) { + return cel_nullptr; + } + } else { + out_param = cel_DynType; + } + out_params_ptr[j] = out_param; + } + cel_FunctionOverloadDecl* out_overload = cel_FunctionOverloadDecl_New( + out_id, + google_api_expr_v1alpha1_Decl_FunctionDecl_Overload_is_instance_function( + in_overload) + ? cel_FunctionScope_kMember + : cel_FunctionScope_kGlobal, + out_type, arena); + if (CEL_UNLIKELY(out_overload == cel_nullptr)) { + cel_OutOfMemoryStatus(status); + return cel_nullptr; + } + cel_StringView out_doc; + if (CEL_UNLIKELY(!cel_Arena_StrDup( + arena, &out_doc, + google_api_expr_v1alpha1_Decl_FunctionDecl_Overload_doc( + in_overload)))) { + cel_OutOfMemoryStatus(status); + return cel_nullptr; + } + cel_FunctionOverloadDecl_SetDoc(out_overload, out_doc); + CEL_USED(cel_FunctionDecl_AddOverload(out, out_overload)); + } + cel_StringView out_doc; + if (CEL_UNLIKELY(!cel_Arena_StrDup( + arena, &out_doc, + google_api_expr_v1alpha1_Decl_FunctionDecl_doc(in_function)))) { + cel_OutOfMemoryStatus(status); + return cel_nullptr; + } + cel_Decl_SetDoc(cel_Decl_UpCast(out), out_doc); + return cel_Decl_UpCast(out); + } + default: + cel_InvalidArgumentStatusF( + status, "cel: unexpected google.api.expr.v1alpha1.Decl.decl_kind: %d", + decl_kind); + return cel_nullptr; + } +} diff --git a/cel-c/duration.cc b/cel-c/duration.cc new file mode 100644 index 0000000..99efeec --- /dev/null +++ b/cel-c/duration.cc @@ -0,0 +1,109 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "cel-c/duration.h" + +#include +#include + +#include "cel-c/assert.h" +#include "cel-c/config.h" +#include "cel-c/src/ckdint.h" + +extern "C" bool cel_Duration_Normalize(int64_t* cel_nonnull sec, + int32_t* cel_nonnull nsec) { + int32_t rem; + + // Normalize nsec. + rem = *nsec / INT32_C(1000000000); + if (rem != 0) { + if (CEL_UNLIKELY(_cel_ckd_add(sec, *sec, (int64_t)rem))) { + return false; + } + *nsec %= INT32_C(1000000000); + } + + // Normalize sign. + if (*sec < 0 && *nsec > 0) { + if (CEL_UNLIKELY(_cel_ckd_add(sec, *sec, INT64_C(1)))) { + return false; + } + *nsec -= INT32_C(1000000000); + } else if (*sec > 0 && *nsec < 0) { + if (CEL_UNLIKELY(_cel_ckd_sub(sec, *sec, INT64_C(1)))) { + return false; + } + *nsec += INT32_C(1000000000); + } + + return cel_Duration_Valid(*sec, *nsec); +} + +extern "C" bool cel_Duration_Add(cel_Duration* cel_nonnull out, + cel_Duration lhs, cel_Duration rhs) { + CEL_ASSERT_NOT_NULL(out); + CEL_ASSERT(cel_Duration_Valid(lhs.sec, lhs.nsec)); + CEL_ASSERT(cel_Duration_Valid(rhs.sec, rhs.nsec)); + + int64_t lhs_sec = lhs.sec; + int64_t rhs_sec = rhs.sec; + int64_t sec; + int32_t nsec; + int32_t lhs_nsec = lhs.nsec; + int32_t rhs_nsec = rhs.nsec; + + if (CEL_UNLIKELY(_cel_ckd_add(&sec, lhs_sec, rhs_sec))) { + return false; + } + + if (CEL_UNLIKELY(_cel_ckd_add(&nsec, lhs_nsec, rhs_nsec))) { + return false; + } + + if (CEL_UNLIKELY(!cel_Duration_Normalize(&sec, &nsec))) { + return false; + } + + *out = cel_Duration_FromUnix(sec, nsec); + return true; +} + +extern "C" bool cel_Duration_Sub(cel_Duration* cel_nonnull out, + cel_Duration lhs, cel_Duration rhs) { + CEL_ASSERT_NOT_NULL(out); + CEL_ASSERT(cel_Duration_Valid(lhs.sec, lhs.nsec)); + CEL_ASSERT(cel_Duration_Valid(rhs.sec, rhs.nsec)); + + int64_t lhs_sec = lhs.sec; + int64_t rhs_sec = rhs.sec; + int64_t sec; + int32_t nsec; + int32_t lhs_nsec = lhs.nsec; + int32_t rhs_nsec = rhs.nsec; + + if (CEL_UNLIKELY(_cel_ckd_sub(&sec, lhs_sec, rhs_sec))) { + return false; + } + + if (CEL_UNLIKELY(_cel_ckd_sub(&nsec, lhs_nsec, rhs_nsec))) { + return false; + } + + if (CEL_UNLIKELY(!cel_Duration_Normalize(&sec, &nsec))) { + return false; + } + + *out = cel_Duration_FromUnix(sec, nsec); + return true; +} diff --git a/cel-c/duration_proto.cc b/cel-c/duration_proto.cc new file mode 100644 index 0000000..df08852 --- /dev/null +++ b/cel-c/duration_proto.cc @@ -0,0 +1,52 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "cel-c/duration_proto.h" + +#include +#include + +#include "google/protobuf/duration.upb.h" +#include "cel-c/assert.h" +#include "cel-c/config.h" +#include "cel-c/duration.h" + +extern "C" void cel_Duration_ToProto(cel_Duration in, + CEL_NONNULL(google_protobuf_Duration*) + out) { + CEL_ASSERT_NOT_NULL(out); + + int64_t sec; + int32_t nsec; + cel_Duration_ToUnix(in, &sec, &nsec); + google_protobuf_Duration_set_seconds(out, sec); + google_protobuf_Duration_set_nanos(out, nsec); +} + +extern "C" bool cel_Duration_FromProto( + CEL_NONNULL(cel_Duration*) out, + CEL_NONNULL(const google_protobuf_Duration*) in) { + CEL_ASSERT_NOT_NULL(out); + CEL_ASSERT_NOT_NULL(in); + + int64_t sec; + int32_t nsec; + sec = google_protobuf_Duration_seconds(in); + nsec = google_protobuf_Duration_nanos(in); + if (!cel_Duration_Normalize(&sec, &nsec)) { + return false; + } + *out = cel_Duration_FromUnix(sec, nsec); + return true; +} diff --git a/cel-c/error.cc b/cel-c/error.cc new file mode 100644 index 0000000..6ea2e61 --- /dev/null +++ b/cel-c/error.cc @@ -0,0 +1,297 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "cel-c/error.h" + +#include +#include +#include +#include +#include + +#include "cel-c/arena.h" +#include "cel-c/assert.h" +#include "cel-c/config.h" +#include "cel-c/error_code.h" +#include "cel-c/error_space.h" +#include "cel-c/status_code.h" +#include "cel-c/string_view.h" + +typedef struct _cel_ErrorPayload _cel_ErrorPayload; + +struct _cel_ErrorPayload { + struct _cel_ErrorPayload* cel_nullable prev; + struct _cel_ErrorPayload* cel_nullable next; + cel_StringView type_url; + cel_StringView value; +}; + +struct cel_Error { + cel_StringView message; + const cel_ErrorSpace* cel_nullable space; + int raw_code; + cel_ErrorCode code; + size_t payloads_len; + _cel_ErrorPayload* cel_nullable payloads_head; + _cel_ErrorPayload* cel_nullable payloads_tail; +}; + +static const cel_Error _cel_DefaultError = { + .message = CEL_STRINGVIEW_C(""), + .space = cel_nullptr, + .raw_code = cel_ErrorCode_kUnknown, + .code = cel_ErrorCode_kUnknown, + .payloads_len = 0, + .payloads_head = cel_nullptr, + .payloads_tail = cel_nullptr, +}; + +extern "C" const cel_Error* const cel_nonnull cel_DefaultError = + &_cel_DefaultError; + +extern "C" cel_Error* cel_nullable cel_Error_New(cel_Arena* cel_nonnull arena) { + CEL_ASSERT_NOT_NULL(arena); + + cel_Error* error = + (cel_Error*)cel_Arena_Malloc(arena, sizeof(cel_Error), cel_nullptr); + if (CEL_LIKELY(error != cel_nullptr)) { + memset(error, 0, sizeof(*error)); + error->space = cel_CanonicalErrorSpace; + error->raw_code = cel_ErrorCode_kUnknown; + error->code = (cel_ErrorCode)error->raw_code; + } + return error; +} + +extern "C" void cel_Error_Clear(cel_Error* cel_nonnull error) { + CEL_ASSERT_NOT_NULL(error); + + error->message = cel_StringView_FromString(""); + error->space = cel_CanonicalErrorSpace; + error->raw_code = cel_ErrorCode_kUnknown; + error->code = (cel_ErrorCode)error->raw_code; + cel_Error_ClearPayloads(error); +} + +extern "C" cel_ErrorCode cel_Error_CanonicalCode( + const cel_Error* cel_nonnull error) { + CEL_ASSERT_NOT_NULL(error); + + return error->code; +} + +extern "C" int cel_Error_Code(const cel_Error* cel_nonnull error) { + CEL_ASSERT_NOT_NULL(error); + + return error->raw_code; +} + +extern "C" void cel_Error_SetCode(cel_Error* cel_nonnull error, + const cel_ErrorSpace* cel_nonnull space, + int code) { + CEL_ASSERT_NOT_NULL(error); + CEL_ASSERT_NOT_NULL(space); + + cel_StatusCode canonical_code = cel_ErrorSpace_Canonical(space, code); + CEL_ASSERT_NE(canonical_code, cel_StatusCode_kOk); + + if (CEL_UNLIKELY(canonical_code == cel_StatusCode_kOk)) { + space = cel_CanonicalErrorSpace; + code = cel_StatusCode_kUnknown; + canonical_code = cel_StatusCode_kUnknown; + } + + error->space = space; + error->raw_code = code; + error->code = (cel_ErrorCode)canonical_code; +} + +extern "C" const cel_ErrorSpace* cel_nonnull +cel_Error_Space(const cel_Error* cel_nonnull error) { + CEL_ASSERT_NOT_NULL(error); + + const cel_ErrorSpace* space = error->space; + if (space == cel_nullptr) { + space = cel_CanonicalErrorSpace; + } + return space; +} + +extern "C" cel_StringView cel_Error_Message( + const cel_Error* cel_nonnull error) { + CEL_ASSERT_NOT_NULL(error); + + return error->message; +} + +extern "C" void cel_Error_SetMessage(cel_Error* cel_nonnull error, + cel_StringView message) { + CEL_ASSERT_NOT_NULL(error); + + error->message = message; +} + +extern "C" bool cel_Error_VFormatMessage(cel_Error* cel_nonnull error, + cel_Arena* cel_nonnull arena, + const char* cel_nonnull fmt, + va_list args) { + CEL_ASSERT_NOT_NULL(error); + CEL_ASSERT_NOT_NULL(arena); + CEL_ASSERT_NOT_NULL(fmt); + + cel_StringView message; + if (!cel_Arena_VPrintF(arena, &message, fmt, args)) { + return false; + } + cel_Error_SetMessage(error, message); + return true; +} + +extern "C" bool cel_Error_NextPayload( + const cel_Error* cel_nonnull error, cel_StringView* cel_nullable type_url, + cel_StringView* cel_nullable value, + cel_ErrorPayloadIterator* cel_nonnull iter) { + CEL_ASSERT_NOT_NULL(error); + CEL_ASSERT_NOT_NULL(iter); + const _cel_ErrorPayload* payload; + if (iter->rep == (uintptr_t)-1) { + payload = error->payloads_head; + } else { + payload = (const _cel_ErrorPayload*)iter->rep; + payload = payload->next; + } + + if (payload == cel_nullptr) { + return false; + } + + if (type_url != cel_nullptr) { + *type_url = payload->type_url; + } + if (value != cel_nullptr) { + *value = payload->value; + } + iter->rep = (uintptr_t)payload; + return true; +} + +extern "C" size_t cel_Error_Payloads(const cel_Error* cel_nonnull error) { + CEL_ASSERT_NOT_NULL(error); + + return error->payloads_len; +} + +extern "C" bool cel_Error_GetPayload(const cel_Error* cel_nonnull error, + cel_StringView type_url, + cel_StringView* cel_nullable value) { + CEL_ASSERT_NOT_NULL(error); + + if (cel_StringView_Empty(type_url)) { + return false; + } + + const _cel_ErrorPayload* payload = error->payloads_head; + while (payload != cel_nullptr) { + if (cel_StringView_Equals(type_url, payload->type_url)) { + if (value != cel_nullptr) { + *value = payload->value; + } + return true; + } + payload = payload->next; + } + return false; +} + +extern "C" bool cel_Error_SetPayload(cel_Error* cel_nonnull error, + cel_StringView type_url, + cel_StringView value, + cel_Arena* cel_nonnull arena) { + CEL_ASSERT_NOT_NULL(error); + CEL_ASSERT_NOT(cel_StringView_Empty(type_url)); + CEL_ASSERT_NOT_NULL(arena); + + _cel_ErrorPayload* payload = error->payloads_head; + while (payload != cel_nullptr) { + if (cel_StringView_Equals(type_url, payload->type_url)) { + payload->value = value; + return true; + } + payload = payload->next; + } + + payload = (_cel_ErrorPayload*)cel_Arena_Malloc( + arena, sizeof(_cel_ErrorPayload), cel_nullptr); + if (CEL_UNLIKELY(payload == cel_nullptr)) { + return false; + } + memset(payload, 0, sizeof(*payload)); + payload->type_url = type_url; + payload->value = value; + payload->prev = error->payloads_tail; + + if (error->payloads_tail != cel_nullptr) { + error->payloads_tail->next = payload; + } else { + CEL_ASSERT_NULL(error->payloads_head); + error->payloads_head = payload; + } + error->payloads_tail = payload; + ++error->payloads_len; + return true; +} + +extern "C" bool cel_Error_DeletePayload(cel_Error* cel_nonnull error, + cel_StringView type_url, + cel_StringView* cel_nullable value) { + CEL_ASSERT_NOT_NULL(error); + CEL_ASSERT_NOT(cel_StringView_Empty(type_url)); + + if (cel_StringView_Empty(type_url)) { + return false; + } + + _cel_ErrorPayload* payload = error->payloads_head; + while (payload != cel_nullptr) { + if (cel_StringView_Equals(type_url, payload->type_url)) { + if (value != cel_nullptr) { + *value = payload->value; + } + if (payload->prev != cel_nullptr) { + payload->prev->next = payload->next; + } else { + CEL_ASSERT_EQ(payload, error->payloads_head); + error->payloads_head = payload->next; + } + if (payload->next != cel_nullptr) { + payload->next->prev = payload->prev; + } else { + CEL_ASSERT_EQ(payload, error->payloads_tail); + error->payloads_tail = payload->prev; + } + --error->payloads_len; + return true; + } + payload = payload->next; + } + return false; +} + +extern "C" void cel_Error_ClearPayloads(cel_Error* cel_nonnull error) { + CEL_ASSERT_NOT_NULL(error); + + error->payloads_len = 0; + error->payloads_head = cel_nullptr; + error->payloads_tail = cel_nullptr; +} diff --git a/cel-c/error_code.cc b/cel-c/error_code.cc new file mode 100644 index 0000000..ce3487b --- /dev/null +++ b/cel-c/error_code.cc @@ -0,0 +1,96 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "cel-c/error_code.h" + +#include "cel-c/assert.h" +#include "cel-c/config.h" + +extern "C" CEL_NONNULL(const char*) cel_ErrorCode_Name(cel_ErrorCode code) { + CEL_ASSERT_NE(code, 0); + + switch (code) { + case cel_ErrorCode_kCancelled: + return "CANCELLED"; + case cel_ErrorCode_kInvalidArgument: + return "INVALID_ARGUMENT"; + case cel_ErrorCode_kDeadlineExceeded: + return "DEADLINE_EXCEEDED"; + case cel_ErrorCode_kNotFound: + return "NOT_FOUND"; + case cel_ErrorCode_kAlreadyExists: + return "ALREADY_EXISTS"; + case cel_ErrorCode_kPermissionDenied: + return "PERMISSION_DENIED"; + case cel_ErrorCode_kResourceExhausted: + return "RESOURCE_EXHAUSTED"; + case cel_ErrorCode_kFailedPrecondition: + return "FAILED_PRECONDITION"; + case cel_ErrorCode_kAborted: + return "ABORTED"; + case cel_ErrorCode_kOutOfRange: + return "OUT_OF_RANGE"; + case cel_ErrorCode_kUnimplemented: + return "UNIMPLEMENTED"; + case cel_ErrorCode_kInternal: + return "INTERNAL"; + case cel_ErrorCode_kUnavailable: + return "UNAVAILABLE"; + case cel_ErrorCode_kDataLoss: + return "DATA_LOSS"; + case cel_ErrorCode_kUnauthenticated: + return "UNAUTHENTICATED"; + default: + return "UNKNOWN"; + } +} + +extern "C" CEL_NONNULL(const char*) cel_ErrorCode_Message(cel_ErrorCode code) { + switch (code) { + case cel_ErrorCode_kCancelled: + return "the operation was cancelled"; + case cel_ErrorCode_kUnknown: + return "unknown error"; + case cel_ErrorCode_kInvalidArgument: + return "request contains an invalid argument"; + case cel_ErrorCode_kDeadlineExceeded: + return "deadline expired before operation could complete"; + case cel_ErrorCode_kNotFound: + return "requested entity was not found"; + case cel_ErrorCode_kAlreadyExists: + return "requested entity already exists"; + case cel_ErrorCode_kPermissionDenied: + return "caller does not have permission"; + case cel_ErrorCode_kResourceExhausted: + return "resource has been exhausted"; + case cel_ErrorCode_kFailedPrecondition: + return "precondition check failed"; + case cel_ErrorCode_kAborted: + return "operation was aborted"; + case cel_ErrorCode_kOutOfRange: + return "operation was attempted past the valid range"; + case cel_ErrorCode_kUnimplemented: + return "operation is not implemented, supported, or enabled"; + case cel_ErrorCode_kInternal: + return "internal error encountered"; + case cel_ErrorCode_kUnavailable: + return "service is currently unavailable"; + case cel_ErrorCode_kDataLoss: + return "unrecoverable data loss or corruption"; + case cel_ErrorCode_kUnauthenticated: + return "request is missing required authentication credential"; + default: + return "there was an error, that's all we know"; + } +} diff --git a/cel-c/error_code_test.cc b/cel-c/error_code_test.cc index 83a4707..c2be7d9 100644 --- a/cel-c/error_code_test.cc +++ b/cel-c/error_code_test.cc @@ -14,8 +14,6 @@ #include "cel-c/error_code.h" -#include - #include "gtest/gtest.h" namespace { @@ -45,10 +43,6 @@ TEST(ErrorCode, Name) { EXPECT_STREQ(cel_ErrorCode_Name(cel_ErrorCode_kDataLoss), "DATA_LOSS"); EXPECT_STREQ(cel_ErrorCode_Name(cel_ErrorCode_kUnauthenticated), "UNAUTHENTICATED"); - - EXPECT_STREQ(cel_ErrorCode_Name( - static_cast(std::numeric_limits::max())), - "UNKNOWN"); } } // namespace diff --git a/cel-c/error_proto.cc b/cel-c/error_proto.cc new file mode 100644 index 0000000..190574e --- /dev/null +++ b/cel-c/error_proto.cc @@ -0,0 +1,105 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "cel-c/error_proto.h" + +#include +#include + +#include "google/protobuf/any.upb.h" +#include "google/rpc/status.upb.h" +#include "cel-c/arena.h" +#include "cel-c/assert.h" +#include "cel-c/config.h" +#include "cel-c/error.h" +#include "cel-c/error_code_proto.h" +#include "cel-c/string_view.h" +#include "upb/base/string_view.h" + +static bool _cel_Error_StrDup(cel_StringView in, cel_Arena* cel_nonnull arena, + cel_StringView* cel_nonnull out) { + if (cel_StringView_Empty(in)) { + *out = cel_StringView_FromString(""); + return true; + } + char* copied = + (char*)cel_Arena_Malloc(arena, cel_StringView_Size(in), cel_nullptr); + if (copied == cel_nullptr) { + return false; + } + memcpy(copied, cel_StringView_Data(in), cel_StringView_Size(in)); + *out = cel_StringView_FromArray(copied, cel_StringView_Size(in)); + return true; +} + +extern "C" bool cel_Error_ToProto(const cel_Error* cel_nonnull in, + cel_Arena* cel_nonnull arena, + google_rpc_Status* cel_nonnull out) { + CEL_ASSERT_NOT_NULL(in); + CEL_ASSERT_NOT_NULL(arena); + CEL_ASSERT_NOT_NULL(out); + + google_rpc_Status_set_code( + out, cel_ErrorCode_ToProto(cel_Error_CanonicalCode(in))); + cel_StringView in_message = cel_Error_Message(in); + upb_StringView out_message; + if (!_cel_Error_StrDup(in_message, arena, &out_message)) { + return false; + } + google_rpc_Status_set_message(out, out_message); + + size_t in_payloads_len = cel_Error_Payloads(in); + if (in_payloads_len == 0) { + google_rpc_Status_clear_details(out); + } else { + google_protobuf_Any** out_payloads = + google_rpc_Status_resize_details(out, in_payloads_len, arena); + if (out_payloads == cel_nullptr) { + return false; + } + size_t i = 0; + cel_StringView in_type_url; + cel_StringView in_value; + cel_ErrorPayloadIterator iter = cel_Error_BeginPayloads(in); + bool oom = false; + while (cel_Error_NextPayload(in, &in_type_url, &in_value, &iter)) { + google_protobuf_Any* out_payload = google_protobuf_Any_new(arena); + if (out_payload == cel_nullptr) { + oom = true; + break; + } + out_payloads[i++] = out_payload; + upb_StringView out_type_url; + if (!_cel_Error_StrDup(in_type_url, arena, &out_type_url)) { + oom = true; + break; + } + google_protobuf_Any_set_type_url(out_payload, out_type_url); + upb_StringView out_value; + if (!_cel_Error_StrDup(in_value, arena, &out_value)) { + oom = true; + break; + } + google_protobuf_Any_set_value(out_payload, out_value); + } + if (i < in_payloads_len) { + google_rpc_Status_resize_details(out, i, arena); + } + if (oom) { + return false; + } + } + + return true; +} diff --git a/cel-c/error_space.cc b/cel-c/error_space.cc new file mode 100644 index 0000000..1004244 --- /dev/null +++ b/cel-c/error_space.cc @@ -0,0 +1,686 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "cel-c/error_space.h" + +#include +#include +#include + +#ifdef _WIN32 +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN 1 +#endif +#ifndef NOMINMAX +#define NOMINMAX 1 +#endif +#include +#include +#endif + +#include "cel-c/assert.h" +#include "cel-c/config.h" +#include "cel-c/cstring_view.h" +#include "cel-c/status_code.h" + +extern "C" int cel_GenericErrorSpace_CurrentCode() { return errno; } + +extern "C" int cel_SystemErrorSpace_CurrentCode() { +#ifndef _WIN32 + return cel_GenericErrorSpace_CurrentCode(); +#else + return (int)GetLastError(); +#endif +} + +static bool _cel_CanonicalErrorSpace_OutOfMemory( + CEL_NONNULL(const cel_ErrorSpace*) space, int code) { + CEL_ASSERT_EQ(space, cel_CanonicalErrorSpace); + + // The canonical error space does not have an out of memory condition. + return false; +} + +static cel_StatusCode _cel_CanonicalErrorSpace_Canonical( + CEL_NONNULL(const cel_ErrorSpace*) space, int code) { + CEL_ASSERT_EQ(space, cel_CanonicalErrorSpace); + + return (cel_StatusCode)code; +} + +static int _cel_CanonicalErrorSpace_Message(CEL_NONNULL(const cel_ErrorSpace*) + space, + int code, CEL_NONNULL(char*) buf, + size_t buflen) { + CEL_ASSERT_EQ(space, cel_CanonicalErrorSpace); + CEL_ASSERT_NOT_NULL(buf); + CEL_ASSERT_GT(buflen, 0); + + if (CEL_UNLIKELY(buflen == 0)) { + return 0; + } + if (code == 0) { + buf[0] = '\0'; + return 0; + } + const char* desc = cel_StatusCode_Message(static_cast(code)); + if (desc == cel_nullptr) { + buf[0] = '\0'; + return 0; + } + size_t desc_len = strlen(desc); + if (desc_len == 0) { + buf[0] = '\0'; + return 0; + } + size_t to_copy = desc_len >= buflen ? buflen - 1 : desc_len; + memcpy(buf, desc, to_copy); + buf[to_copy] = '\0'; + return 0; +} + +static const cel_ErrorSpaceVTable _cel_CanonicalErrorSpaceVTable = { + .name = CEL_CSTRINGVIEW_C("canonical"), + .OutOfMemory = _cel_CanonicalErrorSpace_OutOfMemory, + .Canonical = _cel_CanonicalErrorSpace_Canonical, + .Message = _cel_CanonicalErrorSpace_Message, +}; + +static const cel_ErrorSpace _cel_CanonicalErrorSpace = { + .vtable = &_cel_CanonicalErrorSpaceVTable, +}; + +extern "C" CEL_NONNULL(const cel_ErrorSpace*) +const cel_CanonicalErrorSpace = &_cel_CanonicalErrorSpace; + +static cel_StatusCode _cel_StatusCode_FromPosix(int code) { + switch (code) { + case 0: + return cel_StatusCode_kOk; + case EINVAL: + CEL_ATTRIBUTE_FALLTHROUGH; + case ENAMETOOLONG: + CEL_ATTRIBUTE_FALLTHROUGH; + case E2BIG: + CEL_ATTRIBUTE_FALLTHROUGH; + case EDESTADDRREQ: + CEL_ATTRIBUTE_FALLTHROUGH; + case EDOM: + CEL_ATTRIBUTE_FALLTHROUGH; + case EFAULT: + CEL_ATTRIBUTE_FALLTHROUGH; + case EILSEQ: + CEL_ATTRIBUTE_FALLTHROUGH; + case ENOPROTOOPT: + CEL_ATTRIBUTE_FALLTHROUGH; + case ENOTSOCK: + CEL_ATTRIBUTE_FALLTHROUGH; + case ENOTTY: + CEL_ATTRIBUTE_FALLTHROUGH; + case EPROTOTYPE: + CEL_ATTRIBUTE_FALLTHROUGH; + case ESPIPE: + return cel_StatusCode_kInvalidArgument; + case ETIMEDOUT: + return cel_StatusCode_kDeadlineExceeded; + case ENODEV: + CEL_ATTRIBUTE_FALLTHROUGH; + case ENOENT: + CEL_ATTRIBUTE_FALLTHROUGH; +#ifdef ENOMEDIUM + case ENOMEDIUM: + CEL_ATTRIBUTE_FALLTHROUGH; +#endif + case ENXIO: + CEL_ATTRIBUTE_FALLTHROUGH; + case ESRCH: + return cel_StatusCode_kNotFound; + case EEXIST: + CEL_ATTRIBUTE_FALLTHROUGH; + case EADDRNOTAVAIL: + CEL_ATTRIBUTE_FALLTHROUGH; +#ifdef ENOTUNIQ + case ENOTUNIQ: + CEL_ATTRIBUTE_FALLTHROUGH; +#endif + case EALREADY: + return cel_StatusCode_kAlreadyExists; + case EPERM: + CEL_ATTRIBUTE_FALLTHROUGH; + case EACCES: + CEL_ATTRIBUTE_FALLTHROUGH; +#ifdef ENOKEY + case ENOKEY: + CEL_ATTRIBUTE_FALLTHROUGH; +#endif + case EROFS: + return cel_StatusCode_kPermissionDenied; + case ENOTEMPTY: + CEL_ATTRIBUTE_FALLTHROUGH; + case EISDIR: + CEL_ATTRIBUTE_FALLTHROUGH; + case ENOTDIR: + CEL_ATTRIBUTE_FALLTHROUGH; + case EADDRINUSE: + CEL_ATTRIBUTE_FALLTHROUGH; + case EBADF: + CEL_ATTRIBUTE_FALLTHROUGH; +#ifdef EBADFD + case EBADFD: + CEL_ATTRIBUTE_FALLTHROUGH; +#endif + case ECHILD: + CEL_ATTRIBUTE_FALLTHROUGH; + case EISCONN: + CEL_ATTRIBUTE_FALLTHROUGH; +#ifdef EISNAM + case EISNAM: + CEL_ATTRIBUTE_FALLTHROUGH; +#endif +#ifdef ENOTBLK + case ENOTBLK: + CEL_ATTRIBUTE_FALLTHROUGH; +#endif + case ENOTCONN: + CEL_ATTRIBUTE_FALLTHROUGH; + case EPIPE: + CEL_ATTRIBUTE_FALLTHROUGH; +#ifdef ESHUTDOWN + case ESHUTDOWN: + CEL_ATTRIBUTE_FALLTHROUGH; +#endif + case ETXTBSY: + CEL_ATTRIBUTE_FALLTHROUGH; +#ifdef EUNATCH + case EUNATCH: + CEL_ATTRIBUTE_FALLTHROUGH; +#endif + case EBUSY: + return cel_StatusCode_kFailedPrecondition; + case ENOSPC: + CEL_ATTRIBUTE_FALLTHROUGH; +#ifdef EDQUOT + case EDQUOT: + CEL_ATTRIBUTE_FALLTHROUGH; +#endif + case EMFILE: + CEL_ATTRIBUTE_FALLTHROUGH; + case EMLINK: + CEL_ATTRIBUTE_FALLTHROUGH; + case ENFILE: + CEL_ATTRIBUTE_FALLTHROUGH; + case ENOBUFS: + CEL_ATTRIBUTE_FALLTHROUGH; +#ifdef ENODATA + case ENODATA: + CEL_ATTRIBUTE_FALLTHROUGH; +#endif +#ifdef EUSERS + case EUSERS: + CEL_ATTRIBUTE_FALLTHROUGH; +#endif + case ENOMEM: + return cel_StatusCode_kResourceExhausted; +#ifdef ECHRNG + case ECHRNG: + CEL_ATTRIBUTE_FALLTHROUGH; +#endif + case EFBIG: + CEL_ATTRIBUTE_FALLTHROUGH; + case EOVERFLOW: + CEL_ATTRIBUTE_FALLTHROUGH; + case ERANGE: + return cel_StatusCode_kOutOfRange; +#ifdef ENOPKG + case ENOPKG: + CEL_ATTRIBUTE_FALLTHROUGH; +#endif + case ENOSYS: + CEL_ATTRIBUTE_FALLTHROUGH; + case ENOTSUP: + CEL_ATTRIBUTE_FALLTHROUGH; + case EAFNOSUPPORT: + CEL_ATTRIBUTE_FALLTHROUGH; +#ifdef EPFNOSUPPORT + case EPFNOSUPPORT: + CEL_ATTRIBUTE_FALLTHROUGH; +#endif + case EPROTONOSUPPORT: + CEL_ATTRIBUTE_FALLTHROUGH; +#ifdef ESOCKTNOSUPPORT + case ESOCKTNOSUPPORT: + CEL_ATTRIBUTE_FALLTHROUGH; +#endif + case EXDEV: + return cel_StatusCode_kUnimplemented; + case EAGAIN: + CEL_ATTRIBUTE_FALLTHROUGH; +#ifdef ECOMM + case ECOMM: + CEL_ATTRIBUTE_FALLTHROUGH; +#endif + case ECONNREFUSED: + CEL_ATTRIBUTE_FALLTHROUGH; + case ECONNABORTED: + CEL_ATTRIBUTE_FALLTHROUGH; + case ECONNRESET: + CEL_ATTRIBUTE_FALLTHROUGH; + case EINTR: + CEL_ATTRIBUTE_FALLTHROUGH; +#ifdef EHOSTDOWN + case EHOSTDOWN: + CEL_ATTRIBUTE_FALLTHROUGH; +#endif + case EHOSTUNREACH: + CEL_ATTRIBUTE_FALLTHROUGH; + case ENETDOWN: + CEL_ATTRIBUTE_FALLTHROUGH; + case ENETRESET: + CEL_ATTRIBUTE_FALLTHROUGH; + case ENETUNREACH: + CEL_ATTRIBUTE_FALLTHROUGH; + case ENOLCK: + CEL_ATTRIBUTE_FALLTHROUGH; +#ifdef ENONET + case ENONET: + CEL_ATTRIBUTE_FALLTHROUGH; +#endif + case ENOLINK: + return cel_StatusCode_kUnavailable; +#ifdef ESTALE + case ESTALE: + CEL_ATTRIBUTE_FALLTHROUGH; +#endif + case EDEADLK: + return cel_StatusCode_kAborted; + case ECANCELED: + return cel_StatusCode_kCancelled; + default: + return cel_StatusCode_kUnknown; + } +} + +static bool _cel_GenericErrorSpace_OutOfMemory( + CEL_NONNULL(const cel_ErrorSpace*) space, int code) { + CEL_ASSERT_EQ(space, cel_GenericErrorSpace); + + return code == ENOMEM; +} + +static cel_StatusCode _cel_GenericErrorSpace_Canonical( + CEL_NONNULL(const cel_ErrorSpace*) space, int code) { + CEL_ASSERT_EQ(space, cel_GenericErrorSpace); + + return _cel_StatusCode_FromPosix(code); +} + +static int _cel_GenericErrorSpace_Message(CEL_NONNULL(const cel_ErrorSpace*) + space, + int code, CEL_NONNULL(char*) buf, + size_t buflen) { + CEL_ASSERT_EQ(space, cel_GenericErrorSpace); + CEL_ASSERT_NOT_NULL(buf); + CEL_ASSERT_GT(buflen, 0); + + if (CEL_UNLIKELY(buflen == 0)) { + return 0; + } + if (code == 0) { + buf[0] = '\0'; + return 0; + } +#if defined(__GLIBC__) && defined(__GLIBC_MINOR__) && \ + (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ > 31)) + const char* desc = strerrordesc_np(code); + if (desc == cel_nullptr) { + buf[0] = '\0'; + return 0; + } + size_t desc_len = strlen(desc); + if (desc_len == 0) { + buf[0] = '\0'; + return 0; + } + size_t to_copy = desc_len >= buflen ? buflen - 1 : desc_len; + memcpy(buf, desc, to_copy); + buf[to_copy] = '\0'; + return 0; +#elif defined(_WIN32) + if (code < 0 || code >= _sys_nerr) { + return EINVAL; + } + const char* desc = _sys_errlist[code]; + if (desc == cel_nullptr) { + buf[0] = '\0'; + return 0; + } + size_t desc_len = strlen(desc); + if (desc_len == 0) { + buf[0] = '\0'; + return 0; + } + size_t to_copy = desc_len >= buflen ? buflen - 1 : desc_len; + memcpy(buf, desc, to_copy); + buf[to_copy] = '\0'; + return 0; +#elif defined(__GLIBC__) && defined(_GNU_SOURCE) && _GNU_SOURCE + // GNU extension + char* desc = strerror_r(code, buf, buflen); + if (desc == cel_nullptr) { + buf[0] = '\0'; + return 0; + } + if (desc == buf) { + return (ptrdiff_t)strnlen(buf, buflen); + } + size_t desc_len = strlen(desc); + if (desc_len == 0) { + buf[0] = '\0'; + return 0; + } + size_t to_copy = desc_len >= buflen ? buflen - 1 : desc_len; + memcpy(buf, desc, to_copy); + buf[to_copy] = '\0'; + return 0; +#elif defined(_POSIX_C_SOURCE) && (_POSIX_C_SOURCE >= 200112L) && \ + (!defined(_GNU_SOURCE) || !_GNU_SOURCE) + // XSI compliant. + int err = strerror_r(code, buf, buflen); + if (err) { +#if defined(__GLIBC__) && defined(__GLIBC_MINOR__) && \ + (__GLIBC__ < 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ < 13)) + if (err == -1) { + err = errno; + } +#endif + return err; + } + return 0; +#else + // No other alternative, just use strerror and hope it is thread-safe. + const char* desc = strerror(code); + if (desc == cel_nullptr) { + buf[0] = '\0'; + return 0; + } + size_t desc_len = strlen(desc); + if (desc_len == 0) { + buf[0] = '\0'; + return 0; + } + size_t to_copy = desc_len >= buflen ? buflen - 1 : desc_len; + memcpy(buf, desc, to_copy); + buf[to_copy] = '\0'; + return 0; +#endif +} + +static const cel_ErrorSpaceVTable _cel_GenericErrorSpaceVTable = { + .name = CEL_CSTRINGVIEW_C("generic"), + .OutOfMemory = _cel_GenericErrorSpace_OutOfMemory, + .Canonical = _cel_GenericErrorSpace_Canonical, + .Message = _cel_GenericErrorSpace_Message, +}; + +static const cel_ErrorSpace _cel_GenericErrorSpace = { + .vtable = &_cel_GenericErrorSpaceVTable, +}; + +extern "C" CEL_NONNULL(const cel_ErrorSpace*) +const cel_GenericErrorSpace = &_cel_GenericErrorSpace; + +static bool _cel_SystemErrorSpace_OutOfMemory(CEL_NONNULL(const cel_ErrorSpace*) + space, + int code) { + CEL_ASSERT_EQ(space, cel_SystemErrorSpace); + +#ifndef _WIN32 + return cel_ErrorSpace_OutOfMemory(cel_GenericErrorSpace, code); +#else + return code == ERROR_NOT_ENOUGH_MEMORY || code == ERROR_OUTOFMEMORY; +#endif +} + +static cel_StatusCode _cel_SystemErrorSpace_Canonical( + CEL_NONNULL(const cel_ErrorSpace*) space, int code) { + CEL_ASSERT_EQ(space, cel_SystemErrorSpace); + +#ifndef _WIN32 + return cel_ErrorSpace_Canonical(cel_GenericErrorSpace, code); +#else + // Borrowed from std::system_category() in MSVC STL. + switch (code) { + case 0: + return cel_StatusCode_kOk; + case ERROR_INVALID_FUNCTION: + return _cel_StatusCode_FromPosix(ENOSYS); + case ERROR_FILE_NOT_FOUND: + return _cel_StatusCode_FromPosix(ENOENT); + case ERROR_PATH_NOT_FOUND: + return _cel_StatusCode_FromPosix(ENOENT); + case ERROR_TOO_MANY_OPEN_FILES: + return _cel_StatusCode_FromPosix(ENFILE); + case ERROR_ACCESS_DENIED: + return _cel_StatusCode_FromPosix(EACCES); + case ERROR_INVALID_HANDLE: + return _cel_StatusCode_FromPosix(EINVAL); + case ERROR_NOT_ENOUGH_MEMORY: + return _cel_StatusCode_FromPosix(ENOMEM); + case ERROR_INVALID_ACCESS: + return _cel_StatusCode_FromPosix(EACCES); + case ERROR_OUTOFMEMORY: + return _cel_StatusCode_FromPosix(ENOMEM); + case ERROR_INVALID_DRIVE: + return _cel_StatusCode_FromPosix(ENODEV); + case ERROR_CURRENT_DIRECTORY: + return _cel_StatusCode_FromPosix(EACCES); + case ERROR_NOT_SAME_DEVICE: + return _cel_StatusCode_FromPosix(EXDEV); + case ERROR_WRITE_PROTECT: + return _cel_StatusCode_FromPosix(EACCES); + case ERROR_BAD_UNIT: + return _cel_StatusCode_FromPosix(ENODEV); + case ERROR_NOT_READY: + return _cel_StatusCode_FromPosix(EAGAIN); + case ERROR_SEEK: + return _cel_StatusCode_FromPosix(EIO); + case ERROR_WRITE_FAULT: + return _cel_StatusCode_FromPosix(EIO); + case ERROR_READ_FAULT: + return _cel_StatusCode_FromPosix(EIO); + case ERROR_SHARING_VIOLATION: + return _cel_StatusCode_FromPosix(EACCES); + case ERROR_LOCK_VIOLATION: + return _cel_StatusCode_FromPosix(ENOLCK); + case ERROR_HANDLE_DISK_FULL: + return _cel_StatusCode_FromPosix(ENOSPC); + case ERROR_NOT_SUPPORTED: + return _cel_StatusCode_FromPosix(ENOTSUP); + case ERROR_BAD_NETPATH: + return _cel_StatusCode_FromPosix(ENOENT); + case ERROR_DEV_NOT_EXIST: + return _cel_StatusCode_FromPosix(ENODEV); + case ERROR_BAD_NET_NAME: + return _cel_StatusCode_FromPosix(ENOENT); + case ERROR_FILE_EXISTS: + return _cel_StatusCode_FromPosix(EEXIST); + case ERROR_CANNOT_MAKE: + return _cel_StatusCode_FromPosix(EACCES); + case ERROR_INVALID_PARAMETER: + return _cel_StatusCode_FromPosix(EINVAL); + case ERROR_BROKEN_PIPE: + return _cel_StatusCode_FromPosix(EPIPE); + case ERROR_OPEN_FAILED: + return _cel_StatusCode_FromPosix(EIO); + case ERROR_BUFFER_OVERFLOW: + return _cel_StatusCode_FromPosix(ENAMETOOLONG); + case ERROR_DISK_FULL: + return _cel_StatusCode_FromPosix(ENOSPC); + case ERROR_SEM_TIMEOUT: + return _cel_StatusCode_FromPosix(ETIMEDOUT); + case ERROR_INVALID_NAME: + return _cel_StatusCode_FromPosix(ENOENT); + case ERROR_NEGATIVE_SEEK: + return _cel_StatusCode_FromPosix(EINVAL); + case ERROR_BUSY_DRIVE: + return _cel_StatusCode_FromPosix(EBUSY); + case ERROR_DIR_NOT_EMPTY: + return _cel_StatusCode_FromPosix(ENOTEMPTY); + case ERROR_BUSY: + return _cel_StatusCode_FromPosix(EBUSY); + case ERROR_ALREADY_EXISTS: + return _cel_StatusCode_FromPosix(EEXIST); + case ERROR_FILENAME_EXCED_RANGE: + return _cel_StatusCode_FromPosix(ENAMETOOLONG); + case ERROR_LOCKED: + return _cel_StatusCode_FromPosix(ENOLCK); + case WAIT_TIMEOUT: + return _cel_StatusCode_FromPosix(ETIMEDOUT); + case ERROR_DIRECTORY: + return _cel_StatusCode_FromPosix(EINVAL); + case ERROR_OPERATION_ABORTED: + return _cel_StatusCode_FromPosix(ECANCELED); + case ERROR_NOACCESS: + return _cel_StatusCode_FromPosix(EACCES); + case ERROR_CANTOPEN: + return _cel_StatusCode_FromPosix(EIO); + case ERROR_CANTREAD: + return _cel_StatusCode_FromPosix(EIO); + case ERROR_CANTWRITE: + return _cel_StatusCode_FromPosix(EIO); + case ERROR_RETRY: + return _cel_StatusCode_FromPosix(EAGAIN); + case ERROR_TIMEOUT: + return _cel_StatusCode_FromPosix(ETIMEDOUT); + case ERROR_OPEN_FILES: + return _cel_StatusCode_FromPosix(EBUSY); + case ERROR_DEVICE_IN_USE: + return _cel_StatusCode_FromPosix(EBUSY); + case ERROR_REPARSE_TAG_INVALID: + return _cel_StatusCode_FromPosix(EINVAL); + case WSAEINTR: + return _cel_StatusCode_FromPosix(EINTR); + case WSAEBADF: + return _cel_StatusCode_FromPosix(EBADF); + case WSAEACCES: + return _cel_StatusCode_FromPosix(EACCES); + case WSAEFAULT: + return _cel_StatusCode_FromPosix(EFAULT); + case WSAEINVAL: + return _cel_StatusCode_FromPosix(EINVAL); + case WSAEMFILE: + return _cel_StatusCode_FromPosix(EMFILE); + case WSAEWOULDBLOCK: + return _cel_StatusCode_FromPosix(EWOULDBLOCK); + case WSAEINPROGRESS: + return _cel_StatusCode_FromPosix(EINPROGRESS); + case WSAEALREADY: + return _cel_StatusCode_FromPosix(EALREADY); + case WSAENOTSOCK: + return _cel_StatusCode_FromPosix(ENOTSOCK); + case WSAEDESTADDRREQ: + return _cel_StatusCode_FromPosix(EDESTADDRREQ); + case WSAEMSGSIZE: + return _cel_StatusCode_FromPosix(EMSGSIZE); + case WSAEPROTOTYPE: + return _cel_StatusCode_FromPosix(EPROTOTYPE); + case WSAENOPROTOOPT: + return _cel_StatusCode_FromPosix(ENOPROTOOPT); + case WSAEPROTONOSUPPORT: + return _cel_StatusCode_FromPosix(EPROTONOSUPPORT); + case WSAEOPNOTSUPP: + return _cel_StatusCode_FromPosix(EOPNOTSUPP); + case WSAEAFNOSUPPORT: + return _cel_StatusCode_FromPosix(EAFNOSUPPORT); + case WSAEADDRINUSE: + return _cel_StatusCode_FromPosix(EADDRINUSE); + case WSAEADDRNOTAVAIL: + return _cel_StatusCode_FromPosix(EADDRNOTAVAIL); + case WSAENETDOWN: + return _cel_StatusCode_FromPosix(ENETDOWN); + case WSAENETUNREACH: + return _cel_StatusCode_FromPosix(ENETUNREACH); + case WSAENETRESET: + return _cel_StatusCode_FromPosix(ENETRESET); + case WSAECONNABORTED: + return _cel_StatusCode_FromPosix(ECONNABORTED); + case WSAECONNRESET: + return _cel_StatusCode_FromPosix(ECONNRESET); + case WSAENOBUFS: + return _cel_StatusCode_FromPosix(ENOBUFS); + case WSAEISCONN: + return _cel_StatusCode_FromPosix(EISCONN); + case WSAENOTCONN: + return _cel_StatusCode_FromPosix(ENOTCONN); + case WSAETIMEDOUT: + return _cel_StatusCode_FromPosix(ETIMEDOUT); + case WSAECONNREFUSED: + return _cel_StatusCode_FromPosix(ECONNREFUSED); + case WSAENAMETOOLONG: + return _cel_StatusCode_FromPosix(ENAMETOOLONG); + case WSAEHOSTUNREACH: + return _cel_StatusCode_FromPosix(EHOSTUNREACH); + default: + return cel_StatusCode_kUnknown; + } +#endif +} + +static int _cel_SystemErrorSpace_Message(CEL_NONNULL(const cel_ErrorSpace*) + space, + int code, CEL_NONNULL(char*) buf, + size_t buflen) { + CEL_ASSERT_EQ(space, cel_SystemErrorSpace); + CEL_ASSERT_NOT_NULL(buf); + CEL_ASSERT_GT(buflen, 0); + +#ifndef _WIN32 + return cel_ErrorSpace_Message(cel_GenericErrorSpace, code, buf, buflen); +#else + if (CEL_UNLIKELY(buflen == 0)) { + return 0; + } + if (code == 0) { + buf[0] = '\0'; + return 0; + } + DWORD ret = FormatMessageA( + FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, cel_nullptr, + (DWORD)(unsigned int)code, 0, buf, (DWORD)buflen, cel_nullptr); + if (ret == 0) { + buf[0] = '\0'; + return (int)GetLastError(); + } + CEL_ASSERT_LE(ret, buflen); + if (ret == buflen) { + --ret; + } + buf[ret] = '\0'; + return 0; +#endif +} + +static const cel_ErrorSpaceVTable _cel_SystemErrorSpaceVTable = { + .name = CEL_CSTRINGVIEW_C("system"), + .OutOfMemory = _cel_SystemErrorSpace_OutOfMemory, + .Canonical = _cel_SystemErrorSpace_Canonical, + .Message = _cel_SystemErrorSpace_Message, +}; + +static const cel_ErrorSpace _cel_SystemErrorSpace = { + .vtable = &_cel_SystemErrorSpaceVTable, +}; + +extern "C" CEL_NONNULL(const cel_ErrorSpace*) +const cel_SystemErrorSpace = &_cel_SystemErrorSpace; diff --git a/cel-c/error_status.cc b/cel-c/error_status.cc new file mode 100644 index 0000000..bc124e6 --- /dev/null +++ b/cel-c/error_status.cc @@ -0,0 +1,70 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "cel-c/error_status.h" + +#include +#include + +#include "cel-c/arena.h" +#include "cel-c/assert.h" +#include "cel-c/config.h" +#include "cel-c/error.h" +#include "cel-c/status.h" +#include "cel-c/string_view.h" + +extern "C" bool cel_Error_FromStatus(cel_Error* cel_nonnull out, + const cel_Status* cel_nonnull in, + cel_Arena* cel_nonnull arena) { + CEL_ASSERT_NOT_NULL(out); + CEL_ASSERT_NOT_NULL(in); + CEL_ASSERT_NOT(cel_Status_Ok(in)); + CEL_ASSERT_NOT_NULL(arena); + + if (CEL_UNLIKELY(cel_Status_Ok(in))) { + cel_Error_Clear(out); + return true; + } + + cel_Error_SetCode(out, cel_Status_Space(in), cel_Status_Code(in)); + + cel_StringView in_message = cel_Status_Message(in); + cel_StringView out_message; + if (cel_StringView_Empty(in_message)) { + out_message = cel_StringView_FromString(""); + } else { + if (!cel_Arena_StrDup(arena, &out_message, in_message)) { + return false; + } + } + cel_Error_SetMessage(out, out_message); + + cel_StringView in_type_url; + cel_StringView in_value; + cel_StatusPayloadIterator in_iter = cel_Status_BeginPayloads(in); + while (cel_Status_NextPayload(in, &in_type_url, &in_value, &in_iter)) { + cel_StringView out_type_url; + cel_StringView out_value; + if (!cel_Arena_StrDup(arena, &out_type_url, in_type_url)) { + return false; + } + if (!cel_Arena_StrDup(arena, &out_value, in_value)) { + return false; + } + if (!cel_Error_SetPayload(out, out_type_url, out_value, arena)) { + return false; + } + } + return true; +} diff --git a/cel-c/hash.cc b/cel-c/hash.cc new file mode 100644 index 0000000..f82c26e --- /dev/null +++ b/cel-c/hash.cc @@ -0,0 +1,253 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "cel-c/hash.h" + +#include +#include +#include + +#include "cel-c/assert.h" +#include "cel-c/src/bit.h" +#include "cel-c/src/config.h" +#include "cel-c/src/uint128.h" +#include "cel-c/src/unaligned.h" + +static CEL_NONNULL(const void*) const _cel_kHashSeed = &_cel_kHashSeed; + +CEL_ATTRIBUTE_NODISCARD +static uint64_t _cel_HashSeed() { +#if (!defined(__clang__) || __clang_major__ > 11) && \ + (!defined(__apple_build_version__) || __apple_build_version__ >= 19558921) + return (uint64_t)((uintptr_t)&_cel_kHashSeed); +#else + return (uint64_t)((uintptr_t)_cel_kHashSeed); +#endif +} + +extern "C" cel_HashState cel_HashState_Initialize() { + return ((cel_HashState){_cel_HashSeed()}); +} + +static void _cel_HashRead9To16(const uint8_t* cel_nonnull p, size_t n, + uint64_t* cel_nonnull low, + uint64_t* cel_nonnull high) { + const uint64_t lo = _cel_UnalignedLoad64(p); + const uint64_t hi = _cel_UnalignedLoad64(p + n - 8); +#ifdef _CEL_IS_LITTLE_ENDIAN + const uint64_t most_significant = hi; + const uint64_t least_significant = lo; +#else + const uint64_t most_significant = lo; + const uint64_t least_significant = hi; +#endif + *low = least_significant; + *high = most_significant; +} + +CEL_ATTRIBUTE_NODISCARD +static uint64_t _cel_HashRead4To8(const uint8_t* cel_nonnull p, size_t n) { + const uint32_t low = _cel_UnalignedLoad32(p); + const uint32_t hi = _cel_UnalignedLoad32(p + n - 4); +#ifdef _CEL_IS_LITTLE_ENDIAN + const uint32_t most_significant = hi; + const uint32_t least_significant = low; +#else + const uint32_t most_significant = low; + const uint32_t least_significant = hi; +#endif + return (((uint64_t)most_significant) << (n - 4) * 8) | least_significant; +} + +CEL_ATTRIBUTE_NODISCARD +static uint32_t _cel_HashRead1To3(const uint8_t* cel_nonnull p, size_t n) { + const uint8_t mem0 = p[0]; + const uint8_t mem1 = p[n / 2]; + const uint8_t mem2 = p[n - 1]; +#ifdef _CEL_IS_LITTLE_ENDIAN + const uint8_t significant2 = mem2; + const uint8_t significant1 = mem1; + const uint8_t significant0 = mem0; +#else + const uint8_t significant2 = mem0; + const uint8_t significant1 = n == 2 ? mem0 : mem1; + const uint8_t significant0 = mem2; +#endif + return (uint32_t)(significant0 | (significant1 << (n / 2 * 8)) | + (significant2 << ((n - 1) * 8))); +} + +CEL_ATTRIBUTE_NODISCARD +static uint64_t _cel_HashMix(uint64_t a, uint64_t b) { + _cel_Uint128 m = _cel_Uint128_From(a + b); + m = _cel_Uint128_Mul(m, _cel_Uint128_From(UINT64_C(0x9ddfea08eb382d69))); + m = _cel_Uint128_Xor(m, _cel_Uint128_ShiftRight(m, 64)); + return _cel_Uint128_To(uint64_t, m); +} + +static const uint64_t _cel_kHashSalt[5] = { + UINT64_C(0x243F6A8885A308D3), UINT64_C(0x13198A2E03707344), + UINT64_C(0xA4093822299F31D0), UINT64_C(0x082EFA98EC4E6C89), + UINT64_C(0x452821E638D01377), +}; + +CEL_ATTRIBUTE_NODISCARD +static uint64_t _cel_LowLevelHashMix(uint64_t a, uint64_t b) { + _cel_Uint128 m = _cel_Uint128_From(a); + m = _cel_Uint128_Mul(m, _cel_Uint128_From(b)); + return _cel_Uint128_To(uint64_t, m) ^ + _cel_Uint128_To(uint64_t, _cel_Uint128_ShiftRight(m, 64)); +} + +CEL_ATTRIBUTE_NODISCARD +static uint64_t _cel_LowLevelHashN(const uint8_t* cel_nonnull p, size_t n) { + CEL_ASSERT_GT(n, 16); + const size_t starting_n = n; + const uint8_t* const last_16_p = p + starting_n - 16; + uint64_t state = _cel_HashSeed() ^ _cel_kHashSalt[0]; + + if (n > 64) { + uint64_t dup_state[3]; + dup_state[0] = state; + dup_state[1] = state; + dup_state[2] = state; + + do { + const uint64_t a = _cel_UnalignedLoad64(p); + const uint64_t b = _cel_UnalignedLoad64(p + 8); + const uint64_t c = _cel_UnalignedLoad64(p + 16); + const uint64_t d = _cel_UnalignedLoad64(p + 24); + const uint64_t e = _cel_UnalignedLoad64(p + 32); + const uint64_t f = _cel_UnalignedLoad64(p + 40); + const uint64_t g = _cel_UnalignedLoad64(p + 48); + const uint64_t h = _cel_UnalignedLoad64(p + 56); + + state = _cel_LowLevelHashMix(a ^ _cel_kHashSalt[1], b ^ state); + dup_state[0] = + _cel_LowLevelHashMix(c ^ _cel_kHashSalt[2], d ^ dup_state[0]); + dup_state[1] = + _cel_LowLevelHashMix(e ^ _cel_kHashSalt[3], f ^ dup_state[1]); + dup_state[2] = + _cel_LowLevelHashMix(g ^ _cel_kHashSalt[4], h ^ dup_state[2]); + + p += 64; + n -= 64; + } while (n > 64); + + state = (state ^ dup_state[0]) ^ (dup_state[1] + dup_state[2]); + } + + if (n > 32) { + uint64_t dup_state[2]; + + const uint64_t a = _cel_UnalignedLoad64(p); + const uint64_t b = _cel_UnalignedLoad64(p + 8); + const uint64_t c = _cel_UnalignedLoad64(p + 16); + const uint64_t d = _cel_UnalignedLoad64(p + 24); + + dup_state[0] = _cel_LowLevelHashMix(a ^ _cel_kHashSalt[1], b ^ state); + dup_state[1] = _cel_LowLevelHashMix(c ^ _cel_kHashSalt[2], d ^ state); + state = dup_state[0] ^ dup_state[1]; + + p += 32; + n -= 32; + } + + if (n > 16) { + const uint64_t a = _cel_UnalignedLoad64(p); + const uint64_t b = _cel_UnalignedLoad64(p + 8); + + state = _cel_LowLevelHashMix(a ^ _cel_kHashSalt[1], b ^ state); + } + + const uint64_t a = _cel_UnalignedLoad64(last_16_p); + const uint64_t b = _cel_UnalignedLoad64(last_16_p + 8); + + return _cel_LowLevelHashMix(a ^ _cel_kHashSalt[1] ^ starting_n, b ^ state); +} + +extern "C" cel_HashState cel_HashState_CombineN(cel_HashState state, + const void* cel_nullable data, + size_t size) { + uint64_t v; + if (size > 16) { + if (CEL_UNLIKELY(size > 1024)) { + do { + state.val = _cel_HashMix( + state.val, + _cel_LowLevelHashN(reinterpret_cast(data), 1024)); + size -= 1024; + data = ((const uint8_t*)data) + 1024; + } while (size > 1024); + return cel_HashState_CombineN(state, data, size); + } + v = _cel_LowLevelHashN((const uint8_t*)data, size); + } else if (size > 8) { + uint64_t lo; + uint64_t hi; + _cel_HashRead9To16(reinterpret_cast(data), size, &lo, &hi); + lo = _cel_rotr(lo, 53); + state.val *= UINT64_C(0x9ddfea08eb382d69); + lo += state.val; + state.val ^= hi; + _cel_Uint128 m = _cel_Uint128_From(state.val); + m = _cel_Uint128_Mul(m, _cel_Uint128_From(lo)); + m = _cel_Uint128_Xor(m, _cel_Uint128_ShiftRight(m, 64)); + return ((cel_HashState){_cel_Uint128_To(uint64_t, m)}); + } else if (size > 3) { + v = _cel_HashRead4To8(reinterpret_cast(data), size); + } else if (size > 0) { + v = _cel_HashRead1To3(reinterpret_cast(data), size); + } else { + return state; + } + return ((cel_HashState){_cel_HashMix(state.val, v)}); +} + +extern "C" cel_HashState cel_HashState_Combine64(cel_HashState state, + uint64_t data) { + return ((cel_HashState){_cel_HashMix(state.val, data)}); +} + +extern "C" cel_HashState cel_HashState_CombineLD(cel_HashState state, + long double value) { + const int cat = fpclassify(value); + switch (cat) { + case FP_INFINITE: + // Add the sign bit to differentiate between +Inf and -Inf + state = cel_HashState_CombineB(state, signbit(value)); + break; + case FP_NAN: + CEL_ATTRIBUTE_FALLTHROUGH; + case FP_ZERO: + CEL_ATTRIBUTE_FALLTHROUGH; + default: + // Category is enough for these. + break; + case FP_NORMAL: + CEL_ATTRIBUTE_FALLTHROUGH; + case FP_SUBNORMAL: { + // We can't convert `value` directly to double because this would have + // undefined behavior if the value is out of range. + // std::frexp gives us a value in the range (-1, -.5] or [.5, 1) that is + // guaranteed to be in range for `double`. The truncation is + // implementation defined, but that works as long as it is deterministic. + int exp; + double mant = (double)frexpl(value, &exp); + state = cel_HashState_CombineD(state, mant); + state = cel_HashState_CombineI(state, exp); + } break; + } + return cel_HashState_CombineI(state, cat); +} diff --git a/cel-c/operators.cc b/cel-c/operators.cc new file mode 100644 index 0000000..1ffbaa5 --- /dev/null +++ b/cel-c/operators.cc @@ -0,0 +1,247 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "cel-c/operators.h" + +#include +#include + +#include "cel-c/assert.h" +#include "cel-c/config.h" +#include "cel-c/string_view.h" + +typedef struct { + cel_StringView string; + cel_StringView alias; + int precedence; +} cel_OpData; + +static const cel_OpData cel_kUnaryOpData[] = { + { + .string = CEL_STRINGVIEW_C(""), + .alias = CEL_STRINGVIEW_C(""), + .precedence = 0, + }, + { + .string = CEL_STRINGVIEW_C("!_"), + .alias = CEL_STRINGVIEW_C(""), + .precedence = 2, + }, + { + .string = CEL_STRINGVIEW_C("-_"), + .alias = CEL_STRINGVIEW_C(""), + .precedence = 2, + }, + { + .string = CEL_STRINGVIEW_C("@not_strictly_false"), + .alias = CEL_STRINGVIEW_C("__not_strictly_false__"), + .precedence = 0, + }, +}; + +extern "C" bool cel_UnaryOp_FromString(cel_StringView str, + CEL_NONNULL(cel_UnaryOp*) op) { + CEL_ASSERT_NOT_NULL(op); + + for (size_t i = 1; i < cel_arraysize(cel_kUnaryOpData); ++i) { + const cel_OpData* const data = &cel_kUnaryOpData[i]; + if (cel_StringView_Equals(str, data->string) || + (!cel_StringView_Empty(data->alias) && + cel_StringView_Equals(str, data->alias))) { + *op = (cel_UnaryOp)(int)i; + return true; + } + } + return false; +} + +extern "C" cel_StringView cel_UnaryOp_ToString(cel_UnaryOp op) { + CEL_ASSERT_GE(op, 0); + CEL_ASSERT_LT(static_cast(op), cel_arraysize(cel_kUnaryOpData)); + + return cel_kUnaryOpData[op].string; +} + +extern "C" int cel_UnaryOp_Precedence(cel_UnaryOp op) { + CEL_ASSERT_GE(op, 0); + CEL_ASSERT_LT(static_cast(op), cel_arraysize(cel_kUnaryOpData)); + + return cel_kUnaryOpData[op].precedence; +} + +static const cel_OpData cel_kBinaryOpData[] = { + { + .string = CEL_STRINGVIEW_C(""), + .alias = CEL_STRINGVIEW_C(""), + .precedence = 0, + }, + { + .string = CEL_STRINGVIEW_C("_&&_"), + .alias = CEL_STRINGVIEW_C(""), + .precedence = 6, + }, + { + .string = CEL_STRINGVIEW_C("_||_"), + .alias = CEL_STRINGVIEW_C(""), + .precedence = 7, + }, + { + .string = CEL_STRINGVIEW_C("_==_"), + .alias = CEL_STRINGVIEW_C(""), + .precedence = 5, + }, + { + .string = CEL_STRINGVIEW_C("_!=_"), + .alias = CEL_STRINGVIEW_C(""), + .precedence = 5, + }, + { + .string = CEL_STRINGVIEW_C("_<_"), + .alias = CEL_STRINGVIEW_C(""), + .precedence = 5, + }, + { + .string = CEL_STRINGVIEW_C("_<=_"), + .alias = CEL_STRINGVIEW_C(""), + .precedence = 5, + }, + { + .string = CEL_STRINGVIEW_C("_>_"), + .alias = CEL_STRINGVIEW_C(""), + .precedence = 5, + }, + { + .string = CEL_STRINGVIEW_C("_>=_"), + .alias = CEL_STRINGVIEW_C(""), + .precedence = 5, + }, + { + .string = CEL_STRINGVIEW_C("_+_"), + .alias = CEL_STRINGVIEW_C(""), + .precedence = 4, + }, + { + .string = CEL_STRINGVIEW_C("_-_"), + .alias = CEL_STRINGVIEW_C(""), + .precedence = 4, + }, + { + .string = CEL_STRINGVIEW_C("_*_"), + .alias = CEL_STRINGVIEW_C(""), + .precedence = 3, + }, + { + .string = CEL_STRINGVIEW_C("_/_"), + .alias = CEL_STRINGVIEW_C(""), + .precedence = 3, + }, + { + .string = CEL_STRINGVIEW_C("_%_"), + .alias = CEL_STRINGVIEW_C(""), + .precedence = 3, + }, + { + .string = CEL_STRINGVIEW_C("_[_]"), + .alias = CEL_STRINGVIEW_C(""), + .precedence = 1, + }, + { + .string = CEL_STRINGVIEW_C("@in"), + .alias = CEL_STRINGVIEW_C("_in_"), + .precedence = 5, + }, + { + .string = CEL_STRINGVIEW_C("_[?_]"), + .alias = CEL_STRINGVIEW_C(""), + .precedence = 0, + }, + { + .string = CEL_STRINGVIEW_C("_?._"), + .alias = CEL_STRINGVIEW_C(""), + .precedence = 0, + }, +}; + +extern "C" bool cel_BinaryOp_FromString(cel_StringView str, + CEL_NONNULL(cel_BinaryOp*) op) { + CEL_ASSERT_NOT_NULL(op); + + for (size_t i = 1; i < cel_arraysize(cel_kBinaryOpData); ++i) { + const cel_OpData* const data = &cel_kBinaryOpData[i]; + if (cel_StringView_Equals(str, data->string) || + (!cel_StringView_Empty(data->alias) && + cel_StringView_Equals(str, data->alias))) { + *op = (cel_BinaryOp)(int)i; + return true; + } + } + return false; +} + +extern "C" cel_StringView cel_BinaryOp_ToString(cel_BinaryOp op) { + CEL_ASSERT_GE(op, 0); + CEL_ASSERT_LT(static_cast(op), cel_arraysize(cel_kBinaryOpData)); + + return cel_kBinaryOpData[op].string; +} + +extern "C" int cel_BinaryOp_Precedence(cel_BinaryOp op) { + CEL_ASSERT_GE(op, 0); + CEL_ASSERT_LT(static_cast(op), cel_arraysize(cel_kBinaryOpData)); + + return cel_kBinaryOpData[op].precedence; +} + +static const cel_OpData cel_kTernaryOpData[] = { + { + .string = CEL_STRINGVIEW_C(""), + .alias = CEL_STRINGVIEW_C(""), + .precedence = 0, + }, + { + .string = CEL_STRINGVIEW_C("_?_:_"), + .alias = CEL_STRINGVIEW_C(""), + .precedence = 8, + }, +}; + +extern "C" bool cel_TernaryOp_FromString(cel_StringView str, + CEL_NONNULL(cel_TernaryOp*) op) { + CEL_ASSERT_NOT_NULL(op); + + for (size_t i = 1; i < cel_arraysize(cel_kTernaryOpData); ++i) { + const cel_OpData* const data = &cel_kTernaryOpData[i]; + if (cel_StringView_Equals(str, data->string) || + (!cel_StringView_Empty(data->alias) && + cel_StringView_Equals(str, data->alias))) { + *op = (cel_TernaryOp)(int)i; + return true; + } + } + return false; +} + +extern "C" cel_StringView cel_TernaryOp_ToString(cel_TernaryOp op) { + CEL_ASSERT_GE(op, 0); + CEL_ASSERT_LT(static_cast(op), cel_arraysize(cel_kTernaryOpData)); + + return cel_kTernaryOpData[op].string; +} + +extern "C" int cel_TernaryOp_Precedence(cel_TernaryOp op) { + CEL_ASSERT_GE(op, 0); + CEL_ASSERT_LT(static_cast(op), cel_arraysize(cel_kTernaryOpData)); + + return cel_kTernaryOpData[op].precedence; +} diff --git a/cel-c/ref.cc b/cel-c/ref.cc new file mode 100644 index 0000000..4970cb8 --- /dev/null +++ b/cel-c/ref.cc @@ -0,0 +1,188 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "cel-c/ref.h" + +#include +#include +#include + +#include "cel-c/arena.h" +#include "cel-c/assert.h" +#include "cel-c/config.h" +#include "cel-c/constant.h" +#include "cel-c/string_view.h" + +struct cel_IdentRef { + cel_StringView name; + cel_RefKind kind; + + cel_Constant value; +}; + +struct cel_FunctionRef { + cel_StringView name; + cel_RefKind kind; + + cel_FunctionOverloadRef* head; + cel_FunctionOverloadRef* tail; + size_t overloads; +}; + +struct cel_FunctionOverloadRef { + cel_FunctionOverloadRef* prev; + cel_FunctionOverloadRef* next; + cel_StringView id; +#ifndef NDEBUG + const cel_FunctionRef* func; +#endif +}; + +struct cel_Ref { + union { + struct { + cel_StringView name; + cel_RefKind kind; + }; + cel_IdentRef variable_ref; + cel_FunctionRef function_ref; + }; +}; + +extern "C" cel_RefKind cel_Ref_Kind(const cel_Ref* cel_nonnull ref) { + CEL_ASSERT_NOT_NULL(ref); + + return ref->kind; +} + +extern "C" cel_StringView cel_Ref_Name(const cel_Ref* cel_nonnull ref) { + CEL_ASSERT_NOT_NULL(ref); + + return ref->name; +} + +extern "C" cel_IdentRef* cel_nullable +cel_IdentRef_New(cel_StringView name, cel_Arena* cel_nonnull arena) { + CEL_ASSERT_NOT_NULL(arena); + + cel_IdentRef* ref = + (cel_IdentRef*)cel_Arena_Malloc(arena, sizeof(cel_IdentRef), cel_nullptr); + if (CEL_LIKELY(ref != cel_nullptr)) { + memset(ref, 0, sizeof(*ref)); + ref->name = name; + ref->kind = cel_RefKind_kIdent; + } + return ref; +} + +extern "C" const cel_Constant* cel_nonnull +cel_IdentRef_Value(const cel_IdentRef* cel_nonnull ref) { + CEL_ASSERT_NOT_NULL(ref); + + return &ref->value; +} + +extern "C" cel_FunctionRef* cel_nullable +cel_FunctionRef_New(cel_StringView name, cel_Arena* cel_nonnull arena) { + CEL_ASSERT_NOT_NULL(arena); + + cel_FunctionRef* ref = (cel_FunctionRef*)cel_Arena_Malloc( + arena, sizeof(cel_FunctionRef), cel_nullptr); + if (CEL_LIKELY(ref != cel_nullptr)) { + memset(ref, 0, sizeof(*ref)); + ref->name = name; + ref->kind = cel_RefKind_kFunction; + } + return ref; +} + +extern "C" size_t cel_FunctionRef_Overloads( + const cel_FunctionRef* cel_nonnull ref, + cel_FunctionOverloadRef * cel_nullable * cel_nullable head, + cel_FunctionOverloadRef * cel_nullable * cel_nullable tail) { + CEL_ASSERT_NOT_NULL(ref); + + if (head != cel_nullptr) { + *head = ref->head; + } + if (tail != cel_nullptr) { + *tail = ref->tail; + } + return ref->overloads; +} + +extern "C" bool cel_FunctionRef_AppendOverload( + cel_FunctionRef* cel_nonnull ref, + cel_FunctionOverloadRef* cel_nonnull overload) { + CEL_ASSERT_NOT_NULL(ref); + CEL_ASSERT_NOT_NULL(overload); +#ifndef NDEBUG + CEL_ASSERT_NULL(overload->func); +#endif + + cel_FunctionOverloadRef* head = ref->head; + for (; head != cel_nullptr; head = head->next) { + if (cel_StringView_Equals(head->id, overload->id)) { + return false; + } + } + + overload->prev = ref->tail; + if (ref->tail != cel_nullptr) { + ref->tail->next = overload; + } else { + ref->head = overload; + } + ref->tail = overload; + ++ref->overloads; + +#ifndef NDEBUG + overload->func = ref; +#endif + return true; +} + +extern "C" cel_FunctionOverloadRef* cel_nullable +cel_FunctionOverloadRef_New(cel_StringView id, cel_Arena* cel_nonnull arena) { + CEL_ASSERT_NOT_NULL(arena); + + cel_FunctionOverloadRef* ref = (cel_FunctionOverloadRef*)cel_Arena_Malloc( + arena, sizeof(cel_FunctionOverloadRef), cel_nullptr); + if (CEL_LIKELY(ref != cel_nullptr)) { + memset(ref, 0, sizeof(*ref)); + ref->id = id; + } + return ref; +} + +extern "C" cel_StringView cel_FunctionOverloadRef_Id( + const cel_FunctionOverloadRef* cel_nonnull ref) { + CEL_ASSERT_NOT_NULL(ref); + + return ref->id; +} + +extern "C" cel_FunctionOverloadRef* cel_nullable +cel_FunctionOverloadRef_Prev(const cel_FunctionOverloadRef* cel_nonnull ref) { + CEL_ASSERT_NOT_NULL(ref); + + return ref->prev; +} + +extern "C" cel_FunctionOverloadRef* cel_nullable +cel_FunctionOverloadRef_Next(const cel_FunctionOverloadRef* cel_nonnull ref) { + CEL_ASSERT_NOT_NULL(ref); + + return ref->next; +} diff --git a/cel-c/ref_proto.cc b/cel-c/ref_proto.cc new file mode 100644 index 0000000..89e97cd --- /dev/null +++ b/cel-c/ref_proto.cc @@ -0,0 +1,86 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "cel-c/ref_proto.h" + +#include + +#include "cel/expr/checked.upb.h" +#include "cel-c/arena.h" +#include "cel-c/assert.h" +#include "cel-c/config.h" +#include "cel-c/constant_proto.h" +#include "cel-c/ref.h" +#include "cel-c/status.h" +#include "cel-c/string_view.h" +#include "upb/base/string_view.h" + +extern "C" cel_Ref* cel_nullable cel_Ref_FromProto( + const cel_expr_Reference* cel_nonnull in, + cel_Arena* cel_nonnull arena, cel_Status* cel_nonnull status) { + CEL_ASSERT_NOT_NULL(in); + CEL_ASSERT_NOT_NULL(arena); + CEL_ASSERT_NOT_NULL(status); + CEL_ASSERT(cel_Status_Ok(status)); + + cel_StringView out_name; + if (!cel_Arena_StrDup(arena, &out_name, cel_expr_Reference_name(in))) { + cel_OutOfMemoryStatus(status); + return cel_nullptr; + } + size_t in_overloads_len; + const upb_StringView* in_overloads_ptr = + cel_expr_Reference_overload_id(in, &in_overloads_len); + if (in_overloads_len > 0 && cel_expr_Reference_has_value(in)) { + cel_InvalidArgumentStatus( + status, cel_StringView_From("cel: resolved reference has both function " + "overloads and a constant")); + return cel_nullptr; + } + if (in_overloads_len > 0) { + cel_FunctionRef* out = cel_FunctionRef_New(out_name, arena); + if (out == cel_nullptr) { + cel_OutOfMemoryStatus(status); + return cel_nullptr; + } + for (size_t i = 0; i < in_overloads_len; ++i) { + cel_StringView out_id; + if (!cel_Arena_StrDup(arena, &out_id, in_overloads_ptr[i])) { + cel_OutOfMemoryStatus(status); + return cel_nullptr; + } + cel_FunctionOverloadRef* overload_ref = + cel_FunctionOverloadRef_New(out_id, arena); + if (overload_ref == cel_nullptr) { + cel_OutOfMemoryStatus(status); + return cel_nullptr; + } + CEL_USED(cel_FunctionRef_AppendOverload(out, overload_ref)); + } + return cel_Ref_UpCast(out); + } + cel_IdentRef* out = cel_IdentRef_New(out_name, arena); + if (out == cel_nullptr) { + cel_OutOfMemoryStatus(status); + return cel_nullptr; + } + if (cel_expr_Reference_has_value(in)) { + if (!cel_Constant_FromProto(cel_IdentRef_MutableValue(out), + cel_expr_Reference_value(in), arena, + status)) { + return cel_nullptr; + } + } + return cel_Ref_UpCast(out); +} diff --git a/cel-c/ref_proto_v1alpha1.cc b/cel-c/ref_proto_v1alpha1.cc new file mode 100644 index 0000000..28e4a5b --- /dev/null +++ b/cel-c/ref_proto_v1alpha1.cc @@ -0,0 +1,88 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "cel-c/ref_proto_v1alpha1.h" + +#include + +#include "google/api/expr/v1alpha1/checked.upb.h" +#include "cel-c/arena.h" +#include "cel-c/assert.h" +#include "cel-c/config.h" +#include "cel-c/constant_proto_v1alpha1.h" +#include "cel-c/ref.h" +#include "cel-c/status.h" +#include "cel-c/string_view.h" +#include "upb/base/string_view.h" + +extern "C" cel_Ref* cel_nullable cel_Ref_FromProtoV1Alpha1( + const google_api_expr_v1alpha1_Reference* cel_nonnull in, + cel_Arena* cel_nonnull arena, cel_Status* cel_nonnull status) { + CEL_ASSERT_NOT_NULL(in); + CEL_ASSERT_NOT_NULL(arena); + CEL_ASSERT_NOT_NULL(status); + CEL_ASSERT(cel_Status_Ok(status)); + + cel_StringView out_name; + if (!cel_Arena_StrDup(arena, &out_name, + google_api_expr_v1alpha1_Reference_name(in))) { + cel_OutOfMemoryStatus(status); + return cel_nullptr; + } + size_t in_overloads_len; + const upb_StringView* in_overloads_ptr = + google_api_expr_v1alpha1_Reference_overload_id(in, &in_overloads_len); + if (in_overloads_len > 0 && + google_api_expr_v1alpha1_Reference_has_value(in)) { + cel_InvalidArgumentStatus( + status, cel_StringView_From("cel: resolved reference has both function " + "overloads and a constant")); + return cel_nullptr; + } + if (in_overloads_len > 0) { + cel_FunctionRef* out = cel_FunctionRef_New(out_name, arena); + if (out == cel_nullptr) { + cel_OutOfMemoryStatus(status); + return cel_nullptr; + } + for (size_t i = 0; i < in_overloads_len; ++i) { + cel_StringView out_id; + if (!cel_Arena_StrDup(arena, &out_id, in_overloads_ptr[i])) { + cel_OutOfMemoryStatus(status); + return cel_nullptr; + } + cel_FunctionOverloadRef* overload_ref = + cel_FunctionOverloadRef_New(out_id, arena); + if (overload_ref == cel_nullptr) { + cel_OutOfMemoryStatus(status); + return cel_nullptr; + } + CEL_USED(cel_FunctionRef_AppendOverload(out, overload_ref)); + } + return cel_Ref_UpCast(out); + } + cel_IdentRef* out = cel_IdentRef_New(out_name, arena); + if (out == cel_nullptr) { + cel_OutOfMemoryStatus(status); + return cel_nullptr; + } + if (google_api_expr_v1alpha1_Reference_has_value(in)) { + if (!cel_Constant_FromProtoV1Alpha1( + cel_IdentRef_MutableValue(out), + google_api_expr_v1alpha1_Reference_value(in), arena, status)) { + return cel_nullptr; + } + } + return cel_Ref_UpCast(out); +} diff --git a/cel-c/src/BUILD b/cel-c/src/BUILD index 4fa04a4..13222ee 100644 --- a/cel-c/src/BUILD +++ b/cel-c/src/BUILD @@ -48,7 +48,7 @@ cc_library( cc_library( name = "any", - srcs = ["any.c"], + srcs = ["any.cc"], hdrs = ["any.h"], deps = [ "//cel-c:assert", @@ -260,7 +260,7 @@ cc_test( cc_library( name = "bit", - srcs = ["bit.c"], + srcs = ["bit.cc"], hdrs = ["bit.h"], deps = [ "//cel-c:config", @@ -278,7 +278,7 @@ cc_test( cc_library( name = "bitset", - srcs = ["bitset.c"], + srcs = ["bitset.cc"], hdrs = ["bitset.h"], deps = [ ":bit", @@ -298,7 +298,7 @@ cc_test( cc_library( name = "charconv", - srcs = ["charconv.c"], + srcs = ["charconv.cc"], hdrs = ["charconv.h"], deps = [ ":ckdint", @@ -387,7 +387,7 @@ cc_test( cc_library( name = "ctype", - srcs = ["ctype.c"], + srcs = ["ctype.cc"], hdrs = ["ctype.h"], deps = [ "//cel-c:config", @@ -489,7 +489,7 @@ cc_test( cc_library( name = "generic_array", - srcs = ["generic_array.c"], + srcs = ["generic_array.cc"], hdrs = ["generic_array.h"], deps = [ ":asan", @@ -503,7 +503,7 @@ cc_library( cc_library( name = "generic_deque", - srcs = ["generic_deque.c"], + srcs = ["generic_deque.cc"], hdrs = ["generic_deque.h"], deps = [ ":align", @@ -517,7 +517,7 @@ cc_library( cc_library( name = "generic_flat_hash", - srcs = ["generic_flat_hash.c"], + srcs = ["generic_flat_hash.cc"], hdrs = ["generic_flat_hash.h"], deps = [ ":align", @@ -532,7 +532,7 @@ cc_library( cc_library( name = "generic_string", - srcs = ["generic_string.c"], + srcs = ["generic_string.cc"], hdrs = ["generic_string.h"], deps = [ ":asan", @@ -557,7 +557,7 @@ cc_test( cc_library( name = "malloc", - srcs = ["malloc.c"], + srcs = ["malloc.cc"], hdrs = ["malloc.h"], deps = [ ":bit", @@ -580,7 +580,7 @@ cc_test( cc_library( name = "memory", - srcs = ["memory.c"], + srcs = ["memory.cc"], hdrs = ["memory.h"], deps = [ ":config", @@ -600,7 +600,7 @@ cc_test( cc_library( name = "message_equality", - srcs = ["message_equality.c"], + srcs = ["message_equality.cc"], hdrs = ["message_equality.h"], deps = [ ":any", @@ -933,7 +933,7 @@ cc_test( cc_library( name = "utf8", - srcs = ["utf8.c"], + srcs = ["utf8.cc"], hdrs = ["utf8.h"], deps = [ ":uchar", @@ -958,19 +958,19 @@ cc_test( cc_library( name = "value", srcs = [ - "empty_list_value.c", - "empty_map_value.c", - "list_value.c", - "map_value.c", - "mutable_list_value.c", - "mutable_map_value.c", - "opaque_value.c", - "optional_value.c", - "parsed_map_field_value.c", - "parsed_message_value.c", - "parsed_repeated_field_value.c", - "struct_value.c", - "value.c", + "empty_list_value.cc", + "empty_map_value.cc", + "list_value.cc", + "map_value.cc", + "mutable_list_value.cc", + "mutable_map_value.cc", + "opaque_value.cc", + "optional_value.cc", + "parsed_map_field_value.cc", + "parsed_message_value.cc", + "parsed_repeated_field_value.cc", + "struct_value.cc", + "value.cc", ], hdrs = [ "empty_list_value.h", diff --git a/cel-c/src/any.cc b/cel-c/src/any.cc new file mode 100644 index 0000000..dde6e02 --- /dev/null +++ b/cel-c/src/any.cc @@ -0,0 +1,184 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "cel-c/src/any.h" + +#include +#include + +#include "cel-c/assert.h" +#include "cel-c/config.h" +#include "cel-c/status.h" +#include "cel-c/status_code.h" +#include "cel-c/string_view.h" +#include "cel-c/well_known_types.h" +#include "upb/mem/arena.h" +#include "upb/message/message.h" +#include "upb/mini_table/message.h" +#include "upb/reflection/def.h" +#include "upb/reflection/message.h" +#include "upb/wire/decode.h" + +extern "C" _cel_AnyUnpackResult _cel_AnyUnpack( + const upb_Message* cel_nonnull in_message, + const upb_DefPool* cel_nonnull def_pool, const cel_AnyWellKnownType* wkt, + upb_Arena* cel_nonnull arena, + upb_Message * cel_nullable * cel_nonnull out_message, + const upb_MessageDef * cel_nullable * cel_nonnull out_message_def) { + CEL_ASSERT_NOT_NULL(in_message); + CEL_ASSERT_NOT_NULL(def_pool); + CEL_ASSERT_NOT_NULL(wkt); + CEL_ASSERT_NOT_NULL(arena); + CEL_ASSERT_NOT_NULL(out_message); + CEL_ASSERT_NOT_NULL(out_message_def); + + bool unpacked = false; + + while (true) { + // We expect the type URL to be in the format `/`. + cel_StringView type_url = + upb_Message_GetFieldByDef(in_message, wkt->type_url_def).str_val; + const char* type_url_data = cel_StringView_Data(type_url); + const char* slash = reinterpret_cast( + memchr(type_url_data, '/', cel_StringView_Size(type_url))); + if (slash == type_url_data || slash == cel_nullptr) { + if (!unpacked) { + *out_message = cel_nullptr; + *out_message_def = cel_nullptr; + } + return _cel_AnyUnpackResult_kBadTypeUrl; + } + cel_StringView type_name = type_url; + cel_StringView_RemovePrefix(&type_name, (slash - type_url_data) + 1); + const upb_MessageDef* message_def = upb_DefPool_FindMessageByNameWithSize( + def_pool, cel_StringView_Data(type_name), + cel_StringView_Size(type_name)); + if (message_def == cel_nullptr) { + if (!unpacked) { + *out_message = cel_nullptr; + *out_message_def = cel_nullptr; + } + return _cel_AnyUnpackResult_kDefNotFound; + } + const upb_MiniTable* message_tab = upb_MessageDef_MiniTable(message_def); + upb_Message* message = upb_Message_New(message_tab, arena); + if (message == cel_nullptr) { + if (!unpacked) { + *out_message = cel_nullptr; + *out_message_def = cel_nullptr; + } + return _cel_AnyUnpackResult_kOutOfMemory; + } + cel_StringView value = + upb_Message_GetFieldByDef(in_message, wkt->value_def).str_val; + upb_DecodeStatus status = upb_Decode( + cel_StringView_Data(value), cel_StringView_Size(value), message, + message_tab, upb_DefPool_ExtensionRegistry(def_pool), + kUpb_DecodeOption_AliasString, arena); + if (status == kUpb_DecodeStatus_Ok) { + *out_message = message; + *out_message_def = message_def; + if (upb_MessageDef_WellKnownType(message_def) == kUpb_WellKnown_Any) { + CEL_ASSERT_EQ(message_def, wkt->def); + in_message = message; + unpacked = true; + continue; + } + return _cel_AnyUnpackResult_kOk; + } + if (!unpacked) { + *out_message = cel_nullptr; + *out_message_def = cel_nullptr; + } + switch (status) { + case kUpb_DecodeStatus_Ok: + CEL_UNREACHABLE(); + case kUpb_DecodeStatus_Malformed: + return _cel_AnyUnpackResult_kMalformed; + case kUpb_DecodeStatus_OutOfMemory: + return _cel_AnyUnpackResult_kOutOfMemory; + case kUpb_DecodeStatus_BadUtf8: + return _cel_AnyUnpackResult_kBadUtf8; + case kUpb_DecodeStatus_MaxDepthExceeded: + return _cel_AnyUnpackResult_kMaxDepthExceeded; + default: + return _cel_AnyUnpackResult_kUnknown; + } + } +} + +extern "C" void _cel_AnyUnpackResult_ToStatus(_cel_AnyUnpackResult result, + cel_Status* cel_nonnull status) { + switch (result) { + case _cel_AnyUnpackResult_kOk: + cel_Status_Clear(status); + return; + case _cel_AnyUnpackResult_kOutOfMemory: + cel_OutOfMemoryStatus(status); + return; + default: + cel_CanonicalStatus( + status, _cel_AnyUnpackResult_ToStatusCode(result), + cel_StringView_FromString(_cel_AnyUnpackResult_ToMessage(result))); + return; + } +} + +extern "C" cel_StatusCode _cel_AnyUnpackResult_ToStatusCode( + _cel_AnyUnpackResult result) { + switch (result) { + case _cel_AnyUnpackResult_kOk: + return cel_StatusCode_kOk; + case _cel_AnyUnpackResult_kOutOfMemory: + return cel_StatusCode_kResourceExhausted; + case _cel_AnyUnpackResult_kBadTypeUrl: + return cel_StatusCode_kInvalidArgument; + case _cel_AnyUnpackResult_kDefNotFound: + return cel_StatusCode_kNotFound; + case _cel_AnyUnpackResult_kMalformed: + return cel_StatusCode_kInvalidArgument; + case _cel_AnyUnpackResult_kBadUtf8: + return cel_StatusCode_kInvalidArgument; + case _cel_AnyUnpackResult_kMaxDepthExceeded: + return cel_StatusCode_kInvalidArgument; + case _cel_AnyUnpackResult_kUnknown: + return cel_StatusCode_kUnknown; + default: + CEL_UNREACHABLE(); + } +} + +extern "C" const char* cel_nonnull +_cel_AnyUnpackResult_ToMessage(_cel_AnyUnpackResult result) { + switch (result) { + case _cel_AnyUnpackResult_kOk: + return "OK"; + case _cel_AnyUnpackResult_kOutOfMemory: + return "out of memory"; + case _cel_AnyUnpackResult_kBadTypeUrl: + return "bad type URL"; + case _cel_AnyUnpackResult_kDefNotFound: + return "descriptor not found"; + case _cel_AnyUnpackResult_kMalformed: + return "malformed wire format"; + case _cel_AnyUnpackResult_kBadUtf8: + return "bad UTF-8"; + case _cel_AnyUnpackResult_kMaxDepthExceeded: + return "max depth exceeded"; + case _cel_AnyUnpackResult_kUnknown: + return "unknown error"; + default: + CEL_UNREACHABLE(); + } +} diff --git a/cel-c/src/bit.cc b/cel-c/src/bit.cc new file mode 100644 index 0000000..8cfd869 --- /dev/null +++ b/cel-c/src/bit.cc @@ -0,0 +1,129 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "cel-c/src/bit.h" + +#include // IWYU pragma: keep +#include // IWYU pragma: keep + +#include "cel-c/config.h" + +#if CEL_HAVE_INCLUDE() +#include +#endif + +#if !(defined(__STDC_VERSION_STDBIT_H__) && \ + __STDC_VERSION_STDBIT_H__ >= 202311L) + +#if !(((defined(__GNUC__) && !defined(__clang__)) || \ + CEL_HAVE_BUILTIN(__builtin_clz)) || \ + (defined(_MSC_VER) && (defined(_M_X64) || defined(_M_ARM64) || \ + defined(_M_X86) || defined(_M_ARM)))) +static const unsigned char _cel_kLeadingZerosDeBruijn[32] = { + 0, 9, 1, 10, 13, 21, 2, 29, 11, 14, 16, 18, 22, 25, 3, 30, + 8, 12, 20, 28, 15, 17, 24, 7, 19, 27, 23, 6, 26, 5, 4, 31, +}; + +extern "C" int _cel_leading_zeros_ui(unsigned int x) { + CEL_STATIC_ASSERT(sizeof(x) == 4); + if (x == 0) { + return sizeof(x) * CHAR_BIT; + } + x |= x >> 1; + x |= x >> 2; + x |= x >> 4; + x |= x >> 8; + x |= x >> 16; + return ( + int)_cel_kLeadingZerosDeBruijn[((unsigned int)(x * 0x07c4acddu)) >> 27]; +} +#endif + +#if !(((defined(__GNUC__) && !defined(__clang__)) || \ + CEL_HAVE_BUILTIN(__builtin_clzl)) || \ + (defined(_MSC_VER) && (defined(_M_X64) || defined(_M_ARM64) || \ + defined(_M_X86) || defined(_M_ARM)))) +extern "C" int _cel_leading_zeros_ul(unsigned long x) { + CEL_STATIC_ASSERT(sizeof(x) == 4 || sizeof(x) == 8); +#if LONG_MAX == INT32_MAX + return _cel_leading_zeros_ui((unsigned int)x); +#elif LONG_MAX == INT64_MAX + return _cel_leading_zeros_ull((unsigned long long)x); +#else +#error Unreachable. +#endif +} +#endif + +#if !(((defined(__GNUC__) && !defined(__clang__)) || \ + CEL_HAVE_BUILTIN(__builtin_clzll)) || \ + (defined(_MSC_VER) && (defined(_M_X64) || defined(_M_ARM64)))) +extern "C" int _cel_leading_zeros_ull(unsigned long long x) { + CEL_STATIC_ASSERT(sizeof(x) == 8); + int count = _cel_leading_zeros_ui((unsigned int)(x >> 32)); + return count == sizeof(unsigned int) * CHAR_BIT + ? count + _cel_leading_zeros_ui((unsigned int)x) + : count; +} +#endif + +#if !(((defined(__GNUC__) && !defined(__clang__)) || \ + CEL_HAVE_BUILTIN(__builtin_ctz)) || \ + (defined(_MSC_VER) && (defined(_M_X64) || defined(_M_ARM64) || \ + defined(_M_X86) || defined(_M_ARM)))) +static const unsigned char _cel_kTralingZerosDeBruijn[32] = { + 0, 1, 28, 2, 29, 14, 24, 3, 30, 22, 20, 15, 25, 17, 4, 8, + 31, 27, 13, 23, 21, 19, 16, 7, 26, 12, 18, 6, 11, 5, 10, 9, +}; + +extern "C" int _cel_trailing_zeros_ui(unsigned int x) { + CEL_STATIC_ASSERT(sizeof(x) == 4); + if (x == 0) { + return sizeof(x) * CHAR_BIT; + } + return ( + int)_cel_kTralingZerosDeBruijn[((unsigned int)((v & -v) * 0x077cb531u)) >> + 27]; +} +#endif + +#if !(((defined(__GNUC__) && !defined(__clang__)) || \ + CEL_HAVE_BUILTIN(__builtin_ctzl)) || \ + (defined(_MSC_VER) && (defined(_M_X64) || defined(_M_ARM64) || \ + defined(_M_X86) || defined(_M_ARM)))) +extern "C" int _cel_trailing_zeros_ul(unsigned long x) { + CEL_STATIC_ASSERT(sizeof(x) == 4 || sizeof(x) == 8); +#if LONG_MAX == INT32_MAX + return _cel_trailing_zeros_ui((unsigned int)x); +#elif LONG_MAX == INT64_MAX + return _cel_trailing_zeros_ull((unsigned long long)x); +#else +#error Unreachable. +#endif +} +#endif + +#if !(((defined(__GNUC__) && !defined(__clang__)) || \ + CEL_HAVE_BUILTIN(__builtin_ctzll)) || \ + (defined(_MSC_VER) && (defined(_M_X64) || defined(_M_ARM64)))) +extern "C" int _cel_trailing_zeros_ull(unsigned long long x) { + CEL_STATIC_ASSERT(sizeof(x) == 8); + int count = _cel_trailing_zeros_ui((unsigned int)x); + return count == sizeof(unsigned int) * CHAR_BIT + ? count + _cel_trailing_zeros_ui((unsigned int)(x >> 32)) + : count; +} +#endif + +#endif diff --git a/cel-c/src/bitset.cc b/cel-c/src/bitset.cc new file mode 100644 index 0000000..aaca874 --- /dev/null +++ b/cel-c/src/bitset.cc @@ -0,0 +1,54 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "cel-c/src/bitset.h" + +#include // IWYU pragma: keep +#include + +#include "cel-c/assert.h" +#include "cel-c/config.h" +#include "cel-c/src/bit.h" + +extern "C" bool _cel_BitSet_Next(CEL_NULLABLE(const _cel_BitSetWord*) words, + size_t bits, CEL_NONNULL(size_t*) bit) { + CEL_ASSERT(words != cel_nullptr || bits == 0); + CEL_ASSERT_NOT_NULL(bit); + size_t i = *bit; + // If `i` is `_cel_BitSet_kBegin`, `++i` will wrap back around to 0. + ++i; + while (i < bits) { + const _cel_BitSetWord word_index = _cel_BitSet_WordIndexForBit(i); + const _cel_BitSetWord word = words[word_index]; + // Check if any bits are set in word. If they are not, skip to the next + // word. + if (word != 0) { + const int bit_index = _cel_BitSet_BitIndexForBit(i); + const _cel_BitSetWord word_mask = + ~((((_cel_BitSetWord)1) << bit_index) - 1); + const _cel_BitSetWord masked_word = word & word_mask; + // If the masked word does not have any bits set, we can skip to the next + // word. + if (masked_word != 0) { + const int n = _cel_trailing_zeros(masked_word); + CEL_ASSERT(n >= ((size_t)bit_index) && n < _cel_BitSet_kBitsPerWord); + *bit = (word_index * _cel_BitSet_kBitsPerWord) + n; + return true; + } + } + i = ((word_index + 1) * _cel_BitSet_kBitsPerWord); + } + *bit = bits; + return false; +} diff --git a/cel-c/src/charconv.cc b/cel-c/src/charconv.cc new file mode 100644 index 0000000..3cbf508 --- /dev/null +++ b/cel-c/src/charconv.cc @@ -0,0 +1,393 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "cel-c/src/charconv.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cel-c/assert.h" +#include "cel-c/config.h" +#include "cel-c/src/ckdint.h" +#include "cel-c/src/config.h" +#include "cel-c/src/ctype.h" + +// NOLINTBEGIN(runtime/int) +// NOLINTBEGIN(google-runtime-int) + +CEL_ATTRIBUTE_NODISCARD +static bool _cel_FromChar(char* cel_nonnull out, char in, int base) { + if (in >= '0' && in <= '9') { + *out = in - '0'; + return *out < base; + } + if (base >= 10 && in >= 'a' && in <= 'z') { + *out = (in - 'a') + (char)10; + return *out < base; + } + if (base >= 10 && in >= 'A' && in <= 'Z') { + *out = (in - 'A') + (char)10; + return *out < base; + } + return false; +} + +extern "C" _cel_FromCharsResult _cel_FromChars_signed( + const char* cel_nonnull first, const char* cel_nonnull last, + long long* cel_nonnull val, int base, long long min, long long max) { + bool negative = first != last && *first == '-'; + if (negative) { + ++first; + } + _cel_FromCharsResult result; + result.ec = 0; + bool digits = false; + *val = 0; + while (first != last) { + char digit; + if (!_cel_FromChar(&digit, *first, base)) { + break; + } + if (_cel_ckd_mul(val, *val, (long long)base)) { + result.ec = ERANGE; + break; + } + if (_cel_ckd_add(val, *val, (long long)digit)) { + result.ec = ERANGE; + break; + } + if (*val < min || *val > max) { + result.ec = ERANGE; + break; + } + digits = true; + ++first; + } + result.ptr = first; + if (!digits) { + result.ec = EINVAL; + } else if (negative && _cel_ckd_mul(val, *val, (long long)-1) && + (*val < min || *val > max)) { + result.ec = ERANGE; + } + return result; +} + +extern "C" _cel_FromCharsResult _cel_FromChars_unsigned( + const char* cel_nonnull first, const char* cel_nonnull last, + unsigned long long* cel_nonnull val, int base, unsigned long long max) { + _cel_FromCharsResult result; + result.ec = 0; + bool digits = false; + *val = 0; + while (first != last) { + char digit; + if (!_cel_FromChar(&digit, *first, base)) { + break; + } + if (_cel_ckd_mul(val, *val, (unsigned long long)base)) { + result.ec = ERANGE; + break; + } + if (_cel_ckd_add(val, *val, (unsigned long long)digit)) { + result.ec = ERANGE; + break; + } + if (*val > max) { + result.ec = ERANGE; + break; + } + digits = true; + ++first; + } + result.ptr = first; + if (!digits) { + result.ec = EINVAL; + } + return result; +} + +typedef struct { + char str[5]; + uint8_t len; +} _cel_FromChars_LocaleRadix; + +CEL_ATTRIBUTE_NODISCARD +static _cel_FromChars_LocaleRadix _cel_FromChars_GetLocaleRadix() { + _cel_FromChars_LocaleRadix result; +#ifdef LC_GLOBAL_LOCALE + struct lconv* lc = localeconv(); + result.len = (uint8_t)strlen(lc->decimal_point); + CEL_ASSERT_GT(result.len, 0); + CEL_ASSERT_LE(result.len, 4); + memcpy(result.str, lc->decimal_point, result.len); + result.str[result.len] = '\0'; +#else + char buffer[/*1*/ 1 + /**/ 4 + /*5*/ 1 + /*\0*/ 1]; + snprintf(buffer, sizeof(buffer) / sizeof(buffer[0]), "%.1f", 1.5f); + size_t len = strlen(buffer); + CEL_ASSERT_GT(len, 2); + CEL_ASSERT_EQ(buffer[0], '1'); + CEL_ASSERT_EQ(buffer[len - 1], '5'); + size_t out_len = len - 2; + CEL_ASSERT_GE(out_len, 1); + CEL_ASSERT_LE(out_len, 4); + result.len = (uint8_t)(len - 2); + memcpy(result.str, buffer + 1, out_len); + result.str[result.len] = '\0'; +#endif + return result; +} + +CEL_ATTRIBUTE_NODISCARD +static const char* cel_nonnull _cel_FromChars_MatchMinus( + const char* cel_nonnull first, const char* cel_nonnull last) { + if (first == last || *first != '-') { + return first; + } + return first + 1; +} + +CEL_ATTRIBUTE_NODISCARD +static const char* cel_nonnull _cel_FromChars_MatchDigits( + const char* cel_nonnull first, const char* cel_nonnull last) { + while (first != last) { + if (!_cel_isdigit(*first)) { + break; + } + ++first; + } + return first; +} + +CEL_ATTRIBUTE_NODISCARD +static const char* cel_nonnull _cel_FromChars_MatchDot( + const char* cel_nonnull first, const char* cel_nonnull last) { + if (first == last || *first != '.') { + return first; + } + return first + 1; +} + +CEL_ATTRIBUTE_NODISCARD +static const char* cel_nonnull _cel_FromChars_MatchE( + const char* cel_nonnull first, const char* cel_nonnull last) { + if (first == last || (*first != 'e' && *first != 'E')) { + return first; + } + return first + 1; +} + +CEL_ATTRIBUTE_NODISCARD +static const char* cel_nonnull _cel_FromChars_MatchSign( + const char* cel_nonnull first, const char* cel_nonnull last) { + if (first == last || (*first != '-' && *first != '+')) { + return first; + } + return first + 1; +} + +CEL_ATTRIBUTE_NODISCARD +static int _cel_FromChars_Match(const char* cel_nonnull first, + const char* cel_nonnull last, + char* cel_nonnull out, size_t maxlen, + const char* cel_nullable* cel_nonnull end, + const char* cel_nullable* cel_nonnull radix, + uint8_t* cel_nonnull radixadj) { + const char* cel_nonnull ptr = first; + ptr = _cel_FromChars_MatchMinus(ptr, last); + ptr = _cel_FromChars_MatchDigits(ptr, last); + const char* cel_nullable sep = _cel_FromChars_MatchDot(ptr, last); + if (sep == ptr) { + sep = NULL; + } else { + const char* next = sep; + sep = ptr; + ptr = next; + } + ptr = _cel_FromChars_MatchDigits(ptr, last); + ptr = _cel_FromChars_MatchE(ptr, last); + ptr = _cel_FromChars_MatchSign(ptr, last); + ptr = _cel_FromChars_MatchDigits(ptr, last); + *end = ptr; + size_t len = ptr - first; + if (len >= maxlen) { + return ERANGE; + } + if (sep != NULL) { + _cel_FromChars_LocaleRadix r = _cel_FromChars_GetLocaleRadix(); + size_t before_radix = sep - first; + size_t after_radix = sep < ptr ? (ptr - (sep + 1)) : 0; + memcpy(out, first, before_radix); + memcpy(out + before_radix, r.str, r.len); + memcpy(out + before_radix + r.len, sep + 1, after_radix); + out[before_radix + r.len + after_radix] = '\0'; + *radix = sep; + *radixadj = r.len - 1; + } else { + memcpy(out, first, len); + out[len] = '\0'; + *radix = NULL; + *radixadj = 0; + } + return 0; +} + +extern "C" _cel_FromCharsResult _cel_FromChars_f(const char* cel_nonnull first, + const char* cel_nonnull last, + float* cel_nonnull val) { + _cel_FromCharsResult result; + char buffer[FLT_MANT_DIG + FLT_MAX_EXP + 1 + 4 + 1 + 1]; + const char* radix; + uint8_t radixadj; + result.ec = _cel_FromChars_Match(first, last, buffer, sizeof(buffer) - 3, + &result.ptr, &radix, &radixadj); + if (result.ec != 0) { + return result; + } + size_t radixoff = SIZE_MAX; + if (radix != NULL) { + radixoff = radix - first; + } + int prev_errno = errno; + errno = 0; + char* endptr; + *val = strtof(buffer, &endptr); + result.ec = errno; + result.ptr = first + (endptr - buffer) - + (endptr - buffer > radixoff ? radixadj : ((uint8_t)0)); + errno = prev_errno; + return result; +} + +extern "C" _cel_FromCharsResult _cel_FromChars_d(const char* cel_nonnull first, + const char* cel_nonnull last, + double* cel_nonnull val) { + _cel_FromCharsResult result; + const char* radix; + uint8_t radixadj; + char buffer[DBL_MANT_DIG + DBL_MAX_EXP + 1 + 4 + 1 + 1]; + result.ec = _cel_FromChars_Match(first, last, buffer, sizeof(buffer) - 3, + &result.ptr, &radix, &radixadj); + if (result.ec != 0) { + return result; + } + size_t radixoff = SIZE_MAX; + if (radix != NULL) { + radixoff = radix - first; + } + int prev_errno = errno; + errno = 0; + char* endptr; + *val = strtod(buffer, &endptr); + result.ec = errno; + result.ptr = first + (endptr - buffer) - + (endptr - buffer > radixoff ? radixadj : ((uint8_t)0)); + errno = prev_errno; + return result; +} + +static const char* _cel_ToChars_kDigits = + "0123456789abcdefghijklmnopqrstuvwxyz"; + +static void _cel_ToChars_Reverse(char* cel_nonnull first, + char* cel_nonnull last) { + if (first < last) { + --last; + while (first < last) { + char c = *first; + *first = *last; + *last = c; + ++first; + --last; + } + } +} + +extern "C" size_t _cel_ToChars_ll(char* cel_nonnull out, long long val, + int base) { + CEL_ASSERT_NOT_NULL(out); + CEL_ASSERT_GE(base, 2); + CEL_ASSERT_LE(base, 36); + + const bool negative = val < 0; + if (negative) { + *out++ = '-'; + } + char* begin = out; + do { + int i = (int)(val % base); + if (negative) { + i = -i; + } + *out++ = _cel_ToChars_kDigits[i]; + val /= base; + } while (val != 0); + _cel_ToChars_Reverse(begin, out); + return (size_t)(out - begin) + negative; +} + +extern "C" size_t _cel_ToChars_ull(char* cel_nonnull out, + unsigned long long val, int base) { + CEL_ASSERT_NOT_NULL(out); + CEL_ASSERT_GE(base, 2); + CEL_ASSERT_LE(base, 36); + + char* begin = out; + do { + int i = (int)(val % base); + *out++ = _cel_ToChars_kDigits[i]; + val /= base; + } while (val != 0); + _cel_ToChars_Reverse(begin, out); + return (size_t)(out - begin); +} + +CEL_ATTRIBUTE_NODISCARD +static size_t _cel_ToChars_FixLocale(char* cel_nonnull p) { + _cel_FromChars_LocaleRadix radix = _cel_FromChars_GetLocaleRadix(); + size_t n = strlen(p); + char* r = strstr(p, radix.str); + if (r == NULL) { + return n; + } + if (radix.len != 1) { + memmove(r + 1, r + radix.len, (p + n) - (r + radix.len)); + n -= radix.len - 1; + p[n] = '\0'; + } + *r = '.'; + return n; +} + +extern "C" size_t _cel_ToChars_f(char* cel_nonnull first, float val) { + first[0] = '\0'; + snprintf(first, _CEL_MAX_FLOAT_CHARS, "%.9g", val); + return _cel_ToChars_FixLocale(first); +} + +extern "C" size_t _cel_ToChars_d(char* cel_nonnull first, double val) { + first[0] = '\0'; + snprintf(first, _CEL_MAX_DOUBLE_CHARS, "%.17g", val); + return _cel_ToChars_FixLocale(first); +} + +// NOLINTEND(google-runtime-int) +// NOLINTEND(runtime/int) diff --git a/cel-c/src/ctype.cc b/cel-c/src/ctype.cc new file mode 100644 index 0000000..6e1cca8 --- /dev/null +++ b/cel-c/src/ctype.cc @@ -0,0 +1,98 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "cel-c/src/ctype.h" + +extern "C" const unsigned char _cel_CType_kProperties[256] = { + 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, // 0x00 + 0x40, 0x68, 0x48, 0x48, 0x48, 0x48, 0x40, 0x40, + 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, // 0x10 + 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, + 0x28, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, // 0x20 + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, // 0x30 + 0x84, 0x84, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x10, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x05, // 0x40 + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, // 0x50 + 0x05, 0x05, 0x05, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x10, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x05, // 0x60 + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, // 0x70 + 0x05, 0x05, 0x05, 0x10, 0x10, 0x10, 0x10, 0x40, +}; + +extern "C" const char _cel_CType_kToLower[256] = { + '\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07', '\x08', + '\x09', '\x0a', '\x0b', '\x0c', '\x0d', '\x0e', '\x0f', '\x10', '\x11', + '\x12', '\x13', '\x14', '\x15', '\x16', '\x17', '\x18', '\x19', '\x1a', + '\x1b', '\x1c', '\x1d', '\x1e', '\x1f', '\x20', '\x21', '\x22', '\x23', + '\x24', '\x25', '\x26', '\x27', '\x28', '\x29', '\x2a', '\x2b', '\x2c', + '\x2d', '\x2e', '\x2f', '\x30', '\x31', '\x32', '\x33', '\x34', '\x35', + '\x36', '\x37', '\x38', '\x39', '\x3a', '\x3b', '\x3c', '\x3d', '\x3e', + '\x3f', '\x40', 'a', 'b', 'c', 'd', 'e', 'f', 'g', + 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', + 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', + 'z', '\x5b', '\x5c', '\x5d', '\x5e', '\x5f', '\x60', '\x61', '\x62', + '\x63', '\x64', '\x65', '\x66', '\x67', '\x68', '\x69', '\x6a', '\x6b', + '\x6c', '\x6d', '\x6e', '\x6f', '\x70', '\x71', '\x72', '\x73', '\x74', + '\x75', '\x76', '\x77', '\x78', '\x79', '\x7a', '\x7b', '\x7c', '\x7d', + '\x7e', '\x7f', '\x80', '\x81', '\x82', '\x83', '\x84', '\x85', '\x86', + '\x87', '\x88', '\x89', '\x8a', '\x8b', '\x8c', '\x8d', '\x8e', '\x8f', + '\x90', '\x91', '\x92', '\x93', '\x94', '\x95', '\x96', '\x97', '\x98', + '\x99', '\x9a', '\x9b', '\x9c', '\x9d', '\x9e', '\x9f', '\xa0', '\xa1', + '\xa2', '\xa3', '\xa4', '\xa5', '\xa6', '\xa7', '\xa8', '\xa9', '\xaa', + '\xab', '\xac', '\xad', '\xae', '\xaf', '\xb0', '\xb1', '\xb2', '\xb3', + '\xb4', '\xb5', '\xb6', '\xb7', '\xb8', '\xb9', '\xba', '\xbb', '\xbc', + '\xbd', '\xbe', '\xbf', '\xc0', '\xc1', '\xc2', '\xc3', '\xc4', '\xc5', + '\xc6', '\xc7', '\xc8', '\xc9', '\xca', '\xcb', '\xcc', '\xcd', '\xce', + '\xcf', '\xd0', '\xd1', '\xd2', '\xd3', '\xd4', '\xd5', '\xd6', '\xd7', + '\xd8', '\xd9', '\xda', '\xdb', '\xdc', '\xdd', '\xde', '\xdf', '\xe0', + '\xe1', '\xe2', '\xe3', '\xe4', '\xe5', '\xe6', '\xe7', '\xe8', '\xe9', + '\xea', '\xeb', '\xec', '\xed', '\xee', '\xef', '\xf0', '\xf1', '\xf2', + '\xf3', '\xf4', '\xf5', '\xf6', '\xf7', '\xf8', '\xf9', '\xfa', '\xfb', + '\xfc', '\xfd', '\xfe', '\xff', +}; + +extern "C" const char _cel_CType_kToUpper[256] = { + '\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07', '\x08', + '\x09', '\x0a', '\x0b', '\x0c', '\x0d', '\x0e', '\x0f', '\x10', '\x11', + '\x12', '\x13', '\x14', '\x15', '\x16', '\x17', '\x18', '\x19', '\x1a', + '\x1b', '\x1c', '\x1d', '\x1e', '\x1f', '\x20', '\x21', '\x22', '\x23', + '\x24', '\x25', '\x26', '\x27', '\x28', '\x29', '\x2a', '\x2b', '\x2c', + '\x2d', '\x2e', '\x2f', '\x30', '\x31', '\x32', '\x33', '\x34', '\x35', + '\x36', '\x37', '\x38', '\x39', '\x3a', '\x3b', '\x3c', '\x3d', '\x3e', + '\x3f', '\x40', '\x41', '\x42', '\x43', '\x44', '\x45', '\x46', '\x47', + '\x48', '\x49', '\x4a', '\x4b', '\x4c', '\x4d', '\x4e', '\x4f', '\x50', + '\x51', '\x52', '\x53', '\x54', '\x55', '\x56', '\x57', '\x58', '\x59', + '\x5a', '\x5b', '\x5c', '\x5d', '\x5e', '\x5f', '\x60', 'A', 'B', + 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', + 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', + 'U', 'V', 'W', 'X', 'Y', 'Z', '\x7b', '\x7c', '\x7d', + '\x7e', '\x7f', '\x80', '\x81', '\x82', '\x83', '\x84', '\x85', '\x86', + '\x87', '\x88', '\x89', '\x8a', '\x8b', '\x8c', '\x8d', '\x8e', '\x8f', + '\x90', '\x91', '\x92', '\x93', '\x94', '\x95', '\x96', '\x97', '\x98', + '\x99', '\x9a', '\x9b', '\x9c', '\x9d', '\x9e', '\x9f', '\xa0', '\xa1', + '\xa2', '\xa3', '\xa4', '\xa5', '\xa6', '\xa7', '\xa8', '\xa9', '\xaa', + '\xab', '\xac', '\xad', '\xae', '\xaf', '\xb0', '\xb1', '\xb2', '\xb3', + '\xb4', '\xb5', '\xb6', '\xb7', '\xb8', '\xb9', '\xba', '\xbb', '\xbc', + '\xbd', '\xbe', '\xbf', '\xc0', '\xc1', '\xc2', '\xc3', '\xc4', '\xc5', + '\xc6', '\xc7', '\xc8', '\xc9', '\xca', '\xcb', '\xcc', '\xcd', '\xce', + '\xcf', '\xd0', '\xd1', '\xd2', '\xd3', '\xd4', '\xd5', '\xd6', '\xd7', + '\xd8', '\xd9', '\xda', '\xdb', '\xdc', '\xdd', '\xde', '\xdf', '\xe0', + '\xe1', '\xe2', '\xe3', '\xe4', '\xe5', '\xe6', '\xe7', '\xe8', '\xe9', + '\xea', '\xeb', '\xec', '\xed', '\xee', '\xef', '\xf0', '\xf1', '\xf2', + '\xf3', '\xf4', '\xf5', '\xf6', '\xf7', '\xf8', '\xf9', '\xfa', '\xfb', + '\xfc', '\xfd', '\xfe', '\xff', +}; diff --git a/cel-c/src/empty_list_value.cc b/cel-c/src/empty_list_value.cc new file mode 100644 index 0000000..3ae54de --- /dev/null +++ b/cel-c/src/empty_list_value.cc @@ -0,0 +1,170 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "cel-c/src/empty_list_value.h" + +#include +#include +#include +#include + +#include "cel-c/assert.h" +#include "cel-c/config.h" +#include "cel-c/error.h" +#include "cel-c/error_code.h" +#include "cel-c/status.h" +#include "cel-c/string_view.h" +#include "cel-c/value.h" + +extern const cel_ListValueIteratorVTable _cel_EmptyListValueIteratorVTable; + +static bool _cel_EmptyListValue_FastSize( + const cel_ListValueVTable* cel_nonnull vtable, cel_ValueContent content, + size_t* cel_nonnull size) { + CEL_ASSERT_EQ(vtable, &_cel_EmptyListValueVTable); + CEL_ASSERT_NOT_NULL(size); + + *size = 0; + return true; +} + +static bool _cel_EmptyListValue_SlowSize( + const cel_ListValueVTable* cel_nonnull vtable, cel_ValueContent content, + const cel_ValueContext* cel_nonnull context, cel_Value* cel_nonnull size, + cel_Status* cel_nonnull status) { + CEL_ASSERT_EQ(vtable, &_cel_EmptyListValueVTable); + CEL_ASSERT_NOT_NULL(context); + CEL_ASSERT_NOT_NULL(size); + CEL_ASSERT_NOT_NULL(status); + + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return false; + } + + cel_Value_SetInt(size, 0); + return true; +} + +static bool _cel_EmptyListValue_Get( + const cel_ListValueVTable* cel_nonnull vtable, cel_ValueContent content, + const cel_ValueContext* cel_nonnull context, size_t index, + cel_Value* cel_nonnull element, cel_Status* cel_nonnull status) { + CEL_ASSERT_EQ(vtable, &_cel_EmptyListValueVTable); + CEL_ASSERT_NOT_NULL(context); + CEL_ASSERT_NOT_NULL(element); + CEL_ASSERT_NOT_NULL(status); + + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return false; + } + + cel_Error* error = cel_Error_New(context->arena); + if (CEL_UNLIKELY(error == cel_nullptr)) { + cel_OutOfMemoryStatus(status); + return false; + } + cel_Error_SetCanonicalCode(error, cel_ErrorCode_kOutOfRange); + cel_Error_SetMessage(error, cel_StringView_From("index out of range")); + cel_Value_SetError(element, error); + return true; +} + +static cel_ListValueIterator* cel_nullable _cel_EmptyListValue_NewIterator( + const cel_ListValueVTable* cel_nonnull vtable, cel_ValueContent content, + const cel_ValueContext* cel_nonnull context, + cel_Status* cel_nonnull status) { + CEL_ASSERT_EQ(vtable, &_cel_EmptyListValueVTable); + CEL_ASSERT_NOT_NULL(context); + CEL_ASSERT_NOT_NULL(status); + + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return cel_nullptr; + } + + return &_cel_EmptyListValueIterator; +} + +const cel_ListValueVTable _cel_EmptyListValueVTable = { + .Equals = cel_nullptr, + .FastSize = &_cel_EmptyListValue_FastSize, + .SlowSize = &_cel_EmptyListValue_SlowSize, + .Get = &_cel_EmptyListValue_Get, + .NewIterator = &_cel_EmptyListValue_NewIterator, +}; + +static bool _cel_EmptyListValueIterator_Next1( + cel_ValueIterator* cel_nonnull iterator, + const cel_ValueContext* cel_nonnull context, + cel_Value* cel_nonnull key_or_value, cel_Status* cel_nonnull status) { + CEL_ASSERT_NOT_NULL(iterator); + CEL_ASSERT_EQ(iterator->vtable, &_cel_EmptyListValueIteratorVTable.super); + CEL_ASSERT_NOT_NULL(context); + CEL_ASSERT_NOT_NULL(key_or_value); + CEL_ASSERT_NOT_NULL(status); + + return false; +} + +static bool _cel_EmptyListValueIterator_Next2( + cel_ValueIterator* cel_nonnull iterator, + const cel_ValueContext* cel_nonnull context, cel_Value* cel_nonnull key, + cel_Value* cel_nonnull value, cel_Status* cel_nonnull status) { + CEL_ASSERT_NOT_NULL(iterator); + CEL_ASSERT_EQ(iterator->vtable, &_cel_EmptyListValueIteratorVTable.super); + CEL_ASSERT_NOT_NULL(context); + CEL_ASSERT_NOT_NULL(key); + CEL_ASSERT_NOT_NULL(value); + CEL_ASSERT_NOT_NULL(status); + + return false; +} + +static bool _cel_EmptyListValueIterator_Remaining( + const cel_ValueIterator* cel_nonnull iterator, + size_t* cel_nonnull remaining) { + CEL_ASSERT_NOT_NULL(iterator); + CEL_ASSERT_EQ(iterator->vtable, &_cel_EmptyListValueIteratorVTable.super); + CEL_ASSERT_NOT_NULL(remaining); + + *remaining = 0; + return true; +} + +static bool _cel_EmptyListValueIterator_Next( + cel_ListValueIterator* cel_nonnull iterator, + const cel_ValueContext* cel_nonnull context, size_t* cel_nullable index, + cel_Value* cel_nonnull value, cel_Status* cel_nonnull status) { + CEL_ASSERT_NOT_NULL(iterator); + CEL_ASSERT_EQ(iterator->vtable, &_cel_EmptyListValueIteratorVTable); + CEL_ASSERT_NOT_NULL(context); + CEL_ASSERT_NOT_NULL(value); + CEL_ASSERT_NOT_NULL(status); + + return false; +} + +const cel_ListValueIteratorVTable _cel_EmptyListValueIteratorVTable = { + .super = + { + .Delete = cel_nullptr, + .Next1 = &_cel_EmptyListValueIterator_Next1, + .Next2 = &_cel_EmptyListValueIterator_Next2, + .Remaining = &_cel_EmptyListValueIterator_Remaining, + }, + .Next = &_cel_EmptyListValueIterator_Next, +}; + +extern "C" cel_ListValueIterator _cel_EmptyListValueIterator = { + .vtable = &_cel_EmptyListValueIteratorVTable, +}; diff --git a/cel-c/src/empty_map_value.cc b/cel-c/src/empty_map_value.cc new file mode 100644 index 0000000..99ed0a1 --- /dev/null +++ b/cel-c/src/empty_map_value.cc @@ -0,0 +1,211 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "cel-c/src/empty_map_value.h" + +#include +#include +#include +#include + +#include "cel-c/assert.h" +#include "cel-c/config.h" +#include "cel-c/error.h" +#include "cel-c/error_code.h" +#include "cel-c/status.h" +#include "cel-c/string_view.h" +#include "cel-c/value.h" + +extern const cel_MapValueIteratorVTable _cel_EmptyMapValueIteratorVTable; + +static bool _cel_EmptyMapValue_FastSize( + const cel_MapValueVTable* cel_nonnull vtable, cel_ValueContent content, + size_t* cel_nonnull size) { + CEL_ASSERT_EQ(vtable, &_cel_EmptyMapValueVTable); + CEL_ASSERT_NOT_NULL(size); + + *size = 0; + return true; +} + +static bool _cel_EmptyMapValue_SlowSize( + const cel_MapValueVTable* cel_nonnull vtable, cel_ValueContent content, + const cel_ValueContext* cel_nonnull context, cel_Value* cel_nonnull size, + cel_Status* cel_nonnull status) { + CEL_ASSERT_EQ(vtable, &_cel_EmptyMapValueVTable); + CEL_ASSERT_NOT_NULL(context); + CEL_ASSERT_NOT_NULL(size); + CEL_ASSERT_NOT_NULL(status); + + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return false; + } + + cel_Value_SetInt(size, 0); + return true; +} + +static bool _cel_EmptyMapValue_Get(const cel_MapValueVTable* cel_nonnull vtable, + cel_ValueContent content, + const cel_ValueContext* cel_nonnull context, + const cel_MapValueKey* cel_nonnull key, + cel_Value* cel_nonnull value, + cel_Status* cel_nonnull status) { + CEL_ASSERT_EQ(vtable, &_cel_EmptyMapValueVTable); + CEL_ASSERT_NOT_NULL(context); + CEL_ASSERT_NOT_NULL(key); + CEL_ASSERT_NOT_NULL(value); + CEL_ASSERT_NOT_NULL(status); + + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return false; + } + + cel_Error* error = cel_Error_New(context->arena); + if (CEL_UNLIKELY(error == cel_nullptr)) { + cel_OutOfMemoryStatus(status); + return false; + } + cel_Error_SetCanonicalCode(error, cel_ErrorCode_kNotFound); + cel_Error_SetMessage(error, cel_StringView_From("no such key")); + cel_Value_SetError(value, error); + return true; +} + +static bool _cel_EmptyMapValue_Find( + const cel_MapValueVTable* cel_nonnull vtable, cel_ValueContent content, + const cel_ValueContext* cel_nonnull context, + const cel_MapValueKey* cel_nonnull key, cel_Value* cel_nonnull value, + cel_Status* cel_nonnull status) { + CEL_ASSERT_EQ(vtable, &_cel_EmptyMapValueVTable); + CEL_ASSERT_NOT_NULL(context); + CEL_ASSERT_NOT_NULL(key); + CEL_ASSERT_NOT_NULL(value); + CEL_ASSERT_NOT_NULL(status); + + return false; +} + +static bool _cel_EmptyMapValue_Has(const cel_MapValueVTable* cel_nonnull vtable, + cel_ValueContent content, + const cel_ValueContext* cel_nonnull context, + const cel_MapValueKey* cel_nonnull key, + cel_Value* cel_nonnull result, + cel_Status* cel_nonnull status) { + CEL_ASSERT_EQ(vtable, &_cel_EmptyMapValueVTable); + CEL_ASSERT_NOT_NULL(context); + CEL_ASSERT_NOT_NULL(key); + CEL_ASSERT_NOT_NULL(result); + CEL_ASSERT_NOT_NULL(status); + + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return false; + } + + cel_Value_SetFalse(result); + return true; +} + +static cel_MapValueIterator* cel_nullable _cel_EmptyMapValue_NewIterator( + const cel_MapValueVTable* cel_nonnull vtable, cel_ValueContent content, + const cel_ValueContext* cel_nonnull context, + cel_Status* cel_nonnull status) { + CEL_ASSERT_EQ(vtable, &_cel_EmptyMapValueVTable); + CEL_ASSERT_NOT_NULL(context); + CEL_ASSERT_NOT_NULL(status); + + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return cel_nullptr; + } + + return &_cel_EmptyMapValueIterator; +} + +const cel_MapValueVTable _cel_EmptyMapValueVTable = { + .Equals = cel_nullptr, + .FastSize = &_cel_EmptyMapValue_FastSize, + .SlowSize = &_cel_EmptyMapValue_SlowSize, + .Get = &_cel_EmptyMapValue_Get, + .Find = &_cel_EmptyMapValue_Find, + .Has = &_cel_EmptyMapValue_Has, + .NewIterator = &_cel_EmptyMapValue_NewIterator, +}; + +static bool _cel_EmptyMapValueIterator_Next1( + cel_ValueIterator* cel_nonnull iterator, + const cel_ValueContext* cel_nonnull context, + cel_Value* cel_nonnull key_or_value, cel_Status* cel_nonnull status) { + CEL_ASSERT_NOT_NULL(iterator); + CEL_ASSERT_EQ(iterator->vtable, &_cel_EmptyMapValueIteratorVTable.super); + CEL_ASSERT_NOT_NULL(context); + CEL_ASSERT_NOT_NULL(key_or_value); + CEL_ASSERT_NOT_NULL(status); + + return false; +} + +static bool _cel_EmptyMapValueIterator_Next2( + cel_ValueIterator* cel_nonnull iterator, + const cel_ValueContext* cel_nonnull context, cel_Value* cel_nonnull key, + cel_Value* cel_nonnull value, cel_Status* cel_nonnull status) { + CEL_ASSERT_NOT_NULL(iterator); + CEL_ASSERT_EQ(iterator->vtable, &_cel_EmptyMapValueIteratorVTable.super); + CEL_ASSERT_NOT_NULL(context); + CEL_ASSERT_NOT_NULL(key); + CEL_ASSERT_NOT_NULL(value); + CEL_ASSERT_NOT_NULL(status); + + return false; +} + +static bool _cel_EmptyMapValueIterator_Remaining( + const cel_ValueIterator* cel_nonnull iterator, + size_t* cel_nonnull remaining) { + CEL_ASSERT_NOT_NULL(iterator); + CEL_ASSERT_EQ(iterator->vtable, &_cel_EmptyMapValueIteratorVTable.super); + CEL_ASSERT_NOT_NULL(remaining); + + *remaining = 0; + return true; +} + +static bool _cel_EmptyMapValueIterator_Next( + cel_MapValueIterator* cel_nonnull iterator, + const cel_ValueContext* cel_nonnull context, + cel_MapValueKey* cel_nullable key, cel_Value* cel_nonnull value, + cel_Status* cel_nonnull status) { + CEL_ASSERT_NOT_NULL(iterator); + CEL_ASSERT_EQ(iterator->vtable, &_cel_EmptyMapValueIteratorVTable); + CEL_ASSERT_NOT_NULL(context); + CEL_ASSERT_NOT_NULL(key); + CEL_ASSERT_NOT_NULL(value); + CEL_ASSERT_NOT_NULL(status); + + return false; +} + +const cel_MapValueIteratorVTable _cel_EmptyMapValueIteratorVTable = { + .super = + { + .Delete = cel_nullptr, + .Next1 = &_cel_EmptyMapValueIterator_Next1, + .Next2 = &_cel_EmptyMapValueIterator_Next2, + .Remaining = &_cel_EmptyMapValueIterator_Remaining, + }, + .Next = &_cel_EmptyMapValueIterator_Next, +}; + +extern "C" cel_MapValueIterator _cel_EmptyMapValueIterator = { + .vtable = &_cel_EmptyMapValueIteratorVTable, +}; diff --git a/cel-c/src/generic_array.cc b/cel-c/src/generic_array.cc new file mode 100644 index 0000000..788ef04 --- /dev/null +++ b/cel-c/src/generic_array.cc @@ -0,0 +1,385 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "cel-c/src/generic_array.h" + +#include +#include // IWYU pragma: keep +#include +#include + +#include "cel-c/alloc.h" +#include "cel-c/arena.h" +#include "cel-c/assert.h" +#include "cel-c/src/asan.h" +#include "cel-c/src/ckdint.h" +#include "cel-c/src/config.h" + +static void _cel_GenericArray_Annotate(CEL_NULLABLE(const void*) ptr, + size_t cap, size_t old_len, + size_t new_len, size_t ele_size) { + _cel_sanitizer_annotate_contiguous_container( + ptr, (((CEL_NULLABLE(const char*))ptr) + (cap * ele_size)), + (((CEL_NULLABLE(const char*))ptr) + (old_len * ele_size)), + (((CEL_NULLABLE(const char*))ptr) + (new_len * ele_size))); +} + +// This function abstracts away the complication ASan imposes on us resizing. +CEL_ATTRIBUTE_NODISCARD +static bool _cel_GenericArray_ReallocateAllocator( + CEL_NONNULL(_cel_GenericArray*) arr, CEL_NONNULL(cel_Allocator*) alloc, + size_t new_len, size_t new_cap, size_t ele_size) { + CEL_ASSERT_NE(new_cap, arr->cap); + CEL_ASSERT_LE(new_len, new_cap); + size_t new_cap_bytes; + if (CEL_UNLIKELY(_cel_ckd_mul(&new_cap_bytes, new_cap, ele_size))) { + errno = ENOMEM; + return false; + } + size_t actual_new_cap_bytes; + CEL_NULLABLE(void*) new_ptr; +#ifdef _CEL_HAVE_ASAN + // Under ASan we cannot reallocate directly as we have to perform bookkeeping + // work. Instead perform an allocation, copy, and deallocate the old + // allocation. + new_ptr = cel_Allocator_Malloc(alloc, new_cap_bytes, &actual_new_cap_bytes); + if (CEL_UNLIKELY(new_ptr == cel_nullptr)) { + return false; + } + new_cap = actual_new_cap_bytes / ele_size; + // Annotate new allocation with ASan to match the state before copying to it. + _cel_GenericArray_Annotate(new_ptr, new_cap, new_cap, new_len, ele_size); + memcpy(new_ptr, arr->ptr, + (arr->len < new_len ? arr->len : new_len) * ele_size); + // Annotate the old allocation back to its original state, which is the whole + // allocation is valid. + _cel_GenericArray_Annotate(arr->ptr, arr->cap, arr->len, arr->cap, ele_size); + cel_Allocator_FreeSized(alloc, arr->ptr, arr->cap * ele_size); +#else + new_ptr = cel_Allocator_Realloc(alloc, arr->ptr, arr->cap * ele_size, + new_cap_bytes, &actual_new_cap_bytes); + if (CEL_UNLIKELY(new_ptr == cel_nullptr)) { + return false; + } + new_cap = actual_new_cap_bytes / ele_size; +#endif + arr->ptr = new_ptr; + arr->cap = new_cap; + arr->len = new_len; + return true; +} + +extern "C" void _cel_GenericArray_Construct(CEL_NONNULL(_cel_GenericArray*) + arr) { + CEL_ASSERT_NOT_NULL(arr); + memset(arr, '\0', sizeof(*arr)); +} + +extern "C" void _cel_GenericArray_DestructAllocator( + CEL_NONNULL(_cel_GenericArray*) arr, CEL_NONNULL(cel_Allocator*) alloc, + size_t ele_size) { + CEL_ASSERT_NOT_NULL(arr); + CEL_ASSERT_NOT_NULL(alloc); + CEL_ASSERT_GT(ele_size, 0); + CEL_ASSERT_LE(arr->len, arr->cap); + if (arr->cap > 0) { + _cel_GenericArray_Annotate(arr->ptr, arr->cap, /*old_len=*/arr->len, + /*new_len=*/arr->cap, ele_size); + cel_Allocator_FreeSized(alloc, arr->ptr, arr->cap * ele_size); + } +} + +extern "C" bool _cel_GenericArray_ReserveAllocator( + CEL_NONNULL(_cel_GenericArray*) arr, CEL_NONNULL(cel_Allocator*) alloc, + size_t new_cap, size_t ele_size) { + CEL_ASSERT_NOT_NULL(arr); + CEL_ASSERT_NOT_NULL(alloc); + CEL_ASSERT_GT(ele_size, 0); + CEL_ASSERT_LE(arr->len, arr->cap); + if (arr->cap >= new_cap) { + return true; + } + return _cel_GenericArray_ReallocateAllocator(arr, alloc, arr->len, new_cap, + ele_size); +} + +extern "C" bool _cel_GenericArray_ReserveArena(CEL_NONNULL(_cel_GenericArray*) + arr, + CEL_NONNULL(cel_Arena*) arena, + size_t new_cap, + size_t ele_size) { + CEL_ASSERT_NOT_NULL(arr); + CEL_ASSERT_NOT_NULL(arena); + CEL_ASSERT_GT(ele_size, 0); + CEL_ASSERT_LE(arr->len, arr->cap); + if (arr->cap >= new_cap) { + return true; + } + size_t new_cap_bytes; + if (CEL_UNLIKELY(_cel_ckd_mul(&new_cap_bytes, new_cap, ele_size))) { + errno = ENOMEM; + return false; + } + CEL_NULLABLE(void*) + new_ptr = cel_Arena_Realloc(arena, arr->ptr, arr->cap * ele_size, + new_cap_bytes, cel_nullptr); + if (CEL_UNLIKELY(new_ptr == cel_nullptr)) { + return false; + } + arr->ptr = new_ptr; + arr->cap = new_cap; + return true; +} + +extern "C" void _cel_GenericArray_ShrinkToFitAllocator( + CEL_NONNULL(_cel_GenericArray*) arr, CEL_NONNULL(cel_Allocator*) alloc, + size_t ele_size) { + CEL_ASSERT_NOT_NULL(arr); + CEL_ASSERT_NOT_NULL(alloc); + CEL_ASSERT_GT(ele_size, 0); + CEL_ASSERT_LE(arr->len, arr->cap); + if (arr->len == arr->cap) { + return; + } + if (arr->len == 0) { + _cel_GenericArray_ResetAllocator(arr, alloc, ele_size); + return; + } + CEL_USED(_cel_GenericArray_ReallocateAllocator(arr, alloc, arr->len, arr->len, + ele_size)); +} + +extern "C" CEL_NULLABLE(void*) + _cel_GenericArray_ResizeAllocator(CEL_NONNULL(_cel_GenericArray*) arr, + CEL_NONNULL(cel_Allocator*) alloc, + size_t new_len, size_t ele_size) { + CEL_ASSERT_NOT_NULL(arr); + CEL_ASSERT_NOT_NULL(alloc); + CEL_ASSERT_GT(ele_size, 0); + CEL_ASSERT_LE(arr->len, arr->cap); + if (CEL_UNLIKELY( + !_cel_GenericArray_ReserveAllocator(arr, alloc, new_len, ele_size))) { + return cel_nullptr; + } + CEL_NULLABLE(void*) ptr; + if (new_len < arr->len) { + ptr = ((CEL_NONNULL(char*))arr->ptr) + (new_len * ele_size); + } else { + ptr = ((CEL_NONNULL(char*))arr->ptr) + (arr->len * ele_size); + } + _cel_GenericArray_Annotate(arr->ptr, arr->cap, arr->len, new_len, ele_size); + arr->len = new_len; + return ptr; +} + +extern "C" CEL_NULLABLE(void*) + _cel_GenericArray_ResizeArena(CEL_NONNULL(_cel_GenericArray*) arr, + CEL_NONNULL(cel_Arena*) arena, size_t new_len, + size_t ele_size) { + CEL_ASSERT_NOT_NULL(arr); + CEL_ASSERT_NOT_NULL(arena); + CEL_ASSERT_GT(ele_size, 0); + CEL_ASSERT_LE(arr->len, arr->cap); + if (CEL_UNLIKELY( + !_cel_GenericArray_ReserveArena(arr, arena, new_len, ele_size))) { + return cel_nullptr; + } + CEL_NULLABLE(void*) ptr; + if (new_len < arr->len) { + ptr = ((CEL_NONNULL(char*))arr->ptr) + (new_len * ele_size); + } else { + ptr = ((CEL_NONNULL(char*))arr->ptr) + (arr->len * ele_size); + } + arr->len = new_len; + return ptr; +} + +extern "C" CEL_NULLABLE(void*) + _cel_GenericArray_AppendAllocator(CEL_NONNULL(_cel_GenericArray*) arr, + CEL_NONNULL(cel_Allocator*) alloc, + size_t n, size_t ele_size) { + CEL_ASSERT_NOT_NULL(arr); + CEL_ASSERT_NOT_NULL(alloc); + CEL_ASSERT_GT(ele_size, 0); + CEL_ASSERT_LE(arr->len, arr->cap); + if (n == 0) { + return cel_nullptr; + } + if (CEL_UNLIKELY(n > arr->cap - arr->len)) { + size_t min_cap; + if (CEL_UNLIKELY(_cel_ckd_add(&min_cap, arr->len, n))) { + errno = ENOMEM; + return cel_nullptr; + } + size_t new_cap = arr->cap; + if (new_cap < 8) { + new_cap = 8; + } + while (new_cap < min_cap) { + if (CEL_UNLIKELY(_cel_ckd_mul(&new_cap, new_cap, (size_t)2))) { + new_cap = min_cap; + break; + } + }; + size_t old_len = arr->len; + if (CEL_UNLIKELY(!_cel_GenericArray_ReallocateAllocator( + arr, alloc, old_len + n, new_cap, ele_size))) { + return cel_nullptr; + } + // _cel_GenericArray_ReallocateAllocator has already done the len upate and + // annotation update. + CEL_NONNULL(void*) + ptr = ((CEL_NONNULL(char*))arr->ptr) + (old_len * ele_size); + return ptr; + } + size_t new_len = arr->len + n; + _cel_GenericArray_Annotate(arr->ptr, arr->cap, arr->len, new_len, ele_size); + CEL_NONNULL(void*) + ptr = ((CEL_NONNULL(char*))arr->ptr) + (arr->len * ele_size); + arr->len = new_len; + return ptr; +} + +extern "C" CEL_NULLABLE(void*) + _cel_GenericArray_AppendArena(CEL_NONNULL(_cel_GenericArray*) arr, + CEL_NONNULL(cel_Arena*) arena, size_t n, + size_t ele_size) { + CEL_ASSERT_NOT_NULL(arr); + CEL_ASSERT_NOT_NULL(arena); + CEL_ASSERT_GT(ele_size, 0); + CEL_ASSERT_LE(arr->len, arr->cap); + if (n == 0) { + return cel_nullptr; + } + if (CEL_UNLIKELY(n > arr->cap - arr->len)) { + size_t min_cap; + if (CEL_UNLIKELY(_cel_ckd_add(&min_cap, arr->len, n))) { + errno = ENOMEM; + return cel_nullptr; + } + size_t new_cap = arr->cap; + if (new_cap < 8) { + new_cap = 8; + } + while (new_cap < min_cap) { + if (CEL_UNLIKELY(_cel_ckd_mul(&new_cap, new_cap, (size_t)2))) { + new_cap = min_cap; + break; + } + } + size_t new_cap_bytes; + if (CEL_UNLIKELY(_cel_ckd_mul(&new_cap_bytes, new_cap, ele_size))) { + if (CEL_UNLIKELY(new_cap == min_cap || + _cel_ckd_mul(&new_cap_bytes, min_cap, ele_size))) { + errno = ENOMEM; + return cel_nullptr; + } + new_cap = min_cap; + } + CEL_NULLABLE(void*) + ptr = cel_Arena_Realloc(arena, arr->ptr, arr->cap * ele_size, new_cap_bytes, + cel_nullptr); + if (CEL_UNLIKELY(ptr == cel_nullptr)) { + return cel_nullptr; + } + arr->ptr = ptr; + arr->cap = new_cap; + } + CEL_NONNULL(void*) + ptr = ((CEL_NONNULL(char*))arr->ptr) + (arr->len * ele_size); + arr->len += n; + return ptr; +} + +extern "C" void _cel_GenericArray_PopAllocator(CEL_NONNULL(_cel_GenericArray*) + arr, + size_t ele_size) { + CEL_ASSERT_NOT_NULL(arr); + CEL_ASSERT_GT(ele_size, 0); + CEL_ASSERT_NOT(_cel_GenericArray_Empty(arr)); + CEL_ASSERT_LE(arr->len, arr->cap); + size_t new_len = arr->len - 1; + _cel_GenericArray_Annotate(arr->ptr, arr->cap, arr->len, new_len, ele_size); + arr->len = new_len; +} + +extern "C" void _cel_GenericArray_PopArena(CEL_NONNULL(_cel_GenericArray*) + arr) { + CEL_ASSERT_NOT_NULL(arr); + CEL_ASSERT_NOT(_cel_GenericArray_Empty(arr)); + CEL_ASSERT_LE(arr->len, arr->cap); + --(arr->len); +} + +extern "C" void _cel_GenericArray_EraseAllocator(CEL_NONNULL(_cel_GenericArray*) + arr, + size_t idx, size_t ele_size) { + CEL_ASSERT_NOT_NULL(arr); + CEL_ASSERT_LT(idx, _cel_GenericArray_Size(arr)); + CEL_ASSERT_GT(ele_size, 0); + + CEL_NONNULL(char*) + ptr = ((CEL_NONNULL(char*))arr->ptr); + memmove(ptr + (idx * ele_size), ptr + ((idx + 1) * ele_size), + (arr->len - (idx + 1)) * ele_size); + _cel_GenericArray_PopAllocator(arr, ele_size); +} + +extern "C" void _cel_GenericArray_EraseArena(CEL_NONNULL(_cel_GenericArray*) + arr, + size_t idx, size_t ele_size) { + CEL_ASSERT_NOT_NULL(arr); + CEL_ASSERT_LT(idx, _cel_GenericArray_Size(arr)); + CEL_ASSERT_GT(ele_size, 0); + + CEL_NONNULL(char*) + ptr = ((CEL_NONNULL(char*))arr->ptr); + memmove(ptr + (idx * ele_size), ptr + ((idx + 1) * ele_size), + (arr->len - (idx + 1)) * ele_size); + _cel_GenericArray_PopArena(arr); +} + +extern "C" void _cel_GenericArray_ClearAllocator(CEL_NONNULL(_cel_GenericArray*) + arr, + size_t ele_size) { + CEL_ASSERT_NOT_NULL(arr); + CEL_ASSERT_LE(arr->len, arr->cap); + _cel_GenericArray_Annotate(arr->ptr, arr->cap, /*old_len=*/arr->len, + /*new_len=*/0, ele_size); + arr->len = 0; +} + +extern "C" void _cel_GenericArray_ClearArena(CEL_NONNULL(_cel_GenericArray*) + arr) { + CEL_ASSERT_NOT_NULL(arr); + CEL_ASSERT_LE(arr->len, arr->cap); + arr->len = 0; +} + +extern "C" void _cel_GenericArray_ResetAllocator(CEL_NONNULL(_cel_GenericArray*) + arr, + CEL_NONNULL(cel_Allocator*) + alloc, + size_t ele_size) { + CEL_ASSERT_NOT_NULL(arr); + CEL_ASSERT_NOT_NULL(alloc); + CEL_ASSERT_GT(ele_size, 0); + CEL_ASSERT_LE(arr->len, arr->cap); + if (arr->cap > 0) { + _cel_GenericArray_Annotate(arr->ptr, arr->cap, /*old_len=*/arr->len, + /*new_len=*/arr->cap, ele_size); + cel_Allocator_FreeSized(alloc, arr->ptr, arr->cap * ele_size); + } + memset(arr, '\0', sizeof(*arr)); +} diff --git a/cel-c/src/generic_deque.cc b/cel-c/src/generic_deque.cc new file mode 100644 index 0000000..9ec1819 --- /dev/null +++ b/cel-c/src/generic_deque.cc @@ -0,0 +1,779 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "cel-c/src/generic_deque.h" + +#include +#include // IWYU pragma: keep +#include +#include + +#include "cel-c/alloc.h" +#include "cel-c/arena.h" +#include "cel-c/assert.h" +#include "cel-c/config.h" +#include "cel-c/src/align.h" +#include "cel-c/src/asan.h" + +typedef struct _cel_GenericDequeBlock _cel_GenericDequeBlock; + +struct _cel_GenericDequeBlock { + CEL_NULLABLE(struct _cel_GenericDequeBlock*) prev; + CEL_NULLABLE(struct _cel_GenericDequeBlock*) next; + size_t cap; +}; + +static void _cel_GenericDequeBlock_Size(size_t ele_size, + CEL_NONNULL(size_t*) block_size) { + *block_size = + ((ele_size < + ((4096 - _cel_align_up(sizeof(_cel_GenericDequeBlock), cel_kMaxAlign)) / + 16)) + ? (4096 - + _cel_align_up(sizeof(_cel_GenericDequeBlock), cel_kMaxAlign)) / + ele_size * ele_size + : (16 * ele_size)) + + _cel_align_up(sizeof(_cel_GenericDequeBlock), cel_kMaxAlign); +} + +static void _cel_GenericDequeBlock_Annotate(CEL_NONNULL(_cel_GenericDequeBlock*) + block, + size_t old_begin, size_t old_end, + size_t new_begin, size_t new_end, + size_t ele_size) { + const size_t size = block->cap * ele_size; + CEL_NONNULL(char*) addr = ((CEL_NONNULL(char*))block); + addr += _cel_align_up(sizeof(_cel_GenericDequeBlock), cel_kMaxAlign); + _cel_sanitizer_annotate_double_ended_contiguous_container( + addr, addr + size, addr + (old_begin * ele_size), + addr + (old_end * ele_size), addr + (new_begin * ele_size), + addr + (new_end * ele_size)); +} + +static void _cel_GenericDequeBlock_AnnotateNew( + CEL_NONNULL(_cel_GenericDequeBlock*) block, size_t ele_size) { + _cel_GenericDequeBlock_Annotate(block, /*old_begin=*/0, + /*old_end=*/block->cap, /*new_begin=*/0, + /*new_end=*/0, ele_size); +} + +static void _cel_GenericDequeBlock_AnnotateDelete( + CEL_NONNULL(_cel_GenericDequeBlock*) block, size_t ele_size) { + _cel_GenericDequeBlock_Annotate(block, /*old_begin=*/0, + /*old_end=*/0, /*new_begin=*/0, + /*new_end=*/block->cap, ele_size); +} + +static CEL_NULLABLE(_cel_GenericDequeBlock*) + _cel_GenericDequeBlock_NewAllocator(CEL_NONNULL(cel_Allocator*) alloc, + size_t ele_size) { + size_t block_size; + _cel_GenericDequeBlock_Size(ele_size, &block_size); + size_t actual_block_size; + CEL_NULLABLE(void*) + addr = cel_Allocator_Malloc(alloc, block_size, &actual_block_size); + if (CEL_UNLIKELY(addr == cel_nullptr)) { + return cel_nullptr; + } + const size_t cap = + (actual_block_size - + _cel_align_up(sizeof(_cel_GenericDequeBlock), cel_kMaxAlign)) / + ele_size; + CEL_NONNULL(_cel_GenericDequeBlock*) block; + block = reinterpret_cast<_cel_GenericDequeBlock*>(addr); + block->prev = block->next = cel_nullptr; + block->cap = cap; + return block; +} + +static void _cel_GenericDequeBlock_DeleteAllocator( + CEL_NONNULL(_cel_GenericDequeBlock*) block, + CEL_NONNULL(cel_Allocator*) alloc, size_t ele_size) { + const size_t size = block->cap * ele_size; + CEL_NONNULL(void*) addr; + addr = block; + cel_Allocator_FreeSized( + alloc, addr, + size + _cel_align_up(sizeof(_cel_GenericDequeBlock), cel_kMaxAlign)); +} + +static CEL_NULLABLE(_cel_GenericDequeBlock*) + _cel_GenericDequeBlock_NewArena(CEL_NONNULL(cel_Arena*) arena, + size_t ele_size) { + size_t block_size; + _cel_GenericDequeBlock_Size(ele_size, &block_size); + CEL_NULLABLE(void*) + addr = cel_Arena_Malloc(arena, block_size, cel_nullptr); + if (CEL_UNLIKELY(addr == cel_nullptr)) { + return cel_nullptr; + } + const size_t cap = (block_size - _cel_align_up(sizeof(_cel_GenericDequeBlock), + cel_kMaxAlign)) / + ele_size; + CEL_NONNULL(_cel_GenericDequeBlock*) block; + block = reinterpret_cast<_cel_GenericDequeBlock*>(addr); + block->prev = block->next = cel_nullptr; + block->cap = cap; + return block; +} + +static CEL_NONNULL(const char*) + _cel_GenericDequeBlock_At(CEL_NONNULL(const _cel_GenericDequeBlock*) block, + size_t index, size_t ele_size) { + return ((CEL_NONNULL(const char*))block) + + _cel_align_up(sizeof(_cel_GenericDequeBlock), cel_kMaxAlign) + + (index * ele_size); +} + +static CEL_NONNULL(char*) + _cel_GenericDequeBlock_MutableAt(CEL_NONNULL(_cel_GenericDequeBlock*) block, + size_t index, size_t ele_size) { + return (CEL_NONNULL(char*))_cel_GenericDequeBlock_At(block, index, ele_size); +} + +static CEL_NONNULL(_cel_GenericDequeBlock*) + _cel_GenericDequeBlock_LinkBefore(CEL_NONNULL(_cel_GenericDequeBlock*) + new_block, + CEL_NULLABLE(_cel_GenericDequeBlock*) + before_block) { + CEL_NULLABLE(_cel_GenericDequeBlock*) prev; + if (before_block != cel_nullptr) { + prev = before_block->prev; + } else { + prev = cel_nullptr; + } + if (prev != cel_nullptr) { + prev->next = new_block; + } + new_block->prev = prev; + new_block->next = before_block; + if (before_block != cel_nullptr) { + before_block->prev = new_block; + } + return new_block; +} + +static CEL_NONNULL(_cel_GenericDequeBlock*) + _cel_GenericDequeBlock_LinkAfter(CEL_NONNULL(_cel_GenericDequeBlock*) + new_block, + CEL_NONNULL(_cel_GenericDequeBlock*) + after_block) { + CEL_NULLABLE(_cel_GenericDequeBlock*) next; + if (after_block != cel_nullptr) { + next = after_block->next; + } else { + next = cel_nullptr; + } + if (next != cel_nullptr) { + next->prev = new_block; + } + new_block->prev = after_block; + new_block->next = next; + if (after_block != cel_nullptr) { + after_block->next = new_block; + } + return new_block; +} + +static void _cel_GenericDequeBlock_Unlink(CEL_NONNULL(_cel_GenericDequeBlock*) + block) { + CEL_NULLABLE(_cel_GenericDequeBlock*) prev = block->prev; + CEL_NULLABLE(_cel_GenericDequeBlock*) next = block->next; + if (prev != cel_nullptr) { + prev->next = next; + block->prev = cel_nullptr; + } + if (next != cel_nullptr) { + next->prev = prev; + block->next = cel_nullptr; + } +} + +static void _cel_GenericDequeBlock_DeleteChainAllocator( + CEL_NULLABLE(_cel_GenericDequeBlock*) block, + CEL_NONNULL(cel_Allocator*) alloc, size_t ele_size) { + while (block != cel_nullptr) { + CEL_NULLABLE(_cel_GenericDequeBlock*) next = block->next; + _cel_GenericDequeBlock_DeleteAllocator(block, alloc, ele_size); + block = next; + } +} + +static bool _cel_GenericDeque_NewBlockAllocator( + CEL_NONNULL(_cel_GenericDeque*) deq, int which, + CEL_NONNULL(cel_Allocator*) alloc, size_t ele_size) { + CEL_ASSERT(which == -1 || which == 0 || which == 1); + + // First insertion. There may be a single block we can use at deq->cache, + // otherwise we need to allocate one. + + CEL_NULLABLE(_cel_GenericDequeBlock*) + new_block = deq->cache; + if (new_block != cel_nullptr) { + // allocator-based deque should only cache a single block. + CEL_ASSERT_NULL(new_block->prev); + CEL_ASSERT_NULL(new_block->next); + deq->cache = cel_nullptr; + // No need to update ASan annotation. Blocks in the cache are already + // poisoned fully. + } else { + new_block = _cel_GenericDequeBlock_NewAllocator(alloc, ele_size); + if (CEL_UNLIKELY(new_block == cel_nullptr)) { + return false; + } + _cel_GenericDequeBlock_AnnotateNew(new_block, ele_size); + } + CEL_ASSUME(which == -1 || which == 0 || which == 1); + switch (which) { + case -1: + deq->head = _cel_GenericDequeBlock_LinkBefore(new_block, deq->head); + deq->head_pos = new_block->cap; + break; + case 0: + deq->head = deq->tail = new_block; + deq->tail_pos = deq->head_pos = new_block->cap / 2; + break; + case 1: + deq->tail = _cel_GenericDequeBlock_LinkAfter(new_block, deq->tail); + deq->tail_pos = 0; + break; + default: + CEL_UNREACHABLE(); + } + return true; +} + +static bool _cel_GenericDeque_NewHeadBlockAllocator( + CEL_NONNULL(_cel_GenericDeque*) deq, CEL_NONNULL(cel_Allocator*) alloc, + size_t ele_size) { + CEL_ASSERT_EQ(deq->head_pos, 0); + + return _cel_GenericDeque_NewBlockAllocator( + deq, deq->head != cel_nullptr ? -1 : 0, alloc, ele_size); +} + +static bool _cel_GenericDeque_NewTailBlockAllocator( + CEL_NONNULL(_cel_GenericDeque*) deq, CEL_NONNULL(cel_Allocator*) alloc, + size_t ele_size) { + CEL_ASSERT(deq->tail == cel_nullptr || + deq->tail_pos == + ((CEL_NONNULL(_cel_GenericDequeBlock*))deq->tail)->cap); + + return _cel_GenericDeque_NewBlockAllocator( + deq, deq->tail != cel_nullptr ? 1 : 0, alloc, ele_size); +} + +static bool _cel_GenericDeque_NewBlockArena(CEL_NONNULL(_cel_GenericDeque*) deq, + int which, + CEL_NONNULL(cel_Arena*) arena, + size_t ele_size) { + CEL_ASSERT(which == -1 || which == 0 || which == 1); + + // First insertion. There may be a single block we can use at deq->cache, + // otherwise we need to allocate one. + + CEL_NULLABLE(_cel_GenericDequeBlock*) + new_block = deq->cache; + if (new_block != cel_nullptr) { + // allocator-based deque should only cache a single block. + deq->cache = new_block->next; + _cel_GenericDequeBlock_Unlink(new_block); + } else { + new_block = _cel_GenericDequeBlock_NewArena(arena, ele_size); + if (CEL_UNLIKELY(new_block == cel_nullptr)) { + return false; + } + } + CEL_ASSUME(which == -1 || which == 0 || which == 1); + switch (which) { + case -1: + deq->head = _cel_GenericDequeBlock_LinkBefore(new_block, deq->head); + deq->head_pos = new_block->cap; + break; + case 0: + deq->head = deq->tail = new_block; + deq->tail_pos = deq->head_pos = new_block->cap / 2; + break; + case 1: + deq->tail = _cel_GenericDequeBlock_LinkAfter(new_block, deq->tail); + deq->tail_pos = 0; + break; + default: + CEL_UNREACHABLE(); + } + return true; +} + +static bool _cel_GenericDeque_NewHeadBlockArena(CEL_NONNULL(_cel_GenericDeque*) + deq, + CEL_NONNULL(cel_Arena*) arena, + size_t ele_size) { + CEL_ASSERT_EQ(deq->head_pos, 0); + + return _cel_GenericDeque_NewBlockArena(deq, deq->head != cel_nullptr ? -1 : 0, + arena, ele_size); +} + +static bool _cel_GenericDeque_NewTailBlockArena(CEL_NONNULL(_cel_GenericDeque*) + deq, + CEL_NONNULL(cel_Arena*) arena, + size_t ele_size) { + CEL_ASSERT(deq->tail == cel_nullptr || + deq->tail_pos == + ((CEL_NONNULL(_cel_GenericDequeBlock*))deq->tail)->cap); + + return _cel_GenericDeque_NewBlockArena(deq, deq->tail != cel_nullptr ? 1 : 0, + arena, ele_size); +} + +extern "C" void _cel_GenericDeque_Construct(CEL_NONNULL(_cel_GenericDeque*) + deq) { + CEL_ASSERT_NOT_NULL(deq); + + memset(deq, '\0', sizeof(*deq)); +} + +extern "C" void _cel_GenericDeque_DestructAllocator( + CEL_NONNULL(_cel_GenericDeque*) deq, CEL_NONNULL(cel_Allocator*) alloc, + size_t ele_size) { + CEL_ASSERT_NOT_NULL(deq); + CEL_ASSERT_NOT_NULL(alloc); + CEL_ASSERT_GT(ele_size, 0); + + _cel_GenericDequeBlock_DeleteChainAllocator(deq->head, alloc, ele_size); + _cel_GenericDequeBlock_DeleteChainAllocator(deq->cache, alloc, ele_size); +} + +extern "C" CEL_NULLABLE(const void*) + _cel_GenericDeque_At(CEL_NONNULL(const _cel_GenericDeque*) deq, size_t idx, + size_t ele_size) { + CEL_ASSERT_NOT_NULL(deq); + CEL_ASSERT_LT(idx, _cel_GenericDeque_Size(deq)); + CEL_ASSERT_GT(ele_size, 0); + + // We scan forward is idx < half the size, backwards otherwise. + if (idx >= deq->len / 2) { + // Backward. + _cel_GenericDequeBlock* block = deq->tail; + if (block == deq->head) { + // Only one block, the element should be relative to head_pos. + return _cel_GenericDequeBlock_At(block, deq->head_pos + idx, ele_size); + } + size_t npos = deq->len - deq->tail_pos; + if (npos <= idx) { + return _cel_GenericDequeBlock_At(block, idx - npos, ele_size); + } + block = block->prev; + while (block != deq->head && npos - block->cap > idx) { + npos -= block->cap; + block = block->prev; + } + if (block == deq->head) { + // Only one block, the element should be relative to head_pos. + return _cel_GenericDequeBlock_At(block, deq->head_pos + idx, ele_size); + } + return _cel_GenericDequeBlock_At(block, idx - npos, ele_size); + } + + // Forward. + _cel_GenericDequeBlock* block = deq->head; + size_t block_size; + if (block == deq->tail || (block_size = (block->cap - deq->head_pos)) > idx) { + // Only one block or the element is in the head block, the element should be + // here relative to head_pos. + return _cel_GenericDequeBlock_At(block, deq->head_pos + idx, ele_size); + } + idx -= block_size; + block = block->next; + while (block != deq->tail && (block_size = block->cap) >= idx) { + idx -= block_size; + block = block->next; + } + // Must be in this block which is not head (could be in the middle or tail). + // So we always look relative to 0. deq->tail_pos is the upper bound, + // everything before it is occupied. + return _cel_GenericDequeBlock_At(block, idx, ele_size); +} + +extern "C" CEL_NULLABLE(void*) + _cel_GenericDeque_PushFrontAllocator(CEL_NONNULL(_cel_GenericDeque*) deq, + CEL_NONNULL(cel_Allocator*) alloc, + size_t ele_size) { + CEL_ASSERT_NOT_NULL(deq); + CEL_ASSERT_NOT_NULL(alloc); + CEL_ASSERT_GT(ele_size, 0); + + CEL_NULLABLE(_cel_GenericDequeBlock*) head = deq->head; + if (CEL_UNLIKELY( + head == cel_nullptr || + (deq->head_pos == 0 && (head != deq->tail || deq->tail_pos != 0)))) { + if (!_cel_GenericDeque_NewHeadBlockAllocator(deq, alloc, ele_size)) { + return cel_nullptr; + } + head = deq->head; + } else if (head == deq->tail && deq->head_pos == deq->tail_pos) { + deq->head_pos = deq->tail_pos = head->cap / 2; + } + + CEL_ASSERT_NOT_NULL(head); + CEL_ASSERT_GT(deq->head_pos, 0); + + CEL_NONNULL(void*) + ele = _cel_GenericDequeBlock_MutableAt(head, deq->head_pos - 1, ele_size); + // Before updating fields and returning to the caller, update the ASan + // annotation to unpoison the storage for the newly appended element. Keep in + // mind that deq->tail may be the same as deq->head. + _cel_GenericDequeBlock_Annotate( + head, /*old_begin=*/deq->head_pos, + /*old_end=*/head != deq->tail + ? ((CEL_NONNULL(_cel_GenericDequeBlock*))deq->tail)->cap + : deq->tail_pos, + /*new_begin=*/deq->head_pos - 1, + /*new_end=*/head != deq->tail + ? ((CEL_NONNULL(_cel_GenericDequeBlock*))deq->tail)->cap + : deq->tail_pos, + ele_size); + --(deq->head_pos); + ++(deq->len); + return ele; +} + +extern "C" CEL_NULLABLE(void*) + _cel_GenericDeque_PushFrontArena(CEL_NONNULL(_cel_GenericDeque*) deq, + CEL_NONNULL(cel_Arena*) arena, + size_t ele_size) { + CEL_ASSERT_NOT_NULL(deq); + CEL_ASSERT_NOT_NULL(arena); + CEL_ASSERT_GT(ele_size, 0); + + CEL_NULLABLE(_cel_GenericDequeBlock*) head = deq->head; + if (CEL_UNLIKELY( + head == cel_nullptr || + (deq->head_pos == 0 && (head != deq->tail || deq->tail_pos != 0)))) { + if (!_cel_GenericDeque_NewHeadBlockArena(deq, arena, ele_size)) { + return cel_nullptr; + } + head = deq->head; + } else if (head == deq->tail && deq->head_pos == deq->tail_pos) { + deq->head_pos = deq->tail_pos = head->cap / 2; + } + + CEL_ASSERT_NOT_NULL(head); + CEL_ASSERT_GT(deq->head_pos, 0); + + CEL_NONNULL(void*) + ele = _cel_GenericDequeBlock_MutableAt(head, deq->head_pos - 1, ele_size); + --(deq->head_pos); + ++(deq->len); + return ele; +} + +extern "C" void _cel_GenericDeque_PopFrontAllocator( + CEL_NONNULL(_cel_GenericDeque*) deq, CEL_NONNULL(cel_Allocator*) alloc, + size_t ele_size) { + CEL_ASSERT_NOT_NULL(deq); + CEL_ASSERT_NOT(_cel_GenericDeque_Empty(deq)); + CEL_ASSERT_GT(ele_size, 0); + CEL_ASSERT_NOT_NULL(deq->tail); + + // Before updating fields, update the ASan annotation. Keep in + // mind that deq->tail may be the same as deq->head. + _cel_GenericDequeBlock_Annotate( + deq->head, /*old_begin=*/deq->head_pos, + /*old_end=*/deq->head != deq->tail ? deq->head->cap : deq->tail_pos, + /*new_begin=*/deq->head_pos + 1, + /*new_end=*/deq->head != deq->tail ? deq->head->cap : deq->tail_pos, + ele_size); + + ++(deq->head_pos); + --(deq->len); + if (deq->head != deq->tail && deq->head_pos == deq->head->cap) { + CEL_NONNULL(_cel_GenericDequeBlock*) old_head = deq->head; + CEL_NONNULL(_cel_GenericDequeBlock*) new_head = old_head->next; + deq->head = new_head; + deq->head_pos = 0; + _cel_GenericDequeBlock_Unlink(old_head); + if (deq->cache == cel_nullptr) { + deq->cache = old_head; + } else { + // Block already cached. Delete this one. + _cel_GenericDequeBlock_DeleteAllocator(old_head, alloc, ele_size); + } + } + if (deq->head == deq->tail && deq->head_pos == deq->tail_pos) { + if (deq->cache != cel_nullptr) { + _cel_GenericDequeBlock_AnnotateDelete(deq->cache, ele_size); + _cel_GenericDequeBlock_DeleteAllocator(deq->cache, alloc, ele_size); + deq->cache = cel_nullptr; + } + deq->head_pos = deq->tail_pos = deq->head->cap / 2; + } +} + +extern "C" void _cel_GenericDeque_PopFrontArena(CEL_NONNULL(_cel_GenericDeque*) + deq) { + CEL_ASSERT_NOT_NULL(deq); + CEL_ASSERT_NOT(_cel_GenericDeque_Empty(deq)); + CEL_ASSERT_NOT_NULL(deq->head); + + ++(deq->head_pos); + --(deq->len); + if (deq->head != deq->tail && deq->head_pos == deq->head->cap) { + CEL_NONNULL(_cel_GenericDequeBlock*) old_head = deq->head; + CEL_NONNULL(_cel_GenericDequeBlock*) new_head = old_head->next; + deq->head = new_head; + deq->head_pos = 0; + _cel_GenericDequeBlock_Unlink(old_head); + _cel_GenericDequeBlock_LinkBefore(old_head, deq->cache); + deq->cache = old_head; + } + if (deq->head == deq->tail && deq->head_pos == deq->tail_pos) { + deq->head_pos = deq->tail_pos = deq->head->cap / 2; + } +} + +extern "C" CEL_NONNULL(const void*) + _cel_GenericDeque_PeekFront(CEL_NONNULL(const _cel_GenericDeque*) deq, + size_t ele_size) { + CEL_ASSERT_NOT_NULL(deq); + CEL_ASSERT_NOT(_cel_GenericDeque_Empty(deq)); + CEL_ASSERT_GT(ele_size, 0); + + CEL_ASSERT_NOT_NULL(deq->head); + CEL_ASSERT_GE(deq->head_pos, 0); + CEL_ASSERT_LT(deq->head_pos, deq->head->cap); + + return _cel_GenericDequeBlock_At(deq->head, deq->head_pos, ele_size); +} + +extern "C" CEL_NULLABLE(void*) + _cel_GenericDeque_PushBackAllocator(CEL_NONNULL(_cel_GenericDeque*) deq, + CEL_NONNULL(cel_Allocator*) alloc, + size_t ele_size) { + CEL_ASSERT_NOT_NULL(deq); + CEL_ASSERT_NOT_NULL(alloc); + CEL_ASSERT_GT(ele_size, 0); + + CEL_NULLABLE(_cel_GenericDequeBlock*) tail = deq->tail; + if (CEL_UNLIKELY(tail == cel_nullptr || + (deq->tail_pos == tail->cap && + (tail != deq->head || deq->head_pos != deq->tail_pos)))) { + if (!_cel_GenericDeque_NewTailBlockAllocator(deq, alloc, ele_size)) { + return cel_nullptr; + } + tail = deq->tail; + } else if (tail == deq->head && deq->tail_pos == deq->head_pos) { + deq->head_pos = deq->tail_pos = tail->cap / 2; + } + + CEL_ASSERT_NOT_NULL(tail); + CEL_ASSERT_LT(deq->tail_pos, tail->cap); + + CEL_NONNULL(void*) + ele = _cel_GenericDequeBlock_MutableAt(tail, deq->tail_pos, ele_size); + // Before updating fields and returning to the caller, update the ASan + // annotation to unpoison the storage for the newly appended element. Keep in + // mind that deq->tail may be the same as deq->head. + _cel_GenericDequeBlock_Annotate( + tail, /*old_begin=*/tail != deq->head ? 0 : deq->head_pos, + /*old_end=*/deq->tail_pos, + /*new_begin=*/tail != deq->head ? 0 : deq->head_pos, + /*new_end=*/deq->tail_pos + 1, ele_size); + ++(deq->tail_pos); + ++(deq->len); + return ele; +} + +extern "C" CEL_NULLABLE(void*) + _cel_GenericDeque_PushBackArena(CEL_NONNULL(_cel_GenericDeque*) deq, + CEL_NONNULL(cel_Arena*) arena, + size_t ele_size) { + CEL_ASSERT_NOT_NULL(deq); + CEL_ASSERT_NOT_NULL(arena); + CEL_ASSERT_GT(ele_size, 0); + + CEL_NULLABLE(_cel_GenericDequeBlock*) tail = deq->tail; + if (CEL_UNLIKELY(tail == cel_nullptr || + (deq->tail_pos == tail->cap && + (tail != deq->head || deq->head_pos != deq->tail_pos)))) { + if (!_cel_GenericDeque_NewTailBlockArena(deq, arena, ele_size)) { + return cel_nullptr; + } + tail = deq->tail; + } else if (tail == deq->head && deq->tail_pos == deq->head_pos) { + deq->head_pos = deq->tail_pos = tail->cap / 2; + } + + CEL_ASSERT_NOT_NULL(tail); + CEL_ASSERT_LT(deq->tail_pos, tail->cap); + + CEL_NONNULL(void*) + ele = _cel_GenericDequeBlock_MutableAt(tail, deq->tail_pos, ele_size); + ++(deq->tail_pos); + ++(deq->len); + return ele; +} + +extern "C" void _cel_GenericDeque_PopBackAllocator( + CEL_NONNULL(_cel_GenericDeque*) deq, CEL_NONNULL(cel_Allocator*) alloc, + size_t ele_size) { + CEL_ASSERT_NOT_NULL(deq); + CEL_ASSERT_NOT(_cel_GenericDeque_Empty(deq)); + CEL_ASSERT_GT(ele_size, 0); + CEL_ASSERT_NOT_NULL(deq->tail); + + // Before updating fields, update the ASan annotation. Keep in + // mind that deq->tail may be the same as deq->head. + _cel_GenericDequeBlock_Annotate( + deq->tail, /*old_begin=*/deq->tail != deq->head ? 0 : deq->head_pos, + /*old_end=*/deq->tail_pos, + /*new_begin=*/deq->tail != deq->head ? 0 : deq->head_pos, + /*new_end=*/deq->tail_pos - 1, ele_size); + + --(deq->tail_pos); + --(deq->len); + if (deq->tail_pos == 0 && deq->head != deq->tail) { + CEL_NONNULL(_cel_GenericDequeBlock*) old_tail = deq->tail; + CEL_NONNULL(_cel_GenericDequeBlock*) new_tail = old_tail->prev; + deq->tail = new_tail; + deq->tail_pos = new_tail->cap; + _cel_GenericDequeBlock_Unlink(old_tail); + if (deq->cache == cel_nullptr) { + deq->cache = old_tail; + } else { + // Block already cached. Delete this one. + _cel_GenericDequeBlock_DeleteAllocator(old_tail, alloc, ele_size); + } + } + if (deq->head == deq->tail && deq->head_pos == deq->tail_pos) { + if (deq->cache != cel_nullptr) { + _cel_GenericDequeBlock_AnnotateDelete(deq->cache, ele_size); + _cel_GenericDequeBlock_DeleteAllocator(deq->cache, alloc, ele_size); + deq->cache = cel_nullptr; + } + deq->head_pos = deq->tail_pos = deq->head->cap / 2; + } +} + +extern "C" void _cel_GenericDeque_PopBackArena(CEL_NONNULL(_cel_GenericDeque*) + deq) { + CEL_ASSERT_NOT_NULL(deq); + CEL_ASSERT_NOT(_cel_GenericDeque_Empty(deq)); + CEL_ASSERT_NOT_NULL(deq->tail); + + --(deq->tail_pos); + --(deq->len); + if (deq->tail_pos == 0 && deq->head != deq->tail) { + CEL_NONNULL(_cel_GenericDequeBlock*) old_tail = deq->tail; + CEL_NONNULL(_cel_GenericDequeBlock*) new_tail = old_tail->prev; + deq->tail = new_tail; + deq->tail_pos = new_tail->cap; + _cel_GenericDequeBlock_Unlink(old_tail); + _cel_GenericDequeBlock_LinkBefore(old_tail, deq->cache); + deq->cache = old_tail; + } + if (deq->head == deq->tail && deq->head_pos == deq->tail_pos) { + deq->head_pos = deq->tail_pos = deq->head->cap / 2; + } +} + +extern "C" CEL_NONNULL(const void*) + _cel_GenericDeque_PeekBack(CEL_NONNULL(const _cel_GenericDeque*) deq, + size_t ele_size) { + CEL_ASSERT_NOT_NULL(deq); + CEL_ASSERT_NOT(_cel_GenericDeque_Empty(deq)); + CEL_ASSERT_GT(ele_size, 0); + + CEL_ASSERT_NOT_NULL(deq->tail); + CEL_ASSERT_GT(deq->tail_pos, 0); + CEL_ASSERT_LE(deq->tail_pos, deq->tail->cap); + + return _cel_GenericDequeBlock_At(deq->tail, deq->tail_pos - 1, ele_size); +} + +extern "C" void _cel_GenericDeque_ResetAllocator(CEL_NONNULL(_cel_GenericDeque*) + deq, + CEL_NONNULL(cel_Allocator*) + alloc, + size_t ele_size) { + _cel_GenericDeque_DestructAllocator(deq, alloc, ele_size); + _cel_GenericDeque_Construct(deq); +} + +extern "C" void _cel_GenericDeque_ClearAllocator(CEL_NONNULL(_cel_GenericDeque*) + deq, + CEL_NONNULL(cel_Allocator*) + alloc, + size_t ele_size) { + CEL_ASSERT_NOT_NULL(deq); + CEL_ASSERT_NOT_NULL(alloc); + CEL_ASSERT_GT(ele_size, 0); + + if (deq->len == 0) { + return; + } + deq->len = 0; + + CEL_NULLABLE(_cel_GenericDequeBlock*) head = deq->head; + if (head != cel_nullptr) { + CEL_ASSERT_NOT_NULL(deq->tail); + + // Add a block to the cache, if necessary. For malloc-based deque, we only + // cache a single block. + if (deq->cache == cel_nullptr) { + CEL_NULLABLE(_cel_GenericDequeBlock*) new_head = head->next; + _cel_GenericDequeBlock_Unlink(head); + if (head == deq->tail) { + deq->tail = new_head; + } + deq->head = new_head; + deq->cache = head; + head = new_head; + } + + // Delete the rest of the blocks. + _cel_GenericDequeBlock_DeleteChainAllocator(head, alloc, ele_size); + deq->head = deq->tail = cel_nullptr; + deq->head_pos = deq->tail_pos = 0; + } else { + CEL_ASSERT_NULL(deq->tail); + CEL_ASSERT_EQ(deq->head_pos, 0); + CEL_ASSERT_EQ(deq->tail_pos, 0); + } +} + +extern "C" void _cel_GenericDeque_ClearArena(CEL_NONNULL(_cel_GenericDeque*) + deq) { + // For arena-based dequeue, we do not return memory to the arena as there is + // currently no way to do that. So we move all the allocated blocks to the + // cache list for re-use. + + CEL_ASSERT_NOT_NULL(deq); + + if (deq->len == 0) { + return; + } + deq->len = 0; + + if (deq->head != cel_nullptr) { + CEL_ASSERT_NOT_NULL(deq->tail); + deq->cache = _cel_GenericDequeBlock_LinkBefore(deq->head, deq->cache); + deq->head = deq->tail = cel_nullptr; + deq->head_pos = deq->tail_pos = 0; + } else { + CEL_ASSERT_NULL(deq->tail); + CEL_ASSERT_EQ(deq->head_pos, 0); + CEL_ASSERT_EQ(deq->tail_pos, 0); + } +} diff --git a/cel-c/src/generic_flat_hash.cc b/cel-c/src/generic_flat_hash.cc new file mode 100644 index 0000000..f45ad33 --- /dev/null +++ b/cel-c/src/generic_flat_hash.cc @@ -0,0 +1,505 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "cel-c/src/generic_flat_hash.h" + +#include +#include // IWYU pragma: keep +#include // IWYU pragma: keep +#include +#include +#include + +#include "cel-c/alloc.h" +#include "cel-c/arena.h" +#include "cel-c/assert.h" +#include "cel-c/config.h" +#include "cel-c/src/align.h" +#include "cel-c/src/bit.h" +#include "cel-c/src/bitset.h" + +CEL_ATTRIBUTE_NODISCARD +static size_t _cel_GenericFlatHash_Rehash( + CEL_NONNULL(const _cel_GenericFlatHash*) fh, + CEL_NONNULL(_cel_BitSetWord*) new_ctrl, CEL_NONNULL(char*) new_ents, + size_t old_slot, size_t new_cap, size_t ent_size) { + size_t new_slot = SIZE_MAX; + if (fh->len != 0) { + CEL_ASSERT_GT(new_cap, fh->cap); + + const size_t new_mask = new_cap - 1; + CEL_NONNULL(const _cel_BitSetWord*) const old_ctrl = fh->ctrl; + const size_t old_cap = fh->cap; + CEL_NONNULL(const char*) + const old_ents = reinterpret_cast(fh->ents); + size_t bit = _cel_BitSet_kBegin; + while (_cel_BitSet_Next(old_ctrl, old_cap, &bit)) { + CEL_NONNULL(const char*) old_ent = old_ents + (ent_size * bit); + size_t slot = ((*fh->h)(old_ent)) & new_mask; + while (_cel_BitSet_Test(new_ctrl, new_cap, slot)) { + slot = (slot + 1) & new_mask; + } + _cel_BitSet_Set(new_ctrl, new_cap, slot); + CEL_NONNULL(char*) new_ent = new_ents + (slot * ent_size); + memcpy(new_ent, old_ent, ent_size); + if (bit == old_slot) { + new_slot = slot; + } + } + } + CEL_ASSERT(old_slot == SIZE_MAX || new_slot != SIZE_MAX); + return new_slot; +} + +CEL_ATTRIBUTE_NODISCARD +static bool _cel_GenericFlatHash_PreInsertAllocator( + CEL_NONNULL(_cel_GenericFlatHash*) fh, CEL_NONNULL(cel_Allocator*) alloc, + size_t ent_size) { + CEL_ASSERT_EQ(fh->cap, 0); + + size_t size = + _cel_align_up(_cel_BitSet_WordsForBits(16) * sizeof(_cel_BitSetWord), + cel_kMaxAlign) + + (ent_size * 16); + CEL_NULLABLE(char*) + data = + reinterpret_cast(cel_Allocator_Malloc(alloc, size, cel_nullptr)); + if (CEL_UNLIKELY(data == cel_nullptr)) { + return false; + } + CEL_NONNULL(_cel_BitSetWord*) ctrl = (CEL_NONNULL(_cel_BitSetWord*))data; + CEL_NONNULL(char*) + ents = data + + _cel_align_up(_cel_BitSet_WordsForBits(16) * sizeof(_cel_BitSetWord), + cel_kMaxAlign); + _cel_BitSet_Reset(ctrl, 16); + fh->ctrl = ctrl; + fh->ents = ents; + fh->cap = 16; + fh->thr = 10; + return true; +} + +CEL_ATTRIBUTE_NODISCARD +static bool _cel_GenericFlatHash_GrowAllocator( + CEL_NONNULL(_cel_GenericFlatHash*) fh, CEL_NONNULL(cel_Allocator*) alloc, + size_t new_cap, CEL_NONNULL(size_t*) slot, size_t ent_size) { + CEL_ASSERT(_cel_has_single_bit(new_cap)); + CEL_ASSERT_GT(new_cap, fh->cap); + + size_t size = + _cel_align_up(_cel_BitSet_WordsForBits(new_cap) * sizeof(_cel_BitSetWord), + cel_kMaxAlign) + + (ent_size * new_cap); + CEL_NULLABLE(char*) + data = + reinterpret_cast(cel_Allocator_Malloc(alloc, size, cel_nullptr)); + if (CEL_UNLIKELY(data == cel_nullptr)) { + return false; + } + CEL_NONNULL(_cel_BitSetWord*) ctrl = (CEL_NONNULL(_cel_BitSetWord*))data; + CEL_NONNULL(char*) + ents = data + _cel_align_up( + _cel_BitSet_WordsForBits(new_cap) * sizeof(_cel_BitSetWord), + cel_kMaxAlign); + _cel_BitSet_Reset(ctrl, new_cap); + + *slot = _cel_GenericFlatHash_Rehash(fh, ctrl, ents, *slot, new_cap, ent_size); + + size_t old_size = + _cel_align_up(_cel_BitSet_WordsForBits(fh->cap) * sizeof(_cel_BitSetWord), + cel_kMaxAlign) + + (fh->cap * ent_size); + CEL_NONNULL(void*) old_data = fh->ctrl; + cel_Allocator_FreeSized(alloc, old_data, old_size); + + fh->ctrl = ctrl; + fh->ents = ents; + fh->cap = new_cap; + fh->thr = (new_cap * 2) / 3; + return true; +} + +CEL_ATTRIBUTE_NODISCARD +static bool _cel_GenericFlatHash_PostInsertAllocator( + CEL_NONNULL(_cel_GenericFlatHash*) fh, CEL_NONNULL(cel_Allocator*) alloc, + CEL_NONNULL(size_t*) slot, size_t ent_size) { + CEL_ASSERT_GT(fh->len, fh->thr); + + return _cel_GenericFlatHash_GrowAllocator(fh, alloc, fh->cap << 1, slot, + ent_size); +} + +CEL_ATTRIBUTE_NODISCARD +static bool _cel_GenericFlatHash_PreInsertArena( + CEL_NONNULL(_cel_GenericFlatHash*) fh, CEL_NONNULL(cel_Arena*) arena, + size_t ent_size) { + CEL_ASSERT_EQ(fh->cap, 0); + + size_t size = + _cel_align_up(_cel_BitSet_WordsForBits(16) * sizeof(_cel_BitSetWord), + cel_kMaxAlign) + + (ent_size * 16); + CEL_NULLABLE(char*) + data = reinterpret_cast(cel_Arena_Malloc(arena, size, cel_nullptr)); + if (CEL_UNLIKELY(data == cel_nullptr)) { + return false; + } + CEL_NONNULL(_cel_BitSetWord*) ctrl = (CEL_NONNULL(_cel_BitSetWord*))data; + CEL_NONNULL(char*) + ents = data + + _cel_align_up(_cel_BitSet_WordsForBits(16) * sizeof(_cel_BitSetWord), + cel_kMaxAlign); + _cel_BitSet_Reset(ctrl, 16); + fh->ctrl = ctrl; + fh->ents = ents; + fh->cap = 16; + fh->thr = 10; + return true; +} + +CEL_ATTRIBUTE_NODISCARD +static bool _cel_GenericFlatHash_GrowArena( + CEL_NONNULL(_cel_GenericFlatHash*) fh, CEL_NONNULL(cel_Arena*) arena, + size_t new_cap, CEL_NONNULL(size_t*) slot, size_t ent_size) { + CEL_ASSERT(_cel_has_single_bit(new_cap)); + CEL_ASSERT_GT(new_cap, fh->cap); + + size_t size = + _cel_align_up(_cel_BitSet_WordsForBits(new_cap) * sizeof(_cel_BitSetWord), + cel_kMaxAlign) + + (ent_size * new_cap); + CEL_NULLABLE(char*) + data = reinterpret_cast(cel_Arena_Malloc(arena, size, cel_nullptr)); + if (CEL_UNLIKELY(data == cel_nullptr)) { + return false; + } + CEL_NONNULL(_cel_BitSetWord*) ctrl = (CEL_NONNULL(_cel_BitSetWord*))data; + CEL_NONNULL(char*) + ents = data + _cel_align_up( + _cel_BitSet_WordsForBits(new_cap) * sizeof(_cel_BitSetWord), + cel_kMaxAlign); + _cel_BitSet_Reset(ctrl, new_cap); + + *slot = _cel_GenericFlatHash_Rehash(fh, ctrl, ents, *slot, new_cap, ent_size); + + fh->ctrl = ctrl; + fh->ents = ents; + fh->cap = new_cap; + fh->thr = (new_cap * 2) / 3; + return true; +} + +CEL_ATTRIBUTE_NODISCARD +static bool _cel_GenericFlatHash_PostInsertArena( + CEL_NONNULL(_cel_GenericFlatHash*) fh, CEL_NONNULL(cel_Arena*) arena, + CEL_NONNULL(size_t*) slot, size_t ent_size) { + CEL_ASSERT_GT(fh->len, fh->thr); + + return _cel_GenericFlatHash_GrowArena(fh, arena, fh->cap << 1, slot, + ent_size); +} + +extern "C" void _cel_GenericFlatHash_Construct( + CEL_NONNULL(_cel_GenericFlatHash*) fh, + CEL_NONNULL(_cel_GenericFlatHashHasher) hasher, + CEL_NONNULL(_cel_GenericFlatHashEqualer) equaler) { + CEL_ASSERT_NOT_NULL(fh); + CEL_ASSERT_NOT_NULL(hasher); + CEL_ASSERT_NOT_NULL(equaler); + + memset(fh, '\0', sizeof(*fh)); + fh->eq = equaler; + fh->h = hasher; +} + +extern "C" void _cel_GenericFlatHash_DestructAllocator( + CEL_NONNULL(_cel_GenericFlatHash*) fh, CEL_NONNULL(cel_Allocator*) alloc, + size_t ent_size) { + CEL_ASSERT_NOT_NULL(fh); + CEL_ASSERT_NOT_NULL(alloc); + CEL_ASSERT_GT(ent_size, 0); + + if (fh->cap != 0) { + size_t size = _cel_align_up(_cel_BitSet_WordsForBits(fh->cap) * + sizeof(_cel_BitSetWord), + cel_kMaxAlign) + + (fh->cap * ent_size); + CEL_NONNULL(void*) data = fh->ctrl; + cel_Allocator_FreeSized(alloc, data, size); + } +} + +extern "C" bool _cel_GenericFlatHash_ReserveAllocator( + CEL_NONNULL(_cel_GenericFlatHash*) fh, CEL_NONNULL(cel_Allocator*) alloc, + size_t new_cap, size_t ent_size) { + CEL_ASSERT_NOT_NULL(fh); + CEL_ASSERT_NOT_NULL(alloc); + CEL_ASSERT_GT(ent_size, 0); + + if (new_cap <= fh->cap) { + return true; + } + + new_cap = ((size_t)1) << ((sizeof(size_t) * CHAR_BIT) - + _cel_leading_zeros(new_cap)); + size_t unused_slot = SIZE_MAX; + return _cel_GenericFlatHash_GrowAllocator(fh, alloc, new_cap, &unused_slot, + ent_size); +} + +extern "C" bool _cel_GenericFlatHash_ReserveArena( + CEL_NONNULL(_cel_GenericFlatHash*) fh, CEL_NONNULL(cel_Arena*) arena, + size_t new_cap, size_t ent_size) { + CEL_ASSERT_NOT_NULL(fh); + CEL_ASSERT_NOT_NULL(arena); + CEL_ASSERT_GT(ent_size, 0); + + if (new_cap <= fh->cap) { + return true; + } + + new_cap = ((size_t)1) << ((sizeof(size_t) * CHAR_BIT) - + _cel_leading_zeros(new_cap)); + size_t unused_slot = SIZE_MAX; + return _cel_GenericFlatHash_GrowArena(fh, arena, new_cap, &unused_slot, + ent_size); +} + +extern "C" void _cel_GenericFlatHash_ClearAllocator( + CEL_NONNULL(_cel_GenericFlatHash*) fh) { + CEL_ASSERT_NOT_NULL(fh); + + if (fh->ctrl != cel_nullptr && fh->len != 0) { + _cel_BitSet_Reset(fh->ctrl, _cel_BitSet_WordsForBits(fh->cap)); + fh->len = 0; + } +} + +extern "C" void _cel_GenericFlatHash_ClearArena( + CEL_NONNULL(_cel_GenericFlatHash*) fh) { + CEL_ASSERT_NOT_NULL(fh); + + if (fh->ctrl != cel_nullptr && fh->len != 0) { + _cel_BitSet_Reset(fh->ctrl, _cel_BitSet_WordsForBits(fh->cap)); + fh->len = 0; + } +} + +extern "C" void _cel_GenericFlatHash_ResetAllocator( + CEL_NONNULL(_cel_GenericFlatHash*) fh, CEL_NONNULL(cel_Allocator*) alloc, + size_t ent_size) { + CEL_ASSERT_NOT_NULL(fh); + CEL_ASSERT_NOT_NULL(alloc); + CEL_ASSERT_GT(ent_size, 0); + + CEL_NONNULL(_cel_GenericFlatHashHasher) + hasher = fh->h; + CEL_NONNULL(_cel_GenericFlatHashEqualer) + equaler = fh->eq; + _cel_GenericFlatHash_DestructAllocator(fh, alloc, ent_size); + _cel_GenericFlatHash_Construct(fh, hasher, equaler); +} + +extern "C" CEL_NULLABLE(const void*) + _cel_GenericFlatHash_Find(CEL_NONNULL(const _cel_GenericFlatHash*) fh, + CEL_NONNULL(const void*) key, size_t ent_size) { + CEL_ASSERT_NOT_NULL(fh); + CEL_ASSERT_NOT_NULL(key); + CEL_ASSERT_GT(ent_size, 0); + + if (fh->len == 0) { + return cel_nullptr; + } + size_t hash = (*fh->h)(key); + size_t mask = fh->cap - 1; + size_t slot = hash & mask; + if (_cel_BitSet_Test(fh->ctrl, fh->cap, slot)) { + CEL_NONNULL(const char*) + ents = reinterpret_cast(fh->ents); + do { + if ((*fh->eq)(ents + (ent_size * slot), key)) { + return ents + (ent_size * slot); + } + slot = (slot + 1) & mask; + } while (_cel_BitSet_Test(fh->ctrl, fh->cap, slot)); + } + return cel_nullptr; +} + +extern "C" void _cel_GenericFlatHash_Erase(CEL_NONNULL(_cel_GenericFlatHash*) + fh, + CEL_NONNULL(const void*) ent, + size_t ent_size) { + CEL_ASSERT_NOT_NULL(fh); + CEL_ASSERT_NOT_NULL(ent); + CEL_ASSERT_GT(ent_size, 0); + + CEL_NONNULL(char*) + ents = reinterpret_cast(fh->ents); + + CEL_ASSERT_GE((CEL_NONNULL(const char*))ent, ents); + CEL_ASSERT_LT((CEL_NONNULL(const char*))ent, ents + (fh->cap * ent_size)); + + size_t slot = ((size_t)(((CEL_NONNULL(const char*))ent) - ents)) / ent_size; + CEL_ASSERT(_cel_BitSet_Test(fh->ctrl, fh->cap, slot)); + _cel_BitSet_Clear(fh->ctrl, fh->cap, slot); + --(fh->len); + + size_t mask = fh->cap - 1; + size_t i = slot; + size_t j = slot; + while (true) { + j = (j + 1) & mask; + if (!_cel_BitSet_Test(fh->ctrl, fh->cap, j)) { + break; + } + const size_t k = (*fh->h)(ents + (j * ent_size)) & mask; + if (i <= j) { + if (i < k && k <= j) { + continue; + } + } else { + if (k <= j || i < k) { + continue; + } + } + _cel_BitSet_Set(fh->ctrl, fh->cap, i); + memcpy(ents + (i * ent_size), ents + (j * ent_size), ent_size); + _cel_BitSet_Clear(fh->ctrl, fh->cap, j); + i = j; + } +} + +extern "C" bool _cel_GenericFlatHash_InsertAllocator( + CEL_NONNULL(_cel_GenericFlatHash*) fh, CEL_NONNULL(cel_Allocator*) alloc, + CEL_NONNULL(const void*) key, CEL_NONNULL(CEL_NULLABLE(void*) *) ent, + size_t ent_size, size_t key_size) { + CEL_ASSERT_NOT_NULL(fh); + CEL_ASSERT_NOT_NULL(alloc); + CEL_ASSERT_NOT_NULL(key); + CEL_ASSERT_NOT_NULL(ent); + CEL_ASSERT_GT(ent_size, 0); + + if (CEL_UNLIKELY(fh->cap == 0 && !_cel_GenericFlatHash_PreInsertAllocator( + fh, alloc, ent_size))) { + *ent = cel_nullptr; + return false; + } + + size_t hash = (*fh->h)(key); + size_t mask = fh->cap - 1; + size_t slot = hash & mask; + CEL_NONNULL(char*) + ents = reinterpret_cast(fh->ents); + while (_cel_BitSet_Test(fh->ctrl, fh->cap, slot)) { + if ((*fh->eq)(ents + (ent_size * slot), key)) { + *ent = ents + (ent_size * slot); + return false; + } + slot = (slot + 1) & mask; + } + + memcpy(ents + (slot * ent_size), key, key_size); + _cel_BitSet_Set(fh->ctrl, fh->cap, slot); + ++(fh->len); + + if (CEL_UNLIKELY(fh->len > fh->thr)) { + if (CEL_UNLIKELY(!_cel_GenericFlatHash_PostInsertAllocator(fh, alloc, &slot, + ent_size))) { + _cel_BitSet_Clear(fh->ctrl, fh->cap, slot); + --(fh->len); + *ent = cel_nullptr; + return false; + } + ents = reinterpret_cast(fh->ents); + } + + *ent = ents + (ent_size * slot); + + return true; +} + +extern "C" bool _cel_GenericFlatHash_InsertArena( + CEL_NONNULL(_cel_GenericFlatHash*) fh, CEL_NONNULL(cel_Arena*) arena, + CEL_NONNULL(const void*) key, CEL_NONNULL(CEL_NULLABLE(void*) *) ent, + size_t ent_size, size_t key_size) { + CEL_ASSERT_NOT_NULL(fh); + CEL_ASSERT_NOT_NULL(arena); + CEL_ASSERT_NOT_NULL(key); + CEL_ASSERT_NOT_NULL(ent); + CEL_ASSERT_GT(ent_size, 0); + + if (CEL_UNLIKELY(fh->cap == 0 && + !_cel_GenericFlatHash_PreInsertArena(fh, arena, ent_size))) { + *ent = cel_nullptr; + return false; + } + + size_t hash = (*fh->h)(key); + size_t mask = fh->cap - 1; + size_t slot = hash & mask; + CEL_NONNULL(char*) + ents = reinterpret_cast(fh->ents); + while (_cel_BitSet_Test(fh->ctrl, fh->cap, slot)) { + if ((*fh->eq)(ents + (ent_size * slot), key)) { + *ent = ents + (ent_size * slot); + return false; + } + slot = (slot + 1) & mask; + } + + memcpy(ents + (slot * ent_size), key, key_size); + _cel_BitSet_Set(fh->ctrl, fh->cap, slot); + ++(fh->len); + + if (CEL_UNLIKELY(fh->len > fh->thr)) { + if (CEL_UNLIKELY(!_cel_GenericFlatHash_PostInsertArena(fh, arena, &slot, + ent_size))) { + _cel_BitSet_Clear(fh->ctrl, fh->cap, slot); + --(fh->len); + *ent = cel_nullptr; + return false; + } + ents = reinterpret_cast(fh->ents); + } + + *ent = ents + (ent_size * slot); + + return true; +} + +extern "C" bool _cel_GenericFlatHash_Next( + CEL_NONNULL(const _cel_GenericFlatHash*) fh, + CEL_NONNULL(CEL_NULLABLE(const void*) *) ent, size_t ent_size) { + CEL_ASSERT_NOT_NULL(fh); + CEL_ASSERT_NOT_NULL(ent); + CEL_ASSERT_GT(ent_size, 0); + + CEL_NULLABLE(const char*) + ents = reinterpret_cast(fh->ents); + + size_t i; + if (*ent == _cel_GenericFlatHash_kBegin) { + i = _cel_BitSet_kBegin; + } else { + CEL_ASSERT_GE((CEL_NONNULL(const char*)) * ent, ents); + CEL_ASSERT_LT((CEL_NONNULL(const char*)) * ent, + ents + (fh->cap * ent_size)); + i = (((size_t)(((CEL_NONNULL(const char*)) * ent) - ents)) / ent_size); + } + const bool next = _cel_BitSet_Next(fh->ctrl, fh->cap, &i); + *ent = ents + (ent_size * i); + return next; +} diff --git a/cel-c/src/generic_string.cc b/cel-c/src/generic_string.cc new file mode 100644 index 0000000..792100c --- /dev/null +++ b/cel-c/src/generic_string.cc @@ -0,0 +1,950 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "cel-c/src/generic_string.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cel-c/alloc.h" +#include "cel-c/arena.h" +#include "cel-c/assert.h" +#include "cel-c/config.h" +#include "cel-c/src/asan.h" +#include "cel-c/src/ckdint.h" +#include "cel-c/string_view.h" + +static void _cel_GenericString_AnnotateNew(CEL_NONNULL(char*) str, size_t len, + size_t cap) { + _cel_sanitizer_annotate_contiguous_container(str, str + cap + 1, + str + cap + 1, str + len + 1); +} + +static void _cel_GenericString_Annotate(CEL_NONNULL(char*) str, size_t old_len, + size_t new_len, size_t cap) { + _cel_sanitizer_annotate_contiguous_container( + str, str + cap + 1, str + old_len + 1, str + new_len + 1); +} + +static void _cel_GenericString_AnnotateDelete(CEL_NONNULL(char*) str, + size_t len, size_t cap) { + _cel_sanitizer_annotate_contiguous_container(str, str + cap + 1, + str + len + 1, str + cap + 1); +} + +static void _cel_GenericString_AnnotateLargeNew(CEL_NONNULL(char*) str, + size_t len, size_t cap) { + _cel_GenericString_AnnotateNew(str, len, cap); +} + +static void _cel_GenericString_AnnotateLarge(CEL_NONNULL(char*) str, + size_t old_len, size_t new_len, + size_t cap) { + _cel_GenericString_Annotate(str, old_len, new_len, cap); +} + +static void _cel_GenericString_AnnotateLargeDelete(CEL_NONNULL(char*) str, + size_t len, size_t cap) { + _cel_GenericString_AnnotateDelete(str, len, cap); +} + +extern "C" void _cel_GenericString_Construct(CEL_NONNULL(_cel_GenericString*) + str) { + CEL_ASSERT_NOT_NULL(str); + + str->small.data[0] = '\0'; + str->small.len = 0; + str->small.is_large = 0; +} + +extern "C" void _cel_GenericString_DestructAllocator( + CEL_NONNULL(_cel_GenericString*) str, CEL_NONNULL(cel_Allocator*) alloc) { + CEL_ASSERT_NOT_NULL(str); + CEL_ASSERT_NOT_NULL(alloc); + + if (str->large.is_large) { + _cel_GenericString_AnnotateLargeDelete(str->large.data, str->large.len, + str->large.cap); + cel_Allocator_FreeSized(alloc, str->large.data, str->large.cap + 1); + } +} + +extern "C" void _cel_GenericString_Clear(CEL_NONNULL(_cel_GenericString*) str) { + CEL_ASSERT_NOT_NULL(str); + + if (str->large.is_large) { + // Avoid the likely cache miss if we do not actually need to write '\0'. + if (str->large.len > 0) { + _cel_GenericString_AnnotateLarge(str->large.data, str->large.len, 0, + str->large.cap); + str->large.data[0] = '\0'; + str->large.len = 0; + } + } else { + str->small.data[0] = '\0'; + str->small.len = 0; + } +} + +extern "C" void _cel_GenericString_ResetAllocator( + CEL_NONNULL(_cel_GenericString*) str, CEL_NONNULL(cel_Allocator*) alloc) { + _cel_GenericString_DestructAllocator(str, alloc); + _cel_GenericString_Construct(str); +} + +extern "C" bool _cel_GenericString_ShrinkToFitAllocator( + CEL_NONNULL(_cel_GenericString*) str, CEL_NONNULL(cel_Allocator*) alloc) { + CEL_ASSERT_NOT_NULL(str); + CEL_ASSERT_NOT_NULL(alloc); + + if (str->large.is_large && str->large.cap > str->large.len) { + if (str->large.len <= _cel_GenericStringSmall_kCapacity) { + CEL_NONNULL(char*) data = str->large.data; + size_t len = str->large.len; + size_t cap = str->large.cap; + memcpy(str->small.data, data, len + 1); + str->small.len = len; + str->small.is_large = 0; + _cel_GenericString_AnnotateLargeDelete(data, len, cap); + cel_Allocator_FreeSized(alloc, data, cap + 1); + return true; + } + size_t new_cap = str->large.cap; + size_t actual_new_cap; + CEL_NULLABLE(char*) + data = reinterpret_cast( + cel_Allocator_Malloc(alloc, new_cap + 1, &actual_new_cap)); + if (CEL_UNLIKELY(data == cel_nullptr)) { + return false; + } + --actual_new_cap; + if (actual_new_cap >= str->large.cap) { + cel_Allocator_FreeSized(alloc, data, actual_new_cap + 1); + return true; + } + _cel_GenericString_AnnotateLargeNew(data, str->large.len, actual_new_cap); + memcpy(data, str->large.data, str->large.len + 1); + _cel_GenericString_AnnotateLargeDelete(str->large.data, str->large.len, + str->large.cap); + cel_Allocator_FreeSized(alloc, str->large.data, str->large.cap + 1); + str->large.data = data; + str->large.cap = actual_new_cap; + } + + return true; +} + +extern "C" bool _cel_GenericString_AssignAllocator( + CEL_NONNULL(_cel_GenericString*) str, CEL_NONNULL(cel_Allocator*) alloc, + cel_StringView val) { + CEL_ASSERT_NOT_NULL(str); + CEL_ASSERT_NOT_NULL(alloc); + + size_t new_len = cel_StringView_Size(val); + + if (new_len == 0) { + _cel_GenericString_Clear(str); + return true; + } + + if (str->large.is_large) { + if (new_len > str->large.cap) { + size_t new_cap = new_len; + size_t actual_new_cap; + CEL_NULLABLE(char*) + data = reinterpret_cast( + cel_Allocator_Malloc(alloc, new_cap + 1, &actual_new_cap)); + if (CEL_UNLIKELY(data == cel_nullptr)) { + _cel_GenericString_AnnotateLargeNew(str->large.data, str->large.len, + str->large.cap); + str->large.data[str->large.len] = '\0'; + return false; + } + --actual_new_cap; + _cel_GenericString_AnnotateLargeNew(data, new_len, actual_new_cap); + _cel_GenericString_AnnotateLargeDelete(str->large.data, str->large.len, + str->large.cap); + cel_Allocator_FreeSized(alloc, str->large.data, str->large.cap + 1); + str->large.data = data; + str->large.cap = actual_new_cap; + } + } else { + if (new_len <= _cel_GenericStringSmall_kCapacity) { + memcpy(str->small.data, cel_StringView_Data(val), new_len); + str->small.data[new_len] = '\0'; + str->small.len = new_len; + return true; + } + size_t new_cap = new_len; + size_t actual_new_cap; + CEL_NULLABLE(char*) + data = reinterpret_cast( + cel_Allocator_Malloc(alloc, new_cap + 1, &actual_new_cap)); + if (CEL_UNLIKELY(data == cel_nullptr)) { + str->small.data[str->small.len] = '\0'; + return false; + } + --actual_new_cap; + _cel_GenericString_AnnotateLargeNew(data, new_len, actual_new_cap); + str->large.data = data; + str->large.cap = actual_new_cap; + str->large.is_large = 1; + } + + // If we got here, the string **must** be large. + memcpy(str->large.data, cel_StringView_Data(val), new_len); + str->large.data[new_len] = '\0'; + str->large.len = new_len; + return true; +} + +extern "C" bool _cel_GenericString_AssignArena(CEL_NONNULL(_cel_GenericString*) + str, + CEL_NONNULL(cel_Arena*) arena, + cel_StringView val) { + CEL_ASSERT_NOT_NULL(str); + CEL_ASSERT_NOT_NULL(arena); + + size_t new_len = cel_StringView_Size(val); + + if (new_len == 0) { + _cel_GenericString_Clear(str); + return true; + } + + if (str->large.is_large) { + if (new_len > str->large.cap) { + size_t new_cap = new_len; + CEL_NULLABLE(char*) + data = reinterpret_cast( + cel_Arena_Malloc(arena, new_cap + 1, cel_nullptr)); + if (CEL_UNLIKELY(data == cel_nullptr)) { + _cel_GenericString_AnnotateLargeNew(str->large.data, str->large.len, + str->large.cap); + str->large.data[str->large.len] = '\0'; + return false; + } + _cel_GenericString_AnnotateLargeNew(data, new_len, new_cap); + _cel_GenericString_AnnotateLargeDelete(str->large.data, str->large.len, + str->large.cap); + cel_Arena_FreeSized(arena, str->large.data, str->large.cap + 1); + str->large.data = data; + str->large.cap = new_cap; + } + } else { + if (new_len <= _cel_GenericStringSmall_kCapacity) { + memcpy(str->small.data, cel_StringView_Data(val), new_len); + str->small.data[new_len] = '\0'; + str->small.len = new_len; + return true; + } + size_t new_cap = new_len; + CEL_NULLABLE(char*) + data = reinterpret_cast( + cel_Arena_Malloc(arena, new_cap + 1, cel_nullptr)); + if (CEL_UNLIKELY(data == cel_nullptr)) { + str->small.data[str->small.len] = '\0'; + return false; + } + _cel_GenericString_AnnotateLargeNew(data, new_len, new_cap); + str->large.data = data; + str->large.cap = new_cap; + str->large.is_large = 1; + } + + // If we got here, the string **must** be large. + memcpy(str->large.data, cel_StringView_Data(val), new_len); + str->large.data[new_len] = '\0'; + str->large.len = new_len; + return true; +} + +extern "C" ptrdiff_t _cel_GenericString_VAppendFAllocator( + CEL_NONNULL(_cel_GenericString*) str, CEL_NONNULL(cel_Allocator*) alloc, + CEL_NONNULL(const char*) fmt, va_list args) { + CEL_ASSERT_NOT_NULL(str); + CEL_ASSERT_NOT_NULL(alloc); + CEL_ASSERT_NOT_NULL(fmt); + + int n; + { + va_list args_copy; + va_copy(args_copy, args); + if (str->large.is_large) { + // We need to use the remaining capacity. Unpoison it all. + _cel_GenericString_AnnotateLargeDelete(str->large.data, str->large.len, + str->large.cap); + n = vsnprintf(str->large.data + str->large.len, + (str->large.cap - str->large.len) + 1, fmt, args_copy); + } else { + n = vsnprintf(str->small.data + str->small.len, + (_cel_GenericStringSmall_kCapacity - str->small.len) + 1, + fmt, args_copy); + } + va_end(args_copy); + } + + if (n <= 0) { + // Repoison and trust no one, re-terminate the string with NIL. + if (str->large.is_large) { + _cel_GenericString_AnnotateLargeNew(str->large.data, str->large.len, + str->large.cap); + str->large.data[str->large.len] = '\0'; + } else { + str->small.data[str->small.len] = '\0'; + } + return n; + } + + if (str->large.is_large) { + if ((size_t)n < (str->large.cap - str->large.len) + 1) { + // Fit in the remaining capacity. + size_t new_len = str->large.len + (size_t)n; + _cel_GenericString_AnnotateLargeNew(str->large.data, new_len, + str->large.cap); + str->large.data[new_len] = '\0'; + str->large.len = new_len; + return n; + } + size_t new_cap = str->large.len + (size_t)n; + size_t actual_new_cap; + CEL_NULLABLE(char*) + data = reinterpret_cast( + cel_Allocator_Malloc(alloc, new_cap + 1, &actual_new_cap)); + if (CEL_UNLIKELY(data == cel_nullptr)) { + _cel_GenericString_AnnotateLargeNew(str->large.data, str->large.len, + str->large.cap); + str->large.data[str->large.len] = '\0'; + return -1; + } + --actual_new_cap; + _cel_GenericString_AnnotateLargeNew(data, new_cap, actual_new_cap); + memcpy(data, str->large.data, str->large.len); + _cel_GenericString_AnnotateLargeDelete(str->large.data, str->large.len, + str->large.cap); + cel_Allocator_FreeSized(alloc, str->large.data, str->large.cap + 1); + str->large.data = data; + str->large.cap = actual_new_cap; + } else { + if ((size_t)n < (_cel_GenericStringSmall_kCapacity - str->small.len) + 1) { + size_t new_len = str->small.len + (size_t)n; + // Fit in the remaining capacity. + str->small.data[new_len] = '\0'; + str->small.len = new_len; + return n; + } + size_t len = str->small.len; + size_t new_cap = len + (size_t)n; + size_t actual_new_cap; + CEL_NULLABLE(char*) + data = reinterpret_cast( + cel_Allocator_Malloc(alloc, new_cap + 1, &actual_new_cap)); + if (CEL_UNLIKELY(data == cel_nullptr)) { + str->small.data[len] = '\0'; + return -1; + } + --actual_new_cap; + _cel_GenericString_AnnotateLargeNew(data, new_cap, actual_new_cap); + memcpy(data, str->small.data, len); + str->large.data = data; + str->large.len = len; + str->large.cap = actual_new_cap; + str->large.is_large = 1; + } + + // If we got here, the string **must** be large. + CEL_ASSERT(str->large.is_large); + const int n2 = vsnprintf(str->large.data + str->large.len, + (str->large.cap - str->large.len) + 1, fmt, args); + CEL_ASSERT_EQ(n2, n); + n = n2; + size_t new_len = str->large.len + (size_t)n; + CEL_ASSERT((size_t)n < (str->large.cap - str->large.len) + 1); + str->large.data[new_len] = '\0'; + str->large.len = new_len; + return n; +} + +extern "C" ptrdiff_t _cel_GenericString_VAppendFArena( + CEL_NONNULL(_cel_GenericString*) str, CEL_NONNULL(cel_Arena*) arena, + CEL_NONNULL(const char*) fmt, va_list args) { + CEL_ASSERT_NOT_NULL(str); + CEL_ASSERT_NOT_NULL(arena); + CEL_ASSERT_NOT_NULL(fmt); + + int n; + { + va_list args_copy; + va_copy(args_copy, args); + if (str->large.is_large) { + // We need to use the remaining capacity. Unpoison it all. + _cel_GenericString_AnnotateLargeDelete(str->large.data, str->large.len, + str->large.cap); + n = vsnprintf(str->large.data + str->large.len, + (str->large.cap - str->large.len) + 1, fmt, args_copy); + } else { + n = vsnprintf(str->small.data + str->small.len, + (_cel_GenericStringSmall_kCapacity - str->small.len) + 1, + fmt, args_copy); + } + va_end(args_copy); + } + + if (n <= 0) { + // Repoison and trust no one, re-terminate the string with NIL. + if (str->large.is_large) { + _cel_GenericString_AnnotateLargeNew(str->large.data, str->large.len, + str->large.cap); + str->large.data[str->large.len] = '\0'; + } else { + str->small.data[str->small.len] = '\0'; + } + return n; + } + + if (str->large.is_large) { + if ((size_t)n < (str->large.cap - str->large.len) + 1) { + // Fit in the remaining capacity. + size_t new_len = str->large.len + (size_t)n; + _cel_GenericString_AnnotateLargeNew(str->large.data, new_len, + str->large.cap); + str->large.data[new_len] = '\0'; + str->large.len = new_len; + return n; + } + size_t new_cap = str->large.len + (size_t)n; + CEL_NULLABLE(char*) + data = reinterpret_cast( + cel_Arena_Malloc(arena, new_cap + 1, cel_nullptr)); + if (CEL_UNLIKELY(data == cel_nullptr)) { + _cel_GenericString_AnnotateLargeNew(str->large.data, str->large.len, + str->large.cap); + str->large.data[str->large.len] = '\0'; + return -1; + } + _cel_GenericString_AnnotateLargeNew(data, new_cap, new_cap); + memcpy(data, str->large.data, str->large.len); + _cel_GenericString_AnnotateLargeDelete(str->large.data, str->large.len, + str->large.cap); + cel_Arena_FreeSized(arena, str->large.data, str->large.cap + 1); + str->large.data = data; + str->large.cap = new_cap; + } else { + if ((size_t)n < (_cel_GenericStringSmall_kCapacity - str->small.len) + 1) { + size_t new_len = str->small.len + (size_t)n; + // Fit in the remaining capacity. + str->small.data[new_len] = '\0'; + str->small.len = new_len; + return n; + } + size_t len = str->small.len; + size_t new_cap = len + (size_t)n; + CEL_NULLABLE(char*) + data = reinterpret_cast( + cel_Arena_Malloc(arena, new_cap + 1, cel_nullptr)); + if (CEL_UNLIKELY(data == cel_nullptr)) { + str->small.data[len] = '\0'; + return -1; + } + _cel_GenericString_AnnotateLargeNew(data, new_cap, new_cap); + memcpy(data, str->small.data, len); + str->large.data = data; + str->large.len = len; + str->large.cap = new_cap; + str->large.is_large = 1; + } + + // If we got here, the string **must** be large. + CEL_ASSERT(str->large.is_large); + const int n2 = vsnprintf(str->large.data + str->large.len, + (str->large.cap - str->large.len) + 1, fmt, args); + CEL_ASSERT_EQ(n2, n); + n = n2; + size_t new_len = str->large.len + (size_t)n; + CEL_ASSERT((size_t)n < (str->large.cap - str->large.len) + 1); + str->large.data[new_len] = '\0'; + str->large.len = new_len; + return n; +} + +extern "C" bool _cel_GenericString_StabilizeAllocator( + CEL_NONNULL(_cel_GenericString*) str, CEL_NONNULL(cel_Allocator*) alloc) { + CEL_ASSERT_NOT_NULL(str); + CEL_ASSERT_NOT_NULL(alloc); + + if (str->large.is_large) { + return true; + } + size_t len = str->small.len; + size_t actual_new_cap; + CEL_NULLABLE(char*) + data = reinterpret_cast( + cel_Allocator_Malloc(alloc, len + 1, &actual_new_cap)); + if (CEL_UNLIKELY(data == cel_nullptr)) { + return false; + } + --actual_new_cap; + _cel_GenericString_AnnotateLargeNew(data, len, actual_new_cap); + memcpy(data, str->small.data, str->small.len + 1); + str->large.is_large = 1; + str->large.len = len; + str->large.cap = actual_new_cap; + str->large.data = data; + return true; +} + +extern "C" bool _cel_GenericString_StabilizeArena( + CEL_NONNULL(_cel_GenericString*) str, CEL_NONNULL(cel_Arena*) arena) { + CEL_ASSERT_NOT_NULL(str); + CEL_ASSERT_NOT_NULL(arena); + + if (str->large.is_large) { + return true; + } + size_t len = str->small.len; + size_t actual_new_cap; + CEL_NULLABLE(char*) + data = reinterpret_cast( + cel_Arena_Malloc(arena, len + 1, &actual_new_cap)); + if (CEL_UNLIKELY(data == cel_nullptr)) { + return false; + } + --actual_new_cap; + _cel_GenericString_AnnotateLargeNew(data, len, actual_new_cap); + memcpy(data, str->small.data, str->small.len + 1); + str->large.is_large = 1; + str->large.len = len; + str->large.cap = actual_new_cap; + str->large.data = data; + return true; +} + +extern "C" void _cel_GenericString_DestabilizeAllocator( + CEL_NONNULL(_cel_GenericString*) str, CEL_NONNULL(cel_Allocator*) alloc) { + CEL_ASSERT_NOT_NULL(str); + CEL_ASSERT_NOT_NULL(alloc); + + if (str->large.is_large && + str->large.len <= _cel_GenericStringSmall_kCapacity) { + char* data = str->large.data; + size_t len = str->large.len; + size_t cap = str->large.cap; + str->small.is_large = 0; + str->small.len = (uint8_t)len; + memcpy(str->small.data, data, len + 1); + _cel_GenericString_AnnotateLargeDelete(data, len, cap); + cel_Allocator_FreeSized(alloc, data, cap + 1); + } +} + +extern "C" void _cel_GenericString_DestabilizeArena( + CEL_NONNULL(_cel_GenericString*) str, CEL_NONNULL(cel_Arena*) arena) { + CEL_ASSERT_NOT_NULL(str); + CEL_ASSERT_NOT_NULL(arena); + + if (str->large.is_large && + str->large.len <= _cel_GenericStringSmall_kCapacity) { + char* data = str->large.data; + size_t len = str->large.len; + size_t cap = str->large.cap; + str->small.is_large = 0; + str->small.len = (uint8_t)len; + memcpy(str->small.data, data, len + 1); + _cel_GenericString_AnnotateLargeDelete(data, len, cap); + cel_Arena_FreeSized(arena, data, cap + 1); + } +} + +CEL_ATTRIBUTE_NODISCARD +static bool _cel_GenericString_PrepareToAppendAllocator( + CEL_NONNULL(_cel_GenericString*) str, CEL_NONNULL(cel_Allocator*) alloc, + size_t size) { + CEL_ASSERT_NOT_NULL(str); + CEL_ASSERT_NOT_NULL(alloc); + + if (str->large.is_large) { + size_t capacity; + if (CEL_UNLIKELY(_cel_ckd_add(&capacity, size, (size_t)str->large.len))) { + return false; + } + if (CEL_LIKELY(str->large.cap >= capacity)) { + return true; + } + size_t min_cap = capacity; + size_t max_cap = _cel_GenericString_kMaxSize; + if (min_cap > max_cap) { + return false; + } + size_t new_cap = str->large.cap; + while (new_cap < min_cap) { + if (_cel_ckd_mul(&new_cap, new_cap, (size_t)2) || new_cap > max_cap) { + new_cap = max_cap; + break; + } + } + size_t actual_cap; + char* data = (char*)cel_Allocator_Malloc(alloc, new_cap + 1, &actual_cap); + if (CEL_UNLIKELY(data == cel_nullptr)) { + return false; + } + --actual_cap; + _cel_GenericString_AnnotateNew(data, str->large.len, actual_cap); + memcpy(data, str->large.data, str->large.len + 1); + _cel_GenericString_AnnotateDelete(str->large.data, str->large.len, + str->large.cap); + cel_Allocator_FreeSized(alloc, str->large.data, str->large.cap + 1); + str->large.data = data; + str->large.cap = actual_cap; + } else { + size_t capacity; + if (CEL_UNLIKELY(_cel_ckd_add(&capacity, size, (size_t)str->small.len))) { + return false; + } + if (_cel_GenericStringSmall_kCapacity >= capacity) { + return true; + } + size_t min_cap = capacity; + size_t max_cap = _cel_GenericString_kMaxSize; + if (min_cap > max_cap) { + return false; + } + size_t new_cap = _cel_GenericStringSmall_kCapacity; + while (new_cap < min_cap) { + if (_cel_ckd_mul(&new_cap, new_cap, (size_t)2) || new_cap > max_cap) { + new_cap = max_cap; + break; + } + } + size_t actual_cap; + char* data = (char*)cel_Allocator_Malloc(alloc, new_cap + 1, &actual_cap); + if (CEL_UNLIKELY(data == cel_nullptr)) { + return false; + } + --actual_cap; + _cel_GenericString_AnnotateNew(data, str->small.len, actual_cap); + memcpy(data, str->small.data, str->small.len + 1); + size_t len = str->small.len; + str->large.is_large = 1; + str->large.len = len; + str->large.data = data; + str->large.cap = actual_cap; + } + return true; +} + +CEL_ATTRIBUTE_NODISCARD +static bool _cel_GenericString_PrepareToAppendArena( + CEL_NONNULL(_cel_GenericString*) str, CEL_NONNULL(cel_Arena*) arena, + size_t size) { + CEL_ASSERT_NOT_NULL(str); + CEL_ASSERT_NOT_NULL(arena); + + if (str->large.is_large) { + size_t capacity; + if (CEL_UNLIKELY(_cel_ckd_add(&capacity, size, (size_t)str->large.len))) { + return false; + } + if (CEL_LIKELY(str->large.cap >= capacity)) { + return true; + } + size_t min_cap = capacity; + size_t max_cap = _cel_GenericString_kMaxSize; + if (min_cap > max_cap) { + return false; + } + size_t new_cap = str->large.cap; + while (new_cap < min_cap) { + if (_cel_ckd_mul(&new_cap, new_cap, (size_t)2) || new_cap > max_cap) { + new_cap = max_cap; + break; + } + } + size_t actual_cap; + char* data = (char*)cel_Arena_Malloc(arena, new_cap + 1, &actual_cap); + if (CEL_UNLIKELY(data == cel_nullptr)) { + return false; + } + --actual_cap; + _cel_GenericString_AnnotateNew(data, str->large.len, actual_cap); + memcpy(data, str->large.data, str->large.len + 1); + _cel_GenericString_AnnotateDelete(str->large.data, str->large.len, + str->large.cap); + cel_Arena_FreeSized(arena, str->large.data, str->large.cap + 1); + str->large.data = data; + str->large.cap = actual_cap; + } else { + size_t capacity; + if (CEL_UNLIKELY(_cel_ckd_add(&capacity, size, (size_t)str->small.len))) { + return false; + } + if (_cel_GenericStringSmall_kCapacity >= capacity) { + return true; + } + size_t min_cap = capacity; + size_t max_cap = _cel_GenericString_kMaxSize; + if (min_cap > max_cap) { + return false; + } + size_t new_cap = _cel_GenericStringSmall_kCapacity; + while (new_cap < min_cap) { + if (_cel_ckd_mul(&new_cap, new_cap, (size_t)2) || new_cap > max_cap) { + new_cap = max_cap; + break; + } + } + size_t actual_cap; + char* data = (char*)cel_Arena_Malloc(arena, new_cap + 1, &actual_cap); + if (CEL_UNLIKELY(data == cel_nullptr)) { + return false; + } + --actual_cap; + _cel_GenericString_AnnotateNew(data, str->small.len, actual_cap); + memcpy(data, str->small.data, str->small.len + 1); + size_t len = str->small.len; + str->large.is_large = 1; + str->large.len = len; + str->large.data = data; + str->large.cap = actual_cap; + } + return true; +} + +extern "C" bool _cel_GenericString_AppendAllocator( + CEL_NONNULL(_cel_GenericString*) str, CEL_NONNULL(cel_Allocator*) alloc, + cel_StringView val) { + CEL_ASSERT_NOT_NULL(str); + CEL_ASSERT_NOT_NULL(alloc); + + size_t val_len = cel_StringView_Size(val); + if (val_len > 0) { + if (CEL_UNLIKELY(!_cel_GenericString_PrepareToAppendAllocator(str, alloc, + val_len))) { + return false; + } + if (str->large.is_large) { + size_t new_len = str->large.len + val_len; + _cel_GenericString_AnnotateLarge(str->large.data, str->large.len, new_len, + str->large.cap); + memcpy(str->large.data + str->large.len, cel_StringView_Data(val), + val_len); + str->large.data[new_len] = '\0'; + str->large.len = new_len; + } else { + uint8_t new_len = str->small.len + (uint8_t)val_len; + memcpy(str->small.data + str->small.len, cel_StringView_Data(val), + val_len); + str->small.data[new_len] = '\0'; + str->small.len = new_len; + } + } + return true; +} + +extern "C" bool _cel_GenericString_AppendArena(CEL_NONNULL(_cel_GenericString*) + str, + CEL_NONNULL(cel_Arena*) arena, + cel_StringView val) { + CEL_ASSERT_NOT_NULL(str); + CEL_ASSERT_NOT_NULL(arena); + + size_t val_len = cel_StringView_Size(val); + if (val_len > 0) { + if (CEL_UNLIKELY( + !_cel_GenericString_PrepareToAppendArena(str, arena, val_len))) { + return false; + } + if (str->large.is_large) { + size_t new_len = str->large.len + val_len; + _cel_GenericString_AnnotateLarge(str->large.data, str->large.len, new_len, + str->large.cap); + memcpy(str->large.data + str->large.len, cel_StringView_Data(val), + val_len); + str->large.data[new_len] = '\0'; + str->large.len = new_len; + } else { + uint8_t new_len = str->small.len + (uint8_t)val_len; + memcpy(str->small.data + str->small.len, cel_StringView_Data(val), + val_len); + str->small.data[new_len] = '\0'; + str->small.len = new_len; + } + } + return true; +} + +extern "C" bool _cel_GenericString_PushBackAllocator( + CEL_NONNULL(_cel_GenericString*) str, CEL_NONNULL(cel_Allocator*) alloc, + char val) { + CEL_ASSERT_NOT_NULL(str); + CEL_ASSERT_NOT_NULL(alloc); + + if (CEL_UNLIKELY( + !_cel_GenericString_PrepareToAppendAllocator(str, alloc, 1))) { + return false; + } + if (str->large.is_large) { + size_t new_len = str->large.len + 1; + _cel_GenericString_AnnotateLarge(str->large.data, str->large.len, new_len, + str->large.cap); + str->large.data[new_len - 1] = val; + str->large.data[new_len] = '\0'; + str->large.len = new_len; + } else { + uint8_t new_len = str->small.len + (uint8_t)1; + str->small.data[new_len - 1] = val; + str->small.data[new_len] = '\0'; + str->small.len = new_len; + } + return true; +} + +extern "C" bool _cel_GenericString_PushBackArena( + CEL_NONNULL(_cel_GenericString*) str, CEL_NONNULL(cel_Arena*) arena, + char val) { + CEL_ASSERT_NOT_NULL(str); + CEL_ASSERT_NOT_NULL(arena); + + if (CEL_UNLIKELY(!_cel_GenericString_PrepareToAppendArena(str, arena, 1))) { + return false; + } + if (str->large.is_large) { + size_t new_len = str->large.len + 1; + _cel_GenericString_AnnotateLarge(str->large.data, str->large.len, new_len, + str->large.cap); + str->large.data[new_len - 1] = val; + str->large.data[new_len] = '\0'; + str->large.len = new_len; + } else { + uint8_t new_len = str->small.len + (uint8_t)1; + str->small.data[new_len - 1] = val; + str->small.data[new_len] = '\0'; + str->small.len = new_len; + } + return true; +} + +extern "C" bool _cel_GenericString_ReserveAllocator( + CEL_NONNULL(_cel_GenericString*) str, CEL_NONNULL(cel_Allocator*) alloc, + size_t capacity) { + CEL_ASSERT_NOT_NULL(str); + CEL_ASSERT_NOT_NULL(alloc); + + if (str->large.is_large) { + if (str->large.cap >= capacity) { + return true; + } + size_t min_cap = capacity; + size_t max_cap = _cel_GenericString_kMaxSize; + if (min_cap > max_cap) { + return false; + } + size_t actual_cap; + char* data = (char*)cel_Allocator_Malloc(alloc, min_cap + 1, &actual_cap); + if (CEL_UNLIKELY(data == cel_nullptr)) { + return false; + } + --actual_cap; + _cel_GenericString_AnnotateNew(data, str->large.len, actual_cap); + memcpy(data, str->large.data, str->large.len + 1); + _cel_GenericString_AnnotateDelete(str->large.data, str->large.len, + str->large.cap); + cel_Allocator_FreeSized(alloc, str->large.data, str->large.cap + 1); + str->large.data = data; + str->large.cap = actual_cap; + } else { + if (_cel_GenericStringSmall_kCapacity >= capacity) { + return true; + } + size_t min_cap = capacity; + size_t max_cap = _cel_GenericString_kMaxSize; + if (min_cap > max_cap) { + return false; + } + size_t actual_cap; + char* data = (char*)cel_Allocator_Malloc(alloc, min_cap + 1, &actual_cap); + if (CEL_UNLIKELY(data == cel_nullptr)) { + return false; + } + --actual_cap; + _cel_GenericString_AnnotateNew(data, str->small.len, actual_cap); + memcpy(data, str->small.data, str->small.len + 1); + size_t len = str->small.len; + str->large.is_large = 1; + str->large.len = len; + str->large.data = data; + str->large.cap = actual_cap; + } + return true; +} + +extern "C" bool _cel_GenericString_ReserveArena(CEL_NONNULL(_cel_GenericString*) + str, + CEL_NONNULL(cel_Arena*) arena, + size_t capacity) { + CEL_ASSERT_NOT_NULL(str); + CEL_ASSERT_NOT_NULL(arena); + + if (str->large.is_large) { + if (str->large.cap >= capacity) { + return true; + } + size_t min_cap = capacity; + size_t max_cap = _cel_GenericString_kMaxSize; + if (min_cap > max_cap) { + return false; + } + size_t actual_cap; + char* data = (char*)cel_Arena_Malloc(arena, min_cap + 1, &actual_cap); + if (CEL_UNLIKELY(data == cel_nullptr)) { + return false; + } + --actual_cap; + _cel_GenericString_AnnotateNew(data, str->large.len, actual_cap); + memcpy(data, str->large.data, str->large.len + 1); + _cel_GenericString_AnnotateDelete(str->large.data, str->large.len, + str->large.cap); + cel_Arena_FreeSized(arena, str->large.data, str->large.cap + 1); + str->large.data = data; + str->large.cap = actual_cap; + } else { + if (_cel_GenericStringSmall_kCapacity >= capacity) { + return true; + } + size_t min_cap = capacity; + size_t max_cap = _cel_GenericString_kMaxSize; + if (min_cap > max_cap) { + return false; + } + size_t actual_cap; + char* data = (char*)cel_Arena_Malloc(arena, min_cap + 1, &actual_cap); + if (CEL_UNLIKELY(data == cel_nullptr)) { + return false; + } + --actual_cap; + _cel_GenericString_AnnotateNew(data, str->small.len, actual_cap); + memcpy(data, str->small.data, str->small.len + 1); + size_t len = str->small.len; + str->large.is_large = 1; + str->large.len = len; + str->large.data = data; + str->large.cap = actual_cap; + } + return true; +} diff --git a/cel-c/src/list_value.cc b/cel-c/src/list_value.cc new file mode 100644 index 0000000..b188ba6 --- /dev/null +++ b/cel-c/src/list_value.cc @@ -0,0 +1,154 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include + +#include "cel-c/assert.h" +#include "cel-c/config.h" +#include "cel-c/status.h" +#include "cel-c/value.h" + +extern "C" bool cel_ListValue_Equals( + const cel_ListValue* cel_nonnull list_value, + const cel_ValueContext* cel_nonnull context, + const cel_ListValue* cel_nonnull other, cel_Value* cel_nonnull result, + cel_Status* cel_nonnull status) { + CEL_ASSERT_NOT_NULL(list_value); + CEL_ASSERT_NOT_NULL(context); + CEL_ASSERT_NOT_NULL(other); + CEL_ASSERT_NOT_NULL(result); + CEL_ASSERT_NOT_NULL(status); + + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return false; + } + + if (list_value->vtable->Equals != cel_nullptr) { + if ((*list_value->vtable->Equals)(list_value->vtable, list_value->content, + context, other, result, status)) { + return true; + } + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return false; + } + } + if (other->vtable->Equals != cel_nullptr && + other->vtable->Equals != list_value->vtable->Equals) { + if ((*other->vtable->Equals)(other->vtable, other->content, context, + list_value, result, status)) { + return true; + } + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return false; + } + } + if (list_value->vtable->FastSize != cel_nullptr && + other->vtable->FastSize != cel_nullptr) { + size_t lhs_size; + size_t rhs_size; + if ((*list_value->vtable->FastSize)(list_value->vtable, list_value->content, + &lhs_size) && + (*other->vtable->FastSize)(other->vtable, other->content, &rhs_size)) { + if (lhs_size != rhs_size) { + cel_Value_SetFalse(result); + return true; + } + if (lhs_size == 0) { + cel_Value_SetTrue(result); + return true; + } + } + } + cel_ListValueIterator* lhs_iter = + cel_ListValue_NewIterator(list_value, context, status); + if (CEL_UNLIKELY(lhs_iter == cel_nullptr)) { + CEL_ASSERT(!cel_Status_Ok(status)); + return false; + } + CEL_ASSERT(cel_Status_Ok(status)); + cel_ListValueIterator* rhs_iter = + cel_ListValue_NewIterator(other, context, status); + if (CEL_UNLIKELY(lhs_iter == cel_nullptr)) { + cel_ListValueIterator_Delete(lhs_iter); + CEL_ASSERT(!cel_Status_Ok(status)); + return false; + } + size_t lhs_size; + size_t rhs_size; + bool lhs_has_size; + bool rhs_has_size; + lhs_has_size = cel_ListValueIterator_Remaining(lhs_iter, &lhs_size); + rhs_has_size = cel_ListValueIterator_Remaining(rhs_iter, &rhs_size); + if (lhs_has_size && rhs_has_size) { + if (lhs_size != rhs_size) { + cel_ListValueIterator_Delete(rhs_iter); + cel_ListValueIterator_Delete(lhs_iter); + cel_Value_SetFalse(result); + return true; + } + if (lhs_size == 0) { + cel_ListValueIterator_Delete(rhs_iter); + cel_ListValueIterator_Delete(lhs_iter); + cel_Value_SetTrue(result); + return true; + } + } + CEL_ASSERT(cel_Status_Ok(status)); + bool ok; + while (true) { + cel_Value lhs_ele; + cel_Value rhs_ele; + bool lhs_has_ele; + bool rhs_has_ele; + if (!(lhs_has_ele = cel_ListValueIterator_Next1(lhs_iter, context, &lhs_ele, + status))) { + if (!cel_Status_Ok(status)) { + ok = false; + break; + } + } + CEL_ASSERT(cel_Status_Ok(status)); + if (!(rhs_has_ele = cel_ListValueIterator_Next1(rhs_iter, context, &rhs_ele, + status))) { + if (!cel_Status_Ok(status)) { + ok = false; + break; + } + } + CEL_ASSERT(cel_Status_Ok(status)); + if (lhs_has_ele != rhs_has_ele) { + cel_Value_SetFalse(result); + ok = true; + break; + } + if (!lhs_has_ele) { + cel_Value_SetTrue(result); + ok = true; + break; + } + if (!cel_Value_Equals(&lhs_ele, context, &rhs_ele, result, status)) { + if (!cel_Status_Ok(status)) { + ok = false; + break; + } + } + CEL_ASSERT(cel_Status_Ok(status)); + } + cel_ListValueIterator_Delete(rhs_iter); + cel_ListValueIterator_Delete(lhs_iter); + return ok; +} diff --git a/cel-c/src/malloc.cc b/cel-c/src/malloc.cc new file mode 100644 index 0000000..4896d9b --- /dev/null +++ b/cel-c/src/malloc.cc @@ -0,0 +1,159 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "cel-c/src/malloc.h" + +#if defined(__APPLE__) +#include +#endif + +#if defined(__FreeBSD__) +#include +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include "cel-c/assert.h" +#include "cel-c/src/bit.h" +#include "cel-c/src/ckdint.h" +#include "cel-c/src/config.h" + +#ifdef _CEL_HAVE_SANITIZER +#include +#endif + +#if _CEL_HAVE_ATTRIBUTE_WEAK && !defined(__FreeBSD__) && \ + !defined(__APPLE__) && !defined(_CEL_HAVE_SANITIZER) +extern "C" _CEL_ATTRIBUTE_WEAK size_t nallocx(size_t size, int opts) { + CEL_USED(opts); + return size; +} +#ifndef MALLOCX_LG_ALIGN +#define MALLOCX_LG_ALIGN(la) ((int)(la)) +#endif +#ifndef MALLOCX_ALIGN +#define MALLOCX_ALIGN(a) MALLOCX_LG_ALIGN(_cel_ffs(a) - 1) +#endif +#endif + +CEL_ATTRIBUTE_NODISCARD +static CEL_INLINE size_t _cel_MallocGoodSize(size_t size) { +#if defined(_CEL_HAVE_SANITIZER) + return __sanitizer_get_estimated_allocated_size(size); +#elif defined(__APPLE__) + return malloc_good_size(size); +#elif defined(__FreeBSD__) || _CEL_HAVE_ATTRIBUTE_WEAK + return nallocx(size, MALLOCX_ALIGN(alignof(max_align_t))); +#else + return size; +#endif +} + +extern "C" CEL_NULLABLE(void*) + _cel_Malloc(size_t size, CEL_NULLABLE(size_t*) actual_size) { + if (CEL_UNLIKELY(size == 0)) { + if (actual_size != cel_nullptr) { + *actual_size = 0; + } + return cel_nullptr; + } + if (CEL_UNLIKELY(size > (size_t)PTRDIFF_MAX)) { + if (actual_size != cel_nullptr) { + *actual_size = 0; + } + errno = ENOMEM; + return cel_nullptr; + } + if (actual_size != cel_nullptr) { + size = _cel_MallocGoodSize(size); + } + void* p = malloc(size); + if (actual_size != cel_nullptr) { + *actual_size = p != cel_nullptr ? size : 0; + } + if (CEL_UNLIKELY(p == cel_nullptr)) { + errno = ENOMEM; + } + return p; +} + +extern "C" CEL_NULLABLE(void*) + _cel_Calloc(size_t num, size_t size, CEL_NULLABLE(size_t*) actual_num) { + if (CEL_UNLIKELY(num == 0 || size == 0)) { + if (actual_num != cel_nullptr) { + *actual_num = 0; + } + return cel_nullptr; + } + size_t total_size; + if (CEL_UNLIKELY(_cel_ckd_mul(&total_size, num, size) || + total_size > (size_t)PTRDIFF_MAX)) { + if (actual_num != cel_nullptr) { + *actual_num = 0; + } + errno = ENOMEM; + return cel_nullptr; + } + void* addr = _cel_Malloc(total_size, actual_num); + if (CEL_LIKELY(addr != cel_nullptr)) { + memset(addr, 0, actual_num != cel_nullptr ? *actual_num : total_size); + } + if (actual_num != cel_nullptr) { + *actual_num /= size; + } + return addr; +} + +extern "C" CEL_NULLABLE(void*) + _cel_Realloc(CEL_NULLABLE(void*) addr, size_t old_size, size_t new_size, + CEL_NULLABLE(size_t*) actual_size) { + if (old_size == 0) { + CEL_ASSERT_NULL(addr); + return _cel_Malloc(new_size, actual_size); + } + if (new_size == 0) { + _cel_FreeSized(addr, old_size); + if (actual_size != cel_nullptr) { + *actual_size = 0; + } + return cel_nullptr; + } + if (actual_size == cel_nullptr) { + return realloc(addr, new_size); + } + void* new_addr = _cel_Malloc(new_size, actual_size); + if (CEL_LIKELY(new_addr != cel_nullptr)) { + memcpy(new_addr, addr, old_size < new_size ? old_size : new_size); + _cel_FreeSized(addr, old_size); + } + return new_addr; +} + +extern "C" void _cel_Free(CEL_NULLABLE(void*) addr) { free(addr); } + +extern "C" void _cel_FreeSized(CEL_NULLABLE(void*) addr, size_t size) { + CEL_ASSERT((addr == cel_nullptr && size == 0) || size != 0); + + // Because we may be using `nallocx`/`malloc_good_size` above, the passed in + // `size` may not be in the acceptable range for `free_sized`. So fallback to + // `free`. + CEL_USED(size); + free(addr); +} diff --git a/cel-c/src/map_value.cc b/cel-c/src/map_value.cc new file mode 100644 index 0000000..c749fad --- /dev/null +++ b/cel-c/src/map_value.cc @@ -0,0 +1,396 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "cel-c/src/map_value.h" + +#include +#include +#include +#include + +#include "cel-c/assert.h" +#include "cel-c/config.h" +#include "cel-c/src/array.h" +#include "cel-c/src/sort.h" +#include "cel-c/status.h" +#include "cel-c/value.h" +#include "cel-c/value_kind.h" + +static void _cel_MapValueKey_SetValue(cel_MapValueKey* cel_nonnull + map_value_key, + const cel_Value* cel_nonnull value) { + switch (cel_Value_Kind(value)) { + case cel_ValueKind_kBool: + cel_MapValueKey_SetBool(map_value_key, cel_Value_GetBool(value)); + return; + case cel_ValueKind_kInt: + cel_MapValueKey_SetInt(map_value_key, cel_Value_GetInt(value)); + return; + case cel_ValueKind_kUint: + cel_MapValueKey_SetUint(map_value_key, cel_Value_GetUint(value)); + return; + case cel_ValueKind_kString: + cel_MapValueKey_SetString(map_value_key, cel_Value_GetString(value)); + return; + default: + CEL_UNREACHABLE(); + } +} + +typedef struct { + cel_MapValueKey key; + cel_Value value; +} _cel_MapValueEntry; + +static int _cel_MapValueEntry_Compare(const void* cel_nullability_unknown lhs, + const void* cel_nullability_unknown rhs) { + return _cel_MapValueKey_Compare(&((const _cel_MapValueEntry*)lhs)->key, + &((const _cel_MapValueEntry*)rhs)->key); +} + +static bool _cel_MapValue_EqualsFast( + const cel_MapValue* cel_nonnull lhs, size_t lhs_size, + const cel_ValueContext* cel_nonnull context, + const cel_MapValue* cel_nonnull rhs, size_t rhs_size, + cel_Value* cel_nonnull result, cel_Status* cel_nonnull status) { + if (lhs_size != rhs_size) { + cel_Value_SetFalse(result); + return true; + } + if (lhs_size == 0) { + cel_Value_SetTrue(result); + return true; + } + // Both have size. We can iterate through one and check if the other has the + // same key and value. + cel_MapValueIterator* lhs_iter = + cel_MapValue_NewIterator(lhs, context, status); + if (CEL_UNLIKELY(lhs_iter == cel_nullptr)) { + CEL_ASSERT(!cel_Status_Ok(status)); + return false; + } + bool ok = true; + while (true) { + cel_Value lhs_key; + cel_Value lhs_value; + cel_MapValueKey rhs_key; + cel_Value rhs_value; + if (!cel_MapValueIterator_Next2(lhs_iter, context, &lhs_key, &lhs_value, + status)) { + if (!cel_Status_Ok(status)) { + ok = false; + break; + } + cel_Value_SetTrue(result); + break; + } + if (cel_Value_IsError(&lhs_key) || cel_Value_IsError(&lhs_value)) { + cel_Value_SetFalse(result); + ok = false; + break; + } + _cel_MapValueKey_SetValue(&rhs_key, &lhs_key); + if (cel_MapValue_Find(rhs, context, &rhs_key, &rhs_value, status)) { + if (!cel_Value_Equals(&lhs_value, context, &rhs_value, result, status)) { + ok = cel_Status_Ok(status); + if (ok) { + cel_Value_SetFalse(result); + } + break; + } + } else { + ok = cel_Status_Ok(status); + if (ok) { + cel_Value_SetFalse(result); + } + break; + } + } + cel_MapValueIterator_Delete(lhs_iter); + return ok; +} + +static bool _cel_MapValue_EqualsSlowWithSize( + const cel_MapValue* cel_nonnull lhs, size_t lhs_size, + const cel_ValueContext* cel_nonnull context, + const cel_MapValue* cel_nonnull rhs, cel_Value* cel_nonnull result, + cel_Status* cel_nonnull status) { + cel_MapValueIterator* rhs_iter = + cel_MapValue_NewIterator(rhs, context, status); + if (CEL_UNLIKELY(rhs_iter == cel_nullptr)) { + CEL_ASSERT(!cel_Status_Ok(status)); + return false; + } + size_t rhs_size; + if (cel_MapValueIterator_Remaining(rhs_iter, &rhs_size)) { + if (rhs_size != lhs_size) { + cel_Value_SetFalse(result); + cel_MapValueIterator_Delete(rhs_iter); + return true; + } + if (rhs_size == 0) { + cel_Value_SetTrue(result); + cel_MapValueIterator_Delete(rhs_iter); + return true; + } + } + rhs_size = 0; + bool ok = true; + while (true) { + cel_Value rhs_key; + cel_Value rhs_value; + cel_MapValueKey lhs_key; + cel_Value lhs_value; + if (!cel_MapValueIterator_Next2(rhs_iter, context, &rhs_key, &rhs_value, + status)) { + if (!cel_Status_Ok(status)) { + ok = false; + break; + } + cel_Value_SetBool(result, rhs_size == lhs_size); + break; + } + ++rhs_size; + if (cel_Value_IsError(&rhs_key) || cel_Value_IsError(&rhs_value)) { + cel_Value_SetFalse(result); + ok = false; + break; + } + _cel_MapValueKey_SetValue(&lhs_key, &rhs_key); + if (cel_MapValue_Find(lhs, context, &lhs_key, &lhs_value, status)) { + if (!cel_Value_Equals(&rhs_value, context, &lhs_value, result, status)) { + ok = cel_Status_Ok(status); + if (ok) { + cel_Value_SetFalse(result); + } + break; + } + } else { + ok = cel_Status_Ok(status); + if (ok) { + cel_Value_SetFalse(result); + } + break; + } + } + cel_MapValueIterator_Delete(rhs_iter); + return ok; +} + +static bool _cel_MapValue_EqualsSlow( + const cel_MapValue* cel_nonnull lhs, + const cel_ValueContext* cel_nonnull context, + const cel_MapValue* cel_nonnull rhs, cel_Value* cel_nonnull result, + cel_Status* cel_nonnull status) { + cel_MapValueIterator* lhs_iter = + cel_MapValue_NewIterator(lhs, context, status); + if (CEL_UNLIKELY(lhs_iter == cel_nullptr)) { + CEL_ASSERT(!cel_Status_Ok(status)); + return false; + } + cel_MapValueIterator* rhs_iter = + cel_MapValue_NewIterator(rhs, context, status); + if (CEL_UNLIKELY(rhs_iter == cel_nullptr)) { + CEL_ASSERT(!cel_Status_Ok(status)); + cel_MapValueIterator_Delete(lhs_iter); + return false; + } + size_t lhs_size; + size_t rhs_size; + bool lhs_has_size; + bool rhs_has_size; + lhs_has_size = cel_MapValueIterator_Remaining(lhs_iter, &lhs_size); + rhs_has_size = cel_MapValueIterator_Remaining(rhs_iter, &rhs_size); + if (lhs_has_size && rhs_has_size) { + if (lhs_size != rhs_size) { + cel_Value_SetFalse(result); + cel_MapValueIterator_Delete(rhs_iter); + cel_MapValueIterator_Delete(lhs_iter); + return true; + } + if (lhs_size == 0) { + cel_Value_SetTrue(result); + cel_MapValueIterator_Delete(rhs_iter); + cel_MapValueIterator_Delete(lhs_iter); + return true; + } + } + _cel_Array(_cel_MapValueEntry) lhs_entries; + _cel_Array(_cel_MapValueEntry) rhs_entries; + _cel_Array_Construct(&lhs_entries); + if (lhs_has_size) { + _cel_Array_Reserve(&lhs_entries, context->alloc, lhs_size); + } + _cel_Array_Construct(&rhs_entries); + if (rhs_has_size) { + _cel_Array_Reserve(&rhs_entries, context->alloc, rhs_size); + } + bool ok = true; + bool lhs_next = true; + bool rhs_next = true; + while (true) { + if (lhs_next) { + _cel_MapValueEntry lhs_entry; + lhs_next = cel_MapValueIterator_Next(lhs_iter, context, &lhs_entry.key, + &lhs_entry.value, status); + if (!lhs_next && !cel_Status_Ok(status)) { + ok = false; + break; + } + if (lhs_next) { + _cel_MapValueEntry* lhs_entry_ptr = + _cel_Array_Push(&lhs_entries, context->alloc); + if (CEL_UNLIKELY(lhs_entry_ptr == cel_nullptr)) { + cel_OutOfMemoryStatus(status); + ok = false; + break; + } + *lhs_entry_ptr = lhs_entry; + } + } + if (rhs_next) { + _cel_MapValueEntry rhs_entry; + rhs_next = cel_MapValueIterator_Next(rhs_iter, context, &rhs_entry.key, + &rhs_entry.value, status); + if (!rhs_next && !cel_Status_Ok(status)) { + ok = false; + break; + } + if (rhs_next) { + _cel_MapValueEntry* rhs_entry_ptr = + _cel_Array_Push(&rhs_entries, context->alloc); + if (CEL_UNLIKELY(rhs_entry_ptr == cel_nullptr)) { + cel_OutOfMemoryStatus(status); + ok = false; + break; + } + *rhs_entry_ptr = rhs_entry; + } + } + if (!lhs_next || !rhs_next) { + break; + } + } + if (ok) { + if (lhs_next || rhs_next) { + cel_Value_SetFalse(result); + } else { + const size_t lhs_entries_size = _cel_Array_Size(&lhs_entries); + const size_t rhs_entries_size = _cel_Array_Size(&rhs_entries); + if (lhs_entries_size != rhs_entries_size) { + cel_Value_SetFalse(result); + } else { + _cel_MapValueEntry* lhs_entries_data = + _cel_Array_MutableData(&lhs_entries); + _cel_MapValueEntry* rhs_entries_data = + _cel_Array_MutableData(&rhs_entries); + _cel_Sort(lhs_entries_data, lhs_entries_size, + sizeof(_cel_MapValueEntry), &_cel_MapValueEntry_Compare); + _cel_Sort(rhs_entries_data, rhs_entries_size, + sizeof(_cel_MapValueEntry), &_cel_MapValueEntry_Compare); + for (size_t i = 0; i < lhs_entries_size; ++i) { + if (!_cel_MapValueKey_Equals(&lhs_entries_data[i].key, + &rhs_entries_data[i].key)) { + cel_Value_SetFalse(result); + goto done; + } + if (!cel_Value_Equals(&lhs_entries_data[i].value, context, + &rhs_entries_data[i].value, result, status)) { + ok = false; + goto done; + } + if (!cel_Value_IsTrue(result)) { + cel_Value_SetFalse(result); + goto done; + } + } + cel_Value_SetTrue(result); + } + } + } +done: + _cel_Array_Destruct(&rhs_entries, context->alloc); + _cel_Array_Destruct(&lhs_entries, context->alloc); + cel_MapValueIterator_Delete(rhs_iter); + cel_MapValueIterator_Delete(lhs_iter); + return ok; +} + +extern "C" bool cel_MapValue_Equals(const cel_MapValue* cel_nonnull map_value, + const cel_ValueContext* cel_nonnull context, + const cel_MapValue* cel_nonnull other, + cel_Value* cel_nonnull result, + cel_Status* cel_nonnull status) { + CEL_ASSERT_NOT_NULL(map_value); + CEL_ASSERT_NOT_NULL(context); + CEL_ASSERT_NOT_NULL(other); + CEL_ASSERT_NOT_NULL(result); + CEL_ASSERT_NOT_NULL(status); + + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return false; + } + + if (map_value->vtable->Equals != cel_nullptr) { + if ((*map_value->vtable->Equals)(map_value->vtable, map_value->content, + context, other, result, status)) { + return true; + } + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return false; + } + } + if (other->vtable->Equals != cel_nullptr && + other->vtable->Equals != map_value->vtable->Equals) { + if ((*other->vtable->Equals)(other->vtable, other->content, context, + map_value, result, status)) { + return true; + } + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return false; + } + } + size_t lhs_size; + size_t rhs_size; + bool lhs_has_size; + bool rhs_has_size; + if (map_value->vtable->FastSize != cel_nullptr) { + lhs_has_size = (*map_value->vtable->FastSize)( + map_value->vtable, map_value->content, &lhs_size); + } else { + lhs_has_size = false; + } + if (other->vtable->FastSize != cel_nullptr) { + rhs_has_size = + (*other->vtable->FastSize)(other->vtable, other->content, &rhs_size); + } else { + rhs_has_size = false; + } + if (lhs_has_size && rhs_has_size) { + return _cel_MapValue_EqualsFast(map_value, lhs_size, context, other, + rhs_size, result, status); + } + // Unfortunately one or both of the map values does not know its size in + // constant time. We have no choice but to collect the key value pairs into + // arrays, sort, and then compare. + if (lhs_has_size) { + return _cel_MapValue_EqualsSlowWithSize(map_value, lhs_size, context, other, + result, status); + } + if (rhs_has_size) { + return _cel_MapValue_EqualsSlowWithSize(other, rhs_size, context, map_value, + result, status); + } + return _cel_MapValue_EqualsSlow(map_value, context, other, result, status); +} diff --git a/cel-c/src/memory.cc b/cel-c/src/memory.cc new file mode 100644 index 0000000..e2b4d39 --- /dev/null +++ b/cel-c/src/memory.cc @@ -0,0 +1,166 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "cel-c/src/memory.h" + +#include // IWYU pragma: keep +#include +#include + +#include "cel-c/assert.h" +#include "cel-c/src/config.h" + +#ifndef __GLIBC__ +static inline void* memrchr(const void* b, int c, size_t len) { + if (len > 0) { + const unsigned char* p = ((const unsigned char*)b) + len; + do { + --p; + if (*p == (unsigned char)c) { + return (void*)p; + } + --len; + } while (len > 0); + } + return cel_nullptr; +} +#endif + +extern "C" bool _cel_Memory_Equals(CEL_NULLABLE(const void*) lhs_data, + size_t lhs_size, + CEL_NULLABLE(const void*) rhs_data, + size_t rhs_size) { + CEL_ASSERT(lhs_size == 0 || lhs_data != cel_nullptr); + CEL_ASSERT(rhs_size == 0 || rhs_data != cel_nullptr); + return lhs_size == rhs_size && (lhs_size == 0 || lhs_data == rhs_data || + memcmp(lhs_data, rhs_data, lhs_size) == 0); +} + +extern "C" int _cel_Memory_Compare(CEL_NULLABLE(const void*) lhs_data, + size_t lhs_size, + CEL_NULLABLE(const void*) rhs_data, + size_t rhs_size) { + CEL_ASSERT(lhs_size == 0 || lhs_data != cel_nullptr); + CEL_ASSERT(rhs_size == 0 || rhs_data != cel_nullptr); + int cmp = lhs_data != rhs_data + ? memcmp(lhs_data, rhs_data, + lhs_size < rhs_size ? lhs_size : rhs_size) + : 0; + if (cmp == 0) { + cmp = lhs_size < rhs_size ? -1 : lhs_size > rhs_size ? 1 : 0; + } + return cmp; +} + +extern "C" bool _cel_Memory_StartsWith(CEL_NULLABLE(const void*) hay_data, + size_t hay_size, + CEL_NULLABLE(const void*) ndl_data, + size_t ndl_size) { + CEL_ASSERT(hay_size == 0 || hay_data != cel_nullptr); + CEL_ASSERT(ndl_size == 0 || ndl_data != cel_nullptr); + return hay_size >= ndl_size && memcmp(hay_data, ndl_data, ndl_size) == 0; +} + +extern "C" bool _cel_Memory_EndsWith(CEL_NULLABLE(const void*) hay_data, + size_t hay_size, + CEL_NULLABLE(const void*) ndl_data, + size_t ndl_size) { + CEL_ASSERT(hay_size == 0 || hay_data != cel_nullptr); + CEL_ASSERT(ndl_size == 0 || ndl_data != cel_nullptr); + return hay_size >= ndl_size && + memcmp(((CEL_NULLABLE(const char*))hay_data) + (hay_size - ndl_size), + ndl_data, ndl_size) == 0; +} + +extern "C" CEL_NULLABLE(void*) + _cel_Memory_FindFirst(CEL_NULLABLE(const void*) hay_data, size_t hay_size, + CEL_NULLABLE(const void*) ndl_data, size_t ndl_size) { + CEL_ASSERT(hay_size == 0 || hay_data != cel_nullptr); + CEL_ASSERT(ndl_size == 0 || ndl_data != cel_nullptr); + if (CEL_UNLIKELY(ndl_size == 0)) { + return (CEL_NULLABLE(void*))hay_data; + } + if (ndl_size > hay_size) { + return cel_nullptr; + } + if (CEL_UNLIKELY(hay_data == ndl_data)) { + CEL_ASSERT_GE(hay_size, ndl_size); + return (CEL_NULLABLE(void*))hay_data; + } + if (hay_size > ndl_size) { + const unsigned char ndl_front = + *((CEL_NONNULL(const unsigned char*))ndl_data); + do { + CEL_NULLABLE(char*) + hay_next = (CEL_NULLABLE(char*))memchr(hay_data, ndl_front, + (hay_size - ndl_size) + 1); + if (hay_next == cel_nullptr) { + return cel_nullptr; + } + if (memcmp(hay_next + 1, ((CEL_NONNULL(char*))ndl_data) + 1, + ndl_size - 1) == 0) { + return hay_next; + } + size_t skip = (hay_next - ((CEL_NONNULL(const char*))hay_data)) + 1; + hay_data = ((CEL_NONNULL(const char*))hay_data) + skip; + hay_size -= skip; + } while (hay_size > ndl_size); + } + CEL_ASSERT_LE(hay_size, ndl_size); + if (hay_size == ndl_size) { + return memcmp(hay_data, ndl_data, ndl_size) == 0 + ? (CEL_NULLABLE(void*))hay_data + : (CEL_NULLABLE(void*))cel_nullptr; + } + return cel_nullptr; +} + +extern "C" CEL_NULLABLE(void*) + _cel_Memory_FindLast(CEL_NULLABLE(const void*) hay_data, size_t hay_size, + CEL_NULLABLE(const void*) ndl_data, size_t ndl_size) { + CEL_ASSERT(hay_size == 0 || hay_data != cel_nullptr); + CEL_ASSERT(ndl_size == 0 || ndl_data != cel_nullptr); + if (CEL_UNLIKELY(ndl_size == 0)) { + return (CEL_NULLABLE(void*))(((CEL_NULLABLE(const char*))hay_data)); + } + if (ndl_size > hay_size) { + return cel_nullptr; + } + if (hay_size > ndl_size) { + const unsigned char ndl_front = + *((CEL_NONNULL(const unsigned char*))ndl_data); + do { + const size_t hay_lim = (hay_size - ndl_size) + 1; + CEL_NULLABLE(char*) + hay_prev = (CEL_NULLABLE(char*))memrchr(hay_data, ndl_front, hay_lim); + if (hay_prev == cel_nullptr) { + return cel_nullptr; + } + if (memcmp(hay_prev + 1, ((CEL_NONNULL(char*))ndl_data) + 1, + ndl_size - 1) == 0) { + return hay_prev; + } + size_t skip = + ((((CEL_NULLABLE(const char*))hay_data) + hay_lim) - hay_prev) + 1; + hay_size -= skip; + } while (hay_size > ndl_size); + } + CEL_ASSERT_LE(hay_size, ndl_size); + if (hay_size == ndl_size) { + return memcmp(hay_data, ndl_data, ndl_size) == 0 + ? (CEL_NULLABLE(void*))hay_data + : (CEL_NULLABLE(void*))cel_nullptr; + } + return cel_nullptr; +} diff --git a/cel-c/src/message_equality.cc b/cel-c/src/message_equality.cc new file mode 100644 index 0000000..1836679 --- /dev/null +++ b/cel-c/src/message_equality.cc @@ -0,0 +1,1342 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "cel-c/src/message_equality.h" + +#include +#include +#include + +#include "cel-c/alloc.h" +#include "cel-c/arena.h" +#include "cel-c/assert.h" +#include "cel-c/config.h" +#include "cel-c/src/any.h" +#include "cel-c/src/number.h" +#include "cel-c/src/setjmp.h" +#include "cel-c/string_view.h" +#include "cel-c/well_known_types.h" +#include "upb/base/descriptor_constants.h" +#include "upb/base/string_view.h" +#include "upb/message/array.h" +#include "upb/message/map.h" +#include "upb/message/message.h" +#include "upb/reflection/def.h" +#include "upb/reflection/message.h" +#include "upb/wire/eps_copy_input_stream.h" +#include "upb/wire/reader.h" +#include "upb/wire/types.h" + +typedef struct _cel_UnknownField _cel_UnknownField; +typedef struct _cel_UnknownFields _cel_UnknownFields; + +struct _cel_UnknownFields { + _cel_UnknownField* cel_nullable head; + _cel_UnknownField* cel_nullable tail; + _cel_UnknownField* cel_nullable mid; + uint32_t size; + int32_t balance; +}; + +struct _cel_UnknownField { + _cel_UnknownField* cel_nullable prev; + _cel_UnknownField* cel_nullable next; +#ifdef _MSC_VER +#pragma pack(push, 4) +#endif + union CEL_ATTRIBUTE_PACKED(4) { + uint64_t varint; + uint64_t fixed64; + uint32_t fixed32; + struct CEL_ATTRIBUTE_PACKED(4) { + const char* cel_nullability_unknown data; + uint32_t size; + } delimited; + _cel_UnknownFields* cel_nullable group; + } data; +#ifdef _MSC_VER +#pragma pack(pop) +#endif + uint32_t field_num : 29; + uint8_t wire_type : 3; +}; + +typedef struct { + const upb_DefPool* const cel_nonnull def_pool; + const cel_WellKnownTypes* const cel_nonnull wkts; + cel_Allocator* const cel_nonnull alloc; + cel_Arena* cel_nullable arena; + upb_EpsCopyInputStream stream; + _cel_MessageEquality result; + int depth; + _cel_jmp_buf jmp; +} _cel_MessageEqualityState; + +CEL_ATTRIBUTE_NORETURN +static void _cel_MessageEqualityState_Throw( + _cel_MessageEqualityState* cel_nonnull state, _cel_MessageEquality result) { + CEL_ASSERT_NE(result, _cel_MessageEquality_kEqual); + CEL_ASSERT_NE(result, _cel_MessageEquality_kNotEqual); + + state->result = result; + _cel_longjmp(state->jmp); +} + +CEL_ATTRIBUTE_NODISCARD +static cel_Arena* cel_nonnull +_cel_MessageEqualityState_Arena(_cel_MessageEqualityState* cel_nonnull state) { + cel_Arena* arena = state->arena; + if (arena == cel_nullptr) { + arena = state->arena = cel_Arena_New(state->alloc); + if (CEL_UNLIKELY(arena == cel_nullptr)) { + _cel_MessageEqualityState_Throw(state, _cel_MessageEquality_kOutOfMemory); + } + } + return arena; +} + +CEL_ATTRIBUTE_NODISCARD +static _cel_UnknownFields* cel_nonnull +_cel_UnknownFields_New(_cel_MessageEqualityState* cel_nonnull state) { + _cel_UnknownFields* fields = reinterpret_cast<_cel_UnknownFields*>( + cel_Arena_Malloc(_cel_MessageEqualityState_Arena(state), + sizeof(_cel_UnknownFields), cel_nullptr)); + if (CEL_UNLIKELY(fields == cel_nullptr)) { + _cel_MessageEqualityState_Throw(state, _cel_MessageEquality_kOutOfMemory); + } + fields->head = cel_nullptr; + fields->tail = cel_nullptr; + fields->mid = cel_nullptr; + fields->size = 0; + fields->balance = 0; + return fields; +} + +CEL_ATTRIBUTE_NODISCARD +static _cel_UnknownField* cel_nonnull _cel_UnknownField_New( + _cel_MessageEqualityState* cel_nonnull state, uint32_t tag) { + _cel_UnknownField* field = reinterpret_cast<_cel_UnknownField*>( + cel_Arena_Malloc(_cel_MessageEqualityState_Arena(state), + sizeof(_cel_UnknownField), cel_nullptr)); + if (CEL_UNLIKELY(field == cel_nullptr)) { + _cel_MessageEqualityState_Throw(state, _cel_MessageEquality_kOutOfMemory); + } + field->prev = cel_nullptr; + field->next = cel_nullptr; + field->field_num = upb_WireReader_GetFieldNumber(tag); + field->wire_type = upb_WireReader_GetWireType(tag); + return field; +} + +CEL_ATTRIBUTE_NODISCARD +static int _cel_UnknownField_Compare(const _cel_UnknownField* cel_nonnull lhs, + const _cel_UnknownField* cel_nonnull rhs) { + const uint32_t lhs_field_num = lhs->field_num; + const uint32_t rhs_field_num = rhs->field_num; + if (lhs_field_num < rhs_field_num) { + return -1; + } + if (lhs_field_num > rhs_field_num) { + return 1; + } + const uint8_t lhs_wire_type = lhs->wire_type; + const uint8_t rhs_wire_type = rhs->wire_type; + return lhs_wire_type < rhs_wire_type ? -1 + : lhs_wire_type > rhs_wire_type ? 1 + : 0; +} + +static void _cel_UnknownFields_Add(_cel_UnknownFields* cel_nonnull fields, + _cel_UnknownField* cel_nonnull field) { + if (fields->tail != cel_nullptr) { + _cel_UnknownField* tail = fields->tail; + if (_cel_UnknownField_Compare(field, tail) >= 0) { + // Fast path. Fields are in order already. + field->prev = tail; + tail->next = field; + fields->tail = field; + ++fields->balance; + } else { + // Slow path. + _cel_UnknownField* node = fields->mid; + CEL_ASSERT_NOT_NULL(node); + while (_cel_UnknownField_Compare(field, node) < 0) { + node = node->prev; + if (node == cel_nullptr) { + field->next = fields->head; + fields->head->prev = field; + fields->head = field; + goto inserted; + } + CEL_ASSERT_NOT_NULL(node); + } + CEL_ASSERT_NOT_NULL(node); + while (_cel_UnknownField_Compare(field, node) >= 0) { + node = node->next; + CEL_ASSERT_NOT_NULL(node); + } + field->next = node; + field->prev = node->prev; + if (node->prev != cel_nullptr) { + node->prev->next = field; + node->prev = field; + } else { + fields->head = field; + } + } + inserted: + fields->balance += _cel_UnknownField_Compare(field, fields->mid); + } else { + fields->head = fields->mid = fields->tail = field; + } + ++fields->size; + if (fields->size > 2 && (fields->size & 1) == 1) { + if (fields->balance < 0) { + fields->mid = fields->mid->prev; + } else if (fields->balance > 0) { + fields->mid = fields->mid->next; + } + fields->balance = 0; + } +} + +CEL_ATTRIBUTE_NODISCARD +static _cel_UnknownFields* cel_nullable +_cel_UnknownFields_Read(_cel_MessageEqualityState* cel_nonnull state, + _cel_UnknownFields* fields, const char** buf) { + const char* ptr = *buf; + while (!upb_EpsCopyInputStream_IsDone(&state->stream, &ptr)) { + uint32_t tag; + ptr = upb_WireReader_ReadTag(ptr, &tag, &state->stream); + uint8_t wire_type = upb_WireReader_GetWireType(tag); + if (wire_type == kUpb_WireType_EndGroup) { + break; + } + if (CEL_UNLIKELY(fields == cel_nullptr)) { + fields = _cel_UnknownFields_New(state); + } + _cel_UnknownField* field = _cel_UnknownField_New(state, tag); + switch (wire_type) { + case kUpb_WireType_Varint: + ptr = + upb_WireReader_ReadVarint(ptr, &field->data.varint, &state->stream); + break; + case kUpb_WireType_64Bit: + ptr = upb_WireReader_ReadFixed64(ptr, &field->data.fixed64, + &state->stream); + break; + case kUpb_WireType_32Bit: + ptr = upb_WireReader_ReadFixed32(ptr, &field->data.fixed32, + &state->stream); + break; + case kUpb_WireType_Delimited: { + int size; + upb_StringView sv; + ptr = upb_WireReader_ReadSize(ptr, &size, &state->stream); + ptr = upb_EpsCopyInputStream_ReadStringAlwaysAlias(&state->stream, ptr, + size, &sv); + field->data.delimited.data = sv.data; + field->data.delimited.size = sv.size; + } break; + case kUpb_WireType_StartGroup: + CEL_ASSERT_GT(state->depth, 0); + if (--state->depth == 0) { + _cel_MessageEqualityState_Throw( + state, _cel_MessageEquality_kMaxDepthExceeded); + } + field->data.group = _cel_UnknownFields_Read(state, cel_nullptr, &ptr); + ++state->depth; + break; + default: + CEL_UNREACHABLE(); + } + _cel_UnknownFields_Add(fields, field); + } + *buf = ptr; + return fields; +} + +CEL_ATTRIBUTE_NODISCARD +static _cel_UnknownFields* cel_nullable +_cel_UnknownFields_FromMessage(_cel_MessageEqualityState* cel_nonnull state, + const upb_Message* cel_nonnull msg) { + _cel_UnknownFields* fields = cel_nullptr; + cel_StringView unknown; + uintptr_t iter = kUpb_Message_UnknownBegin; + while (upb_Message_NextUnknown(msg, &unknown, &iter)) { + upb_EpsCopyInputStream_Init(&state->stream, &unknown.data, + cel_StringView_Size(unknown)); + fields = _cel_UnknownFields_Read(state, fields, &unknown.data); + CEL_ASSERT(upb_EpsCopyInputStream_IsDone(&state->stream, &unknown.data) && + !upb_EpsCopyInputStream_IsError(&state->stream)); + } + return fields; +} + +CEL_ATTRIBUTE_NODISCARD +static bool _cel_UnknownFields_Equals( + _cel_MessageEqualityState* cel_nonnull state, + const _cel_UnknownFields* cel_nullable lhs_fields, + const _cel_UnknownFields* cel_nullable rhs_fields) { + const size_t lhs_fields_size = + lhs_fields != cel_nullptr ? lhs_fields->size : 0; + const size_t rhs_fields_size = + rhs_fields != cel_nullptr ? rhs_fields->size : 0; + if (lhs_fields_size != rhs_fields_size) { + return false; + } + if (lhs_fields_size == 0) { + return true; + } + + const _cel_UnknownField* lhs_field = lhs_fields->head; + const _cel_UnknownField* rhs_field = rhs_fields->head; + for (size_t i = 0; i < lhs_fields_size; + ++i, lhs_field = lhs_field->next, rhs_field = rhs_field->next) { + if (lhs_field->field_num != rhs_field->field_num || + lhs_field->wire_type != rhs_field->wire_type) { + return false; + } + switch (lhs_field->wire_type) { + case kUpb_WireType_Varint: + if (lhs_field->data.varint != rhs_field->data.varint) { + return false; + } + break; + case kUpb_WireType_64Bit: + if (lhs_field->data.fixed64 != rhs_field->data.fixed64) { + return false; + } + break; + case kUpb_WireType_32Bit: + if (lhs_field->data.fixed32 != rhs_field->data.fixed32) { + return false; + } + break; + case kUpb_WireType_Delimited: + if (!cel_StringView_Equals( + cel_StringView_FromArray(lhs_field->data.delimited.data, + lhs_field->data.delimited.size), + cel_StringView_FromArray(rhs_field->data.delimited.data, + rhs_field->data.delimited.size))) { + return false; + } + break; + case kUpb_WireType_StartGroup: + if (!_cel_UnknownFields_Equals(state, lhs_field->data.group, + rhs_field->data.group)) { + return false; + } + break; + default: + CEL_UNREACHABLE(); + } + } + return true; +} + +CEL_ATTRIBUTE_NODISCARD +static bool _cel_MessageEqualityState_UnknownEquals( + _cel_MessageEqualityState* cel_nonnull state, + const upb_Message* cel_nonnull lhs, const upb_Message* cel_nonnull rhs) { + const bool lhs_has_unknown = upb_Message_HasUnknown(lhs); + const bool rhs_has_unknown = upb_Message_HasUnknown(rhs); + if (!lhs_has_unknown && !rhs_has_unknown) { + return true; + } + if (!(lhs_has_unknown && rhs_has_unknown)) { + return false; + } + + _cel_UnknownFields* lhs_fields = _cel_UnknownFields_FromMessage(state, lhs); + _cel_UnknownFields* rhs_fields = _cel_UnknownFields_FromMessage(state, rhs); + return _cel_UnknownFields_Equals(state, lhs_fields, rhs_fields); +} + +CEL_ATTRIBUTE_NODISCARD +static bool _cel_MessageEqualityState_FieldHomoEquals( + _cel_MessageEqualityState* cel_nonnull state, upb_MessageValue lhs_val, + upb_MessageValue rhs_val, const upb_FieldDef* cel_nonnull val_def); + +CEL_ATTRIBUTE_NODISCARD +static bool _cel_MessageEqualityState_HomoEquals( + _cel_MessageEqualityState* cel_nonnull state, + const upb_Message* cel_nonnull lhs_val, + const upb_Message* cel_nonnull rhs_val, + const upb_MessageDef* cel_nonnull val_def) { + if (lhs_val == rhs_val) { + return true; + } + + const upb_FieldDef* lhs_field_def; + const upb_FieldDef* rhs_field_def; + upb_MessageValue lhs_field_val; + upb_MessageValue rhs_field_val; + size_t lhs_iter = static_cast(kUpb_Message_Begin); + size_t rhs_iter = static_cast(kUpb_Message_Begin); + while (true) { + bool lhs_has_next = + upb_Message_Next(lhs_val, val_def, state->def_pool, &lhs_field_def, + &lhs_field_val, &lhs_iter); + bool rhs_has_next = + upb_Message_Next(rhs_val, val_def, state->def_pool, &rhs_field_def, + &rhs_field_val, &rhs_iter); + if (lhs_has_next != rhs_has_next) { + return false; + } + if (!lhs_has_next) { + break; + } + if (lhs_field_def != rhs_field_def) { + return false; + } + if (!_cel_MessageEqualityState_FieldHomoEquals( + state, lhs_field_val, rhs_field_val, rhs_field_def)) { + return false; + } + } + + return _cel_MessageEqualityState_UnknownEquals(state, lhs_val, rhs_val); +} + +CEL_ATTRIBUTE_NODISCARD +static bool _cel_MessageEqualityState_SingularFieldHomoEquals( + _cel_MessageEqualityState* cel_nonnull state, upb_MessageValue lhs_val, + upb_MessageValue rhs_val, const upb_FieldDef* cel_nonnull val_def) { + CEL_ASSERT_NOT(upb_FieldDef_IsMap(val_def)); + + switch (upb_FieldDef_CType(val_def)) { + case kUpb_CType_Bool: + return lhs_val.bool_val == rhs_val.bool_val; + case kUpb_CType_Float: + return lhs_val.float_val == rhs_val.float_val; + case kUpb_CType_Enum: + CEL_ATTRIBUTE_FALLTHROUGH; + case kUpb_CType_Int32: + return lhs_val.int32_val == rhs_val.int32_val; + case kUpb_CType_UInt32: + return lhs_val.uint32_val == rhs_val.uint32_val; + case kUpb_CType_Message: + return _cel_MessageEqualityState_HomoEquals( + state, lhs_val.msg_val, rhs_val.msg_val, + upb_FieldDef_MessageSubDef(val_def)); + case kUpb_CType_Double: + return lhs_val.double_val == rhs_val.double_val; + case kUpb_CType_Int64: + return lhs_val.int64_val == rhs_val.int64_val; + case kUpb_CType_UInt64: + return lhs_val.uint64_val == rhs_val.uint64_val; + case kUpb_CType_String: + CEL_ATTRIBUTE_FALLTHROUGH; + case kUpb_CType_Bytes: + return cel_StringView_Equals(lhs_val.str_val, rhs_val.str_val); + default: + CEL_UNREACHABLE(); + } +} + +CEL_ATTRIBUTE_NODISCARD +static bool _cel_MessageEqualityState_MapFieldHomoEquals( + _cel_MessageEqualityState* cel_nonnull state, + const upb_Map* cel_nullable lhs_val, const upb_Map* cel_nullable rhs_val, + const upb_FieldDef* cel_nonnull val_def) { + const size_t lhs_val_size = + lhs_val != cel_nullptr ? upb_Map_Size(lhs_val) : 0; + const size_t rhs_val_size = + rhs_val != cel_nullptr ? upb_Map_Size(rhs_val) : 0; + + if (lhs_val_size != rhs_val_size) { + return false; + } + + if (lhs_val_size == 0) { + return true; + } + + const upb_MessageDef* val_entry_def = upb_FieldDef_MessageSubDef(val_def); + const upb_FieldDef* val_value_def = upb_MessageDef_FindFieldByNumber( + val_entry_def, kUpb_MapEntry_ValueFieldNumber); + + size_t lhs_iter = kUpb_Map_Begin; + upb_MessageValue lhs_val_key; + upb_MessageValue lhs_val_value; + while (upb_Map_Next(lhs_val, &lhs_val_key, &lhs_val_value, &lhs_iter)) { + upb_MessageValue rhs_val_value; + if (!upb_Map_Get(rhs_val, lhs_val_key, &rhs_val_value)) { + return false; + } + if (!_cel_MessageEqualityState_SingularFieldHomoEquals( + state, lhs_val_value, rhs_val_value, val_value_def)) { + return false; + } + } + return true; +} + +CEL_ATTRIBUTE_NODISCARD +static bool _cel_MessageEqualityState_RepeatedFieldHomoEquals( + _cel_MessageEqualityState* cel_nonnull state, + const upb_Array* cel_nullable lhs_val, + const upb_Array* cel_nullable rhs_val, + const upb_FieldDef* cel_nonnull val_def) { + const size_t lhs_val_size = + lhs_val != cel_nullptr ? upb_Array_Size(lhs_val) : 0; + const size_t rhs_val_size = + rhs_val != cel_nullptr ? upb_Array_Size(rhs_val) : 0; + + if (lhs_val_size != rhs_val_size) { + return false; + } + + if (lhs_val_size == 0) { + return true; + } + + for (size_t i = 0; i < lhs_val_size; ++i) { + if (!_cel_MessageEqualityState_SingularFieldHomoEquals( + state, upb_Array_Get(lhs_val, i), upb_Array_Get(rhs_val, i), + val_def)) { + return false; + } + } + return true; +} + +CEL_ATTRIBUTE_NODISCARD +static bool _cel_MessageEqualityState_FieldHomoEquals( + _cel_MessageEqualityState* cel_nonnull state, upb_MessageValue lhs_val, + upb_MessageValue rhs_val, const upb_FieldDef* cel_nonnull val_def) { + if (upb_FieldDef_IsMap(val_def)) { + return _cel_MessageEqualityState_MapFieldHomoEquals( + state, lhs_val.map_val, rhs_val.map_val, val_def); + } + if (upb_FieldDef_IsRepeated(val_def)) { + return _cel_MessageEqualityState_RepeatedFieldHomoEquals( + state, lhs_val.array_val, rhs_val.array_val, val_def); + } + return _cel_MessageEqualityState_SingularFieldHomoEquals(state, lhs_val, + rhs_val, val_def); +} + +typedef enum CEL_ATTRIBUTE_CLOSED_ENUM { + _cel_MessageEqualityFieldKind_kScalar = 1, + _cel_MessageEqualityFieldKind_kMessage, + _cel_MessageEqualityFieldKind_kNullValue, + _cel_MessageEqualityFieldKind_kRepeated, + _cel_MessageEqualityFieldKind_kMap, +} _cel_MessageEqualityFieldKind; + +typedef enum CEL_ATTRIBUTE_CLOSED_ENUM { + _cel_MessageEqualityScalarFieldKind_kBool = 1, + _cel_MessageEqualityScalarFieldKind_kNumber, + _cel_MessageEqualityScalarFieldKind_kString, + _cel_MessageEqualityScalarFieldKind_kBytes, +} _cel_MessageEqualityScalarFieldKind; + +typedef struct { + struct { + union { + _cel_MessageEqualityScalarFieldKind scalar_def; + const upb_MessageDef* message_def; + const upb_FieldDef* field_def; + } def; + union { + bool bool_val; + _cel_Number num_val; + cel_StringView str_val; + const upb_Array* array_val; + const upb_Map* map_val; + const upb_Message* msg_val; + } val; + } data; + _cel_MessageEqualityFieldKind kind; +} _cel_MessageEqualityField; + +static void _cel_MessageEqualityState_Field( + _cel_MessageEqualityState* cel_nonnull state, upb_MessageValue in_val, + const upb_FieldDef* cel_nonnull in_def, bool single, + _cel_MessageEqualityField* cel_nonnull out) { + if (!single) { + if (upb_FieldDef_IsMap(in_def)) { + out->data.val.map_val = in_val.map_val; + out->data.def.field_def = in_def; + out->kind = _cel_MessageEqualityFieldKind_kMap; + return; + } + if (upb_FieldDef_IsRepeated(in_def)) { + out->data.val.array_val = in_val.array_val; + out->data.def.field_def = in_def; + out->kind = _cel_MessageEqualityFieldKind_kRepeated; + return; + } + } + switch (upb_FieldDef_CType(in_def)) { + case kUpb_CType_Bool: + out->data.def.scalar_def = _cel_MessageEqualityScalarFieldKind_kBool; + out->data.val.bool_val = in_val.bool_val; + out->kind = _cel_MessageEqualityFieldKind_kScalar; + break; + case kUpb_CType_Float: + out->data.def.scalar_def = _cel_MessageEqualityScalarFieldKind_kNumber; + out->data.val.num_val = _cel_DoubleNumber(in_val.float_val); + out->kind = _cel_MessageEqualityFieldKind_kScalar; + break; + case kUpb_CType_Int32: + out->data.def.scalar_def = _cel_MessageEqualityScalarFieldKind_kNumber; + out->data.val.num_val = _cel_IntNumber(in_val.int32_val); + out->kind = _cel_MessageEqualityFieldKind_kScalar; + break; + case kUpb_CType_UInt32: + out->data.def.scalar_def = _cel_MessageEqualityScalarFieldKind_kNumber; + out->data.val.num_val = _cel_UintNumber(in_val.uint32_val); + out->kind = _cel_MessageEqualityFieldKind_kScalar; + break; + case kUpb_CType_Enum: { + const upb_EnumDef* enum_def = upb_FieldDef_EnumSubDef(in_def); + const char* enum_def_name = upb_EnumDef_FullName(enum_def); + if (enum_def_name != cel_nullptr && + strcmp(enum_def_name, "google.protobuf.NullValue") == 0) { + out->kind = _cel_MessageEqualityFieldKind_kNullValue; + } else { + out->data.def.scalar_def = _cel_MessageEqualityScalarFieldKind_kNumber; + out->data.val.num_val = _cel_IntNumber(in_val.int32_val); + out->kind = _cel_MessageEqualityFieldKind_kScalar; + } + } break; + case kUpb_CType_Message: { + const upb_MessageDef* message_def = upb_FieldDef_MessageSubDef(in_def); + upb_WellKnown well_known = upb_MessageDef_WellKnownType(message_def); + if (well_known == kUpb_WellKnown_Any) { + CEL_ASSERT_EQ(message_def, state->wkts->any.def); + upb_Message* out_message; + const upb_MessageDef* out_message_def; + cel_Arena* arena = _cel_MessageEqualityState_Arena(state); + switch (_cel_AnyUnpack(in_val.msg_val, state->def_pool, + &state->wkts->any, arena, &out_message, + &out_message_def)) { + case _cel_AnyUnpackResult_kOk: + in_val.msg_val = out_message; + message_def = out_message_def; + well_known = upb_MessageDef_WellKnownType(message_def); + break; + case _cel_AnyUnpackResult_kOutOfMemory: + _cel_MessageEqualityState_Throw(state, + _cel_MessageEquality_kOutOfMemory); + default: + message_def = + out_message_def != cel_nullptr ? out_message_def : message_def; + in_val.msg_val = + out_message != cel_nullptr ? out_message : in_val.msg_val; + well_known = upb_MessageDef_WellKnownType(message_def); + break; + } + } + switch (well_known) { + case kUpb_WellKnown_DoubleValue: + CEL_ASSERT_EQ(message_def, state->wkts->double_value.def); + out->data.def.scalar_def = + _cel_MessageEqualityScalarFieldKind_kNumber; + out->data.val.num_val = _cel_DoubleNumber( + upb_Message_GetFieldByDef(in_val.msg_val, + state->wkts->double_value.value_def) + .double_val); + out->kind = _cel_MessageEqualityFieldKind_kScalar; + break; + case kUpb_WellKnown_FloatValue: + CEL_ASSERT_EQ(message_def, state->wkts->float_value.def); + out->data.def.scalar_def = + _cel_MessageEqualityScalarFieldKind_kNumber; + out->data.val.num_val = _cel_DoubleNumber( + upb_Message_GetFieldByDef(in_val.msg_val, + state->wkts->float_value.value_def) + .float_val); + out->kind = _cel_MessageEqualityFieldKind_kScalar; + break; + case kUpb_WellKnown_Int64Value: + CEL_ASSERT_EQ(message_def, state->wkts->int64_value.def); + out->data.def.scalar_def = + _cel_MessageEqualityScalarFieldKind_kNumber; + out->data.val.num_val = _cel_IntNumber( + upb_Message_GetFieldByDef(in_val.msg_val, + state->wkts->int64_value.value_def) + .int64_val); + out->kind = _cel_MessageEqualityFieldKind_kScalar; + break; + case kUpb_WellKnown_UInt64Value: + CEL_ASSERT_EQ(message_def, state->wkts->uint64_value.def); + out->data.def.scalar_def = + _cel_MessageEqualityScalarFieldKind_kNumber; + out->data.val.num_val = _cel_UintNumber( + upb_Message_GetFieldByDef(in_val.msg_val, + state->wkts->uint64_value.value_def) + .uint64_val); + out->kind = _cel_MessageEqualityFieldKind_kScalar; + break; + case kUpb_WellKnown_Int32Value: + CEL_ASSERT_EQ(message_def, state->wkts->int32_value.def); + out->data.def.scalar_def = + _cel_MessageEqualityScalarFieldKind_kNumber; + out->data.val.num_val = _cel_IntNumber( + upb_Message_GetFieldByDef(in_val.msg_val, + state->wkts->int32_value.value_def) + .int32_val); + out->kind = _cel_MessageEqualityFieldKind_kScalar; + break; + case kUpb_WellKnown_UInt32Value: + CEL_ASSERT_EQ(message_def, state->wkts->uint32_value.def); + out->data.def.scalar_def = + _cel_MessageEqualityScalarFieldKind_kNumber; + out->data.val.num_val = _cel_UintNumber( + upb_Message_GetFieldByDef(in_val.msg_val, + state->wkts->uint32_value.value_def) + .uint32_val); + out->kind = _cel_MessageEqualityFieldKind_kScalar; + break; + case kUpb_WellKnown_StringValue: + CEL_ASSERT_EQ(message_def, state->wkts->string_value.def); + out->data.def.scalar_def = + _cel_MessageEqualityScalarFieldKind_kString; + out->data.val.str_val = + upb_Message_GetFieldByDef(in_val.msg_val, + state->wkts->string_value.value_def) + .str_val; + out->kind = _cel_MessageEqualityFieldKind_kScalar; + break; + case kUpb_WellKnown_BytesValue: + CEL_ASSERT_EQ(message_def, state->wkts->bytes_value.def); + out->data.def.scalar_def = _cel_MessageEqualityScalarFieldKind_kBytes; + out->data.val.str_val = + upb_Message_GetFieldByDef(in_val.msg_val, + state->wkts->bytes_value.value_def) + .str_val; + out->kind = _cel_MessageEqualityFieldKind_kScalar; + break; + case kUpb_WellKnown_BoolValue: + CEL_ASSERT_EQ(message_def, state->wkts->bool_value.def); + out->data.def.scalar_def = _cel_MessageEqualityScalarFieldKind_kBool; + out->data.val.bool_val = + upb_Message_GetFieldByDef(in_val.msg_val, + state->wkts->bool_value.value_def) + .bool_val; + out->kind = _cel_MessageEqualityFieldKind_kScalar; + break; + case kUpb_WellKnown_Value: { + CEL_ASSERT_EQ(message_def, state->wkts->value.def); + const upb_FieldDef* field_def = upb_Message_WhichOneofByDef( + in_val.msg_val, state->wkts->value.kind_def); + if (field_def == cel_nullptr) { + out->kind = _cel_MessageEqualityFieldKind_kNullValue; + } else { + switch (upb_FieldDef_Number(field_def)) { + case 1: // null_value + out->kind = _cel_MessageEqualityFieldKind_kNullValue; + break; + case 2: // number_value + out->data.def.scalar_def = + _cel_MessageEqualityScalarFieldKind_kNumber; + out->data.val.num_val = _cel_DoubleNumber( + upb_Message_GetFieldByDef(in_val.msg_val, field_def) + .double_val); + out->kind = _cel_MessageEqualityFieldKind_kScalar; + break; + case 3: // string_value + out->data.def.scalar_def = + _cel_MessageEqualityScalarFieldKind_kString; + out->data.val.str_val = + upb_Message_GetFieldByDef(in_val.msg_val, field_def) + .str_val; + out->kind = _cel_MessageEqualityFieldKind_kScalar; + break; + case 4: // bool_value + out->data.def.scalar_def = + _cel_MessageEqualityScalarFieldKind_kBool; + out->data.val.bool_val = + upb_Message_GetFieldByDef(in_val.msg_val, field_def) + .bool_val; + out->kind = _cel_MessageEqualityFieldKind_kScalar; + break; + case 5: // struct_value + out->data.val.map_val = + upb_Message_GetFieldByDef( + upb_Message_GetFieldByDef(in_val.msg_val, field_def) + .msg_val, + state->wkts->struct_value.fields_def) + .map_val; + out->data.def.field_def = state->wkts->struct_value.fields_def; + out->kind = _cel_MessageEqualityFieldKind_kMap; + break; + case 6: // list_value + out->data.val.array_val = + upb_Message_GetFieldByDef( + upb_Message_GetFieldByDef(in_val.msg_val, field_def) + .msg_val, + state->wkts->list_value.values_def) + .array_val; + out->data.def.field_def = state->wkts->list_value.values_def; + out->kind = _cel_MessageEqualityFieldKind_kRepeated; + break; + default: + CEL_UNREACHABLE(); + } + } + } break; + case kUpb_WellKnown_ListValue: + CEL_ASSERT_EQ(message_def, state->wkts->list_value.def); + out->data.val.array_val = + upb_Message_GetFieldByDef(in_val.msg_val, + state->wkts->list_value.values_def) + .array_val; + out->data.def.field_def = state->wkts->list_value.values_def; + out->kind = _cel_MessageEqualityFieldKind_kRepeated; + break; + case kUpb_WellKnown_Struct: + CEL_ASSERT_EQ(message_def, state->wkts->struct_value.def); + out->data.val.map_val = + upb_Message_GetFieldByDef(in_val.msg_val, + state->wkts->struct_value.fields_def) + .map_val; + out->data.def.field_def = state->wkts->struct_value.fields_def; + out->kind = _cel_MessageEqualityFieldKind_kMap; + break; + default: + out->data.def.message_def = message_def; + out->data.val.msg_val = in_val.msg_val; + out->kind = _cel_MessageEqualityFieldKind_kMessage; + break; + } + } break; + case kUpb_CType_Double: + out->data.def.scalar_def = _cel_MessageEqualityScalarFieldKind_kNumber; + out->data.val.num_val = _cel_DoubleNumber(in_val.double_val); + out->kind = _cel_MessageEqualityFieldKind_kScalar; + break; + case kUpb_CType_Int64: + out->data.def.scalar_def = _cel_MessageEqualityScalarFieldKind_kNumber; + out->data.val.num_val = _cel_IntNumber(in_val.int64_val); + out->kind = _cel_MessageEqualityFieldKind_kScalar; + break; + case kUpb_CType_UInt64: + out->data.def.scalar_def = _cel_MessageEqualityScalarFieldKind_kNumber; + out->data.val.num_val = _cel_UintNumber(in_val.uint64_val); + out->kind = _cel_MessageEqualityFieldKind_kScalar; + break; + case kUpb_CType_String: + out->data.def.scalar_def = _cel_MessageEqualityScalarFieldKind_kString; + out->data.val.str_val = in_val.str_val; + out->kind = _cel_MessageEqualityFieldKind_kScalar; + break; + case kUpb_CType_Bytes: + out->data.def.scalar_def = _cel_MessageEqualityScalarFieldKind_kBytes; + out->data.val.str_val = in_val.str_val; + out->kind = _cel_MessageEqualityFieldKind_kScalar; + break; + default: + CEL_UNREACHABLE(); + } +} + +CEL_ATTRIBUTE_NODISCARD +static bool _cel_MessageEqualityState_FieldHeteroEquals( + _cel_MessageEqualityState* cel_nonnull state, upb_MessageValue lhs_val, + const upb_FieldDef* cel_nonnull lhs_def, upb_MessageValue rhs_val, + const upb_FieldDef* cel_nonnull rhs_def, bool single); + +typedef bool _cel_MessageEqualityState_MapKeyCoalescer( + upb_MessageValue in, upb_MessageValue* cel_nonnull out); + +// T -> T +static bool _cel_MessageEqualityState_CoalesceMapKeyIdentity( + upb_MessageValue in, upb_MessageValue* cel_nonnull out) { + *out = in; + return true; +} + +// int32 -> int64 +static bool _cel_MessageEqualityState_CoalesceMapKeyInt32Int64( + upb_MessageValue in, upb_MessageValue* cel_nonnull out) { + out->int64_val = in.int32_val; + return true; +} + +// int32 -> uint32 +static bool _cel_MessageEqualityState_CoalesceMapKeyInt32UInt32( + upb_MessageValue in, upb_MessageValue* cel_nonnull out) { + if (in.int32_val < 0) { + return false; + } + out->uint32_val = (uint32_t)in.int32_val; + return true; +} + +// int32 -> uint64 +static bool _cel_MessageEqualityState_CoalesceMapKeyInt32UInt64( + upb_MessageValue in, upb_MessageValue* cel_nonnull out) { + if (in.int32_val < 0) { + return false; + } + out->uint64_val = (uint32_t)in.int32_val; + return true; +} + +// uint32 -> int32 +static bool _cel_MessageEqualityState_CoalesceMapKeyUInt32Int32( + upb_MessageValue in, upb_MessageValue* cel_nonnull out) { + if (in.uint32_val > (uint32_t)INT32_MAX) { + return false; + } + out->int32_val = (int32_t)in.uint32_val; + return true; +} + +// uint32 -> int64 +static bool _cel_MessageEqualityState_CoalesceMapKeyUInt32Int64( + upb_MessageValue in, upb_MessageValue* cel_nonnull out) { + out->int64_val = (int64_t)(uint64_t)in.uint32_val; + return true; +} + +// uint32 -> uint64 +static bool _cel_MessageEqualityState_CoalesceMapKeyUInt32UInt64( + upb_MessageValue in, upb_MessageValue* cel_nonnull out) { + out->uint64_val = in.uint32_val; + return true; +} + +// int64 -> int32 +static bool _cel_MessageEqualityState_CoalesceMapKeyInt64Int32( + upb_MessageValue in, upb_MessageValue* cel_nonnull out) { + if (in.int64_val < INT32_MIN || in.int64_val > INT32_MAX) { + return false; + } + out->int32_val = (int32_t)in.int64_val; + return true; +} + +// int64 -> uint32 +static bool _cel_MessageEqualityState_CoalesceMapKeyInt64UInt32( + upb_MessageValue in, upb_MessageValue* cel_nonnull out) { + if (in.int64_val < 0 || in.int64_val > (int64_t)(uint64_t)UINT32_MAX) { + return false; + } + out->uint32_val = (uint32_t)(int32_t)in.int64_val; + return true; +} + +// int64 -> uint64 +static bool _cel_MessageEqualityState_CoalesceMapKeyInt64UInt64( + upb_MessageValue in, upb_MessageValue* cel_nonnull out) { + if (in.int64_val < 0) { + return false; + } + out->uint64_val = (uint64_t)in.int64_val; + return true; +} + +// uint64 -> int32 +static bool _cel_MessageEqualityState_CoalesceMapKeyUInt64Int32( + upb_MessageValue in, upb_MessageValue* cel_nonnull out) { + if (in.uint64_val > (uint64_t)(uint32_t)INT32_MAX) { + return false; + } + out->int32_val = (uint64_t)(uint32_t)in.uint64_val; + return true; +} + +// uint64 -> int32 +static bool _cel_MessageEqualityState_CoalesceMapKeyUInt64Int64( + upb_MessageValue in, upb_MessageValue* cel_nonnull out) { + if (in.uint64_val > (uint64_t)INT64_MAX) { + return false; + } + out->int64_val = (int64_t)in.uint64_val; + return true; +} + +// uint64 -> uint32 +static bool _cel_MessageEqualityState_CoalesceMapKeyUInt64UInt32( + upb_MessageValue in, upb_MessageValue* cel_nonnull out) { + if (in.uint64_val > UINT32_MAX) { + return false; + } + out->uint32_val = (uint32_t)in.uint64_val; + return true; +} + +CEL_ATTRIBUTE_NODISCARD +static bool _cel_MessageEqualityState_MapFieldHeteroEquals( + _cel_MessageEqualityState* cel_nonnull state, + const upb_Map* cel_nullable lhs_val, + const upb_FieldDef* cel_nonnull lhs_def, + const upb_Map* cel_nullable rhs_val, + const upb_FieldDef* cel_nonnull rhs_def) { + CEL_ASSERT(upb_FieldDef_IsMap(lhs_def)); + CEL_ASSERT(upb_FieldDef_IsMap(rhs_def)); + + const size_t lhs_val_size = + lhs_val != cel_nullptr ? upb_Map_Size(lhs_val) : 0; + const size_t rhs_val_size = + rhs_val != cel_nullptr ? upb_Map_Size(rhs_val) : 0; + + if (lhs_val_size != rhs_val_size) { + return false; + } + + if (lhs_val_size == 0) { + return true; + } + + const upb_MessageDef* lhs_entry_def = upb_FieldDef_MessageSubDef(lhs_def); + const upb_FieldDef* lhs_key_def = upb_MessageDef_FindFieldByNumber( + lhs_entry_def, kUpb_MapEntry_KeyFieldNumber); + const upb_CType lhs_key_def_type = upb_FieldDef_CType(lhs_key_def); + + const upb_MessageDef* rhs_entry_def = upb_FieldDef_MessageSubDef(rhs_def); + const upb_FieldDef* rhs_key_def = upb_MessageDef_FindFieldByNumber( + rhs_entry_def, kUpb_MapEntry_KeyFieldNumber); + const upb_CType rhs_key_def_type = upb_FieldDef_CType(rhs_key_def); + + _cel_MessageEqualityState_MapKeyCoalescer* map_key_coalescer; + + switch (lhs_key_def_type) { + case kUpb_CType_Bool: + if (rhs_key_def_type != kUpb_CType_Bool) { + return false; + } + map_key_coalescer = &_cel_MessageEqualityState_CoalesceMapKeyIdentity; + break; + case kUpb_CType_Enum: { + if (strcmp(upb_EnumDef_FullName(upb_FieldDef_EnumSubDef(lhs_key_def)), + "google.protobuf.NullValue") == 0) { + if (rhs_key_def_type != kUpb_CType_Enum || + strcmp(upb_EnumDef_FullName(upb_FieldDef_EnumSubDef(rhs_key_def)), + "google.protobuf.NullValue") != 0) { + return false; + } + // Both null. + map_key_coalescer = &_cel_MessageEqualityState_CoalesceMapKeyIdentity; + break; + } + if (rhs_key_def_type == kUpb_CType_Enum && + strcmp(upb_EnumDef_FullName(upb_FieldDef_EnumSubDef(rhs_key_def)), + "google.protobuf.NullValue") == 0) { + return false; + } + // Neither null. + map_key_coalescer = &_cel_MessageEqualityState_CoalesceMapKeyIdentity; + break; + } + case kUpb_CType_Int32: + switch (rhs_key_def_type) { + case kUpb_CType_Enum: + if (strcmp(upb_EnumDef_FullName(upb_FieldDef_EnumSubDef(rhs_key_def)), + "google.protobuf.NullValue") == 0) { + return false; + } + CEL_ATTRIBUTE_FALLTHROUGH; + case kUpb_CType_Int32: + map_key_coalescer = &_cel_MessageEqualityState_CoalesceMapKeyIdentity; + break; + case kUpb_CType_Int64: + map_key_coalescer = + &_cel_MessageEqualityState_CoalesceMapKeyInt32Int64; + break; + case kUpb_CType_UInt32: + map_key_coalescer = + &_cel_MessageEqualityState_CoalesceMapKeyInt32UInt32; + break; + case kUpb_CType_UInt64: + map_key_coalescer = + &_cel_MessageEqualityState_CoalesceMapKeyInt32UInt64; + break; + default: + return false; + } + break; + case kUpb_CType_Int64: + switch (rhs_key_def_type) { + case kUpb_CType_Enum: + if (strcmp(upb_EnumDef_FullName(upb_FieldDef_EnumSubDef(rhs_key_def)), + "google.protobuf.NullValue") == 0) { + return false; + } + CEL_ATTRIBUTE_FALLTHROUGH; + case kUpb_CType_Int32: + map_key_coalescer = + &_cel_MessageEqualityState_CoalesceMapKeyInt64Int32; + break; + case kUpb_CType_Int64: + map_key_coalescer = &_cel_MessageEqualityState_CoalesceMapKeyIdentity; + break; + case kUpb_CType_UInt32: + map_key_coalescer = + &_cel_MessageEqualityState_CoalesceMapKeyInt64UInt32; + break; + case kUpb_CType_UInt64: + map_key_coalescer = + &_cel_MessageEqualityState_CoalesceMapKeyInt64UInt64; + break; + default: + return false; + } + break; + case kUpb_CType_UInt32: + switch (rhs_key_def_type) { + case kUpb_CType_Enum: + if (strcmp(upb_EnumDef_FullName(upb_FieldDef_EnumSubDef(rhs_key_def)), + "google.protobuf.NullValue") == 0) { + return false; + } + CEL_ATTRIBUTE_FALLTHROUGH; + case kUpb_CType_Int32: + map_key_coalescer = + &_cel_MessageEqualityState_CoalesceMapKeyUInt32Int32; + break; + case kUpb_CType_Int64: + map_key_coalescer = + &_cel_MessageEqualityState_CoalesceMapKeyUInt32Int64; + break; + case kUpb_CType_UInt32: + map_key_coalescer = &_cel_MessageEqualityState_CoalesceMapKeyIdentity; + break; + case kUpb_CType_UInt64: + map_key_coalescer = + &_cel_MessageEqualityState_CoalesceMapKeyUInt32UInt64; + break; + default: + return false; + } + break; + case kUpb_CType_UInt64: + switch (rhs_key_def_type) { + case kUpb_CType_Enum: + if (strcmp(upb_EnumDef_FullName(upb_FieldDef_EnumSubDef(rhs_key_def)), + "google.protobuf.NullValue") == 0) { + return false; + } + CEL_ATTRIBUTE_FALLTHROUGH; + case kUpb_CType_Int32: + map_key_coalescer = + &_cel_MessageEqualityState_CoalesceMapKeyUInt64Int32; + break; + case kUpb_CType_Int64: + map_key_coalescer = + &_cel_MessageEqualityState_CoalesceMapKeyUInt64Int64; + break; + case kUpb_CType_UInt32: + map_key_coalescer = + &_cel_MessageEqualityState_CoalesceMapKeyUInt64UInt32; + break; + case kUpb_CType_UInt64: + map_key_coalescer = &_cel_MessageEqualityState_CoalesceMapKeyIdentity; + break; + default: + return false; + } + break; + case kUpb_CType_String: + if (rhs_key_def_type != kUpb_CType_String) { + return false; + } + map_key_coalescer = &_cel_MessageEqualityState_CoalesceMapKeyIdentity; + break; + default: + CEL_UNREACHABLE(); + } + + const upb_FieldDef* lhs_value_def = upb_MessageDef_FindFieldByNumber( + lhs_entry_def, kUpb_MapEntry_ValueFieldNumber); + const upb_FieldDef* rhs_value_def = upb_MessageDef_FindFieldByNumber( + lhs_entry_def, kUpb_MapEntry_ValueFieldNumber); + + size_t lhs_iter = kUpb_Map_Begin; + upb_MessageValue lhs_key; + upb_MessageValue lhs_value; + while (upb_Map_Next(lhs_val, &lhs_key, &lhs_value, &lhs_iter)) { + upb_MessageValue rhs_key; + if (!(*map_key_coalescer)(lhs_key, &rhs_key)) { + return false; + } + upb_MessageValue rhs_value; + if (!upb_Map_Get(rhs_val, rhs_key, &rhs_value)) { + return false; + } + if (!_cel_MessageEqualityState_FieldHeteroEquals( + state, lhs_value, lhs_value_def, rhs_value, rhs_value_def, + /*single=*/true)) { + return false; + } + } + return true; +} + +CEL_ATTRIBUTE_NODISCARD +static bool _cel_MessageEqualityState_RepeatedFieldHeteroEquals( + _cel_MessageEqualityState* cel_nonnull state, + const upb_Array* cel_nullable lhs_val, + const upb_FieldDef* cel_nonnull lhs_def, + const upb_Array* cel_nullable rhs_val, + const upb_FieldDef* cel_nonnull rhs_def) { + const size_t lhs_val_size = + lhs_val != cel_nullptr ? upb_Array_Size(lhs_val) : 0; + const size_t rhs_val_size = + rhs_val != cel_nullptr ? upb_Array_Size(rhs_val) : 0; + + if (lhs_val_size != rhs_val_size) { + return false; + } + + if (lhs_val_size == 0) { + return true; + } + + for (size_t i = 0; i < lhs_val_size; ++i) { + if (!_cel_MessageEqualityState_FieldHeteroEquals( + state, upb_Array_Get(lhs_val, i), lhs_def, + upb_Array_Get(rhs_val, i), rhs_def, /*single=*/true)) { + return false; + } + } + return true; +} + +CEL_ATTRIBUTE_NODISCARD +static bool _cel_MessageEqualityState_FieldHeteroEquals( + _cel_MessageEqualityState* cel_nonnull state, upb_MessageValue lhs_val, + const upb_FieldDef* cel_nonnull lhs_def, upb_MessageValue rhs_val, + const upb_FieldDef* cel_nonnull rhs_def, bool single) { + _cel_MessageEqualityField lhs_field; + _cel_MessageEqualityField rhs_field; + + _cel_MessageEqualityState_Field(state, lhs_val, lhs_def, single, &lhs_field); + _cel_MessageEqualityState_Field(state, rhs_val, rhs_def, single, &rhs_field); + + switch (lhs_field.kind) { + case _cel_MessageEqualityFieldKind_kScalar: + if (lhs_field.kind != rhs_field.kind) { + return false; + } + if (lhs_field.data.def.scalar_def != rhs_field.data.def.scalar_def) { + return false; + } + switch (lhs_field.data.def.scalar_def) { + case _cel_MessageEqualityScalarFieldKind_kBool: + return lhs_field.data.val.bool_val == rhs_field.data.val.bool_val; + case _cel_MessageEqualityScalarFieldKind_kNumber: + return _cel_Number_Equals(lhs_field.data.val.num_val, + rhs_field.data.val.num_val); + case _cel_MessageEqualityScalarFieldKind_kString: + CEL_ATTRIBUTE_FALLTHROUGH; + case _cel_MessageEqualityScalarFieldKind_kBytes: + return cel_StringView_Equals(lhs_field.data.val.str_val, + rhs_field.data.val.str_val); + default: + CEL_UNREACHABLE(); + } + case _cel_MessageEqualityFieldKind_kMessage: + if (lhs_field.kind != rhs_field.kind || + lhs_field.data.def.message_def != rhs_field.data.def.message_def) { + return false; + } + return _cel_MessageEqualityState_HomoEquals( + state, lhs_field.data.val.msg_val, rhs_field.data.val.msg_val, + lhs_field.data.def.message_def); + case _cel_MessageEqualityFieldKind_kNullValue: + return lhs_field.kind == rhs_field.kind; + case _cel_MessageEqualityFieldKind_kRepeated: + if (lhs_field.kind != rhs_field.kind) { + return false; + } + return _cel_MessageEqualityState_RepeatedFieldHeteroEquals( + state, lhs_field.data.val.array_val, lhs_field.data.def.field_def, + rhs_field.data.val.array_val, rhs_field.data.def.field_def); + case _cel_MessageEqualityFieldKind_kMap: + if (lhs_field.kind != rhs_field.kind) { + return false; + } + return _cel_MessageEqualityState_MapFieldHeteroEquals( + state, lhs_field.data.val.map_val, lhs_field.data.def.field_def, + rhs_field.data.val.map_val, rhs_field.data.def.field_def); + default: + CEL_UNREACHABLE(); + } +} + +extern "C" _cel_MessageEquality _cel_Message_Equals( + const upb_Message* cel_nonnull lhs_val, + const upb_Message* cel_nonnull rhs_val, + const upb_MessageDef* cel_nonnull val_def, + const upb_DefPool* cel_nonnull def_pool, + const cel_WellKnownTypes* cel_nonnull wkts, + cel_Allocator* cel_nonnull alloc) { + CEL_ASSERT_NOT_NULL(lhs_val); + CEL_ASSERT_NOT_NULL(rhs_val); + CEL_ASSERT_NOT_NULL(val_def); + CEL_ASSERT_NOT_NULL(def_pool); + CEL_ASSERT_NOT_NULL(wkts); + CEL_ASSERT_NOT_NULL(alloc); + + _cel_MessageEqualityState state = { + .def_pool = def_pool, + .wkts = wkts, + .alloc = alloc, + .arena = cel_nullptr, + .depth = 100, + }; + _cel_MessageEqualityState* volatile state_ptr = &state; + if (_cel_setjmp(state_ptr->jmp)) { + CEL_ASSERT_NE(state_ptr->result, _cel_MessageEquality_kEqual); + CEL_ASSERT_NE(state_ptr->result, _cel_MessageEquality_kNotEqual); + } else { + state_ptr->result = _cel_MessageEqualityState_HomoEquals(state_ptr, lhs_val, + rhs_val, val_def) + ? _cel_MessageEquality_kEqual + : _cel_MessageEquality_kNotEqual; + } + cel_Arena_Delete(state_ptr->arena); + return state_ptr->result; +} + +extern "C" _cel_MessageEquality _cel_MessageField_Equals( + upb_MessageValue lhs_val, const upb_FieldDef* cel_nonnull lhs_def, + upb_MessageValue rhs_val, const upb_FieldDef* cel_nonnull rhs_def, + const upb_DefPool* cel_nonnull def_pool, + const cel_WellKnownTypes* cel_nonnull wkts, + cel_Allocator* cel_nonnull alloc) { + CEL_ASSERT_NOT_NULL(lhs_def); + CEL_ASSERT_NOT_NULL(rhs_def); + CEL_ASSERT_NOT_NULL(def_pool); + CEL_ASSERT_NOT_NULL(wkts); + CEL_ASSERT_NOT_NULL(alloc); + + _cel_MessageEqualityState state = { + .def_pool = def_pool, + .wkts = wkts, + .alloc = alloc, + .arena = cel_nullptr, + .depth = 100, + }; + _cel_MessageEqualityState* volatile state_ptr = &state; + if (_cel_setjmp(state_ptr->jmp)) { + CEL_ASSERT_NE(state_ptr->result, _cel_MessageEquality_kEqual); + CEL_ASSERT_NE(state_ptr->result, _cel_MessageEquality_kNotEqual); + } else { + state_ptr->result = + _cel_MessageEqualityState_FieldHeteroEquals( + state_ptr, lhs_val, lhs_def, rhs_val, rhs_def, /*single=*/false) + ? _cel_MessageEquality_kEqual + : _cel_MessageEquality_kNotEqual; + } + cel_Arena_Delete(state_ptr->arena); + return state_ptr->result; +} diff --git a/cel-c/src/mutable_list_value.cc b/cel-c/src/mutable_list_value.cc new file mode 100644 index 0000000..2da0489 --- /dev/null +++ b/cel-c/src/mutable_list_value.cc @@ -0,0 +1,366 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "cel-c/src/mutable_list_value.h" + +#include +#include +#include +#include + +#include "cel-c/alloc.h" +#include "cel-c/arena.h" +#include "cel-c/assert.h" +#include "cel-c/config.h" +#include "cel-c/error.h" +#include "cel-c/error_code.h" +#include "cel-c/src/ckdint.h" +#include "cel-c/src/empty_list_value.h" +#include "cel-c/status.h" +#include "cel-c/string_view.h" +#include "cel-c/value.h" + +extern const cel_ListValueIteratorVTable _cel_MutableListValueIteratorVTable; + +typedef struct { + union { + struct { + cel_Value* cel_nullable ptr; + uint32_t len; + uint32_t cap; + }; + cel_ValueContent super; + }; +} _cel_MutableListValueContent; + +typedef struct { + cel_ListValueIterator super; + cel_Allocator* cel_nonnull alloc; + cel_Value* cel_nonnull ptr; + uint32_t len; + uint32_t idx; +} _cel_MutableListValueIterator; + +#define _cel_MutableListValueContent_kPtrOffset 0 +#define _cel_MutableListValueContent_kLenOffset \ + (sizeof(void*) / sizeof(uint32_t)) +#define _cel_MutableListValueContent_kCapOffset \ + (_cel_MutableListValueContent_kLenOffset + 1) + +CEL_STATIC_ASSERT(sizeof(_cel_MutableListValueContent) <= + sizeof(cel_ValueContent)); +CEL_STATIC_ASSERT(alignof(_cel_MutableListValueContent) <= + alignof(cel_ValueContent)); + +static bool _cel_MutableListValue_FastSize( + const cel_ListValueVTable* cel_nonnull vtable, cel_ValueContent content, + size_t* cel_nonnull size) { + CEL_ASSERT_EQ(vtable, &_cel_MutableListValueVTable); + CEL_ASSERT_NOT_NULL(size); + + *size = content.u32[_cel_MutableListValueContent_kLenOffset]; + return true; +} + +static bool _cel_MutableListValue_SlowSize( + const cel_ListValueVTable* cel_nonnull vtable, cel_ValueContent content, + const cel_ValueContext* cel_nonnull context, cel_Value* cel_nonnull size, + cel_Status* cel_nonnull status) { + CEL_ASSERT_EQ(vtable, &_cel_MutableListValueVTable); + CEL_ASSERT_NOT_NULL(context); + CEL_ASSERT_NOT_NULL(size); + CEL_ASSERT_NOT_NULL(status); + + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return false; + } + + cel_Value_SetInt(size, content.u32[_cel_MutableListValueContent_kLenOffset]); + return true; +} + +static bool _cel_MutableListValue_Get( + const cel_ListValueVTable* cel_nonnull vtable, cel_ValueContent content, + const cel_ValueContext* cel_nonnull context, size_t index, + cel_Value* cel_nonnull element, cel_Status* cel_nonnull status) { + CEL_ASSERT_EQ(vtable, &_cel_MutableListValueVTable); + CEL_ASSERT_NOT_NULL(context); + CEL_ASSERT_NOT_NULL(element); + CEL_ASSERT_NOT_NULL(status); + + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return false; + } + + uint32_t len = content.u32[_cel_MutableListValueContent_kLenOffset]; + if (CEL_LIKELY(index < len)) { + const cel_Value* ptr = + (cel_Value*)content.ptr[_cel_MutableListValueContent_kPtrOffset]; + *element = ptr[index]; + return true; + } + + cel_Error* error = cel_Error_New(context->arena); + if (CEL_UNLIKELY(error == cel_nullptr)) { + cel_OutOfMemoryStatus(status); + return false; + } + cel_Error_SetCanonicalCode(error, cel_ErrorCode_kOutOfRange); + cel_Error_SetMessage(error, cel_StringView_From("index out of range")); + cel_Value_SetError(element, error); + return true; +} + +extern "C" bool _cel_MutableListValue_Reserve( + cel_ListValue* cel_nonnull list_value, uint32_t size, + cel_Arena* cel_nonnull arena) { + CEL_ASSERT_NOT_NULL(list_value); + CEL_ASSERT(_cel_ListValue_IsMutable(list_value)); + CEL_ASSERT_NOT_NULL(arena); + + uint32_t cap = + list_value->content.u32[_cel_MutableListValueContent_kCapOffset]; + if (size <= cap) { + return true; + } + + if (size <= 8) { + size = 8; + } + cel_Value* ptr = + (cel_Value*) + list_value->content.ptr[_cel_MutableListValueContent_kPtrOffset]; + size_t new_cap_bytes; + if (_cel_ckd_mul(&new_cap_bytes, (size_t)size, sizeof(cel_Value))) { + return false; + } + cel_Value* new_ptr = (cel_Value*)cel_Arena_Realloc( + arena, ptr, (size_t)cap * sizeof(cel_Value), new_cap_bytes, cel_nullptr); + if (CEL_UNLIKELY(new_ptr == cel_nullptr)) { + return false; + } + list_value->content.ptr[_cel_MutableListValueContent_kPtrOffset] = new_ptr; + list_value->content.u32[_cel_MutableListValueContent_kCapOffset] = size; + return true; +} + +extern "C" cel_Value* cel_nullable +_cel_MutableListValue_AddN(cel_ListValue* cel_nonnull list_value, uint32_t size, + cel_Arena* cel_nonnull arena) { + CEL_ASSERT_NOT_NULL(list_value); + CEL_ASSERT(_cel_ListValue_IsMutable(list_value)); + CEL_ASSERT_NOT_NULL(arena); + + uint32_t len = + list_value->content.u32[_cel_MutableListValueContent_kLenOffset]; + uint32_t cap = + list_value->content.u32[_cel_MutableListValueContent_kCapOffset]; + cel_Value* ptr = + (cel_Value*) + list_value->content.ptr[_cel_MutableListValueContent_kPtrOffset]; + if (CEL_UNLIKELY(size > len || len > cap - size)) { + if (cap == UINT32_MAX) { + return cel_nullptr; + } + uint32_t min_cap; + if (_cel_ckd_add(&min_cap, len, size)) { + return cel_nullptr; + } + uint32_t new_cap; + if (_cel_ckd_mul(&new_cap, cap, (uint32_t)2)) { + new_cap = UINT32_MAX; + } + if (new_cap < 8) { + new_cap = 8; + } + while (new_cap < min_cap) { + if (_cel_ckd_mul(&new_cap, new_cap, (uint32_t)2)) { + new_cap = UINT32_MAX; + break; + } + } + size_t new_cap_bytes; + if (_cel_ckd_mul(&new_cap_bytes, (size_t)new_cap, sizeof(cel_Value))) { + return cel_nullptr; + } + ptr = (cel_Value*)cel_Arena_Realloc(arena, ptr, + (size_t)cap * sizeof(cel_Value), + new_cap_bytes, cel_nullptr); + if (CEL_UNLIKELY(ptr == cel_nullptr)) { + return cel_nullptr; + } + list_value->content.ptr[_cel_MutableListValueContent_kPtrOffset] = ptr; + list_value->content.u32[_cel_MutableListValueContent_kCapOffset] = new_cap; + } + list_value->content.u32[_cel_MutableListValueContent_kLenOffset] = len + size; + return ptr + len; +} + +static cel_ListValueIterator* cel_nullable _cel_MutableListValue_NewIterator( + const cel_ListValueVTable* cel_nonnull vtable, cel_ValueContent content, + const cel_ValueContext* cel_nonnull context, + cel_Status* cel_nonnull status) { + CEL_ASSERT_EQ(vtable, &_cel_MutableListValueVTable); + CEL_ASSERT_NOT_NULL(context); + CEL_ASSERT_NOT_NULL(status); + + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return cel_nullptr; + } + + uint32_t len = content.u32[_cel_MutableListValueContent_kLenOffset]; + if (len == 0) { + return &_cel_EmptyListValueIterator; + } + + _cel_MutableListValueIterator* iter = + (_cel_MutableListValueIterator*)cel_Allocator_Malloc( + context->alloc, sizeof(_cel_MutableListValueIterator), cel_nullptr); + if (CEL_UNLIKELY(iter == cel_nullptr)) { + cel_OutOfMemoryStatus(status); + return cel_nullptr; + } + iter->super.vtable = &_cel_MutableListValueIteratorVTable; + iter->alloc = context->alloc; + iter->ptr = (cel_Value*)content.ptr[_cel_MutableListValueContent_kPtrOffset]; + iter->len = len; + iter->idx = 0; + + return &iter->super; +} + +extern "C" const cel_ListValueVTable _cel_MutableListValueVTable = { + .Equals = cel_nullptr, + .FastSize = &_cel_MutableListValue_FastSize, + .SlowSize = &_cel_MutableListValue_SlowSize, + .Get = &_cel_MutableListValue_Get, + .NewIterator = &_cel_MutableListValue_NewIterator, +}; + +static void _cel_MutableListValueIterator_Delete( + cel_ValueIterator* cel_nonnull iterator) { + CEL_ASSERT_NOT_NULL(iterator); + CEL_ASSERT_EQ( + iterator->vtable, + (const cel_ValueIteratorVTable*)&_cel_MutableListValueIteratorVTable); + + _cel_MutableListValueIterator* iter = + cel_containerof(iterator, _cel_MutableListValueIterator, super); + cel_Allocator_FreeSized(iter->alloc, iter, sizeof(*iter)); +} + +static bool _cel_MutableListValueIterator_Next1( + cel_ValueIterator* cel_nonnull iterator, + const cel_ValueContext* cel_nonnull context, + cel_Value* cel_nonnull key_or_value, cel_Status* cel_nonnull status) { + CEL_ASSERT_NOT_NULL(iterator); + CEL_ASSERT_EQ(iterator->vtable, &_cel_MutableListValueIteratorVTable.super); + CEL_ASSERT_NOT_NULL(context); + CEL_ASSERT_NOT_NULL(key_or_value); + CEL_ASSERT_NOT_NULL(status); + + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return false; + } + + _cel_MutableListValueIterator* iter = + cel_containerof(iterator, _cel_MutableListValueIterator, super); + if (CEL_UNLIKELY(iter->idx >= iter->len)) { + return false; + } + + *key_or_value = iter->ptr[iter->idx]; + ++iter->idx; + return true; +} + +static bool _cel_MutableListValueIterator_Next2( + cel_ValueIterator* cel_nonnull iterator, + const cel_ValueContext* cel_nonnull context, cel_Value* cel_nonnull key, + cel_Value* cel_nonnull value, cel_Status* cel_nonnull status) { + CEL_ASSERT_NOT_NULL(iterator); + CEL_ASSERT_EQ(iterator->vtable, &_cel_MutableListValueIteratorVTable.super); + CEL_ASSERT_NOT_NULL(context); + CEL_ASSERT_NOT_NULL(key); + CEL_ASSERT_NOT_NULL(value); + CEL_ASSERT_NOT_NULL(status); + + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return false; + } + + _cel_MutableListValueIterator* iter = + cel_containerof(iterator, _cel_MutableListValueIterator, super); + if (CEL_UNLIKELY(iter->idx >= iter->len)) { + return false; + } + + cel_Value_SetInt(key, iter->idx); + *value = iter->ptr[iter->idx]; + ++iter->idx; + return true; +} + +static bool _cel_MutableListValueIterator_Remaining( + const cel_ValueIterator* cel_nonnull iterator, + size_t* cel_nonnull remaining) { + CEL_ASSERT_NOT_NULL(iterator); + CEL_ASSERT_EQ(iterator->vtable, &_cel_MutableListValueIteratorVTable.super); + CEL_ASSERT_NOT_NULL(remaining); + + const _cel_MutableListValueIterator* iter = + cel_containerof(iterator, _cel_MutableListValueIterator, super); + + *remaining = iter->len - iter->idx; + return true; +} + +static bool _cel_MutableListValueIterator_Next( + cel_ListValueIterator* cel_nonnull iterator, + const cel_ValueContext* cel_nonnull context, size_t* cel_nullable index, + cel_Value* cel_nonnull value, cel_Status* cel_nonnull status) { + CEL_ASSERT_NOT_NULL(iterator); + CEL_ASSERT_EQ(iterator->vtable, &_cel_MutableListValueIteratorVTable); + CEL_ASSERT_NOT_NULL(context); + CEL_ASSERT_NOT_NULL(value); + CEL_ASSERT_NOT_NULL(status); + + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return false; + } + + _cel_MutableListValueIterator* iter = + cel_containerof(iterator, _cel_MutableListValueIterator, super); + if (CEL_UNLIKELY(iter->idx >= iter->len)) { + return false; + } + + if (index != cel_nullptr) { + *index = iter->idx; + } + *value = iter->ptr[iter->idx]; + ++iter->idx; + return true; +} + +const cel_ListValueIteratorVTable _cel_MutableListValueIteratorVTable = { + .super = + { + .Delete = &_cel_MutableListValueIterator_Delete, + .Next1 = &_cel_MutableListValueIterator_Next1, + .Next2 = &_cel_MutableListValueIterator_Next2, + .Remaining = &_cel_MutableListValueIterator_Remaining, + }, + .Next = &_cel_MutableListValueIterator_Next, +}; diff --git a/cel-c/src/mutable_map_value.cc b/cel-c/src/mutable_map_value.cc new file mode 100644 index 0000000..0b015d6 --- /dev/null +++ b/cel-c/src/mutable_map_value.cc @@ -0,0 +1,563 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "cel-c/src/mutable_map_value.h" + +#include +#include +#include +#include +#include + +#include "cel-c/alloc.h" +#include "cel-c/arena.h" +#include "cel-c/assert.h" +#include "cel-c/config.h" +#include "cel-c/error.h" +#include "cel-c/error_code.h" +#include "cel-c/hash.h" +#include "cel-c/src/bit.h" +#include "cel-c/src/empty_map_value.h" +#include "cel-c/src/map_value.h" +#include "cel-c/status.h" +#include "cel-c/string_view.h" +#include "cel-c/value.h" + +extern const cel_MapValueIteratorVTable _cel_MutableMapValueIteratorVTable; + +typedef struct { + cel_MapValueKey key; + cel_Value value; +} _cel_MutableMapValueEntry; + +typedef struct { + union { + struct { + // ptr points to a memory block which starts with a bitset and pointers to + // the entries immediately follow. + _cel_MutableMapValueEntry * cel_nullable * cel_nullable ptr; + uint32_t len; + // cap is always a power of 2 + uint32_t cap; + }; + cel_ValueContent super; + }; +} _cel_MutableMapValueContent; + +typedef struct { + cel_MapValueIterator super; + cel_Allocator* cel_nonnull alloc; + _cel_MutableMapValueEntry * cel_nullable * cel_nonnull ents; + uint32_t len; + uint32_t cap; + uint32_t bit; + uint32_t rem; +} _cel_MutableMapValueIterator; + +CEL_ATTRIBUTE_NODISCARD +static bool _cel_MutableMapValueIterator_Done( + const _cel_MutableMapValueIterator* cel_nonnull iter) { + return iter->rem == 0; +} + +static size_t _cel_MutableMapValueIterator_Advance( + const _cel_MutableMapValueIterator* cel_nonnull iter) { + CEL_ASSERT_NOT(_cel_MutableMapValueIterator_Done(iter)); + size_t bit; + for (bit = iter->bit + 1; iter->ents[bit] == cel_nullptr; ++bit) { + } + return bit; +} + +#define _cel_MutableMapValueContent_kPtrOffset 0 +#define _cel_MutableMapValueContent_kLenOffset \ + (sizeof(void*) / sizeof(uint32_t)) +#define _cel_MutableMapValueContent_kCapOffset \ + (_cel_MutableMapValueContent_kLenOffset + 1) + +CEL_STATIC_ASSERT(sizeof(_cel_MutableMapValueContent) <= + sizeof(cel_ValueContent)); +CEL_STATIC_ASSERT(alignof(_cel_MutableMapValueContent) <= + alignof(cel_ValueContent)); + +static bool _cel_MutableMapValue_FastSize( + const cel_MapValueVTable* cel_nonnull vtable, cel_ValueContent content, + size_t* cel_nonnull size) { + CEL_ASSERT_EQ(vtable, &_cel_MutableMapValueVTable); + CEL_ASSERT_NOT_NULL(size); + + *size = content.u32[_cel_MutableMapValueContent_kLenOffset]; + return true; +} + +static bool _cel_MutableMapValue_SlowSize( + const cel_MapValueVTable* cel_nonnull vtable, cel_ValueContent content, + const cel_ValueContext* cel_nonnull context, cel_Value* cel_nonnull size, + cel_Status* cel_nonnull status) { + CEL_ASSERT_EQ(vtable, &_cel_MutableMapValueVTable); + CEL_ASSERT_NOT_NULL(context); + CEL_ASSERT_NOT_NULL(size); + CEL_ASSERT_NOT_NULL(status); + + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return false; + } + + cel_Value_SetInt(size, content.u32[_cel_MutableMapValueContent_kLenOffset]); + return true; +} + +static bool _cel_MutableMapValue_Get( + const cel_MapValueVTable* cel_nonnull vtable, cel_ValueContent content, + const cel_ValueContext* cel_nonnull context, + const cel_MapValueKey* cel_nonnull key, cel_Value* cel_nonnull value, + cel_Status* cel_nonnull status) { + CEL_ASSERT_EQ(vtable, &_cel_MutableMapValueVTable); + CEL_ASSERT_NOT_NULL(context); + CEL_ASSERT_NOT_NULL(key); + CEL_ASSERT_NOT_NULL(value); + CEL_ASSERT_NOT_NULL(status); + + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return false; + } + + uint32_t len = content.u32[_cel_MutableMapValueContent_kLenOffset]; + if (len != 0) { + _cel_MutableMapValueEntry** ents = + (_cel_MutableMapValueEntry**) + content.ptr[_cel_MutableMapValueContent_kPtrOffset]; + const size_t mask = + (size_t)content.u32[_cel_MutableMapValueContent_kCapOffset] - 1; + size_t bit = cel_HashState_Finalize( + _cel_MapValueKey_Hash(key, cel_HashState_Initialize())) & + mask; + _cel_MutableMapValueEntry* ent; + for (ent = ents[bit]; ent != cel_nullptr; ent = ents[(++bit) & mask]) { + if (_cel_MapValueKey_Equals(&ent->key, key)) { + *value = ent->value; + return true; + } + } + } + + cel_Error* error = cel_Error_New(context->arena); + if (CEL_UNLIKELY(error == cel_nullptr)) { + cel_OutOfMemoryStatus(status); + return false; + } + cel_Error_SetCanonicalCode(error, cel_ErrorCode_kNotFound); + cel_Error_SetMessage(error, cel_StringView_From("no such key")); + cel_Value_SetError(value, error); + return true; +} + +static bool _cel_MutableMapValue_Find( + const cel_MapValueVTable* cel_nonnull vtable, cel_ValueContent content, + const cel_ValueContext* cel_nonnull context, + const cel_MapValueKey* cel_nonnull key, cel_Value* cel_nonnull value, + cel_Status* cel_nonnull status) { + CEL_ASSERT_EQ(vtable, &_cel_MutableMapValueVTable); + CEL_ASSERT_NOT_NULL(context); + CEL_ASSERT_NOT_NULL(key); + CEL_ASSERT_NOT_NULL(value); + CEL_ASSERT_NOT_NULL(status); + + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return false; + } + + uint32_t len = content.u32[_cel_MutableMapValueContent_kLenOffset]; + if (len != 0) { + _cel_MutableMapValueEntry** ents = + (_cel_MutableMapValueEntry**) + content.ptr[_cel_MutableMapValueContent_kPtrOffset]; + const size_t mask = + (size_t)content.u32[_cel_MutableMapValueContent_kCapOffset] - 1; + size_t bit = cel_HashState_Finalize( + _cel_MapValueKey_Hash(key, cel_HashState_Initialize())) & + mask; + _cel_MutableMapValueEntry* ent; + for (ent = ents[bit]; ent != cel_nullptr; ent = ents[(++bit) & mask]) { + if (_cel_MapValueKey_Equals(&ent->key, key)) { + *value = ent->value; + return true; + } + } + } + + return false; +} + +static bool _cel_MutableMapValue_Has( + const cel_MapValueVTable* cel_nonnull vtable, cel_ValueContent content, + const cel_ValueContext* cel_nonnull context, + const cel_MapValueKey* cel_nonnull key, cel_Value* cel_nonnull result, + cel_Status* cel_nonnull status) { + CEL_ASSERT_EQ(vtable, &_cel_MutableMapValueVTable); + CEL_ASSERT_NOT_NULL(context); + CEL_ASSERT_NOT_NULL(key); + CEL_ASSERT_NOT_NULL(result); + CEL_ASSERT_NOT_NULL(status); + + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return false; + } + + uint32_t len = content.u32[_cel_MutableMapValueContent_kLenOffset]; + if (len != 0) { + _cel_MutableMapValueEntry** ents = + (_cel_MutableMapValueEntry**) + content.ptr[_cel_MutableMapValueContent_kPtrOffset]; + const size_t mask = + (size_t)content.u32[_cel_MutableMapValueContent_kCapOffset] - 1; + size_t bit = cel_HashState_Finalize( + _cel_MapValueKey_Hash(key, cel_HashState_Initialize())) & + mask; + _cel_MutableMapValueEntry* ent; + for (ent = ents[bit]; ent != cel_nullptr; ent = ents[(++bit) & mask]) { + if (_cel_MapValueKey_Equals(&ent->key, key)) { + cel_Value_SetTrue(result); + return true; + } + } + } + + cel_Value_SetFalse(result); + return true; +} + +static void _cel_MutableMapValue_Rehash(_cel_MutableMapValueEntry** new_ents, + size_t new_cap, + _cel_MutableMapValueEntry** old_ents, + size_t old_cap, size_t len) { + const size_t new_mask = new_cap - 1; + const cel_HashState state = cel_HashState_Initialize(); + size_t old_bit; + for (old_bit = 0; len > 0; ++old_bit) { + _cel_MutableMapValueEntry* ent = old_ents[old_bit]; + if (ent == cel_nullptr) { + continue; + } + size_t new_bit = + cel_HashState_Finalize(_cel_MapValueKey_Hash(&ent->key, state)) & + new_mask; + for (; new_ents[new_bit] != cel_nullptr; + new_bit = (new_bit + 1) & new_mask) { + } + new_ents[new_bit] = ent; + --len; + } +} + +static bool _cel_MutableMapValue_Resize(cel_MapValue* cel_nonnull map_value, + _cel_MutableMapValueEntry** old_ents, + uint32_t new_cap, uint32_t old_cap, + uint32_t len, + cel_Arena* cel_nonnull arena) { + CEL_ASSERT(new_cap > old_cap && _cel_has_single_bit(new_cap) && + (old_cap == 0 || _cel_has_single_bit(old_cap))); + _cel_MutableMapValueEntry** new_ents = + (_cel_MutableMapValueEntry**)cel_Arena_Malloc( + arena, sizeof(_cel_MutableMapValueEntry) * new_cap, cel_nullptr); + if (CEL_UNLIKELY(new_ents == cel_nullptr)) { + return false; + } + memset(new_ents, 0, sizeof(_cel_MutableMapValueEntry) * new_cap); + _cel_MutableMapValue_Rehash(new_ents, new_cap, old_ents, old_cap, len); + map_value->content.ptr[_cel_MutableMapValueContent_kPtrOffset] = new_ents; + map_value->content.u32[_cel_MutableMapValueContent_kCapOffset] = new_cap; + return true; +} + +extern "C" bool _cel_MutableMapValue_Reserve( + cel_MapValue* cel_nonnull map_value, uint32_t size, + cel_Arena* cel_nonnull arena) { + CEL_ASSERT_NOT_NULL(map_value); + CEL_ASSERT_EQ(map_value->vtable, &_cel_MutableMapValueVTable); + CEL_ASSERT_NOT_NULL(arena); + + uint32_t cap = map_value->content.u32[_cel_MutableMapValueContent_kCapOffset]; + if (cap >= size) { + return true; + } + if (size > (((uint32_t)1) << ((sizeof(uint32_t) * CHAR_BIT) - 1))) { + return false; + } + size = _cel_bit_ceil(size); + if (size < 16) { + size = 16; + } + return _cel_MutableMapValue_Resize( + map_value, + (_cel_MutableMapValueEntry**) + map_value->content.ptr[_cel_MutableMapValueContent_kPtrOffset], + _cel_bit_ceil(size), cap, + map_value->content.u32[_cel_MutableMapValueContent_kLenOffset], arena); +} + +extern "C" _cel_MutableMapValueInsertResult _cel_MutableMapValue_Insert( + cel_MapValue* cel_nonnull map_value, const cel_MapValueKey* cel_nonnull key, + cel_MapValueKey * cel_nullable * cel_nullable out_key, + cel_Value * cel_nullable * cel_nonnull out_value, + cel_Arena* cel_nonnull arena) { + CEL_ASSERT_NOT_NULL(map_value); + CEL_ASSERT_EQ(map_value->vtable, &_cel_MutableMapValueVTable); + CEL_ASSERT_NOT_NULL(key); + CEL_ASSERT_NOT_NULL(out_value); + CEL_ASSERT_NOT_NULL(arena); + + uint32_t cap = map_value->content.u32[_cel_MutableMapValueContent_kCapOffset]; + uint32_t len; + if (CEL_UNLIKELY(cap == 0)) { + cap = 16; + len = 0; + _cel_MutableMapValueEntry** ents = + (_cel_MutableMapValueEntry**)cel_Arena_Malloc( + arena, sizeof(_cel_MutableMapValueEntry) * cap, cel_nullptr); + if (CEL_UNLIKELY(ents == cel_nullptr)) { + return _cel_MutableMapValueInsertResult_kOutOfMemory; + } + memset(ents, 0, sizeof(_cel_MutableMapValueEntry) * cap); + map_value->content.ptr[_cel_MutableMapValueContent_kPtrOffset] = ents; + map_value->content.u32[_cel_MutableMapValueContent_kCapOffset] = cap; + } else { + len = map_value->content.u32[_cel_MutableMapValueContent_kLenOffset]; + } + _cel_MutableMapValueEntry** ents = + (_cel_MutableMapValueEntry**) + map_value->content.ptr[_cel_MutableMapValueContent_kPtrOffset]; + const size_t mask = + (size_t)map_value->content.u32[_cel_MutableMapValueContent_kCapOffset] - + 1; + size_t bit = cel_HashState_Finalize( + _cel_MapValueKey_Hash(key, cel_HashState_Initialize())) & + mask; + _cel_MutableMapValueEntry* ent; + for (ent = ents[bit]; ent != cel_nullptr; ent = ents[(++bit) & mask]) { + if (_cel_MapValueKey_Equals(&ent->key, key)) { + if (out_key != cel_nullptr) { + *out_key = &ent->key; + } + *out_value = &ent->value; + return _cel_MutableMapValueInsertResult_kReplaced; + } + } + size_t threshold = (size_t)cap * 2 / 3; + if (len > threshold) { + // Refuse insertion. We must have failed to resize before. + return _cel_MutableMapValueInsertResult_kOutOfMemory; + } + ent = reinterpret_cast<_cel_MutableMapValueEntry*>( + cel_Arena_Malloc(arena, sizeof(_cel_MutableMapValueEntry), cel_nullptr)); + if (CEL_UNLIKELY(ent == cel_nullptr)) { + return _cel_MutableMapValueInsertResult_kOutOfMemory; + } + ent->key = *key; + if (out_key != cel_nullptr) { + *out_key = &ent->key; + } + *out_value = &ent->value; + ents[bit] = ent; + map_value->content.u32[_cel_MutableMapValueContent_kLenOffset] = ++len; + + if (len > threshold) { + (void)_cel_MutableMapValue_Resize(map_value, ents, cap << 1, cap, len, + arena); + } + + return _cel_MutableMapValueInsertResult_kInserted; +} + +static cel_MapValueIterator* cel_nullable _cel_MutableMapValue_NewIterator( + const cel_MapValueVTable* cel_nonnull vtable, cel_ValueContent content, + const cel_ValueContext* cel_nonnull context, + cel_Status* cel_nonnull status) { + CEL_ASSERT_EQ(vtable, &_cel_MutableMapValueVTable); + CEL_ASSERT_NOT_NULL(context); + CEL_ASSERT_NOT_NULL(status); + + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return cel_nullptr; + } + + uint32_t len = content.u32[_cel_MutableMapValueContent_kLenOffset]; + if (len == 0) { + return &_cel_EmptyMapValueIterator; + } + + _cel_MutableMapValueIterator* iter = + (_cel_MutableMapValueIterator*)cel_Allocator_Malloc( + context->alloc, sizeof(_cel_MutableMapValueIterator), cel_nullptr); + if (CEL_UNLIKELY(iter == cel_nullptr)) { + cel_OutOfMemoryStatus(status); + return cel_nullptr; + } + + _cel_MutableMapValueEntry** ents = + (_cel_MutableMapValueEntry**) + content.ptr[_cel_MutableMapValueContent_kPtrOffset]; + iter->super.vtable = &_cel_MutableMapValueIteratorVTable; + iter->alloc = context->alloc; + iter->ents = ents; + iter->len = iter->rem = len; + iter->cap = content.u32[_cel_MutableMapValueContent_kCapOffset]; + + size_t bit; + for (bit = 0; ents[bit] == cel_nullptr; ++bit) { + } + iter->bit = bit; + + return &iter->super; +} + +extern "C" const cel_MapValueVTable _cel_MutableMapValueVTable = { + .Equals = cel_nullptr, + .FastSize = &_cel_MutableMapValue_FastSize, + .SlowSize = &_cel_MutableMapValue_SlowSize, + .Get = &_cel_MutableMapValue_Get, + .Find = &_cel_MutableMapValue_Find, + .Has = &_cel_MutableMapValue_Has, + .NewIterator = &_cel_MutableMapValue_NewIterator, +}; + +static void _cel_MutableMapValueIterator_Delete( + cel_ValueIterator* cel_nonnull iterator) { + CEL_ASSERT_NOT_NULL(iterator); + CEL_ASSERT_EQ( + iterator->vtable, + (const cel_ValueIteratorVTable*)&_cel_MutableMapValueIteratorVTable); + + _cel_MutableMapValueIterator* iter = + cel_containerof(iterator, _cel_MutableMapValueIterator, super); + cel_Allocator_FreeSized(iter->alloc, iter, sizeof(*iter)); +} + +static bool _cel_MutableMapValueIterator_Next1( + cel_ValueIterator* cel_nonnull iterator, + const cel_ValueContext* cel_nonnull context, + cel_Value* cel_nonnull key_or_value, cel_Status* cel_nonnull status) { + CEL_ASSERT_NOT_NULL(iterator); + CEL_ASSERT_EQ(iterator->vtable, &_cel_MutableMapValueIteratorVTable.super); + CEL_ASSERT_NOT_NULL(context); + CEL_ASSERT_NOT_NULL(key_or_value); + CEL_ASSERT_NOT_NULL(status); + + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return false; + } + + _cel_MutableMapValueIterator* iter = + cel_containerof(iterator, _cel_MutableMapValueIterator, super); + if (_cel_MutableMapValueIterator_Done(iter)) { + return false; + } + + _cel_Value_SetMapValueKey(key_or_value, &iter->ents[iter->bit]->key); + --iter->rem; + if (iter->rem != 0) { + iter->bit = _cel_MutableMapValueIterator_Advance(iter); + } + return true; +} + +static bool _cel_MutableMapValueIterator_Next2( + cel_ValueIterator* cel_nonnull iterator, + const cel_ValueContext* cel_nonnull context, cel_Value* cel_nonnull key, + cel_Value* cel_nonnull value, cel_Status* cel_nonnull status) { + CEL_ASSERT_NOT_NULL(iterator); + CEL_ASSERT_EQ(iterator->vtable, &_cel_MutableMapValueIteratorVTable.super); + CEL_ASSERT_NOT_NULL(context); + CEL_ASSERT_NOT_NULL(key); + CEL_ASSERT_NOT_NULL(value); + CEL_ASSERT_NOT_NULL(status); + + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return false; + } + + _cel_MutableMapValueIterator* iter = + cel_containerof(iterator, _cel_MutableMapValueIterator, super); + if (_cel_MutableMapValueIterator_Done(iter)) { + return false; + } + + _cel_MutableMapValueEntry* ent = iter->ents[iter->bit]; + _cel_Value_SetMapValueKey(key, &ent->key); + *value = ent->value; + --iter->rem; + if (iter->rem != 0) { + iter->bit = _cel_MutableMapValueIterator_Advance(iter); + } + return true; +} + +static bool _cel_MutableMapValueIterator_Remaining( + const cel_ValueIterator* cel_nonnull iterator, + size_t* cel_nonnull remaining) { + CEL_ASSERT_NOT_NULL(iterator); + CEL_ASSERT_EQ(iterator->vtable, &_cel_MutableMapValueIteratorVTable.super); + CEL_ASSERT_NOT_NULL(remaining); + + _cel_MutableMapValueIterator* iter = + cel_containerof(iterator, _cel_MutableMapValueIterator, super); + + *remaining = iter->rem; + return true; +} + +static bool _cel_MutableMapValueIterator_Next( + cel_MapValueIterator* cel_nonnull iterator, + const cel_ValueContext* cel_nonnull context, + cel_MapValueKey* cel_nullable key, cel_Value* cel_nonnull value, + cel_Status* cel_nonnull status) { + CEL_ASSERT_NOT_NULL(iterator); + CEL_ASSERT_EQ(iterator->vtable, &_cel_MutableMapValueIteratorVTable); + CEL_ASSERT_NOT_NULL(context); + CEL_ASSERT_NOT_NULL(key); + CEL_ASSERT_NOT_NULL(value); + CEL_ASSERT_NOT_NULL(status); + + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return false; + } + + _cel_MutableMapValueIterator* iter = + cel_containerof(iterator, _cel_MutableMapValueIterator, super); + if (_cel_MutableMapValueIterator_Done(iter)) { + return false; + } + + _cel_MutableMapValueEntry* ent = iter->ents[iter->bit]; + if (key != cel_nullptr) { + *key = ent->key; + } + *value = ent->value; + --iter->rem; + if (iter->rem != 0) { + iter->bit = _cel_MutableMapValueIterator_Advance(iter); + } + return true; +} + +const cel_MapValueIteratorVTable _cel_MutableMapValueIteratorVTable = { + .super = + { + .Delete = &_cel_MutableMapValueIterator_Delete, + .Next1 = &_cel_MutableMapValueIterator_Next1, + .Next2 = &_cel_MutableMapValueIterator_Next2, + .Remaining = &_cel_MutableMapValueIterator_Remaining, + }, + .Next = &_cel_MutableMapValueIterator_Next, +}; diff --git a/cel-c/src/opaque_value.cc b/cel-c/src/opaque_value.cc new file mode 100644 index 0000000..822fa3a --- /dev/null +++ b/cel-c/src/opaque_value.cc @@ -0,0 +1,66 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include + +#include "cel-c/assert.h" +#include "cel-c/config.h" +#include "cel-c/status.h" +#include "cel-c/string_view.h" +#include "cel-c/value.h" + +extern "C" bool cel_OpaqueValue_Equals( + const cel_OpaqueValue* cel_nonnull opaque_value, + const cel_ValueContext* cel_nonnull context, + const cel_OpaqueValue* cel_nonnull other, cel_Value* cel_nonnull result, + cel_Status* cel_nonnull status) { + CEL_ASSERT_NOT_NULL(opaque_value); + CEL_ASSERT_NOT_NULL(context); + CEL_ASSERT_NOT_NULL(other); + CEL_ASSERT_NOT_NULL(result); + CEL_ASSERT_NOT_NULL(status); + + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return false; + } + + if (cel_StringView_Equals(cel_OpaqueValue_TypeName(opaque_value), + cel_OpaqueValue_TypeName(other))) { + if (opaque_value->vtable->Equals != cel_nullptr) { + if ((*opaque_value->vtable->Equals)(opaque_value->vtable, + opaque_value->content, context, other, + result, status)) { + return true; + } + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return false; + } + } + if (other->vtable->Equals != cel_nullptr && + other->vtable->Equals != opaque_value->vtable->Equals) { + if ((*other->vtable->Equals)(other->vtable, other->content, context, + opaque_value, result, status)) { + return true; + } + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return false; + } + } + } + + cel_Value_SetFalse(result); + return true; +} diff --git a/cel-c/src/optional_value.cc b/cel-c/src/optional_value.cc new file mode 100644 index 0000000..23ad29a --- /dev/null +++ b/cel-c/src/optional_value.cc @@ -0,0 +1,625 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include + +#include "cel-c/arena.h" +#include "cel-c/assert.h" +#include "cel-c/config.h" +#include "cel-c/duration.h" +#include "cel-c/error.h" +#include "cel-c/error_code.h" +#include "cel-c/status.h" +#include "cel-c/string_view.h" +#include "cel-c/timestamp.h" +#include "cel-c/value.h" +#include "cel-c/value_kind.h" + +extern const cel_OptionalValueVTable _cel_OptionalValueVTables[11]; + +#define _cel_EmptyOptionalValueVTable _cel_OptionalValueVTables[0] +#define _cel_NullOptionalValueVTable _cel_OptionalValueVTables[1] +#define _cel_BoolOptionalValueVTable _cel_OptionalValueVTables[2] +#define _cel_IntOptionalValueVTable _cel_OptionalValueVTables[3] +#define _cel_UintOptionalValueVTable _cel_OptionalValueVTables[4] +#define _cel_DoubleOptionalValueVTable _cel_OptionalValueVTables[5] +#define _cel_BytesOptionalValueVTable _cel_OptionalValueVTables[6] +#define _cel_StringOptionalValueVTable _cel_OptionalValueVTables[7] +#define _cel_DurationOptionalValueVTable _cel_OptionalValueVTables[8] +#define _cel_TimestampOptionalValueVTable _cel_OptionalValueVTables[9] +#define _cel_OptionalValueVTable _cel_OptionalValueVTables[10] + +static void _cel_EmptyOptionalValue_Set( + cel_OptionalValue* cel_nonnull optional_value) { + CEL_ASSERT_NOT_NULL(optional_value); + + optional_value->vtable = &_cel_EmptyOptionalValueVTable; +} + +static void _cel_NullOptionalValue_Set( + cel_OptionalValue* cel_nonnull optional_value) { + CEL_ASSERT_NOT_NULL(optional_value); + + optional_value->vtable = &_cel_NullOptionalValueVTable; +} + +static void _cel_BoolOptionalValue_Set( + cel_OptionalValue* cel_nonnull optional_value, bool value) { + CEL_ASSERT_NOT_NULL(optional_value); + + optional_value->vtable = &_cel_BoolOptionalValueVTable; + optional_value->content.b[0] = value; +} + +static void _cel_IntOptionalValue_Set( + cel_OptionalValue* cel_nonnull optional_value, int64_t value) { + CEL_ASSERT_NOT_NULL(optional_value); + + optional_value->vtable = &_cel_IntOptionalValueVTable; + optional_value->content.i64[0] = value; +} + +static void _cel_UintOptionalValue_Set( + cel_OptionalValue* cel_nonnull optional_value, uint64_t value) { + CEL_ASSERT_NOT_NULL(optional_value); + + optional_value->vtable = &_cel_UintOptionalValueVTable; + optional_value->content.u64[0] = value; +} + +static void _cel_DoubleOptionalValue_Set( + cel_OptionalValue* cel_nonnull optional_value, double value) { + CEL_ASSERT_NOT_NULL(optional_value); + + optional_value->vtable = &_cel_DoubleOptionalValueVTable; + optional_value->content.d[0] = value; +} + +static void _cel_BytesOptionalValue_Set( + cel_OptionalValue* cel_nonnull optional_value, cel_StringView value) { + CEL_ASSERT_NOT_NULL(optional_value); + + optional_value->vtable = &_cel_BytesOptionalValueVTable; + optional_value->content.str.data = value.data; + optional_value->content.str.size = value.size; +} + +static void _cel_StringOptionalValue_Set( + cel_OptionalValue* cel_nonnull optional_value, cel_StringView value) { + CEL_ASSERT_NOT_NULL(optional_value); + + optional_value->vtable = &_cel_StringOptionalValueVTable; + optional_value->content.str.data = value.data; + optional_value->content.str.size = value.size; +} + +static void _cel_DurationOptionalValue_Set( + cel_OptionalValue* cel_nonnull optional_value, cel_Duration value) { + CEL_ASSERT_NOT_NULL(optional_value); + + optional_value->vtable = &_cel_DurationOptionalValueVTable; + CEL_STATIC_ASSERT(sizeof(cel_Duration) <= sizeof(cel_ValueContent)); + CEL_STATIC_ASSERT(alignof(cel_Duration) <= alignof(cel_ValueContent)); + memcpy(&optional_value->content.raw, &value, sizeof(value)); +} + +static void _cel_TimestampOptionalValue_Set( + cel_OptionalValue* cel_nonnull optional_value, cel_Timestamp value) { + CEL_ASSERT_NOT_NULL(optional_value); + + optional_value->vtable = &_cel_TimestampOptionalValueVTable; + CEL_STATIC_ASSERT(sizeof(cel_Timestamp) <= sizeof(cel_ValueContent)); + CEL_STATIC_ASSERT(alignof(cel_Timestamp) <= alignof(cel_ValueContent)); + memcpy(&optional_value->content.raw, &value, sizeof(value)); +} + +static void _cel_OptionalValue_Set(cel_OptionalValue* cel_nonnull + optional_value, + const cel_Value* cel_nonnull value) { + CEL_ASSERT_NOT_NULL(optional_value); + CEL_ASSERT_NOT(cel_Value_IsNull(value)); + CEL_ASSERT_NOT(cel_Value_IsBool(value)); + CEL_ASSERT_NOT(cel_Value_IsInt(value)); + CEL_ASSERT_NOT(cel_Value_IsUint(value)); + CEL_ASSERT_NOT(cel_Value_IsDouble(value)); + CEL_ASSERT_NOT(cel_Value_IsBytes(value)); + CEL_ASSERT_NOT(cel_Value_IsString(value)); + CEL_ASSERT_NOT(cel_Value_IsDuration(value)); + CEL_ASSERT_NOT(cel_Value_IsTimestamp(value)); + CEL_ASSERT_NOT(cel_Value_IsError(value)); + + optional_value->vtable = &_cel_OptionalValueVTable; + optional_value->content.ptr[0] = (cel_Value*)value; +} + +CEL_ATTRIBUTE_NODISCARD +static bool _cel_OpaqueValue_IsOptional(const cel_OpaqueValue* opaque_value) { + return opaque_value->vtable >= + ((const cel_OpaqueValueVTable*)_cel_OptionalValueVTables) && + opaque_value->vtable <= + ((const cel_OpaqueValueVTable*)(_cel_OptionalValueVTables + + cel_arraysize( + _cel_OptionalValueVTables))); +} + +static bool _cel_OptionalValue_Equals( + const cel_OpaqueValueVTable* cel_nonnull vtable, cel_ValueContent content, + const cel_ValueContext* cel_nonnull context, + const cel_OpaqueValue* cel_nonnull other, cel_Value* cel_nonnull result, + cel_Status* cel_nonnull status) { + CEL_ASSERT_NOT_NULL(vtable); + CEL_ASSERT_NOT_NULL(context); + CEL_ASSERT_NOT_NULL(other); + CEL_ASSERT_NOT_NULL(result); + CEL_ASSERT_NOT_NULL(status); + + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return false; + } + + if (!_cel_OpaqueValue_IsOptional(other)) { + // The only way to get here would be if somebody tried to mimic the optional + // value implementation, as the type name would have to be `optional_type`. + cel_Value_SetFalse(result); + return true; + } + + const bool has_value = + vtable != (const cel_OpaqueValueVTable*)&_cel_EmptyOptionalValueVTable; + const bool other_has_value = + other->vtable != + (const cel_OpaqueValueVTable*)&_cel_EmptyOptionalValueVTable; + if (has_value && other_has_value) { + cel_Value value; + cel_Value other_value; + if (((const cel_OptionalValueVTable*)vtable) + ->Value((const cel_OptionalValueVTable*)vtable, content, context, + &value, status) && + ((const cel_OptionalValueVTable*)other->vtable) + ->Value((const cel_OptionalValueVTable*)other->vtable, + other->content, context, &other_value, status)) { + return cel_Value_Equals(&value, context, &other_value, result, status); + } + CEL_ASSERT(!cel_Status_Ok(status)); + return false; + } + if (has_value != other_has_value) { + // One empty. + cel_Value_SetFalse(result); + return true; + } + // Both empty. + cel_Value_SetTrue(result); + return true; +} + +static cel_StringView _cel_OptionalValue_TypeName( + const cel_OpaqueValueVTable* cel_nonnull vtable, cel_ValueContent content) { + CEL_ASSERT_NOT_NULL(vtable); + + return cel_StringView_From("optional_type"); +} + +static bool _cel_EmptyOptionalValue_HasValue( + const cel_OptionalValueVTable* cel_nonnull vtable, cel_ValueContent content, + const cel_ValueContext* cel_nonnull context, cel_Value* cel_nonnull result, + cel_Status* cel_nonnull status) { + CEL_ASSERT_NOT_NULL(vtable); + CEL_ASSERT_NOT_NULL(context); + CEL_ASSERT_NOT_NULL(result); + CEL_ASSERT_NOT_NULL(status); + + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return false; + } + + cel_Value_SetFalse(result); + return true; +} + +static bool _cel_EmptyOptionalValue_Value( + const cel_OptionalValueVTable* cel_nonnull vtable, cel_ValueContent content, + const cel_ValueContext* cel_nonnull context, cel_Value* cel_nonnull value, + cel_Status* cel_nonnull status) { + CEL_ASSERT_NOT_NULL(vtable); + CEL_ASSERT_NOT_NULL(context); + CEL_ASSERT_NOT_NULL(value); + CEL_ASSERT_NOT_NULL(status); + + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return false; + } + + cel_Error* error = cel_Error_New(context->arena); + if (CEL_UNLIKELY(error == cel_nullptr)) { + cel_OutOfMemoryStatus(status); + return false; + } + cel_Error_SetCanonicalCode(error, cel_ErrorCode_kFailedPrecondition); + cel_Error_SetMessage(error, + cel_StringView_From("optional.none() dereference")); + cel_Value_SetError(value, error); + return true; +} + +static bool _cel_NullOptionalValue_Value( + const cel_OptionalValueVTable* cel_nonnull vtable, cel_ValueContent content, + const cel_ValueContext* cel_nonnull context, cel_Value* cel_nonnull value, + cel_Status* cel_nonnull status) { + CEL_ASSERT_NOT_NULL(vtable); + CEL_ASSERT_NOT_NULL(value); + CEL_ASSERT_NOT_NULL(status); + + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return false; + } + + cel_Value_SetNull(value); + return true; +} + +static bool _cel_BoolOptionalValue_Value( + const cel_OptionalValueVTable* cel_nonnull vtable, cel_ValueContent content, + const cel_ValueContext* cel_nonnull context, cel_Value* cel_nonnull value, + cel_Status* cel_nonnull status) { + CEL_ASSERT_NOT_NULL(vtable); + CEL_ASSERT_NOT_NULL(value); + CEL_ASSERT_NOT_NULL(status); + + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return false; + } + + cel_Value_SetBool(value, content.b[0]); + return true; +} + +static bool _cel_IntOptionalValue_Value( + const cel_OptionalValueVTable* cel_nonnull vtable, cel_ValueContent content, + const cel_ValueContext* cel_nonnull context, cel_Value* cel_nonnull value, + cel_Status* cel_nonnull status) { + CEL_ASSERT_NOT_NULL(vtable); + CEL_ASSERT_NOT_NULL(value); + CEL_ASSERT_NOT_NULL(status); + + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return false; + } + + cel_Value_SetInt(value, content.i64[0]); + return true; +} + +static bool _cel_UintOptionalValue_Value( + const cel_OptionalValueVTable* cel_nonnull vtable, cel_ValueContent content, + const cel_ValueContext* cel_nonnull context, cel_Value* cel_nonnull value, + cel_Status* cel_nonnull status) { + CEL_ASSERT_NOT_NULL(vtable); + CEL_ASSERT_NOT_NULL(value); + CEL_ASSERT_NOT_NULL(status); + + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return false; + } + + cel_Value_SetUint(value, content.u64[0]); + return true; +} + +static bool _cel_DoubleOptionalValue_Value( + const cel_OptionalValueVTable* cel_nonnull vtable, cel_ValueContent content, + const cel_ValueContext* cel_nonnull context, cel_Value* cel_nonnull value, + cel_Status* cel_nonnull status) { + CEL_ASSERT_NOT_NULL(vtable); + CEL_ASSERT_NOT_NULL(value); + CEL_ASSERT_NOT_NULL(status); + + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return false; + } + + cel_Value_SetDouble(value, content.d[0]); + return true; +} + +static bool _cel_BytesOptionalValue_Value( + const cel_OptionalValueVTable* cel_nonnull vtable, cel_ValueContent content, + const cel_ValueContext* cel_nonnull context, cel_Value* cel_nonnull value, + cel_Status* cel_nonnull status) { + CEL_ASSERT_NOT_NULL(vtable); + CEL_ASSERT_NOT_NULL(value); + CEL_ASSERT_NOT_NULL(status); + + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return false; + } + + cel_Value_SetBytes( + value, cel_StringView_FromArray(content.str.data, content.str.size)); + return true; +} + +static bool _cel_StringOptionalValue_Value( + const cel_OptionalValueVTable* cel_nonnull vtable, cel_ValueContent content, + const cel_ValueContext* cel_nonnull context, cel_Value* cel_nonnull value, + cel_Status* cel_nonnull status) { + CEL_ASSERT_NOT_NULL(vtable); + CEL_ASSERT_NOT_NULL(value); + CEL_ASSERT_NOT_NULL(status); + + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return false; + } + + cel_Value_SetString( + value, cel_StringView_FromArray(content.str.data, content.str.size)); + return true; +} + +static bool _cel_DurationOptionalValue_Value( + const cel_OptionalValueVTable* cel_nonnull vtable, cel_ValueContent content, + const cel_ValueContext* cel_nonnull context, cel_Value* cel_nonnull value, + cel_Status* cel_nonnull status) { + CEL_ASSERT_NOT_NULL(vtable); + CEL_ASSERT_NOT_NULL(value); + CEL_ASSERT_NOT_NULL(status); + + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return false; + } + + CEL_STATIC_ASSERT(sizeof(cel_Duration) <= sizeof(cel_ValueContent)); + CEL_STATIC_ASSERT(alignof(cel_Duration) <= alignof(cel_ValueContent)); + + cel_Duration d; + memcpy(&d, content.raw, sizeof(d)); + cel_Value_SetDuration(value, d); + return true; +} + +static bool _cel_TimestampOptionalValue_Value( + const cel_OptionalValueVTable* cel_nonnull vtable, cel_ValueContent content, + const cel_ValueContext* cel_nonnull context, cel_Value* cel_nonnull value, + cel_Status* cel_nonnull status) { + CEL_ASSERT_NOT_NULL(vtable); + CEL_ASSERT_NOT_NULL(value); + CEL_ASSERT_NOT_NULL(status); + + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return false; + } + + CEL_STATIC_ASSERT(sizeof(cel_Timestamp) <= sizeof(cel_ValueContent)); + CEL_STATIC_ASSERT(alignof(cel_Timestamp) <= alignof(cel_ValueContent)); + cel_Timestamp t; + memcpy(&t, content.raw, sizeof(t)); + cel_Value_SetTimestamp(value, t); + return true; +} + +static bool _cel_OptionalValue_HasValue( + const cel_OptionalValueVTable* cel_nonnull vtable, cel_ValueContent content, + const cel_ValueContext* cel_nonnull context, cel_Value* cel_nonnull result, + cel_Status* cel_nonnull status) { + CEL_ASSERT_NOT_NULL(vtable); + CEL_ASSERT_NOT_NULL(context); + CEL_ASSERT_NOT_NULL(result); + CEL_ASSERT_NOT_NULL(status); + + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return false; + } + + cel_Value_SetTrue(result); + return true; +} + +static bool _cel_OptionalValue_Value( + const cel_OptionalValueVTable* cel_nonnull vtable, cel_ValueContent content, + const cel_ValueContext* cel_nonnull context, cel_Value* cel_nonnull value, + cel_Status* cel_nonnull status) { + CEL_ASSERT_NOT_NULL(vtable); + CEL_ASSERT_NOT_NULL(value); + CEL_ASSERT_NOT_NULL(status); + + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return false; + } + + *value = *(const cel_Value*)content.ptr[0]; + return true; +} + +const cel_OptionalValueVTable _cel_OptionalValueVTables[11] = { + // _cel_EmptyOptionalValueVTable + { + .super = + { + .Equals = &_cel_OptionalValue_Equals, + .TypeName = &_cel_OptionalValue_TypeName, + }, + .HasValue = &_cel_EmptyOptionalValue_HasValue, + .Value = &_cel_EmptyOptionalValue_Value, + }, + // _cel_NullOptionalValueVTable + { + .super = + { + .Equals = &_cel_OptionalValue_Equals, + .TypeName = &_cel_OptionalValue_TypeName, + }, + .HasValue = &_cel_OptionalValue_HasValue, + .Value = &_cel_NullOptionalValue_Value, + }, + // _cel_BoolOptionalValueVTable + { + .super = + { + .Equals = &_cel_OptionalValue_Equals, + .TypeName = &_cel_OptionalValue_TypeName, + }, + .HasValue = &_cel_OptionalValue_HasValue, + .Value = &_cel_BoolOptionalValue_Value, + }, + // _cel_IntOptionalValueVTable + { + .super = + { + .Equals = &_cel_OptionalValue_Equals, + .TypeName = &_cel_OptionalValue_TypeName, + }, + .HasValue = &_cel_OptionalValue_HasValue, + .Value = &_cel_IntOptionalValue_Value, + }, + // _cel_UintOptionalValueVTable + { + .super = + { + .Equals = &_cel_OptionalValue_Equals, + .TypeName = &_cel_OptionalValue_TypeName, + }, + .HasValue = &_cel_OptionalValue_HasValue, + .Value = &_cel_UintOptionalValue_Value, + }, + // _cel_DoubleOptionalValueVTable + { + .super = + { + .Equals = &_cel_OptionalValue_Equals, + .TypeName = &_cel_OptionalValue_TypeName, + }, + .HasValue = &_cel_OptionalValue_HasValue, + .Value = &_cel_DoubleOptionalValue_Value, + }, + // _cel_BytesOptionalValueVTable + { + .super = + { + .Equals = &_cel_OptionalValue_Equals, + .TypeName = &_cel_OptionalValue_TypeName, + }, + .HasValue = &_cel_OptionalValue_HasValue, + .Value = &_cel_BytesOptionalValue_Value, + }, + // _cel_StringOptionalValueVTable + { + .super = + { + .Equals = &_cel_OptionalValue_Equals, + .TypeName = &_cel_OptionalValue_TypeName, + }, + .HasValue = &_cel_OptionalValue_HasValue, + .Value = &_cel_StringOptionalValue_Value, + }, + // _cel_DurationOptionalValueVTable + { + .super = + { + .Equals = &_cel_OptionalValue_Equals, + .TypeName = &_cel_OptionalValue_TypeName, + }, + .HasValue = &_cel_OptionalValue_HasValue, + .Value = &_cel_DurationOptionalValue_Value, + }, + // _cel_TimestampOptionalValueVTable + { + .super = + { + .Equals = &_cel_OptionalValue_Equals, + .TypeName = &_cel_OptionalValue_TypeName, + }, + .HasValue = &_cel_OptionalValue_HasValue, + .Value = &_cel_TimestampOptionalValue_Value, + }, + // _cel_OptionalValueVTable + { + .super = + { + .Equals = &_cel_OptionalValue_Equals, + .TypeName = &_cel_OptionalValue_TypeName, + }, + .HasValue = &_cel_OptionalValue_HasValue, + .Value = &_cel_OptionalValue_Value, + }, +}; + +cel_OptionalValue* cel_nonnull +cel_OptionalValue_Empty(cel_OptionalValue* cel_nonnull optional_value) { + CEL_ASSERT_NOT_NULL(optional_value); + + _cel_EmptyOptionalValue_Set(optional_value); + return optional_value; +} + +extern "C" bool cel_OptionalValue_Of( + cel_OptionalValue* cel_nonnull optional_value, + const cel_Value* cel_nonnull value, cel_Arena* cel_nonnull arena) { + CEL_ASSERT_NOT_NULL(optional_value); + CEL_ASSERT_NOT_NULL(value); + CEL_ASSERT_NOT_NULL(arena); + + switch (cel_Value_Kind(value)) { + case cel_ValueKind_kNull: + _cel_NullOptionalValue_Set(optional_value); + return true; + case cel_ValueKind_kBool: + _cel_BoolOptionalValue_Set(optional_value, cel_Value_GetBool(value)); + return true; + case cel_ValueKind_kInt: + _cel_IntOptionalValue_Set(optional_value, cel_Value_GetInt(value)); + return true; + case cel_ValueKind_kUint: + _cel_UintOptionalValue_Set(optional_value, cel_Value_GetUint(value)); + return true; + case cel_ValueKind_kDouble: + _cel_DoubleOptionalValue_Set(optional_value, cel_Value_GetDouble(value)); + return true; + case cel_ValueKind_kBytes: + _cel_BytesOptionalValue_Set(optional_value, cel_Value_GetBytes(value)); + return true; + case cel_ValueKind_kString: + _cel_StringOptionalValue_Set(optional_value, cel_Value_GetString(value)); + return true; + case cel_ValueKind_kDuration: + _cel_DurationOptionalValue_Set(optional_value, + cel_Value_GetDuration(value)); + return true; + case cel_ValueKind_kTimestamp: + _cel_TimestampOptionalValue_Set(optional_value, + cel_Value_GetTimestamp(value)); + return true; + default: { + cel_Value* value_ptr = + (cel_Value*)cel_Arena_Malloc(arena, sizeof(cel_Value), cel_nullptr); + if (CEL_UNLIKELY(value_ptr == cel_nullptr)) { + return false; + } + *value_ptr = *value; + _cel_OptionalValue_Set(optional_value, value_ptr); + return true; + } + } +} + +extern "C" bool cel_Value_IsOptional(const cel_Value* cel_nonnull value) { + return cel_Value_IsOpaque(value) && + _cel_OpaqueValue_IsOptional(cel_Value_GetOpaque(value)); +} + +extern "C" bool cel_OpaqueValue_IsOptional( + const cel_OpaqueValue* cel_nonnull opaque_value) { + return _cel_OpaqueValue_IsOptional(opaque_value); +} diff --git a/cel-c/src/parsed_map_field_value.cc b/cel-c/src/parsed_map_field_value.cc new file mode 100644 index 0000000..038f8be --- /dev/null +++ b/cel-c/src/parsed_map_field_value.cc @@ -0,0 +1,460 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "cel-c/src/parsed_map_field_value.h" + +#include +#include +#include +#include + +#include "cel-c/alloc.h" +#include "cel-c/assert.h" +#include "cel-c/config.h" +#include "cel-c/error.h" +#include "cel-c/error_code.h" +#include "cel-c/src/map_value.h" +#include "cel-c/src/message_equality.h" +#include "cel-c/status.h" +#include "cel-c/status_code.h" +#include "cel-c/string_view.h" +#include "cel-c/value.h" +#include "upb/message/array.h" +#include "upb/message/map.h" +#include "upb/message/message.h" +#include "upb/reflection/def.h" + +extern const cel_MapValueIteratorVTable _cel_ParsedMapFieldValueIteratorVTable; + +typedef struct { + cel_MapValueIterator base; + cel_Allocator* cel_nonnull alloc; + const upb_Message* cel_nonnull message; + const upb_FieldDef* cel_nonnull field_key_def; + const upb_FieldDef* cel_nonnull field_val_def; + const upb_Map* cel_nullability_unknown field_val; + size_t iter; + size_t remain; +} _cel_ParsedMapFieldValueIterator; + +static bool _cel_ParsedMapFieldValue_Equals( + const cel_MapValueVTable* cel_nonnull vtable, cel_ValueContent content, + const cel_ValueContext* cel_nonnull context, + const cel_MapValue* cel_nonnull other, cel_Value* cel_nonnull result, + cel_Status* cel_nonnull status) { + CEL_ASSERT_EQ(vtable, &_cel_ParsedMapFieldValueVTable); + CEL_ASSERT_NOT_NULL(context); + CEL_ASSERT_NOT_NULL(other); + CEL_ASSERT_NOT_NULL(result); + CEL_ASSERT_NOT_NULL(status); + + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return false; + } + + if (other->vtable == &_cel_ParsedMapFieldValueVTable) { + upb_MessageValue field_val; + upb_MessageValue other_field_val; + field_val.map_val = (const upb_Map*)content.ptr[0]; + other_field_val.map_val = (const upb_Map*)other->content.ptr[0]; + const upb_FieldDef* field_def = (const upb_FieldDef*)content.ptr[1]; + const upb_FieldDef* other_field_def = + (const upb_FieldDef*)other->content.ptr[1]; + switch (_cel_MessageField_Equals( + field_val, field_def, other_field_val, other_field_def, + context->def_pool, context->well_known_types, context->alloc)) { + case _cel_MessageEquality_kEqual: + cel_Value_SetTrue(result); + return true; + case _cel_MessageEquality_kNotEqual: + cel_Value_SetFalse(result); + return true; + case _cel_MessageEquality_kOutOfMemory: + cel_OutOfMemoryStatus(status); + return false; + case _cel_MessageEquality_kMaxDepthExceeded: + cel_Status_SetCanonicalCode(status, cel_StatusCode_kInvalidArgument); + cel_Status_SetMessage( + status, cel_StringView_From("max message depth exceeded")); + return false; + } + } + + // Fallback to default implementation. + return false; +} + +static bool _cel_ParsedMapFieldValue_FastSize( + const cel_MapValueVTable* cel_nonnull vtable, cel_ValueContent content, + size_t* cel_nonnull size) { + CEL_ASSERT_EQ(vtable, &_cel_ParsedMapFieldValueVTable); + CEL_ASSERT_NOT_NULL(size); + + const upb_Map* field_val = (const upb_Map*)content.ptr[0]; + *size = field_val != cel_nullptr ? upb_Map_Size(field_val) : (size_t)0; + return true; +} + +static bool _cel_ParsedMapFieldValue_SlowSize( + const cel_MapValueVTable* cel_nonnull vtable, cel_ValueContent content, + const cel_ValueContext* cel_nonnull context, cel_Value* cel_nonnull size, + cel_Status* cel_nonnull status) { + CEL_ASSERT_EQ(vtable, &_cel_ParsedMapFieldValueVTable); + CEL_ASSERT_NOT_NULL(context); + CEL_ASSERT_NOT_NULL(size); + CEL_ASSERT_NOT_NULL(status); + + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return false; + } + + const upb_Map* field_val = (const upb_Map*)content.ptr[0]; + cel_Value_SetInt(size, field_val != cel_nullptr + ? (int64_t)upb_Map_Size(field_val) + : (int64_t)0); + return true; +} + +static bool _cel_ParsedMapFieldValue_Get( + const cel_MapValueVTable* cel_nonnull vtable, cel_ValueContent content, + const cel_ValueContext* cel_nonnull context, + const cel_MapValueKey* cel_nonnull key, cel_Value* cel_nonnull value, + cel_Status* cel_nonnull status) { + CEL_ASSERT_EQ(vtable, &_cel_ParsedMapFieldValueVTable); + CEL_ASSERT_NOT_NULL(context); + CEL_ASSERT_NOT_NULL(key); + CEL_ASSERT_NOT_NULL(value); + CEL_ASSERT_NOT_NULL(status); + + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return false; + } + + const upb_FieldDef* field_def = (const upb_FieldDef*)content.ptr[1]; + const upb_Map* field_val = (const upb_Map*)content.ptr[0]; + + if (field_val != cel_nullptr && upb_Map_Size(field_val) > 0) { + const upb_MessageDef* field_entry_def = + upb_FieldDef_MessageSubDef(field_def); + const upb_FieldDef* field_key_def = upb_MessageDef_FindFieldByNumber( + field_entry_def, kUpb_MapEntry_KeyFieldNumber); + + upb_MessageValue message_key; + if (!_cel_MapValueKey_ToMessageValue(key, upb_FieldDef_CType(field_key_def), + &message_key)) { + goto no_such_key; + } + + upb_MessageValue message_val; + if (!upb_Map_Get(field_val, message_key, &message_val)) { + goto no_such_key; + } + + const upb_FieldDef* field_val_def = upb_MessageDef_FindFieldByNumber( + field_entry_def, kUpb_MapEntry_ValueFieldNumber); + return cel_Value_FromMapFieldValue(value, context, message_val, + field_val_def, status); + } + +no_such_key: { + cel_Error* error = cel_Error_New(context->arena); + if (CEL_UNLIKELY(error == cel_nullptr)) { + cel_OutOfMemoryStatus(status); + return false; + } + cel_Error_SetCanonicalCode(error, cel_ErrorCode_kNotFound); + cel_Error_SetMessage(error, cel_StringView_From("no such key")); + cel_Value_SetError(value, error); + return true; +} +} + +static bool _cel_ParsedMapFieldValue_Find( + const cel_MapValueVTable* cel_nonnull vtable, cel_ValueContent content, + const cel_ValueContext* cel_nonnull context, + const cel_MapValueKey* cel_nonnull key, cel_Value* cel_nonnull value, + cel_Status* cel_nonnull status) { + CEL_ASSERT_EQ(vtable, &_cel_ParsedMapFieldValueVTable); + CEL_ASSERT_NOT_NULL(context); + CEL_ASSERT_NOT_NULL(key); + CEL_ASSERT_NOT_NULL(value); + CEL_ASSERT_NOT_NULL(status); + + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return false; + } + + const upb_FieldDef* field_def = (const upb_FieldDef*)content.ptr[1]; + const upb_Map* field_val = (const upb_Map*)content.ptr[0]; + + if (field_val != cel_nullptr && upb_Map_Size(field_val) > 0) { + const upb_MessageDef* field_entry_def = + upb_FieldDef_MessageSubDef(field_def); + const upb_FieldDef* field_key_def = upb_MessageDef_FindFieldByNumber( + field_entry_def, kUpb_MapEntry_KeyFieldNumber); + + upb_MessageValue message_key; + if (!_cel_MapValueKey_ToMessageValue(key, upb_FieldDef_CType(field_key_def), + &message_key)) { + return false; + } + + upb_MessageValue message_val; + if (!upb_Map_Get(field_val, message_key, &message_val)) { + return false; + } + + const upb_FieldDef* field_val_def = upb_MessageDef_FindFieldByNumber( + field_entry_def, kUpb_MapEntry_ValueFieldNumber); + return cel_Value_FromMapFieldValue(value, context, message_val, + field_val_def, status); + } + + return false; +} + +static bool _cel_ParsedMapFieldValue_Has( + const cel_MapValueVTable* cel_nonnull vtable, cel_ValueContent content, + const cel_ValueContext* cel_nonnull context, + const cel_MapValueKey* cel_nonnull key, cel_Value* cel_nonnull result, + cel_Status* cel_nonnull status) { + CEL_ASSERT_EQ(vtable, &_cel_ParsedMapFieldValueVTable); + CEL_ASSERT_NOT_NULL(context); + CEL_ASSERT_NOT_NULL(key); + CEL_ASSERT_NOT_NULL(result); + CEL_ASSERT_NOT_NULL(status); + + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return false; + } + + const upb_FieldDef* field_def = (const upb_FieldDef*)content.ptr[1]; + const upb_Map* field_val = (const upb_Map*)content.ptr[0]; + + if (field_val != cel_nullptr && upb_Map_Size(field_val) > 0) { + const upb_MessageDef* field_entry_def = + upb_FieldDef_MessageSubDef(field_def); + const upb_FieldDef* field_key_def = upb_MessageDef_FindFieldByNumber( + field_entry_def, kUpb_MapEntry_KeyFieldNumber); + + upb_MessageValue message_key; + if (!_cel_MapValueKey_ToMessageValue(key, upb_FieldDef_CType(field_key_def), + &message_key)) { + return false; + } + + upb_MessageValue message_val; + cel_Value_SetBool(result, + upb_Map_Get(field_val, message_key, &message_val)); + return true; + } + + cel_Value_SetFalse(result); + return true; +} + +static cel_MapValueIterator* cel_nullable _cel_ParsedMapFieldValue_NewIterator( + const cel_MapValueVTable* cel_nonnull vtable, cel_ValueContent content, + const cel_ValueContext* cel_nonnull context, + cel_Status* cel_nonnull status) { + CEL_ASSERT_EQ(vtable, &_cel_ParsedMapFieldValueVTable); + CEL_ASSERT_NOT_NULL(context); + CEL_ASSERT_NOT_NULL(status); + + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return cel_nullptr; + } + + _cel_ParsedMapFieldValueIterator* iter = + (_cel_ParsedMapFieldValueIterator*)cel_Allocator_Malloc( + context->alloc, sizeof(_cel_ParsedMapFieldValueIterator), + cel_nullptr); + if (CEL_UNLIKELY(iter == cel_nullptr)) { + cel_OutOfMemoryStatus(status); + return cel_nullptr; + } + memset(iter, 0, sizeof(*iter)); + iter->base.vtable = &_cel_ParsedMapFieldValueIteratorVTable; + iter->alloc = context->alloc; + const upb_FieldDef* field_def = (const upb_FieldDef*)content.ptr[1]; + iter->field_val = (const upb_Map*)content.ptr[0]; + const upb_MessageDef* field_entry_def = upb_FieldDef_MessageSubDef(field_def); + iter->field_key_def = upb_MessageDef_FindFieldByNumber( + field_entry_def, kUpb_MapEntry_KeyFieldNumber); + iter->field_val_def = upb_MessageDef_FindFieldByNumber( + field_entry_def, kUpb_MapEntry_ValueFieldNumber); + iter->iter = kUpb_Map_Begin; + iter->remain = iter->field_val != cel_nullptr ? upb_Map_Size(iter->field_val) + : (size_t)0; + return &iter->base; +} + +extern "C" const cel_MapValueVTable _cel_ParsedMapFieldValueVTable = { + .Equals = &_cel_ParsedMapFieldValue_Equals, + .FastSize = &_cel_ParsedMapFieldValue_FastSize, + .SlowSize = &_cel_ParsedMapFieldValue_SlowSize, + .Get = &_cel_ParsedMapFieldValue_Get, + .Find = &_cel_ParsedMapFieldValue_Find, + .Has = &_cel_ParsedMapFieldValue_Has, + .NewIterator = &_cel_ParsedMapFieldValue_NewIterator, +}; + +static void _cel_ParsedMapFieldValueIterator_Delete( + cel_ValueIterator* cel_nonnull iterator) { + CEL_ASSERT_NOT_NULL(iterator); + CEL_ASSERT_EQ(iterator->vtable, + &_cel_ParsedMapFieldValueIteratorVTable.super); + + _cel_ParsedMapFieldValueIterator* iter = + cel_containerof(iterator, _cel_ParsedMapFieldValueIterator, base); + cel_Allocator_FreeSized(iter->alloc, iter, sizeof(*iter)); +} + +static bool _cel_ParsedMapFieldValueIterator_Next1( + cel_ValueIterator* cel_nonnull iterator, + const cel_ValueContext* cel_nonnull context, + cel_Value* cel_nonnull key_or_value, cel_Status* cel_nonnull status) { + CEL_ASSERT_NOT_NULL(iterator); + CEL_ASSERT_EQ(iterator->vtable, + &_cel_ParsedMapFieldValueIteratorVTable.super); + CEL_ASSERT_NOT_NULL(context); + CEL_ASSERT_NOT_NULL(key_or_value); + CEL_ASSERT_NOT_NULL(status); + + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return false; + } + + _cel_ParsedMapFieldValueIterator* iter = + cel_containerof(iterator, _cel_ParsedMapFieldValueIterator, base); + + upb_MessageValue entry_key; + upb_MessageValue entry_val; + size_t iter_next = iter->iter; + if (iter->field_val != cel_nullptr && + upb_Map_Next(iter->field_val, &entry_key, &entry_val, &iter_next)) { + const bool ok = cel_Value_FromMapFieldKey(key_or_value, context, entry_key, + iter->field_key_def, status); + if (ok) { + iter->iter = iter_next; + --iter->remain; + } + return ok; + } + + return false; +} + +static bool _cel_ParsedMapFieldValueIterator_Next2( + cel_ValueIterator* cel_nonnull iterator, + const cel_ValueContext* cel_nonnull context, cel_Value* cel_nonnull key, + cel_Value* cel_nonnull value, cel_Status* cel_nonnull status) { + CEL_ASSERT_NOT_NULL(iterator); + CEL_ASSERT_EQ(iterator->vtable, + &_cel_ParsedMapFieldValueIteratorVTable.super); + CEL_ASSERT_NOT_NULL(context); + CEL_ASSERT_NOT_NULL(key); + CEL_ASSERT_NOT_NULL(value); + CEL_ASSERT_NOT_NULL(status); + + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return false; + } + + _cel_ParsedMapFieldValueIterator* iter = + cel_containerof(iterator, _cel_ParsedMapFieldValueIterator, base); + + upb_MessageValue entry_key; + upb_MessageValue entry_val; + size_t iter_next = iter->iter; + if (iter->field_val != cel_nullptr && + upb_Map_Next(iter->field_val, &entry_key, &entry_val, &iter_next)) { + bool ok = cel_Value_FromMapFieldKey(key, context, entry_key, + iter->field_key_def, status); + if (ok) { + ok = cel_Value_FromMapFieldValue(value, context, entry_val, + iter->field_val_def, status); + if (ok) { + iter->iter = iter_next; + --iter->remain; + } + } + return ok; + } + + return false; +} + +static bool _cel_ParsedMapFieldValueIterator_Remaining( + const cel_ValueIterator* cel_nonnull iterator, + size_t* cel_nonnull remaining) { + CEL_ASSERT_NOT_NULL(iterator); + CEL_ASSERT_EQ(iterator->vtable, + &_cel_ParsedMapFieldValueIteratorVTable.super); + CEL_ASSERT_NOT_NULL(remaining); + + _cel_ParsedMapFieldValueIterator* iter = + cel_containerof(iterator, _cel_ParsedMapFieldValueIterator, base); + *remaining = iter->remain; + return true; +} + +static bool _cel_ParsedMapFieldValueIterator_Next( + cel_MapValueIterator* cel_nonnull iterator, + const cel_ValueContext* cel_nonnull context, + cel_MapValueKey* cel_nullable key, cel_Value* cel_nonnull value, + cel_Status* cel_nonnull status) { + CEL_ASSERT_NOT_NULL(iterator); + CEL_ASSERT_EQ(iterator->vtable, &_cel_ParsedMapFieldValueIteratorVTable); + CEL_ASSERT_NOT_NULL(context); + CEL_ASSERT_NOT_NULL(key); + CEL_ASSERT_NOT_NULL(value); + CEL_ASSERT_NOT_NULL(status); + + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return false; + } + + _cel_ParsedMapFieldValueIterator* iter = + cel_containerof(iterator, _cel_ParsedMapFieldValueIterator, base); + + upb_MessageValue entry_key; + upb_MessageValue entry_val; + size_t iter_next = iter->iter; + if (iter->field_val != cel_nullptr && + upb_Map_Next(iter->field_val, &entry_key, &entry_val, &iter_next)) { + _cel_MapValueKey_FromMessageValue( + key, upb_FieldDef_CType(iter->field_key_def), entry_key); + bool ok = cel_Value_FromMapFieldValue(value, context, entry_val, + iter->field_val_def, status); + if (ok) { + iter->iter = iter_next; + --iter->remain; + } + return ok; + } + + return false; +} + +const cel_MapValueIteratorVTable _cel_ParsedMapFieldValueIteratorVTable = { + .super = + { + .Delete = &_cel_ParsedMapFieldValueIterator_Delete, + .Next1 = &_cel_ParsedMapFieldValueIterator_Next1, + .Next2 = &_cel_ParsedMapFieldValueIterator_Next2, + .Remaining = &_cel_ParsedMapFieldValueIterator_Remaining, + }, + .Next = &_cel_ParsedMapFieldValueIterator_Next, +}; diff --git a/cel-c/src/parsed_message_value.cc b/cel-c/src/parsed_message_value.cc new file mode 100644 index 0000000..dbb4e64 --- /dev/null +++ b/cel-c/src/parsed_message_value.cc @@ -0,0 +1,348 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "cel-c/src/parsed_message_value.h" + +#include +#include +#include +#include + +#include "cel-c/alloc.h" +#include "cel-c/assert.h" +#include "cel-c/config.h" +#include "cel-c/error.h" +#include "cel-c/error_code.h" +#include "cel-c/src/message_equality.h" +#include "cel-c/status.h" +#include "cel-c/status_code.h" +#include "cel-c/string_view.h" +#include "cel-c/value.h" +#include "upb/base/descriptor_constants.h" +#include "upb/message/array.h" +#include "upb/message/map.h" +#include "upb/message/message.h" +#include "upb/reflection/def.h" +#include "upb/reflection/message.h" + +extern const cel_StructValueIteratorVTable + _cel_ParsedMessageValueIteratorVTable; + +typedef struct { + cel_StructValueIterator base; + cel_Allocator* cel_nonnull alloc; + const upb_Message* cel_nonnull message; + const upb_MessageDef* cel_nonnull message_def; + size_t iter; +} _cel_ParsedMessageValueIterator; + +static bool _cel_ParsedMessageValue_Equals( + const cel_StructValueVTable* cel_nonnull vtable, cel_ValueContent content, + const cel_ValueContext* cel_nonnull context, + const cel_StructValue* cel_nonnull other, cel_Value* cel_nonnull result, + cel_Status* cel_nonnull status) { + CEL_ASSERT_EQ(vtable, &_cel_ParsedMessageValueVTable); + CEL_ASSERT_NOT_NULL(context); + CEL_ASSERT_NOT_NULL(other); + CEL_ASSERT_NOT_NULL(result); + CEL_ASSERT_NOT_NULL(status); + + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return false; + } + + if (other->vtable == &_cel_ParsedMessageValueVTable) { + const upb_Message* message = (const upb_Message*)content.ptr[0]; + const upb_Message* other_message = + (const upb_Message*)other->content.ptr[0]; + const upb_MessageDef* message_def = (const upb_MessageDef*)content.ptr[1]; + const upb_MessageDef* other_message_def = + (const upb_MessageDef*)other->content.ptr[1]; + CEL_ASSERT_EQ(message_def, other_message_def); + if (CEL_LIKELY(message_def == other_message_def)) { + switch (_cel_Message_Equals(message, other_message, other_message_def, + context->def_pool, context->well_known_types, + context->alloc)) { + case _cel_MessageEquality_kEqual: + cel_Value_SetTrue(result); + return true; + case _cel_MessageEquality_kNotEqual: + cel_Value_SetFalse(result); + return true; + case _cel_MessageEquality_kOutOfMemory: + cel_OutOfMemoryStatus(status); + return false; + case _cel_MessageEquality_kMaxDepthExceeded: + cel_Status_SetCanonicalCode(status, cel_StatusCode_kInvalidArgument); + cel_Status_SetMessage( + status, cel_StringView_From("max message depth exceeded")); + return false; + } + } + } + + // Fallback to default implementation. + return false; +} + +static cel_StringView _cel_ParsedMessageValue_TypeName( + const cel_StructValueVTable* cel_nonnull vtable, cel_ValueContent content) { + CEL_ASSERT_EQ(vtable, &_cel_ParsedMessageValueVTable); + + return cel_StringView_FromString( + upb_MessageDef_FullName((const upb_MessageDef*)content.ptr[1])); +} + +static bool _cel_ParsedMessageValue_Get( + const cel_StructValueVTable* cel_nonnull vtable, cel_ValueContent content, + const cel_ValueContext* cel_nonnull context, + const cel_StructValueKey* cel_nonnull key, cel_Value* cel_nonnull value, + cel_Status* cel_nonnull status) { + CEL_ASSERT_EQ(vtable, &_cel_ParsedMessageValueVTable); + + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return false; + } + + const upb_Message* message = (const upb_Message*)content.ptr[0]; + const upb_MessageDef* message_def = (const upb_MessageDef*)content.ptr[1]; + const upb_FieldDef* field_def; + const cel_StructValueKeyKind key_kind = cel_StructValueKey_Kind(key); + switch (key_kind) { + case cel_StructValueKeyKind_kName: { + cel_StringView name = cel_StructValueKey_GetName(key); + field_def = upb_MessageDef_FindFieldByNameWithSize( + message_def, cel_StringView_Data(name), cel_StringView_Size(name)); + if (CEL_UNLIKELY(field_def == cel_nullptr)) { + cel_Error* error = cel_Error_New(context->arena); + if (CEL_UNLIKELY(error == cel_nullptr)) { + cel_OutOfMemoryStatus(status); + return false; + } + cel_Error_SetCanonicalCode(error, cel_ErrorCode_kNotFound); + cel_Error_SetMessage(error, cel_StringView_From("no such field")); + cel_Value_SetError(value, error); + return true; + } + } break; + case cel_StructValueKeyKind_kDef: + field_def = cel_StructValueKey_GetDef(key); + break; + default: + cel_InvalidArgumentStatusF( + status, "cel: unexpected struct value key kind: %d", key_kind); + return false; + } + + CEL_ASSERT_NOT_NULL(field_def); + + return cel_Value_FromField(value, context, + upb_Message_GetFieldByDef(message, field_def), + field_def, status); +} + +static bool _cel_ParsedMessageValue_Has( + const cel_StructValueVTable* cel_nonnull vtable, cel_ValueContent content, + const cel_ValueContext* cel_nonnull context, + const cel_StructValueKey* cel_nonnull key, cel_Value* cel_nonnull result, + cel_Status* cel_nonnull status) { + CEL_ASSERT_EQ(vtable, &_cel_ParsedMessageValueVTable); + + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return false; + } + + const upb_Message* message = (const upb_Message*)content.ptr[0]; + const upb_MessageDef* message_def = (const upb_MessageDef*)content.ptr[1]; + const upb_FieldDef* field_def; + const cel_StructValueKeyKind key_kind = cel_StructValueKey_Kind(key); + switch (key_kind) { + case cel_StructValueKeyKind_kName: { + cel_StringView name = cel_StructValueKey_GetName(key); + field_def = upb_MessageDef_FindFieldByNameWithSize( + message_def, cel_StringView_Data(name), cel_StringView_Size(name)); + if (CEL_UNLIKELY(field_def == cel_nullptr)) { + cel_Error* error = cel_Error_New(context->arena); + if (CEL_UNLIKELY(error == cel_nullptr)) { + cel_OutOfMemoryStatus(status); + return false; + } + cel_Error_SetCanonicalCode(error, cel_ErrorCode_kNotFound); + cel_Error_SetMessage(error, cel_StringView_From("no such field")); + cel_Value_SetError(result, error); + return true; + } + } break; + case cel_StructValueKeyKind_kDef: + field_def = cel_StructValueKey_GetDef(key); + break; + default: + cel_InvalidArgumentStatusF( + status, "cel: unexpected struct value key kind: %d", key_kind); + return false; + } + + CEL_ASSERT_NOT_NULL(field_def); + + if (upb_FieldDef_IsMap(field_def)) { + upb_MessageValue value = upb_Message_GetFieldByDef(message, field_def); + cel_Value_SetBool(result, value.map_val != cel_nullptr && + upb_Map_Size(value.map_val) > 0); + } else if (upb_FieldDef_IsRepeated(field_def)) { + upb_MessageValue value = upb_Message_GetFieldByDef(message, field_def); + cel_Value_SetBool(result, value.array_val != cel_nullptr && + upb_Array_Size(value.array_val) > 0); + } else if (upb_FieldDef_HasPresence(field_def)) { + cel_Value_SetBool(result, upb_Message_HasFieldByDef(message, field_def)); + } else { + upb_MessageValue value = upb_Message_GetFieldByDef(message, field_def); + switch (upb_FieldDef_CType(field_def)) { + case kUpb_CType_Bool: + cel_Value_SetBool(result, value.bool_val); + break; + case kUpb_CType_Enum: + CEL_ATTRIBUTE_FALLTHROUGH; + case kUpb_CType_Int32: + cel_Value_SetBool(result, value.int32_val != 0); + break; + case kUpb_CType_Float: + CEL_ATTRIBUTE_FALLTHROUGH; + CEL_STATIC_ASSERT(sizeof(float) == sizeof(uint32_t)); + case kUpb_CType_UInt32: + cel_Value_SetBool(result, value.uint32_val != 0); + break; + case kUpb_CType_Int64: + cel_Value_SetBool(result, value.int64_val != 0); + break; + case kUpb_CType_Double: + CEL_ATTRIBUTE_FALLTHROUGH; + CEL_STATIC_ASSERT(sizeof(double) == sizeof(uint64_t)); + case kUpb_CType_UInt64: + cel_Value_SetBool(result, value.uint64_val != 0); + break; + case kUpb_CType_Bytes: + CEL_ATTRIBUTE_FALLTHROUGH; + case kUpb_CType_String: + cel_Value_SetBool(result, value.str_val.size != 0); + break; + case kUpb_CType_Message: + cel_Value_SetBool(result, value.msg_val != cel_nullptr); + break; + } + } + + return true; +} + +static cel_StructValueIterator* cel_nullable +_cel_ParsedMessageValue_NewIterator( + const cel_StructValueVTable* cel_nonnull vtable, cel_ValueContent content, + const cel_ValueContext* cel_nonnull context, + cel_Status* cel_nonnull status) { + CEL_ASSERT_EQ(vtable, &_cel_ParsedMessageValueVTable); + CEL_ASSERT_NOT_NULL(context); + CEL_ASSERT_NOT_NULL(status); + + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return cel_nullptr; + } + + _cel_ParsedMessageValueIterator* iter = + (_cel_ParsedMessageValueIterator*)cel_Allocator_Malloc( + context->alloc, sizeof(_cel_ParsedMessageValueIterator), cel_nullptr); + if (CEL_UNLIKELY(iter == cel_nullptr)) { + cel_OutOfMemoryStatus(status); + return cel_nullptr; + } + memset(iter, 0, sizeof(*iter)); + iter->base.vtable = &_cel_ParsedMessageValueIteratorVTable; + iter->alloc = context->alloc; + iter->message = (const upb_Message*)content.ptr[0]; + iter->message_def = (const upb_MessageDef*)content.ptr[1]; + iter->iter = kUpb_Message_Begin; + return &iter->base; +} + +extern "C" const cel_StructValueVTable _cel_ParsedMessageValueVTable = { + .Equals = &_cel_ParsedMessageValue_Equals, + .TypeName = &_cel_ParsedMessageValue_TypeName, + .Get = &_cel_ParsedMessageValue_Get, + .Has = &_cel_ParsedMessageValue_Has, + .NewIterator = &_cel_ParsedMessageValue_NewIterator, +}; + +static void _cel_ParsedMessageValueIterator_Delete( + cel_StructValueIterator* cel_nonnull iterator) { + CEL_ASSERT_NOT_NULL(iterator); + CEL_ASSERT_NOT_NULL(iterator->vtable); + CEL_ASSERT_EQ(iterator->vtable, &_cel_ParsedMessageValueIteratorVTable); + + _cel_ParsedMessageValueIterator* iter = + cel_containerof(iterator, _cel_ParsedMessageValueIterator, base); + cel_Allocator_FreeSized(iter->alloc, iter, sizeof(*iter)); +} + +static bool _cel_ParsedMessageValueIterator_Next( + cel_StructValueIterator* cel_nonnull iterator, + const cel_ValueContext* cel_nonnull context, + cel_StructValueKey* cel_nonnull key, cel_Value* cel_nullable value, + cel_Status* cel_nonnull status) { + CEL_ASSERT_NOT_NULL(iterator); + CEL_ASSERT_NOT_NULL(iterator->vtable); + CEL_ASSERT_EQ(iterator->vtable, &_cel_ParsedMessageValueIteratorVTable); + CEL_ASSERT_NOT_NULL(context); + CEL_ASSERT_NOT_NULL(key); + CEL_ASSERT_NOT_NULL(status); + + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return false; + } + + _cel_ParsedMessageValueIterator* iter = + cel_containerof(iterator, _cel_ParsedMessageValueIterator, base); + const upb_FieldDef* field_def; + upb_MessageValue field_val; + size_t iter_next = iter->iter; + if (upb_Message_Next(iter->message, iter->message_def, context->def_pool, + &field_def, &field_val, &iter_next)) { + cel_StructValueKey_SetDef(key, field_def); + if (value != cel_nullptr) { + if (!cel_Value_FromField(value, context, field_val, field_def, status)) { + return false; + } + } + iter->iter = iter_next; + return true; + } + return false; +} + +static bool _cel_ParsedMessageValueIterator_Remaining( + const cel_StructValueIterator* cel_nonnull iterator, + size_t* cel_nonnull remaining) { + CEL_ASSERT_NOT_NULL(iterator); + CEL_ASSERT_NOT_NULL(iterator->vtable); + CEL_ASSERT_EQ(iterator->vtable, &_cel_ParsedMessageValueIteratorVTable); + CEL_ASSERT_NOT_NULL(remaining); + + // We do not know this in constant time, it requires iterating over all set + // fields to count. + return false; +} + +const cel_StructValueIteratorVTable _cel_ParsedMessageValueIteratorVTable = { + .Delete = &_cel_ParsedMessageValueIterator_Delete, + .Next = &_cel_ParsedMessageValueIterator_Next, + .Remaining = &_cel_ParsedMessageValueIterator_Remaining, +}; diff --git a/cel-c/src/parsed_repeated_field_value.cc b/cel-c/src/parsed_repeated_field_value.cc new file mode 100644 index 0000000..f959b4f --- /dev/null +++ b/cel-c/src/parsed_repeated_field_value.cc @@ -0,0 +1,339 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "cel-c/src/parsed_repeated_field_value.h" + +#include +#include +#include +#include + +#include "cel-c/alloc.h" +#include "cel-c/assert.h" +#include "cel-c/config.h" +#include "cel-c/error.h" +#include "cel-c/error_code.h" +#include "cel-c/src/message_equality.h" +#include "cel-c/status.h" +#include "cel-c/status_code.h" +#include "cel-c/string_view.h" +#include "cel-c/value.h" +#include "upb/message/array.h" +#include "upb/message/message.h" +#include "upb/reflection/def.h" + +extern const cel_ListValueIteratorVTable + _cel_ParsedRepeatedFieldValueIteratorVTable; + +typedef struct { + cel_ListValueIterator base; + cel_Allocator* cel_nonnull alloc; + const upb_Message* cel_nonnull message; + const upb_FieldDef* cel_nonnull field_def; + const upb_Array* cel_nullability_unknown field_val; + size_t index; + size_t remain; +} _cel_ParsedRepeatedFieldValueIterator; + +static bool _cel_ParsedRepeatedFieldValue_Equals( + const cel_ListValueVTable* cel_nonnull vtable, cel_ValueContent content, + const cel_ValueContext* cel_nonnull context, + const cel_ListValue* cel_nonnull other, cel_Value* cel_nonnull result, + cel_Status* cel_nonnull status) { + CEL_ASSERT_EQ(vtable, &_cel_ParsedRepeatedFieldValueVTable); + CEL_ASSERT_NOT_NULL(context); + CEL_ASSERT_NOT_NULL(other); + CEL_ASSERT_NOT_NULL(result); + CEL_ASSERT_NOT_NULL(status); + + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return false; + } + + if (other->vtable == &_cel_ParsedRepeatedFieldValueVTable) { + upb_MessageValue field_val; + upb_MessageValue other_field_val; + field_val.array_val = (const upb_Array*)content.ptr[0]; + other_field_val.array_val = (const upb_Array*)other->content.ptr[0]; + const upb_FieldDef* field_def = (const upb_FieldDef*)content.ptr[1]; + const upb_FieldDef* other_field_def = + (const upb_FieldDef*)other->content.ptr[1]; + switch (_cel_MessageField_Equals( + field_val, field_def, other_field_val, other_field_def, + context->def_pool, context->well_known_types, context->alloc)) { + case _cel_MessageEquality_kEqual: + cel_Value_SetTrue(result); + return true; + case _cel_MessageEquality_kNotEqual: + cel_Value_SetFalse(result); + return true; + case _cel_MessageEquality_kOutOfMemory: + cel_OutOfMemoryStatus(status); + return false; + case _cel_MessageEquality_kMaxDepthExceeded: + cel_Status_SetCanonicalCode(status, cel_StatusCode_kInvalidArgument); + cel_Status_SetMessage( + status, cel_StringView_From("max message depth exceeded")); + return false; + } + } + + // Fallback to default implementation. + return false; +} + +static bool _cel_ParsedRepeatedFieldValue_FastSize( + const cel_ListValueVTable* cel_nonnull vtable, cel_ValueContent content, + size_t* cel_nonnull size) { + CEL_ASSERT_EQ(vtable, &_cel_ParsedRepeatedFieldValueVTable); + CEL_ASSERT_NOT_NULL(size); + + const upb_Array* field_val = (const upb_Array*)content.ptr[0]; + *size = field_val != cel_nullptr ? upb_Array_Size(field_val) : (size_t)0; + return true; +} + +static bool _cel_ParsedRepeatedFieldValue_SlowSize( + const cel_ListValueVTable* cel_nonnull vtable, cel_ValueContent content, + const cel_ValueContext* cel_nonnull context, cel_Value* cel_nonnull size, + cel_Status* cel_nonnull status) { + CEL_ASSERT_EQ(vtable, &_cel_ParsedRepeatedFieldValueVTable); + CEL_ASSERT_NOT_NULL(context); + CEL_ASSERT_NOT_NULL(size); + CEL_ASSERT_NOT_NULL(status); + + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return false; + } + + const upb_Array* field_val = (const upb_Array*)content.ptr[0]; + cel_Value_SetInt(size, field_val != cel_nullptr + ? (int64_t)upb_Array_Size(field_val) + : (int64_t)0); + return true; +} + +static bool _cel_ParsedRepeatedFieldValue_Get( + const cel_ListValueVTable* cel_nonnull vtable, cel_ValueContent content, + const cel_ValueContext* cel_nonnull context, size_t index, + cel_Value* cel_nonnull element, cel_Status* cel_nonnull status) { + CEL_ASSERT_EQ(vtable, &_cel_ParsedRepeatedFieldValueVTable); + CEL_ASSERT_NOT_NULL(context); + CEL_ASSERT_NOT_NULL(element); + CEL_ASSERT_NOT_NULL(status); + + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return false; + } + + const upb_FieldDef* field_def = (const upb_FieldDef*)content.ptr[1]; + const upb_Array* field_val = (const upb_Array*)content.ptr[0]; + + const size_t size = + field_val != cel_nullptr ? upb_Array_Size(field_val) : (size_t)0; + + if (CEL_UNLIKELY(index >= size)) { + cel_Error* error = cel_Error_New(context->arena); + if (CEL_UNLIKELY(error == cel_nullptr)) { + cel_OutOfMemoryStatus(status); + return false; + } + cel_Error_SetCanonicalCode(error, cel_ErrorCode_kOutOfRange); + cel_Error_SetMessage(error, cel_StringView_From("index out of range")); + cel_Value_SetError(element, error); + return true; + } + + return cel_Value_FromRepeatedFieldElement( + element, context, upb_Array_Get(field_val, index), field_def, status); +} + +static cel_ListValueIterator* cel_nullable +_cel_ParsedRepeatedFieldValue_NewIterator( + const cel_ListValueVTable* cel_nonnull vtable, cel_ValueContent content, + const cel_ValueContext* cel_nonnull context, + cel_Status* cel_nonnull status) { + CEL_ASSERT_EQ(vtable, &_cel_ParsedRepeatedFieldValueVTable); + CEL_ASSERT_NOT_NULL(context); + CEL_ASSERT_NOT_NULL(status); + + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return cel_nullptr; + } + + _cel_ParsedRepeatedFieldValueIterator* iter = + (_cel_ParsedRepeatedFieldValueIterator*)cel_Allocator_Malloc( + context->alloc, sizeof(_cel_ParsedRepeatedFieldValueIterator), + cel_nullptr); + if (CEL_UNLIKELY(iter == cel_nullptr)) { + cel_OutOfMemoryStatus(status); + return cel_nullptr; + } + memset(iter, 0, sizeof(*iter)); + iter->base.vtable = &_cel_ParsedRepeatedFieldValueIteratorVTable; + iter->alloc = context->alloc; + iter->field_def = (const upb_FieldDef*)content.ptr[1]; + iter->field_val = (const upb_Array*)content.ptr[0]; + iter->index = 0; + iter->remain = iter->field_val != cel_nullptr + ? upb_Array_Size(iter->field_val) + : (size_t)0; + return &iter->base; +} + +extern "C" const cel_ListValueVTable _cel_ParsedRepeatedFieldValueVTable = { + .Equals = &_cel_ParsedRepeatedFieldValue_Equals, + .FastSize = &_cel_ParsedRepeatedFieldValue_FastSize, + .SlowSize = &_cel_ParsedRepeatedFieldValue_SlowSize, + .Get = &_cel_ParsedRepeatedFieldValue_Get, + .NewIterator = &_cel_ParsedRepeatedFieldValue_NewIterator, +}; + +static void _cel_ParsedRepeatedFieldValueIterator_Delete( + cel_ValueIterator* cel_nonnull iterator) { + CEL_ASSERT_NOT_NULL(iterator); + CEL_ASSERT_EQ( + iterator->vtable, + (const cel_ValueIteratorVTable*)&_cel_ParsedRepeatedFieldValueIteratorVTable); + + _cel_ParsedRepeatedFieldValueIterator* iter = + cel_containerof(iterator, _cel_ParsedRepeatedFieldValueIterator, base); + cel_Allocator_FreeSized(iter->alloc, iter, sizeof(*iter)); +} + +static bool _cel_ParsedRepeatedFieldValueIterator_Next1( + cel_ValueIterator* cel_nonnull iterator, + const cel_ValueContext* cel_nonnull context, + cel_Value* cel_nonnull key_or_value, cel_Status* cel_nonnull status) { + CEL_ASSERT_NOT_NULL(iterator); + CEL_ASSERT_EQ( + iterator->vtable, + (const cel_ValueIteratorVTable*)&_cel_ParsedRepeatedFieldValueIteratorVTable); + CEL_ASSERT_NOT_NULL(context); + CEL_ASSERT_NOT_NULL(key_or_value); + CEL_ASSERT_NOT_NULL(status); + + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return false; + } + + _cel_ParsedRepeatedFieldValueIterator* iter = + cel_containerof(iterator, _cel_ParsedRepeatedFieldValueIterator, base); + if (iter->remain == 0) { + return false; + } + + const bool ok = cel_Value_FromRepeatedFieldElement( + key_or_value, context, upb_Array_Get(iter->field_val, iter->index), + iter->field_def, status); + if (ok) { + ++iter->index; + --iter->remain; + } + return ok; +} + +static bool _cel_ParsedRepeatedFieldValueIterator_Next2( + cel_ValueIterator* cel_nonnull iterator, + const cel_ValueContext* cel_nonnull context, cel_Value* cel_nonnull key, + cel_Value* cel_nonnull value, cel_Status* cel_nonnull status) { + CEL_ASSERT_NOT_NULL(iterator); + CEL_ASSERT_EQ( + iterator->vtable, + (const cel_ValueIteratorVTable*)&_cel_ParsedRepeatedFieldValueIteratorVTable); + CEL_ASSERT_NOT_NULL(context); + CEL_ASSERT_NOT_NULL(key); + CEL_ASSERT_NOT_NULL(value); + CEL_ASSERT_NOT_NULL(status); + + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return false; + } + + _cel_ParsedRepeatedFieldValueIterator* iter = + cel_containerof(iterator, _cel_ParsedRepeatedFieldValueIterator, base); + if (iter->remain == 0) { + return false; + } + + const bool ok = cel_Value_FromRepeatedFieldElement( + value, context, upb_Array_Get(iter->field_val, iter->index), + iter->field_def, status); + if (ok) { + cel_Value_SetInt(key, (int64_t)iter->index); + ++iter->index; + --iter->remain; + } + return ok; +} + +static bool _cel_ParsedRepeatedFieldValueIterator_Remaining( + const cel_ValueIterator* cel_nonnull iterator, + size_t* cel_nonnull remaining) { + CEL_ASSERT_NOT_NULL(iterator); + CEL_ASSERT_EQ( + iterator->vtable, + (const cel_ValueIteratorVTable*)&_cel_ParsedRepeatedFieldValueIteratorVTable); + CEL_ASSERT_NOT_NULL(remaining); + + _cel_ParsedRepeatedFieldValueIterator* iter = + cel_containerof(iterator, _cel_ParsedRepeatedFieldValueIterator, base); + *remaining = iter->remain; + return true; +} + +static bool _cel_ParsedRepeatedFieldValueIterator_Next( + cel_ListValueIterator* cel_nonnull iterator, + const cel_ValueContext* cel_nonnull context, size_t* cel_nullable index, + cel_Value* cel_nonnull value, cel_Status* cel_nonnull status) { + CEL_ASSERT_NOT_NULL(iterator); + CEL_ASSERT_EQ(iterator->vtable, &_cel_ParsedRepeatedFieldValueIteratorVTable); + CEL_ASSERT_NOT_NULL(context); + CEL_ASSERT_NOT_NULL(value); + CEL_ASSERT_NOT_NULL(status); + + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return false; + } + + _cel_ParsedRepeatedFieldValueIterator* iter = + cel_containerof(iterator, _cel_ParsedRepeatedFieldValueIterator, base); + if (iter->remain == 0) { + return false; + } + + const bool ok = cel_Value_FromRepeatedFieldElement( + value, context, upb_Array_Get(iter->field_val, iter->index), + iter->field_def, status); + if (ok) { + if (index != cel_nullptr) { + *index = iter->index; + } + ++iter->index; + --iter->remain; + } + return ok; +} + +const cel_ListValueIteratorVTable _cel_ParsedRepeatedFieldValueIteratorVTable = + { + .super = + { + .Delete = &_cel_ParsedRepeatedFieldValueIterator_Delete, + .Next1 = &_cel_ParsedRepeatedFieldValueIterator_Next1, + .Next2 = &_cel_ParsedRepeatedFieldValueIterator_Next2, + .Remaining = &_cel_ParsedRepeatedFieldValueIterator_Remaining, + }, + .Next = &_cel_ParsedRepeatedFieldValueIterator_Next, + }; diff --git a/cel-c/src/runtime/BUILD b/cel-c/src/runtime/BUILD index 1840b6e..0cc6995 100644 --- a/cel-c/src/runtime/BUILD +++ b/cel-c/src/runtime/BUILD @@ -22,11 +22,11 @@ package( cc_library( name = "runtime", srcs = [ - "activation.c", - "interpretable.c", - "interpreter.c", - "program.c", - "runtime.c", + "activation.cc", + "interpretable.cc", + "interpreter.cc", + "program.cc", + "runtime.cc", ], hdrs = [ "activation.h", diff --git a/cel-c/src/runtime/activation.cc b/cel-c/src/runtime/activation.cc new file mode 100644 index 0000000..491e232 --- /dev/null +++ b/cel-c/src/runtime/activation.cc @@ -0,0 +1,104 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "cel-c/src/runtime/activation.h" + +#include +#include +#include + +#include "cel-c/alloc.h" +#include "cel-c/arena.h" +#include "cel-c/assert.h" +#include "cel-c/config.h" +#include "cel-c/src/runtime/instr.h" +#include "cel-c/src/runtime/interpretable.h" +#include "cel-c/src/runtime/program.h" +#include "cel-c/src/runtime/runtime.h" +#include "cel-c/status.h" +#include "cel-c/string_view.h" +#include "cel-c/trilean.h" +#include "cel-c/value.h" + +extern "C" cel_ActivationOptions* cel_nonnull +cel_ActivationOptions_Default(cel_ActivationOptions* cel_nonnull opts) { + CEL_ASSERT_NOT_NULL(opts); + + memset(opts, 0, sizeof(*opts)); + return opts; +} + +void cel_Activation_Delete(cel_Activation* cel_nullable activation) { + if (activation == cel_nullptr) { + return; + } + const cel_Program* prog = activation->prog; + cel_Allocator_FreeSized(prog->rt->alloc, activation, activation->size); + _cel_Program_Unref(prog); +} + +extern "C" cel_Activation* cel_nullable +_cel_Activation_New(const cel_Program* cel_nonnull prog, + const cel_VariableResolver* cel_nonnull var_resolver) { + CEL_ASSERT_NOT_NULL(prog); + CEL_ASSERT_NOT_NULL(var_resolver); + + // Allocate the activation, value stack, slots, and call stack in a single + // allocation. + size_t size = sizeof(cel_Activation) + + (sizeof(cel_Value) * prog->max_stack_size) + + (sizeof(_cel_InterpretableSlot) * prog->max_slot_size) + + (sizeof(_cel_Instr*) * prog->max_slot_size); + char* addr = (char*)cel_Allocator_Malloc(prog->rt->alloc, size, cel_nullptr); + if (addr == cel_nullptr) { + return cel_nullptr; + } + memset(addr, 0, sizeof(cel_Activation)); + cel_Activation* act = (cel_Activation*)addr; + act->size = size; + act->prog = _cel_Program_ConstRef(prog); + act->var_resolver = var_resolver; + act->value_stack_base = (cel_Value*)(addr + sizeof(cel_Activation)); + act->value_stack_end = act->value_stack_base + prog->max_stack_size; + act->slots = (_cel_InterpretableSlot*)act->value_stack_end; + act->lazy_stack_base = (const _cel_Instr**)(act->slots + prog->max_slot_size); + act->lazy_stack_end = act->lazy_stack_base + prog->max_slot_size; + memset(act->slots, 0, sizeof(_cel_InterpretableSlot) * prog->max_slot_size); + _cel_Interpretable_Initialize(&act->interp, prog, act->value_stack_base, + act->value_stack_end - act->value_stack_base, + act->slots, act->prog->max_slot_size, + act->lazy_stack_base, + act->lazy_stack_end - act->lazy_stack_base); + return act; +} + +extern "C" cel_Trilean cel_Activation_FindVariable( + const cel_Activation* cel_nonnull activation, cel_StringView name, + cel_Value* cel_nonnull result, cel_Arena* cel_nonnull arena, + cel_Status* cel_nonnull status) { + return _cel_Activation_FindVariable(activation, name, result, arena, status); +} + +extern "C" bool cel_Activation_Execute(cel_Activation* cel_nonnull activation, + cel_Value* cel_nonnull result, + cel_Arena* cel_nonnull arena, + cel_Status* cel_nonnull status) { + CEL_ASSERT_NOT_NULL(activation); + CEL_ASSERT_NOT_NULL(result); + CEL_ASSERT_NOT_NULL(arena); + CEL_ASSERT(cel_Status_Ok(status)); + + return _cel_Interpretable_Interpret(&activation->interp, result, arena, + status); +} diff --git a/cel-c/src/runtime/interpretable.cc b/cel-c/src/runtime/interpretable.cc new file mode 100644 index 0000000..acece06 --- /dev/null +++ b/cel-c/src/runtime/interpretable.cc @@ -0,0 +1,3615 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "cel-c/src/runtime/interpretable.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "cel-c/activation.h" +#include "cel-c/arena.h" +#include "cel-c/assert.h" +#include "cel-c/config.h" +#include "cel-c/duration.h" +#include "cel-c/error.h" +#include "cel-c/error_code.h" +#include "cel-c/src/array.h" +#include "cel-c/src/charconv.h" +#include "cel-c/src/ckdint.h" +#include "cel-c/src/compare.h" +#include "cel-c/src/config.h" +#include "cel-c/src/durationconv.h" +#include "cel-c/src/empty_list_value.h" +#include "cel-c/src/empty_map_value.h" +#include "cel-c/src/mutable_list_value.h" +#include "cel-c/src/mutable_map_value.h" +#include "cel-c/src/number.h" +#include "cel-c/src/regexp.h" +#include "cel-c/src/runtime/activation.h" +#include "cel-c/src/runtime/instr.h" +#include "cel-c/src/runtime/program.h" +#include "cel-c/src/runtime/runtime.h" +#include "cel-c/src/setjmp.h" +#include "cel-c/src/string.h" +#include "cel-c/src/timestampconv.h" +#include "cel-c/src/utf8.h" +#include "cel-c/status.h" +#include "cel-c/string_view.h" +#include "cel-c/timestamp.h" +#include "cel-c/trilean.h" +#include "cel-c/value.h" +#include "cel-c/value_kind.h" +#include "upb/reflection/def.h" + +CEL_ATTRIBUTE_NODISCARD +static CEL_INLINE cel_Activation* _cel_Interpretable_ToActivation( + _cel_Interpretable* cel_nonnull interp) { + CEL_ASSERT_NOT_NULL(interp); + + return cel_containerof(interp, cel_Activation, interp); +} + +CEL_ATTRIBUTE_NORETURN +static CEL_INLINE void _cel_Interpretable_Throw( + _cel_Interpretable* cel_nonnull interp) { + CEL_ASSERT_NOT_NULL(interp); + CEL_ASSERT_NOT(cel_Status_Ok(interp->status)); + + _cel_longjmp(interp->jmp); +} + +CEL_ATTRIBUTE_NORETURN +static CEL_INLINE void _cel_Interpretable_ThrowOutOfMemory( + _cel_Interpretable* cel_nonnull interp) { + CEL_ASSERT_NOT_NULL(interp); + + cel_OutOfMemoryStatus(interp->status); + _cel_Interpretable_Throw(interp); +} + +CEL_ATTRIBUTE_NORETURN +static CEL_INLINE void _cel_Interpretable_ThrowStackOverflow( + _cel_Interpretable* cel_nonnull interp, const _cel_InstrData* instr) { + CEL_ASSERT_NOT_NULL(interp); + CEL_ASSERT_NOT_NULL(instr); + + cel_InternalStatusF( + interp->status, "cel: stack overflow: pc=%" PRIuPTR, + cel_containerof(instr, _cel_Instr, data) - interp->instr_ptr); + _cel_Interpretable_Throw(interp); +} + +CEL_ATTRIBUTE_NORETURN +static CEL_INLINE void _cel_Interpretable_ThrowStackUnderflow( + _cel_Interpretable* cel_nonnull interp, const _cel_InstrData* instr) { + CEL_ASSERT_NOT_NULL(interp); + CEL_ASSERT_NOT_NULL(instr); + + cel_InternalStatusF( + interp->status, "cel: stack underflow: pc=%" PRIuPTR, + cel_containerof(instr, _cel_Instr, data) - interp->instr_ptr); + _cel_Interpretable_Throw(interp); +} + +static CEL_INLINE void _cel_Interpretable_ThrowIfError( + _cel_Interpretable* cel_nonnull interp) { + if (CEL_UNLIKELY(!cel_Status_Ok(interp->status))) { + _cel_Interpretable_Throw(interp); + } +} + +CEL_ATTRIBUTE_NORETURN +static void _cel_Interpretable_Unreachable( + _cel_Interpretable* cel_nonnull interp, const _cel_InstrData* instr) { + CEL_ASSERT_NOT_NULL(interp); + CEL_ASSERT_NOT_NULL(instr); + + cel_InternalStatusF( + interp->status, "cel: unreachable code reached: pc=%" PRIuPTR, + cel_containerof(instr, _cel_Instr, data) - interp->instr_ptr); + _cel_Interpretable_Throw(interp); +} + +CEL_ATTRIBUTE_NODISCARD +static CEL_INLINE cel_StringView _cel_Interpretable_InternedString( + const _cel_Interpretable* cel_nonnull interp, uint32_t index) { + CEL_ASSERT_NOT_NULL(interp); + CEL_ASSERT_LT(index, interp->strings_table_len); + + return _cel_String_ToStringView(&interp->strings_table_ptr[index]); +} + +CEL_ATTRIBUTE_NODISCARD +static CEL_INLINE const _cel_CandidateNames* cel_nonnull +_cel_Interpretable_InternedCandidateNames( + const _cel_Interpretable* cel_nonnull interp, uint32_t index) { + CEL_ASSERT_NOT_NULL(interp); + CEL_ASSERT_LT(index, interp->candidate_names_len); + + return &interp->candidate_names_ptr[index]; +} + +CEL_ATTRIBUTE_NODISCARD +static CEL_INLINE cel_Value* cel_nonnull _cel_Interpretable_PushN( + _cel_Interpretable* cel_nonnull interp, + const _cel_InstrData* cel_nonnull instr, uint32_t count) { + CEL_ASSERT_NOT_NULL(interp); + CEL_ASSERT_NOT_NULL(instr); + CEL_ASSERT_GT(count, 0); + + cel_Value* top = interp->value_stack_top; + if (CEL_UNLIKELY(interp->value_stack_end - top < count)) { + _cel_Interpretable_ThrowStackOverflow(interp, instr); + } + interp->value_stack_top += count; + return top; +} + +CEL_ATTRIBUTE_NODISCARD +static CEL_INLINE cel_Value* cel_nonnull +_cel_Interpretable_Push(_cel_Interpretable* cel_nonnull interp, + const _cel_InstrData* cel_nonnull instr) { + return _cel_Interpretable_PushN(interp, instr, 1); +} + +static CEL_INLINE void _cel_Interpretable_PopN( + _cel_Interpretable* cel_nonnull interp, + const _cel_InstrData* cel_nonnull instr, uint32_t count) { + CEL_ASSERT_NOT_NULL(interp); + CEL_ASSERT_NOT_NULL(instr); + CEL_ASSERT_GT(count, 0); + + cel_Value* top = interp->value_stack_top; + if (CEL_UNLIKELY(top - interp->value_stack_base < count)) { + _cel_Interpretable_ThrowStackUnderflow(interp, instr); + } + interp->value_stack_top -= count; +} + +static CEL_INLINE void _cel_Interpretable_Pop( + _cel_Interpretable* cel_nonnull interp, + const _cel_InstrData* cel_nonnull instr) { + _cel_Interpretable_PopN(interp, instr, 1); +} + +CEL_ATTRIBUTE_NODISCARD +static CEL_INLINE cel_Value* cel_nonnull _cel_Interpretable_TopN( + _cel_Interpretable* cel_nonnull interp, + const _cel_InstrData* cel_nonnull instr, uint32_t count) { + CEL_ASSERT_NOT_NULL(interp); + CEL_ASSERT_NOT_NULL(instr); + CEL_ASSERT_GT(count, 0); + CEL_ASSERT(interp->value_stack_top >= interp->value_stack_base); + CEL_ASSERT(interp->value_stack_top <= interp->value_stack_end); + + cel_Value* top = interp->value_stack_top; + if (CEL_UNLIKELY(top - interp->value_stack_base < count)) { + _cel_Interpretable_ThrowStackUnderflow(interp, instr); + } + return top - count; +} + +CEL_ATTRIBUTE_NODISCARD +static CEL_INLINE cel_Value* cel_nonnull +_cel_Interpretable_Top(_cel_Interpretable* cel_nonnull interp, + const _cel_InstrData* cel_nonnull instr) { + return _cel_Interpretable_TopN(interp, instr, 1); +} + +CEL_ATTRIBUTE_NODISCARD +static CEL_INLINE cel_Value* _cel_Interpretable_PushAndPopN( + _cel_Interpretable* cel_nonnull interp, + const _cel_InstrData* cel_nonnull instr, uint32_t count) { + CEL_ASSERT_NOT_NULL(interp); + CEL_ASSERT_NOT_NULL(instr); + + if (count == 0) { + return _cel_Interpretable_Push(interp, instr); + } + if (count > 1) { + _cel_Interpretable_PopN(interp, instr, count - 1); + } + return _cel_Interpretable_Top(interp, instr); +} + +CEL_ATTRIBUTE_NODISCARD +static CEL_INLINE const _cel_Instr* cel_nonnull _cel_Interpretable_ShortJump( + const _cel_InstrData* cel_nonnull instr, int32_t offset) { + CEL_ASSERT_NOT_NULL(instr); + CEL_ASSERT_NE(offset, 0); + + return cel_containerof(instr, _cel_Instr, data) + offset; +} + +CEL_ATTRIBUTE_NODISCARD +static CEL_INLINE cel_Error* cel_nonnull +_cel_Interpretable_NewNoSuchOverloadError( + _cel_Interpretable* cel_nonnull interp) { + cel_Error* error = cel_Error_New(interp->arena); + if (CEL_UNLIKELY(error == cel_nullptr)) { + _cel_Interpretable_ThrowOutOfMemory(interp); + } + cel_Error_SetMessage(error, cel_StringView_From("cel: no such overload")); + cel_Error_SetCanonicalCode(error, cel_ErrorCode_kInvalidArgument); + return error; +} + +CEL_ATTRIBUTE_NODISCARD +static CEL_INLINE cel_Error* cel_nonnull +_cel_Interpretable_NewModuloByZeroError( + _cel_Interpretable* cel_nonnull interp) { + cel_Error* error = cel_Error_New(interp->arena); + if (CEL_UNLIKELY(error == cel_nullptr)) { + _cel_Interpretable_ThrowOutOfMemory(interp); + } + cel_Error_SetMessage(error, cel_StringView_From("cel: modulo by zero")); + cel_Error_SetCanonicalCode(error, cel_ErrorCode_kInvalidArgument); + return error; +} + +CEL_ATTRIBUTE_NODISCARD +static CEL_INLINE cel_Error* cel_nonnull +_cel_Interpretable_NewDivideByZeroError( + _cel_Interpretable* cel_nonnull interp) { + cel_Error* error = cel_Error_New(interp->arena); + if (CEL_UNLIKELY(error == cel_nullptr)) { + _cel_Interpretable_ThrowOutOfMemory(interp); + } + cel_Error_SetMessage(error, cel_StringView_From("cel: divide by zero")); + cel_Error_SetCanonicalCode(error, cel_ErrorCode_kInvalidArgument); + return error; +} + +CEL_ATTRIBUTE_NODISCARD +static CEL_INLINE cel_Error* cel_nonnull +_cel_Interpretable_NewIntOverflowError(_cel_Interpretable* cel_nonnull interp) { + cel_Error* error = cel_Error_New(interp->arena); + if (CEL_UNLIKELY(error == cel_nullptr)) { + _cel_Interpretable_ThrowOutOfMemory(interp); + } + cel_Error_SetMessage(error, cel_StringView_From("cel: int overflow")); + cel_Error_SetCanonicalCode(error, cel_ErrorCode_kInvalidArgument); + return error; +} + +CEL_ATTRIBUTE_NODISCARD +static CEL_INLINE cel_Error* cel_nonnull +_cel_Interpretable_NewUintOverflowError( + _cel_Interpretable* cel_nonnull interp) { + cel_Error* error = cel_Error_New(interp->arena); + if (CEL_UNLIKELY(error == cel_nullptr)) { + _cel_Interpretable_ThrowOutOfMemory(interp); + } + cel_Error_SetMessage(error, cel_StringView_From("cel: uint overflow")); + cel_Error_SetCanonicalCode(error, cel_ErrorCode_kInvalidArgument); + return error; +} + +CEL_ATTRIBUTE_NODISCARD +static CEL_INLINE cel_Error* cel_nonnull +_cel_Interpretable_NewDurationOverflowError( + _cel_Interpretable* cel_nonnull interp) { + cel_Error* error = cel_Error_New(interp->arena); + if (CEL_UNLIKELY(error == cel_nullptr)) { + _cel_Interpretable_ThrowOutOfMemory(interp); + } + cel_Error_SetMessage(error, cel_StringView_From("cel: duration overflow")); + cel_Error_SetCanonicalCode(error, cel_ErrorCode_kInvalidArgument); + return error; +} + +CEL_ATTRIBUTE_NODISCARD +static CEL_INLINE cel_Error* cel_nonnull +_cel_Interpretable_NewTimestampOverflowError( + _cel_Interpretable* cel_nonnull interp) { + cel_Error* error = cel_Error_New(interp->arena); + if (CEL_UNLIKELY(error == cel_nullptr)) { + _cel_Interpretable_ThrowOutOfMemory(interp); + } + cel_Error_SetMessage(error, cel_StringView_From("cel: int overflow")); + cel_Error_SetCanonicalCode(error, cel_ErrorCode_kInvalidArgument); + return error; +} + +CEL_ATTRIBUTE_NODISCARD +static CEL_INLINE cel_Error* cel_nonnull +_cel_Interpretable_NewDuplicateKeyError( + _cel_Interpretable* cel_nonnull interp) { + cel_Error* error = cel_Error_New(interp->arena); + if (CEL_UNLIKELY(error == cel_nullptr)) { + _cel_Interpretable_ThrowOutOfMemory(interp); + } + cel_Error_SetMessage(error, cel_StringView_From("cel: duplicate key in map")); + cel_Error_SetCanonicalCode(error, cel_ErrorCode_kAlreadyExists); + return error; +} + +CEL_ATTRIBUTE_NODISCARD +static CEL_INLINE cel_Error* cel_nonnull +_cel_Interpretable_NewBadKeyError(_cel_Interpretable* cel_nonnull interp) { + cel_Error* error = cel_Error_New(interp->arena); + if (CEL_UNLIKELY(error == cel_nullptr)) { + _cel_Interpretable_ThrowOutOfMemory(interp); + } + cel_Error_SetMessage(error, cel_StringView_From("cel: bad map key")); + cel_Error_SetCanonicalCode(error, cel_ErrorCode_kInvalidArgument); + return error; +} + +CEL_ATTRIBUTE_NODISCARD +static CEL_INLINE cel_Error* cel_nonnull +_cel_Interpretable_NewIndexOutOfRangeError( + _cel_Interpretable* cel_nonnull interp) { + cel_Error* error = cel_Error_New(interp->arena); + if (CEL_UNLIKELY(error == cel_nullptr)) { + _cel_Interpretable_ThrowOutOfMemory(interp); + } + cel_Error_SetMessage(error, cel_StringView_From("cel: index out of range")); + cel_Error_SetCanonicalCode(error, cel_ErrorCode_kInvalidArgument); + return error; +} + +CEL_ATTRIBUTE_NODISCARD +static CEL_INLINE cel_Error* cel_nonnull +_cel_Interpretable_NewNoSuchKeyError(_cel_Interpretable* cel_nonnull interp) { + cel_Error* error = cel_Error_New(interp->arena); + if (CEL_UNLIKELY(error == cel_nullptr)) { + _cel_Interpretable_ThrowOutOfMemory(interp); + } + cel_Error_SetMessage(error, cel_StringView_From("cel: no such key in map")); + cel_Error_SetCanonicalCode(error, cel_ErrorCode_kNotFound); + return error; +} + +static inline void _cel_Interpretable_NullConst( + _cel_Interpretable* cel_nonnull interp, + const _cel_InstrData* cel_nonnull instr) { + CEL_ASSERT_NOT_NULL(interp); + CEL_ASSERT_NOT_NULL(instr); + + cel_Value_SetNull(_cel_Interpretable_Push(interp, instr)); +} + +static inline void _cel_Interpretable_FalseConst( + _cel_Interpretable* cel_nonnull interp, + const _cel_InstrData* cel_nonnull instr) { + CEL_ASSERT_NOT_NULL(interp); + CEL_ASSERT_NOT_NULL(instr); + + cel_Value_SetFalse(_cel_Interpretable_Push(interp, instr)); +} + +static inline void _cel_Interpretable_TrueConst( + _cel_Interpretable* cel_nonnull interp, + const _cel_InstrData* cel_nonnull instr) { + CEL_ASSERT_NOT_NULL(interp); + CEL_ASSERT_NOT_NULL(instr); + + cel_Value_SetTrue(_cel_Interpretable_Push(interp, instr)); +} + +static inline void _cel_Interpretable_IntConst( + _cel_Interpretable* cel_nonnull interp, + const _cel_InstrData* cel_nonnull instr) { + CEL_ASSERT_NOT_NULL(interp); + CEL_ASSERT_NOT_NULL(instr); + + cel_Value_SetInt(_cel_Interpretable_Push(interp, instr), + instr->int_const.value); +} + +static inline void _cel_Interpretable_UintConst( + _cel_Interpretable* cel_nonnull interp, + const _cel_InstrData* cel_nonnull instr) { + CEL_ASSERT_NOT_NULL(interp); + CEL_ASSERT_NOT_NULL(instr); + + cel_Value_SetUint(_cel_Interpretable_Push(interp, instr), + instr->uint_const.value); +} + +static inline void _cel_Interpretable_DoubleConst( + _cel_Interpretable* cel_nonnull interp, + const _cel_InstrData* cel_nonnull instr) { + CEL_ASSERT_NOT_NULL(interp); + CEL_ASSERT_NOT_NULL(instr); + + cel_Value_SetDouble(_cel_Interpretable_Push(interp, instr), + instr->double_const.value); +} + +static inline void _cel_Interpretable_BytesConst( + _cel_Interpretable* cel_nonnull interp, + const _cel_InstrData* cel_nonnull instr) { + CEL_ASSERT_NOT_NULL(interp); + CEL_ASSERT_NOT_NULL(instr); + + cel_Value_SetBytes( + _cel_Interpretable_Push(interp, instr), + _cel_PackedStringView_ToStringView(instr->bytes_const.value.direct)); +} + +static inline void _cel_Interpretable_StringConst( + _cel_Interpretable* cel_nonnull interp, + const _cel_InstrData* cel_nonnull instr) { + CEL_ASSERT_NOT_NULL(interp); + CEL_ASSERT_NOT_NULL(instr); + + cel_Value_SetString( + _cel_Interpretable_Push(interp, instr), + _cel_PackedStringView_ToStringView(instr->string_const.value.direct)); +} + +static inline void _cel_Interpretable_DurationConst( + _cel_Interpretable* cel_nonnull interp, + const _cel_InstrData* cel_nonnull instr) { + CEL_ASSERT_NOT_NULL(interp); + CEL_ASSERT_NOT_NULL(instr); + + cel_Value_SetDuration(_cel_Interpretable_Push(interp, instr), + instr->duration_const.value); +} + +static inline void _cel_Interpretable_TimestampConst( + _cel_Interpretable* cel_nonnull interp, + const _cel_InstrData* cel_nonnull instr) { + CEL_ASSERT_NOT_NULL(interp); + CEL_ASSERT_NOT_NULL(instr); + + cel_Value_SetTimestamp(_cel_Interpretable_Push(interp, instr), + instr->timestamp_const.value); +} + +static inline void _cel_Interpretable_Ident( + _cel_Interpretable* cel_nonnull interp, + const _cel_InstrData* cel_nonnull instr) { + CEL_ASSERT_NOT_NULL(interp); + CEL_ASSERT_NOT_NULL(instr); + + cel_StringView name = + _cel_PackedStringView_ToStringView(instr->ident.name.direct); + cel_Value* top = _cel_Interpretable_Push(interp, instr); + switch (_cel_Activation_FindVariable(_cel_Interpretable_ToActivation(interp), + name, top, interp->arena, + interp->status)) { + case cel_Trilean_kFalse: { + CEL_ASSERT(cel_Status_Ok(interp->status)); + // Did not find the variable. + cel_Error* error = cel_Error_New(interp->arena); + if (CEL_UNLIKELY(error == cel_nullptr)) { + _cel_Interpretable_ThrowOutOfMemory(interp); + } + cel_Error_SetCanonicalCode(error, cel_ErrorCode_kNotFound); + if (CEL_UNLIKELY( + !cel_Error_FormatMessage(error, interp->arena, + "cel: variable binding not found in " + "activation: " CEL_STRINGVIEW_FMT, + CEL_STRINGVIEW_ARGS(name)))) { + _cel_Interpretable_ThrowOutOfMemory(interp); + } + cel_Value_SetError(top, error); + } break; + case cel_Trilean_kTrue: + CEL_ASSERT(cel_Status_Ok(interp->status)); + break; + case cel_Trilean_kError: + _cel_Interpretable_Throw(interp); + default: + CEL_UNREACHABLE(); + } +} + +CEL_ATTRIBUTE_NODISCARD +static inline const _cel_Instr* cel_nonnull +_cel_Interpretable_IdentJump(_cel_Interpretable* cel_nonnull interp, + const _cel_InstrData* cel_nonnull instr) { + CEL_ASSERT_NOT_NULL(interp); + CEL_ASSERT_NOT_NULL(instr); + CEL_ASSERT_NE(instr->ident_jump.found_jump, 0); + CEL_ASSERT_NE(instr->ident_jump.missing_jump, 0); + + ptrdiff_t jump; + + cel_StringView name = + _cel_Interpretable_InternedString(interp, instr->ident_jump.name); + cel_Value* top = _cel_Interpretable_Push(interp, instr); + switch (_cel_Activation_FindVariable(_cel_Interpretable_ToActivation(interp), + name, top, interp->arena, + interp->status)) { + case cel_Trilean_kFalse: + CEL_ASSERT(cel_Status_Ok(interp->status)); + if (instr->ident_jump.missing_error) { + // Did not find the variable. + cel_Error* error = cel_Error_New(interp->arena); + if (CEL_UNLIKELY(error == cel_nullptr)) { + _cel_Interpretable_ThrowOutOfMemory(interp); + } + cel_Error_SetCanonicalCode(error, cel_ErrorCode_kNotFound); + if (CEL_UNLIKELY( + !cel_Error_FormatMessage(error, interp->arena, + "cel: variable binding not found in " + "activation: " CEL_STRINGVIEW_FMT, + CEL_STRINGVIEW_ARGS(name)))) { + _cel_Interpretable_ThrowOutOfMemory(interp); + } + cel_Value_SetError(top, error); + } else { + _cel_Interpretable_Pop(interp, instr); + } + jump = instr->ident_jump.missing_jump; + break; + case cel_Trilean_kTrue: + CEL_ASSERT(cel_Status_Ok(interp->status)); + jump = instr->ident_jump.found_jump; + break; + case cel_Trilean_kError: + _cel_Interpretable_Pop(interp, instr); + _cel_Interpretable_Throw(interp); + default: + CEL_UNREACHABLE(); + } + return _cel_Interpretable_ShortJump(instr, jump); +} + +static inline void _cel_Interpretable_ContIdent( + _cel_Interpretable* cel_nonnull interp, + const _cel_InstrData* cel_nonnull instr) { + CEL_ASSERT_NOT_NULL(interp); + CEL_ASSERT_NOT_NULL(instr); + + cel_Value* top = _cel_Interpretable_Push(interp, instr); + const _cel_CandidateNames* candidate_names = + _cel_Interpretable_InternedCandidateNames( + interp, instr->cont_ident.candidate_names); + for (size_t i = 0; i < candidate_names->size; ++i) { + switch (_cel_Activation_FindVariable( + _cel_Interpretable_ToActivation(interp), + _cel_Interpretable_InternedString(interp, candidate_names->data[i]), + top, interp->arena, interp->status)) { + case cel_Trilean_kFalse: + CEL_ASSERT(cel_Status_Ok(interp->status)); + break; + case cel_Trilean_kTrue: + CEL_ASSERT(cel_Status_Ok(interp->status)); + return; + case cel_Trilean_kError: + _cel_Interpretable_Throw(interp); + default: + CEL_UNREACHABLE(); + } + } + // Did not find the variable. + cel_Error* error = cel_Error_New(interp->arena); + if (CEL_UNLIKELY(error == cel_nullptr)) { + _cel_Interpretable_ThrowOutOfMemory(interp); + } + cel_Error_SetCanonicalCode(error, cel_ErrorCode_kNotFound); + if (CEL_UNLIKELY(!cel_Error_FormatMessage( + error, interp->arena, + "cel: variable binding not found in " + "activation: " CEL_STRINGVIEW_FMT, + CEL_STRINGVIEW_ARGS(_cel_Interpretable_InternedString( + interp, candidate_names->data[candidate_names->size - 1]))))) { + _cel_Interpretable_ThrowOutOfMemory(interp); + } + cel_Value_SetError(top, error); +} + +CEL_ATTRIBUTE_NODISCARD +static inline const _cel_Instr* cel_nonnull +_cel_Interpretable_ContIdentJump(_cel_Interpretable* cel_nonnull interp, + const _cel_InstrData* cel_nonnull instr) { + CEL_ASSERT_NOT_NULL(interp); + CEL_ASSERT_NOT_NULL(instr); + CEL_ASSERT_NE(instr->cont_ident_jump.found_jump, 0); + CEL_ASSERT_NE(instr->cont_ident_jump.missing_jump, 0); + + cel_Value* top = _cel_Interpretable_Push(interp, instr); + const _cel_CandidateNames* candidate_names = + _cel_Interpretable_InternedCandidateNames( + interp, instr->cont_ident_jump.candidate_names); + for (size_t i = 0; i < candidate_names->size; ++i) { + switch (_cel_Activation_FindVariable( + _cel_Interpretable_ToActivation(interp), + _cel_Interpretable_InternedString(interp, candidate_names->data[i]), + top, interp->arena, interp->status)) { + case cel_Trilean_kFalse: + CEL_ASSERT(cel_Status_Ok(interp->status)); + break; + case cel_Trilean_kTrue: + CEL_ASSERT(cel_Status_Ok(interp->status)); + return _cel_Interpretable_ShortJump(instr, + instr->cont_ident_jump.found_jump); + case cel_Trilean_kError: + _cel_Interpretable_Pop(interp, instr); + _cel_Interpretable_Throw(interp); + default: + CEL_UNREACHABLE(); + } + } + if (instr->cont_ident_jump.missing_error) { + // Did not find the variable. + cel_Error* error = cel_Error_New(interp->arena); + if (CEL_UNLIKELY(error == cel_nullptr)) { + _cel_Interpretable_ThrowOutOfMemory(interp); + } + cel_Error_SetCanonicalCode(error, cel_ErrorCode_kNotFound); + if (CEL_UNLIKELY(!cel_Error_FormatMessage( + error, interp->arena, + "cel: variable binding not found in " + "activation: " CEL_STRINGVIEW_FMT, + CEL_STRINGVIEW_ARGS(_cel_Interpretable_InternedString( + interp, candidate_names->data[candidate_names->size - 1]))))) { + _cel_Interpretable_ThrowOutOfMemory(interp); + } + cel_Value_SetError(top, error); + } else { + _cel_Interpretable_Pop(interp, instr); + } + return _cel_Interpretable_ShortJump(instr, + instr->cont_ident_jump.missing_jump); +} + +CEL_ATTRIBUTE_NODISCARD +static inline const _cel_Instr* cel_nonnull +_cel_Interpretable_Jump(_cel_Interpretable* cel_nonnull interp, + const _cel_InstrData* cel_nonnull instr) { + CEL_ASSERT_NOT_NULL(interp); + CEL_ASSERT_NOT_NULL(instr); + CEL_ASSERT_NE(instr->jump.jump, 0); + + return _cel_Interpretable_ShortJump(instr, instr->jump.jump); +} + +static inline void _cel_Interpretable_Has( + _cel_Interpretable* cel_nonnull interp, + const _cel_InstrData* cel_nonnull instr) { + cel_Value* top = _cel_Interpretable_Top(interp, instr); + switch (cel_Value_Kind(top)) { + case cel_ValueKind_kError: + break; + case cel_ValueKind_kMap: { + cel_MapValue map_value = *cel_Value_GetMap(top); + cel_MapValueKey map_value_key; + cel_MapValueKey_SetString( + &map_value_key, + _cel_PackedStringView_ToStringView(instr->has.field.direct)); + if (CEL_UNLIKELY(!cel_MapValue_Has(&map_value, &interp->context, + &map_value_key, top, + interp->status))) { + _cel_Interpretable_Throw(interp); + } + } break; + case cel_ValueKind_kStruct: { + cel_StructValue struct_value = *cel_Value_GetStruct(top); + cel_StructValueKey struct_value_key; + cel_StructValueKey_SetName( + &struct_value_key, + _cel_PackedStringView_ToStringView(instr->has.field.direct)); + if (CEL_UNLIKELY(!cel_StructValue_Has(&struct_value, &interp->context, + &struct_value_key, top, + interp->status))) { + _cel_Interpretable_Throw(interp); + } + } break; + default: { + cel_Error* error = cel_Error_New(interp->arena); + if (CEL_UNLIKELY(error == cel_nullptr)) { + _cel_Interpretable_ThrowOutOfMemory(interp); + } + cel_Error_SetCanonicalCode(error, cel_ErrorCode_kInvalidArgument); + cel_Value_SetError(top, error); + } break; + } +} + +// Similar to _cel_Interpretable_Has, except this is used when we know the +// underlying field is in a message. This is an optimization and allows us to +// avoid looking up the field by reflection everytime. +static inline void _cel_Interpretable_MessageHas( + _cel_Interpretable* cel_nonnull interp, + const _cel_InstrData* cel_nonnull instr) { + CEL_ASSERT_NOT_NULL(interp); + CEL_ASSERT_NOT_NULL(instr); + + cel_Value* top = _cel_Interpretable_Top(interp, instr); + switch (cel_Value_Kind(top)) { + case cel_ValueKind_kError: + break; + case cel_ValueKind_kMap: { + cel_MapValue map_value = *cel_Value_GetMap(top); + cel_MapValueKey map_value_key; + cel_MapValueKey_SetString( + &map_value_key, cel_StringView_FromString( + upb_FieldDef_Name(instr->message_has.field))); + if (CEL_UNLIKELY(!cel_MapValue_Has(&map_value, &interp->context, + &map_value_key, top, + interp->status))) { + _cel_Interpretable_Throw(interp); + } + } break; + case cel_ValueKind_kStruct: { + cel_StructValue struct_value = *cel_Value_GetStruct(top); + cel_StructValueKey struct_value_key; + cel_StructValueKey_SetDef(&struct_value_key, instr->message_has.field); + if (CEL_UNLIKELY(!cel_StructValue_Has(&struct_value, &interp->context, + &struct_value_key, top, + interp->status))) { + _cel_Interpretable_Throw(interp); + } + } break; + default: { + cel_Error* error = cel_Error_New(interp->arena); + if (CEL_UNLIKELY(error == cel_nullptr)) { + _cel_Interpretable_ThrowOutOfMemory(interp); + } + cel_Error_SetCanonicalCode(error, cel_ErrorCode_kInvalidArgument); + cel_Value_SetError(top, error); + } break; + } +} + +static inline void _cel_Interpretable_Select( + _cel_Interpretable* cel_nonnull interp, + const _cel_InstrData* cel_nonnull instr) { + CEL_ASSERT_NOT_NULL(interp); + CEL_ASSERT_NOT_NULL(instr); + + cel_Value* top = _cel_Interpretable_Top(interp, instr); + switch (cel_Value_Kind(top)) { + case cel_ValueKind_kError: + break; + case cel_ValueKind_kMap: { + cel_MapValue map_value = *cel_Value_GetMap(top); + cel_MapValueKey map_value_key; + cel_MapValueKey_SetString( + &map_value_key, + _cel_PackedStringView_ToStringView(instr->select.field.direct)); + if (CEL_UNLIKELY(!cel_MapValue_Get(&map_value, &interp->context, + &map_value_key, top, + interp->status))) { + _cel_Interpretable_Throw(interp); + } + } break; + case cel_ValueKind_kStruct: { + cel_StructValue struct_value = *cel_Value_GetStruct(top); + cel_StructValueKey struct_value_key; + cel_StructValueKey_SetName( + &struct_value_key, + _cel_PackedStringView_ToStringView(instr->select.field.direct)); + if (CEL_UNLIKELY(!cel_StructValue_Get(&struct_value, &interp->context, + &struct_value_key, top, + interp->status))) { + _cel_Interpretable_Throw(interp); + } + } break; + default: { + cel_Error* error = cel_Error_New(interp->arena); + if (CEL_UNLIKELY(error == cel_nullptr)) { + _cel_Interpretable_ThrowOutOfMemory(interp); + } + cel_Error_SetCanonicalCode(error, cel_ErrorCode_kInvalidArgument); + cel_Value_SetError(top, error); + } break; + } +} + +// Similar to _cel_Interpretable_Select, except this is used when we know the +// underlying field is in a message. This is an optimization and allows us to +// avoid looking up the field by reflection everytime. +static inline void _cel_Interpretable_MessageSelect( + _cel_Interpretable* cel_nonnull interp, + const _cel_InstrData* cel_nonnull instr) { + CEL_ASSERT_NOT_NULL(interp); + CEL_ASSERT_NOT_NULL(instr); + + cel_Value* top = _cel_Interpretable_Top(interp, instr); + switch (cel_Value_Kind(top)) { + case cel_ValueKind_kError: + break; + case cel_ValueKind_kMap: { + cel_MapValue map_value = *cel_Value_GetMap(top); + cel_MapValueKey map_value_key; + cel_MapValueKey_SetString( + &map_value_key, cel_StringView_FromString( + upb_FieldDef_Name(instr->message_has.field))); + if (CEL_UNLIKELY(!cel_MapValue_Get(&map_value, &interp->context, + &map_value_key, top, + interp->status))) { + _cel_Interpretable_Throw(interp); + } + } break; + case cel_ValueKind_kStruct: { + cel_StructValue struct_value = *cel_Value_GetStruct(top); + cel_StructValueKey struct_value_key; + cel_StructValueKey_SetDef(&struct_value_key, instr->message_has.field); + if (CEL_UNLIKELY(!cel_StructValue_Get(&struct_value, &interp->context, + &struct_value_key, top, + interp->status))) { + _cel_Interpretable_Throw(interp); + } + } break; + default: { + cel_Error* error = cel_Error_New(interp->arena); + if (CEL_UNLIKELY(error == cel_nullptr)) { + _cel_Interpretable_ThrowOutOfMemory(interp); + } + cel_Error_SetCanonicalCode(error, cel_ErrorCode_kInvalidArgument); + cel_Value_SetError(top, error); + } break; + } +} + +CEL_ATTRIBUTE_NODISCARD +static inline cel_StringView _cel_Interpretable_Concat( + _cel_Interpretable* cel_nonnull interp, cel_StringView lhs, + cel_StringView rhs) { + CEL_ASSERT_NOT_NULL(interp); + + size_t lhs_size = cel_StringView_Size(lhs); + size_t rhs_size = cel_StringView_Size(rhs); + if (lhs_size == 0) { + return rhs; + } + if (rhs_size == 0) { + return lhs; + } + size_t size; + if (_cel_ckd_add(&size, lhs_size, rhs_size) || + size > (size_t)(uint32_t)INT32_MAX) { + _cel_Interpretable_ThrowOutOfMemory(interp); + } + char* data = reinterpret_cast( + cel_Arena_Malloc(interp->arena, size, cel_nullptr)); + if (CEL_UNLIKELY(data == cel_nullptr)) { + _cel_Interpretable_ThrowOutOfMemory(interp); + } + memcpy(data, cel_StringView_Data(lhs), lhs_size); + memcpy(data + lhs_size, cel_StringView_Data(rhs), rhs_size); + return cel_StringView_FromArray(data, size); +} + +static inline void _cel_Interpretable_Add( + _cel_Interpretable* cel_nonnull interp, + const _cel_InstrData* cel_nonnull instr) { + CEL_ASSERT_NOT_NULL(interp); + CEL_ASSERT_NOT_NULL(instr); + + cel_Value* const top = _cel_Interpretable_TopN(interp, instr, 2); + cel_Value* const lhs = top; + cel_Value* const rhs = lhs + 1; + switch (cel_Value_Kind(lhs)) { + case cel_ValueKind_kError: + break; + case cel_ValueKind_kInt: { + switch (cel_Value_Kind(rhs)) { + case cel_ValueKind_kError: + *top = *rhs; + break; + case cel_ValueKind_kInt: { + int64_t result; + if (_cel_ckd_add(&result, cel_Value_GetInt(lhs), + cel_Value_GetInt(rhs))) { + cel_Value_SetError(top, + _cel_Interpretable_NewIntOverflowError(interp)); + break; + } + cel_Value_SetInt(top, result); + } break; + default: + cel_Value_SetError(top, + _cel_Interpretable_NewNoSuchOverloadError(interp)); + break; + } + } break; + case cel_ValueKind_kUint: { + switch (cel_Value_Kind(rhs)) { + case cel_ValueKind_kError: + *top = *rhs; + break; + case cel_ValueKind_kUint: { + uint64_t result; + if (_cel_ckd_add(&result, cel_Value_GetUint(lhs), + cel_Value_GetUint(rhs))) { + cel_Value_SetError(top, + _cel_Interpretable_NewUintOverflowError(interp)); + break; + } + cel_Value_SetUint(top, result); + } break; + default: + cel_Value_SetError(top, + _cel_Interpretable_NewNoSuchOverloadError(interp)); + break; + } + } break; + case cel_ValueKind_kDouble: { + switch (cel_Value_Kind(rhs)) { + case cel_ValueKind_kError: + *top = *rhs; + break; + case cel_ValueKind_kDouble: + cel_Value_SetDouble( + top, cel_Value_GetDouble(lhs) + cel_Value_GetDouble(rhs)); + break; + default: + cel_Value_SetError(top, + _cel_Interpretable_NewNoSuchOverloadError(interp)); + break; + } + } break; + case cel_ValueKind_kBytes: { + switch (cel_Value_Kind(rhs)) { + case cel_ValueKind_kError: + *top = *rhs; + break; + case cel_ValueKind_kBytes: + cel_Value_SetBytes( + top, _cel_Interpretable_Concat(interp, cel_Value_GetBytes(lhs), + cel_Value_GetBytes(rhs))); + break; + default: + cel_Value_SetError(top, + _cel_Interpretable_NewNoSuchOverloadError(interp)); + break; + } + } break; + case cel_ValueKind_kString: { + switch (cel_Value_Kind(rhs)) { + case cel_ValueKind_kError: + *top = *rhs; + break; + case cel_ValueKind_kString: + cel_Value_SetString( + top, _cel_Interpretable_Concat(interp, cel_Value_GetString(lhs), + cel_Value_GetString(rhs))); + break; + default: + cel_Value_SetError(top, + _cel_Interpretable_NewNoSuchOverloadError(interp)); + break; + } + } break; + case cel_ValueKind_kDuration: { + switch (cel_Value_Kind(rhs)) { + case cel_ValueKind_kError: + *top = *rhs; + break; + case cel_ValueKind_kDuration: { + cel_Duration result; + if (!cel_Duration_Add(&result, cel_Value_GetDuration(lhs), + cel_Value_GetDuration(rhs))) { + cel_Value_SetError( + top, _cel_Interpretable_NewDurationOverflowError(interp)); + break; + } + cel_Value_SetDuration(top, result); + } break; + case cel_ValueKind_kTimestamp: { + cel_Timestamp result; + if (!cel_Timestamp_Add(&result, cel_Value_GetTimestamp(rhs), + cel_Value_GetDuration(lhs))) { + cel_Value_SetError( + top, _cel_Interpretable_NewTimestampOverflowError(interp)); + break; + } + cel_Value_SetTimestamp(top, result); + } break; + default: + cel_Value_SetError(top, + _cel_Interpretable_NewNoSuchOverloadError(interp)); + break; + } + } break; + case cel_ValueKind_kTimestamp: { + switch (cel_Value_Kind(rhs)) { + case cel_ValueKind_kError: + *top = *rhs; + break; + case cel_ValueKind_kDuration: { + cel_Timestamp result; + if (!cel_Timestamp_Add(&result, cel_Value_GetTimestamp(lhs), + cel_Value_GetDuration(rhs))) { + cel_Value_SetError( + top, _cel_Interpretable_NewTimestampOverflowError(interp)); + break; + } + cel_Value_SetTimestamp(top, result); + } break; + default: + cel_Value_SetError(top, + _cel_Interpretable_NewNoSuchOverloadError(interp)); + break; + } + } break; + default: + if (cel_Value_IsError(rhs)) { + *top = *rhs; + break; + } + cel_Value_SetError(top, + _cel_Interpretable_NewNoSuchOverloadError(interp)); + break; + } + _cel_Interpretable_Pop(interp, instr); +} + +static inline void _cel_Interpretable_Subtract( + _cel_Interpretable* cel_nonnull interp, + const _cel_InstrData* cel_nonnull instr) { + CEL_ASSERT_NOT_NULL(interp); + CEL_ASSERT_NOT_NULL(instr); + + cel_Value* const top = _cel_Interpretable_TopN(interp, instr, 2); + cel_Value* const lhs = top; + cel_Value* const rhs = lhs + 1; + switch (cel_Value_Kind(lhs)) { + case cel_ValueKind_kError: + // error - T -> error + break; + case cel_ValueKind_kInt: { + switch (cel_Value_Kind(rhs)) { + case cel_ValueKind_kError: + // int - error -> error + *top = *rhs; + break; + case cel_ValueKind_kInt: { + // int - int -> int + int64_t result; + if (_cel_ckd_sub(&result, cel_Value_GetInt(lhs), + cel_Value_GetInt(rhs))) { + cel_Value_SetError(top, + _cel_Interpretable_NewIntOverflowError(interp)); + break; + } + cel_Value_SetInt(top, result); + } break; + default: + cel_Value_SetError(top, + _cel_Interpretable_NewNoSuchOverloadError(interp)); + break; + } + } break; + case cel_ValueKind_kUint: { + switch (cel_Value_Kind(rhs)) { + case cel_ValueKind_kError: + // uint - error -> error + *top = *rhs; + *top = *rhs; + break; + case cel_ValueKind_kUint: { + // uint - uint -> uint + uint64_t result; + if (_cel_ckd_sub(&result, cel_Value_GetUint(lhs), + cel_Value_GetUint(rhs))) { + cel_Value_SetError(top, + _cel_Interpretable_NewUintOverflowError(interp)); + break; + } + cel_Value_SetUint(top, result); + } break; + default: + cel_Value_SetError(top, + _cel_Interpretable_NewNoSuchOverloadError(interp)); + break; + } + } break; + case cel_ValueKind_kDouble: { + switch (cel_Value_Kind(rhs)) { + case cel_ValueKind_kError: + // double - error -> error + *top = *rhs; + break; + case cel_ValueKind_kDouble: + // double - double -> double + cel_Value_SetDouble( + top, cel_Value_GetDouble(lhs) - cel_Value_GetDouble(rhs)); + break; + default: + cel_Value_SetError(top, + _cel_Interpretable_NewNoSuchOverloadError(interp)); + break; + } + } break; + case cel_ValueKind_kDuration: { + switch (cel_Value_Kind(rhs)) { + case cel_ValueKind_kError: + // google.protobuf.Duration - error -> error + *top = *rhs; + break; + case cel_ValueKind_kDuration: { + // google.protobuf.Duration - google.protobuf.Duration -> + // google.protobuf.Duration + cel_Duration result; + if (!cel_Duration_Sub(&result, cel_Value_GetDuration(lhs), + cel_Value_GetDuration(rhs))) { + cel_Value_SetError( + top, _cel_Interpretable_NewDurationOverflowError(interp)); + break; + } + cel_Value_SetDuration(top, result); + } break; + default: + cel_Value_SetError(top, + _cel_Interpretable_NewNoSuchOverloadError(interp)); + break; + } + } break; + case cel_ValueKind_kTimestamp: { + switch (cel_Value_Kind(rhs)) { + case cel_ValueKind_kError: + // google.protobuf.Timestamp - error -> error + *top = *rhs; + break; + case cel_ValueKind_kDuration: { + // google.protobuf.Timestamp - google.protobuf.Duration -> + // google.protobuf.Timestamp + cel_Timestamp result; + if (!cel_Timestamp_Sub(&result, cel_Value_GetTimestamp(lhs), + cel_Value_GetDuration(rhs))) { + cel_Value_SetError( + top, _cel_Interpretable_NewTimestampOverflowError(interp)); + break; + } + cel_Value_SetTimestamp(top, result); + } break; + case cel_ValueKind_kTimestamp: { + // google.protobuf.Timestamp - google.protobuf.Timestamp -> + // google.protobuf.Duration + cel_Duration result; + if (!cel_Timestamp_Diff(&result, cel_Value_GetTimestamp(lhs), + cel_Value_GetTimestamp(rhs))) { + cel_Value_SetError( + top, _cel_Interpretable_NewDurationOverflowError(interp)); + break; + } + cel_Value_SetDuration(top, result); + } break; + default: + cel_Value_SetError(top, + _cel_Interpretable_NewNoSuchOverloadError(interp)); + break; + } + } break; + default: { + cel_Error* error = cel_Error_New(interp->arena); + if (CEL_UNLIKELY(error == cel_nullptr)) { + _cel_Interpretable_ThrowOutOfMemory(interp); + } + cel_Error_SetCanonicalCode(error, cel_ErrorCode_kInvalidArgument); + cel_Value_SetError(top, error); + } break; + } + _cel_Interpretable_Pop(interp, instr); +} + +static inline void _cel_Interpretable_Multiply( + _cel_Interpretable* cel_nonnull interp, + const _cel_InstrData* cel_nonnull instr) { + CEL_ASSERT_NOT_NULL(interp); + CEL_ASSERT_NOT_NULL(instr); + + cel_Value* const top = _cel_Interpretable_TopN(interp, instr, 2); + cel_Value* const lhs = top; + cel_Value* const rhs = lhs + 1; + + switch (cel_Value_Kind(lhs)) { + case cel_ValueKind_kError: + // error * T -> error + break; + case cel_ValueKind_kInt: { + switch (cel_Value_Kind(rhs)) { + case cel_ValueKind_kError: + // int * error -> error + *top = *rhs; + break; + case cel_ValueKind_kInt: { + // int * int -> int + int64_t result; + if (_cel_ckd_mul(&result, cel_Value_GetInt(lhs), + cel_Value_GetInt(rhs))) { + cel_Value_SetError(top, + _cel_Interpretable_NewIntOverflowError(interp)); + break; + } + cel_Value_SetInt(top, result); + } break; + default: + cel_Value_SetError(top, + _cel_Interpretable_NewNoSuchOverloadError(interp)); + break; + } + } break; + case cel_ValueKind_kUint: { + switch (cel_Value_Kind(rhs)) { + case cel_ValueKind_kError: + // uint * error -> error + *top = *rhs; + break; + case cel_ValueKind_kUint: { + // uint * uint -> uint + uint64_t result; + if (_cel_ckd_mul(&result, cel_Value_GetUint(lhs), + cel_Value_GetUint(rhs))) { + cel_Value_SetError(top, + _cel_Interpretable_NewUintOverflowError(interp)); + break; + } + cel_Value_SetUint(top, result); + } break; + default: + cel_Value_SetError(top, + _cel_Interpretable_NewNoSuchOverloadError(interp)); + break; + } + } break; + case cel_ValueKind_kDouble: { + switch (cel_Value_Kind(rhs)) { + case cel_ValueKind_kError: + // double * error -> error + *top = *rhs; + break; + case cel_ValueKind_kDouble: + // double * double -> double + cel_Value_SetDouble( + top, cel_Value_GetDouble(lhs) * cel_Value_GetDouble(rhs)); + break; + default: + cel_Value_SetError(top, + _cel_Interpretable_NewNoSuchOverloadError(interp)); + break; + } + } break; + default: { + cel_Error* error = cel_Error_New(interp->arena); + if (CEL_UNLIKELY(error == cel_nullptr)) { + _cel_Interpretable_ThrowOutOfMemory(interp); + } + cel_Error_SetCanonicalCode(error, cel_ErrorCode_kInvalidArgument); + cel_Value_SetError(top, error); + } break; + } + + _cel_Interpretable_Pop(interp, instr); +} + +static inline void _cel_Interpretable_Divide( + _cel_Interpretable* cel_nonnull interp, + const _cel_InstrData* cel_nonnull instr) { + CEL_ASSERT_NOT_NULL(interp); + CEL_ASSERT_NOT_NULL(instr); + + cel_Value* const top = _cel_Interpretable_TopN(interp, instr, 2); + cel_Value* const lhs = top; + cel_Value* const rhs = lhs + 1; + + switch (cel_Value_Kind(lhs)) { + case cel_ValueKind_kError: + // error / T -> error + break; + case cel_ValueKind_kInt: { + switch (cel_Value_Kind(rhs)) { + case cel_ValueKind_kError: + // int / error -> error + *top = *rhs; + break; + case cel_ValueKind_kInt: { + // int / 0 -> error + if (cel_Value_GetInt(rhs) == 0) { + cel_Value_SetError(top, + _cel_Interpretable_NewDivideByZeroError(interp)); + break; + } + // int / int -> int + int64_t result; + if (_cel_ckd_div(&result, cel_Value_GetInt(lhs), + cel_Value_GetInt(rhs))) { + cel_Value_SetError(top, + _cel_Interpretable_NewIntOverflowError(interp)); + break; + } + cel_Value_SetInt(top, result); + } break; + default: + cel_Value_SetError(top, + _cel_Interpretable_NewNoSuchOverloadError(interp)); + break; + } + } break; + case cel_ValueKind_kUint: { + switch (cel_Value_Kind(rhs)) { + case cel_ValueKind_kError: + // uint / error -> error + *top = *rhs; + break; + case cel_ValueKind_kUint: { + // uint / 0 -> error + if (cel_Value_GetUint(rhs) == 0) { + cel_Value_SetError(top, + _cel_Interpretable_NewDivideByZeroError(interp)); + break; + } + // uint / uint -> uint + uint64_t result; + if (_cel_ckd_div(&result, cel_Value_GetUint(lhs), + cel_Value_GetUint(rhs))) { + cel_Value_SetError(top, + _cel_Interpretable_NewUintOverflowError(interp)); + break; + } + cel_Value_SetUint(top, result); + } break; + default: + cel_Value_SetError(top, + _cel_Interpretable_NewNoSuchOverloadError(interp)); + break; + } + } break; + case cel_ValueKind_kDouble: { + switch (cel_Value_Kind(rhs)) { + case cel_ValueKind_kError: + // double / error -> error + *top = *rhs; + break; + case cel_ValueKind_kDouble: + // double / 0 -> error + if (cel_Value_GetDouble(rhs) == 0) { + cel_Value_SetError(top, + _cel_Interpretable_NewDivideByZeroError(interp)); + break; + } + // double / double -> double + cel_Value_SetDouble( + top, cel_Value_GetDouble(lhs) / cel_Value_GetDouble(rhs)); + break; + default: + cel_Value_SetError(top, + _cel_Interpretable_NewNoSuchOverloadError(interp)); + break; + } + } break; + default: { + cel_Error* error = cel_Error_New(interp->arena); + if (CEL_UNLIKELY(error == cel_nullptr)) { + _cel_Interpretable_ThrowOutOfMemory(interp); + } + cel_Error_SetCanonicalCode(error, cel_ErrorCode_kInvalidArgument); + cel_Value_SetError(top, error); + } break; + } + _cel_Interpretable_Pop(interp, instr); +} + +static inline void _cel_Interpretable_Modulo( + _cel_Interpretable* cel_nonnull interp, + const _cel_InstrData* cel_nonnull instr) { + CEL_ASSERT_NOT_NULL(interp); + CEL_ASSERT_NOT_NULL(instr); + + cel_Value* const top = _cel_Interpretable_TopN(interp, instr, 2); + cel_Value* const lhs = top; + cel_Value* const rhs = lhs + 1; + + switch (cel_Value_Kind(lhs)) { + case cel_ValueKind_kError: + // error % T -> error + break; + case cel_ValueKind_kInt: { + switch (cel_Value_Kind(rhs)) { + case cel_ValueKind_kError: + // int % error -> error + *top = *rhs; + break; + case cel_ValueKind_kInt: { + // int % 0 -> error + if (cel_Value_GetInt(rhs) == 0) { + cel_Value_SetError(top, + _cel_Interpretable_NewModuloByZeroError(interp)); + break; + } + // int % int -> int + int64_t result; + if (_cel_ckd_mod(&result, cel_Value_GetInt(lhs), + cel_Value_GetInt(rhs))) { + cel_Value_SetError(top, + _cel_Interpretable_NewIntOverflowError(interp)); + break; + } + cel_Value_SetInt(top, result); + } break; + default: + cel_Value_SetError(top, + _cel_Interpretable_NewNoSuchOverloadError(interp)); + break; + } + } break; + case cel_ValueKind_kUint: { + switch (cel_Value_Kind(rhs)) { + case cel_ValueKind_kError: + // uint % error -> error + *top = *rhs; + break; + case cel_ValueKind_kUint: { + // uint % 0 -> error + if (cel_Value_GetUint(rhs) == 0) { + cel_Value_SetError(top, + _cel_Interpretable_NewModuloByZeroError(interp)); + break; + } + // uint % uint -> uint + uint64_t result; + if (_cel_ckd_mod(&result, cel_Value_GetUint(lhs), + cel_Value_GetUint(rhs))) { + cel_Value_SetError(top, + _cel_Interpretable_NewUintOverflowError(interp)); + break; + } + cel_Value_SetUint(top, result); + } break; + default: + cel_Value_SetError(top, + _cel_Interpretable_NewNoSuchOverloadError(interp)); + break; + } + } break; + default: { + cel_Error* error = cel_Error_New(interp->arena); + if (CEL_UNLIKELY(error == cel_nullptr)) { + _cel_Interpretable_ThrowOutOfMemory(interp); + } + cel_Error_SetCanonicalCode(error, cel_ErrorCode_kInvalidArgument); + cel_Value_SetError(top, error); + } break; + } + + _cel_Interpretable_Pop(interp, instr); +} + +static inline void _cel_Interpretable_LogicalNot( + _cel_Interpretable* cel_nonnull interp, + const _cel_InstrData* cel_nonnull instr) { + CEL_ASSERT_NOT_NULL(interp); + CEL_ASSERT_NOT_NULL(instr); + + cel_Value* const top = _cel_Interpretable_TopN(interp, instr, 1); + cel_Value* const val = top; + + switch (cel_Value_Kind(val)) { + case cel_ValueKind_kError: + // !error -> error + break; + case cel_ValueKind_kBool: { + // !true -> false + cel_Value_SetBool(top, !cel_Value_GetBool(val)); + } break; + default: { + cel_Error* error = cel_Error_New(interp->arena); + if (CEL_UNLIKELY(error == cel_nullptr)) { + _cel_Interpretable_ThrowOutOfMemory(interp); + } + cel_Error_SetCanonicalCode(error, cel_ErrorCode_kInvalidArgument); + cel_Value_SetError(top, error); + } break; + } +} + +static inline void _cel_Interpretable_Negate( + _cel_Interpretable* cel_nonnull interp, + const _cel_InstrData* cel_nonnull instr) { + CEL_ASSERT_NOT_NULL(interp); + CEL_ASSERT_NOT_NULL(instr); + + cel_Value* const top = _cel_Interpretable_TopN(interp, instr, 1); + cel_Value* const val = top; + + switch (cel_Value_Kind(val)) { + case cel_ValueKind_kError: + // -error -> error + break; + case cel_ValueKind_kInt: { + // -(int) -> -int + int64_t result; + if (_cel_ckd_sub(&result, 0, cel_Value_GetInt(val))) { + cel_Value_SetError(top, _cel_Interpretable_NewIntOverflowError(interp)); + break; + } + cel_Value_SetInt(top, result); + } break; + case cel_ValueKind_kDouble: { + // -(double) -> -double + cel_Value_SetDouble(top, -cel_Value_GetDouble(val)); + } break; + default: { + cel_Error* error = cel_Error_New(interp->arena); + if (CEL_UNLIKELY(error == cel_nullptr)) { + _cel_Interpretable_ThrowOutOfMemory(interp); + } + cel_Error_SetCanonicalCode(error, cel_ErrorCode_kInvalidArgument); + cel_Value_SetError(top, error); + } break; + } +} + +static inline void _cel_Interpretable_LogicalAnd( + _cel_Interpretable* cel_nonnull interp, + const _cel_InstrData* cel_nonnull instr) { + CEL_ASSERT_NOT_NULL(interp); + CEL_ASSERT_NOT_NULL(instr); + + cel_Value* const top = _cel_Interpretable_TopN(interp, instr, 2); + cel_Value* const lhs = top; + cel_Value* const rhs = lhs + 1; + + switch (cel_Value_Kind(lhs)) { + case cel_ValueKind_kError: { + // error && T + if (cel_Value_Kind(rhs) == cel_ValueKind_kBool && + cel_Value_GetBool(rhs) == false) { + // error && False -> False + cel_Value_SetFalse(top); + } + // Otherwise, error && T -> error, which is already the value of *top. + } break; + case cel_ValueKind_kBool: { + switch (cel_Value_Kind(rhs)) { + case cel_ValueKind_kError: + // bool && error + if (cel_Value_GetBool(lhs) == false) { + // False && error -> False + cel_Value_SetFalse(top); + } else { + // True && error -> error + *top = *rhs; + } + break; + case cel_ValueKind_kBool: + // bool && bool -> bool + cel_Value_SetBool(top, + cel_Value_GetBool(lhs) && cel_Value_GetBool(rhs)); + break; + default: + cel_Value_SetError(top, + _cel_Interpretable_NewNoSuchOverloadError(interp)); + break; + } + } break; + default: { + cel_Error* error = cel_Error_New(interp->arena); + if (CEL_UNLIKELY(error == cel_nullptr)) { + _cel_Interpretable_ThrowOutOfMemory(interp); + } + cel_Error_SetCanonicalCode(error, cel_ErrorCode_kInvalidArgument); + cel_Value_SetError(top, error); + } break; + } + + _cel_Interpretable_Pop(interp, instr); +} + +static inline void _cel_Interpretable_LogicalOr( + _cel_Interpretable* cel_nonnull interp, + const _cel_InstrData* cel_nonnull instr) { + CEL_ASSERT_NOT_NULL(interp); + CEL_ASSERT_NOT_NULL(instr); + + cel_Value* const top = _cel_Interpretable_TopN(interp, instr, 2); + cel_Value* const lhs = top; + cel_Value* const rhs = lhs + 1; + + switch (cel_Value_Kind(lhs)) { + case cel_ValueKind_kError: { + // error || T + if (cel_Value_Kind(rhs) == cel_ValueKind_kBool && + cel_Value_GetBool(rhs) == true) { + // error || True -> True + cel_Value_SetTrue(top); + } + // Otherwise, error || T -> error, which is already the value of *top. + } break; + case cel_ValueKind_kBool: { + switch (cel_Value_Kind(rhs)) { + case cel_ValueKind_kError: + // bool || error + if (cel_Value_GetBool(lhs) == true) { + // True || error -> True + cel_Value_SetTrue(top); + } else { + // False || error -> error + *top = *rhs; + } + break; + case cel_ValueKind_kBool: + // bool || bool -> bool + cel_Value_SetBool(top, + cel_Value_GetBool(lhs) || cel_Value_GetBool(rhs)); + break; + default: + cel_Value_SetError(top, + _cel_Interpretable_NewNoSuchOverloadError(interp)); + break; + } + } break; + default: { + cel_Error* error = cel_Error_New(interp->arena); + if (CEL_UNLIKELY(error == cel_nullptr)) { + _cel_Interpretable_ThrowOutOfMemory(interp); + } + cel_Error_SetCanonicalCode(error, cel_ErrorCode_kInvalidArgument); + cel_Value_SetError(top, error); + } break; + } + + _cel_Interpretable_Pop(interp, instr); +} + +static inline void _cel_Interpretable_Equals( + _cel_Interpretable* cel_nonnull interp, + const _cel_InstrData* cel_nonnull instr) { + CEL_ASSERT_NOT_NULL(interp); + CEL_ASSERT_NOT_NULL(instr); + + cel_Value* const top = _cel_Interpretable_TopN(interp, instr, 2); + cel_Value* const lhs = top; + cel_Value* const rhs = lhs + 1; + + if (cel_Value_IsError(lhs)) { + // Already on top. + } else if (cel_Value_IsError(rhs)) { + *top = *rhs; + } else { + if (!cel_Value_Equals(lhs, &interp->context, rhs, top, interp->status)) { + _cel_Interpretable_ThrowIfError(interp); + cel_Value_SetError(top, + _cel_Interpretable_NewNoSuchOverloadError(interp)); + } + } + + _cel_Interpretable_Pop(interp, instr); +} + +static inline void _cel_Interpretable_NotEquals( + _cel_Interpretable* cel_nonnull interp, + const _cel_InstrData* cel_nonnull instr) { + CEL_ASSERT_NOT_NULL(interp); + CEL_ASSERT_NOT_NULL(instr); + + cel_Value* const top = _cel_Interpretable_TopN(interp, instr, 2); + cel_Value* const lhs = top; + cel_Value* const rhs = lhs + 1; + + if (cel_Value_IsError(lhs)) { + // Already on top. + } else if (cel_Value_IsError(rhs)) { + *top = *rhs; + } else { + if (cel_Value_Equals(lhs, &interp->context, rhs, top, interp->status)) { + cel_Value_SetBool(top, !cel_Value_GetBool(top)); + } else { + _cel_Interpretable_ThrowIfError(interp); + cel_Value_SetError(top, + _cel_Interpretable_NewNoSuchOverloadError(interp)); + } + } + + _cel_Interpretable_Pop(interp, instr); +} + +CEL_ATTRIBUTE_NODISCARD +static inline bool _cel_Interpretable_Compare( + const cel_Value* cel_nonnull lhs, const cel_Value* cel_nonnull rhs, + cel_ValueKind lhs_kind, cel_ValueKind rhs_kind, + _cel_PartialOrdering* cel_nonnull order) { + if (lhs_kind != rhs_kind) { + // Heterogeneous + switch (lhs_kind) { + case cel_ValueKind_kInt: { + _cel_Number lhs_number = _cel_IntNumber(cel_Value_GetInt(lhs)); + switch (rhs_kind) { + case cel_ValueKind_kUint: { + _cel_Number rhs_number = _cel_UintNumber(cel_Value_GetUint(rhs)); + *order = _cel_Number_Compare(lhs_number, rhs_number); + return true; + } + case cel_ValueKind_kDouble: { + _cel_Number rhs_number = + _cel_DoubleNumber(cel_Value_GetDouble(rhs)); + *order = _cel_Number_Compare(lhs_number, rhs_number); + return true; + } + default: + return false; + } + } + case cel_ValueKind_kUint: { + _cel_Number lhs_number = _cel_UintNumber(cel_Value_GetUint(lhs)); + switch (rhs_kind) { + case cel_ValueKind_kInt: { + _cel_Number rhs_number = _cel_IntNumber(cel_Value_GetInt(rhs)); + *order = _cel_Number_Compare(lhs_number, rhs_number); + return true; + } + case cel_ValueKind_kDouble: { + _cel_Number rhs_number = + _cel_DoubleNumber(cel_Value_GetDouble(rhs)); + *order = _cel_Number_Compare(lhs_number, rhs_number); + return true; + } + default: + return false; + } + } + case cel_ValueKind_kDouble: { + _cel_Number lhs_number = _cel_DoubleNumber(cel_Value_GetDouble(lhs)); + switch (rhs_kind) { + case cel_ValueKind_kInt: { + _cel_Number rhs_number = _cel_IntNumber(cel_Value_GetInt(rhs)); + *order = _cel_Number_Compare(lhs_number, rhs_number); + return true; + } + case cel_ValueKind_kUint: { + _cel_Number rhs_number = _cel_UintNumber(cel_Value_GetUint(rhs)); + *order = _cel_Number_Compare(lhs_number, rhs_number); + return true; + } + default: + return false; + } + } + default: + return false; + } + } + switch (lhs_kind) { + case cel_ValueKind_kBool: { + bool lhs_val = cel_Value_GetBool(lhs); + bool rhs_val = cel_Value_GetBool(rhs); + *order = lhs_val < rhs_val ? _cel_PartialOrdering_kLess + : lhs_val > rhs_val ? _cel_PartialOrdering_kGreater + : _cel_PartialOrdering_kEquivalent; + return true; + } + case cel_ValueKind_kInt: { + int64_t lhs_val = cel_Value_GetInt(lhs); + int64_t rhs_val = cel_Value_GetInt(rhs); + *order = lhs_val < rhs_val ? _cel_PartialOrdering_kLess + : lhs_val > rhs_val ? _cel_PartialOrdering_kGreater + : _cel_PartialOrdering_kEquivalent; + return true; + } + case cel_ValueKind_kUint: { + uint64_t lhs_val = cel_Value_GetUint(lhs); + uint64_t rhs_val = cel_Value_GetUint(rhs); + *order = lhs_val < rhs_val ? _cel_PartialOrdering_kLess + : lhs_val > rhs_val ? _cel_PartialOrdering_kGreater + : _cel_PartialOrdering_kEquivalent; + return true; + } + case cel_ValueKind_kDouble: { + double lhs_val = cel_Value_GetDouble(lhs); + double rhs_val = cel_Value_GetDouble(rhs); + *order = _cel_Number_DoubleCompare(lhs_val, rhs_val); + return true; + } + case cel_ValueKind_kString: { + *order = _cel_PartialOrdering_FromInt(cel_StringView_Compare( + cel_Value_GetString(lhs), cel_Value_GetString(rhs))); + return true; + } + case cel_ValueKind_kBytes: { + *order = _cel_PartialOrdering_FromInt(cel_StringView_Compare( + cel_Value_GetBytes(lhs), cel_Value_GetBytes(rhs))); + return true; + } + case cel_ValueKind_kDuration: { + *order = _cel_PartialOrdering_FromInt(cel_Duration_Compare( + cel_Value_GetDuration(lhs), cel_Value_GetDuration(rhs))); + return true; + } + case cel_ValueKind_kTimestamp: { + *order = _cel_PartialOrdering_FromInt(cel_Timestamp_Compare( + cel_Value_GetTimestamp(lhs), cel_Value_GetTimestamp(rhs))); + return true; + } + default: + return false; + } +} + +static inline void _cel_Interpretable_Less( + _cel_Interpretable* cel_nonnull interp, + const _cel_InstrData* cel_nonnull instr) { + CEL_ASSERT_NOT_NULL(interp); + CEL_ASSERT_NOT_NULL(instr); + + cel_Value* const top = _cel_Interpretable_TopN(interp, instr, 2); + cel_Value* const lhs = top; + cel_Value* const rhs = lhs + 1; + + const cel_ValueKind lhs_kind = cel_Value_Kind(lhs); + const cel_ValueKind rhs_kind = cel_Value_Kind(rhs); + + if (lhs_kind == cel_ValueKind_kError) { + // Already on top. + } else if (rhs_kind == cel_ValueKind_kError) { + *top = *rhs; + } else { + _cel_PartialOrdering order; + if (_cel_Interpretable_Compare(lhs, rhs, lhs_kind, rhs_kind, &order)) { + cel_Value_SetBool(top, order == _cel_PartialOrdering_kLess); + } else { + cel_Value_SetError(top, + _cel_Interpretable_NewNoSuchOverloadError(interp)); + } + } + + _cel_Interpretable_Pop(interp, instr); +} + +static inline void _cel_Interpretable_LessEquals( + _cel_Interpretable* cel_nonnull interp, + const _cel_InstrData* cel_nonnull instr) { + CEL_ASSERT_NOT_NULL(interp); + CEL_ASSERT_NOT_NULL(instr); + + cel_Value* const top = _cel_Interpretable_TopN(interp, instr, 2); + cel_Value* const lhs = top; + cel_Value* const rhs = lhs + 1; + + const cel_ValueKind lhs_kind = cel_Value_Kind(lhs); + const cel_ValueKind rhs_kind = cel_Value_Kind(rhs); + + if (lhs_kind == cel_ValueKind_kError) { + // Already on top. + } else if (rhs_kind == cel_ValueKind_kError) { + *top = *rhs; + } else { + _cel_PartialOrdering order; + if (_cel_Interpretable_Compare(lhs, rhs, lhs_kind, rhs_kind, &order)) { + cel_Value_SetBool(top, order == _cel_PartialOrdering_kLess || + order == _cel_PartialOrdering_kEquivalent); + } else { + cel_Value_SetError(top, + _cel_Interpretable_NewNoSuchOverloadError(interp)); + } + } + + _cel_Interpretable_Pop(interp, instr); +} + +static inline void _cel_Interpretable_Greater( + _cel_Interpretable* cel_nonnull interp, + const _cel_InstrData* cel_nonnull instr) { + CEL_ASSERT_NOT_NULL(interp); + CEL_ASSERT_NOT_NULL(instr); + + cel_Value* const top = _cel_Interpretable_TopN(interp, instr, 2); + cel_Value* const lhs = top; + cel_Value* const rhs = lhs + 1; + + const cel_ValueKind lhs_kind = cel_Value_Kind(lhs); + const cel_ValueKind rhs_kind = cel_Value_Kind(rhs); + + if (lhs_kind == cel_ValueKind_kError) { + // Already on top. + } else if (rhs_kind == cel_ValueKind_kError) { + *top = *rhs; + } else { + _cel_PartialOrdering order; + if (_cel_Interpretable_Compare(lhs, rhs, lhs_kind, rhs_kind, &order)) { + cel_Value_SetBool(top, order == _cel_PartialOrdering_kGreater); + } else { + cel_Value_SetError(top, + _cel_Interpretable_NewNoSuchOverloadError(interp)); + } + } + + _cel_Interpretable_Pop(interp, instr); +} + +static inline void _cel_Interpretable_GreaterEquals( + _cel_Interpretable* cel_nonnull interp, + const _cel_InstrData* cel_nonnull instr) { + CEL_ASSERT_NOT_NULL(interp); + CEL_ASSERT_NOT_NULL(instr); + + cel_Value* const top = _cel_Interpretable_TopN(interp, instr, 2); + cel_Value* const lhs = top; + cel_Value* const rhs = lhs + 1; + + const cel_ValueKind lhs_kind = cel_Value_Kind(lhs); + const cel_ValueKind rhs_kind = cel_Value_Kind(rhs); + + if (lhs_kind == cel_ValueKind_kError) { + // Already on top. + } else if (rhs_kind == cel_ValueKind_kError) { + *top = *rhs; + } else { + _cel_PartialOrdering order; + if (_cel_Interpretable_Compare(lhs, rhs, lhs_kind, rhs_kind, &order)) { + cel_Value_SetBool(top, order == _cel_PartialOrdering_kGreater || + order == _cel_PartialOrdering_kEquivalent); + } else { + cel_Value_SetError(top, + _cel_Interpretable_NewNoSuchOverloadError(interp)); + } + } + + _cel_Interpretable_Pop(interp, instr); +} + +CEL_ATTRIBUTE_NODISCARD +static inline const _cel_Instr* cel_nonnull +_cel_Interpretable_CondJump(_cel_Interpretable* cel_nonnull interp, + const _cel_InstrData* cel_nonnull instr) { + CEL_ASSERT_NOT_NULL(interp); + CEL_ASSERT_NOT_NULL(instr); + CEL_ASSERT_NE(instr->cond_jump.jump, 0); + + cel_Value* const top = _cel_Interpretable_TopN(interp, instr, 1); + if (cel_Value_IsBool(top) && + cel_Value_GetBool(top) == instr->cond_jump.cond) { + return _cel_Interpretable_ShortJump(instr, instr->cond_jump.jump); + } + return _cel_Interpretable_ShortJump(instr, 1); +} + +CEL_ATTRIBUTE_NODISCARD +static inline const _cel_Instr* cel_nonnull +_cel_Interpretable_TrileanJump(_cel_Interpretable* cel_nonnull interp, + const _cel_InstrData* cel_nonnull instr) { + CEL_ASSERT_NOT_NULL(interp); + CEL_ASSERT_NOT_NULL(instr); + CEL_ASSERT_NE(instr->trilean_jump.false_jump, 0); + CEL_ASSERT_NE(instr->trilean_jump.error_jump, 0); + + cel_Value* const top = _cel_Interpretable_TopN(interp, instr, 1); + if (cel_Value_IsError(top)) { + return _cel_Interpretable_ShortJump(instr, instr->trilean_jump.error_jump); + } + if (cel_Value_IsBool(top)) { + int32_t jump = cel_Value_GetBool(top) ? 1 : instr->trilean_jump.false_jump; + _cel_Interpretable_Pop(interp, instr); + return _cel_Interpretable_ShortJump(instr, jump); + } + cel_Value_SetError(top, _cel_Interpretable_NewNoSuchOverloadError(interp)); + return _cel_Interpretable_ShortJump(instr, instr->trilean_jump.error_jump); +} + +CEL_ATTRIBUTE_NODISCARD +static inline const _cel_Instr* cel_nonnull +_cel_Interpretable_ErrorJump(_cel_Interpretable* cel_nonnull interp, + const _cel_InstrData* cel_nonnull instr) { + CEL_ASSERT_NOT_NULL(interp); + CEL_ASSERT_NOT_NULL(instr); + CEL_ASSERT_NE(instr->error_jump.jump, 0); + + cel_Value* const top = _cel_Interpretable_TopN(interp, instr, 1); + if (cel_Value_IsError(top)) { + uint32_t pop = instr->error_jump.pop; + if (pop > 0) { + cel_Value error = *top; + _cel_Interpretable_PopN(interp, instr, pop); + *_cel_Interpretable_Top(interp, instr) = error; + } + return _cel_Interpretable_ShortJump(instr, instr->error_jump.jump); + } + return _cel_Interpretable_ShortJump(instr, 1); +} + +static inline void _cel_Interpretable_List( + _cel_Interpretable* cel_nonnull interp, + const _cel_InstrData* cel_nonnull instr) { + CEL_ASSERT_NOT_NULL(interp); + CEL_ASSERT_NOT_NULL(instr); + + uint32_t element_count = instr->list.count; + if (element_count == 0) { + _cel_EmptyListValue_Set( + cel_Value_SetList(_cel_Interpretable_Push(interp, instr))); + return; + } + cel_Value* src_elements = + _cel_Interpretable_TopN(interp, instr, element_count); + cel_Value value; + cel_ListValue* list_value = cel_Value_SetList(&value); + _cel_MutableListValue_Set(list_value); + if (!_cel_MutableListValue_Reserve(list_value, element_count, + interp->arena)) { + _cel_Interpretable_ThrowOutOfMemory(interp); + } + cel_Value* dst_elements = + _cel_MutableListValue_AddN(list_value, element_count, interp->arena); + if (dst_elements == cel_nullptr) { + _cel_Interpretable_ThrowOutOfMemory(interp); + } + memcpy(dst_elements, src_elements, element_count * sizeof(cel_Value)); + *_cel_Interpretable_PushAndPopN(interp, instr, element_count) = value; +} + +CEL_ATTRIBUTE_NODISCARD +static inline const _cel_Instr* cel_nonnull +_cel_Interpretable_KeyJump(_cel_Interpretable* cel_nonnull interp, + const _cel_InstrData* cel_nonnull instr) { + CEL_ASSERT_NOT_NULL(interp); + CEL_ASSERT_NOT_NULL(instr); + CEL_ASSERT_NE(instr->key_jump.jump, 0); + + cel_Value* const top = _cel_Interpretable_TopN(interp, instr, 1); + if (cel_Value_IsError(top)) { + uint32_t pop = instr->key_jump.pop; + if (pop > 0) { + cel_Value error = *top; + _cel_Interpretable_PopN(interp, instr, pop); + *_cel_Interpretable_Top(interp, instr) = error; + } + return _cel_Interpretable_ShortJump(instr, instr->key_jump.jump); + } + if (!cel_Value_IsMapKey(top)) { + cel_Value_SetError( + _cel_Interpretable_PushAndPopN(interp, instr, instr->key_jump.pop + 1), + _cel_Interpretable_NewBadKeyError(interp)); + return _cel_Interpretable_ShortJump(instr, instr->key_jump.jump); + } + return _cel_Interpretable_ShortJump(instr, 1); +} + +static inline void _cel_Interpretable_Map( + _cel_Interpretable* cel_nonnull interp, + const _cel_InstrData* cel_nonnull instr) { + CEL_ASSERT_NOT_NULL(interp); + CEL_ASSERT_NOT_NULL(instr); + + uint32_t entry_count = instr->map.count; + if (entry_count == 0) { + _cel_EmptyMapValue_Set( + cel_Value_SetMap(_cel_Interpretable_Push(interp, instr))); + return; + } + cel_Value* src_entries = + _cel_Interpretable_TopN(interp, instr, entry_count * 2); + cel_Value value; + cel_MapValue* map_value = cel_Value_SetMap(&value); + _cel_MutableMapValue_Set(map_value); + if (!_cel_MutableMapValue_Reserve(map_value, entry_count, interp->arena)) { + _cel_Interpretable_ThrowOutOfMemory(interp); + } + for (size_t i = 0; i < entry_count; ++i) { + cel_Value* src_entry_key = &src_entries[i * 2]; + cel_MapValueKey entry_key; + switch (cel_Value_Kind(src_entry_key)) { + case cel_ValueKind_kBool: + cel_MapValueKey_SetBool(&entry_key, cel_Value_GetBool(src_entry_key)); + break; + case cel_ValueKind_kInt: + cel_MapValueKey_SetInt(&entry_key, cel_Value_GetInt(src_entry_key)); + break; + case cel_ValueKind_kUint: + cel_MapValueKey_SetUint(&entry_key, cel_Value_GetUint(src_entry_key)); + break; + case cel_ValueKind_kString: + cel_MapValueKey_SetString(&entry_key, + cel_Value_GetString(src_entry_key)); + break; + default: + CEL_UNREACHABLE(); + } + cel_Value* entry_value; + switch (_cel_MutableMapValue_Insert(map_value, &entry_key, cel_nullptr, + &entry_value, interp->arena)) { + case _cel_MutableMapValueInsertResult_kInserted: { + *entry_value = src_entries[i * 2 + 1]; + } break; + case _cel_MutableMapValueInsertResult_kReplaced: { + cel_Value_SetError( + _cel_Interpretable_PushAndPopN(interp, instr, entry_count * 2), + _cel_Interpretable_NewDuplicateKeyError(interp)); + return; + } + case _cel_MutableMapValueInsertResult_kOutOfMemory: + _cel_Interpretable_ThrowOutOfMemory(interp); + } + } + *_cel_Interpretable_PushAndPopN(interp, instr, entry_count * 2) = value; +} + +CEL_ATTRIBUTE_NODISCARD +int _cel_Interpretable_FromCharsToInt(cel_StringView string, int64_t* val) { + const char* data = cel_StringView_Data(string); + size_t size = cel_StringView_Size(string); + _cel_FromCharsResult result = _cel_FromChars(data, data + size, val, 10); + if (result.ec == 0 && result.ptr != data + size) { + return EINVAL; + } + return result.ec; +} + +CEL_ATTRIBUTE_NODISCARD +int _cel_Interpretable_FromCharsToUint(cel_StringView string, uint64_t* val) { + const char* data = cel_StringView_Data(string); + size_t size = cel_StringView_Size(string); + _cel_FromCharsResult result = _cel_FromChars(data, data + size, val, 10); + if (result.ec == 0 && result.ptr != data + size) { + return EINVAL; + } + return result.ec; +} + +CEL_ATTRIBUTE_NODISCARD +int _cel_Interpretable_FromCharsToDouble(cel_StringView string, double* val) { + const char* data = cel_StringView_Data(string); + size_t size = cel_StringView_Size(string); + _cel_FromCharsResult result = _cel_FromChars(data, data + size, val); + if (result.ec == 0 && result.ptr != data + size) { + return EINVAL; + } + return result.ec; +} + +CEL_ATTRIBUTE_NODISCARD +cel_StringView _cel_Interpretable_FromIntToChars(_cel_Interpretable* interp, + int64_t val) { + CEL_ASSERT_NOT_NULL(interp); + char buffer[_CEL_MAX_INT_CHARS]; + size_t buffer_size = _cel_ToChars(buffer, val, 10); + char* data = (char*)cel_Arena_Malloc(interp->arena, buffer_size, cel_nullptr); + if (CEL_UNLIKELY(data == cel_nullptr)) { + _cel_Interpretable_ThrowOutOfMemory(interp); + } + memcpy(data, buffer, buffer_size); + return cel_StringView_FromArray(data, buffer_size); +} + +CEL_ATTRIBUTE_NODISCARD +cel_StringView _cel_Interpretable_FromUintToChars(_cel_Interpretable* interp, + uint64_t val) { + CEL_ASSERT_NOT_NULL(interp); + char buffer[_CEL_MAX_UINT_CHARS]; + size_t buffer_size = _cel_ToChars(buffer, val, 10); + char* data = (char*)cel_Arena_Malloc(interp->arena, buffer_size, cel_nullptr); + if (CEL_UNLIKELY(data == cel_nullptr)) { + _cel_Interpretable_ThrowOutOfMemory(interp); + } + memcpy(data, buffer, buffer_size); + return cel_StringView_FromArray(data, buffer_size); +} + +CEL_ATTRIBUTE_NODISCARD +cel_StringView _cel_Interpretable_FromDoubleToChars(_cel_Interpretable* interp, + double val) { + CEL_ASSERT_NOT_NULL(interp); + char buffer[_CEL_MAX_DOUBLE_CHARS]; + size_t buffer_size = _cel_ToChars(buffer, val); + char* data = (char*)cel_Arena_Malloc(interp->arena, buffer_size, cel_nullptr); + if (CEL_UNLIKELY(data == cel_nullptr)) { + _cel_Interpretable_ThrowOutOfMemory(interp); + } + memcpy(data, buffer, buffer_size); + return cel_StringView_FromArray(data, buffer_size); +} + +static inline void _cel_Interpretable_CallUint( + _cel_Interpretable* cel_nonnull interp, + const _cel_InstrData* cel_nonnull instr) { + CEL_ASSERT_NOT_NULL(interp); + CEL_ASSERT_NOT_NULL(instr); + CEL_ASSERT(instr->call.args == 1); + + cel_Value* const top = _cel_Interpretable_Top(interp, instr); + switch (cel_Value_Kind(top)) { + case cel_ValueKind_kError: + break; + case cel_ValueKind_kInt: { + _cel_Number number = _cel_IntNumber(cel_Value_GetInt(top)); + uint64_t uint_value; + if (!_cel_Number_ToUint(number, &uint_value)) { + cel_Value_SetError(top, + _cel_Interpretable_NewNoSuchOverloadError(interp)); + } else { + cel_Value_SetUint(top, uint_value); + } + } break; + case cel_ValueKind_kUint: + cel_Value_SetUint(top, cel_Value_GetUint(top)); + break; + case cel_ValueKind_kDouble: { + _cel_Number number = _cel_DoubleNumber(cel_Value_GetDouble(top)); + uint64_t uint_value; + if (!_cel_Number_ToUint(number, &uint_value)) { + cel_Value_SetError(top, + _cel_Interpretable_NewNoSuchOverloadError(interp)); + } else { + cel_Value_SetUint(top, uint_value); + } + } break; + case cel_ValueKind_kString: { + uint64_t value; + cel_StringView str = cel_Value_GetString(top); + if (_cel_Interpretable_FromCharsToUint(str, &value) != 0) { + cel_Value_SetError(top, + _cel_Interpretable_NewNoSuchOverloadError(interp)); + } else { + cel_Value_SetUint(top, value); + } + } break; + default: + cel_Value_SetError(top, + _cel_Interpretable_NewNoSuchOverloadError(interp)); + } +} + +static inline void _cel_Interpretable_CallDouble( + _cel_Interpretable* cel_nonnull interp, + const _cel_InstrData* cel_nonnull instr) { + CEL_ASSERT_NOT_NULL(interp); + CEL_ASSERT_NOT_NULL(instr); + CEL_ASSERT(instr->call.args == 1); + + cel_Value* const top = _cel_Interpretable_Top(interp, instr); + switch (cel_Value_Kind(top)) { + case cel_ValueKind_kError: + break; + case cel_ValueKind_kInt: { + _cel_Number number = _cel_IntNumber(cel_Value_GetInt(top)); + double double_value = _cel_Number_ToDouble(number); + if (double_value == NAN) { + cel_Value_SetError(top, + _cel_Interpretable_NewNoSuchOverloadError(interp)); + } else { + cel_Value_SetDouble(top, double_value); + } + } break; + case cel_ValueKind_kUint: { + _cel_Number number = _cel_UintNumber(cel_Value_GetUint(top)); + double double_value = _cel_Number_ToDouble(number); + if (double_value == NAN) { + cel_Value_SetError(top, + _cel_Interpretable_NewNoSuchOverloadError(interp)); + } else { + cel_Value_SetDouble(top, double_value); + } + } break; + case cel_ValueKind_kDouble: + cel_Value_SetDouble(top, cel_Value_GetDouble(top)); + break; + case cel_ValueKind_kString: { + double value; + cel_StringView str = cel_Value_GetString(top); + if (_cel_Interpretable_FromCharsToDouble(str, &value) != 0) { + cel_Value_SetError(top, + _cel_Interpretable_NewNoSuchOverloadError(interp)); + } else { + cel_Value_SetDouble(top, value); + } + } break; + default: + cel_Value_SetError(top, + _cel_Interpretable_NewNoSuchOverloadError(interp)); + } +} + +static inline void _cel_Interpretable_CallBytes( + _cel_Interpretable* cel_nonnull interp, + const _cel_InstrData* cel_nonnull instr) { + CEL_ASSERT_NOT_NULL(interp); + CEL_ASSERT_NOT_NULL(instr); + CEL_ASSERT(instr->call.args == 1); + + cel_Value* const top = _cel_Interpretable_Top(interp, instr); + switch (cel_Value_Kind(top)) { + case cel_ValueKind_kError: + break; + case cel_ValueKind_kBytes: + cel_Value_SetBytes(top, cel_Value_GetBytes(top)); + break; + case cel_ValueKind_kString: + cel_Value_SetBytes(top, cel_Value_GetString(top)); + break; + default: + cel_Value_SetError(top, + _cel_Interpretable_NewNoSuchOverloadError(interp)); + } +} + +static inline void _cel_Interpretable_CallString( + _cel_Interpretable* cel_nonnull interp, + const _cel_InstrData* cel_nonnull instr) { + CEL_ASSERT_NOT_NULL(interp); + CEL_ASSERT_NOT_NULL(instr); + CEL_ASSERT(instr->call.args == 1); + + cel_Value* const top = _cel_Interpretable_Top(interp, instr); + switch (cel_Value_Kind(top)) { + case cel_ValueKind_kError: + break; + case cel_ValueKind_kBool: { + cel_Value_SetString(top, cel_Value_GetBool(top) + ? cel_StringView_From("true") + : cel_StringView_From("false")); + } break; + case cel_ValueKind_kInt: { + int64_t value = cel_Value_GetInt(top); + cel_Value_SetString(top, + _cel_Interpretable_FromIntToChars(interp, value)); + } break; + case cel_ValueKind_kUint: { + uint64_t value = cel_Value_GetUint(top); + cel_Value_SetString(top, + _cel_Interpretable_FromUintToChars(interp, value)); + } break; + case cel_ValueKind_kDouble: { + double value = cel_Value_GetDouble(top); + cel_Value_SetString(top, + _cel_Interpretable_FromDoubleToChars(interp, value)); + } break; + case cel_ValueKind_kBytes: { + cel_StringView bytes = cel_Value_GetBytes(top); + if (!_cel_Utf8_IsValid(bytes)) { + cel_Value_SetError(top, + _cel_Interpretable_NewNoSuchOverloadError(interp)); + } else { + cel_Value_SetString(top, bytes); + } + } break; + case cel_ValueKind_kString: + cel_Value_SetString(top, cel_Value_GetString(top)); + break; + case cel_ValueKind_kTimestamp: { + cel_StringView str; + if (!_cel_Timestamp_ToRFC3339(cel_Value_GetTimestamp(top), &str, + interp->arena)) { + cel_Value_SetError(top, + _cel_Interpretable_NewNoSuchOverloadError(interp)); + } else { + cel_Value_SetString(top, str); + } + } break; + case cel_ValueKind_kDuration: { + cel_StringView str; + if (!_cel_Duration_ToStringView(cel_Value_GetDuration(top), interp->arena, + &str)) { + cel_Value_SetError(top, + _cel_Interpretable_NewNoSuchOverloadError(interp)); + } else { + cel_Value_SetString(top, str); + } + } break; + default: + cel_Value_SetError(top, + _cel_Interpretable_NewNoSuchOverloadError(interp)); + } +} + +static inline void _cel_Interpretable_CallBool( + _cel_Interpretable* cel_nonnull interp, + const _cel_InstrData* cel_nonnull instr) { + CEL_ASSERT_NOT_NULL(interp); + CEL_ASSERT_NOT_NULL(instr); + CEL_ASSERT(instr->call.args == 1); + + cel_Value* const top = _cel_Interpretable_Top(interp, instr); + switch (cel_Value_Kind(top)) { + case cel_ValueKind_kError: + break; + case cel_ValueKind_kBool: + cel_Value_SetBool(top, cel_Value_GetBool(top)); + break; + case cel_ValueKind_kString: { + cel_StringView value = cel_Value_GetString(top); + if (cel_StringView_EqualsIgnoreCase(value, cel_StringView_From("true"))) { + cel_Value_SetBool(top, true); + } else if (cel_StringView_EqualsIgnoreCase( + value, cel_StringView_From("false"))) { + cel_Value_SetBool(top, false); + } else { + cel_Value_SetError(top, + _cel_Interpretable_NewNoSuchOverloadError(interp)); + } + } break; + default: + cel_Value_SetError(top, + _cel_Interpretable_NewNoSuchOverloadError(interp)); + } +} + +static inline void _cel_Interpretable_CallTimestamp( + _cel_Interpretable* cel_nonnull interp, + const _cel_InstrData* cel_nonnull instr) { + CEL_ASSERT_NOT_NULL(interp); + CEL_ASSERT_NOT_NULL(instr); + CEL_ASSERT(instr->call.args == 1); + + cel_Value* const top = _cel_Interpretable_Top(interp, instr); + switch (cel_Value_Kind(top)) { + case cel_ValueKind_kError: + break; + case cel_ValueKind_kTimestamp: + cel_Value_SetTimestamp(top, cel_Value_GetTimestamp(top)); + break; + case cel_ValueKind_kString: { + cel_Timestamp value; + cel_StringView str = cel_Value_GetString(top); + if (!_cel_Timestamp_FromRFC3339(&value, str)) { + cel_Value_SetError(top, + _cel_Interpretable_NewNoSuchOverloadError(interp)); + } else { + cel_Value_SetTimestamp(top, value); + } + } break; + default: + cel_Value_SetError(top, + _cel_Interpretable_NewNoSuchOverloadError(interp)); + } +} + +static inline void _cel_Interpretable_CallDuration( + _cel_Interpretable* cel_nonnull interp, + const _cel_InstrData* cel_nonnull instr) { + CEL_ASSERT_NOT_NULL(interp); + CEL_ASSERT_NOT_NULL(instr); + CEL_ASSERT(instr->call.args == 1); + + cel_Value* const top = _cel_Interpretable_Top(interp, instr); + switch (cel_Value_Kind(top)) { + case cel_ValueKind_kError: + break; + case cel_ValueKind_kDuration: + cel_Value_SetDuration(top, cel_Value_GetDuration(top)); + break; + case cel_ValueKind_kString: { + cel_Duration value; + cel_StringView str = cel_Value_GetString(top); + if (!_cel_Duration_FromStringView(str, &value)) { + cel_Value_SetError(top, + _cel_Interpretable_NewNoSuchOverloadError(interp)); + } else { + cel_Value_SetDuration(top, value); + } + } break; + default: + cel_Value_SetError(top, + _cel_Interpretable_NewNoSuchOverloadError(interp)); + } +} + +static inline void _cel_Interpretable_CallContainsString( + _cel_Interpretable* cel_nonnull interp, + const _cel_InstrData* cel_nonnull instr) { + CEL_ASSERT_NOT_NULL(interp); + CEL_ASSERT_NOT_NULL(instr); + CEL_ASSERT(instr->call.args == 2); + + cel_Value* const top = _cel_Interpretable_TopN(interp, instr, 2); + cel_Value* const str = top; + cel_Value* const substr = str + 1; + + switch (cel_Value_Kind(str)) { + case cel_ValueKind_kError: + break; + case cel_ValueKind_kString: + switch (cel_Value_Kind(substr)) { + case cel_ValueKind_kError: + *top = *substr; + break; + case cel_ValueKind_kString: + cel_Value_SetBool( + top, cel_StringView_FindFirst(cel_Value_GetString(str), + cel_Value_GetString(substr)) != + cel_nullptr); + break; + default: + cel_Value_SetError(top, + _cel_Interpretable_NewNoSuchOverloadError(interp)); + break; + } + break; + default: + if (cel_Value_IsError(substr)) { + *top = *substr; + } else { + cel_Value_SetError(top, + _cel_Interpretable_NewNoSuchOverloadError(interp)); + } + break; + } + + _cel_Interpretable_Pop(interp, instr); +} + +static inline void _cel_Interpretable_CallStartsWithString( + _cel_Interpretable* cel_nonnull interp, + const _cel_InstrData* cel_nonnull instr) { + CEL_ASSERT_NOT_NULL(interp); + CEL_ASSERT_NOT_NULL(instr); + CEL_ASSERT(instr->call.args == 2); + + cel_Value* const top = _cel_Interpretable_TopN(interp, instr, 2); + cel_Value* const str = top; + cel_Value* const substr = str + 1; + + switch (cel_Value_Kind(str)) { + case cel_ValueKind_kError: + break; + case cel_ValueKind_kString: + switch (cel_Value_Kind(substr)) { + case cel_ValueKind_kError: + *top = *substr; + break; + case cel_ValueKind_kString: + cel_Value_SetBool( + top, cel_StringView_StartsWith(cel_Value_GetString(str), + cel_Value_GetString(substr))); + break; + default: + cel_Value_SetError(top, + _cel_Interpretable_NewNoSuchOverloadError(interp)); + break; + } + break; + default: + if (cel_Value_IsError(substr)) { + *top = *substr; + } else { + cel_Value_SetError(top, + _cel_Interpretable_NewNoSuchOverloadError(interp)); + } + break; + } + + _cel_Interpretable_Pop(interp, instr); +} + +static inline void _cel_Interpretable_CallEndsWithString( + _cel_Interpretable* cel_nonnull interp, + const _cel_InstrData* cel_nonnull instr) { + CEL_ASSERT_NOT_NULL(interp); + CEL_ASSERT_NOT_NULL(instr); + CEL_ASSERT(instr->call.args == 2); + + cel_Value* const top = _cel_Interpretable_TopN(interp, instr, 2); + cel_Value* const str = top; + cel_Value* const substr = str + 1; + + switch (cel_Value_Kind(str)) { + case cel_ValueKind_kError: + break; + case cel_ValueKind_kString: + switch (cel_Value_Kind(substr)) { + case cel_ValueKind_kError: + *top = *substr; + break; + case cel_ValueKind_kString: + cel_Value_SetBool( + top, cel_StringView_EndsWith(cel_Value_GetString(str), + cel_Value_GetString(substr))); + break; + default: + cel_Value_SetError(top, + _cel_Interpretable_NewNoSuchOverloadError(interp)); + break; + } + break; + default: + if (cel_Value_IsError(substr)) { + *top = *substr; + } else { + cel_Value_SetError(top, + _cel_Interpretable_NewNoSuchOverloadError(interp)); + } + break; + } + + _cel_Interpretable_Pop(interp, instr); +} + +static inline void _cel_Interpretable_CallRegexExpMatch( + _cel_Interpretable* cel_nonnull interp, + const _cel_InstrData* cel_nonnull instr) { + CEL_ASSERT_NOT_NULL(interp); + CEL_ASSERT_NOT_NULL(instr); + CEL_ASSERT(instr->call.args == 2); + + cel_Value* const top = _cel_Interpretable_TopN(interp, instr, 2); + cel_Value* const str = top; + cel_Value* const regex = str + 1; + + switch (cel_Value_Kind(str)) { + case cel_ValueKind_kError: + break; + case cel_ValueKind_kString: + switch (cel_Value_Kind(regex)) { + case cel_ValueKind_kError: + *top = *regex; + break; + case cel_ValueKind_kString: { + cel_Status status; + cel_Status_Construct(&status); + cel_StringView pattern = cel_Value_GetString(regex); + cel_StringView subject = cel_Value_GetString(str); + // TODO: Enable regexp limits to be configurable. + bool matched = + _cel_RegExp_Matches(pattern, cel_nullptr, subject, &status); + if (CEL_UNLIKELY(!cel_Status_Ok(&status))) { + cel_Error* error = cel_Error_New(interp->arena); + if (CEL_UNLIKELY(error == cel_nullptr)) { + cel_Status_Destruct(&status); + _cel_Interpretable_ThrowOutOfMemory(interp); + } + cel_Error_SetMessage(error, cel_Status_Message(&status)); + cel_Value_SetError(top, error); + } else { + cel_Value_SetBool(top, matched); + } + cel_Status_Destruct(&status); + } break; + default: + cel_Value_SetError(top, + _cel_Interpretable_NewNoSuchOverloadError(interp)); + break; + } + break; + default: + if (cel_Value_IsError(regex)) { + *top = *regex; + } else { + cel_Value_SetError(top, + _cel_Interpretable_NewNoSuchOverloadError(interp)); + } + break; + } + _cel_Interpretable_Pop(interp, instr); +} + +static inline void _cel_Interpretable_CallInt( + _cel_Interpretable* cel_nonnull interp, + const _cel_InstrData* cel_nonnull instr) { + CEL_ASSERT_NOT_NULL(interp); + CEL_ASSERT_NOT_NULL(instr); + CEL_ASSERT(instr->call.args == 1); + + cel_Value* const top = _cel_Interpretable_Top(interp, instr); + switch (cel_Value_Kind(top)) { + case cel_ValueKind_kError: + break; + case cel_ValueKind_kInt: + cel_Value_SetInt(top, cel_Value_GetInt(top)); + break; + case cel_ValueKind_kUint: { + _cel_Number number = _cel_UintNumber(cel_Value_GetUint(top)); + int64_t int_value; + if (!_cel_Number_ToInt(number, &int_value)) { + cel_Value_SetError(top, _cel_Interpretable_NewIntOverflowError(interp)); + } else { + cel_Value_SetInt(top, int_value); + } + } break; + case cel_ValueKind_kDouble: { + _cel_Number number = _cel_DoubleNumber(cel_Value_GetDouble(top)); + int64_t int_value; + if (!_cel_Number_ToInt(number, &int_value)) { + cel_Value_SetError(top, _cel_Interpretable_NewIntOverflowError(interp)); + } else { + cel_Value_SetInt(top, int_value); + } + } break; + case cel_ValueKind_kString: { + int64_t value; + cel_StringView str = cel_Value_GetString(top); + if (_cel_Interpretable_FromCharsToInt(str, &value) != 0) { + cel_Value_SetError(top, + _cel_Interpretable_NewNoSuchOverloadError(interp)); + } else { + cel_Value_SetInt(top, value); + } + } break; + case cel_ValueKind_kTimestamp: { + cel_Value_SetInt( + top, cel_Timestamp_ToUnixSeconds(cel_Value_GetTimestamp(top))); + } break; + default: + cel_Value_SetError(top, + _cel_Interpretable_NewNoSuchOverloadError(interp)); + } +} + +static inline void _cel_Interpretable_CallSize( + _cel_Interpretable* cel_nonnull interp, + const _cel_InstrData* cel_nonnull instr) { + CEL_ASSERT_NOT_NULL(interp); + CEL_ASSERT_NOT_NULL(instr); + CEL_ASSERT(instr->call.args == 1); + + cel_Value* const top = _cel_Interpretable_Top(interp, instr); + switch (cel_Value_Kind(top)) { + case cel_ValueKind_kError: + break; + case cel_ValueKind_kString: + cel_Value_SetInt(top, _cel_Utf8_DecodedSize(cel_Value_GetString(top))); + break; + case cel_ValueKind_kBytes: + cel_Value_SetInt(top, cel_StringView_Size(cel_Value_GetBytes(top))); + break; + // TODO: Add support for other types. + default: + cel_Value_SetError(top, + _cel_Interpretable_NewNoSuchOverloadError(interp)); + } +} + +static inline void _cel_Interpretable_Index( + _cel_Interpretable* cel_nonnull interp, + const _cel_InstrData* cel_nonnull instr) { + CEL_ASSERT_NOT_NULL(interp); + CEL_ASSERT_NOT_NULL(instr); + + cel_Value* const top = _cel_Interpretable_TopN(interp, instr, 2); + cel_Value* const lhs = top; + cel_Value* const rhs = lhs + 1; + + cel_ValueContext context = interp->context; + + switch (cel_Value_Kind(lhs)) { + case cel_ValueKind_kError: + // error[T] -> error + break; + case cel_ValueKind_kList: { + int64_t index; + bool index_is_int = false; + if (cel_Value_Kind(rhs) == cel_ValueKind_kError) { + *top = *rhs; + break; + } + switch (cel_Value_Kind(rhs)) { + case cel_ValueKind_kInt: + index = cel_Value_GetInt(rhs); + index_is_int = true; + break; + case cel_ValueKind_kUint: + index_is_int = _cel_Number_ToIntLossless( + _cel_UintNumber(cel_Value_GetUint(rhs)), &index); + break; + case cel_ValueKind_kDouble: + index_is_int = _cel_Number_ToIntLossless( + _cel_DoubleNumber(cel_Value_GetDouble(rhs)), &index); + break; + default: + index_is_int = false; + break; + } + + if (!index_is_int) { + cel_Value_SetError(top, + _cel_Interpretable_NewNoSuchOverloadError(interp)); + break; + } + + // List[int] -> cel_value + cel_Value list_size; + const cel_ListValue* list_value = cel_Value_GetList(lhs); + if (!cel_ListValue_Size(list_value, &context, &list_size, + interp->status)) { + _cel_Interpretable_Throw(interp); + } + if (index < 0 || index >= cel_Value_GetInt(&list_size)) { + cel_Value_SetError(top, + _cel_Interpretable_NewIndexOutOfRangeError(interp)); + } else { + if (!cel_ListValue_Get(list_value, &context, index, top, + interp->status)) { + _cel_Interpretable_Throw(interp); + } + } + } break; + case cel_ValueKind_kMap: { + cel_MapValueKey key; + bool in_error = false; + switch (cel_Value_Kind(rhs)) { + case cel_ValueKind_kError: + *top = *rhs; + in_error = true; + break; + case cel_ValueKind_kInt: + cel_MapValueKey_SetInt(&key, cel_Value_GetInt(rhs)); + break; + case cel_ValueKind_kUint: + cel_MapValueKey_SetUint(&key, cel_Value_GetUint(rhs)); + break; + case cel_ValueKind_kBool: + cel_MapValueKey_SetBool(&key, cel_Value_GetBool(rhs)); + break; + case cel_ValueKind_kString: + cel_MapValueKey_SetString(&key, cel_Value_GetString(rhs)); + break; + default: + cel_Value_SetError(top, + _cel_Interpretable_NewNoSuchOverloadError(interp)); + in_error = true; + break; + } + if (!in_error) { + cel_MapValue map_value = *cel_Value_GetMap(lhs); + cel_Value has_result; + if (!cel_MapValue_Has(&map_value, &interp->context, &key, &has_result, + interp->status)) { + _cel_Interpretable_Throw(interp); + } + if (!cel_Value_GetBool(&has_result)) { + cel_Value_SetError(top, _cel_Interpretable_NewNoSuchKeyError(interp)); + } else { + if (!cel_MapValue_Get(&map_value, &interp->context, &key, top, + interp->status)) { + _cel_Interpretable_Throw(interp); + } + } + } + } break; + default: { + cel_Value_SetError(top, + _cel_Interpretable_NewNoSuchOverloadError(interp)); + } break; + } + + _cel_Interpretable_Pop(interp, instr); +} + +static inline void _cel_Interpretable_In(_cel_Interpretable* cel_nonnull interp, + const _cel_InstrData* cel_nonnull + instr) { + CEL_ASSERT_NOT_NULL(interp); + CEL_ASSERT_NOT_NULL(instr); + + cel_Value* const top = _cel_Interpretable_TopN(interp, instr, 2); + cel_Value* const lhs = top; + cel_Value* const rhs = lhs + 1; + + switch (cel_Value_Kind(rhs)) { + case cel_ValueKind_kError: + if (!cel_Value_IsError(lhs)) { + *top = *rhs; + } + break; + case cel_ValueKind_kList: { + if (cel_Value_IsError(lhs)) { + break; + } + const cel_ListValue* list_value = cel_Value_GetList(rhs); + cel_Value list_size_value; + if (!cel_ListValue_Size(list_value, &interp->context, &list_size_value, + interp->status)) { + _cel_Interpretable_Throw(interp); + } + int64_t list_size = cel_Value_GetInt(&list_size_value); + bool present = false; + for (int64_t i = 0; i < list_size; ++i) { + cel_Value element; + if (!cel_ListValue_Get(list_value, &interp->context, i, &element, + interp->status)) { + _cel_Interpretable_Throw(interp); + } + cel_Value eq_result; + if (!cel_Value_Equals(lhs, &interp->context, &element, &eq_result, + interp->status)) { + _cel_Interpretable_ThrowIfError(interp); + continue; + } + if (cel_Value_IsError(&eq_result)) { + continue; + } + if (cel_Value_IsBool(&eq_result) && cel_Value_GetBool(&eq_result)) { + present = true; + break; + } + } + cel_Value_SetBool(top, present); + } break; + case cel_ValueKind_kMap: { + if (cel_Value_IsError(lhs)) { + break; + } + if (!cel_Value_IsMapKey(lhs)) { + cel_Value_SetError(top, _cel_Interpretable_NewBadKeyError(interp)); + break; + } + cel_MapValueKey key; + switch (cel_Value_Kind(lhs)) { + case cel_ValueKind_kBool: + cel_MapValueKey_SetBool(&key, cel_Value_GetBool(lhs)); + break; + case cel_ValueKind_kInt: + cel_MapValueKey_SetInt(&key, cel_Value_GetInt(lhs)); + break; + case cel_ValueKind_kUint: + cel_MapValueKey_SetUint(&key, cel_Value_GetUint(lhs)); + break; + case cel_ValueKind_kString: + cel_MapValueKey_SetString(&key, cel_Value_GetString(lhs)); + break; + default: + // Unreachable + CEL_UNREACHABLE(); + } + const cel_MapValue* map_value = cel_Value_GetMap(rhs); + if (!cel_MapValue_Has(map_value, &interp->context, &key, top, + interp->status)) { + _cel_Interpretable_Throw(interp); + } + if (cel_Value_IsError(top)) { + cel_Value_SetBool(top, false); + } + } break; + default: { + if (cel_Value_IsError(lhs)) { + break; + } + cel_Value_SetError(top, + _cel_Interpretable_NewNoSuchOverloadError(interp)); + } break; + } + + _cel_Interpretable_Pop(interp, instr); +} + +CEL_ATTRIBUTE_NODISCARD +static inline const _cel_Instr* cel_nonnull +_cel_Interpretable_LazyCall(_cel_Interpretable* cel_nonnull interp, + const _cel_InstrData* cel_nonnull instr) { + if (CEL_UNLIKELY(instr->lazy_call.slot) >= interp->slots_len) { + cel_InternalStatusF( + interp->status, "cel: bad slot: pc=%" PRIuPTR, + cel_containerof(instr, _cel_Instr, data) - interp->instr_ptr); + _cel_Interpretable_Throw(interp); + } + _cel_InterpretableSlot* slot = &interp->slots[instr->lazy_call.slot]; + const _cel_Instr* next = _cel_Interpretable_ShortJump(instr, 1); + if (slot->active) { + *_cel_Interpretable_Push(interp, instr) = slot->value; + return next; + } + if (CEL_UNLIKELY(interp->lazy_stack_top == interp->lazy_stack_end)) { + cel_InternalStatusF( + interp->status, "cel: lazy stack overflow: pc=%" PRIuPTR, + cel_containerof(instr, _cel_Instr, data) - interp->instr_ptr); + _cel_Interpretable_Throw(interp); + } + *interp->lazy_stack_top = next; + ++interp->lazy_stack_top; + return _cel_Interpretable_ShortJump(instr, instr->lazy_call.jump); +} + +CEL_ATTRIBUTE_NODISCARD +static inline const _cel_Instr* cel_nonnull +_cel_Interpretable_LazyReturn(_cel_Interpretable* cel_nonnull interp, + const _cel_InstrData* cel_nonnull instr) { + if (CEL_UNLIKELY(instr->lazy_return.slot) >= interp->slots_len) { + cel_InternalStatusF( + interp->status, "cel: bad slot: pc=%" PRIuPTR, + cel_containerof(instr, _cel_Instr, data) - interp->instr_ptr); + _cel_Interpretable_Throw(interp); + } + if (CEL_UNLIKELY(interp->lazy_stack_top == interp->lazy_stack_base)) { + cel_InternalStatusF( + interp->status, "cel: lazy stack underflow: pc=%" PRIuPTR, + cel_containerof(instr, _cel_Instr, data) - interp->instr_ptr); + _cel_Interpretable_Throw(interp); + } + _cel_InterpretableSlot* slot = &interp->slots[instr->lazy_call.slot]; + if (CEL_UNLIKELY(slot->active)) { + cel_InternalStatusF( + interp->status, "cel: slot reused: pc=%" PRIuPTR, + cel_containerof(instr, _cel_Instr, data) - interp->instr_ptr); + _cel_Interpretable_Throw(interp); + } + slot->value = *_cel_Interpretable_Top(interp, instr); + slot->active = true; + --interp->lazy_stack_top; + return *interp->lazy_stack_top; +} + +static inline void _cel_Interpretable_LazyEnter( + _cel_Interpretable* cel_nonnull interp, + const _cel_InstrData* cel_nonnull instr) { + CEL_ASSERT_LT(instr->lazy_enter.slot, interp->slots_len); + if (CEL_UNLIKELY(interp->slots[instr->lazy_enter.slot].active)) { + cel_InternalStatusF( + interp->status, "cel: slot reused: pc=%" PRIuPTR, + cel_containerof(instr, _cel_Instr, data) - interp->instr_ptr); + _cel_Interpretable_Throw(interp); + } +} + +static inline void _cel_Interpretable_LazyLeave( + _cel_Interpretable* cel_nonnull interp, + const _cel_InstrData* cel_nonnull instr) { + CEL_ASSERT_GE(instr->lazy_leave.slot, 0); + CEL_ASSERT_LT(instr->lazy_leave.slot, interp->slots_len); + CEL_ASSERT_LE(instr->lazy_leave.slot + instr->lazy_leave.num_slots, + interp->slots_len); + for (uint32_t i = 0; i < instr->lazy_leave.num_slots; ++i) { + interp->slots[instr->lazy_leave.slot + i].active = false; + } +} + +#if defined(__GNUC__) || defined(__clang__) +static inline void _cel_Interpretable_ComputedGoto( + _cel_Interpretable* const cel_nonnull interp, + cel_Value* const cel_nonnull result) { + static const void* const dispatch_table[] = { + &&INSTR_UNREACHABLE, + &&INSTR_NULL_CONST, + &&INSTR_FALSE_CONST, + &&INSTR_TRUE_CONST, + &&INSTR_INT_CONST, + &&INSTR_UINT_CONST, + &&INSTR_DOUBLE_CONST, + &&INSTR_BYTES_CONST, + &&INSTR_STRING_CONST, + &&INSTR_DURATION_CONST, + &&INSTR_TIMESTAMP_CONST, + &&INSTR_IDENT, + &&INSTR_IDENT_JUMP, + &&INSTR_CONT_IDENT, + &&INSTR_CONT_IDENT_JUMP, + &&INSTR_JUMP, + &&INSTR_HAS, + &&INSTR_MESSAGE_HAS, + &&INSTR_SELECT, + &&INSTR_MESSAGE_SELECT, + &&INSTR_ADD, + &&INSTR_SUBTRACT, + &&INSTR_MULTIPLY, + &&INSTR_DIVIDE, + &&INSTR_MODULO, + &&INSTR_LOGICAL_NOT, + &&INSTR_NEGATE, + &&INSTR_LOGICAL_AND, + &&INSTR_LOGICAL_OR, + &&INSTR_EQUALS, + &&INSTR_NOT_EQUALS, + &&INSTR_LESS, + &&INSTR_LESS_EQUALS, + &&INSTR_GREATER, + &&INSTR_GREATER_EQUALS, + &&INSTR_COND_JUMP, + &&INSTR_TRILEAN_JUMP, + &&INSTR_ERROR_JUMP, + &&INSTR_LIST, + &&INSTR_KEY_JUMP, + &&INSTR_MAP, + &&INSTR_CALL_BOOL, + &&INSTR_CALL_INT, + &&INSTR_CALL_UINT, + &&INSTR_CALL_DOUBLE, + &&INSTR_CALL_BYTES, + &&INSTR_CALL_STRING, + &&INSTR_CALL_TIMESTAMP, + &&INSTR_CALL_DURATION, + &&INSTR_CALL_SIZE, + &&INSTR_CALL_CONTAINS_STRING, + &&INSTR_CALL_STARTS_WITH_STRING, + &&INSTR_CALL_ENDS_WITH_STRING, + &&INSTR_CALL_REGEX_EXP_MATCH, + &&INSTR_IN, + &&INSTR_INDEX, + &&INSTR_LAZY_CALL, + &&INSTR_LAZY_RETURN, + &&INSTR_LAZY_ENTER, + &&INSTR_LAZY_LEAVE, + &&INSTR_EXIT, + }; +#define DISPATCH_INSTR() goto* dispatch_table[instr->kind] +#define NEXT_INSTR() ++instr +#define DISPATCH_NEXT_INSTR() \ + NEXT_INSTR(); \ + DISPATCH_INSTR() + const _cel_Instr* instr = interp->instr_ptr; + DISPATCH_INSTR(); +INSTR_UNREACHABLE: + _cel_Interpretable_Unreachable(interp, &instr->data); +INSTR_NULL_CONST: + _cel_Interpretable_NullConst(interp, &instr->data); + DISPATCH_NEXT_INSTR(); +INSTR_FALSE_CONST: + _cel_Interpretable_FalseConst(interp, &instr->data); + DISPATCH_NEXT_INSTR(); +INSTR_TRUE_CONST: + _cel_Interpretable_TrueConst(interp, &instr->data); + DISPATCH_NEXT_INSTR(); +INSTR_INT_CONST: + _cel_Interpretable_IntConst(interp, &instr->data); + DISPATCH_NEXT_INSTR(); +INSTR_UINT_CONST: + _cel_Interpretable_UintConst(interp, &instr->data); + DISPATCH_NEXT_INSTR(); +INSTR_DOUBLE_CONST: + _cel_Interpretable_DoubleConst(interp, &instr->data); + DISPATCH_NEXT_INSTR(); +INSTR_BYTES_CONST: + _cel_Interpretable_BytesConst(interp, &instr->data); + DISPATCH_NEXT_INSTR(); +INSTR_STRING_CONST: + _cel_Interpretable_StringConst(interp, &instr->data); + DISPATCH_NEXT_INSTR(); +INSTR_DURATION_CONST: + _cel_Interpretable_DurationConst(interp, &instr->data); + DISPATCH_NEXT_INSTR(); +INSTR_TIMESTAMP_CONST: + _cel_Interpretable_TimestampConst(interp, &instr->data); + DISPATCH_NEXT_INSTR(); +INSTR_IDENT: + _cel_Interpretable_Ident(interp, &instr->data); + DISPATCH_NEXT_INSTR(); +INSTR_IDENT_JUMP: + instr = _cel_Interpretable_IdentJump(interp, &instr->data); + DISPATCH_INSTR(); +INSTR_CONT_IDENT: + _cel_Interpretable_ContIdent(interp, &instr->data); + DISPATCH_NEXT_INSTR(); +INSTR_CONT_IDENT_JUMP: + instr = _cel_Interpretable_ContIdentJump(interp, &instr->data); + DISPATCH_INSTR(); +INSTR_JUMP: + instr = _cel_Interpretable_Jump(interp, &instr->data); + DISPATCH_INSTR(); +INSTR_HAS: + _cel_Interpretable_Has(interp, &instr->data); + DISPATCH_NEXT_INSTR(); +INSTR_MESSAGE_HAS: + _cel_Interpretable_MessageHas(interp, &instr->data); + DISPATCH_NEXT_INSTR(); +INSTR_SELECT: + _cel_Interpretable_Select(interp, &instr->data); + DISPATCH_NEXT_INSTR(); +INSTR_MESSAGE_SELECT: + _cel_Interpretable_MessageSelect(interp, &instr->data); + DISPATCH_NEXT_INSTR(); +INSTR_ADD: + _cel_Interpretable_Add(interp, &instr->data); + DISPATCH_NEXT_INSTR(); +INSTR_SUBTRACT: + _cel_Interpretable_Subtract(interp, &instr->data); + DISPATCH_NEXT_INSTR(); +INSTR_MULTIPLY: + _cel_Interpretable_Multiply(interp, &instr->data); + DISPATCH_NEXT_INSTR(); +INSTR_DIVIDE: + _cel_Interpretable_Divide(interp, &instr->data); + DISPATCH_NEXT_INSTR(); +INSTR_MODULO: + _cel_Interpretable_Modulo(interp, &instr->data); + DISPATCH_NEXT_INSTR(); +INSTR_LOGICAL_NOT: + _cel_Interpretable_LogicalNot(interp, &instr->data); + DISPATCH_NEXT_INSTR(); +INSTR_NEGATE: + _cel_Interpretable_Negate(interp, &instr->data); + DISPATCH_NEXT_INSTR(); +INSTR_LOGICAL_AND: + _cel_Interpretable_LogicalAnd(interp, &instr->data); + DISPATCH_NEXT_INSTR(); +INSTR_LOGICAL_OR: + _cel_Interpretable_LogicalOr(interp, &instr->data); + DISPATCH_NEXT_INSTR(); +INSTR_EQUALS: + _cel_Interpretable_Equals(interp, &instr->data); + DISPATCH_NEXT_INSTR(); +INSTR_NOT_EQUALS: + _cel_Interpretable_NotEquals(interp, &instr->data); + DISPATCH_NEXT_INSTR(); +INSTR_LESS: + _cel_Interpretable_Less(interp, &instr->data); + DISPATCH_NEXT_INSTR(); +INSTR_LESS_EQUALS: + _cel_Interpretable_LessEquals(interp, &instr->data); + DISPATCH_NEXT_INSTR(); +INSTR_GREATER: + _cel_Interpretable_Greater(interp, &instr->data); + DISPATCH_NEXT_INSTR(); +INSTR_GREATER_EQUALS: + _cel_Interpretable_GreaterEquals(interp, &instr->data); + DISPATCH_NEXT_INSTR(); +INSTR_COND_JUMP: + instr = _cel_Interpretable_CondJump(interp, &instr->data); + DISPATCH_INSTR(); +INSTR_TRILEAN_JUMP: + instr = _cel_Interpretable_TrileanJump(interp, &instr->data); + DISPATCH_INSTR(); +INSTR_ERROR_JUMP: + instr = _cel_Interpretable_ErrorJump(interp, &instr->data); + DISPATCH_INSTR(); +INSTR_LIST: + _cel_Interpretable_List(interp, &instr->data); + DISPATCH_NEXT_INSTR(); +INSTR_KEY_JUMP: + instr = _cel_Interpretable_KeyJump(interp, &instr->data); + DISPATCH_INSTR(); +INSTR_MAP: + _cel_Interpretable_Map(interp, &instr->data); + DISPATCH_NEXT_INSTR(); +INSTR_CALL_BOOL: + _cel_Interpretable_CallBool(interp, &instr->data); + DISPATCH_NEXT_INSTR(); +INSTR_CALL_INT: + _cel_Interpretable_CallInt(interp, &instr->data); + DISPATCH_NEXT_INSTR(); +INSTR_CALL_UINT: + _cel_Interpretable_CallUint(interp, &instr->data); + DISPATCH_NEXT_INSTR(); +INSTR_CALL_DOUBLE: + _cel_Interpretable_CallDouble(interp, &instr->data); + DISPATCH_NEXT_INSTR(); +INSTR_CALL_BYTES: + _cel_Interpretable_CallBytes(interp, &instr->data); + DISPATCH_NEXT_INSTR(); +INSTR_CALL_STRING: + _cel_Interpretable_CallString(interp, &instr->data); + DISPATCH_NEXT_INSTR(); +INSTR_CALL_TIMESTAMP: + _cel_Interpretable_CallTimestamp(interp, &instr->data); + DISPATCH_NEXT_INSTR(); +INSTR_CALL_DURATION: + _cel_Interpretable_CallDuration(interp, &instr->data); + DISPATCH_NEXT_INSTR(); +INSTR_CALL_SIZE: + _cel_Interpretable_CallSize(interp, &instr->data); + DISPATCH_NEXT_INSTR(); +INSTR_CALL_CONTAINS_STRING: + _cel_Interpretable_CallContainsString(interp, &instr->data); + DISPATCH_NEXT_INSTR(); +INSTR_CALL_STARTS_WITH_STRING: + _cel_Interpretable_CallStartsWithString(interp, &instr->data); + DISPATCH_NEXT_INSTR(); +INSTR_CALL_ENDS_WITH_STRING: + _cel_Interpretable_CallEndsWithString(interp, &instr->data); + DISPATCH_NEXT_INSTR(); +INSTR_CALL_REGEX_EXP_MATCH: + _cel_Interpretable_CallRegexExpMatch(interp, &instr->data); + DISPATCH_NEXT_INSTR(); +INSTR_INDEX: + _cel_Interpretable_Index(interp, &instr->data); + DISPATCH_NEXT_INSTR(); +INSTR_IN: + _cel_Interpretable_In(interp, &instr->data); + DISPATCH_NEXT_INSTR(); +INSTR_LAZY_CALL: + instr = _cel_Interpretable_LazyCall(interp, &instr->data); + DISPATCH_INSTR(); +INSTR_LAZY_RETURN: + instr = _cel_Interpretable_LazyReturn(interp, &instr->data); + DISPATCH_INSTR(); +INSTR_LAZY_ENTER: + _cel_Interpretable_LazyEnter(interp, &instr->data); + DISPATCH_NEXT_INSTR(); +INSTR_LAZY_LEAVE: + _cel_Interpretable_LazyLeave(interp, &instr->data); + DISPATCH_NEXT_INSTR(); +INSTR_EXIT: + return; +#undef DISPATCH_NEXT_INSTR +#undef NEXT_INSTR +#undef DISPATCH_INSTR +} +#endif + +_CEL_ATTRIBUTE_UNUSED +static inline void _cel_Interpretable_Switch( + _cel_Interpretable* const cel_nonnull interp, + cel_Value* const cel_nonnull result) { + const _cel_Instr* instr = interp->instr_ptr; + while (true) { + CEL_ASSERT_GE(instr, interp->instr_ptr); + CEL_ASSERT_LT(instr, interp->instr_ptr + interp->instr_len); + switch (instr->kind) { + case _cel_InstrKind_kUnreachable: + _cel_Interpretable_Unreachable(interp, &instr->data); + case _cel_InstrKind_kNullConst: + _cel_Interpretable_NullConst(interp, &instr->data); + ++instr; + break; + case _cel_InstrKind_kFalseConst: + _cel_Interpretable_FalseConst(interp, &instr->data); + ++instr; + break; + case _cel_InstrKind_kTrueConst: + _cel_Interpretable_TrueConst(interp, &instr->data); + ++instr; + break; + case _cel_InstrKind_kIntConst: + _cel_Interpretable_IntConst(interp, &instr->data); + ++instr; + break; + case _cel_InstrKind_kUintConst: + _cel_Interpretable_UintConst(interp, &instr->data); + ++instr; + break; + case _cel_InstrKind_kDoubleConst: + _cel_Interpretable_DoubleConst(interp, &instr->data); + ++instr; + break; + case _cel_InstrKind_kBytesConst: + _cel_Interpretable_BytesConst(interp, &instr->data); + ++instr; + break; + case _cel_InstrKind_kStringConst: + _cel_Interpretable_StringConst(interp, &instr->data); + ++instr; + break; + case _cel_InstrKind_kDurationConst: + _cel_Interpretable_DurationConst(interp, &instr->data); + ++instr; + break; + case _cel_InstrKind_kTimestampConst: + _cel_Interpretable_TimestampConst(interp, &instr->data); + ++instr; + break; + case _cel_InstrKind_kIdent: + _cel_Interpretable_Ident(interp, &instr->data); + ++instr; + break; + case _cel_InstrKind_kIdentJump: + instr = _cel_Interpretable_IdentJump(interp, &instr->data); + break; + case _cel_InstrKind_kContIdent: + _cel_Interpretable_ContIdent(interp, &instr->data); + ++instr; + break; + case _cel_InstrKind_kContIdentJump: + instr = _cel_Interpretable_ContIdentJump(interp, &instr->data); + break; + case _cel_InstrKind_kJump: + instr = _cel_Interpretable_Jump(interp, &instr->data); + break; + case _cel_InstrKind_kHas: + _cel_Interpretable_Has(interp, &instr->data); + ++instr; + break; + case _cel_InstrKind_kMessageHas: + _cel_Interpretable_MessageHas(interp, &instr->data); + ++instr; + break; + case _cel_InstrKind_kSelect: + _cel_Interpretable_Select(interp, &instr->data); + ++instr; + break; + case _cel_InstrKind_kMessageSelect: + _cel_Interpretable_MessageSelect(interp, &instr->data); + ++instr; + break; + case _cel_InstrKind_kAdd: + _cel_Interpretable_Add(interp, &instr->data); + ++instr; + break; + case _cel_InstrKind_kSubtract: + _cel_Interpretable_Subtract(interp, &instr->data); + ++instr; + break; + case _cel_InstrKind_kMultiply: + _cel_Interpretable_Multiply(interp, &instr->data); + ++instr; + break; + case _cel_InstrKind_kDivide: + _cel_Interpretable_Divide(interp, &instr->data); + ++instr; + break; + case _cel_InstrKind_kModulo: + _cel_Interpretable_Modulo(interp, &instr->data); + ++instr; + break; + case _cel_InstrKind_kLogicalNot: + _cel_Interpretable_LogicalNot(interp, &instr->data); + ++instr; + break; + case _cel_InstrKind_kNegate: + _cel_Interpretable_Negate(interp, &instr->data); + ++instr; + break; + case _cel_InstrKind_kLogicalAnd: + _cel_Interpretable_LogicalAnd(interp, &instr->data); + ++instr; + break; + case _cel_InstrKind_kLogicalOr: + _cel_Interpretable_LogicalOr(interp, &instr->data); + ++instr; + break; + case _cel_InstrKind_kEquals: + _cel_Interpretable_Equals(interp, &instr->data); + ++instr; + break; + case _cel_InstrKind_kNotEquals: + _cel_Interpretable_NotEquals(interp, &instr->data); + ++instr; + break; + case _cel_InstrKind_kLess: + _cel_Interpretable_Less(interp, &instr->data); + ++instr; + break; + case _cel_InstrKind_kLessEquals: + _cel_Interpretable_LessEquals(interp, &instr->data); + ++instr; + break; + case _cel_InstrKind_kGreater: + _cel_Interpretable_Greater(interp, &instr->data); + ++instr; + break; + case _cel_InstrKind_kGreaterEquals: + _cel_Interpretable_GreaterEquals(interp, &instr->data); + ++instr; + break; + case _cel_InstrKind_kCondJump: + instr = _cel_Interpretable_CondJump(interp, &instr->data); + break; + case _cel_InstrKind_kTrileanJump: + instr = _cel_Interpretable_TrileanJump(interp, &instr->data); + break; + case _cel_InstrKind_kErrorJump: + instr = _cel_Interpretable_ErrorJump(interp, &instr->data); + break; + case _cel_InstrKind_kList: + _cel_Interpretable_List(interp, &instr->data); + ++instr; + break; + case _cel_InstrKind_kKeyJump: + instr = _cel_Interpretable_KeyJump(interp, &instr->data); + break; + case _cel_InstrKind_kMap: + _cel_Interpretable_Map(interp, &instr->data); + ++instr; + break; + case _cel_InstrKind_kCallBool: + _cel_Interpretable_CallBool(interp, &instr->data); + ++instr; + break; + case _cel_InstrKind_kCallInt: + _cel_Interpretable_CallInt(interp, &instr->data); + ++instr; + break; + case _cel_InstrKind_kCallUint: + _cel_Interpretable_CallUint(interp, &instr->data); + ++instr; + break; + case _cel_InstrKind_kCallDouble: + _cel_Interpretable_CallDouble(interp, &instr->data); + ++instr; + break; + case _cel_InstrKind_kCallBytes: + _cel_Interpretable_CallBytes(interp, &instr->data); + ++instr; + break; + case _cel_InstrKind_kCallString: + _cel_Interpretable_CallString(interp, &instr->data); + ++instr; + break; + case _cel_InstrKind_kCallTimestamp: + _cel_Interpretable_CallTimestamp(interp, &instr->data); + ++instr; + break; + case _cel_InstrKind_kCallDuration: + _cel_Interpretable_CallDuration(interp, &instr->data); + ++instr; + break; + case _cel_InstrKind_kCallSize: + _cel_Interpretable_CallSize(interp, &instr->data); + ++instr; + break; + case _cel_InstrKind_kCallContainsString: + _cel_Interpretable_CallContainsString(interp, &instr->data); + ++instr; + break; + case _cel_InstrKind_kCallStartsWithString: + _cel_Interpretable_CallStartsWithString(interp, &instr->data); + ++instr; + break; + case _cel_InstrKind_kCallEndsWithString: + _cel_Interpretable_CallEndsWithString(interp, &instr->data); + ++instr; + break; + case _cel_InstrKind_kCallRegexExpMatch: + _cel_Interpretable_CallRegexExpMatch(interp, &instr->data); + ++instr; + break; + case _cel_InstrKind_kIndex: + _cel_Interpretable_Index(interp, &instr->data); + ++instr; + break; + case _cel_InstrKind_kIn: + _cel_Interpretable_In(interp, &instr->data); + ++instr; + break; + case _cel_InstrKind_kLazyCall: + instr = _cel_Interpretable_LazyCall(interp, &instr->data); + break; + case _cel_InstrKind_kLazyReturn: + instr = _cel_Interpretable_LazyReturn(interp, &instr->data); + break; + case _cel_InstrKind_kLazyEnter: + _cel_Interpretable_LazyEnter(interp, &instr->data); + ++instr; + break; + case _cel_InstrKind_kLazyLeave: + _cel_Interpretable_LazyLeave(interp, &instr->data); + ++instr; + break; + case _cel_InstrKind_kExit: + return; + default: + CEL_UNREACHABLE(); + } + } +} + +extern "C" void _cel_Interpretable_Initialize( + _cel_Interpretable* cel_nonnull interp, const cel_Program* cel_nonnull prog, + cel_Value* cel_nonnull value_stack_base, size_t value_stack_size, + _cel_InterpretableSlot* cel_nonnull slots, size_t num_slots, + const _cel_Instr * cel_nonnull * cel_nonnull lazy_stack_base, + size_t lazy_stack_size) { + CEL_ASSERT_NOT_NULL(interp); + CEL_ASSERT_NOT_NULL(prog); + CEL_ASSERT_NOT_NULL(value_stack_base); + CEL_ASSERT_GE(value_stack_size, 1); + + memset(interp, 0, sizeof(*interp)); + interp->alloc = prog->rt->alloc; + interp->def_pool = prog->rt->def_pool; + interp->wkts = &prog->rt->wkts; + interp->instr_ptr = _cel_Array_Data(&prog->instrs); + interp->instr_len = _cel_Array_Size(&prog->instrs); + interp->strings_table_ptr = _cel_Array_Data(&prog->strings_table); + interp->strings_table_len = _cel_Array_Size(&prog->strings_table); + interp->candidate_names_ptr = _cel_Array_Data(&prog->candidate_names_table); + interp->candidate_names_len = _cel_Array_Size(&prog->candidate_names_table); + interp->value_stack_base = interp->value_stack_top = value_stack_base; + interp->value_stack_end = value_stack_base + value_stack_size; + interp->context.alloc = interp->alloc; + interp->context.def_pool = interp->def_pool; + interp->context.well_known_types = interp->wkts; + interp->slots = slots; + interp->slots_len = num_slots; + interp->lazy_stack_base = interp->lazy_stack_top = lazy_stack_base; + interp->lazy_stack_end = lazy_stack_base + lazy_stack_size; +} + +extern "C" CEL_ATTRIBUTE_NOINLINE bool _cel_Interpretable_Execute( + _cel_Interpretable* const volatile cel_nonnull interp, + cel_Value* const volatile cel_nonnull result) { + CEL_ASSERT_NOT_NULL(interp); + CEL_ASSERT_NOT_NULL(result); + + switch (_cel_setjmp(interp->jmp)) { + case 0: { + // Under GCC and clang, we can use computed gotos. This is faster than the + // switch alternative. On other compilers we fallback to the switch + // implementation. +#if defined(__GNUC__) || defined(__clang__) + _cel_Interpretable_ComputedGoto(interp, result); +#else + _cel_Interpretable_Switch(interp, result); +#endif + CEL_ASSERT(cel_Status_Ok(interp->status)); + CEL_ASSERT(interp->value_stack_top == interp->value_stack_base + 1); + *result = *(--interp->value_stack_top); + return true; + } + case 1: { + CEL_ASSERT_NOT(cel_Status_Ok(interp->status)); + interp->value_stack_top = interp->value_stack_base; + return false; + } + default: + CEL_UNREACHABLE(); + } +} diff --git a/cel-c/src/runtime/interpreter.cc b/cel-c/src/runtime/interpreter.cc new file mode 100644 index 0000000..f3c72f7 --- /dev/null +++ b/cel-c/src/runtime/interpreter.cc @@ -0,0 +1,2065 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "cel-c/src/runtime/interpreter.h" + +#include +#include +#include + +#include "cel-c/alloc.h" +#include "cel-c/assert.h" +#include "cel-c/ast.h" +#include "cel-c/ast_traverse.h" +#include "cel-c/ast_visitor.h" +#include "cel-c/config.h" +#include "cel-c/constant.h" +#include "cel-c/hash.h" +#include "cel-c/operators.h" +#include "cel-c/ref.h" +#include "cel-c/src/array.h" +#include "cel-c/src/container.h" +#include "cel-c/src/deque.h" +#include "cel-c/src/flat_hash_map.h" +#include "cel-c/src/runtime/instr.h" +#include "cel-c/src/runtime/program.h" +#include "cel-c/src/runtime/runtime.h" +#include "cel-c/src/setjmp.h" +#include "cel-c/src/string.h" +#include "cel-c/status.h" +#include "cel-c/string_view.h" +#include "cel-c/type.h" +#include "cel-c/well_known_types.h" +#include "upb/reflection/def.h" + +typedef struct { + const cel_IdentExpr* cel_nullability_unknown expr; + // Starting PC. + uint32_t start_pc; +} _cel_InterpreterNspaceVarIdent; + +typedef struct { + const cel_SelectExpr* cel_nonnull expr; + // Starting PC. + uint32_t ident_pc; + uint32_t select_pc; +} _cel_InterpreterNspaceVarSelect; + +typedef struct { + _cel_InterpreterNspaceVarIdent ident; + _cel_Array(_cel_InterpreterNspaceVarSelect) selects; + _cel_String name; +} _cel_InterpreterNspaceVar; + +static void _cel_InterpreterNspaceVar_Construct( + _cel_InterpreterNspaceVar* cel_nonnull nspace_var) { + memset(nspace_var, 0, sizeof(*nspace_var)); + _cel_Array_Construct(&nspace_var->selects); + _cel_String_Construct(&nspace_var->name); +} + +static void _cel_InterpreterNspaceVar_Destruct( + _cel_InterpreterNspaceVar* cel_nonnull nspace_var, + cel_Allocator* cel_nonnull alloc) { + _cel_Array_Destruct(&nspace_var->selects, alloc); + _cel_String_Destruct(&nspace_var->name, alloc); +} + +typedef struct { + const cel_BinaryExpr* cel_nonnull node; + uint32_t pc; + bool cond; +} _cel_InterpreterBinaryCond; + +typedef struct { + const cel_TernaryExpr* cel_nonnull node; + uint32_t trilean_jump_pc; + uint32_t jump_pc; +} _cel_InterpreterTernaryCond; + +typedef struct { + // cel_BinaryExpr or cel_TernaryExpr + union { + struct { + const cel_Expr* cel_nonnull node; + }; + _cel_InterpreterBinaryCond binary; + _cel_InterpreterTernaryCond ternary; + }; +} _cel_InterpreterCond; + +typedef struct { + // One for each element of the list. Each is an _cel_ErrorJumpInstr. + _cel_Array(uint32_t) pcs; +} _cel_InterpreterList; + +typedef struct { + // One for each key and value of the map. The key is an _cel_Foo and the value + // is an _cel_ErrorJumpInstr. + _cel_Array(uint32_t) pcs; +} _cel_InterpreterMap; + +typedef struct { + uint32_t current; + uint32_t max; +} _cel_InterpreterValueStackBounds; + +static CEL_INLINE void _cel_InterpreterValueStackBounds_Construct( + _cel_InterpreterValueStackBounds* cel_nonnull bounds) { + bounds->current = 0; + bounds->max = 0; +} + +static CEL_INLINE void _cel_InterpreterValueStackBounds_Add( + _cel_InterpreterValueStackBounds* cel_nonnull bounds, uint32_t n) { + CEL_ASSERT_LE(n, UINT32_MAX - bounds->current); + + bounds->current += n; + + if (bounds->current > bounds->max) { + bounds->max = bounds->current; + } +} + +static CEL_INLINE void _cel_InterpreterValueStackBounds_Subtract( + _cel_InterpreterValueStackBounds* cel_nonnull bounds, uint32_t n) { + CEL_ASSERT_LE(n, bounds->current); + + bounds->current -= n; +} + +static CEL_INLINE void _cel_InterpreterValueStackBounds_Increment( + _cel_InterpreterValueStackBounds* cel_nonnull bounds) { + _cel_InterpreterValueStackBounds_Add(bounds, 1); +} + +static CEL_INLINE void _cel_InterpreterValueStackBounds_Decrement( + _cel_InterpreterValueStackBounds* cel_nonnull bounds) { + _cel_InterpreterValueStackBounds_Subtract(bounds, 1); +} + +typedef struct { + // Name of the bound variable. + cel_StringView name; + // When variable names shadow each other, we need to know the prev slot index + // so we can restore it in _cel_Interpreter::slot_map. + int32_t prev_slot; + // The slot index, also the index into _cel_Interpreter::slots. + uint32_t index; + // PC for the jump we insert before assembling the subexpression. It is filled + // in to jump over the subexpression. + uint32_t guard_pc; + // PC for the first instruction in the subexpression. + uint32_t pc; + // Current value stack size for the initExpr of this comprehension. + _cel_InterpreterValueStackBounds value_stack_bounds; +} _cel_InterpreterSlot; + +typedef struct { + cel_AstVisitor visitor; + + cel_AstTraverser* cel_nonnull traverser; + const cel_Runtime* cel_nonnull rt; + cel_Allocator* cel_nonnull alloc; + const upb_DefPool* cel_nonnull def_pool; + const cel_WellKnownTypes* cel_nonnull wkts; + cel_Status* cel_nonnull status; + + cel_Program* cel_nullable prog; + + // Mapping between strings, bytes, idents and their index in + // `prog->string_pool`. + _cel_FlatHashMap(cel_StringView, uint32_t) string_pool_indices; + + _cel_FlatHashMap(cel_StringView, uint32_t) candidate_names_indices; + + _cel_InterpreterNspaceVar nspace_var; + + _cel_Array(_cel_InterpreterCond) conds; + + _cel_Array(_cel_InterpreterList) lists; + + _cel_Array(_cel_InterpreterMap) maps; + + _cel_Deque(_cel_InterpreterSlot) slots; + _cel_FlatHashMap(cel_StringView, uint32_t) slot_map; + _cel_Array(uint32_t) slot_stack; + + const cel_ComprehensionExpr* cel_nullable root_bind; + + _cel_InterpreterValueStackBounds value_stack_bounds; + _cel_Array(_cel_InterpreterValueStackBounds* cel_nonnull) + value_stack_bounds_stack; + + _cel_jmp_buf jmp; +} _cel_Interpreter; + +CEL_ATTRIBUTE_NODISCARD +static uint32_t _cel_Interpreter_PC(const _cel_Interpreter* cel_nonnull interp, + const _cel_Instr* cel_nonnull instr) { + return (uint32_t)(instr - _cel_Array_Data(&interp->prog->instrs)); +} + +CEL_ATTRIBUTE_NODISCARD +static uint32_t _cel_Interpreter_NextPC( + const _cel_Interpreter* cel_nonnull interp) { + return (uint32_t)_cel_Array_Size(&interp->prog->instrs); +} + +CEL_ATTRIBUTE_NODISCARD +static _cel_Instr* _cel_Interpreter_InstrAt( + const _cel_Interpreter* cel_nonnull interp, uint32_t pc) { + return _cel_Array_MutableData(&interp->prog->instrs) + pc; +} + +CEL_ATTRIBUTE_NODISCARD +static CEL_INLINE _cel_Interpreter* cel_nonnull +_cel_Interpreter_FromAstVisitor(cel_AstVisitor* cel_nonnull visitor) { + return cel_containerof(visitor, _cel_Interpreter, visitor); +} + +CEL_ATTRIBUTE_NODISCARD +static _cel_Instr* cel_nonnull _cel_Interpreter_AppendInstrN( + _cel_Interpreter* cel_nonnull interp, size_t count) { + _cel_Instr* instr = + _cel_Array_Append(&interp->prog->instrs, interp->alloc, count); + if (CEL_UNLIKELY(instr == cel_nullptr)) { + cel_OutOfMemoryStatus(interp->status); + _cel_longjmp(interp->jmp); + } + return instr; +} + +CEL_ATTRIBUTE_NODISCARD +static _cel_Instr* cel_nonnull +_cel_Interpreter_AppendInstr(_cel_Interpreter* cel_nonnull interp) { + return _cel_Interpreter_AppendInstrN(interp, 1); +} + +CEL_ATTRIBUTE_NODISCARD +static _cel_InterpreterCond* cel_nonnull +_cel_Interpreter_PushCond(_cel_Interpreter* cel_nonnull interp) { + _cel_InterpreterCond* cond = _cel_Array_Push(&interp->conds, interp->alloc); + if (CEL_UNLIKELY(cond == cel_nullptr)) { + cel_OutOfMemoryStatus(interp->status); + _cel_longjmp(interp->jmp); + } + return cond; +} + +CEL_ATTRIBUTE_NODISCARD +static _cel_InterpreterCond* cel_nonnull +_cel_Interpreter_TopCond(_cel_Interpreter* cel_nonnull interp) { + if (_cel_Array_Empty(&interp->conds)) { + cel_InternalStatus(interp->status, + cel_StringView_From("cel: cond stack empty")); + _cel_longjmp(interp->jmp); + } + return _cel_Array_MutableBack(&interp->conds); +} + +CEL_ATTRIBUTE_NODISCARD +static _cel_InterpreterCond* cel_nullable +_cel_Interpreter_PeekCond(_cel_Interpreter* cel_nonnull interp) { + if (_cel_Array_Empty(&interp->conds)) { + return cel_nullptr; + } + return _cel_Array_MutableBack(&interp->conds); +} + +static void _cel_Interpreter_PopCond(_cel_Interpreter* cel_nonnull interp) { + if (_cel_Array_Empty(&interp->conds)) { + cel_InternalStatus(interp->status, + cel_StringView_From("cel: cond stack empty")); + _cel_longjmp(interp->jmp); + } + _cel_Array_Pop(&interp->conds); +} + +CEL_ATTRIBUTE_NODISCARD +static _cel_InterpreterList* cel_nonnull +_cel_Interpreter_PushList(_cel_Interpreter* cel_nonnull interp) { + _cel_InterpreterList* list = _cel_Array_Push(&interp->lists, interp->alloc); + if (CEL_UNLIKELY(list == cel_nullptr)) { + cel_OutOfMemoryStatus(interp->status); + _cel_longjmp(interp->jmp); + } + return list; +} + +CEL_ATTRIBUTE_NODISCARD +static _cel_InterpreterList* cel_nonnull +_cel_Interpreter_TopList(_cel_Interpreter* cel_nonnull interp) { + if (_cel_Array_Empty(&interp->lists)) { + cel_InternalStatus(interp->status, + cel_StringView_From("cel: list stack empty")); + _cel_longjmp(interp->jmp); + } + return _cel_Array_MutableBack(&interp->lists); +} + +static void _cel_Interpreter_PopList(_cel_Interpreter* cel_nonnull interp) { + if (_cel_Array_Empty(&interp->lists)) { + cel_InternalStatus(interp->status, + cel_StringView_From("cel: list stack empty")); + _cel_longjmp(interp->jmp); + } + _cel_Array_Pop(&interp->lists); +} + +CEL_ATTRIBUTE_NODISCARD +static _cel_InterpreterMap* cel_nonnull +_cel_Interpreter_PushMap(_cel_Interpreter* cel_nonnull interp) { + _cel_InterpreterMap* list = _cel_Array_Push(&interp->maps, interp->alloc); + if (CEL_UNLIKELY(list == cel_nullptr)) { + cel_OutOfMemoryStatus(interp->status); + _cel_longjmp(interp->jmp); + } + return list; +} + +CEL_ATTRIBUTE_NODISCARD +static _cel_InterpreterMap* cel_nonnull +_cel_Interpreter_TopMap(_cel_Interpreter* cel_nonnull interp) { + if (_cel_Array_Empty(&interp->maps)) { + cel_InternalStatus(interp->status, + cel_StringView_From("cel: map stack empty")); + _cel_longjmp(interp->jmp); + } + return _cel_Array_MutableBack(&interp->maps); +} + +static void _cel_Interpreter_PopMap(_cel_Interpreter* cel_nonnull interp) { + if (_cel_Array_Empty(&interp->maps)) { + cel_InternalStatus(interp->status, + cel_StringView_From("cel: map stack empty")); + _cel_longjmp(interp->jmp); + } + _cel_Array_Pop(&interp->maps); +} + +CEL_ATTRIBUTE_NODISCARD +static _cel_InterpreterSlot* cel_nonnull _cel_Interpreter_NewSlot( + _cel_Interpreter* cel_nonnull interp, cel_StringView name) { + uint32_t slot_index = (uint32_t)_cel_Deque_Size(&interp->slots); + _cel_InterpreterSlot* slot = + _cel_Deque_PushBack(&interp->slots, interp->alloc); + if (CEL_UNLIKELY(slot == cel_nullptr)) { + cel_OutOfMemoryStatus(interp->status); + _cel_longjmp(interp->jmp); + } + slot->index = slot_index; + if (slot_index + 1 > interp->prog->max_slot_size) { + interp->prog->max_slot_size = slot_index + 1; + } + slot->name = name; + slot->pc = 0; + slot->guard_pc = 0; + slot->prev_slot = -1; + _cel_InterpreterValueStackBounds_Construct(&slot->value_stack_bounds); + return slot; +} + +static void _cel_Interpreter_PushSlot(_cel_Interpreter* cel_nonnull interp, + _cel_InterpreterSlot* cel_nonnull slot) { + uint32_t* index = _cel_Array_Push(&interp->slot_stack, interp->alloc); + if (CEL_UNLIKELY(slot == cel_nullptr)) { + cel_OutOfMemoryStatus(interp->status); + _cel_longjmp(interp->jmp); + } + *index = slot->index; +} + +CEL_ATTRIBUTE_NODISCARD +static _cel_InterpreterSlot* cel_nonnull +_cel_Interpreter_TopSlot(_cel_Interpreter* cel_nonnull interp) { + if (_cel_Array_Empty(&interp->slot_stack)) { + cel_InternalStatus(interp->status, + cel_StringView_From("cel: slot stack empty")); + _cel_longjmp(interp->jmp); + } + const uint32_t* index = _cel_Array_Back(&interp->slot_stack); + return _cel_Deque_MutableAt(&interp->slots, *index); +} + +static void _cel_Interpreter_PopSlot(_cel_Interpreter* cel_nonnull interp) { + if (_cel_Array_Empty(&interp->slot_stack)) { + cel_InternalStatus(interp->status, + cel_StringView_From("cel: slot stack empty")); + _cel_longjmp(interp->jmp); + } + _cel_Array_Pop(&interp->slot_stack); +} + +static void _cel_Interpreter_PushValueStackBounds( + _cel_Interpreter* cel_nonnull interp, + _cel_InterpreterValueStackBounds* cel_nonnull bounds) { + _cel_InterpreterValueStackBounds** bounds_ptr = + _cel_Array_Push(&interp->value_stack_bounds_stack, interp->alloc); + if (CEL_UNLIKELY(bounds_ptr == cel_nullptr)) { + cel_OutOfMemoryStatus(interp->status); + _cel_longjmp(interp->jmp); + } + *bounds_ptr = bounds; +} + +CEL_ATTRIBUTE_NODISCARD +static _cel_InterpreterValueStackBounds* cel_nonnull +_cel_Interpreter_TopValueStackBounds(_cel_Interpreter* cel_nonnull interp) { + if (_cel_Array_Empty(&interp->value_stack_bounds_stack)) { + cel_InternalStatus( + interp->status, + cel_StringView_From("cel: value stack bounds stack empty")); + _cel_longjmp(interp->jmp); + } + return *_cel_Array_Back(&interp->value_stack_bounds_stack); +} + +static void _cel_Interpreter_PopValueStackBounds( + _cel_Interpreter* cel_nonnull interp) { + if (_cel_Array_Empty(&interp->value_stack_bounds_stack)) { + cel_InternalStatus( + interp->status, + cel_StringView_From("cel: value stack bounds stack empty")); + _cel_longjmp(interp->jmp); + } + _cel_Array_Pop(&interp->value_stack_bounds_stack); +} + +CEL_ATTRIBUTE_NODISCARD +static uint32_t _cel_Interpreter_DeleteSlots( + _cel_Interpreter* cel_nonnull interp) { + uint32_t size = (uint32_t)_cel_Deque_Size(&interp->slots); + _cel_Deque_Clear(&interp->slots, interp->alloc); + return size; +} + +static void _cel_Interpreter_IncrementValueStackSizeN( + _cel_Interpreter* cel_nonnull interp, uint32_t n) { + _cel_InterpreterValueStackBounds_Add( + _cel_Interpreter_TopValueStackBounds(interp), n); +} + +static void _cel_Interpreter_IncrementValueStackSize( + _cel_Interpreter* cel_nonnull interp) { + _cel_InterpreterValueStackBounds_Increment( + _cel_Interpreter_TopValueStackBounds(interp)); +} + +static void _cel_Interpreter_DecrementValueStackSizeN( + _cel_Interpreter* cel_nonnull interp, uint32_t n) { + _cel_InterpreterValueStackBounds_Subtract( + _cel_Interpreter_TopValueStackBounds(interp), n); +} + +static void _cel_Interpreter_DecrementValueStackSize( + _cel_Interpreter* cel_nonnull interp) { + _cel_InterpreterValueStackBounds_Decrement( + _cel_Interpreter_TopValueStackBounds(interp)); +} + +static void _cel_Interpreter_PreVisitExpr(cel_AstVisitor* cel_nonnull visitor, + const cel_Expr* cel_nonnull expr) { + cel_ExprKind kind = cel_Expr_Kind(expr); + switch (kind) { + case cel_ExprKind_kUnspecified: + CEL_ATTRIBUTE_FALLTHROUGH; + case cel_ExprKind_kIdent: + CEL_ATTRIBUTE_FALLTHROUGH; + case cel_ExprKind_kConst: + CEL_ATTRIBUTE_FALLTHROUGH; + case cel_ExprKind_kSelect: + CEL_ATTRIBUTE_FALLTHROUGH; + case cel_ExprKind_kBinary: + CEL_ATTRIBUTE_FALLTHROUGH; + case cel_ExprKind_kUnary: + CEL_ATTRIBUTE_FALLTHROUGH; + case cel_ExprKind_kTernary: + CEL_ATTRIBUTE_FALLTHROUGH; + case cel_ExprKind_kListElement: + CEL_ATTRIBUTE_FALLTHROUGH; + case cel_ExprKind_kList: + CEL_ATTRIBUTE_FALLTHROUGH; + case cel_ExprKind_kMapEntry: + CEL_ATTRIBUTE_FALLTHROUGH; + case cel_ExprKind_kMap: + CEL_ATTRIBUTE_FALLTHROUGH; + case cel_ExprKind_kCallArg: + CEL_ATTRIBUTE_FALLTHROUGH; + case cel_ExprKind_kCall: + CEL_ATTRIBUTE_FALLTHROUGH; + case cel_ExprKind_kComprehension: + break; + default: + // For now, reject other AST node kinds. + cel_InvalidArgumentStatusF( + _cel_Interpreter_FromAstVisitor(visitor)->status, + "cel: unsupported AST node kind: %d", kind); + _cel_longjmp(_cel_Interpreter_FromAstVisitor(visitor)->jmp); + } +} + +static void _cel_Interpreter_VisitUnspecifiedExpr( + cel_AstVisitor* cel_nonnull visitor, + const cel_UnspecifiedExpr* cel_nonnull expr) { + _cel_Interpreter* interp = _cel_Interpreter_FromAstVisitor(visitor); + // Reject unspecified AST nodes, they are only created when errors occur. + cel_InvalidArgumentStatus( + interp->status, + cel_StringView_From("cel: unexpected unspecified AST node kind")); + _cel_longjmp(interp->jmp); +} + +CEL_ATTRIBUTE_NODISCARD +static cel_StringView _cel_Interpreter_InternedString( + const _cel_Interpreter* cel_nonnull interp, uint32_t index) { + return _cel_String_ToStringView( + _cel_Array_At(&interp->prog->strings_table, index)); +} + +CEL_ATTRIBUTE_NODISCARD +static uint32_t _cel_Interpreter_InternString( + _cel_Interpreter* cel_nonnull interp, cel_StringView string) { + _cel_FlatHashMapNode(interp->string_pool_indices) node = + _cel_FlatHashMap_Find(&interp->string_pool_indices, &string); + if (node == cel_nullptr) { + uint32_t index = (uint32_t)_cel_Array_Size(&interp->prog->strings_table); + _cel_String* string_ptr = + _cel_Array_Push(&interp->prog->strings_table, interp->alloc); + if (CEL_UNLIKELY(string_ptr == cel_nullptr)) { + cel_OutOfMemoryStatus(interp->status); + _cel_longjmp(interp->jmp); + } + _cel_String_Construct(string_ptr); + if (!_cel_String_Assign(string_ptr, interp->alloc, string)) { + cel_OutOfMemoryStatus(interp->status); + _cel_longjmp(interp->jmp); + } + if (!_cel_String_Stabilize(string_ptr, interp->alloc)) { + cel_OutOfMemoryStatus(interp->status); + _cel_longjmp(interp->jmp); + } + string = _cel_String_ToStringView(string_ptr); + _cel_FlatHashMapMutableNode(interp->string_pool_indices) mutable_node; + if (!_cel_FlatHashMap_Insert(&interp->string_pool_indices, interp->alloc, + &string, &mutable_node)) { + cel_OutOfMemoryStatus(interp->status); + _cel_longjmp(interp->jmp); + } + mutable_node->val = index; + return index; + } + return node->val; +} + +static void _cel_Interpreter_ValidateIdentExpr( + _cel_Interpreter* cel_nonnull interp, + const cel_IdentExpr* cel_nonnull expr) { + cel_StringView name = cel_IdentExpr_Name(expr); + if (CEL_UNLIKELY(cel_StringView_Empty(name))) { + cel_InvalidArgumentStatusF( + interp->status, "cel: ident expr AST node is missing name: id=%" PRIi64, + cel_Expr_Id(cel_Expr_UpCast(expr))); + _cel_longjmp(interp->jmp); + } +} + +static void _cel_Interpreter_ValidateSelectExpr( + _cel_Interpreter* cel_nonnull interp, + const cel_SelectExpr* cel_nonnull expr) { + const cel_Expr* operand = cel_SelectExpr_Operand(expr); + if (CEL_UNLIKELY(operand == cel_nullptr)) { + cel_InvalidArgumentStatusF( + interp->status, + "cel: select expr AST node is missing operand: id=%" PRIi64, + cel_Expr_Id(cel_Expr_UpCast(expr))); + _cel_longjmp(interp->jmp); + } + cel_StringView field = cel_SelectExpr_Field(expr); + if (CEL_UNLIKELY(cel_StringView_Empty(field))) { + cel_InvalidArgumentStatusF( + interp->status, + "cel: select expr AST node is missing field: id=%" PRIi64, + cel_Expr_Id(cel_Expr_UpCast(expr))); + _cel_longjmp(interp->jmp); + } +} + +static void _cel_Interpreter_PlanIdent(_cel_Interpreter* cel_nonnull interp, + cel_StringView name, + bool jump_if_found) { + // TODO: fix namespaced identifier lookup + + if (_cel_Container_Empty(&interp->rt->container)) { + int32_t index = _cel_Interpreter_InternString(interp, name); + if (CEL_UNLIKELY(index == -1)) { + cel_OutOfMemoryStatus(interp->status); + _cel_longjmp(interp->jmp); + } + _cel_Instr* instr = _cel_Interpreter_AppendInstr(interp); + if (CEL_UNLIKELY(instr == cel_nullptr)) { + cel_OutOfMemoryStatus(interp->status); + _cel_longjmp(interp->jmp); + } + instr->data.ident.name.indirect = (uint32_t)index; + instr->kind = + jump_if_found ? _cel_InstrKind_kIdentJump : _cel_InstrKind_kIdent; + } else { + _cel_FlatHashMapNode(interp->candidate_names_indices) node = + _cel_FlatHashMap_Find(&interp->candidate_names_indices, &name); + uint32_t candidate_names_index; + if (node == cel_nullptr) { + candidate_names_index = + (uint32_t)_cel_Array_Size(&interp->prog->candidate_names_table); + _cel_CandidateNames* candidate_names = + _cel_Array_Push(&interp->prog->candidate_names_table, interp->alloc); + if (CEL_UNLIKELY(candidate_names == cel_nullptr)) { + cel_OutOfMemoryStatus(interp->status); + _cel_longjmp(interp->jmp); + } + memset(candidate_names, 0, sizeof(*candidate_names)); + candidate_names->size = _cel_Container_Count(&interp->rt->container) + 1; + candidate_names->data = (uint32_t*)cel_Allocator_Malloc( + interp->alloc, sizeof(uint32_t) * candidate_names->size, cel_nullptr); + if (CEL_UNLIKELY(candidate_names->data == cel_nullptr)) { + _cel_Array_Pop(&interp->prog->candidate_names_table); + cel_OutOfMemoryStatus(interp->status); + _cel_longjmp(interp->jmp); + } + _cel_String candidate_name; + _cel_String_Construct(&candidate_name); + _cel_ContainerIterator cont_iter = + _cel_Container_Iterate(&interp->rt->container); + for (size_t i = 0; i < candidate_names->size - 1; ++i) { + _cel_String_Clear(&candidate_name); + CEL_ASSERT(_cel_ContainerIterator_HasNext(&cont_iter)); + cel_StringView prefix = _cel_ContainerIterator_Next(&cont_iter); + _cel_String_Reserve( + &candidate_name, interp->alloc, + cel_StringView_Size(prefix) + 1 + cel_StringView_Size(name)); + if (!_cel_String_Append(&candidate_name, interp->alloc, prefix) || + !_cel_String_PushBack(&candidate_name, interp->alloc, '.') || + !_cel_String_Append(&candidate_name, interp->alloc, name)) { + _cel_String_Destruct(&candidate_name, interp->alloc); + cel_Allocator_FreeSized(interp->alloc, candidate_names->data, + candidate_names->size * sizeof(uint32_t)); + _cel_Array_Pop(&interp->prog->candidate_names_table); + cel_OutOfMemoryStatus(interp->status); + _cel_longjmp(interp->jmp); + } + uint32_t string_index = _cel_Interpreter_InternString( + interp, _cel_String_ToStringView(&candidate_name)); + candidate_names->data[i] = string_index; + } + uint32_t string_index = _cel_Interpreter_InternString(interp, name); + candidate_names->data[candidate_names->size - 1] = string_index; + name = _cel_Interpreter_InternedString(interp, string_index); + _cel_FlatHashMapMutableNode(interp->candidate_names_indices) mutable_node; + if (!_cel_FlatHashMap_Insert(&interp->candidate_names_indices, + interp->alloc, &name, &mutable_node)) { + cel_OutOfMemoryStatus(interp->status); + _cel_longjmp(interp->jmp); + } + mutable_node->val = candidate_names_index; + } else { + candidate_names_index = node->val; + } + _cel_Instr* instr = _cel_Interpreter_AppendInstr(interp); + instr->data.cont_ident.candidate_names = candidate_names_index; + instr->kind = jump_if_found ? _cel_InstrKind_kContIdentJump + : _cel_InstrKind_kContIdent; + } +} + +static void _cel_Interpreter_VisitIdentExpr(cel_AstVisitor* cel_nonnull visitor, + const cel_IdentExpr* cel_nonnull + expr) { + _cel_Interpreter* interp = _cel_Interpreter_FromAstVisitor(visitor); + _cel_Interpreter_ValidateIdentExpr(interp, expr); + cel_StringView name = cel_IdentExpr_Name(expr); + _cel_FlatHashMapNode(interp->slot_map) slot_node; + slot_node = _cel_FlatHashMap_Find(&interp->slot_map, &name); + if (slot_node != cel_nullptr) { + // Identifier belongs to a comprehension. + _cel_Instr* lazy_call = _cel_Interpreter_AppendInstr(interp); + lazy_call->kind = _cel_InstrKind_kLazyCall; + lazy_call->data.lazy_call.slot = slot_node->val; + const _cel_InterpreterSlot* slot = + _cel_Deque_At(&interp->slots, slot_node->val); + lazy_call->data.lazy_call.jump = + (int32_t)slot->pc - (int32_t)_cel_Interpreter_PC(interp, lazy_call); + // Account for the max value stack size of the subexpression in the current + // expression. + _cel_Interpreter_IncrementValueStackSizeN(interp, + slot->value_stack_bounds.max); + _cel_Interpreter_DecrementValueStackSizeN(interp, + slot->value_stack_bounds.max); + } else { + _cel_Interpreter_PlanIdent(interp, name, + /*jump_if_found=*/false); + } + _cel_Interpreter_IncrementValueStackSize(interp); +} + +static void _cel_Interpreter_VisitConstExpr(cel_AstVisitor* cel_nonnull visitor, + const cel_ConstExpr* cel_nonnull + expr) { + _cel_Interpreter* interp = _cel_Interpreter_FromAstVisitor(visitor); + const cel_Constant* value = cel_ConstExpr_Value(expr); + const cel_ConstantKind kind = cel_Constant_Kind(value); + switch (kind) { + case cel_ConstantKind_kNull: { + _cel_Instr* instr = _cel_Interpreter_AppendInstr(interp); + instr->kind = _cel_InstrKind_kNullConst; + } break; + case cel_ConstantKind_kBool: { + _cel_Instr* instr = _cel_Interpreter_AppendInstr(interp); + instr->kind = cel_Constant_GetBool(value) ? _cel_InstrKind_kTrueConst + : _cel_InstrKind_kFalseConst; + } break; + case cel_ConstantKind_kInt: { + _cel_Instr* instr = _cel_Interpreter_AppendInstr(interp); + instr->data.int_const.value = cel_Constant_GetInt(value); + instr->kind = _cel_InstrKind_kIntConst; + } break; + case cel_ConstantKind_kUint: { + _cel_Instr* instr = _cel_Interpreter_AppendInstr(interp); + instr->data.uint_const.value = cel_Constant_GetUint(value); + instr->kind = _cel_InstrKind_kUintConst; + } break; + case cel_ConstantKind_kDouble: { + _cel_Instr* instr = _cel_Interpreter_AppendInstr(interp); + instr->data.double_const.value = cel_Constant_GetDouble(value); + instr->kind = _cel_InstrKind_kDoubleConst; + } break; + case cel_ConstantKind_kBytes: { + uint32_t index = + _cel_Interpreter_InternString(interp, cel_Constant_GetBytes(value)); + _cel_Instr* instr = _cel_Interpreter_AppendInstr(interp); + instr->data.bytes_const.value.indirect = index; + instr->kind = _cel_InstrKind_kBytesConst; + } break; + case cel_ConstantKind_kString: { + uint32_t index = + _cel_Interpreter_InternString(interp, cel_Constant_GetString(value)); + _cel_Instr* instr = _cel_Interpreter_AppendInstr(interp); + instr->data.string_const.value.indirect = index; + instr->kind = _cel_InstrKind_kStringConst; + } break; + case cel_ConstantKind_kDuration: { + _cel_Instr* instr = _cel_Interpreter_AppendInstr(interp); + instr->data.duration_const.value = cel_Constant_GetDuration(value); + instr->kind = _cel_InstrKind_kDurationConst; + } break; + case cel_ConstantKind_kTimestamp: { + _cel_Instr* instr = _cel_Interpreter_AppendInstr(interp); + instr->data.timestamp_const.value = cel_Constant_GetTimestamp(value); + instr->kind = _cel_InstrKind_kTimestampConst; + } break; + default: + cel_InvalidArgumentStatusF( + _cel_Interpreter_FromAstVisitor(visitor)->status, + "cel: const expr AST node has unspecified value: id=%" PRIi64, + cel_Expr_Id(cel_Expr_UpCast(expr))); + _cel_longjmp(interp->jmp); + } + _cel_Interpreter_IncrementValueStackSize(interp); +} + +typedef struct { + const cel_IdentExpr* cel_nullable ident; + size_t select_depth; +} _cel_InterpreterSelectIdentExpr; + +CEL_ATTRIBUTE_NODISCARD +static _cel_InterpreterSelectIdentExpr _cel_Intepreter_GetSelectIdentExpr( + const cel_SelectExpr* cel_nonnull expr) { + _cel_InterpreterSelectIdentExpr result; + result.ident = cel_nullptr; + result.select_depth = 0; + while (true) { + const cel_Expr* operand = cel_SelectExpr_Operand(expr); + if (operand == cel_nullptr) { + result.select_depth = 0; + return result; + } + switch (cel_Expr_Kind(operand)) { + case cel_ExprKind_kIdent: + result.ident = cel_IdentExpr_DownCast(operand); + return result; + case cel_ExprKind_kSelect: + expr = cel_SelectExpr_DownCast(operand); + ++result.select_depth; + break; + default: + result.select_depth = 0; + return result; + } + } +} + +static void _cel_Intepreter_CollectNspaceVarSelects( + _cel_Interpreter* cel_nonnull interp, + const cel_SelectExpr* cel_nonnull expr) { + { + _cel_InterpreterNspaceVarSelect* select = + _cel_Array_Push(&interp->nspace_var.selects, interp->alloc); + if (CEL_UNLIKELY(select == cel_nullptr)) { + cel_OutOfMemoryStatus(interp->status); + _cel_longjmp(interp->jmp); + } + select->expr = expr; + select->ident_pc = 0; + select->select_pc = 0; + } + while (true) { + const cel_Expr* operand = cel_SelectExpr_Operand(expr); + CEL_ASSERT_NOT_NULL(operand); + switch (cel_Expr_Kind(operand)) { + case cel_ExprKind_kIdent: + return; + case cel_ExprKind_kSelect: { + _cel_InterpreterNspaceVarSelect* select = + _cel_Array_Push(&interp->nspace_var.selects, interp->alloc); + if (CEL_UNLIKELY(select == cel_nullptr)) { + cel_OutOfMemoryStatus(interp->status); + _cel_longjmp(interp->jmp); + } + expr = select->expr = cel_SelectExpr_DownCast(operand); + select->ident_pc = 0; + select->select_pc = 0; + break; + } + default: + CEL_UNREACHABLE(); + } + } +} + +CEL_ATTRIBUTE_NODISCARD +static bool _cel_Interpreter_GetNspaceVar(_cel_Interpreter* cel_nonnull interp, + const cel_SelectExpr* cel_nonnull + expr) { + // Check if this is the first select expr in a chain of `foo.bar.baz`, such + // that this is `baz`. + const cel_Expr* parent = cel_Expr_Parent(cel_Expr_UpCast(expr)); + if (parent == cel_nullptr || cel_Expr_Kind(parent) != cel_ExprKind_kSelect) { + _cel_InterpreterSelectIdentExpr select_ident_expr = + _cel_Intepreter_GetSelectIdentExpr(expr); + if (select_ident_expr.ident != cel_nullptr) { + // First select in a chain of select(s) and a single ident. + const cel_Ref* ident_operand_ref = + cel_Expr_Ref(cel_Expr_UpCast(select_ident_expr.ident)); + if (ident_operand_ref == cel_nullptr) { + _cel_Array_Clear(&interp->nspace_var.selects); + interp->nspace_var.ident.expr = select_ident_expr.ident; + interp->nspace_var.ident.start_pc = 0; + _cel_Array_Reserve(&interp->nspace_var.selects, interp->alloc, + select_ident_expr.select_depth + 1); + _cel_Intepreter_CollectNspaceVarSelects(interp, expr); + return true; + } + } + } + return false; +} + +static void _cel_Interpreter_PlanSelectExpr( + _cel_Interpreter* cel_nonnull interp, + const cel_SelectExpr* cel_nonnull expr) { + cel_StringView field_name = cel_SelectExpr_Field(expr); + const cel_Expr* operand = cel_SelectExpr_Operand(expr); + const cel_Type* operand_type = + operand != cel_nullptr ? cel_Expr_Type(operand) : cel_DynType; + const upb_FieldDef* field = cel_nullptr; + if (cel_Type_IsStruct(operand_type)) { + cel_StringView operand_type_name = + cel_StructType_Name(cel_StructType_DownCast(operand_type)); + const upb_MessageDef* operand_message = + upb_DefPool_FindMessageByNameWithSize( + interp->def_pool, cel_StringView_Data(operand_type_name), + cel_StringView_Size(operand_type_name)); + if (operand_message != cel_nullptr) { + field = upb_MessageDef_FindFieldByNameWithSize( + operand_message, cel_StringView_Data(field_name), + cel_StringView_Size(field_name)); + } + if (field != cel_nullptr) { + _cel_Instr* instr = _cel_Interpreter_AppendInstr(interp); + if (cel_SelectExpr_TestOnly(expr)) { + instr->kind = _cel_InstrKind_kMessageHas; + instr->data.message_has.field = field; + } else { + instr->kind = _cel_InstrKind_kMessageSelect; + instr->data.message_select.field = field; + } + return; + } + } + uint32_t string_index = _cel_Interpreter_InternString(interp, field_name); + _cel_Instr* instr = _cel_Interpreter_AppendInstr(interp); + if (cel_SelectExpr_TestOnly(expr)) { + instr->kind = _cel_InstrKind_kHas; + instr->data.has.field.indirect = (uint32_t)string_index; + } else { + instr->kind = _cel_InstrKind_kSelect; + instr->data.select.field.indirect = (uint32_t)string_index; + } +} + +static void _cel_Interpreter_SetIdentJump(_cel_Instr* ident_jump, + bool missing_error, + int32_t found_jump, + int32_t missing_jump) { + CEL_ASSERT_NE(found_jump, 0); + CEL_ASSERT_NE(missing_jump, 0); + + switch (ident_jump->kind) { + case _cel_InstrKind_kIdentJump: + ident_jump->data.ident_jump.missing_error = missing_error ? 1 : 0; + ident_jump->data.ident_jump.found_jump = found_jump; + ident_jump->data.ident_jump.missing_jump = missing_jump; + break; + case _cel_InstrKind_kContIdentJump: + ident_jump->data.cont_ident_jump.missing_error = missing_error ? 1 : 0; + ident_jump->data.cont_ident_jump.found_jump = found_jump; + ident_jump->data.cont_ident_jump.missing_jump = missing_jump; + break; + default: + CEL_UNREACHABLE(); + } +} + +static void _cel_Interpreter_BuildIdentName(_cel_Interpreter* interp, + size_t selects_num, + size_t selects_count) { + CEL_ASSERT_GE(selects_count, selects_num); + + _cel_InterpreterNspaceVarSelect* const selects = + _cel_Array_MutableData(&interp->nspace_var.selects); + _cel_String_Clear(&interp->nspace_var.name); + if (!_cel_String_Append(&interp->nspace_var.name, interp->alloc, + cel_IdentExpr_Name(interp->nspace_var.ident.expr))) { + cel_OutOfMemoryStatus(interp->status); + _cel_longjmp(interp->jmp); + } + for (size_t i = 0; i < selects_num; ++i) { + if (!_cel_String_PushBack(&interp->nspace_var.name, interp->alloc, '.') || + !_cel_String_Append( + &interp->nspace_var.name, interp->alloc, + cel_SelectExpr_Field(selects[selects_count - i - 1].expr))) { + cel_OutOfMemoryStatus(interp->status); + _cel_longjmp(interp->jmp); + } + } +} + +static void _cel_Interpreter_PreVisitSelectExpr( + cel_AstVisitor* cel_nonnull visitor, + const cel_SelectExpr* cel_nonnull expr) { + _cel_Interpreter* interp = _cel_Interpreter_FromAstVisitor(visitor); + if (_cel_Interpreter_GetNspaceVar(interp, expr)) { + // If we are dealing with namespaced variables, only under parse-only + // expressions, detect that here. If that is the case, we skip traversing + // further down the subtree and handle it all here. + _cel_InterpreterNspaceVarSelect* const selects = + _cel_Array_MutableData(&interp->nspace_var.selects); + const size_t selects_count = _cel_Array_Size(&interp->nspace_var.selects); + CEL_ASSERT_GT(selects_count, 0); + { + _cel_Interpreter_ValidateIdentExpr(interp, interp->nspace_var.ident.expr); + for (size_t i = selects_count; i > 0; --i) { + _cel_Interpreter_ValidateSelectExpr(interp, selects[i - 1].expr); + } + } + + // Idents. + for (size_t i = selects_count; i > 0; --i) { + _cel_Interpreter_BuildIdentName(interp, i, selects_count); + selects[i - 1].ident_pc = _cel_Interpreter_NextPC(interp); + _cel_Interpreter_PlanIdent( + interp, _cel_String_ToStringView(&interp->nspace_var.name), + /*jump_if_found=*/true); + } + _cel_Interpreter_BuildIdentName(interp, 0, selects_count); + interp->nspace_var.ident.start_pc = _cel_Interpreter_NextPC(interp); + _cel_Interpreter_PlanIdent( + interp, _cel_String_ToStringView(&interp->nspace_var.name), + /*jump_if_found=*/true); + + // Selects. + const uint32_t first_select_pc = _cel_Interpreter_NextPC(interp); + for (size_t i = selects_count; i > 0; --i) { + selects[i - 1].select_pc = _cel_Interpreter_NextPC(interp); + _cel_Interpreter_PlanSelectExpr(interp, selects[i - 1].expr); + } + + // Jumps. + const uint32_t last_pc = _cel_Interpreter_NextPC(interp); + uint32_t select_pc; + uint32_t ident_pc = selects[selects_count - 1].ident_pc; + _cel_Instr* ident_jump = _cel_Interpreter_InstrAt(interp, ident_pc); + _cel_Interpreter_SetIdentJump(ident_jump, /*missing_error=*/false, + last_pc - ident_pc, 1); + for (size_t i = selects_count - 1; i > 0; --i) { + ident_pc = selects[i - 1].ident_pc; + ident_jump = _cel_Interpreter_InstrAt(interp, ident_pc); + select_pc = selects[selects_count - 1 - i].select_pc; + _cel_Interpreter_SetIdentJump(ident_jump, /*missing_error=*/false, + select_pc - ident_pc, 1); + } + ident_pc = interp->nspace_var.ident.start_pc; + ident_jump = _cel_Interpreter_InstrAt(interp, ident_pc); + _cel_Interpreter_SetIdentJump(ident_jump, /*missing_error=*/true, + first_select_pc - ident_pc, + last_pc - ident_pc); + + _cel_Interpreter_IncrementValueStackSize(interp); + cel_AstTraverser_StepOut(interp->traverser); + } else { + _cel_Interpreter_ValidateSelectExpr(interp, expr); + } +} + +static void _cel_Interpreter_PostVisitSelectExpr( + cel_AstVisitor* cel_nonnull visitor, + const cel_SelectExpr* cel_nonnull expr) { + _cel_Interpreter* interp = _cel_Interpreter_FromAstVisitor(visitor); + _cel_Interpreter_PlanSelectExpr(interp, expr); +} + +static void _cel_Interpreter_PreVisitBinaryExpr( + cel_AstVisitor* cel_nonnull visitor, + const cel_BinaryExpr* cel_nonnull expr) { + _cel_Interpreter* interp = _cel_Interpreter_FromAstVisitor(visitor); + switch (cel_BinaryExpr_Op(expr)) { + case cel_BinaryOp_kAdd: + CEL_ATTRIBUTE_FALLTHROUGH; + case cel_BinaryOp_kSubtract: + CEL_ATTRIBUTE_FALLTHROUGH; + case cel_BinaryOp_kMultiply: + CEL_ATTRIBUTE_FALLTHROUGH; + case cel_BinaryOp_kDivide: + CEL_ATTRIBUTE_FALLTHROUGH; + case cel_BinaryOp_kModulo: + break; + case cel_BinaryOp_kLogicalAnd: { + _cel_InterpreterCond* cond = _cel_Interpreter_PushCond(interp); + cond->binary.node = expr; + // pc is filled in _cel_Interpreter_PostVisitBinaryExprLeft. + cond->binary.pc = 0; + cond->binary.cond = false; + } break; + case cel_BinaryOp_kLogicalOr: { + _cel_InterpreterCond* cond = _cel_Interpreter_PushCond(interp); + cond->binary.node = expr; + // pc is filled in _cel_Interpreter_PostVisitBinaryExprLeft. + cond->binary.pc = 0; + cond->binary.cond = true; + } break; + case cel_BinaryOp_kEquals: + CEL_ATTRIBUTE_FALLTHROUGH; + case cel_BinaryOp_kNotEquals: + CEL_ATTRIBUTE_FALLTHROUGH; + case cel_BinaryOp_kLess: + CEL_ATTRIBUTE_FALLTHROUGH; + case cel_BinaryOp_kLessEquals: + CEL_ATTRIBUTE_FALLTHROUGH; + case cel_BinaryOp_kGreater: + CEL_ATTRIBUTE_FALLTHROUGH; + case cel_BinaryOp_kGreaterEquals: + CEL_ATTRIBUTE_FALLTHROUGH; + case cel_BinaryOp_kIndex: + CEL_ATTRIBUTE_FALLTHROUGH; + case cel_BinaryOp_kIn: + break; + default: + // For now, reject other AST node kinds. + cel_InvalidArgumentStatusF(interp->status, + "cel: unsupported binary expr operator: %d", + cel_BinaryExpr_Op(expr)); + _cel_longjmp(interp->jmp); + } + if (cel_BinaryExpr_Left(expr) == cel_nullptr) { + cel_InvalidArgumentStatusF( + interp->status, "cel: binary expr missing left operand: %" PRId64, + cel_Expr_Id(cel_Expr_UpCast(expr))); + _cel_longjmp(interp->jmp); + } + if (cel_BinaryExpr_Right(expr) == cel_nullptr) { + cel_InvalidArgumentStatusF( + interp->status, "cel: binary expr missing right operand: %" PRId64, + cel_Expr_Id(cel_Expr_UpCast(expr))); + _cel_longjmp(interp->jmp); + } +} + +static void _cel_Interpreter_PostVisitBinaryExpr( + cel_AstVisitor* cel_nonnull visitor, + const cel_BinaryExpr* cel_nonnull expr) { + _cel_Interpreter* interp = _cel_Interpreter_FromAstVisitor(visitor); + _cel_Instr* instr = _cel_Interpreter_AppendInstr(interp); + switch (cel_BinaryExpr_Op(expr)) { + case cel_BinaryOp_kAdd: + instr->kind = _cel_InstrKind_kAdd; + break; + case cel_BinaryOp_kSubtract: + instr->kind = _cel_InstrKind_kSubtract; + break; + case cel_BinaryOp_kMultiply: + instr->kind = _cel_InstrKind_kMultiply; + break; + case cel_BinaryOp_kDivide: + instr->kind = _cel_InstrKind_kDivide; + break; + case cel_BinaryOp_kModulo: + instr->kind = _cel_InstrKind_kModulo; + break; + case cel_BinaryOp_kLogicalAnd: { + instr->kind = _cel_InstrKind_kLogicalAnd; + _cel_InterpreterCond* cond = _cel_Interpreter_TopCond(interp); + CEL_ASSERT_EQ(cel_Expr_Kind(cond->node), cel_ExprKind_kBinary); + _cel_InterpreterBinaryCond* binary_cond = &cond->binary; + CEL_ASSERT_GT(binary_cond->pc, 0); + _cel_Instr* jump_instr = + _cel_Interpreter_InstrAt(interp, binary_cond->pc); + CEL_ASSERT_EQ(jump_instr->kind, _cel_InstrKind_kCondJump); + jump_instr->data.cond_jump.jump = (int32_t)((instr - jump_instr) + 1); + _cel_Interpreter_PopCond(interp); + } break; + case cel_BinaryOp_kLogicalOr: { + instr->kind = _cel_InstrKind_kLogicalOr; + _cel_InterpreterCond* cond = _cel_Interpreter_TopCond(interp); + CEL_ASSERT_EQ(cel_Expr_Kind(cond->node), cel_ExprKind_kBinary); + _cel_InterpreterBinaryCond* binary_cond = &cond->binary; + CEL_ASSERT_GT(binary_cond->pc, 0); + _cel_Instr* jump_instr = + _cel_Interpreter_InstrAt(interp, binary_cond->pc); + CEL_ASSERT_EQ(jump_instr->kind, _cel_InstrKind_kCondJump); + jump_instr->data.cond_jump.jump = (int32_t)((instr - jump_instr) + 1); + _cel_Interpreter_PopCond(interp); + } break; + case cel_BinaryOp_kEquals: + instr->kind = _cel_InstrKind_kEquals; + break; + case cel_BinaryOp_kNotEquals: + instr->kind = _cel_InstrKind_kNotEquals; + break; + case cel_BinaryOp_kLess: + instr->kind = _cel_InstrKind_kLess; + break; + case cel_BinaryOp_kLessEquals: + instr->kind = _cel_InstrKind_kLessEquals; + break; + case cel_BinaryOp_kGreater: + instr->kind = _cel_InstrKind_kGreater; + break; + case cel_BinaryOp_kGreaterEquals: + instr->kind = _cel_InstrKind_kGreaterEquals; + break; + case cel_BinaryOp_kIndex: + instr->kind = _cel_InstrKind_kIndex; + break; + case cel_BinaryOp_kIn: + instr->kind = _cel_InstrKind_kIn; + break; + default: + CEL_UNREACHABLE(); + } + _cel_Interpreter_DecrementValueStackSize(interp); +} + +static void _cel_Interpreter_PostVisitBinaryExprLeft( + cel_AstVisitor* cel_nonnull visitor, const cel_Expr* cel_nonnull expr) { + _cel_Interpreter* interp = _cel_Interpreter_FromAstVisitor(visitor); + _cel_InterpreterCond* cond = _cel_Interpreter_PeekCond(interp); + if (cond == cel_nullptr || + cel_Expr_Kind(cond->node) != cel_ExprKind_kBinary) { + return; + } + _cel_InterpreterBinaryCond* binary_cond = &cond->binary; + if (cel_Expr_UpCast(binary_cond->node) != cel_Expr_Parent(expr)) { + return; + } + _cel_Instr* cond_jump = _cel_Interpreter_AppendInstr(interp); + cond_jump->kind = _cel_InstrKind_kCondJump; + // jump is filled out in _cel_Interpreter_PostVisitBinaryExpr. + cond_jump->data.cond_jump.jump = 0; + cond_jump->data.cond_jump.cond = binary_cond->cond; + switch (cel_BinaryExpr_Op(cel_BinaryExpr_DownCast(cel_Expr_Parent(expr)))) { + case cel_BinaryOp_kLogicalAnd: + cond_jump->data.cond_jump.cond = false; + break; + case cel_BinaryOp_kLogicalOr: + cond_jump->data.cond_jump.cond = true; + break; + default: + CEL_UNREACHABLE(); + } + binary_cond->pc = _cel_Interpreter_PC(interp, cond_jump); +} + +static void _cel_Interpreter_PostVisitUnaryExpr( + cel_AstVisitor* cel_nonnull visitor, + const cel_UnaryExpr* cel_nonnull expr) { + _cel_Interpreter* interp = _cel_Interpreter_FromAstVisitor(visitor); + _cel_Instr* instr = _cel_Interpreter_AppendInstr(interp); + switch (cel_UnaryExpr_Op(expr)) { + case cel_UnaryOp_kLogicalNot: + instr->kind = _cel_InstrKind_kLogicalNot; + break; + case cel_UnaryOp_kNegate: + instr->kind = _cel_InstrKind_kNegate; + break; + default: + CEL_UNREACHABLE(); + } +} + +static void _cel_Interpreter_PreVisitUnaryExpr( + cel_AstVisitor* cel_nonnull visitor, + const cel_UnaryExpr* cel_nonnull expr) { + _cel_Interpreter* interp = _cel_Interpreter_FromAstVisitor(visitor); + switch (cel_UnaryExpr_Op(expr)) { + case cel_UnaryOp_kLogicalNot: + CEL_ATTRIBUTE_FALLTHROUGH; + case cel_UnaryOp_kNegate: + break; + default: + // For now, reject other AST node kinds. + cel_InvalidArgumentStatusF(interp->status, + "cel: unsupported unary expr operator: %d", + cel_UnaryExpr_Op(expr)); + _cel_longjmp(interp->jmp); + } + if (cel_UnaryExpr_Arg(expr) == cel_nullptr) { + cel_InvalidArgumentStatusF(interp->status, + "cel: unary expr missing operand: %" PRId64, + cel_Expr_Id(cel_Expr_UpCast(expr))); + _cel_longjmp(interp->jmp); + } +} + +static void _cel_Interpreter_PreVisitTernaryExpr( + cel_AstVisitor* cel_nonnull visitor, + const cel_TernaryExpr* cel_nonnull expr) { + _cel_Interpreter* interp = _cel_Interpreter_FromAstVisitor(visitor); + switch (cel_TernaryExpr_Op(expr)) { + case cel_TernaryOp_kConditional: + break; + default: + // For now, reject other AST node kinds. + cel_InvalidArgumentStatusF(interp->status, + "cel: unsupported ternary expr operator: %d", + cel_TernaryExpr_Op(expr)); + _cel_longjmp(interp->jmp); + } + if (cel_TernaryExpr_Condition(expr) == cel_nullptr) { + cel_InvalidArgumentStatusF(interp->status, + "cel: ternary expr missing condition: %" PRId64, + cel_Expr_Id(cel_Expr_UpCast(expr))); + _cel_longjmp(interp->jmp); + } + if (cel_TernaryExpr_IfTrue(expr) == cel_nullptr) { + cel_InvalidArgumentStatusF(interp->status, + "cel: ternary expr missing if-true: %" PRId64, + cel_Expr_Id(cel_Expr_UpCast(expr))); + _cel_longjmp(interp->jmp); + } + if (cel_TernaryExpr_IfFalse(expr) == cel_nullptr) { + cel_InvalidArgumentStatusF(interp->status, + "cel: ternary expr missing if-false: %" PRId64, + cel_Expr_Id(cel_Expr_UpCast(expr))); + _cel_longjmp(interp->jmp); + } + _cel_InterpreterCond* cond = _cel_Interpreter_PushCond(interp); + cond->ternary.node = expr; + // Set in _cel_Interpreter_PostVisitTernaryExprCondition. + cond->ternary.trilean_jump_pc = 0; + // Set in _cel_Interpreter_PostVisitTernaryExprIfTrue. + cond->ternary.jump_pc = 0; +} + +static void _cel_Interpreter_PostVisitTernaryExpr( + cel_AstVisitor* cel_nonnull visitor, + const cel_TernaryExpr* cel_nonnull expr) { + _cel_Interpreter* interp = _cel_Interpreter_FromAstVisitor(visitor); + _cel_InterpreterCond* cond = _cel_Interpreter_TopCond(interp); + CEL_ASSERT_EQ(cel_Expr_Kind(cond->node), cel_ExprKind_kTernary); + _cel_InterpreterTernaryCond* ternary_cond = &cond->ternary; + _cel_Instr* trilean_jump = + _cel_Interpreter_InstrAt(interp, ternary_cond->trilean_jump_pc); + CEL_ASSERT_EQ(trilean_jump->kind, _cel_InstrKind_kTrileanJump); + trilean_jump->data.trilean_jump.error_jump = + _cel_Interpreter_NextPC(interp) - + _cel_Interpreter_PC(interp, trilean_jump); + _cel_Instr* jump = _cel_Interpreter_InstrAt(interp, ternary_cond->jump_pc); + CEL_ASSERT_EQ(jump->kind, _cel_InstrKind_kJump); + jump->data.jump.jump = + _cel_Interpreter_NextPC(interp) - _cel_Interpreter_PC(interp, jump); + _cel_Interpreter_PopCond(interp); +} + +static void _cel_Interpreter_PostVisitTernaryExprCondition( + cel_AstVisitor* cel_nonnull visitor, const cel_Expr* cel_nonnull expr) { + _cel_Interpreter* interp = _cel_Interpreter_FromAstVisitor(visitor); + _cel_InterpreterCond* cond = _cel_Interpreter_TopCond(interp); + CEL_ASSERT_EQ(cel_Expr_Kind(cond->node), cel_ExprKind_kTernary); + _cel_InterpreterTernaryCond* ternary_cond = &cond->ternary; + _cel_Instr* trilean_jump = _cel_Interpreter_AppendInstr(interp); + trilean_jump->kind = _cel_InstrKind_kTrileanJump; + // false_jump is set in _cel_Interpreter_PostVisitTernaryExprIfTrue. + trilean_jump->data.trilean_jump.false_jump = 0; + // error_jump is set in _cel_Interpreter_PostVisitTernaryExpr. + trilean_jump->data.trilean_jump.error_jump = 0; + ternary_cond->trilean_jump_pc = _cel_Interpreter_PC(interp, trilean_jump); + _cel_Interpreter_DecrementValueStackSize(interp); +} + +static void _cel_Interpreter_PostVisitTernaryExprIfTrue( + cel_AstVisitor* cel_nonnull visitor, const cel_Expr* cel_nonnull expr) { + _cel_Interpreter* interp = _cel_Interpreter_FromAstVisitor(visitor); + _cel_InterpreterCond* cond = _cel_Interpreter_TopCond(interp); + CEL_ASSERT_EQ(cel_Expr_Kind(cond->node), cel_ExprKind_kTernary); + _cel_InterpreterTernaryCond* ternary_cond = &cond->ternary; + _cel_Instr* jump = _cel_Interpreter_AppendInstr(interp); + _cel_Instr* trilean_jump = + _cel_Interpreter_InstrAt(interp, ternary_cond->trilean_jump_pc); + CEL_ASSERT_EQ(trilean_jump->kind, _cel_InstrKind_kTrileanJump); + trilean_jump->data.trilean_jump.false_jump = + _cel_Interpreter_NextPC(interp) - + _cel_Interpreter_PC(interp, trilean_jump); + jump->kind = _cel_InstrKind_kJump; + // Set in _cel_Interpreter_PostVisitTernaryExpr. + jump->data.jump.jump = 0; + ternary_cond->jump_pc = _cel_Interpreter_PC(interp, jump); + _cel_Interpreter_DecrementValueStackSize(interp); +} + +static void _cel_Interpreter_PreVisitListExpr( + cel_AstVisitor* cel_nonnull visitor, const cel_ListExpr* cel_nonnull expr) { + _cel_Interpreter* interp = _cel_Interpreter_FromAstVisitor(visitor); + size_t num_elements = cel_ListExpr_Elements(expr, cel_nullptr, cel_nullptr); + if (num_elements > (size_t)INT32_MAX) { + // TODO: allow this to be configurable + cel_InvalidArgumentStatusF(interp->status, + "cel: list expr too large: %" PRId64, + cel_Expr_Id(cel_Expr_UpCast(expr))); + _cel_longjmp(interp->jmp); + } + _cel_InterpreterList* list = _cel_Interpreter_PushList(interp); + _cel_Array_Construct(&list->pcs); + if (!_cel_Array_Reserve(&list->pcs, interp->alloc, num_elements)) { + cel_OutOfMemoryStatus(interp->status); + _cel_longjmp(interp->jmp); + } +} + +static void _cel_Interpreter_PostVisitListExpr( + cel_AstVisitor* cel_nonnull visitor, const cel_ListExpr* cel_nonnull expr) { + _cel_Interpreter* interp = _cel_Interpreter_FromAstVisitor(visitor); + _cel_Instr* instr = _cel_Interpreter_AppendInstr(interp); + instr->kind = _cel_InstrKind_kList; + size_t element_count = cel_ListExpr_Elements(expr, cel_nullptr, cel_nullptr); + instr->data.list.count = (uint32_t)element_count; + _cel_InterpreterList* list = _cel_Interpreter_TopList(interp); + CEL_ASSERT_EQ(_cel_Array_Size(&list->pcs), element_count); + for (size_t i = 0; i < element_count; ++i) { + uint32_t pc = *_cel_Array_At(&list->pcs, i); + _cel_Instr* error_jump = _cel_Interpreter_InstrAt(interp, pc); + CEL_ASSERT_EQ(error_jump->kind, _cel_InstrKind_kErrorJump); + error_jump->data.error_jump.jump = _cel_Interpreter_NextPC(interp) - + _cel_Interpreter_PC(interp, error_jump); + error_jump->data.error_jump.pop = (uint32_t)i; + } + _cel_Array_Destruct(&list->pcs, interp->alloc); + _cel_Interpreter_PopList(interp); + if (element_count > 0) { + _cel_Interpreter_DecrementValueStackSizeN(interp, element_count - 1); + } else { + _cel_Interpreter_IncrementValueStackSize(interp); + } +} + +static void _cel_Interpreter_PreVisitListElementExpr( + cel_AstVisitor* cel_nonnull visitor, + const cel_ListElementExpr* cel_nonnull expr) { + _cel_Interpreter* interp = _cel_Interpreter_FromAstVisitor(visitor); + if (cel_ListElementExpr_Value(expr) == cel_nullptr) { + cel_InvalidArgumentStatusF(interp->status, + "cel: list element expr missing value: %" PRId64, + cel_Expr_Id(cel_Expr_UpCast(expr))); + _cel_longjmp(interp->jmp); + } + if (cel_ListElementExpr_Optional(expr)) { + cel_InvalidArgumentStatusF( + interp->status, + "cel: list element expr optional is not yet implemented: %" PRId64, + cel_Expr_Id(cel_Expr_UpCast(expr))); + _cel_longjmp(interp->jmp); + } +} + +static void _cel_Interpreter_PostVisitListElementExpr( + cel_AstVisitor* cel_nonnull visitor, + const cel_ListElementExpr* cel_nonnull expr) { + _cel_Interpreter* interp = _cel_Interpreter_FromAstVisitor(visitor); + _cel_InterpreterList* list = _cel_Interpreter_TopList(interp); + _cel_Instr* instr = _cel_Interpreter_AppendInstr(interp); + instr->kind = _cel_InstrKind_kErrorJump; + // Set in _cel_Interpreter_PostVisitListExpr. + instr->data.error_jump.jump = 0; + instr->data.error_jump.pop = 0; + uint32_t* pc = _cel_Array_Push(&list->pcs, interp->alloc); + if (pc == cel_nullptr) { + cel_OutOfMemoryStatus(interp->status); + _cel_longjmp(interp->jmp); + } + *pc = _cel_Interpreter_PC(interp, instr); +} + +static void _cel_Interpreter_PreVisitMapExpr( + cel_AstVisitor* cel_nonnull visitor, const cel_MapExpr* cel_nonnull expr) { + _cel_Interpreter* interp = _cel_Interpreter_FromAstVisitor(visitor); + size_t num_entries = cel_MapExpr_Entries(expr, cel_nullptr, cel_nullptr); + if (num_entries > (size_t)INT32_MAX) { + // TODO: allow this to be configurable + cel_InvalidArgumentStatusF(interp->status, + "cel: map expr too large: %" PRId64, + cel_Expr_Id(cel_Expr_UpCast(expr))); + _cel_longjmp(interp->jmp); + } + _cel_InterpreterMap* map = _cel_Interpreter_PushMap(interp); + _cel_Array_Construct(&map->pcs); + if (!_cel_Array_Reserve(&map->pcs, interp->alloc, num_entries * 2)) { + cel_OutOfMemoryStatus(interp->status); + _cel_longjmp(interp->jmp); + } +} + +static void _cel_Interpreter_PostVisitMapExpr( + cel_AstVisitor* cel_nonnull visitor, const cel_MapExpr* cel_nonnull expr) { + _cel_Interpreter* interp = _cel_Interpreter_FromAstVisitor(visitor); + _cel_Instr* instr = _cel_Interpreter_AppendInstr(interp); + instr->kind = _cel_InstrKind_kMap; + size_t entries_count = cel_MapExpr_Entries(expr, cel_nullptr, cel_nullptr); + instr->data.map.count = (uint32_t)entries_count; + _cel_InterpreterMap* map = _cel_Interpreter_TopMap(interp); + CEL_ASSERT_EQ(_cel_Array_Size(&map->pcs), entries_count * 2); + for (size_t i = 0; i < entries_count; ++i) { + uint32_t key_pc = *_cel_Array_At(&map->pcs, i * 2); + uint32_t value_pc = *_cel_Array_At(&map->pcs, i * 2 + 1); + _cel_Instr* key_jump = _cel_Interpreter_InstrAt(interp, key_pc); + CEL_ASSERT_EQ(key_jump->kind, _cel_InstrKind_kKeyJump); + key_jump->data.key_jump.jump = + _cel_Interpreter_NextPC(interp) - _cel_Interpreter_PC(interp, key_jump); + key_jump->data.key_jump.pop = (uint32_t)(i * 2); + _cel_Instr* error_jump = _cel_Interpreter_InstrAt(interp, value_pc); + CEL_ASSERT_EQ(error_jump->kind, _cel_InstrKind_kErrorJump); + error_jump->data.error_jump.jump = _cel_Interpreter_NextPC(interp) - + _cel_Interpreter_PC(interp, error_jump); + error_jump->data.error_jump.pop = (uint32_t)(i * 2 + 1); + } + _cel_Array_Destruct(&map->pcs, interp->alloc); + _cel_Interpreter_PopMap(interp); + if (entries_count > 0) { + _cel_Interpreter_DecrementValueStackSizeN(interp, entries_count * 2 - 1); + } else { + _cel_Interpreter_IncrementValueStackSize(interp); + } +} + +static void _cel_Interpreter_PreVisitMapEntryExpr( + cel_AstVisitor* cel_nonnull visitor, + const cel_MapEntryExpr* cel_nonnull expr) { + _cel_Interpreter* interp = _cel_Interpreter_FromAstVisitor(visitor); + if (cel_MapEntryExpr_Key(expr) == cel_nullptr) { + cel_InvalidArgumentStatusF(interp->status, + "cel: map entry expr missing key: %" PRId64, + cel_Expr_Id(cel_Expr_UpCast(expr))); + _cel_longjmp(interp->jmp); + } + if (cel_MapEntryExpr_Value(expr) == cel_nullptr) { + cel_InvalidArgumentStatusF(interp->status, + "cel: map entry expr missing value: %" PRId64, + cel_Expr_Id(cel_Expr_UpCast(expr))); + _cel_longjmp(interp->jmp); + } + if (cel_MapEntryExpr_Optional(expr)) { + cel_InvalidArgumentStatusF( + interp->status, + "cel: map entry expr optional is not yet implemented: %" PRId64, + cel_Expr_Id(cel_Expr_UpCast(expr))); + _cel_longjmp(interp->jmp); + } +} + +static void _cel_Interpreter_PostVisitMapEntryExprKey( + cel_AstVisitor* cel_nonnull visitor, const cel_Expr* cel_nonnull expr) { + _cel_Interpreter* interp = _cel_Interpreter_FromAstVisitor(visitor); + _cel_InterpreterMap* map = _cel_Interpreter_TopMap(interp); + _cel_Instr* instr = _cel_Interpreter_AppendInstr(interp); + instr->kind = _cel_InstrKind_kKeyJump; + // Set in _cel_Interpreter_PostVisitMapExpr. + instr->data.key_jump.jump = 0; + instr->data.key_jump.pop = 0; + uint32_t* pc = _cel_Array_Push(&map->pcs, interp->alloc); + if (pc == cel_nullptr) { + cel_OutOfMemoryStatus(interp->status); + _cel_longjmp(interp->jmp); + } + *pc = _cel_Interpreter_PC(interp, instr); +} + +static void _cel_Interpreter_PostVisitMapEntryExprValue( + cel_AstVisitor* cel_nonnull visitor, const cel_Expr* cel_nonnull expr) { + _cel_Interpreter* interp = _cel_Interpreter_FromAstVisitor(visitor); + _cel_InterpreterMap* map = _cel_Interpreter_TopMap(interp); + _cel_Instr* instr = _cel_Interpreter_AppendInstr(interp); + instr->kind = _cel_InstrKind_kErrorJump; + // Set in _cel_Interpreter_PostVisitMapExpr. + instr->data.key_jump.jump = 0; + instr->data.key_jump.pop = 0; + uint32_t* pc = _cel_Array_Push(&map->pcs, interp->alloc); + if (pc == cel_nullptr) { + cel_OutOfMemoryStatus(interp->status); + _cel_longjmp(interp->jmp); + } + *pc = _cel_Interpreter_PC(interp, instr); +} + +static void _cel_Interpreter_PreVisitCallArgExpr( + cel_AstVisitor* cel_nonnull visitor, + const cel_CallArgExpr* cel_nonnull expr) { + _cel_Interpreter* interp = _cel_Interpreter_FromAstVisitor(visitor); + if (cel_CallArgExpr_Value(expr) == cel_nullptr) { + cel_InvalidArgumentStatusF(interp->status, + "cel: call arg expr missing value: %" PRId64, + cel_Expr_Id(cel_Expr_UpCast(expr))); + _cel_longjmp(interp->jmp); + } +} + +static void _cel_Interpreter_PostVisitCallExpr( + cel_AstVisitor* cel_nonnull visitor, const cel_CallExpr* cel_nonnull expr) { + _cel_Interpreter* interp = _cel_Interpreter_FromAstVisitor(visitor); + uint32_t index = + _cel_Interpreter_InternString(interp, cel_CallExpr_Function(expr)); + _cel_Instr* instr = _cel_Interpreter_AppendInstr(interp); + instr->data.call.name = index; + instr->data.call.args = cel_CallExpr_Args(expr, cel_nullptr, cel_nullptr) + + (cel_CallExpr_Target(expr) != 0); + cel_StringView fn_name = + _cel_Interpreter_InternedString(interp, instr->data.call.name); + bool found = false; + if (instr->data.call.args == 1) { + if (cel_StringView_Equals(fn_name, cel_StringView_From("int"))) { + instr->kind = _cel_InstrKind_kCallInt; + found = true; + } else if (cel_StringView_Equals(fn_name, cel_StringView_From("uint"))) { + instr->kind = _cel_InstrKind_kCallUint; + found = true; + } else if (cel_StringView_Equals(fn_name, cel_StringView_From("bool"))) { + instr->kind = _cel_InstrKind_kCallBool; + found = true; + } else if (cel_StringView_Equals(fn_name, cel_StringView_From("double"))) { + instr->kind = _cel_InstrKind_kCallDouble; + found = true; + } else if (cel_StringView_Equals(fn_name, cel_StringView_From("bytes"))) { + instr->kind = _cel_InstrKind_kCallBytes; + found = true; + } else if (cel_StringView_Equals(fn_name, cel_StringView_From("string"))) { + instr->kind = _cel_InstrKind_kCallString; + found = true; + } else if (cel_StringView_Equals(fn_name, + cel_StringView_From("timestamp"))) { + instr->kind = _cel_InstrKind_kCallTimestamp; + found = true; + } else if (cel_StringView_Equals(fn_name, + cel_StringView_From("duration"))) { + instr->kind = _cel_InstrKind_kCallDuration; + found = true; + } else if (cel_StringView_Equals(fn_name, cel_StringView_From("size"))) { + instr->kind = _cel_InstrKind_kCallSize; + found = true; + } + } else if (instr->data.call.args == 2) { + if (cel_StringView_Equals(fn_name, cel_StringView_From("contains"))) { + instr->kind = _cel_InstrKind_kCallContainsString; + found = true; + } else if (cel_StringView_Equals(fn_name, + cel_StringView_From("startsWith"))) { + instr->kind = _cel_InstrKind_kCallStartsWithString; + found = true; + } else if (cel_StringView_Equals(fn_name, + cel_StringView_From("endsWith"))) { + instr->kind = _cel_InstrKind_kCallEndsWithString; + found = true; + } else if (cel_StringView_Equals(fn_name, cel_StringView_From("matches"))) { + instr->kind = _cel_InstrKind_kCallRegexExpMatch; + found = true; + } + } + + if (!found) { + cel_InvalidArgumentStatusF(interp->status, + "cel: function with name " CEL_STRINGVIEW_FMT + " taking %d args not found", + CEL_STRINGVIEW_ARGS(fn_name), + instr->data.call.args); + _cel_longjmp(interp->jmp); + } + + if (instr->data.call.args == 0) { + _cel_Interpreter_IncrementValueStackSize(interp); + } else { + for (size_t i = 1; i < instr->data.call.args; ++i) { + _cel_Interpreter_DecrementValueStackSize(interp); + } + } +} + +CEL_ATTRIBUTE_NODISCARD +static bool _cel_Interpreter_IsBind( + const cel_ComprehensionExpr* cel_nonnull expr) { + if (!cel_StringView_Equals(cel_ComprehensionExpr_IterVar(expr), + cel_StringView_From("#unused"))) { + return false; + } + if (!cel_StringView_Empty(cel_ComprehensionExpr_IterVar2(expr))) { + return false; + } + cel_StringView accu_var = cel_ComprehensionExpr_AccuVar(expr); + if (cel_StringView_Empty(accu_var)) { + return false; + } + const cel_Expr* iter_range = cel_ComprehensionExpr_IterRange(expr); + if (iter_range == cel_nullptr || + cel_Expr_Kind(iter_range) != cel_ExprKind_kList) { + return false; + } + const cel_Expr* loop_condition = cel_ComprehensionExpr_LoopCondition(expr); + if (loop_condition == cel_nullptr || + cel_Expr_Kind(loop_condition) != cel_ExprKind_kConst) { + return false; + } + const cel_Constant* loop_condition_constant = + cel_ConstExpr_Value(cel_ConstExpr_DownCast(loop_condition)); + if (cel_Constant_Kind(loop_condition_constant) != cel_ConstantKind_kBool || + cel_Constant_GetBool(loop_condition_constant)) { + return false; + } + const cel_Expr* loop_step = cel_ComprehensionExpr_LoopStep(expr); + if (loop_step == cel_nullptr || + cel_Expr_Kind(loop_step) != cel_ExprKind_kIdent || + !cel_StringView_Equals( + cel_IdentExpr_Name(cel_IdentExpr_DownCast(loop_step)), accu_var)) { + return false; + } + return true; +} + +static void _cel_Interpreter_PreVisitComprehensionExpr( + cel_AstVisitor* cel_nonnull visitor, + const cel_ComprehensionExpr* cel_nonnull expr) { + _cel_Interpreter* interp = _cel_Interpreter_FromAstVisitor(visitor); + if (cel_StringView_Empty(cel_ComprehensionExpr_IterVar(expr))) { + cel_InvalidArgumentStatusF( + interp->status, "cel: comprehension expr missing iter var: %" PRId64, + cel_Expr_Id(cel_Expr_UpCast(expr))); + _cel_longjmp(interp->jmp); + } + if (cel_ComprehensionExpr_IterRange(expr) == cel_nullptr) { + cel_InvalidArgumentStatusF( + interp->status, "cel: comprehension expr missing iter range: %" PRId64, + cel_Expr_Id(cel_Expr_UpCast(expr))); + _cel_longjmp(interp->jmp); + } + if (cel_StringView_Empty(cel_ComprehensionExpr_AccuVar(expr))) { + cel_InvalidArgumentStatusF( + interp->status, "cel: comprehension expr missing accu var: %" PRId64, + cel_Expr_Id(cel_Expr_UpCast(expr))); + _cel_longjmp(interp->jmp); + } + if (cel_ComprehensionExpr_AccuInit(expr) == cel_nullptr) { + cel_InvalidArgumentStatusF( + interp->status, "cel: comprehension expr missing iter range: %" PRId64, + cel_Expr_Id(cel_Expr_UpCast(expr))); + _cel_longjmp(interp->jmp); + } + if (cel_ComprehensionExpr_LoopCondition(expr) == cel_nullptr) { + cel_InvalidArgumentStatusF( + interp->status, + "cel: comprehension expr missing loop condition: %" PRId64, + cel_Expr_Id(cel_Expr_UpCast(expr))); + _cel_longjmp(interp->jmp); + } + if (cel_ComprehensionExpr_LoopStep(expr) == cel_nullptr) { + cel_InvalidArgumentStatusF( + interp->status, "cel: comprehension expr missing loop step: %" PRId64, + cel_Expr_Id(cel_Expr_UpCast(expr))); + _cel_longjmp(interp->jmp); + } + if (cel_ComprehensionExpr_Result(expr) == cel_nullptr) { + cel_InvalidArgumentStatusF( + interp->status, "cel: comprehension expr missing result: %" PRId64, + cel_Expr_Id(cel_Expr_UpCast(expr))); + _cel_longjmp(interp->jmp); + } + + // We only support cel.bind comprehensions currently. + if (!_cel_Interpreter_IsBind(expr)) { + cel_InvalidArgumentStatusF( + interp->status, + "cel: comprehension expr does not resemble cel.bind: %" PRId64, + cel_Expr_Id(cel_Expr_UpCast(expr))); + _cel_longjmp(interp->jmp); + } + + // Keep track of the outer most cel.bind. + if (interp->root_bind == cel_nullptr) { + interp->root_bind = expr; + } + + _cel_InterpreterSlot* slot = + _cel_Interpreter_NewSlot(interp, cel_ComprehensionExpr_AccuVar(expr)); + _cel_Interpreter_PushSlot(interp, slot); +} + +static void _cel_Interpreter_PostVisitComprehensionExpr( + cel_AstVisitor* cel_nonnull visitor, + const cel_ComprehensionExpr* cel_nonnull expr) { + _cel_Interpreter* interp = _cel_Interpreter_FromAstVisitor(visitor); + _cel_Interpreter_PopSlot(interp); + if (interp->root_bind == expr) { + _cel_Instr* lazy_enter = _cel_Interpreter_AppendInstr(interp); + lazy_enter->kind = _cel_InstrKind_kLazyLeave; + // At the moment only cel.bind comprehensions are implemented. So we always + // start at slot 0 when clearing. + lazy_enter->data.lazy_leave.slot = 0; + lazy_enter->data.lazy_leave.num_slots = + _cel_Interpreter_DeleteSlots(interp); + interp->root_bind = cel_nullptr; + } +} + +static void _cel_Interpreter_PreVisitComprehensionExprIterRange( + cel_AstVisitor* cel_nonnull visitor, const cel_Expr* cel_nonnull expr) { + _cel_Interpreter* interp = _cel_Interpreter_FromAstVisitor(visitor); + cel_AstTraverser_StepOut(interp->traverser); +} + +static void _cel_Interpreter_PreVisitComprehensionExprAccuInit( + cel_AstVisitor* cel_nonnull visitor, const cel_Expr* cel_nonnull expr) { + _cel_Interpreter* interp = _cel_Interpreter_FromAstVisitor(visitor); + _cel_InterpreterSlot* slot = _cel_Interpreter_TopSlot(interp); + _cel_Instr* instr = _cel_Interpreter_AppendInstr(interp); + instr->kind = _cel_InstrKind_kJump; + instr->data.jump.jump = 0; + slot->guard_pc = _cel_Interpreter_PC(interp, instr); + slot->pc = _cel_Interpreter_NextPC(interp); + _cel_Interpreter_PushValueStackBounds(interp, &slot->value_stack_bounds); +} + +static void _cel_Interpreter_PostVisitComprehensionExprAccuInit( + cel_AstVisitor* cel_nonnull visitor, const cel_Expr* cel_nonnull expr) { + _cel_Interpreter* interp = _cel_Interpreter_FromAstVisitor(visitor); + _cel_InterpreterSlot* slot = _cel_Interpreter_TopSlot(interp); + _cel_Instr* lazy_return = _cel_Interpreter_AppendInstr(interp); + lazy_return->kind = _cel_InstrKind_kLazyReturn; + lazy_return->data.lazy_return.slot = slot->index; + _cel_Instr* jump = _cel_Interpreter_InstrAt(interp, slot->guard_pc); + CEL_ASSERT_EQ(jump->kind, _cel_InstrKind_kJump); + jump->data.jump.jump = _cel_Interpreter_NextPC(interp) - slot->guard_pc; + _cel_Interpreter_PopValueStackBounds(interp); +} + +static void _cel_Interpreter_PreVisitComprehensionExprLoopCondition( + cel_AstVisitor* cel_nonnull visitor, const cel_Expr* cel_nonnull expr) { + _cel_Interpreter* interp = _cel_Interpreter_FromAstVisitor(visitor); + cel_AstTraverser_StepOut(interp->traverser); +} + +static void _cel_Interpreter_PreVisitComprehensionExprLoopStep( + cel_AstVisitor* cel_nonnull visitor, const cel_Expr* cel_nonnull expr) { + _cel_Interpreter* interp = _cel_Interpreter_FromAstVisitor(visitor); + cel_AstTraverser_StepOut(interp->traverser); +} + +static void _cel_Interpreter_PreVisitComprehensionExprResult( + cel_AstVisitor* cel_nonnull visitor, const cel_Expr* cel_nonnull expr) { + _cel_Interpreter* interp = _cel_Interpreter_FromAstVisitor(visitor); + _cel_InterpreterSlot* slot = _cel_Interpreter_TopSlot(interp); + _cel_FlatHashMapNode(interp->slot_map) prev_slot; + prev_slot = _cel_FlatHashMap_Find(&interp->slot_map, &slot->name); + if (prev_slot != cel_nullptr) { + slot->prev_slot = (int32_t)prev_slot->val; + } + _cel_FlatHashMapMutableNode(interp->slot_map) slot_node; + if (!_cel_FlatHashMap_Insert(&interp->slot_map, interp->alloc, &slot->name, + &slot_node)) { + if (slot_node == cel_nullptr) { + cel_OutOfMemoryStatus(interp->status); + _cel_longjmp(interp->jmp); + } + CEL_ASSERT_NE(slot->prev_slot, -1); + } + slot_node->val = slot->index; +#ifndef NDEBUG + _cel_Instr* lazy_enter = _cel_Interpreter_AppendInstr(interp); + lazy_enter->kind = _cel_InstrKind_kLazyEnter; + lazy_enter->data.lazy_enter.slot = slot->index; +#endif +} + +static void _cel_Interpreter_PostVisitComprehensionExprResult( + cel_AstVisitor* cel_nonnull visitor, const cel_Expr* cel_nonnull expr) { + _cel_Interpreter* interp = _cel_Interpreter_FromAstVisitor(visitor); + _cel_InterpreterSlot* slot = _cel_Interpreter_TopSlot(interp); + _cel_FlatHashMapMutableNode(interp->slot_map) prev_slot; + prev_slot = _cel_FlatHashMap_MutableFind(&interp->slot_map, &slot->name); + CEL_ASSERT_NOT_NULL(prev_slot); + if (slot->prev_slot != -1) { + prev_slot->val = slot->prev_slot; + } else { + _cel_FlatHashMap_Erase(&interp->slot_map, prev_slot); + } +} + +static const cel_AstVisitorVTable _cel_InterpreterVTable = { + .PreVisitExpr = &_cel_Interpreter_PreVisitExpr, + .VisitUnspecifiedExpr = &_cel_Interpreter_VisitUnspecifiedExpr, + .VisitIdentExpr = &_cel_Interpreter_VisitIdentExpr, + .VisitConstExpr = &_cel_Interpreter_VisitConstExpr, + .PreVisitSelectExpr = &_cel_Interpreter_PreVisitSelectExpr, + .PostVisitSelectExpr = &_cel_Interpreter_PostVisitSelectExpr, + .PreVisitUnaryExpr = &_cel_Interpreter_PreVisitUnaryExpr, + .PostVisitUnaryExpr = &_cel_Interpreter_PostVisitUnaryExpr, + .PreVisitBinaryExpr = &_cel_Interpreter_PreVisitBinaryExpr, + .PostVisitBinaryExpr = &_cel_Interpreter_PostVisitBinaryExpr, + .PostVisitBinaryExprLeft = &_cel_Interpreter_PostVisitBinaryExprLeft, + .PreVisitTernaryExpr = &_cel_Interpreter_PreVisitTernaryExpr, + .PostVisitTernaryExpr = &_cel_Interpreter_PostVisitTernaryExpr, + .PostVisitTernaryExprCondition = + &_cel_Interpreter_PostVisitTernaryExprCondition, + .PostVisitTernaryExprIfTrue = &_cel_Interpreter_PostVisitTernaryExprIfTrue, + .PreVisitCallArgExpr = &_cel_Interpreter_PreVisitCallArgExpr, + .PostVisitCallExpr = &_cel_Interpreter_PostVisitCallExpr, + .PreVisitListElementExpr = &_cel_Interpreter_PreVisitListElementExpr, + .PostVisitListElementExpr = &_cel_Interpreter_PostVisitListElementExpr, + .PreVisitListExpr = &_cel_Interpreter_PreVisitListExpr, + .PostVisitListExpr = &_cel_Interpreter_PostVisitListExpr, + .PreVisitMapEntryExpr = &_cel_Interpreter_PreVisitMapEntryExpr, + .PostVisitMapEntryExprKey = &_cel_Interpreter_PostVisitMapEntryExprKey, + .PostVisitMapEntryExprValue = &_cel_Interpreter_PostVisitMapEntryExprValue, + .PreVisitMapExpr = &_cel_Interpreter_PreVisitMapExpr, + .PostVisitMapExpr = &_cel_Interpreter_PostVisitMapExpr, + .PreVisitComprehensionExpr = &_cel_Interpreter_PreVisitComprehensionExpr, + .PostVisitComprehensionExpr = &_cel_Interpreter_PostVisitComprehensionExpr, + .PreVisitComprehensionExprIterRange = + &_cel_Interpreter_PreVisitComprehensionExprIterRange, + .PreVisitComprehensionExprAccuInit = + &_cel_Interpreter_PreVisitComprehensionExprAccuInit, + .PostVisitComprehensionExprAccuInit = + &_cel_Interpreter_PostVisitComprehensionExprAccuInit, + .PreVisitComprehensionExprLoopCondition = + &_cel_Interpreter_PreVisitComprehensionExprLoopCondition, + .PreVisitComprehensionExprLoopStep = + &_cel_Interpreter_PreVisitComprehensionExprLoopStep, + .PreVisitComprehensionExprResult = + &_cel_Interpreter_PreVisitComprehensionExprResult, + .PostVisitComprehensionExprResult = + &_cel_Interpreter_PostVisitComprehensionExprResult, +}; + +static size_t _cel_Interpreter_StringViewHasher(const void* cel_nonnull key) { + return cel_HashState_Finalize(cel_HashState_Combine( + cel_HashState_Initialize(), *(const cel_StringView*)key)); +} + +static bool _cel_Interpreter_StringViewEqualer(const void* cel_nonnull lhs, + const void* cel_nonnull rhs) { + return cel_StringView_Equals(*(const cel_StringView*)lhs, + *(const cel_StringView*)rhs); +} + +static void _cel_Interpreter_Construct(_cel_Interpreter* cel_nonnull interp, + const cel_Runtime* cel_nonnull rt, + cel_Status* cel_nonnull status) { + memset(interp, 0, sizeof(*interp)); + interp->visitor.vtable = &_cel_InterpreterVTable; + interp->rt = _cel_Runtime_ConstRef(rt); + interp->alloc = rt->alloc; + interp->def_pool = rt->def_pool; + interp->wkts = &rt->wkts; + interp->status = status; + interp->root_bind = cel_nullptr; + _cel_FlatHashMap_Construct(&interp->string_pool_indices, + &_cel_Interpreter_StringViewHasher, + &_cel_Interpreter_StringViewEqualer); + _cel_FlatHashMap_Construct(&interp->candidate_names_indices, + &_cel_Interpreter_StringViewHasher, + &_cel_Interpreter_StringViewEqualer); + _cel_InterpreterNspaceVar_Construct(&interp->nspace_var); + _cel_Array_Construct(&interp->conds); + _cel_Array_Construct(&interp->lists); + _cel_Array_Construct(&interp->maps); + _cel_Deque_Construct(&interp->slots); + _cel_FlatHashMap_Construct(&interp->slot_map, + &_cel_Interpreter_StringViewHasher, + &_cel_Interpreter_StringViewEqualer); + _cel_Array_Construct(&interp->slot_stack); + _cel_Array_Construct(&interp->value_stack_bounds_stack); +} + +static void _cel_Interpreter_Destruct(_cel_Interpreter* cel_nonnull interp) { + cel_AstTraverser_Delete(interp->traverser); + _cel_Array_Destruct(&interp->value_stack_bounds_stack, interp->alloc); + _cel_Array_Destruct(&interp->slot_stack, interp->alloc); + _cel_FlatHashMap_Destruct(&interp->slot_map, interp->alloc); + _cel_Deque_Destruct(&interp->slots, interp->alloc); + _cel_Array_Destruct(&interp->conds, interp->alloc); + for (size_t i = 0; i < _cel_Array_Size(&interp->lists); ++i) { + _cel_Array_Destruct(&_cel_Array_MutableAt(&interp->lists, i)->pcs, + interp->alloc); + } + _cel_Array_Destruct(&interp->lists, interp->alloc); + for (size_t i = 0; i < _cel_Array_Size(&interp->maps); ++i) { + _cel_Array_Destruct(&_cel_Array_MutableAt(&interp->maps, i)->pcs, + interp->alloc); + } + _cel_Array_Destruct(&interp->maps, interp->alloc); + _cel_InterpreterNspaceVar_Destruct(&interp->nspace_var, interp->alloc); + _cel_FlatHashMap_Destruct(&interp->candidate_names_indices, interp->alloc); + _cel_FlatHashMap_Destruct(&interp->string_pool_indices, interp->alloc); + // interp->prog is only non-null when planning failed, call cel_Program_Delete + // to avoid hitting a debug assert as we hold the only reference + cel_Program_Delete(interp->prog); + _cel_Runtime_Unref(interp->rt); +} + +static void _cel_Interpreter_DirectInstrs( + _cel_Interpreter* cel_nonnull interp) { + const size_t instr_len = _cel_Array_Size(&interp->prog->instrs); + _cel_Instr* instr_ptr = _cel_Array_MutableData(&interp->prog->instrs); + for (size_t i = 0; i < instr_len; ++instr_ptr, ++i) { + switch (instr_ptr->kind) { + case _cel_InstrKind_kIdent: + instr_ptr->data.ident.name.direct = + _cel_PackedStringView_FromStringView( + _cel_Interpreter_InternedString( + interp, instr_ptr->data.ident.name.indirect)); + break; + case _cel_InstrKind_kBytesConst: + instr_ptr->data.bytes_const.value.direct = + _cel_PackedStringView_FromStringView( + _cel_Interpreter_InternedString( + interp, instr_ptr->data.bytes_const.value.indirect)); + break; + case _cel_InstrKind_kStringConst: + instr_ptr->data.string_const.value.direct = + _cel_PackedStringView_FromStringView( + _cel_Interpreter_InternedString( + interp, instr_ptr->data.string_const.value.indirect)); + break; + case _cel_InstrKind_kHas: + instr_ptr->data.has.field.direct = _cel_PackedStringView_FromStringView( + _cel_Interpreter_InternedString( + interp, instr_ptr->data.has.field.indirect)); + break; + case _cel_InstrKind_kSelect: + instr_ptr->data.select.field.direct = + _cel_PackedStringView_FromStringView( + _cel_Interpreter_InternedString( + interp, instr_ptr->data.select.field.indirect)); + break; + default: + break; + } + } +} + +extern "C" cel_Program* cel_nullable _cel_Interpreter_Compile( + const cel_Runtime* cel_nonnull rt, const cel_Ast* cel_nonnull ast, + cel_Status* cel_nonnull status) { + CEL_ASSERT_NOT_NULL(rt); + CEL_ASSERT_NOT_NULL(ast); + CEL_ASSERT(cel_Status_Ok(status)); + + if (cel_Ast_Expr(ast) == cel_nullptr) { + cel_InvalidArgumentStatus(status, cel_StringView_From("cel: AST is empty")); + return cel_nullptr; + } + + _cel_Interpreter interp; + _cel_Interpreter* volatile interp_ptr = &interp; + _cel_Interpreter_Construct(&interp, rt, status); + + cel_Program* prog = _cel_Program_New(rt); + if (CEL_UNLIKELY(prog == cel_nullptr)) { + cel_OutOfMemoryStatus(status); + _cel_Interpreter_Destruct(&interp); + return cel_nullptr; + } + interp.prog = prog; + + cel_AstTraverser* traverser = cel_AstTraverser_New(ast, &interp.visitor); + if (CEL_UNLIKELY(traverser == cel_nullptr)) { + cel_OutOfMemoryStatus(status); + _cel_Interpreter_Destruct(&interp); + return cel_nullptr; + } + interp.traverser = traverser; + if (_cel_setjmp(interp_ptr->jmp)) { + CEL_ASSERT_NOT(cel_Status_Ok(interp_ptr->status)); + _cel_Interpreter_Destruct(&interp); + return cel_nullptr; + } + + _cel_Interpreter_PushValueStackBounds(interp_ptr, + &interp_ptr->value_stack_bounds); + + while (cel_AstTraverser_Traverse(traverser, status) && + cel_Status_Ok(status)) { + } + if (cel_Status_Ok(status)) { + prog->max_stack_size = interp.value_stack_bounds.max; + _cel_Instr* instr = _cel_Interpreter_AppendInstr(&interp); + if (CEL_UNLIKELY(instr == cel_nullptr)) { + _cel_Interpreter_Destruct(&interp); + return cel_nullptr; + } + instr->kind = _cel_InstrKind_kExit; + _cel_Array_ShrinkToFit(&prog->instrs, interp.alloc); + _cel_Array_ShrinkToFit(&prog->strings_table, interp.alloc); + _cel_Array_ShrinkToFit(&prog->candidate_names_table, interp.alloc); + // We only needed pointer stability for strings in the string pool with + // _cel_Interpreter. Now that planning is complete, we can forgo pointer + // stability in the name of less memory usage. + _cel_String* string_pool_ptr = _cel_Array_MutableData(&prog->strings_table); + size_t string_pool_len = _cel_Array_Size(&prog->strings_table); + for (; string_pool_len > 0; ++string_pool_ptr, --string_pool_len) { + _cel_String_Destabilize(string_pool_ptr, interp.alloc); + } + // Now that we have destablized strings, we can convert instructions from + // their indirect references to direct. + _cel_Interpreter_DirectInstrs(&interp); + + interp.prog = cel_nullptr; + } else { + prog = cel_nullptr; + } + _cel_Interpreter_Destruct(&interp); + return prog; +} diff --git a/cel-c/src/runtime/program.cc b/cel-c/src/runtime/program.cc new file mode 100644 index 0000000..6cca502 --- /dev/null +++ b/cel-c/src/runtime/program.cc @@ -0,0 +1,99 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "cel-c/src/runtime/program.h" + +#include +#include + +#include "cel-c/alloc.h" +#include "cel-c/assert.h" +#include "cel-c/config.h" +#include "cel-c/src/arc.h" +#include "cel-c/src/array.h" +#include "cel-c/src/runtime/activation.h" +#include "cel-c/src/runtime/runtime.h" +#include "cel-c/src/string.h" + +extern "C" cel_ProgramOptions* cel_nonnull +cel_ProgramOptions_Default(cel_ProgramOptions* cel_nonnull opts) { + CEL_ASSERT_NOT_NULL(opts); + + memset(opts, 0, sizeof(*opts)); + return opts; +} + +extern "C" void cel_Program_Delete(cel_Program* cel_nullable prog) { + CEL_ASSERT(prog == cel_nullptr || !prog->deleted); + + if (prog != cel_nullptr) { + prog->deleted = true; + _cel_Program_Unref(prog); + } +} + +extern "C" cel_Activation* cel_nullable +cel_Program_Activate(const cel_Program* cel_nonnull program, + const cel_VariableResolver* cel_nonnull var_resolver, + const cel_ActivationOptions* cel_nullable opts) { + CEL_ASSERT_NOT_NULL(program); + CEL_ASSERT_NOT_NULL(var_resolver); + CEL_USED(opts); + + return _cel_Activation_New(program, var_resolver); +} + +extern "C" cel_Program* cel_nullable +_cel_Program_New(const cel_Runtime* cel_nonnull rt) { + CEL_ASSERT_NOT_NULL(rt); + + cel_Program* prog = (cel_Program*)cel_Allocator_Malloc( + rt->alloc, sizeof(cel_Program), cel_nullptr); + if (CEL_UNLIKELY(prog == cel_nullptr)) { + return cel_nullptr; + } + memset(prog, 0, sizeof(*prog)); + prog->rt = _cel_Runtime_ConstRef(rt); + _cel_AtomicRefCount_Initialize(&prog->rc); + _cel_Array_Construct(&prog->instrs); + _cel_Array_Construct(&prog->strings_table); + _cel_Array_Construct(&prog->candidate_names_table); + return prog; +} + +extern "C" void _cel_Program_Delete(cel_Program* cel_nonnull prog) { + CEL_ASSERT_NOT_NULL(prog); + CEL_ASSERT(_cel_AtomicRefCount_Expired(&prog->rc)); + CEL_ASSERT(prog->deleted); + + const cel_Runtime* rt = prog->rt; + cel_Allocator* alloc = rt->alloc; + _cel_CandidateNames* candidate_names_ptr = + _cel_Array_MutableData(&prog->candidate_names_table); + size_t candidate_names_len = _cel_Array_Size(&prog->candidate_names_table); + for (; candidate_names_len > 0; + ++candidate_names_ptr, --candidate_names_len) { + _cel_CandidateNames_Destruct(candidate_names_ptr, alloc); + } + _cel_Array_Destruct(&prog->candidate_names_table, alloc); + _cel_String* string_ptr = _cel_Array_MutableData(&prog->strings_table); + size_t string_len = _cel_Array_Size(&prog->strings_table); + for (; string_len > 0; ++string_ptr, --string_len) { + _cel_String_Destruct(string_ptr, alloc); + } + _cel_Array_Destruct(&prog->strings_table, alloc); + _cel_Array_Destruct(&prog->instrs, alloc); + cel_Allocator_FreeSized(alloc, prog, sizeof(*prog)); + _cel_Runtime_Unref(rt); +} diff --git a/cel-c/src/runtime/runtime.cc b/cel-c/src/runtime/runtime.cc new file mode 100644 index 0000000..d2f6222 --- /dev/null +++ b/cel-c/src/runtime/runtime.cc @@ -0,0 +1,133 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "cel-c/src/runtime/runtime.h" + +#include + +#include "cel-c/alloc.h" +#include "cel-c/assert.h" +#include "cel-c/ast.h" +#include "cel-c/config.h" +#include "cel-c/program.h" +#include "cel-c/status.h" +#include "cel-c/well_known_types.h" +#include "cel-c/src/arc.h" +#include "cel-c/src/container.h" +#include "cel-c/src/runtime/interpreter.h" +#include "cel-c/src/string.h" +#include "upb/reflection/def.h" + +cel_RuntimeOptions* cel_nonnull +cel_RuntimeOptions_Default(cel_RuntimeOptions* cel_nonnull opts) { + CEL_ASSERT_NOT_NULL(opts); + + memset(opts, 0, sizeof(*opts)); + return opts; +} + +cel_Runtime* cel_nullable cel_Runtime_New( + cel_Allocator* cel_nonnull alloc, const upb_DefPool* cel_nonnull def_pool, + const cel_RuntimeOptions* cel_nullable opts, + cel_Status* cel_nonnull status) { + CEL_ASSERT_NOT_NULL(alloc); + CEL_ASSERT_NOT_NULL(def_pool); + CEL_ASSERT_NOT_NULL(status); + CEL_ASSERT(cel_Status_Ok(status)); + + cel_Runtime* rt = (cel_Runtime*)cel_Allocator_Malloc( + alloc, sizeof(cel_Runtime), cel_nullptr); + if (CEL_UNLIKELY(rt == cel_nullptr)) { + cel_OutOfMemoryStatus(status); + return cel_nullptr; + } + memset(rt, 0, sizeof(*rt)); + if (opts != cel_nullptr) { + rt->opts = *opts; + } else { + opts = cel_RuntimeOptions_Default(&rt->opts); + } + rt->alloc = alloc; + rt->def_pool = def_pool; + if (!cel_WellKnownTypes_Initialize(&rt->wkts, def_pool, status)) { + cel_Allocator_FreeSized(alloc, rt, sizeof(*rt)); + return cel_nullptr; + } + _cel_Container_Construct(&rt->container); + if (!_cel_Container_Update(&rt->container, rt->opts.container, rt->alloc)) { + _cel_Container_Destruct(&rt->container, rt->alloc); + cel_Allocator_FreeSized(alloc, rt, sizeof(*rt)); + return cel_nullptr; + } + rt->opts.container = _cel_String_ToStringView(&rt->container.name); + _cel_AtomicRefCount_Initialize(&rt->rc); + return rt; +} + +void cel_Runtime_Delete(cel_Runtime* cel_nullable rt) { + CEL_ASSERT(rt == cel_nullptr || !rt->deleted); + + if (rt != cel_nullptr) { + rt->deleted = true; + _cel_Runtime_Unref(rt); + } +} + +cel_Allocator* cel_nonnull +cel_Runtime_Allocator(const cel_Runtime* cel_nonnull rt) { + CEL_ASSERT_NOT_NULL(rt); + + return rt->alloc; +} + +const cel_RuntimeOptions* cel_nonnull +cel_Runtime_Options(const cel_Runtime* cel_nonnull rt) { + CEL_ASSERT_NOT_NULL(rt); + + return &rt->opts; +} + +const upb_DefPool* cel_nonnull +cel_Runtime_DefPool(const cel_Runtime* cel_nonnull rt) { + CEL_ASSERT_NOT_NULL(rt); + + return rt->def_pool; +} + +const cel_WellKnownTypes* cel_nonnull +cel_Runtime_WellKnownTypes(const cel_Runtime* cel_nonnull rt) { + CEL_ASSERT_NOT_NULL(rt); + + return &rt->wkts; +} + +cel_Program* cel_nullable cel_Runtime_Compile( + const cel_Runtime* cel_nonnull runtime, const cel_Ast* cel_nonnull ast, + const cel_ProgramOptions* cel_nullable opts, + cel_Status* cel_nonnull status) { + CEL_ASSERT_NOT_NULL(runtime); + CEL_ASSERT_NOT_NULL(ast); + CEL_ASSERT(cel_Status_Ok(status)); + + return _cel_Interpreter_Compile(runtime, ast, status); +} + +void _cel_Runtime_Delete(cel_Runtime* cel_nonnull rt) { + CEL_ASSERT_NOT_NULL(rt); + CEL_ASSERT(_cel_AtomicRefCount_Expired(&rt->rc)); + CEL_ASSERT(rt->deleted); + + _cel_Container_Destruct(&rt->container, rt->alloc); + cel_Allocator_FreeSized(rt->alloc, rt, sizeof(*rt)); +} diff --git a/cel-c/src/struct_value.cc b/cel-c/src/struct_value.cc new file mode 100644 index 0000000..5a5cabe --- /dev/null +++ b/cel-c/src/struct_value.cc @@ -0,0 +1,248 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include + +#include "cel-c/assert.h" +#include "cel-c/config.h" +#include "cel-c/src/array.h" +#include "cel-c/src/sort.h" +#include "cel-c/status.h" +#include "cel-c/string_view.h" +#include "cel-c/value.h" +#include "upb/reflection/def.h" + +typedef struct { + cel_StringView key; + cel_Value value; +} _cel_StructValueField; + +static int _cel_StructValueField_Compare( + const void* cel_nullability_unknown lhs, + const void* cel_nullability_unknown rhs) { + return cel_StringView_Compare(((const _cel_StructValueField*)lhs)->key, + ((const _cel_StructValueField*)rhs)->key); +} + +static bool _cel_StructValue_EqualsSlow( + const cel_StructValue* cel_nonnull struct_value, + const cel_ValueContext* cel_nonnull context, + const cel_StructValue* cel_nonnull other, cel_Value* cel_nonnull result, + cel_Status* cel_nonnull status) { + cel_StructValueIterator* lhs_iter = + cel_StructValue_NewIterator(struct_value, context, status); + if (CEL_UNLIKELY(lhs_iter == cel_nullptr)) { + CEL_ASSERT(!cel_Status_Ok(status)); + return false; + } + cel_StructValueIterator* rhs_iter = + cel_StructValue_NewIterator(other, context, status); + if (CEL_UNLIKELY(rhs_iter == cel_nullptr)) { + CEL_ASSERT(!cel_Status_Ok(status)); + cel_StructValueIterator_Delete(lhs_iter); + return false; + } + size_t lhs_size; + size_t rhs_size; + bool lhs_has_size; + bool rhs_has_size; + lhs_has_size = cel_StructValueIterator_Remaining(lhs_iter, &lhs_size); + rhs_has_size = cel_StructValueIterator_Remaining(rhs_iter, &rhs_size); + if (lhs_has_size && rhs_has_size) { + if (lhs_size != rhs_size) { + cel_Value_SetFalse(result); + cel_StructValueIterator_Delete(rhs_iter); + cel_StructValueIterator_Delete(lhs_iter); + return true; + } + if (lhs_size == 0) { + cel_Value_SetTrue(result); + cel_StructValueIterator_Delete(rhs_iter); + cel_StructValueIterator_Delete(lhs_iter); + return true; + } + } + _cel_Array(_cel_StructValueField) lhs_fields; + _cel_Array_Construct(&lhs_fields); + if (lhs_has_size) { + _cel_Array_Reserve(&lhs_fields, context->alloc, lhs_size); + } + _cel_Array(_cel_StructValueField) rhs_fields; + _cel_Array_Construct(&rhs_fields); + if (rhs_has_size) { + _cel_Array_Reserve(&rhs_fields, context->alloc, rhs_size); + } + bool ok = true; + bool lhs_next = true; + bool rhs_next = true; + while (true) { + if (lhs_next) { + cel_StructValueKey lhs_field_key; + _cel_StructValueField lhs_field; + lhs_next = cel_StructValueIterator_Next(lhs_iter, context, &lhs_field_key, + &lhs_field.value, status); + if (!lhs_next && !cel_Status_Ok(status)) { + ok = false; + break; + } + if (lhs_next) { + _cel_StructValueField* lhs_field_ptr = + _cel_Array_Push(&lhs_fields, context->alloc); + if (CEL_UNLIKELY(lhs_field_ptr == cel_nullptr)) { + cel_OutOfMemoryStatus(status); + ok = false; + break; + } + lhs_field_ptr->value = lhs_field.value; + switch (cel_StructValueKey_Kind(&lhs_field_key)) { + case cel_StructValueKeyKind_kName: + lhs_field_ptr->key = cel_StructValueKey_GetName(&lhs_field_key); + break; + case cel_StructValueKeyKind_kDef: + lhs_field_ptr->key = cel_StringView_FromString( + upb_FieldDef_Name(cel_StructValueKey_GetDef(&lhs_field_key))); + break; + default: + CEL_UNREACHABLE(); + } + } + } + if (rhs_next) { + cel_StructValueKey rhs_field_key; + _cel_StructValueField rhs_field; + rhs_next = cel_StructValueIterator_Next(rhs_iter, context, &rhs_field_key, + &rhs_field.value, status); + if (!rhs_next && !cel_Status_Ok(status)) { + ok = false; + break; + } + if (rhs_next) { + _cel_StructValueField* rhs_field_ptr = + _cel_Array_Push(&rhs_fields, context->alloc); + if (CEL_UNLIKELY(rhs_field_ptr == cel_nullptr)) { + cel_OutOfMemoryStatus(status); + ok = false; + break; + } + rhs_field_ptr->value = rhs_field.value; + switch (cel_StructValueKey_Kind(&rhs_field_key)) { + case cel_StructValueKeyKind_kName: + rhs_field_ptr->key = cel_StructValueKey_GetName(&rhs_field_key); + break; + case cel_StructValueKeyKind_kDef: + rhs_field_ptr->key = cel_StringView_FromString( + upb_FieldDef_Name(cel_StructValueKey_GetDef(&rhs_field_key))); + break; + default: + CEL_UNREACHABLE(); + } + } + } + if (!lhs_next || !rhs_next) { + break; + } + } + if (ok) { + if (lhs_next || rhs_next) { + cel_Value_SetFalse(result); + } else { + const size_t lhs_fields_size = _cel_Array_Size(&lhs_fields); + const size_t rhs_fields_size = _cel_Array_Size(&rhs_fields); + if (lhs_fields_size != rhs_fields_size) { + cel_Value_SetFalse(result); + } else { + _cel_StructValueField* lhs_fields_data = + _cel_Array_MutableData(&lhs_fields); + _cel_StructValueField* rhs_fields_data = + _cel_Array_MutableData(&rhs_fields); + _cel_Sort(lhs_fields_data, lhs_fields_size, + sizeof(_cel_StructValueField), + &_cel_StructValueField_Compare); + _cel_Sort(rhs_fields_data, rhs_fields_size, + sizeof(_cel_StructValueField), + &_cel_StructValueField_Compare); + for (size_t i = 0; i < lhs_fields_size; ++i) { + if (!cel_StringView_Equals(lhs_fields_data[i].key, + rhs_fields_data[i].key)) { + cel_Value_SetFalse(result); + goto done; + } + if (!cel_Value_Equals(&lhs_fields_data[i].value, context, + &rhs_fields_data[i].value, result, status)) { + ok = false; + goto done; + } + if (!cel_Value_IsTrue(result)) { + goto done; + } + } + cel_Value_SetTrue(result); + } + } + } +done: + _cel_Array_Destruct(&rhs_fields, context->alloc); + _cel_Array_Destruct(&lhs_fields, context->alloc); + cel_StructValueIterator_Delete(rhs_iter); + cel_StructValueIterator_Delete(lhs_iter); + return ok; +} + +extern "C" bool cel_StructValue_Equals( + const cel_StructValue* cel_nonnull struct_value, + const cel_ValueContext* cel_nonnull context, + const cel_StructValue* cel_nonnull other, cel_Value* cel_nonnull result, + cel_Status* cel_nonnull status) { + CEL_ASSERT_NOT_NULL(struct_value); + CEL_ASSERT_NOT_NULL(context); + CEL_ASSERT_NOT_NULL(other); + CEL_ASSERT_NOT_NULL(result); + CEL_ASSERT_NOT_NULL(status); + + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return false; + } + + if (cel_StringView_Equals(cel_StructValue_TypeName(struct_value), + cel_StructValue_TypeName(other))) { + if (struct_value->vtable->Equals != cel_nullptr) { + if ((*struct_value->vtable->Equals)(struct_value->vtable, + struct_value->content, context, other, + result, status)) { + return true; + } + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return false; + } + } + if (other->vtable->Equals != cel_nullptr && + other->vtable->Equals != struct_value->vtable->Equals) { + if ((*other->vtable->Equals)(other->vtable, other->content, context, + struct_value, result, status)) { + return true; + } + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return false; + } + } + return _cel_StructValue_EqualsSlow(struct_value, context, other, result, + status); + } + + cel_Value_SetFalse(result); + return true; +} diff --git a/cel-c/src/utf8.cc b/cel-c/src/utf8.cc new file mode 100644 index 0000000..b5c9856 --- /dev/null +++ b/cel-c/src/utf8.cc @@ -0,0 +1,230 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "cel-c/src/utf8.h" + +#include // IWYU pragma: keep +#include +#include + +#include "cel-c/assert.h" +#include "cel-c/config.h" +#include "cel-c/src/uchar.h" // IWYU pragma: keep +#include "cel-c/src/unicode.h" +#include "upb/base/string_view.h" + +#define _cel_kUtf8RuneSelf 0x80 + +#define _cel_kUtf8Low 0x80 +#define _cel_kUtf8High 0xbf + +#define _cel_kUtf8MaskX 0x3f +#define _cel_kUtf8Mask2 0x1f +#define _cel_kUtf8Mask3 0xf +#define _cel_kUtf8Mask4 0x7 + +#define _cel_kUtf8TX 0x80 +#define _cel_kUtf8T2 0xc0 +#define _cel_kUtf8T3 0xe0 +#define _cel_kUtf8T4 0xf0 + +#define _cel_kUtf8XX 0xf1 +#define _cel_kUtf8AS 0xf0 +#define _cel_kUtf8S1 0x02 +#define _cel_kUtf8S2 0x13 +#define _cel_kUtf8S3 0x03 +#define _cel_kUtf8S4 0x23 +#define _cel_kUtf8S5 0x34 +#define _cel_kUtf8S6 0x04 +#define _cel_kUtf8S7 0x44 + +// NOLINTBEGIN +// clang-format off +static const uint8_t _cel_kUtf8Leading[256] = { + _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, // 0x00-0x0F + _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, // 0x10-0x1F + _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, // 0x20-0x2F + _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, // 0x30-0x3F + _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, // 0x40-0x4F + _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, // 0x50-0x5F + _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, // 0x60-0x6F + _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, _cel_kUtf8AS, // 0x70-0x7F + _cel_kUtf8XX, _cel_kUtf8XX, _cel_kUtf8XX, _cel_kUtf8XX, _cel_kUtf8XX, _cel_kUtf8XX, _cel_kUtf8XX, _cel_kUtf8XX, _cel_kUtf8XX, _cel_kUtf8XX, _cel_kUtf8XX, _cel_kUtf8XX, _cel_kUtf8XX, _cel_kUtf8XX, _cel_kUtf8XX, _cel_kUtf8XX, // 0x80-0x8F + _cel_kUtf8XX, _cel_kUtf8XX, _cel_kUtf8XX, _cel_kUtf8XX, _cel_kUtf8XX, _cel_kUtf8XX, _cel_kUtf8XX, _cel_kUtf8XX, _cel_kUtf8XX, _cel_kUtf8XX, _cel_kUtf8XX, _cel_kUtf8XX, _cel_kUtf8XX, _cel_kUtf8XX, _cel_kUtf8XX, _cel_kUtf8XX, // 0x90-0x9F + _cel_kUtf8XX, _cel_kUtf8XX, _cel_kUtf8XX, _cel_kUtf8XX, _cel_kUtf8XX, _cel_kUtf8XX, _cel_kUtf8XX, _cel_kUtf8XX, _cel_kUtf8XX, _cel_kUtf8XX, _cel_kUtf8XX, _cel_kUtf8XX, _cel_kUtf8XX, _cel_kUtf8XX, _cel_kUtf8XX, _cel_kUtf8XX, // 0xA0-0xAF + _cel_kUtf8XX, _cel_kUtf8XX, _cel_kUtf8XX, _cel_kUtf8XX, _cel_kUtf8XX, _cel_kUtf8XX, _cel_kUtf8XX, _cel_kUtf8XX, _cel_kUtf8XX, _cel_kUtf8XX, _cel_kUtf8XX, _cel_kUtf8XX, _cel_kUtf8XX, _cel_kUtf8XX, _cel_kUtf8XX, _cel_kUtf8XX, // 0xB0-0xBF + _cel_kUtf8XX, _cel_kUtf8XX, _cel_kUtf8S1, _cel_kUtf8S1, _cel_kUtf8S1, _cel_kUtf8S1, _cel_kUtf8S1, _cel_kUtf8S1, _cel_kUtf8S1, _cel_kUtf8S1, _cel_kUtf8S1, _cel_kUtf8S1, _cel_kUtf8S1, _cel_kUtf8S1, _cel_kUtf8S1, _cel_kUtf8S1, // 0xC0-0xCF + _cel_kUtf8S1, _cel_kUtf8S1, _cel_kUtf8S1, _cel_kUtf8S1, _cel_kUtf8S1, _cel_kUtf8S1, _cel_kUtf8S1, _cel_kUtf8S1, _cel_kUtf8S1, _cel_kUtf8S1, _cel_kUtf8S1, _cel_kUtf8S1, _cel_kUtf8S1, _cel_kUtf8S1, _cel_kUtf8S1, _cel_kUtf8S1, // 0xD0-0xDF + _cel_kUtf8S2, _cel_kUtf8S3, _cel_kUtf8S3, _cel_kUtf8S3, _cel_kUtf8S3, _cel_kUtf8S3, _cel_kUtf8S3, _cel_kUtf8S3, _cel_kUtf8S3, _cel_kUtf8S3, _cel_kUtf8S3, _cel_kUtf8S3, _cel_kUtf8S3, _cel_kUtf8S4, _cel_kUtf8S3, _cel_kUtf8S3, // 0xE0-0xEF + _cel_kUtf8S5, _cel_kUtf8S6, _cel_kUtf8S6, _cel_kUtf8S6, _cel_kUtf8S7, _cel_kUtf8XX, _cel_kUtf8XX, _cel_kUtf8XX, _cel_kUtf8XX, _cel_kUtf8XX, _cel_kUtf8XX, _cel_kUtf8XX, _cel_kUtf8XX, _cel_kUtf8XX, _cel_kUtf8XX, _cel_kUtf8XX, // 0xF0-0xFF +}; +// clang-format on +// NOLINTEND + +static const uint8_t _cel_kUtf8Accept[32] = { + _cel_kUtf8Low, _cel_kUtf8High, + 0xa0, _cel_kUtf8High, + _cel_kUtf8Low, 0x9f, + 0x90, _cel_kUtf8High, + _cel_kUtf8Low, 0x8f, + 0x0, 0x0, + 0x0, 0x0, + 0x0, 0x0, + 0x0, 0x0, + 0x0, 0x0, + 0x0, 0x0, + 0x0, 0x0, + 0x0, 0x0, + 0x0, 0x0, + 0x0, 0x0, + 0x0, 0x0, +}; + +static void _cel_Utf8_DecodeResult(char32_t pnt, size_t units, + CEL_NULLABLE(char32_t*) out_pnt, + CEL_NULLABLE(size_t*) out_units) { + if (out_pnt != cel_nullptr) { + *out_pnt = pnt; + } + if (out_units != cel_nullptr) { + *out_units = units; + } +} + +static void _cel_Utf8_DecodeFailed(CEL_NULLABLE(char32_t*) pnt, + CEL_NULLABLE(size_t*) units) { + _cel_Utf8_DecodeResult(_cel_Unicode_kReplacementChar, 1, pnt, units); +} + +extern "C" void _cel_Utf8_Decode(upb_StringView str, + CEL_NULLABLE(char32_t*) pnt, + CEL_NULLABLE(size_t*) units) { + CEL_ASSERT_NOT_NULL(str.data); + CEL_ASSERT_GT(str.size, 0); + CEL_ASSERT(pnt != cel_nullptr || units != cel_nullptr); + CEL_NONNULL(const char*) data = str.data; + size_t len = str.size; + const uint8_t b = (uint8_t)(*data++); + --len; + if (b < _cel_kUtf8RuneSelf) { + _cel_Utf8_DecodeResult(b, 1, pnt, units); + return; + } + const uint8_t leading = _cel_kUtf8Leading[b]; + if (CEL_UNLIKELY(leading == _cel_kUtf8XX)) { + _cel_Utf8_DecodeFailed(pnt, units); + return; + } + const size_t size = ((size_t)(leading & 7)) - 1; + CEL_ASSERT(size >= 1 && size <= 3); + if (CEL_UNLIKELY(size > len)) { + _cel_Utf8_DecodeFailed(pnt, units); + return; + } + const uint8_t* const accept = &_cel_kUtf8Accept[(leading >> 4) * 2]; + const uint8_t b1 = (uint8_t)(*data++); + if (CEL_UNLIKELY(b1 < accept[0] || b1 > accept[1])) { + _cel_Utf8_DecodeFailed(pnt, units); + return; + } + if (size == 1) { + _cel_Utf8_DecodeResult((((char32_t)(b & _cel_kUtf8Mask2)) << 6) | + ((char32_t)(b1 & _cel_kUtf8MaskX)), + 2, pnt, units); + return; + } + const uint8_t b2 = (uint8_t)(*data++); + if (CEL_UNLIKELY(b2 < _cel_kUtf8Low || b2 > _cel_kUtf8High)) { + _cel_Utf8_DecodeFailed(pnt, units); + return; + } + if (size == 2) { + _cel_Utf8_DecodeResult((((char32_t)(b & _cel_kUtf8Mask3)) << 12) | + (((char32_t)(b1 & _cel_kUtf8MaskX)) << 6) | + ((char32_t)(b2 & _cel_kUtf8MaskX)), + 3, pnt, units); + return; + } + const uint8_t b3 = (uint8_t)(*data++); + if (CEL_UNLIKELY(b3 < _cel_kUtf8Low || b3 > _cel_kUtf8High)) { + _cel_Utf8_DecodeFailed(pnt, units); + return; + } + CEL_ASSERT_EQ(size, 3); + _cel_Utf8_DecodeResult((((char32_t)(b & _cel_kUtf8Mask4)) << 18) | + (((char32_t)(b1 & _cel_kUtf8MaskX)) << 12) | + (((char32_t)(b2 & _cel_kUtf8MaskX)) << 6) | + ((char32_t)(b3 & _cel_kUtf8MaskX)), + 4, pnt, units); +} + +extern "C" size_t _cel_Utf8_Encode(char32_t pnt, CEL_NONNULL(char*) str, + size_t len) { + CEL_ASSERT_NOT_NULL(str); + CEL_ASSERT_GT(len, 0); + if (CEL_UNLIKELY(!_cel_Unicode_IsValid(pnt))) { + pnt = _cel_Unicode_kReplacementChar; + } + size_t units; + if (pnt <= 0x7f) { + CEL_ASSERT_GE(len, 1); + *str = (char)((uint8_t)pnt); + units = 1; + } else if (pnt <= 0x7ff) { + CEL_ASSERT_GE(len, 2); + *str++ = (char)(_cel_kUtf8T2 | ((uint8_t)(pnt >> 6))); + *str = (char)(_cel_kUtf8TX | (((uint8_t)pnt) & _cel_kUtf8MaskX)); + units = 2; + } else if (pnt <= 0xffff) { + CEL_ASSERT_GE(len, 3); + *str++ = (char)(_cel_kUtf8T3 | ((uint8_t)(pnt >> 12))); + *str++ = (char)(_cel_kUtf8TX | (((uint8_t)(pnt >> 6)) & _cel_kUtf8MaskX)); + *str = (char)(_cel_kUtf8TX | (((uint8_t)pnt) & _cel_kUtf8MaskX)); + units = 3; + } else { + CEL_ASSERT_GE(len, 4); + *str++ = (char)(_cel_kUtf8T4 | ((uint8_t)(pnt >> 18))); + *str++ = (char)(_cel_kUtf8TX | (((uint8_t)(pnt >> 12)) & _cel_kUtf8MaskX)); + *str++ = (char)(_cel_kUtf8TX | (((uint8_t)(pnt >> 6)) & _cel_kUtf8MaskX)); + *str = (char)(_cel_kUtf8TX | (((uint8_t)pnt) & _cel_kUtf8MaskX)); + units = 4; + } + return units; +} + +extern "C" size_t _cel_Utf8_DecodedSize(upb_StringView str) { + size_t pnts = 0; + size_t units; + while (str.size > 0) { + _cel_Utf8_Decode(str, cel_nullptr, &units); + ++pnts; + str.data += units; + str.size -= units; + } + return pnts; +} + +extern "C" bool _cel_Utf8_IsValid(upb_StringView str) { + char32_t pnt; + size_t units; + while (str.size > 0) { + _cel_Utf8_Decode(str, &pnt, &units); + if (pnt == _cel_Unicode_kReplacementChar && units == 1) { + return false; + } + str.data += units; + str.size -= units; + } + return true; +} diff --git a/cel-c/src/value.cc b/cel-c/src/value.cc new file mode 100644 index 0000000..5323eab --- /dev/null +++ b/cel-c/src/value.cc @@ -0,0 +1,730 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "cel-c/value.h" + +#include +#include +#include + +#include "cel-c/assert.h" +#include "cel-c/config.h" +#include "cel-c/duration.h" +#include "cel-c/error.h" +#include "cel-c/error_code.h" +#include "cel-c/src/any.h" +#include "cel-c/src/parsed_map_field_value.h" +#include "cel-c/src/parsed_message_value.h" +#include "cel-c/src/parsed_repeated_field_value.h" +#include "cel-c/status.h" +#include "cel-c/string_view.h" +#include "cel-c/timestamp.h" +#include "cel-c/type.h" +#include "cel-c/value_kind.h" +#include "upb/base/descriptor_constants.h" +#include "upb/message/array.h" +#include "upb/message/message.h" +#include "upb/reflection/def.h" +#include "upb/reflection/message.h" + +#define _cel_kDoubleToIntMax ((double)(int64_t)INT64_MAX) +#define _cel_kDoubleToIntMin ((double)(int64_t)INT64_MIN) +#define _cel_kDoubleToUintMin ((double)(uint64_t)0) +#define _cel_kDoubleToUintMax ((double)(uint64_t)UINT64_MAX) + +extern "C" const cel_Value cel_NullValue = { + .data = + { + .v = {(char)0}, + }, + .kind = cel_ValueKind_kNull, + .padding = {(char)0}, +}; + +extern "C" const cel_Value cel_FalseValue = { + .data = + { + .bl = false, + }, + .kind = cel_ValueKind_kBool, + .padding = {(char)0}, +}; + +extern "C" const cel_Value cel_TrueValue = { + .data = + { + .bl = true, + }, + .kind = cel_ValueKind_kBool, + .padding = {(char)0}, +}; + +extern "C" cel_StringView cel_Value_TypeName( + const cel_Value* cel_nonnull value) { + CEL_ASSERT_NOT(cel_Value_IsError(value)); + CEL_ASSERT_NOT(cel_Value_IsUnknown(value)); + + switch (cel_Value_Kind(value)) { + case cel_ValueKind_kNull: + return cel_StringView_From("null_type"); + case cel_ValueKind_kBool: + return cel_StringView_From("bool"); + case cel_ValueKind_kInt: + return cel_StringView_From("int"); + case cel_ValueKind_kUint: + return cel_StringView_From("uint"); + case cel_ValueKind_kDouble: + return cel_StringView_From("double"); + case cel_ValueKind_kString: + return cel_StringView_From("string"); + case cel_ValueKind_kBytes: + return cel_StringView_From("bytes"); + case cel_ValueKind_kDuration: + return cel_StringView_From("google.protobuf.Duration"); + case cel_ValueKind_kTimestamp: + return cel_StringView_From("google.protobuf.Timestamp"); + case cel_ValueKind_kList: + return cel_StringView_From("list"); + case cel_ValueKind_kMap: + return cel_StringView_From("map"); + case cel_ValueKind_kStruct: + return cel_StructValue_TypeName(cel_Value_GetStruct(value)); + case cel_ValueKind_kOpaque: + return cel_OpaqueValue_TypeName(cel_Value_GetOpaque(value)); + case cel_ValueKind_kType: + return cel_StringView_From("type"); + default: + return cel_StringView_From(""); + } +} + +extern "C" bool cel_Value_Equals(const cel_Value* cel_nonnull value, + const cel_ValueContext* cel_nonnull context, + const cel_Value* cel_nonnull other, + cel_Value* cel_nonnull result, + cel_Status* cel_nonnull status) { + CEL_ASSERT_NOT_NULL(value); + CEL_ASSERT_NOT_NULL(context); + CEL_ASSERT_NOT_NULL(other); + CEL_ASSERT_NOT_NULL(result); + CEL_ASSERT_NOT_NULL(status); + + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return false; + } + + const cel_ValueKind value_kind = cel_Value_Kind(value); + const cel_ValueKind other_kind = cel_Value_Kind(other); + + if (value_kind == cel_ValueKind_kError) { + *result = *value; + return true; + } + + if (other_kind == cel_ValueKind_kError) { + *result = *other; + return true; + } + + if (value == other) { + cel_Value_SetTrue(result); + return true; + } + + if (value_kind != other_kind) { + { + const bool null_value = value_kind == cel_ValueKind_kNull; + const bool other_null_value = other_kind == cel_ValueKind_kNull; + if (null_value || other_null_value) { + cel_Value_SetBool(result, null_value == other_null_value); + return true; + } + } + // Heterogeneous + switch (value_kind) { + case cel_ValueKind_kInt: + switch (other_kind) { + case cel_ValueKind_kUint: { + const int64_t int_value = cel_Value_GetInt(value); + cel_Value_SetBool( + result, int_value >= 0 && + int_value == (int64_t)cel_Value_GetUint(other)); + } break; + case cel_ValueKind_kDouble: { + const double double_value = cel_Value_GetDouble(other); + cel_Value_SetBool( + result, double_value >= _cel_kDoubleToIntMin && + double_value <= _cel_kDoubleToIntMax && + double_value == (double)cel_Value_GetInt(value)); + } break; + default: + cel_Value_SetFalse(result); + break; + } + break; + case cel_ValueKind_kUint: + switch (other_kind) { + case cel_ValueKind_kInt: { + const int64_t int_value = cel_Value_GetInt(other); + cel_Value_SetBool( + result, int_value >= 0 && + int_value == (int64_t)cel_Value_GetUint(value)); + } break; + case cel_ValueKind_kDouble: { + const double double_value = cel_Value_GetDouble(other); + cel_Value_SetBool( + result, double_value >= _cel_kDoubleToUintMin && + double_value <= _cel_kDoubleToUintMax && + double_value == (double)cel_Value_GetUint(value)); + } break; + default: + cel_Value_SetFalse(result); + break; + } + break; + case cel_ValueKind_kDouble: + switch (other_kind) { + case cel_ValueKind_kInt: { + const double double_value = cel_Value_GetDouble(value); + cel_Value_SetBool( + result, double_value >= _cel_kDoubleToIntMin && + double_value <= _cel_kDoubleToIntMax && + double_value == (double)cel_Value_GetInt(other)); + } break; + case cel_ValueKind_kUint: { + const double double_value = cel_Value_GetDouble(value); + cel_Value_SetBool( + result, double_value >= _cel_kDoubleToUintMin && + double_value <= _cel_kDoubleToUintMax && + double_value == (double)cel_Value_GetUint(other)); + } break; + default: + cel_Value_SetFalse(result); + break; + } + break; + default: + cel_Value_SetFalse(result); + break; + } + return true; + } + + // Homogeneous + switch (value_kind) { + case cel_ValueKind_kNull: + cel_Value_SetTrue(result); + break; + case cel_ValueKind_kBool: + cel_Value_SetBool(result, + cel_Value_GetBool(value) == cel_Value_GetBool(other)); + break; + case cel_ValueKind_kInt: + cel_Value_SetBool(result, + cel_Value_GetInt(value) == cel_Value_GetInt(other)); + break; + case cel_ValueKind_kUint: + cel_Value_SetBool(result, + cel_Value_GetUint(value) == cel_Value_GetUint(other)); + break; + case cel_ValueKind_kDouble: + cel_Value_SetBool( + result, cel_Value_GetDouble(value) == cel_Value_GetDouble(other)); + break; + case cel_ValueKind_kString: + cel_Value_SetBool(result, + cel_StringView_Equals(cel_Value_GetString(value), + cel_Value_GetString(other))); + break; + case cel_ValueKind_kBytes: + cel_Value_SetBool(result, + cel_StringView_Equals(cel_Value_GetBytes(value), + cel_Value_GetBytes(other))); + break; + case cel_ValueKind_kDuration: + cel_Value_SetBool(result, + cel_Duration_Equals(cel_Value_GetDuration(value), + cel_Value_GetDuration(other))); + break; + case cel_ValueKind_kTimestamp: + cel_Value_SetBool(result, + cel_Timestamp_Equals(cel_Value_GetTimestamp(value), + cel_Value_GetTimestamp(other))); + break; + case cel_ValueKind_kList: + return cel_ListValue_Equals(cel_Value_GetList(value), context, + cel_Value_GetList(other), result, status); + case cel_ValueKind_kMap: + return cel_MapValue_Equals(cel_Value_GetMap(value), context, + cel_Value_GetMap(other), result, status); + case cel_ValueKind_kStruct: + return cel_StructValue_Equals(cel_Value_GetStruct(value), context, + cel_Value_GetStruct(other), result, status); + case cel_ValueKind_kOpaque: + return cel_OpaqueValue_Equals(cel_Value_GetOpaque(value), context, + cel_Value_GetOpaque(other), result, status); + case cel_ValueKind_kType: + cel_Value_SetBool(result, + cel_StringView_Equals(cel_Value_GetType(value), + cel_Value_GetType(other))); + break; + case cel_ValueKind_kUnknown: + CEL_ATTRIBUTE_FALLTHROUGH; + default: + cel_Value_SetFalse(result); + break; + } + return true; +} + +extern "C" bool cel_Value_FromMessage( + cel_Value* cel_nonnull value, const cel_ValueContext* cel_nonnull context, + const upb_Message* message_val, + const upb_MessageDef* cel_nonnull message_def, + cel_Status* cel_nonnull status) { + CEL_ASSERT_NOT_NULL(value); + CEL_ASSERT_NOT_NULL(context); + CEL_ASSERT_NOT_NULL(message_val); + CEL_ASSERT_NOT_NULL(message_def); + CEL_ASSERT_NOT_NULL(status); + + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return false; + } + + upb_WellKnown well_known_type = upb_MessageDef_WellKnownType(message_def); + if (well_known_type == kUpb_WellKnown_Any) { + CEL_ASSERT_EQ(message_def, context->well_known_types->any.def); + upb_Message* out_message_val; + const upb_MessageDef* out_message_def; + _cel_AnyUnpackResult result = _cel_AnyUnpack( + message_val, context->def_pool, &context->well_known_types->any, + context->arena, &out_message_val, &out_message_def); + if (result != _cel_AnyUnpackResult_kOk) { + if (result == _cel_AnyUnpackResult_kOutOfMemory) { + cel_OutOfMemoryStatus(status); + return false; + } + cel_Error* error = cel_Error_New(context->arena); + if (CEL_UNLIKELY(error == cel_nullptr)) { + cel_OutOfMemoryStatus(status); + return false; + } + cel_Error_SetCanonicalCode( + error, (cel_ErrorCode)_cel_AnyUnpackResult_ToStatusCode(result)); + cel_Error_SetMessage(error, cel_StringView_FromString( + _cel_AnyUnpackResult_ToMessage(result))); + cel_Value_SetError(value, error); + return true; + } + message_val = out_message_val; + message_def = out_message_def; + well_known_type = upb_MessageDef_WellKnownType(out_message_def); + } + + switch (well_known_type) { + case kUpb_WellKnown_Duration: { + CEL_ASSERT_EQ(message_def, context->well_known_types->duration.def); + int64_t seconds = + upb_Message_GetFieldByDef( + message_val, context->well_known_types->duration.seconds_def) + .int64_val; + int32_t nanos = + upb_Message_GetFieldByDef( + message_val, context->well_known_types->duration.nanos_def) + .int32_val; + if (cel_Duration_Normalize(&seconds, &nanos)) { + cel_Value_SetDuration(value, cel_Duration_FromUnix(seconds, nanos)); + } else { + cel_Error* error = cel_Error_New(context->arena); + if (CEL_UNLIKELY(error == cel_nullptr)) { + cel_OutOfMemoryStatus(status); + return false; + } + cel_Error_SetCanonicalCode(error, cel_ErrorCode_kOutOfRange); + cel_Error_SetMessage(error, + cel_StringView_From("duration out of range")); + cel_Value_SetError(value, error); + } + return true; + } + case kUpb_WellKnown_Timestamp: { + CEL_ASSERT_EQ(message_def, context->well_known_types->timestamp.def); + int64_t seconds = + upb_Message_GetFieldByDef( + message_val, context->well_known_types->timestamp.seconds_def) + .int64_val; + int32_t nanos = + upb_Message_GetFieldByDef( + message_val, context->well_known_types->timestamp.nanos_def) + .int32_val; + if (cel_Timestamp_Normalize(&seconds, &nanos)) { + cel_Value_SetTimestamp(value, cel_Timestamp_FromUnix(seconds, nanos)); + } else { + cel_Error* error = cel_Error_New(context->arena); + if (CEL_UNLIKELY(error == cel_nullptr)) { + cel_OutOfMemoryStatus(status); + return false; + } + cel_Error_SetCanonicalCode(error, cel_ErrorCode_kOutOfRange); + cel_Error_SetMessage(error, + cel_StringView_From("timestamp out of range")); + cel_Value_SetError(value, error); + } + return true; + } + case kUpb_WellKnown_DoubleValue: + CEL_ASSERT_EQ(message_def, context->well_known_types->double_value.def); + cel_Value_SetDouble( + value, + upb_Message_GetFieldByDef( + message_val, context->well_known_types->double_value.value_def) + .double_val); + return true; + case kUpb_WellKnown_FloatValue: + CEL_ASSERT_EQ(message_def, context->well_known_types->float_value.def); + cel_Value_SetDouble( + value, + upb_Message_GetFieldByDef( + message_val, context->well_known_types->float_value.value_def) + .float_val); + return true; + case kUpb_WellKnown_Int64Value: + CEL_ASSERT_EQ(message_def, context->well_known_types->int64_value.def); + cel_Value_SetInt( + value, + upb_Message_GetFieldByDef( + message_val, context->well_known_types->int64_value.value_def) + .int64_val); + return true; + case kUpb_WellKnown_UInt64Value: + CEL_ASSERT_EQ(message_def, context->well_known_types->uint64_value.def); + cel_Value_SetUint( + value, + upb_Message_GetFieldByDef( + message_val, context->well_known_types->uint64_value.value_def) + .uint64_val); + return true; + case kUpb_WellKnown_Int32Value: + CEL_ASSERT_EQ(message_def, context->well_known_types->int32_value.def); + cel_Value_SetInt( + value, + upb_Message_GetFieldByDef( + message_val, context->well_known_types->int32_value.value_def) + .int32_val); + return true; + case kUpb_WellKnown_UInt32Value: + CEL_ASSERT_EQ(message_def, context->well_known_types->uint32_value.def); + cel_Value_SetUint( + value, + upb_Message_GetFieldByDef( + message_val, context->well_known_types->uint32_value.value_def) + .uint32_val); + return true; + case kUpb_WellKnown_StringValue: + CEL_ASSERT_EQ(message_def, context->well_known_types->string_value.def); + cel_Value_SetString( + value, + upb_Message_GetFieldByDef( + message_val, context->well_known_types->string_value.value_def) + .str_val); + return true; + case kUpb_WellKnown_BytesValue: + CEL_ASSERT_EQ(message_def, context->well_known_types->bytes_value.def); + cel_Value_SetBytes( + value, + upb_Message_GetFieldByDef( + message_val, context->well_known_types->bytes_value.value_def) + .str_val); + return true; + case kUpb_WellKnown_BoolValue: + CEL_ASSERT_EQ(message_def, context->well_known_types->bool_value.def); + cel_Value_SetBool( + value, + upb_Message_GetFieldByDef( + message_val, context->well_known_types->bool_value.value_def) + .bool_val); + return true; + case kUpb_WellKnown_Value: { + CEL_ASSERT_EQ(message_def, context->well_known_types->value.def); + const upb_FieldDef* field_def = upb_Message_WhichOneofByDef( + message_val, context->well_known_types->value.kind_def); + if (field_def == cel_nullptr) { + cel_Value_SetNull(value); + return true; + } + switch (upb_FieldDef_Number(field_def)) { + case 1: // null_value + CEL_ASSERT_EQ(field_def, + context->well_known_types->value.null_value_def); + cel_Value_SetNull(value); + return true; + case 2: // number_value + CEL_ASSERT_EQ(field_def, + context->well_known_types->value.number_value_def); + cel_Value_SetDouble( + value, + upb_Message_GetFieldByDef(message_val, field_def).double_val); + return true; + case 3: // string_value + CEL_ASSERT_EQ(field_def, + context->well_known_types->value.string_value_def); + cel_Value_SetString( + value, upb_Message_GetFieldByDef(message_val, field_def).str_val); + return true; + case 4: // bool_value + CEL_ASSERT_EQ(field_def, + context->well_known_types->value.bool_value_def); + cel_Value_SetBool( + value, + upb_Message_GetFieldByDef(message_val, field_def).bool_val); + return true; + case 5: // struct_value + CEL_ASSERT_EQ(field_def, + context->well_known_types->value.struct_value_def); + _cel_ParsedMapFieldValue_Set( + cel_Value_SetMap(value), + upb_Message_GetFieldByDef( + upb_Message_GetFieldByDef(message_val, field_def).msg_val, + context->well_known_types->struct_value.fields_def) + .map_val, + context->well_known_types->struct_value.fields_def); + return true; + case 6: // list_value + CEL_ASSERT_EQ(field_def, + context->well_known_types->value.list_value_def); + _cel_ParsedRepeatedFieldValue_Set( + cel_Value_SetList(value), + upb_Message_GetFieldByDef( + upb_Message_GetFieldByDef(message_val, field_def).msg_val, + context->well_known_types->list_value.values_def) + .array_val, + context->well_known_types->list_value.values_def); + return true; + default: + CEL_UNREACHABLE(); + } + } + case kUpb_WellKnown_ListValue: + CEL_ASSERT_EQ(message_def, context->well_known_types->list_value.def); + _cel_ParsedRepeatedFieldValue_Set( + cel_Value_SetList(value), + upb_Message_GetFieldByDef( + message_val, context->well_known_types->list_value.values_def) + .array_val, + context->well_known_types->list_value.values_def); + return true; + case kUpb_WellKnown_Struct: + CEL_ASSERT_EQ(message_def, context->well_known_types->struct_value.def); + _cel_ParsedMapFieldValue_Set( + cel_Value_SetMap(value), + upb_Message_GetFieldByDef( + message_val, context->well_known_types->struct_value.fields_def) + .map_val, + context->well_known_types->struct_value.fields_def); + return true; + default: + _cel_ParsedMessageValue_Set(cel_Value_SetStruct(value), message_val, + message_def); + return true; + } +} + +extern "C" bool cel_Value_FromEnum(cel_Value* cel_nonnull value, + const cel_ValueContext* cel_nonnull context, + int32_t enum_val, + const upb_EnumDef* cel_nonnull enum_def, + cel_Status* cel_nonnull status) { + CEL_ASSERT_NOT_NULL(value); + CEL_ASSERT_NOT_NULL(enum_def); + CEL_ASSERT_NOT_NULL(status); + + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return false; + } + + if (context->well_known_types->null_value.def == enum_def) { + cel_Value_SetNull(value); + return true; + } + CEL_ASSERT_NOT(cel_IsWellKnownEnumType(enum_def)); + + if (upb_EnumDef_IsClosed(enum_def)) { + const upb_EnumValueDef* enum_val_def = + upb_EnumDef_FindValueByNumber(enum_def, enum_val); + if (CEL_UNLIKELY(enum_val_def == cel_nullptr)) { + cel_Error* error = cel_Error_New(context->arena); + if (CEL_UNLIKELY(error == cel_nullptr)) { + cel_OutOfMemoryStatus(status); + return false; + } + cel_Error_SetCanonicalCode(error, cel_ErrorCode_kInvalidArgument); + cel_Error_SetMessage( + error, cel_StringView_From("cel: number not present in closed enum")); + cel_Value_SetError(value, error); + return true; + } + } + cel_Value_SetInt(value, enum_val); + return true; +} + +extern "C" bool cel_Value_FromEnumValue( + cel_Value* cel_nonnull value, const cel_ValueContext* cel_nonnull context, + const upb_EnumValueDef* enum_val, cel_Status* cel_nonnull status) { + CEL_ASSERT_NOT_NULL(value); + CEL_ASSERT_NOT_NULL(enum_val); + CEL_ASSERT_NOT_NULL(status); + + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return false; + } + + if (context->well_known_types->null_value.value_def == enum_val) { + cel_Value_SetNull(value); + } else { + CEL_ASSERT_NOT(cel_IsWellKnownEnumType(upb_EnumValueDef_Enum(enum_val))); + cel_Value_SetInt(value, upb_EnumValueDef_Number(enum_val)); + } + return true; +} + +CEL_ATTRIBUTE_NODISCARD +static bool _cel_Value_FromSingularField( + cel_Value* cel_nonnull value, const cel_ValueContext* cel_nonnull context, + upb_MessageValue field_val, const upb_FieldDef* cel_nonnull field_def, + cel_Status* cel_nonnull status) { + CEL_ASSERT_NOT(upb_FieldDef_IsMap(field_def)); + + switch (upb_FieldDef_CType(field_def)) { + case kUpb_CType_Bool: + cel_Value_SetBool(value, field_val.bool_val); + return true; + case kUpb_CType_Float: + cel_Value_SetDouble(value, field_val.float_val); + return true; + case kUpb_CType_Int32: + cel_Value_SetInt(value, field_val.int32_val); + return true; + case kUpb_CType_UInt32: + cel_Value_SetUint(value, field_val.uint32_val); + return true; + case kUpb_CType_Enum: + return cel_Value_FromEnum(value, context, field_val.int32_val, + upb_FieldDef_EnumSubDef(field_def), status); + case kUpb_CType_Message: + return cel_Value_FromMessage(value, context, field_val.msg_val, + upb_FieldDef_MessageSubDef(field_def), + status); + case kUpb_CType_Double: + cel_Value_SetDouble(value, field_val.double_val); + return true; + case kUpb_CType_Int64: + cel_Value_SetInt(value, field_val.int64_val); + return true; + case kUpb_CType_UInt64: + cel_Value_SetUint(value, field_val.uint64_val); + return true; + case kUpb_CType_String: + cel_Value_SetString(value, field_val.str_val); + return true; + case kUpb_CType_Bytes: + cel_Value_SetBytes(value, field_val.str_val); + return true; + } +} + +extern "C" bool cel_Value_FromField(cel_Value* cel_nonnull value, + const cel_ValueContext* cel_nonnull context, + upb_MessageValue field_val, + const upb_FieldDef* cel_nonnull field_def, + cel_Status* cel_nonnull status) { + CEL_ASSERT_NOT_NULL(value); + CEL_ASSERT_NOT_NULL(context); + CEL_ASSERT_NOT_NULL(field_def); + CEL_ASSERT_NOT_NULL(status); + + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return false; + } + + if (upb_FieldDef_IsMap(field_def)) { + _cel_ParsedMapFieldValue_Set(cel_Value_SetMap(value), field_val.map_val, + field_def); + return true; + } + + if (upb_FieldDef_IsRepeated(field_def)) { + _cel_ParsedRepeatedFieldValue_Set(cel_Value_SetList(value), + field_val.array_val, field_def); + return true; + } + + return _cel_Value_FromSingularField(value, context, field_val, field_def, + status); +} + +extern "C" bool cel_Value_FromRepeatedFieldElement( + cel_Value* cel_nonnull value, const cel_ValueContext* cel_nonnull context, + upb_MessageValue field_val, const upb_FieldDef* cel_nonnull field_def, + cel_Status* cel_nonnull status) { + CEL_ASSERT_NOT_NULL(value); + CEL_ASSERT_NOT_NULL(context); + CEL_ASSERT_NOT_NULL(field_def); + CEL_ASSERT(upb_FieldDef_IsRepeated(field_def)); + CEL_ASSERT_NOT_NULL(status); + + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return false; + } + + return _cel_Value_FromSingularField(value, context, field_val, field_def, + status); +} + +extern "C" bool cel_Value_FromMapFieldKey( + cel_Value* cel_nonnull value, const cel_ValueContext* cel_nonnull context, + upb_MessageValue field_val, const upb_FieldDef* cel_nonnull field_def, + cel_Status* cel_nonnull status) { + CEL_ASSERT_NOT_NULL(value); + CEL_ASSERT_NOT_NULL(context); + CEL_ASSERT_NOT_NULL(field_def); + CEL_ASSERT_NOT(upb_FieldDef_IsRepeated(field_def)); + CEL_ASSERT_NOT_NULL(status); + + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return false; + } + + return _cel_Value_FromSingularField(value, context, field_val, field_def, + status); +} + +extern "C" bool cel_Value_FromMapFieldValue( + cel_Value* cel_nonnull value, const cel_ValueContext* cel_nonnull context, + upb_MessageValue field_val, const upb_FieldDef* cel_nonnull field_def, + cel_Status* cel_nonnull status) { + CEL_ASSERT_NOT_NULL(value); + CEL_ASSERT_NOT_NULL(context); + CEL_ASSERT_NOT_NULL(field_def); + CEL_ASSERT_NOT(upb_FieldDef_IsRepeated(field_def)); + CEL_ASSERT_NOT_NULL(status); + + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return false; + } + + return _cel_Value_FromSingularField(value, context, field_val, field_def, + status); +} diff --git a/cel-c/status.cc b/cel-c/status.cc new file mode 100644 index 0000000..07a8826 --- /dev/null +++ b/cel-c/status.cc @@ -0,0 +1,411 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "cel-c/status.h" + +#include +#include +#include +#include +#include +#include + +#include "cel-c/alloc.h" +#include "cel-c/assert.h" +#include "cel-c/config.h" +#include "cel-c/error_space.h" +#include "cel-c/src/array.h" +#include "cel-c/src/string.h" +#include "cel-c/status_code.h" +#include "cel-c/string_view.h" + +#undef cel_OutOfMemoryStatus +#undef cel_Status_SetMessage +#undef cel_Status_VFormatMessage +#undef cel_Status_FormatMessage +#undef cel_Status_Set +#undef cel_Status_SetPayload + +typedef struct { + _cel_String type_url; + _cel_String value; +} _cel_StatusRepPayload; + +typedef union { + CEL_ATTRIBUTE_MAYBE_UNUSED cel_Status pub; + struct { + // code **must** be first. + unsigned int code; + + int raw_code; + CEL_NONNULL(const cel_ErrorSpace*) space; + _cel_String message; + _cel_Array(_cel_StatusRepPayload) payloads; + CEL_NULLABLE(const char*) file; + int line; + }; +} _cel_StatusRep; + +CEL_STATIC_ASSERT(sizeof(_cel_StatusRep) == sizeof(cel_Status)); +CEL_STATIC_ASSERT(alignof(_cel_StatusRep) == alignof(cel_Status)); +CEL_STATIC_ASSERT(offsetof(_cel_StatusRep, code) == offsetof(cel_Status, code)); + +CEL_ATTRIBUTE_NODISCARD +static CEL_NONNULL(const _cel_StatusRep*) + _cel_Status_ConstRep(CEL_NONNULL(const cel_Status*) status) { + return (const _cel_StatusRep*)status; +} + +CEL_ATTRIBUTE_NODISCARD +static CEL_NONNULL(_cel_StatusRep*) + _cel_Status_MutableRep(CEL_NONNULL(cel_Status*) status) { + return (_cel_StatusRep*)status; +} + +#define _cel_Status_Rep(status) \ + (_Generic((status), \ + const cel_Status*: _cel_Status_ConstRep, \ + cel_Status*: _cel_Status_MutableRep)((status))) + +static void _cel_StatusRepPayload_Construct(CEL_NONNULL(_cel_StatusRepPayload*) + payload) { + _cel_String_Construct(&payload->type_url); + _cel_String_Construct(&payload->value); +} + +static void _cel_StatusRepPayload_Destruct(CEL_NONNULL(_cel_StatusRepPayload*) + payload) { + _cel_String_Destruct(&payload->value, cel_DefaultAllocator); + _cel_String_Destruct(&payload->type_url, cel_DefaultAllocator); +} + +static void _cel_StatusRep_ResetMessage(CEL_NONNULL(_cel_StatusRep*) + status_rep) { + _cel_String_Reset(&status_rep->message, cel_DefaultAllocator); +} + +static void _cel_StatusRep_ResetPayloads(CEL_NONNULL(_cel_StatusRep*) + status_rep) { + const size_t payloads_size = _cel_Array_Size(&status_rep->payloads); + for (size_t i = 0; i < payloads_size; ++i) { + _cel_StatusRepPayload_Destruct( + _cel_Array_MutableAt(&status_rep->payloads, i)); + } + _cel_Array_Reset(&status_rep->payloads, cel_DefaultAllocator); +} + +CEL_ATTRIBUTE_NODISCARD +static unsigned int _cel_StatusRep_EncodeCanonical(cel_StatusCode code, + bool oom) { + return (((unsigned int)code) << 1) | (oom ? 1u : 0u); +} + +extern "C" void cel_Status_Construct(CEL_NONNULL(cel_Status*) status) { + CEL_ASSERT_NOT_NULL(status); + + CEL_NONNULL(_cel_StatusRep*) status_rep = _cel_Status_Rep(status); + memset(status_rep, '\0', sizeof(*status_rep)); + status_rep->space = cel_CanonicalErrorSpace; + _cel_String_Construct(&status_rep->message); + _cel_Array_Construct(&status_rep->payloads); +} + +extern "C" void cel_Status_Destruct(CEL_NONNULL(cel_Status*) status) { + CEL_ASSERT_NOT_NULL(status); + + CEL_NONNULL(_cel_StatusRep*) status_rep = _cel_Status_Rep(status); + const size_t payloads_size = _cel_Array_Size(&status_rep->payloads); + for (size_t i = 0; i < payloads_size; ++i) { + _cel_StatusRepPayload_Destruct( + _cel_Array_MutableAt(&status_rep->payloads, i)); + } + _cel_Array_Destruct(&status_rep->payloads, cel_DefaultAllocator); + _cel_String_Destruct(&status_rep->message, cel_DefaultAllocator); +} + +extern "C" void cel_Status_Clear(CEL_NONNULL(cel_Status*) status) { + CEL_ASSERT_NOT_NULL(status); + + CEL_NONNULL(_cel_StatusRep*) status_rep = _cel_Status_Rep(status); + const size_t payloads_size = _cel_Array_Size(&status_rep->payloads); + for (size_t i = 0; i < payloads_size; ++i) { + _cel_StatusRepPayload_Destruct( + _cel_Array_MutableAt(&status_rep->payloads, i)); + } + _cel_Array_Clear(&status_rep->payloads); + _cel_String_Clear(&status_rep->message); + status_rep->code = + _cel_StatusRep_EncodeCanonical(cel_StatusCode_kOk, /*oom=*/false); + status_rep->space = cel_CanonicalErrorSpace; + status_rep->raw_code = cel_StatusCode_kOk; + status_rep->file = cel_nullptr; + status_rep->line = 0; +} + +extern "C" void cel_Status_Reset(CEL_NONNULL(cel_Status*) status) { + CEL_ASSERT_NOT_NULL(status); + + CEL_NONNULL(_cel_StatusRep*) status_rep = _cel_Status_Rep(status); + _cel_StatusRep_ResetMessage(status_rep); + _cel_StatusRep_ResetPayloads(status_rep); + status_rep->code = + _cel_StatusRep_EncodeCanonical(cel_StatusCode_kOk, /*oom=*/false); + status_rep->space = cel_CanonicalErrorSpace; + status_rep->raw_code = cel_StatusCode_kOk; + status_rep->file = cel_nullptr; + status_rep->line = 0; +} + +extern "C" CEL_NONNULL(const cel_ErrorSpace*) + cel_Status_Space(CEL_NONNULL(const cel_Status*) status) { + CEL_ASSERT_NOT_NULL(status); + + return _cel_Status_Rep(status)->space; +} + +extern "C" int cel_Status_Code(CEL_NONNULL(const cel_Status*) status) { + CEL_ASSERT_NOT_NULL(status); + + return _cel_Status_Rep(status)->raw_code; +} + +extern "C" cel_StringView cel_Status_Message(CEL_NONNULL(const cel_Status*) + status) { + CEL_ASSERT_NOT_NULL(status); + + return _cel_String_ToStringView(&_cel_Status_Rep(status)->message); +} + +extern "C" void cel_Status_SetCode(CEL_NONNULL(cel_Status*) status, + CEL_NONNULL(const cel_ErrorSpace*) space, + int code) { + CEL_ASSERT_NOT_NULL(status); + CEL_ASSERT_NOT_NULL(space); + + cel_StatusCode canonical_code = cel_ErrorSpace_Canonical(space, code); + bool oom; + if (canonical_code == cel_StatusCode_kOk) { + cel_Status_Reset(status); + oom = false; + } else { + oom = cel_ErrorSpace_OutOfMemory(space, code); + } + CEL_NONNULL(_cel_StatusRep*) status_rep = _cel_Status_Rep(status); + status_rep->code = _cel_StatusRep_EncodeCanonical(canonical_code, oom); + status_rep->space = space; + status_rep->raw_code = code; +} + +extern "C" bool cel_Status_SetMessage(CEL_NONNULL(cel_Status*) status, + CEL_NONNULL(const char*) file, int line, + cel_StringView message) { + CEL_ASSERT_NOT_NULL(status); + CEL_ASSERT_NOT_NULL(file); + CEL_ASSERT_GT(line, 0); + + if (cel_StringView_Empty(message) || status->code == cel_StatusCode_kOk) { + return true; + } + + if (!_cel_String_Assign(&_cel_Status_Rep(status)->message, + cel_DefaultAllocator, message)) { + cel_OutOfMemoryStatus(status, file, line); + return false; + } + + return true; +} + +extern "C" void cel_OutOfMemoryStatus(CEL_NONNULL(cel_Status*) status, + CEL_NONNULL(const char*) file, int line) { + CEL_ASSERT_NOT_NULL(status); + CEL_ASSERT_NOT_NULL(file); + CEL_ASSERT_GT(line, 0); + + CEL_NONNULL(_cel_StatusRep*) status_rep = _cel_Status_Rep(status); + _cel_StatusRep_ResetMessage(status_rep); + _cel_StatusRep_ResetPayloads(status_rep); + status_rep->code = _cel_StatusRep_EncodeCanonical( + cel_StatusCode_kResourceExhausted, /*oom=*/true); + status_rep->space = cel_CanonicalErrorSpace; + status_rep->file = file; + status_rep->line = line; + status_rep->raw_code = cel_StatusCode_kResourceExhausted; +} + +extern "C" bool cel_Status_VFormatMessage(CEL_NONNULL(cel_Status*) status, + CEL_NONNULL(const char*) file, + int line, const char* cel_nonnull fmt, + va_list args) { + CEL_ASSERT_NOT_NULL(status); + CEL_ASSERT_NOT_NULL(file); + CEL_ASSERT_GT(line, 0); + CEL_ASSERT_NOT_NULL(fmt); + + CEL_NONNULL(_cel_StatusRep*) status_rep = _cel_Status_Rep(status); + _cel_String_Clear(&status_rep->message); + if (!_cel_String_VAppendF(&status_rep->message, cel_DefaultAllocator, fmt, + args)) { + cel_OutOfMemoryStatus(status, file, line); + return false; + } + status_rep->file = file; + status_rep->line = line; + return true; +} + +extern "C" bool cel_Status_GetPayload(CEL_NONNULL(const cel_Status*) status, + cel_StringView type_url, + CEL_NULLABLE(cel_StringView*) value) { + CEL_ASSERT_NOT_NULL(status); + + if (cel_Status_Ok(status) || cel_StringView_Empty(type_url)) { + return false; + } + + CEL_NONNULL(const _cel_StatusRep*) status_rep = _cel_Status_Rep(status); + const size_t payloads_len = _cel_Array_Size(&status_rep->payloads); + for (size_t i = 0; i < payloads_len; ++i) { + CEL_NONNULL(const _cel_StatusRepPayload*) + payload_at = _cel_Array_At(&status_rep->payloads, i); + if (cel_StringView_Equals(_cel_String_ToStringView(&payload_at->type_url), + type_url)) { + if (value) { + *value = _cel_String_ToStringView(&payload_at->value); + } + return true; + } + } + + return false; +} + +extern "C" bool cel_Status_SetPayload(CEL_NONNULL(cel_Status*) status, + cel_StringView type_url, + cel_StringView value, + CEL_NONNULL(const char*) file, int line) { + CEL_ASSERT_NOT_NULL(status); + CEL_ASSERT_NOT_NULL(file); + CEL_ASSERT_GT(line, 0); + + if (cel_Status_Ok(status) || cel_StringView_Empty(type_url)) { + return true; + } + + CEL_NONNULL(_cel_StatusRep*) status_rep = _cel_Status_Rep(status); + const size_t payloads_len = _cel_Array_Size(&status_rep->payloads); + for (size_t i = 0; i < payloads_len; ++i) { + CEL_NONNULL(_cel_StatusRepPayload*) + payload_at = _cel_Array_MutableAt(&status_rep->payloads, i); + if (cel_StringView_Equals(_cel_String_ToStringView(&payload_at->type_url), + type_url)) { + if (!_cel_String_Assign(&payload_at->value, cel_DefaultAllocator, + value)) { + cel_OutOfMemoryStatus(status, file, line); + return false; + } + return true; + } + } + + CEL_NULLABLE(_cel_StatusRepPayload*) + payload = _cel_Array_Push(&status_rep->payloads, cel_DefaultAllocator); + if (payload == cel_nullptr) { + cel_OutOfMemoryStatus(status, file, line); + return false; + } + + _cel_StatusRepPayload_Construct(payload); + + if (!_cel_String_Assign(&payload->type_url, cel_DefaultAllocator, type_url)) { + cel_OutOfMemoryStatus(status, file, line); + return false; + } + + if (!_cel_String_Assign(&payload->value, cel_DefaultAllocator, value)) { + cel_OutOfMemoryStatus(status, file, line); + return false; + } + + return true; +} + +extern "C" bool cel_Status_DeletePayload(CEL_NONNULL(cel_Status*) status, + cel_StringView type_url) { + CEL_ASSERT_NOT_NULL(status); + + if (cel_Status_Ok(status) || cel_StringView_Empty(type_url)) { + return false; + } + + CEL_NONNULL(_cel_StatusRep*) status_rep = _cel_Status_Rep(status); + const size_t payloads_len = _cel_Array_Size(&status_rep->payloads); + for (size_t i = 0; i < payloads_len; ++i) { + CEL_NONNULL(_cel_StatusRepPayload*) + payload_at = _cel_Array_MutableAt(&status_rep->payloads, i); + if (cel_StringView_Equals(_cel_String_ToStringView(&payload_at->type_url), + type_url)) { + _cel_StatusRepPayload_Destruct(payload_at); + _cel_Array_Erase(&status_rep->payloads, i); + return true; + } + } + + return false; +} + +extern "C" bool cel_Status_NextPayload(CEL_NONNULL(const cel_Status*) status, + CEL_NULLABLE(cel_StringView*) type_url, + CEL_NULLABLE(cel_StringView*) value, + CEL_NONNULL(cel_StatusPayloadIterator*) + iter) { + CEL_ASSERT_NOT_NULL(status); + CEL_ASSERT_NE(type_url, value); + CEL_ASSERT_NOT_NULL(iter); + CEL_ASSERT(iter->rep < cel_Status_Payloads(status) || iter->rep == SIZE_MAX); + + if (cel_Status_Ok(status)) { + return false; + } + + CEL_NONNULL(const _cel_StatusRep*) status_rep = _cel_Status_Rep(status); + size_t idx = ++iter->rep; + if (idx >= _cel_Array_Size(&status_rep->payloads)) { + return false; + } + CEL_NONNULL(const _cel_StatusRepPayload*) + payload = _cel_Array_At(&status_rep->payloads, idx); + if (type_url != cel_nullptr) { + *type_url = _cel_String_ToStringView(&payload->type_url); + } + if (value != cel_nullptr) { + *value = _cel_String_ToStringView(&payload->value); + } + iter->rep = idx; + return true; +} + +extern "C" size_t cel_Status_Payloads(CEL_NONNULL(const cel_Status*) status) { + CEL_ASSERT_NOT_NULL(status); + + return cel_Status_Ok(status) + ? 0 + : _cel_Array_Size(&_cel_Status_Rep(status)->payloads); +} + +extern "C" void cel_Status_ClearPayloads(CEL_NONNULL(cel_Status*) status) { + CEL_ASSERT_NOT_NULL(status); + + _cel_Array_Clear(&_cel_Status_Rep(status)->payloads); +} diff --git a/cel-c/status_code.cc b/cel-c/status_code.cc new file mode 100644 index 0000000..f49833e --- /dev/null +++ b/cel-c/status_code.cc @@ -0,0 +1,37 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "cel-c/status_code.h" + +#include "cel-c/config.h" +#include "cel-c/error_code.h" + +extern "C" CEL_NONNULL(const char*) cel_StatusCode_Name(cel_StatusCode code) { + switch (code) { + case cel_StatusCode_kOk: + return "OK"; + default: + return cel_ErrorCode_Name((cel_ErrorCode)code); + } +} + +extern "C" CEL_NONNULL(const char*) + cel_StatusCode_Message(cel_StatusCode code) { + switch (code) { + case cel_StatusCode_kOk: + return ""; + default: + return cel_ErrorCode_Message((cel_ErrorCode)code); + } +} diff --git a/cel-c/status_code_test.cc b/cel-c/status_code_test.cc index 1aed477..20b61b9 100644 --- a/cel-c/status_code_test.cc +++ b/cel-c/status_code_test.cc @@ -14,8 +14,6 @@ #include "cel-c/status_code.h" -#include - #include "gtest/gtest.h" namespace { @@ -46,10 +44,6 @@ TEST(StatusCode, Name) { EXPECT_STREQ(cel_StatusCode_Name(cel_StatusCode_kDataLoss), "DATA_LOSS"); EXPECT_STREQ(cel_StatusCode_Name(cel_StatusCode_kUnauthenticated), "UNAUTHENTICATED"); - - EXPECT_STREQ(cel_StatusCode_Name(static_cast( - std::numeric_limits::max())), - "UNKNOWN"); } } // namespace diff --git a/cel-c/status_proto.cc b/cel-c/status_proto.cc new file mode 100644 index 0000000..f141fac --- /dev/null +++ b/cel-c/status_proto.cc @@ -0,0 +1,148 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "cel-c/status_proto.h" + +#include +#include + +#include "google/protobuf/any.upb.h" +#include "google/rpc/code.upb.h" +#include "google/rpc/status.upb.h" +#include "cel-c/assert.h" +#include "cel-c/config.h" +#include "cel-c/status.h" +#include "cel-c/status_code_proto.h" +#include "cel-c/string_view.h" +#include "upb/base/string_view.h" +#include "upb/mem/arena.h" + +static bool _cel_Status_StrDup(cel_StringView in, CEL_NONNULL(upb_Arena*) arena, + CEL_NONNULL(upb_StringView*) out) { + if (cel_StringView_Empty(in)) { + *out = upb_StringView_FromString(""); + return true; + } + char* copied = + reinterpret_cast(upb_Arena_Malloc(arena, cel_StringView_Size(in))); + if (copied == cel_nullptr) { + return false; + } + memcpy(copied, cel_StringView_Data(in), cel_StringView_Size(in)); + *out = upb_StringView_FromDataAndSize(copied, cel_StringView_Size(in)); + return true; +} + +extern "C" bool cel_Status_ToProto(CEL_NONNULL(const cel_Status*) in, + CEL_NONNULL(upb_Arena*) arena, + CEL_NONNULL(google_rpc_Status*) out) { + CEL_ASSERT_NOT_NULL(in); + CEL_ASSERT_NOT_NULL(arena); + CEL_ASSERT_NOT_NULL(out); + + if (cel_Status_Ok(in)) { + google_rpc_Status_clear_code(out); + google_rpc_Status_clear_message(out); + google_rpc_Status_clear_details(out); + return true; + } + + google_rpc_Status_set_code( + out, cel_StatusCode_ToProto(cel_Status_CanonicalCode(in))); + cel_StringView in_message = cel_Status_Message(in); + upb_StringView out_message; + if (!_cel_Status_StrDup(in_message, arena, &out_message)) { + return false; + } + google_rpc_Status_set_message(out, out_message); + + size_t in_payloads_len = cel_Status_Payloads(in); + if (in_payloads_len == 0) { + google_rpc_Status_clear_details(out); + } else { + google_protobuf_Any** out_payloads = + google_rpc_Status_resize_details(out, in_payloads_len, arena); + if (out_payloads == cel_nullptr) { + return false; + } + size_t i = 0; + cel_StringView in_type_url; + cel_StringView in_value; + cel_StatusPayloadIterator iter = cel_Status_BeginPayloads(in); + bool oom = false; + while (cel_Status_NextPayload(in, &in_type_url, &in_value, &iter)) { + google_protobuf_Any* out_payload = google_protobuf_Any_new(arena); + if (out_payload == cel_nullptr) { + oom = true; + break; + } + out_payloads[i++] = out_payload; + upb_StringView out_type_url; + if (!_cel_Status_StrDup(in_type_url, arena, &out_type_url)) { + oom = true; + break; + } + google_protobuf_Any_set_type_url(out_payload, out_type_url); + upb_StringView out_value; + if (!_cel_Status_StrDup(in_value, arena, &out_value)) { + oom = true; + break; + } + google_protobuf_Any_set_value(out_payload, out_value); + } + if (i < in_payloads_len) { + google_rpc_Status_resize_details(out, i, arena); + } + if (oom) { + return false; + } + } + + return true; +} + +extern "C" bool cel_Status_FromProto(CEL_NONNULL(cel_Status*) out, + CEL_NONNULL(const google_rpc_Status*) in) { + CEL_ASSERT_NOT_NULL(in); + CEL_ASSERT_NOT_NULL(out); + + google_rpc_Code in_code = + static_cast(google_rpc_Status_code(in)); + if (in_code == google_rpc_OK) { + cel_Status_Clear(out); + return true; + } + cel_Status_SetCanonicalCode(out, cel_StatusCode_FromProto(in_code)); + if (!cel_Status_SetMessage(out, google_rpc_Status_message(in))) { + return false; + } + + cel_Status_ClearPayloads(out); + + size_t in_payloads_len; + const google_protobuf_Any* const* in_payloads = + google_rpc_Status_details(in, &in_payloads_len); + for (size_t i = 0; i < in_payloads_len; ++i) { + const google_protobuf_Any* in_payload = in_payloads[i]; + CEL_ASSERT_NOT_NULL(in_payload); + if (in_payload == cel_nullptr) { + continue; + } + if (!cel_Status_SetPayload(out, google_protobuf_Any_type_url(in_payload), + google_protobuf_Any_value(in_payload))) { + return false; + } + } + return true; +} diff --git a/cel-c/string_view.cc b/cel-c/string_view.cc new file mode 100644 index 0000000..5e1ec21 --- /dev/null +++ b/cel-c/string_view.cc @@ -0,0 +1,100 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "cel-c/string_view.h" + +#include +#include + +#include "cel-c/assert.h" +#include "cel-c/config.h" +#include "cel-c/src/memory.h" + +extern "C" const char* cel_nullable +cel_StringView_FindFirst(cel_StringView haystack, cel_StringView needle) { + return reinterpret_cast(_cel_Memory_FindFirst( + cel_StringView_Data(haystack), cel_StringView_Size(haystack), + cel_StringView_Data(needle), cel_StringView_Size(needle))); +} + +extern "C" const char* cel_nullable +cel_StringView_FindLast(cel_StringView haystack, cel_StringView needle) { + return reinterpret_cast(_cel_Memory_FindLast( + cel_StringView_Data(haystack), cel_StringView_Size(haystack), + cel_StringView_Data(needle), cel_StringView_Size(needle))); +} + +extern "C" size_t cel_StringView_Count(cel_StringView haystack, + cel_StringView needle) { + CEL_ASSERT_NOT(cel_StringView_Empty(needle)); + + size_t count = 0; + const char* pos; + while ((pos = cel_StringView_FindFirst(haystack, needle)) != cel_nullptr) { + cel_StringView_RemovePrefix( + &haystack, + (pos - cel_StringView_Data(haystack)) + cel_StringView_Size(needle)); + ++count; + } + return count; +} + +extern "C" bool cel_StringViewTokenizer_Next( + cel_StringViewTokenizer* cel_nonnull tokenizer, + cel_StringView* cel_nonnull token) { + CEL_ASSERT_NOT_NULL(tokenizer); + CEL_ASSERT_NOT_NULL(token); + + if (tokenizer->done) { + return false; + } + const char* next = + cel_StringView_FindFirst(tokenizer->subject, tokenizer->delim); + if (next == cel_nullptr) { + *token = tokenizer->subject; + tokenizer->done = true; + return true; + } + const char* data = cel_StringView_Data(tokenizer->subject); + *token = cel_StringView_FromArray(data, next - data); + cel_StringView_RemovePrefix( + &tokenizer->subject, + (next - data) + cel_StringView_Size(tokenizer->delim)); + return true; +} + +extern "C" bool cel_StringView_EqualsIgnoreCase(cel_StringView lhs, + cel_StringView rhs) { + const char* const lhs_data = cel_StringView_Data(lhs); + const size_t lhs_size = cel_StringView_Size(lhs); + const char* const rhs_data = cel_StringView_Data(rhs); + if (lhs_size != cel_StringView_Size(rhs)) { + return false; + } + if (lhs_data == rhs_data) { + return true; + } + for (size_t i = 0; i < lhs_size; ++i) { + uint8_t lhs_c = (uint8_t)lhs_data[i]; + uint8_t rhs_c = (uint8_t)rhs_data[i]; + if (lhs_c != rhs_c) { + lhs_c = lhs_c >= 'A' && lhs_c <= 'Z' ? lhs_c - 'A' + 'a' : lhs_c; + rhs_c = rhs_c >= 'A' && rhs_c <= 'Z' ? rhs_c - 'A' + 'a' : rhs_c; + if (lhs_c != rhs_c) { + return false; + } + } + } + return true; +} diff --git a/cel-c/timestamp.cc b/cel-c/timestamp.cc new file mode 100644 index 0000000..10c8ef2 --- /dev/null +++ b/cel-c/timestamp.cc @@ -0,0 +1,134 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "cel-c/timestamp.h" + +#include +#include + +#include "cel-c/assert.h" +#include "cel-c/config.h" +#include "cel-c/duration.h" +#include "cel-c/src/ckdint.h" + +extern "C" bool cel_Timestamp_Normalize(int64_t* cel_nonnull sec, + int32_t* cel_nonnull nsec) { + int32_t rem; + + // Normalize nsec. + rem = *nsec / INT32_C(1000000000); + if (rem != 0) { + if (CEL_UNLIKELY(_cel_ckd_add(sec, *sec, (int64_t)rem))) { + return false; + } + *nsec %= INT32_C(1000000000); + } + + // Normalize sign. + if (*nsec < 0) { + if (CEL_UNLIKELY(_cel_ckd_sub(sec, *sec, INT64_C(1)))) { + return false; + } + *nsec += INT32_C(1000000000); + } + + return cel_Timestamp_Valid(*sec, *nsec); +} + +extern "C" bool cel_Timestamp_Add(cel_Timestamp* cel_nonnull out, + cel_Timestamp lhs, cel_Duration rhs) { + CEL_ASSERT_NOT_NULL(out); + CEL_ASSERT(cel_Timestamp_Valid(lhs.sec, lhs.nsec)); + CEL_ASSERT(cel_Duration_Valid(rhs.sec, rhs.nsec)); + + int64_t lhs_sec = lhs.sec; + int64_t rhs_sec = rhs.sec; + int64_t sec; + int32_t nsec; + int32_t lhs_nsec = lhs.nsec; + int32_t rhs_nsec = rhs.nsec; + + if (CEL_UNLIKELY(_cel_ckd_add(&sec, lhs_sec, rhs_sec))) { + return false; + } + + if (CEL_UNLIKELY(_cel_ckd_add(&nsec, lhs_nsec, rhs_nsec))) { + return false; + } + + if (CEL_UNLIKELY(!cel_Timestamp_Normalize(&sec, &nsec))) { + return false; + } + + *out = cel_Timestamp_FromUnix(sec, nsec); + return true; +} + +extern "C" bool cel_Timestamp_Sub(cel_Timestamp* cel_nonnull out, + cel_Timestamp lhs, cel_Duration rhs) { + CEL_ASSERT_NOT_NULL(out); + CEL_ASSERT(cel_Timestamp_Valid(lhs.sec, lhs.nsec)); + CEL_ASSERT(cel_Duration_Valid(rhs.sec, rhs.nsec)); + + int64_t lhs_sec = lhs.sec; + int64_t rhs_sec = rhs.sec; + int64_t sec; + int32_t nsec; + int32_t lhs_nsec = lhs.nsec; + int32_t rhs_nsec = rhs.nsec; + + if (CEL_UNLIKELY(_cel_ckd_sub(&sec, lhs_sec, rhs_sec))) { + return false; + } + + if (CEL_UNLIKELY(_cel_ckd_sub(&nsec, lhs_nsec, rhs_nsec))) { + return false; + } + + if (CEL_UNLIKELY(!cel_Timestamp_Normalize(&sec, &nsec))) { + return false; + } + + *out = cel_Timestamp_FromUnix(sec, nsec); + return true; +} + +extern "C" bool cel_Timestamp_Diff(cel_Duration* cel_nonnull out, + cel_Timestamp lhs, cel_Timestamp rhs) { + CEL_ASSERT_NOT_NULL(out); + CEL_ASSERT(cel_Timestamp_Valid(lhs.sec, lhs.nsec)); + CEL_ASSERT(cel_Timestamp_Valid(rhs.sec, rhs.nsec)); + + int64_t lhs_sec = lhs.sec; + int64_t rhs_sec = rhs.sec; + int64_t sec; + int32_t nsec; + int32_t lhs_nsec = lhs.nsec; + int32_t rhs_nsec = rhs.nsec; + + if (CEL_UNLIKELY(_cel_ckd_sub(&sec, lhs_sec, rhs_sec))) { + return false; + } + + if (CEL_UNLIKELY(_cel_ckd_sub(&nsec, lhs_nsec, rhs_nsec))) { + return false; + } + + if (CEL_UNLIKELY(!cel_Duration_Normalize(&sec, &nsec))) { + return false; + } + + *out = cel_Duration_FromUnix(sec, nsec); + return true; +} diff --git a/cel-c/timestamp_proto.cc b/cel-c/timestamp_proto.cc new file mode 100644 index 0000000..c085264 --- /dev/null +++ b/cel-c/timestamp_proto.cc @@ -0,0 +1,52 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "cel-c/timestamp_proto.h" + +#include +#include + +#include "google/protobuf/timestamp.upb.h" +#include "cel-c/assert.h" +#include "cel-c/config.h" +#include "cel-c/timestamp.h" + +extern "C" void cel_Timestamp_ToProto(cel_Timestamp in, + CEL_NONNULL(google_protobuf_Timestamp*) + out) { + CEL_ASSERT_NOT_NULL(out); + + int64_t sec; + int32_t nsec; + cel_Timestamp_ToUnix(in, &sec, &nsec); + google_protobuf_Timestamp_set_seconds(out, sec); + google_protobuf_Timestamp_set_nanos(out, nsec); +} + +extern "C" bool cel_Timestamp_FromProto( + CEL_NONNULL(cel_Timestamp*) out, + CEL_NONNULL(const google_protobuf_Timestamp*) in) { + CEL_ASSERT_NOT_NULL(out); + CEL_ASSERT_NOT_NULL(in); + + int64_t sec; + int32_t nsec; + sec = google_protobuf_Timestamp_seconds(in); + nsec = google_protobuf_Timestamp_nanos(in); + if (!cel_Timestamp_Normalize(&sec, &nsec)) { + return false; + } + *out = cel_Timestamp_FromUnix(sec, nsec); + return true; +} diff --git a/cel-c/type.cc b/cel-c/type.cc new file mode 100644 index 0000000..10cc62a --- /dev/null +++ b/cel-c/type.cc @@ -0,0 +1,842 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "cel-c/type.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "cel-c/arena.h" +#include "cel-c/assert.h" +#include "cel-c/config.h" +#include "cel-c/string_view.h" +#include "cel-c/type_kind.h" +#include "upb/reflection/def.h" + +struct cel_ListType { + cel_TypeKind kind; + + const cel_Type* cel_nonnull elem; +}; + +struct cel_MapType { + cel_TypeKind kind; + + const cel_Type* cel_nonnull key_val[2]; +}; + +struct cel_StructType { + cel_TypeKind kind; + + cel_StringView name; +}; + +struct cel_EnumType { + cel_TypeKind kind; + + cel_StringView name; +}; + +struct cel_OpaqueType { + cel_TypeKind kind; + + cel_StringView name; + size_t params_len; + const cel_Type* cel_nonnull params[]; +}; + +struct cel_OptionalType { + cel_OpaqueType super; +}; + +struct cel_TypeType { + cel_TypeKind kind; + + const cel_Type* cel_nonnull type; +}; + +struct cel_TypeParamType { + cel_TypeKind kind; + + cel_StringView name; +}; + +struct cel_FunctionType { + cel_TypeKind kind; + + const cel_Type* cel_nonnull result; + size_t args_len; + const cel_Type* cel_nonnull args[]; +}; + +struct cel_Type { + union { + struct { + cel_TypeKind kind; + }; + cel_ListType list_type; + cel_MapType map_type; + cel_StructType struct_type; + cel_EnumType enum_type; + cel_OpaqueType opaque_type; + cel_OptionalType optional_type; + cel_TypeType type_type; + cel_TypeParamType type_param_type; + cel_FunctionType function_type; + }; +}; + +extern "C" bool cel_IsWellKnownMessageType( + const upb_MessageDef* cel_nonnull def) { + switch (upb_MessageDef_WellKnownType(def)) { + case kUpb_WellKnown_Any: + return true; + case kUpb_WellKnown_Duration: + return true; + case kUpb_WellKnown_Timestamp: + return true; + case kUpb_WellKnown_DoubleValue: + return true; + case kUpb_WellKnown_FloatValue: + return true; + case kUpb_WellKnown_Int64Value: + return true; + case kUpb_WellKnown_UInt64Value: + return true; + case kUpb_WellKnown_Int32Value: + return true; + case kUpb_WellKnown_UInt32Value: + return true; + case kUpb_WellKnown_StringValue: + return true; + case kUpb_WellKnown_BytesValue: + return true; + case kUpb_WellKnown_BoolValue: + return true; + case kUpb_WellKnown_Value: + return true; + case kUpb_WellKnown_ListValue: + return true; + case kUpb_WellKnown_Struct: + return true; + default: + return false; + } +} + +static const cel_StringView _cel_kWellKnownMessageTypeNames[] = { + CEL_STRINGVIEW_C("Any"), CEL_STRINGVIEW_C("BoolValue"), + CEL_STRINGVIEW_C("BytesValue"), CEL_STRINGVIEW_C("DoubleValue"), + CEL_STRINGVIEW_C("Duration"), CEL_STRINGVIEW_C("FloatValue"), + CEL_STRINGVIEW_C("Int32Value"), CEL_STRINGVIEW_C("Int64Value"), + CEL_STRINGVIEW_C("ListValue"), CEL_STRINGVIEW_C("StringValue"), + CEL_STRINGVIEW_C("Struct"), CEL_STRINGVIEW_C("Timestamp"), + CEL_STRINGVIEW_C("UInt32Value"), CEL_STRINGVIEW_C("UInt64Value"), + CEL_STRINGVIEW_C("Value"), +}; + +static int _cel_kWellKnownMessageTypeNames_Compare( + const void* cel_nullability_unknown lhs, + const void* cel_nullability_unknown rhs) { + return cel_StringView_Compare(*(const cel_StringView*)lhs, + *(const cel_StringView*)rhs); +} + +extern "C" bool cel_IsWellKnownMessageTypeName(cel_StringView name) { + return cel_StringView_ConsumePrefix( + &name, cel_StringView_From("google.protobuf.")) && + bsearch(&name, &_cel_kWellKnownMessageTypeNames[0], + cel_arraysize(_cel_kWellKnownMessageTypeNames), + sizeof(*_cel_kWellKnownMessageTypeNames), + &_cel_kWellKnownMessageTypeNames_Compare) != cel_nullptr; +} + +extern "C" bool cel_IsWellKnownEnumType(const upb_EnumDef* cel_nonnull def) { + const char* name = upb_EnumDef_FullName(def); + return name != cel_nullptr && strcmp(name, "google.protobuf.NullValue") == 0; +} + +extern "C" bool cel_IsWellKnownEnumTypeName(cel_StringView name) { + return cel_StringView_Equals( + name, cel_StringView_From("google.protobuf.NullValue")); +} + +static const cel_Type _cel_DynType = { + .kind = cel_TypeKind_kDyn, +}; + +extern "C" const cel_Type* const cel_nonnull cel_DynType = &_cel_DynType; + +static const cel_Type _cel_NullType = { + .kind = cel_TypeKind_kNull, +}; + +extern "C" const cel_Type* const cel_nonnull cel_NullType = &_cel_NullType; + +static const cel_Type _cel_BoolType = { + .kind = cel_TypeKind_kBool, +}; + +extern "C" const cel_Type* const cel_nonnull cel_BoolType = &_cel_BoolType; + +static const cel_Type _cel_IntType = { + .kind = cel_TypeKind_kInt, +}; + +extern "C" const cel_Type* const cel_nonnull cel_IntType = &_cel_IntType; + +static const cel_Type _cel_UintType = { + .kind = cel_TypeKind_kUint, +}; + +extern "C" const cel_Type* const cel_nonnull cel_UintType = &_cel_UintType; + +static const cel_Type _cel_DoubleType = { + .kind = cel_TypeKind_kDouble, +}; + +extern "C" const cel_Type* const cel_nonnull cel_DoubleType = &_cel_DoubleType; + +static const cel_Type _cel_StringType = { + .kind = cel_TypeKind_kString, +}; + +extern "C" const cel_Type* const cel_nonnull cel_StringType = &_cel_StringType; + +static const cel_Type _cel_BytesType = { + .kind = cel_TypeKind_kBytes, +}; + +extern "C" const cel_Type* const cel_nonnull cel_BytesType = &_cel_BytesType; + +static const cel_Type _cel_AnyType = { + .kind = cel_TypeKind_kAny, +}; + +extern "C" const cel_Type* const cel_nonnull cel_AnyType = &_cel_AnyType; + +static const cel_Type _cel_DurationType = { + .kind = cel_TypeKind_kDuration, +}; + +extern "C" const cel_Type* const cel_nonnull cel_DurationType = + &_cel_DurationType; + +static const cel_Type _cel_TimestampType = { + .kind = cel_TypeKind_kTimestamp, +}; + +extern "C" const cel_Type* const cel_nonnull cel_TimestampType = + &_cel_TimestampType; + +static const cel_Type _cel_UnknownType = { + .kind = cel_TypeKind_kUnknown, +}; + +extern "C" const cel_Type* const cel_nonnull cel_UnknownType = + &_cel_UnknownType; + +static const cel_Type _cel_ErrorType = { + .kind = cel_TypeKind_kError, +}; + +extern "C" const cel_Type* const cel_nonnull cel_ErrorType = &_cel_ErrorType; + +static const cel_Type _cel_BoolWrapperType = { + .kind = cel_TypeKind_kBoolWrapper, +}; + +extern "C" const cel_Type* const cel_nonnull cel_BoolWrapperType = + &_cel_BoolWrapperType; + +static const cel_Type _cel_IntWrapperType = { + .kind = cel_TypeKind_kIntWrapper, +}; + +extern "C" const cel_Type* const cel_nonnull cel_IntWrapperType = + &_cel_IntWrapperType; + +static const cel_Type _cel_UintWrapperType = { + .kind = cel_TypeKind_kUintWrapper, +}; + +extern "C" const cel_Type* const cel_nonnull cel_UintWrapperType = + &_cel_UintWrapperType; + +static const cel_Type _cel_DoubleWrapperType = { + .kind = cel_TypeKind_kDoubleWrapper, +}; + +extern "C" const cel_Type* const cel_nonnull cel_DoubleWrapperType = + &_cel_DoubleWrapperType; + +static const cel_Type _cel_StringWrapperType = { + .kind = cel_TypeKind_kStringWrapper, +}; + +extern "C" const cel_Type* const cel_nonnull cel_StringWrapperType = + &_cel_StringWrapperType; + +static const cel_Type _cel_BytesWrapperType = { + .kind = cel_TypeKind_kBytesWrapper, +}; + +extern "C" const cel_Type* const cel_nonnull cel_BytesWrapperType = + &_cel_BytesWrapperType; + +extern "C" cel_TypeKind cel_Type_Kind(const cel_Type* cel_nonnull type) { + CEL_ASSERT_NOT_NULL(type); + + return type->kind; +} + +extern "C" cel_StringView cel_Type_Name(const cel_Type* cel_nonnull type) { + CEL_ASSERT_NOT_NULL(type); + + switch (cel_Type_Kind(type)) { + case cel_TypeKind_kDyn: + return cel_StringView_From("dyn"); + case cel_TypeKind_kNull: + return cel_StringView_From("null_type"); + case cel_TypeKind_kBool: + return cel_StringView_From("bool"); + case cel_TypeKind_kInt: + return cel_StringView_From("int"); + case cel_TypeKind_kUint: + return cel_StringView_From("uint"); + case cel_TypeKind_kDouble: + return cel_StringView_From("double"); + case cel_TypeKind_kString: + return cel_StringView_From("string"); + case cel_TypeKind_kBytes: + return cel_StringView_From("bytes"); + case cel_TypeKind_kStruct: + return cel_StructType_Name(cel_StructType_DownCast(type)); + case cel_TypeKind_kDuration: + return cel_StringView_From("google.protobuf.Duration"); + case cel_TypeKind_kTimestamp: + return cel_StringView_From("google.protobuf.Timestamp"); + case cel_TypeKind_kList: + return cel_StringView_From("list"); + case cel_TypeKind_kMap: + return cel_StringView_From("map"); + case cel_TypeKind_kUnknown: + return cel_StringView_From("**unknown**"); + case cel_TypeKind_kType: + return cel_StringView_From("type"); + case cel_TypeKind_kError: + return cel_StringView_From("**error**"); + case cel_TypeKind_kAny: + return cel_StringView_From("google.protobuf.Any"); + case cel_TypeKind_kOpaque: + return cel_OpaqueType_Name(cel_OpaqueType_DownCast(type)); + case cel_TypeKind_kBoolWrapper: + return cel_StringView_From("google.protobuf.BoolValue"); + case cel_TypeKind_kIntWrapper: + return cel_StringView_From("google.protobuf.Int64Value"); + case cel_TypeKind_kUintWrapper: + return cel_StringView_From("google.protobuf.UInt64Value"); + case cel_TypeKind_kDoubleWrapper: + return cel_StringView_From("google.protobuf.DoubleValue"); + case cel_TypeKind_kStringWrapper: + return cel_StringView_From("google.protobuf.StringValue"); + case cel_TypeKind_kBytesWrapper: + return cel_StringView_From("google.protobuf.BytesValue"); + case cel_TypeKind_kTypeParam: + return cel_TypeParamType_Name(cel_TypeParamType_DownCast(type)); + case cel_TypeKind_kFunction: + return cel_StringView_From("**function**"); + case cel_TypeKind_kEnum: + return cel_EnumType_Name(cel_EnumType_DownCast(type)); + default: + return cel_StringView_From(""); + } +} + +extern "C" const cel_Type* cel_nonnull const* cel_nullability_unknown +cel_Type_Params(const cel_Type* cel_nonnull type, size_t* cel_nullable size) { + CEL_ASSERT_NOT_NULL(type); + + switch (cel_Type_Kind(type)) { + case cel_TypeKind_kList: + if (size != cel_nullptr) { + *size = 1; + } + return &cel_ListType_DownCast(type)->elem; + case cel_TypeKind_kMap: + if (size != cel_nullptr) { + *size = 2; + } + return cel_MapType_DownCast(type)->key_val; + case cel_TypeKind_kOpaque: + return cel_OpaqueType_Params(cel_OpaqueType_DownCast(type), size); + default: + if (size != cel_nullptr) { + *size = 0; + } + return cel_nullptr; + } +} + +extern "C" bool cel_Type_Equals(const cel_Type* cel_nonnull lhs, + const cel_Type* cel_nonnull rhs) { + CEL_ASSERT_NOT_NULL(lhs); + CEL_ASSERT_NOT_NULL(rhs); + + if (lhs == rhs) { + return true; + } + + const cel_TypeKind kind = cel_Type_Kind(lhs); + if (kind != cel_Type_Kind(rhs)) { + return false; + } + + switch (kind) { + case cel_TypeKind_kDyn: + return true; + case cel_TypeKind_kNull: + return true; + case cel_TypeKind_kBool: + return true; + case cel_TypeKind_kInt: + return true; + case cel_TypeKind_kUint: + return true; + case cel_TypeKind_kDouble: + return true; + case cel_TypeKind_kString: + return true; + case cel_TypeKind_kBytes: + return true; + case cel_TypeKind_kStruct: + return cel_StructType_Equals(cel_StructType_DownCast(lhs), + cel_StructType_DownCast(rhs)); + case cel_TypeKind_kDuration: + return true; + case cel_TypeKind_kTimestamp: + return true; + case cel_TypeKind_kList: + return cel_ListType_Equals(cel_ListType_DownCast(lhs), + cel_ListType_DownCast(rhs)); + case cel_TypeKind_kMap: + return cel_MapType_Equals(cel_MapType_DownCast(lhs), + cel_MapType_DownCast(rhs)); + case cel_TypeKind_kUnknown: + return true; + case cel_TypeKind_kType: + return cel_TypeType_Equals(cel_TypeType_DownCast(lhs), + cel_TypeType_DownCast(rhs)); + case cel_TypeKind_kError: + return true; + case cel_TypeKind_kAny: + return true; + case cel_TypeKind_kOpaque: + return cel_OpaqueType_Equals(cel_OpaqueType_DownCast(lhs), + cel_OpaqueType_DownCast(rhs)); + case cel_TypeKind_kBoolWrapper: + return true; + case cel_TypeKind_kIntWrapper: + return true; + case cel_TypeKind_kUintWrapper: + return true; + case cel_TypeKind_kDoubleWrapper: + return true; + case cel_TypeKind_kStringWrapper: + return true; + case cel_TypeKind_kBytesWrapper: + return true; + case cel_TypeKind_kTypeParam: + return cel_TypeParamType_Equals(cel_TypeParamType_DownCast(lhs), + cel_TypeParamType_DownCast(rhs)); + case cel_TypeKind_kFunction: + return cel_FunctionType_Equals(cel_FunctionType_DownCast(lhs), + cel_FunctionType_DownCast(rhs)); + case cel_TypeKind_kEnum: + return cel_EnumType_Equals(cel_EnumType_DownCast(lhs), + cel_EnumType_DownCast(rhs)); + default: + return false; + } +} + +extern "C" const cel_ListType* cel_nullable cel_ListType_New( + const cel_Type* cel_nonnull element, cel_Arena* cel_nonnull arena) { + CEL_ASSERT_NOT_NULL(element); + CEL_ASSERT_NOT_NULL(arena); + + cel_ListType* type = + (cel_ListType*)cel_Arena_Malloc(arena, sizeof(cel_ListType), cel_nullptr); + if (CEL_LIKELY(type != cel_nullptr)) { + memset(type, '\0', sizeof(*type)); + type->kind = cel_TypeKind_kList; + type->elem = element; + } + return type; +} + +extern "C" const cel_Type* cel_nonnull +cel_ListType_Element(const cel_ListType* cel_nonnull type) { + CEL_ASSERT_NOT_NULL(type); + + return type->elem; +} + +extern "C" bool cel_ListType_Equals(const cel_ListType* cel_nonnull lhs, + const cel_ListType* cel_nonnull rhs) { + CEL_ASSERT_NOT_NULL(lhs); + CEL_ASSERT_NOT_NULL(rhs); + + if (lhs == rhs) { + return true; + } + return cel_Type_Equals(cel_ListType_Element(lhs), cel_ListType_Element(rhs)); +} + +extern "C" const cel_MapType* cel_nullable cel_MapType_New( + const cel_Type* cel_nonnull key, const cel_Type* cel_nonnull value, + cel_Arena* cel_nonnull arena) { + CEL_ASSERT_NOT_NULL(key); + CEL_ASSERT_NOT_NULL(value); + CEL_ASSERT_NOT_NULL(arena); + + cel_MapType* type = + (cel_MapType*)cel_Arena_Malloc(arena, sizeof(cel_MapType), cel_nullptr); + if (CEL_LIKELY(type != cel_nullptr)) { + memset(type, '\0', sizeof(*type)); + type->kind = cel_TypeKind_kMap; + type->key_val[0] = key; + type->key_val[1] = value; + } + return type; +} + +extern "C" const cel_Type* cel_nonnull +cel_MapType_Key(const cel_MapType* cel_nonnull type) { + CEL_ASSERT_NOT_NULL(type); + + return type->key_val[0]; +} + +extern "C" const cel_Type* cel_nonnull +cel_MapType_Value(const cel_MapType* cel_nonnull type) { + CEL_ASSERT_NOT_NULL(type); + + return type->key_val[1]; +} + +extern "C" bool cel_MapType_Equals(const cel_MapType* cel_nonnull lhs, + const cel_MapType* cel_nonnull rhs) { + CEL_ASSERT_NOT_NULL(lhs); + CEL_ASSERT_NOT_NULL(rhs); + + if (lhs == rhs) { + return true; + } + return cel_Type_Equals(cel_MapType_Key(lhs), cel_MapType_Key(rhs)) && + cel_Type_Equals(cel_MapType_Value(lhs), cel_MapType_Value(rhs)); +} + +extern "C" const cel_StructType* cel_nullable +cel_StructType_New(cel_StringView name, cel_Arena* cel_nonnull arena) { + CEL_ASSERT_NOT_NULL(arena); + + cel_StructType* type = (cel_StructType*)cel_Arena_Malloc( + arena, sizeof(cel_StructType), cel_nullptr); + if (CEL_LIKELY(type != cel_nullptr)) { + memset(type, '\0', sizeof(*type)); + type->kind = cel_TypeKind_kStruct; + type->name = name; + } + return type; +} + +extern "C" cel_StringView cel_StructType_Name( + const cel_StructType* cel_nonnull type) { + CEL_ASSERT_NOT_NULL(type); + + return type->name; +} + +extern "C" bool cel_StructType_Equals(const cel_StructType* cel_nonnull lhs, + const cel_StructType* cel_nonnull rhs) { + CEL_ASSERT_NOT_NULL(lhs); + CEL_ASSERT_NOT_NULL(rhs); + + if (lhs == rhs) { + return true; + } + return cel_StringView_Equals(cel_StructType_Name(lhs), + cel_StructType_Name(rhs)); +} + +extern "C" const cel_EnumType* cel_nullable +cel_EnumType_New(cel_StringView name, cel_Arena* cel_nonnull arena) { + CEL_ASSERT_NOT_NULL(arena); + + cel_EnumType* type = + (cel_EnumType*)cel_Arena_Malloc(arena, sizeof(cel_EnumType), cel_nullptr); + if (CEL_LIKELY(type != cel_nullptr)) { + memset(type, '\0', sizeof(*type)); + type->kind = cel_TypeKind_kEnum; + type->name = name; + } + return type; +} + +extern "C" cel_StringView cel_EnumType_Name( + const cel_EnumType* cel_nonnull type) { + CEL_ASSERT_NOT_NULL(type); + + return type->name; +} + +extern "C" bool cel_EnumType_Equals(const cel_EnumType* cel_nonnull lhs, + const cel_EnumType* cel_nonnull rhs) { + CEL_ASSERT_NOT_NULL(lhs); + CEL_ASSERT_NOT_NULL(rhs); + + if (lhs == rhs) { + return true; + } + return cel_StringView_Equals(cel_EnumType_Name(lhs), cel_EnumType_Name(rhs)); +} + +extern "C" cel_OpaqueType* cel_nullable +cel_OpaqueType_New(cel_StringView name, size_t params_len, + const cel_Type * cel_nullability_unknown * + cel_nullability_unknown * cel_nullability_unknown params, + cel_Arena* cel_nonnull arena) { + CEL_ASSERT(params != cel_nullptr || params_len == 0); + CEL_ASSERT_NOT_NULL(arena); + + const size_t size = + offsetof(cel_OpaqueType, params) + (sizeof(const cel_Type*) * params_len); + cel_OpaqueType* type = + (cel_OpaqueType*)cel_Arena_Malloc(arena, size, cel_nullptr); + if (CEL_LIKELY(type != cel_nullptr)) { + memset(type, '\0', size); + type->kind = cel_TypeKind_kOpaque; + type->name = name; + type->params_len = params_len; + if (params_len > 0) { + *params = type->params; + } + } + return type; +} + +extern "C" cel_StringView cel_OpaqueType_Name( + const cel_OpaqueType* cel_nonnull type) { + CEL_ASSERT_NOT_NULL(type); + + return type->name; +} + +extern "C" const cel_Type* const cel_nonnull* cel_nullability_unknown +cel_OpaqueType_Params(const cel_OpaqueType* cel_nonnull type, + size_t* cel_nullable size) { + CEL_ASSERT_NOT_NULL(type); + + if (size != cel_nullptr) { + *size = type->params_len; + } + return type->params; +} + +extern "C" bool cel_OpaqueType_Equals(const cel_OpaqueType* cel_nonnull lhs, + const cel_OpaqueType* cel_nonnull rhs) { + CEL_ASSERT_NOT_NULL(lhs); + CEL_ASSERT_NOT_NULL(rhs); + + if (lhs == rhs) { + return true; + } + size_t lhs_params_size; + size_t rhs_params_size; + const cel_Type* const* lhs_params = + cel_OpaqueType_Params(lhs, &lhs_params_size); + const cel_Type* const* rhs_params = + cel_OpaqueType_Params(rhs, &rhs_params_size); + if (lhs_params_size != rhs_params_size) { + return false; + } + if (!cel_StringView_Equals(cel_OpaqueType_Name(lhs), + cel_OpaqueType_Name(rhs))) { + return false; + } + for (size_t i = 0; i < lhs_params_size; ++i) { + if (!cel_Type_Equals(lhs_params[i], rhs_params[i])) { + return false; + } + } + return true; +} + +extern "C" const cel_OptionalType* cel_nullable cel_OptionalType_New( + const cel_Type* cel_nonnull param, cel_Arena* cel_nonnull arena) { + CEL_ASSERT_NOT_NULL(param); + CEL_ASSERT_NOT_NULL(arena); + + const cel_Type** params; + cel_OpaqueType* type = cel_OpaqueType_New( + cel_StringView_FromString("optional_type"), 1, ¶ms, arena); + if (CEL_LIKELY(type != cel_nullptr)) { + *params = param; + } + return cel_OptionalType_MutableDownCast(type); +} + +extern "C" const cel_TypeType* cel_nullable cel_TypeType_New( + const cel_Type* cel_nonnull type, cel_Arena* cel_nonnull arena) { + CEL_ASSERT_NOT_NULL(type); + CEL_ASSERT_NOT_NULL(arena); + + cel_TypeType* type_type = + (cel_TypeType*)cel_Arena_Malloc(arena, sizeof(cel_TypeType), cel_nullptr); + if (CEL_LIKELY(type_type != cel_nullptr)) { + memset(type_type, '\0', sizeof(*type_type)); + type_type->kind = cel_TypeKind_kType; + type_type->type = type; + } + return type_type; +} + +extern "C" const cel_Type* cel_nonnull +cel_TypeType_Type(const cel_TypeType* cel_nonnull type) { + CEL_ASSERT_NOT_NULL(type); + + return type->type; +} + +extern "C" bool cel_TypeType_Equals(const cel_TypeType* cel_nonnull lhs, + const cel_TypeType* cel_nonnull rhs) { + CEL_ASSERT_NOT_NULL(lhs); + CEL_ASSERT_NOT_NULL(rhs); + + if (lhs == rhs) { + return true; + } + return cel_Type_Equals(cel_TypeType_Type(lhs), cel_TypeType_Type(rhs)); +} + +extern "C" const cel_TypeParamType* cel_nullable +cel_TypeParamType_New(cel_StringView name, cel_Arena* cel_nonnull arena) { + CEL_ASSERT_NOT_NULL(arena); + + cel_TypeParamType* type = (cel_TypeParamType*)cel_Arena_Malloc( + arena, sizeof(cel_TypeParamType), cel_nullptr); + if (CEL_LIKELY(type != cel_nullptr)) { + memset(type, '\0', sizeof(*type)); + type->kind = cel_TypeKind_kTypeParam; + type->name = name; + } + return type; +} + +extern "C" cel_StringView cel_TypeParamType_Name( + const cel_TypeParamType* cel_nonnull type) { + CEL_ASSERT_NOT_NULL(type); + + return type->name; +} + +extern "C" bool cel_TypeParamType_Equals( + const cel_TypeParamType* cel_nonnull lhs, + const cel_TypeParamType* cel_nonnull rhs) { + CEL_ASSERT_NOT_NULL(lhs); + CEL_ASSERT_NOT_NULL(rhs); + + if (lhs == rhs) { + return true; + } + return cel_StringView_Equals(cel_TypeParamType_Name(lhs), + cel_TypeParamType_Name(rhs)); +} + +extern "C" cel_FunctionType* cel_nullable +cel_FunctionType_New(const cel_Type* cel_nonnull result, size_t args_len, + const cel_Type * cel_nullability_unknown * + cel_nullability_unknown * cel_nullability_unknown args, + cel_Arena* cel_nonnull arena) { + CEL_ASSERT_NOT_NULL(result); + CEL_ASSERT(args != cel_nullptr || args_len == 0); + CEL_ASSERT_NOT_NULL(arena); + + const size_t size = + offsetof(cel_FunctionType, args) + (sizeof(const cel_Type*) * args_len); + cel_FunctionType* type = + (cel_FunctionType*)cel_Arena_Malloc(arena, size, cel_nullptr); + if (CEL_LIKELY(type != cel_nullptr)) { + memset(type, '\0', size); + type->kind = cel_TypeKind_kFunction; + type->result = result; + type->args_len = args_len; + if (args_len > 0) { + *args = type->args; + } + } + return type; +} + +extern "C" const cel_Type* cel_nonnull +cel_FunctionType_Result(const cel_FunctionType* cel_nonnull type) { + CEL_ASSERT_NOT_NULL(type); + + return type->result; +} + +extern "C" const cel_Type* const cel_nonnull* cel_nullability_unknown +cel_FunctionType_Args(const cel_FunctionType* cel_nonnull type, + size_t* cel_nullable size) { + CEL_ASSERT_NOT_NULL(type); + + if (size != cel_nullptr) { + *size = type->args_len; + } + return type->args; +} + +extern "C" bool cel_FunctionType_Equals(const cel_FunctionType* cel_nonnull lhs, + const cel_FunctionType* cel_nonnull + rhs) { + CEL_ASSERT_NOT_NULL(lhs); + CEL_ASSERT_NOT_NULL(rhs); + + if (lhs == rhs) { + return true; + } + size_t lhs_args_size; + size_t rhs_args_size; + const cel_Type* const* lhs_args = cel_FunctionType_Args(lhs, &lhs_args_size); + const cel_Type* const* rhs_args = cel_FunctionType_Args(rhs, &rhs_args_size); + if (lhs_args_size != rhs_args_size) { + return false; + } + if (!cel_Type_Equals(cel_FunctionType_Result(lhs), + cel_FunctionType_Result(rhs))) { + return false; + } + for (size_t i = 0; i < lhs_args_size; ++i) { + if (!cel_Type_Equals(lhs_args[i], rhs_args[i])) { + return false; + } + } + return true; +} diff --git a/cel-c/type_proto.cc b/cel-c/type_proto.cc new file mode 100644 index 0000000..501962b --- /dev/null +++ b/cel-c/type_proto.cc @@ -0,0 +1,301 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "cel-c/type_proto.h" + +#include + +#include "cel/expr/checked.upb.h" +#include "cel-c/arena.h" +#include "cel-c/assert.h" +#include "cel-c/config.h" +#include "cel-c/status.h" +#include "cel-c/string_view.h" +#include "cel-c/type.h" + +extern "C" const cel_Type* cel_nullable cel_Type_FromProto( + const cel_expr_Type* cel_nonnull in, cel_Arena* cel_nonnull arena, + cel_Status* cel_nonnull status) { + CEL_ASSERT_NOT_NULL(in); + CEL_ASSERT_NOT_NULL(arena); + CEL_ASSERT(cel_Status_Ok(status)); + + const cel_expr_Type_type_kind_oneofcases type_kind = + cel_expr_Type_type_kind_case(in); + switch (type_kind) { + case cel_expr_Type_type_kind_dyn: + return cel_DynType; + case cel_expr_Type_type_kind_null: + return cel_NullType; + case cel_expr_Type_type_kind_primitive: { + const cel_expr_Type_PrimitiveType primitive = + cel_expr_Type_PrimitiveType( + cel_expr_Type_primitive(in)); + switch (primitive) { + case cel_expr_Type_BOOL: + return cel_BoolType; + case cel_expr_Type_INT64: + return cel_IntType; + case cel_expr_Type_UINT64: + return cel_UintType; + case cel_expr_Type_DOUBLE: + return cel_DoubleType; + case cel_expr_Type_STRING: + return cel_StringType; + case cel_expr_Type_BYTES: + return cel_BytesType; + default: + cel_InvalidArgumentStatusF( + status, "cel: unexpected google.api.expr.Type.PrimitiveType: %d", + primitive); + return cel_nullptr; + } + } + case cel_expr_Type_type_kind_wrapper: { + const cel_expr_Type_PrimitiveType primitive = + cel_expr_Type_PrimitiveType(cel_expr_Type_wrapper(in)); + switch (primitive) { + case cel_expr_Type_BOOL: + return cel_BoolWrapperType; + case cel_expr_Type_INT64: + return cel_IntWrapperType; + case cel_expr_Type_UINT64: + return cel_UintWrapperType; + case cel_expr_Type_DOUBLE: + return cel_DoubleWrapperType; + case cel_expr_Type_STRING: + return cel_StringWrapperType; + case cel_expr_Type_BYTES: + return cel_BytesWrapperType; + default: + cel_InvalidArgumentStatusF( + status, "cel: unexpected google.api.expr.Type.PrimitiveType: %d", + primitive); + return cel_nullptr; + } + } + case cel_expr_Type_type_kind_well_known: { + const cel_expr_Type_WellKnownType well_known = + static_cast( + cel_expr_Type_well_known(in)); + switch (well_known) { + case cel_expr_Type_ANY: + return cel_AnyType; + case cel_expr_Type_TIMESTAMP: + return cel_TimestampType; + case cel_expr_Type_DURATION: + return cel_DurationType; + default: + cel_InvalidArgumentStatusF( + status, "cel: unexpected google.api.expr.Type.WellKnownType: %d", + well_known); + return cel_nullptr; + } + } + case cel_expr_Type_type_kind_list_type: { + const cel_expr_Type_ListType* in_list = + cel_expr_Type_list_type(in); + const cel_ListType* out_list; + if (CEL_UNLIKELY(in_list == cel_nullptr)) { + out_list = cel_ListType_New(cel_DynType, arena); + } else { + const cel_expr_Type* in_list_element = + cel_expr_Type_ListType_elem_type(in_list); + if (CEL_UNLIKELY(in_list_element == cel_nullptr)) { + out_list = cel_ListType_New(cel_DynType, arena); + } else { + const cel_Type* out_list_element = + cel_Type_FromProto(in_list_element, arena, status); + if (CEL_UNLIKELY(out_list_element == cel_nullptr)) { + return cel_nullptr; + } + out_list = cel_ListType_New(out_list_element, arena); + } + } + if (CEL_UNLIKELY(out_list == cel_nullptr)) { + cel_OutOfMemoryStatus(status); + } + return cel_Type_UpCast(out_list); + } + case cel_expr_Type_type_kind_map_type: { + const cel_expr_Type_MapType* in_map = + cel_expr_Type_map_type(in); + const cel_MapType* out_map; + if (CEL_UNLIKELY(in_map == cel_nullptr)) { + out_map = cel_MapType_New(cel_DynType, cel_DynType, arena); + } else { + const cel_Type* out_map_key; + const cel_Type* out_map_value; + const cel_expr_Type* in_map_key = + cel_expr_Type_MapType_key_type(in_map); + if (CEL_UNLIKELY(in_map_key == cel_nullptr)) { + out_map_key = cel_DynType; + } else { + out_map_key = cel_Type_FromProto(in_map_key, arena, status); + if (CEL_UNLIKELY(out_map_key == cel_nullptr)) { + return cel_nullptr; + } + } + const cel_expr_Type* in_map_value = + cel_expr_Type_MapType_value_type(in_map); + if (CEL_UNLIKELY(in_map_value == cel_nullptr)) { + out_map_value = cel_DynType; + } else { + out_map_value = cel_Type_FromProto(in_map_value, arena, status); + if (CEL_UNLIKELY(out_map_value == cel_nullptr)) { + return cel_nullptr; + } + } + out_map = cel_MapType_New(out_map_key, out_map_value, arena); + } + if (CEL_UNLIKELY(out_map == cel_nullptr)) { + cel_OutOfMemoryStatus(status); + } + return cel_Type_UpCast(out_map); + } + case cel_expr_Type_type_kind_function: { + const cel_expr_Type_FunctionType* in_function = + cel_expr_Type_function(in); + const cel_expr_Type* in_function_result = + cel_expr_Type_FunctionType_result_type(in_function); + const cel_Type* out_function_result; + if (CEL_UNLIKELY(in_function_result == cel_nullptr)) { + out_function_result = cel_DynType; + } else { + out_function_result = + cel_Type_FromProto(in_function_result, arena, status); + if (CEL_UNLIKELY(out_function_result == cel_nullptr)) { + return cel_nullptr; + } + } + size_t function_args_len; + const cel_expr_Type* const* in_function_args = + cel_expr_Type_FunctionType_arg_types(in_function, + &function_args_len); + const cel_Type** out_function_args; + cel_FunctionType* out_function = cel_FunctionType_New( + out_function_result, function_args_len, &out_function_args, arena); + if (CEL_UNLIKELY(out_function == cel_nullptr)) { + cel_OutOfMemoryStatus(status); + return cel_nullptr; + } + for (size_t i = 0; i < function_args_len; ++i) { + const cel_expr_Type* in_function_arg = in_function_args[i]; + const cel_Type* out_function_arg; + if (CEL_UNLIKELY(in_function_arg == cel_nullptr)) { + out_function_arg = cel_DynType; + } else { + out_function_arg = cel_Type_FromProto(in_function_arg, arena, status); + if (CEL_UNLIKELY(out_function_arg == cel_nullptr)) { + return cel_nullptr; + } + } + out_function_args[i] = out_function_arg; + } + return cel_Type_UpCast(out_function); + } + case cel_expr_Type_type_kind_message_type: { + cel_StringView in_struct_name = cel_expr_Type_message_type(in); + cel_StringView out_struct_name; + if (CEL_UNLIKELY( + !cel_Arena_StrDup(arena, &out_struct_name, in_struct_name))) { + cel_OutOfMemoryStatus(status); + return cel_nullptr; + } + const cel_StructType* out_struct = + cel_StructType_New(out_struct_name, arena); + if (CEL_UNLIKELY(out_struct == cel_nullptr)) { + cel_OutOfMemoryStatus(status); + } + return cel_Type_UpCast(out_struct); + } + case cel_expr_Type_type_kind_type_param: { + cel_StringView in_type_param_name = cel_expr_Type_type_param(in); + cel_StringView out_type_param_name; + if (CEL_UNLIKELY(!cel_Arena_StrDup(arena, &out_type_param_name, + in_type_param_name))) { + cel_OutOfMemoryStatus(status); + return cel_nullptr; + } + const cel_TypeParamType* out_type_param = + cel_TypeParamType_New(out_type_param_name, arena); + if (CEL_UNLIKELY(out_type_param == cel_nullptr)) { + cel_OutOfMemoryStatus(status); + } + return cel_Type_UpCast(out_type_param); + } + case cel_expr_Type_type_kind_type: { + const cel_expr_Type* in_type = cel_expr_Type_type(in); + const cel_Type* out_type; + if (CEL_UNLIKELY(in_type == cel_nullptr)) { + out_type = cel_DynType; + } else { + out_type = cel_Type_FromProto(in_type, arena, status); + if (CEL_UNLIKELY(out_type == cel_nullptr)) { + return cel_nullptr; + } + } + const cel_TypeType* out_type_type = cel_TypeType_New(out_type, arena); + if (CEL_UNLIKELY(out_type == cel_nullptr)) { + cel_OutOfMemoryStatus(status); + return cel_nullptr; + } + return cel_Type_UpCast(out_type_type); + } + case cel_expr_Type_type_kind_error: + return cel_ErrorType; + case cel_expr_Type_type_kind_abstract_type: { + const cel_expr_Type_AbstractType* in_opaque = + cel_expr_Type_abstract_type(in); + cel_StringView in_opaque_name = + cel_expr_Type_AbstractType_name(in_opaque); + cel_StringView out_opaque_name; + if (CEL_UNLIKELY( + !cel_Arena_StrDup(arena, &out_opaque_name, in_opaque_name))) { + cel_OutOfMemoryStatus(status); + return cel_nullptr; + } + size_t opaque_params_len; + const cel_expr_Type* const* in_opaque_params = + cel_expr_Type_AbstractType_parameter_types(in_opaque, + &opaque_params_len); + const cel_Type** out_opaque_params; + cel_OpaqueType* out_opaque = cel_OpaqueType_New( + out_opaque_name, opaque_params_len, &out_opaque_params, arena); + if (CEL_UNLIKELY(out_opaque == cel_nullptr)) { + cel_OutOfMemoryStatus(status); + return cel_nullptr; + } + for (size_t i = 0; i < opaque_params_len; ++i) { + const cel_expr_Type* in_opaque_arg = in_opaque_params[i]; + const cel_Type* out_opaque_arg; + if (CEL_UNLIKELY(in_opaque_arg == cel_nullptr)) { + out_opaque_arg = cel_DynType; + } else { + out_opaque_arg = cel_Type_FromProto(in_opaque_arg, arena, status); + if (CEL_UNLIKELY(out_opaque_arg == cel_nullptr)) { + return cel_nullptr; + } + } + out_opaque_params[i] = out_opaque_arg; + } + return cel_Type_UpCast(out_opaque); + } + default: + cel_InvalidArgumentStatusF( + status, "cel: unexpected google.api.expr.Type.type_kind: %d", + type_kind); + return cel_nullptr; + } +} diff --git a/cel-c/type_proto_v1alpha1.cc b/cel-c/type_proto_v1alpha1.cc new file mode 100644 index 0000000..14d7812 --- /dev/null +++ b/cel-c/type_proto_v1alpha1.cc @@ -0,0 +1,313 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "cel-c/type_proto_v1alpha1.h" + +#include + +#include "google/api/expr/v1alpha1/checked.upb.h" +#include "cel-c/arena.h" +#include "cel-c/assert.h" +#include "cel-c/config.h" +#include "cel-c/status.h" +#include "cel-c/string_view.h" +#include "cel-c/type.h" + +extern "C" const cel_Type* cel_nullable cel_Type_FromProtoV1Alpha1( + const google_api_expr_v1alpha1_Type* cel_nonnull in, + cel_Arena* cel_nonnull arena, cel_Status* cel_nonnull status) { + CEL_ASSERT_NOT_NULL(in); + CEL_ASSERT_NOT_NULL(arena); + CEL_ASSERT(cel_Status_Ok(status)); + + const google_api_expr_v1alpha1_Type_type_kind_oneofcases type_kind = + google_api_expr_v1alpha1_Type_type_kind_case(in); + switch (type_kind) { + case google_api_expr_v1alpha1_Type_type_kind_dyn: + return cel_DynType; + case google_api_expr_v1alpha1_Type_type_kind_null: + return cel_NullType; + case google_api_expr_v1alpha1_Type_type_kind_primitive: { + const google_api_expr_v1alpha1_Type_PrimitiveType primitive = + static_cast( + google_api_expr_v1alpha1_Type_primitive(in)); + switch (primitive) { + case google_api_expr_v1alpha1_Type_BOOL: + return cel_BoolType; + case google_api_expr_v1alpha1_Type_INT64: + return cel_IntType; + case google_api_expr_v1alpha1_Type_UINT64: + return cel_UintType; + case google_api_expr_v1alpha1_Type_DOUBLE: + return cel_DoubleType; + case google_api_expr_v1alpha1_Type_STRING: + return cel_StringType; + case google_api_expr_v1alpha1_Type_BYTES: + return cel_BytesType; + default: + cel_InvalidArgumentStatusF( + status, + "cel: unexpected google.api.expr.v1alpha1.Type.PrimitiveType: %d", + primitive); + return cel_nullptr; + } + } + case google_api_expr_v1alpha1_Type_type_kind_wrapper: { + const google_api_expr_v1alpha1_Type_PrimitiveType primitive = + static_cast( + google_api_expr_v1alpha1_Type_wrapper(in)); + switch (primitive) { + case google_api_expr_v1alpha1_Type_BOOL: + return cel_BoolWrapperType; + case google_api_expr_v1alpha1_Type_INT64: + return cel_IntWrapperType; + case google_api_expr_v1alpha1_Type_UINT64: + return cel_UintWrapperType; + case google_api_expr_v1alpha1_Type_DOUBLE: + return cel_DoubleWrapperType; + case google_api_expr_v1alpha1_Type_STRING: + return cel_StringWrapperType; + case google_api_expr_v1alpha1_Type_BYTES: + return cel_BytesWrapperType; + default: + cel_InvalidArgumentStatusF( + status, + "cel: unexpected google.api.expr.v1alpha1.Type.PrimitiveType: %d", + primitive); + return cel_nullptr; + } + } + case google_api_expr_v1alpha1_Type_type_kind_well_known: { + const google_api_expr_v1alpha1_Type_WellKnownType well_known = + static_cast( + google_api_expr_v1alpha1_Type_well_known(in)); + switch (well_known) { + case google_api_expr_v1alpha1_Type_ANY: + return cel_AnyType; + case google_api_expr_v1alpha1_Type_TIMESTAMP: + return cel_TimestampType; + case google_api_expr_v1alpha1_Type_DURATION: + return cel_DurationType; + default: + cel_InvalidArgumentStatusF( + status, + "cel: unexpected google.api.expr.v1alpha1.Type.WellKnownType: %d", + well_known); + return cel_nullptr; + } + } + case google_api_expr_v1alpha1_Type_type_kind_list_type: { + const google_api_expr_v1alpha1_Type_ListType* in_list = + google_api_expr_v1alpha1_Type_list_type(in); + const cel_ListType* out_list; + if (CEL_UNLIKELY(in_list == cel_nullptr)) { + out_list = cel_ListType_New(cel_DynType, arena); + } else { + const google_api_expr_v1alpha1_Type* in_list_element = + google_api_expr_v1alpha1_Type_ListType_elem_type(in_list); + if (CEL_UNLIKELY(in_list_element == cel_nullptr)) { + out_list = cel_ListType_New(cel_DynType, arena); + } else { + const cel_Type* out_list_element = + cel_Type_FromProtoV1Alpha1(in_list_element, arena, status); + if (CEL_UNLIKELY(out_list_element == cel_nullptr)) { + return cel_nullptr; + } + out_list = cel_ListType_New(out_list_element, arena); + } + } + if (CEL_UNLIKELY(out_list == cel_nullptr)) { + cel_OutOfMemoryStatus(status); + } + return cel_Type_UpCast(out_list); + } + case google_api_expr_v1alpha1_Type_type_kind_map_type: { + const google_api_expr_v1alpha1_Type_MapType* in_map = + google_api_expr_v1alpha1_Type_map_type(in); + const cel_MapType* out_map; + if (CEL_UNLIKELY(in_map == cel_nullptr)) { + out_map = cel_MapType_New(cel_DynType, cel_DynType, arena); + } else { + const cel_Type* out_map_key; + const cel_Type* out_map_value; + const google_api_expr_v1alpha1_Type* in_map_key = + google_api_expr_v1alpha1_Type_MapType_key_type(in_map); + if (CEL_UNLIKELY(in_map_key == cel_nullptr)) { + out_map_key = cel_DynType; + } else { + out_map_key = cel_Type_FromProtoV1Alpha1(in_map_key, arena, status); + if (CEL_UNLIKELY(out_map_key == cel_nullptr)) { + return cel_nullptr; + } + } + const google_api_expr_v1alpha1_Type* in_map_value = + google_api_expr_v1alpha1_Type_MapType_value_type(in_map); + if (CEL_UNLIKELY(in_map_value == cel_nullptr)) { + out_map_value = cel_DynType; + } else { + out_map_value = + cel_Type_FromProtoV1Alpha1(in_map_value, arena, status); + if (CEL_UNLIKELY(out_map_value == cel_nullptr)) { + return cel_nullptr; + } + } + out_map = cel_MapType_New(out_map_key, out_map_value, arena); + } + if (CEL_UNLIKELY(out_map == cel_nullptr)) { + cel_OutOfMemoryStatus(status); + } + return cel_Type_UpCast(out_map); + } + case google_api_expr_v1alpha1_Type_type_kind_function: { + const google_api_expr_v1alpha1_Type_FunctionType* in_function = + google_api_expr_v1alpha1_Type_function(in); + const google_api_expr_v1alpha1_Type* in_function_result = + google_api_expr_v1alpha1_Type_FunctionType_result_type(in_function); + const cel_Type* out_function_result; + if (CEL_UNLIKELY(in_function_result == cel_nullptr)) { + out_function_result = cel_DynType; + } else { + out_function_result = + cel_Type_FromProtoV1Alpha1(in_function_result, arena, status); + if (CEL_UNLIKELY(out_function_result == cel_nullptr)) { + return cel_nullptr; + } + } + size_t function_args_len; + const google_api_expr_v1alpha1_Type* const* in_function_args = + google_api_expr_v1alpha1_Type_FunctionType_arg_types( + in_function, &function_args_len); + const cel_Type** out_function_args; + cel_FunctionType* out_function = cel_FunctionType_New( + out_function_result, function_args_len, &out_function_args, arena); + if (CEL_UNLIKELY(out_function == cel_nullptr)) { + cel_OutOfMemoryStatus(status); + return cel_nullptr; + } + for (size_t i = 0; i < function_args_len; ++i) { + const google_api_expr_v1alpha1_Type* in_function_arg = + in_function_args[i]; + const cel_Type* out_function_arg; + if (CEL_UNLIKELY(in_function_arg == cel_nullptr)) { + out_function_arg = cel_DynType; + } else { + out_function_arg = + cel_Type_FromProtoV1Alpha1(in_function_arg, arena, status); + if (CEL_UNLIKELY(out_function_arg == cel_nullptr)) { + return cel_nullptr; + } + } + out_function_args[i] = out_function_arg; + } + return cel_Type_UpCast(out_function); + } + case google_api_expr_v1alpha1_Type_type_kind_message_type: { + cel_StringView in_struct_name = + google_api_expr_v1alpha1_Type_message_type(in); + cel_StringView out_struct_name; + if (CEL_UNLIKELY( + !cel_Arena_StrDup(arena, &out_struct_name, in_struct_name))) { + cel_OutOfMemoryStatus(status); + return cel_nullptr; + } + const cel_StructType* out_struct = + cel_StructType_New(out_struct_name, arena); + if (CEL_UNLIKELY(out_struct == cel_nullptr)) { + cel_OutOfMemoryStatus(status); + } + return cel_Type_UpCast(out_struct); + } + case google_api_expr_v1alpha1_Type_type_kind_type_param: { + cel_StringView in_type_param_name = + google_api_expr_v1alpha1_Type_type_param(in); + cel_StringView out_type_param_name; + if (CEL_UNLIKELY(!cel_Arena_StrDup(arena, &out_type_param_name, + in_type_param_name))) { + cel_OutOfMemoryStatus(status); + return cel_nullptr; + } + const cel_TypeParamType* out_type_param = + cel_TypeParamType_New(out_type_param_name, arena); + if (CEL_UNLIKELY(out_type_param == cel_nullptr)) { + cel_OutOfMemoryStatus(status); + } + return cel_Type_UpCast(out_type_param); + } + case google_api_expr_v1alpha1_Type_type_kind_type: { + const google_api_expr_v1alpha1_Type* in_type = + google_api_expr_v1alpha1_Type_type(in); + const cel_Type* out_type; + if (CEL_UNLIKELY(in_type == cel_nullptr)) { + out_type = cel_DynType; + } else { + out_type = cel_Type_FromProtoV1Alpha1(in_type, arena, status); + if (CEL_UNLIKELY(out_type == cel_nullptr)) { + return cel_nullptr; + } + } + const cel_TypeType* out_type_type = cel_TypeType_New(out_type, arena); + if (CEL_UNLIKELY(out_type == cel_nullptr)) { + cel_OutOfMemoryStatus(status); + return cel_nullptr; + } + return cel_Type_UpCast(out_type_type); + } + case google_api_expr_v1alpha1_Type_type_kind_error: + return cel_ErrorType; + case google_api_expr_v1alpha1_Type_type_kind_abstract_type: { + const google_api_expr_v1alpha1_Type_AbstractType* in_opaque = + google_api_expr_v1alpha1_Type_abstract_type(in); + cel_StringView in_opaque_name = + google_api_expr_v1alpha1_Type_AbstractType_name(in_opaque); + cel_StringView out_opaque_name; + if (CEL_UNLIKELY( + !cel_Arena_StrDup(arena, &out_opaque_name, in_opaque_name))) { + cel_OutOfMemoryStatus(status); + return cel_nullptr; + } + size_t opaque_params_len; + const google_api_expr_v1alpha1_Type* const* in_opaque_params = + google_api_expr_v1alpha1_Type_AbstractType_parameter_types( + in_opaque, &opaque_params_len); + const cel_Type** out_opaque_params; + cel_OpaqueType* out_opaque = cel_OpaqueType_New( + out_opaque_name, opaque_params_len, &out_opaque_params, arena); + if (CEL_UNLIKELY(out_opaque == cel_nullptr)) { + cel_OutOfMemoryStatus(status); + return cel_nullptr; + } + for (size_t i = 0; i < opaque_params_len; ++i) { + const google_api_expr_v1alpha1_Type* in_opaque_arg = + in_opaque_params[i]; + const cel_Type* out_opaque_arg; + if (CEL_UNLIKELY(in_opaque_arg == cel_nullptr)) { + out_opaque_arg = cel_DynType; + } else { + out_opaque_arg = + cel_Type_FromProtoV1Alpha1(in_opaque_arg, arena, status); + if (CEL_UNLIKELY(out_opaque_arg == cel_nullptr)) { + return cel_nullptr; + } + } + out_opaque_params[i] = out_opaque_arg; + } + return cel_Type_UpCast(out_opaque); + } + default: + cel_InvalidArgumentStatusF( + status, "cel: unexpected google.api.expr.v1alpha1.Type.type_kind: %d", + type_kind); + return cel_nullptr; + } +} diff --git a/cel-c/well_known_types.cc b/cel-c/well_known_types.cc new file mode 100644 index 0000000..58720a6 --- /dev/null +++ b/cel-c/well_known_types.cc @@ -0,0 +1,738 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "cel-c/well_known_types.h" + +#include +#include +#include +#include + +#include "cel-c/assert.h" +#include "cel-c/config.h" +#include "cel-c/status.h" +#include "cel-c/string_view.h" +#include "upb/base/descriptor_constants.h" +#include "upb/reflection/def.h" + +CEL_ATTRIBUTE_NODISCARD +static const upb_MessageDef* cel_nullable _cel_WellKnownType_FindMessage( + const upb_DefPool* cel_nonnull def_pool, cel_StringView name, + bool error_if_not_found, cel_Status* cel_nonnull status) { + CEL_ASSERT(cel_Status_Ok(status)); + + const upb_MessageDef* def = upb_DefPool_FindMessageByNameWithSize( + def_pool, cel_StringView_Data(name), cel_StringView_Size(name)); + if (CEL_UNLIKELY(def == cel_nullptr)) { + if (error_if_not_found) { + cel_NotFoundStatusF( + status, "cel: well known message type not found: " CEL_STRINGVIEW_FMT, + CEL_STRINGVIEW_ARGS(name)); + } + return cel_nullptr; + } + return def; +} + +typedef enum { + _cel_WellKnownTypeFieldLabel_kOptional = 1, + _cel_WellKnownTypeFieldLabel_kRepeated, + _cel_WellKnownTypeFieldLabel_kMap, +} _cel_WellKnownTypeFieldLabel; + +static const char* cel_nonnull _cel_WellKnownType_LabelName(upb_Label label) { + switch (label) { + case kUpb_Label_Optional: + return "optional"; + case kUpb_Label_Required: + return "required"; + case kUpb_Label_Repeated: + return "repeated"; + default: + return "unknown"; + } +} + +CEL_ATTRIBUTE_NODISCARD +static const upb_FieldDef* cel_nullable _cel_WellKnownType_CheckField( + const upb_FieldDef* cel_nonnull field_def, uint32_t number, upb_CType type, + _cel_WellKnownTypeFieldLabel label, cel_StringView type_name, + const upb_OneofDef* cel_nullable oneof, cel_Status* cel_nonnull status) { + CEL_ASSERT(cel_Status_Ok(status)); + + if (CEL_UNLIKELY(upb_FieldDef_Number(field_def) != number)) { + cel_FailedPreconditionStatusF( + status, + "cel: well known message type field number unexpected: " + "%s: got %" PRIu32 ", want %" PRIu32, + upb_FieldDef_FullName(field_def), upb_FieldDef_Number(field_def), + number); + return cel_nullptr; + } + if (CEL_UNLIKELY(upb_FieldDef_CType(field_def) != type)) { + cel_FailedPreconditionStatusF( + status, + "cel: well known message type field type unexpected: " + "%s: got %d, want %d", + upb_FieldDef_FullName(field_def), upb_FieldDef_CType(field_def), type); + return cel_nullptr; + } + switch (label) { + case _cel_WellKnownTypeFieldLabel_kOptional: + if (CEL_UNLIKELY(upb_FieldDef_Label(field_def) != kUpb_Label_Optional)) { + cel_FailedPreconditionStatusF( + status, + "cel: well known message type field label unexpected: " + "%s: got %s, want optional", + upb_FieldDef_FullName(field_def), + _cel_WellKnownType_LabelName(upb_FieldDef_Label(field_def))); + return cel_nullptr; + } + break; + case _cel_WellKnownTypeFieldLabel_kRepeated: + if (CEL_UNLIKELY(upb_FieldDef_IsMap(field_def))) { + cel_FailedPreconditionStatusF( + status, + "cel: well known message type field label unexpected: " + "%s: got map, want repeated", + upb_FieldDef_FullName(field_def)); + return cel_nullptr; + } + if (CEL_UNLIKELY(!upb_FieldDef_IsRepeated(field_def))) { + cel_FailedPreconditionStatusF( + status, + "cel: well known message type field label unexpected: " + "%s: got %s, want repeated", + upb_FieldDef_FullName(field_def), + _cel_WellKnownType_LabelName(upb_FieldDef_Label(field_def))); + return cel_nullptr; + } + break; + case _cel_WellKnownTypeFieldLabel_kMap: + if (CEL_UNLIKELY(!upb_FieldDef_IsMap(field_def))) { + cel_FailedPreconditionStatusF( + status, + "cel: well known message type field label unexpected: " + "%s: got %s, want map", + upb_FieldDef_FullName(field_def), + _cel_WellKnownType_LabelName(upb_FieldDef_Label(field_def))); + return cel_nullptr; + } + break; + default: + CEL_UNREACHABLE(); + } + if (type == kUpb_CType_Message) { + if (CEL_UNLIKELY(!cel_StringView_Equals( + cel_StringView_From( + upb_MessageDef_FullName(upb_FieldDef_MessageSubDef(field_def))), + type_name))) { + cel_FailedPreconditionStatusF( + status, + "cel: well known message type field type name unexpected: " + "%s: got %s, want " CEL_STRINGVIEW_FMT, + upb_FieldDef_FullName(field_def), + upb_MessageDef_FullName(upb_FieldDef_MessageSubDef(field_def)), + CEL_STRINGVIEW_ARGS(type_name)); + return cel_nullptr; + } + } else if (type == kUpb_CType_Enum) { + if (CEL_UNLIKELY(!cel_StringView_Equals( + cel_StringView_From( + upb_EnumDef_FullName(upb_FieldDef_EnumSubDef(field_def))), + type_name))) { + cel_FailedPreconditionStatusF( + status, + "cel: well known message type field type name unexpected: " + "%s: got %s, want " CEL_STRINGVIEW_FMT, + upb_FieldDef_FullName(field_def), + upb_EnumDef_FullName(upb_FieldDef_EnumSubDef(field_def)), + CEL_STRINGVIEW_ARGS(type_name)); + return cel_nullptr; + } + } + const upb_OneofDef* got_oneof = upb_FieldDef_ContainingOneof(field_def); + if (CEL_UNLIKELY(got_oneof != oneof)) { + cel_FailedPreconditionStatusF( + status, + "cel: well known message type field oneof unexpected: " + "%s: got %s, want %s", + upb_FieldDef_FullName(field_def), + got_oneof != cel_nullptr ? upb_OneofDef_Name(got_oneof) : "null", + oneof != cel_nullptr ? upb_OneofDef_Name(oneof) : "null"); + return cel_nullptr; + } + return field_def; +} + +CEL_ATTRIBUTE_NODISCARD +static const upb_FieldDef* cel_nullable _cel_WellKnownType_FindField( + const upb_MessageDef* cel_nonnull def, cel_StringView name, uint32_t number, + upb_CType type, _cel_WellKnownTypeFieldLabel label, + cel_StringView type_name, const upb_OneofDef* cel_nullable oneof, + cel_Status* cel_nonnull status) { + CEL_ASSERT(cel_Status_Ok(status)); + + const upb_FieldDef* field_def = upb_MessageDef_FindFieldByNameWithSize( + def, cel_StringView_Data(name), cel_StringView_Size(name)); + if (CEL_UNLIKELY(field_def == cel_nullptr)) { + cel_NotFoundStatusF( + status, + "cel: well known message type field not found: %s." CEL_STRINGVIEW_FMT, + upb_MessageDef_FullName(def), CEL_STRINGVIEW_ARGS(name)); + return cel_nullptr; + } + return _cel_WellKnownType_CheckField(field_def, number, type, label, + type_name, oneof, status); +} + +extern "C" bool cel_NullValueWellKnownType_Initialize( + cel_NullValueWellKnownType* cel_nonnull wkt, + const upb_DefPool* cel_nonnull def_pool, cel_Status* cel_nonnull status) { + CEL_ASSERT_NOT_NULL(wkt); + CEL_ASSERT_NOT_NULL(def_pool); + CEL_ASSERT_NOT_NULL(status); + + memset(wkt, 0, sizeof(*wkt)); + + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return false; + } + + const upb_EnumDef* def = + upb_DefPool_FindEnumByName(def_pool, "google.protobuf.NullValue"); + if (CEL_UNLIKELY(def == cel_nullptr)) { + cel_NotFoundStatus( + status, + cel_StringView_From( + "cel: well known enum type not found: google.protobuf.NullValue")); + return false; + } + if (CEL_UNLIKELY(upb_EnumDef_ValueCount(def) != 1)) { + cel_FailedPreconditionStatus( + status, + cel_StringView_From("cel: well known enum type does not have exactly " + "one value: google.protobuf.NullValue")); + return false; + } + const upb_EnumValueDef* value_def = upb_EnumDef_FindValueByNumber(def, 0); + if (CEL_UNLIKELY(value_def == cel_nullptr)) { + cel_NotFoundStatus(status, cel_StringView_From( + "cel: well known enum type value not found: " + "google.protobuf.NullValue.0")); + return false; + } + const char* name = upb_EnumValueDef_Name(value_def); + if (CEL_UNLIKELY(name == cel_nullptr || strcmp(name, "NULL_VALUE") != 0)) { + cel_NotFoundStatus(status, cel_StringView_From( + "cel: well known enum type value not found: " + "google.protobuf.NullValue.NULL_VALUE")); + return false; + } + + wkt->def = def; + wkt->value_def = value_def; + return true; +} + +static bool _cel_WrapperWellKnownType_Initialize( + cel_BoolValueWellKnownType* cel_nonnull wkt, + const upb_DefPool* cel_nonnull def_pool, cel_StringView name, + upb_CType type, cel_Status* cel_nonnull status) { + CEL_ASSERT_NOT_NULL(wkt); + CEL_ASSERT_NOT_NULL(def_pool); + CEL_ASSERT_NOT_NULL(status); + + memset(wkt, 0, sizeof(*wkt)); + + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return false; + } + + const upb_MessageDef* def = _cel_WellKnownType_FindMessage( + def_pool, name, /*error_if_not_found=*/true, status); + if (CEL_UNLIKELY(def == cel_nullptr)) { + return false; + } + const upb_FieldDef* value_def = _cel_WellKnownType_FindField( + def, cel_StringView_From("value"), UINT32_C(1), type, + _cel_WellKnownTypeFieldLabel_kOptional, cel_StringView_From(""), + /*oneof=*/cel_nullptr, status); + if (CEL_UNLIKELY(value_def == cel_nullptr)) { + return false; + } + + wkt->def = def; + wkt->value_def = value_def; + return true; +} + +extern "C" bool cel_BoolValueWellKnownType_Initialize( + cel_BoolValueWellKnownType* cel_nonnull wkt, + const upb_DefPool* cel_nonnull def_pool, cel_Status* cel_nonnull status) { + return _cel_WrapperWellKnownType_Initialize( + wkt, def_pool, cel_StringView_From("google.protobuf.BoolValue"), + kUpb_CType_Bool, status); +} + +extern "C" bool cel_Int32ValueWellKnownType_Initialize( + cel_Int32ValueWellKnownType* cel_nonnull wkt, + const upb_DefPool* cel_nonnull def_pool, cel_Status* cel_nonnull status) { + return _cel_WrapperWellKnownType_Initialize( + wkt, def_pool, cel_StringView_From("google.protobuf.Int32Value"), + kUpb_CType_Int32, status); +} + +extern "C" bool cel_UInt32ValueWellKnownType_Initialize( + cel_UInt32ValueWellKnownType* cel_nonnull wkt, + const upb_DefPool* cel_nonnull def_pool, cel_Status* cel_nonnull status) { + return _cel_WrapperWellKnownType_Initialize( + wkt, def_pool, cel_StringView_From("google.protobuf.UInt32Value"), + kUpb_CType_UInt32, status); +} + +extern "C" bool cel_Int64ValueWellKnownType_Initialize( + cel_Int64ValueWellKnownType* cel_nonnull wkt, + const upb_DefPool* cel_nonnull def_pool, cel_Status* cel_nonnull status) { + return _cel_WrapperWellKnownType_Initialize( + wkt, def_pool, cel_StringView_From("google.protobuf.Int64Value"), + kUpb_CType_Int64, status); +} + +extern "C" bool cel_UInt64ValueWellKnownType_Initialize( + cel_UInt64ValueWellKnownType* cel_nonnull wkt, + const upb_DefPool* cel_nonnull def_pool, cel_Status* cel_nonnull status) { + return _cel_WrapperWellKnownType_Initialize( + wkt, def_pool, cel_StringView_From("google.protobuf.UInt64Value"), + kUpb_CType_UInt64, status); +} + +extern "C" bool cel_FloatValueWellKnownType_Initialize( + cel_FloatValueWellKnownType* cel_nonnull wkt, + const upb_DefPool* cel_nonnull def_pool, cel_Status* cel_nonnull status) { + return _cel_WrapperWellKnownType_Initialize( + wkt, def_pool, cel_StringView_From("google.protobuf.FloatValue"), + kUpb_CType_Float, status); +} + +extern "C" bool cel_DoubleValueWellKnownType_Initialize( + cel_DoubleValueWellKnownType* cel_nonnull wkt, + const upb_DefPool* cel_nonnull def_pool, cel_Status* cel_nonnull status) { + return _cel_WrapperWellKnownType_Initialize( + wkt, def_pool, cel_StringView_From("google.protobuf.DoubleValue"), + kUpb_CType_Double, status); +} + +extern "C" bool cel_BytesValueWellKnownType_Initialize( + cel_BytesValueWellKnownType* cel_nonnull wkt, + const upb_DefPool* cel_nonnull def_pool, cel_Status* cel_nonnull status) { + return _cel_WrapperWellKnownType_Initialize( + wkt, def_pool, cel_StringView_From("google.protobuf.BytesValue"), + kUpb_CType_Bytes, status); +} + +extern "C" bool cel_StringValueWellKnownType_Initialize( + cel_StringValueWellKnownType* cel_nonnull wkt, + const upb_DefPool* cel_nonnull def_pool, cel_Status* cel_nonnull status) { + return _cel_WrapperWellKnownType_Initialize( + wkt, def_pool, cel_StringView_From("google.protobuf.StringValue"), + kUpb_CType_String, status); +} + +CEL_ATTRIBUTE_NODISCARD +static bool _cel_TemporalWellKnownType_Initialize( + cel_TemporalWellKnownType* cel_nonnull wkt, + const upb_DefPool* cel_nonnull def_pool, cel_StringView name, + cel_Status* cel_nonnull status) { + CEL_ASSERT_NOT_NULL(wkt); + CEL_ASSERT_NOT_NULL(def_pool); + CEL_ASSERT_NOT_NULL(status); + + memset(wkt, 0, sizeof(*wkt)); + + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return false; + } + + const upb_MessageDef* def = _cel_WellKnownType_FindMessage( + def_pool, name, /*error_if_not_found=*/true, status); + if (CEL_UNLIKELY(def == cel_nullptr)) { + return false; + } + const upb_FieldDef* seconds_def = _cel_WellKnownType_FindField( + def, cel_StringView_From("seconds"), UINT32_C(1), kUpb_CType_Int64, + _cel_WellKnownTypeFieldLabel_kOptional, cel_StringView_From(""), + /*oneof=*/cel_nullptr, status); + if (CEL_UNLIKELY(seconds_def == cel_nullptr)) { + return false; + } + const upb_FieldDef* nanos_def = _cel_WellKnownType_FindField( + def, cel_StringView_From("nanos"), UINT32_C(2), kUpb_CType_Int32, + _cel_WellKnownTypeFieldLabel_kOptional, cel_StringView_From(""), + /*oneof=*/cel_nullptr, status); + if (CEL_UNLIKELY(nanos_def == cel_nullptr)) { + return false; + } + + wkt->def = def; + wkt->seconds_def = seconds_def; + wkt->nanos_def = nanos_def; + return true; +} + +extern "C" bool cel_DurationWellKnownType_Initialize( + cel_DurationWellKnownType* cel_nonnull wkt, + const upb_DefPool* cel_nonnull def_pool, cel_Status* cel_nonnull status) { + return _cel_TemporalWellKnownType_Initialize( + wkt, def_pool, cel_StringView_From("google.protobuf.Duration"), status); +} + +extern "C" bool cel_TimestampWellKnownType_Initialize( + cel_TimestampWellKnownType* cel_nonnull wkt, + const upb_DefPool* cel_nonnull def_pool, cel_Status* cel_nonnull status) { + return _cel_TemporalWellKnownType_Initialize( + wkt, def_pool, cel_StringView_From("google.protobuf.Timestamp"), status); +} + +extern "C" bool cel_AnyWellKnownType_Initialize( + cel_AnyWellKnownType* cel_nonnull wkt, + const upb_DefPool* cel_nonnull def_pool, cel_Status* cel_nonnull status) { + CEL_ASSERT_NOT_NULL(wkt); + CEL_ASSERT_NOT_NULL(def_pool); + CEL_ASSERT_NOT_NULL(status); + + memset(wkt, 0, sizeof(*wkt)); + + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return false; + } + + const upb_MessageDef* def = _cel_WellKnownType_FindMessage( + def_pool, cel_StringView_From("google.protobuf.Any"), + /*error_if_not_found=*/true, status); + if (CEL_UNLIKELY(def == cel_nullptr)) { + return false; + } + const upb_FieldDef* type_url_def = _cel_WellKnownType_FindField( + def, cel_StringView_From("type_url"), UINT32_C(1), kUpb_CType_String, + _cel_WellKnownTypeFieldLabel_kOptional, cel_StringView_From(""), + /*oneof=*/cel_nullptr, status); + if (CEL_UNLIKELY(type_url_def == cel_nullptr)) { + return false; + } + const upb_FieldDef* value_def = _cel_WellKnownType_FindField( + def, cel_StringView_From("value"), UINT32_C(2), kUpb_CType_Bytes, + _cel_WellKnownTypeFieldLabel_kOptional, cel_StringView_From(""), + /*oneof=*/cel_nullptr, status); + if (CEL_UNLIKELY(value_def == cel_nullptr)) { + return false; + } + + wkt->def = def; + wkt->type_url_def = type_url_def; + wkt->value_def = value_def; + return true; +} + +extern "C" bool cel_ValueWellKnownType_Initialize( + cel_ValueWellKnownType* cel_nonnull wkt, + const upb_DefPool* cel_nonnull def_pool, cel_Status* cel_nonnull status) { + CEL_ASSERT_NOT_NULL(wkt); + CEL_ASSERT_NOT_NULL(def_pool); + CEL_ASSERT_NOT_NULL(status); + + memset(wkt, 0, sizeof(*wkt)); + + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return false; + } + + const upb_MessageDef* def = _cel_WellKnownType_FindMessage( + def_pool, cel_StringView_From("google.protobuf.Value"), + /*error_if_not_found=*/true, status); + if (CEL_UNLIKELY(def == cel_nullptr)) { + return false; + } + const upb_OneofDef* kind_def = upb_MessageDef_FindOneofByName(def, "kind"); + if (CEL_UNLIKELY(kind_def == cel_nullptr)) { + cel_NotFoundStatusF(status, + "cel: well known message type oneof not found: %s.kind", + upb_MessageDef_FullName(def)); + return false; + } + if (CEL_UNLIKELY(upb_OneofDef_FieldCount(kind_def) != 6)) { + cel_FailedPreconditionStatusF( + status, + "cel: well known message type oneof does not have " + "exactly 6 fields: %s %d", + upb_OneofDef_FullName(kind_def), upb_OneofDef_FieldCount(kind_def)); + return false; + } + const upb_FieldDef* null_value_def = _cel_WellKnownType_FindField( + def, cel_StringView_From("null_value"), UINT32_C(1), kUpb_CType_Enum, + _cel_WellKnownTypeFieldLabel_kOptional, + cel_StringView_From("google.protobuf.NullValue"), kind_def, status); + if (CEL_UNLIKELY(null_value_def == cel_nullptr)) { + return false; + } + const upb_FieldDef* number_value_def = _cel_WellKnownType_FindField( + def, cel_StringView_From("number_value"), UINT32_C(2), kUpb_CType_Double, + _cel_WellKnownTypeFieldLabel_kOptional, cel_StringView_From(""), kind_def, + status); + if (CEL_UNLIKELY(number_value_def == cel_nullptr)) { + return false; + } + const upb_FieldDef* string_value_def = _cel_WellKnownType_FindField( + def, cel_StringView_From("string_value"), UINT32_C(3), kUpb_CType_String, + _cel_WellKnownTypeFieldLabel_kOptional, cel_StringView_From(""), kind_def, + status); + if (CEL_UNLIKELY(string_value_def == cel_nullptr)) { + return false; + } + const upb_FieldDef* bool_value_def = _cel_WellKnownType_FindField( + def, cel_StringView_From("bool_value"), UINT32_C(4), kUpb_CType_Bool, + _cel_WellKnownTypeFieldLabel_kOptional, cel_StringView_From(""), kind_def, + status); + if (CEL_UNLIKELY(bool_value_def == cel_nullptr)) { + return false; + } + const upb_FieldDef* struct_value_def = _cel_WellKnownType_FindField( + def, cel_StringView_From("struct_value"), UINT32_C(5), kUpb_CType_Message, + _cel_WellKnownTypeFieldLabel_kOptional, + cel_StringView_From("google.protobuf.Struct"), kind_def, status); + if (CEL_UNLIKELY(struct_value_def == cel_nullptr)) { + return false; + } + const upb_FieldDef* list_value_def = _cel_WellKnownType_FindField( + def, cel_StringView_From("list_value"), UINT32_C(6), kUpb_CType_Message, + _cel_WellKnownTypeFieldLabel_kOptional, + cel_StringView_From("google.protobuf.ListValue"), kind_def, status); + if (CEL_UNLIKELY(list_value_def == cel_nullptr)) { + return false; + } + + wkt->def = def; + wkt->kind_def = kind_def; + wkt->null_value_def = null_value_def; + wkt->number_value_def = number_value_def; + wkt->string_value_def = string_value_def; + wkt->bool_value_def = bool_value_def; + wkt->struct_value_def = struct_value_def; + wkt->list_value_def = list_value_def; + return true; +} + +extern "C" bool cel_StructWellKnownType_Initialize( + cel_StructWellKnownType* cel_nonnull wkt, + const upb_DefPool* cel_nonnull def_pool, cel_Status* cel_nonnull status) { + CEL_ASSERT_NOT_NULL(wkt); + CEL_ASSERT_NOT_NULL(def_pool); + CEL_ASSERT_NOT_NULL(status); + + memset(wkt, 0, sizeof(*wkt)); + + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return false; + } + + const upb_MessageDef* def = _cel_WellKnownType_FindMessage( + def_pool, cel_StringView_From("google.protobuf.Struct"), + /*error_if_not_found=*/true, status); + if (CEL_UNLIKELY(def == cel_nullptr)) { + return false; + } + const upb_FieldDef* fields_def = _cel_WellKnownType_FindField( + def, cel_StringView_From("fields"), UINT32_C(1), kUpb_CType_Message, + _cel_WellKnownTypeFieldLabel_kMap, + cel_StringView_From("google.protobuf.Struct.FieldsEntry"), + /*oneof=*/cel_nullptr, status); + if (CEL_UNLIKELY(fields_def == cel_nullptr)) { + return false; + } + const upb_MessageDef* fields_entry_def = + upb_FieldDef_MessageSubDef(fields_def); + const upb_FieldDef* fields_key_def = _cel_WellKnownType_CheckField( + upb_MessageDef_FindFieldByNumber(fields_entry_def, + kUpb_MapEntry_KeyFieldNumber), + kUpb_MapEntry_KeyFieldNumber, kUpb_CType_String, + _cel_WellKnownTypeFieldLabel_kOptional, cel_StringView_From(""), + /*oneof=*/cel_nullptr, status); + if (fields_key_def == cel_nullptr) { + return false; + } + const upb_FieldDef* fields_value_def = _cel_WellKnownType_CheckField( + upb_MessageDef_FindFieldByNumber(fields_entry_def, + kUpb_MapEntry_ValueFieldNumber), + kUpb_MapEntry_ValueFieldNumber, kUpb_CType_Message, + _cel_WellKnownTypeFieldLabel_kOptional, + cel_StringView_From("google.protobuf.Value"), + /*oneof=*/cel_nullptr, status); + if (fields_value_def == cel_nullptr) { + return false; + } + + wkt->def = def; + wkt->fields_def = fields_def; + wkt->fields_key_def = fields_key_def; + wkt->fields_value_def = fields_value_def; + return true; +} + +extern "C" bool cel_ListValueWellKnownType_Initialize( + cel_ListValueWellKnownType* cel_nonnull wkt, + const upb_DefPool* cel_nonnull def_pool, cel_Status* cel_nonnull status) { + CEL_ASSERT_NOT_NULL(wkt); + CEL_ASSERT_NOT_NULL(def_pool); + CEL_ASSERT_NOT_NULL(status); + + memset(wkt, 0, sizeof(*wkt)); + + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return false; + } + + const upb_MessageDef* def = _cel_WellKnownType_FindMessage( + def_pool, cel_StringView_From("google.protobuf.ListValue"), + /*error_if_not_found=*/true, status); + if (CEL_UNLIKELY(def == cel_nullptr)) { + return false; + } + const upb_FieldDef* values_def = _cel_WellKnownType_FindField( + def, cel_StringView_From("values"), UINT32_C(1), kUpb_CType_Message, + _cel_WellKnownTypeFieldLabel_kRepeated, + cel_StringView_From("google.protobuf.Value"), + /*oneof=*/cel_nullptr, status); + if (CEL_UNLIKELY(values_def == cel_nullptr)) { + return false; + } + + wkt->def = def; + wkt->values_def = values_def; + return true; +} + +extern "C" bool cel_FieldMaskWellKnownType_Initialize( + cel_FieldMaskWellKnownType* cel_nonnull wkt, + const upb_DefPool* cel_nonnull def_pool, cel_Status* cel_nonnull status) { + CEL_ASSERT_NOT_NULL(wkt); + CEL_ASSERT_NOT_NULL(def_pool); + CEL_ASSERT_NOT_NULL(status); + + memset(wkt, 0, sizeof(*wkt)); + + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return false; + } + + const upb_MessageDef* def = _cel_WellKnownType_FindMessage( + def_pool, cel_StringView_From("google.protobuf.FieldMask"), + /*error_if_not_found=*/false, status); + if (CEL_UNLIKELY(def == cel_nullptr)) { + return false; + } + const upb_FieldDef* paths_def = _cel_WellKnownType_FindField( + def, cel_StringView_From("paths"), UINT32_C(1), kUpb_CType_String, + _cel_WellKnownTypeFieldLabel_kRepeated, cel_StringView_From(""), + /*oneof=*/cel_nullptr, status); + if (CEL_UNLIKELY(paths_def == cel_nullptr)) { + return false; + } + + wkt->def = def; + wkt->paths_def = paths_def; + return true; +} + +extern "C" bool cel_WellKnownTypes_Initialize( + cel_WellKnownTypes* cel_nonnull wkts, + const upb_DefPool* cel_nonnull def_pool, cel_Status* cel_nonnull status) { + CEL_ASSERT_NOT_NULL(wkts); + CEL_ASSERT_NOT_NULL(def_pool); + CEL_ASSERT_NOT_NULL(status); + + memset(wkts, 0, sizeof(*wkts)); + + if (CEL_UNLIKELY(!cel_Status_Ok(status))) { + return false; + } + + if (!cel_NullValueWellKnownType_Initialize(&wkts->null_value, def_pool, + status)) { + return false; + } + if (!cel_BoolValueWellKnownType_Initialize(&wkts->bool_value, def_pool, + status)) { + return false; + } + if (!cel_Int32ValueWellKnownType_Initialize(&wkts->int32_value, def_pool, + status)) { + return false; + } + if (!cel_UInt32ValueWellKnownType_Initialize(&wkts->uint32_value, def_pool, + status)) { + return false; + } + if (!cel_Int64ValueWellKnownType_Initialize(&wkts->int64_value, def_pool, + status)) { + return false; + } + if (!cel_UInt64ValueWellKnownType_Initialize(&wkts->uint64_value, def_pool, + status)) { + return false; + } + if (!cel_FloatValueWellKnownType_Initialize(&wkts->float_value, def_pool, + status)) { + return false; + } + if (!cel_DoubleValueWellKnownType_Initialize(&wkts->double_value, def_pool, + status)) { + return false; + } + if (!cel_BytesValueWellKnownType_Initialize(&wkts->bytes_value, def_pool, + status)) { + return false; + } + if (!cel_StringValueWellKnownType_Initialize(&wkts->string_value, def_pool, + status)) { + return false; + } + if (!cel_DurationWellKnownType_Initialize(&wkts->duration, def_pool, + status)) { + return false; + } + if (!cel_TimestampWellKnownType_Initialize(&wkts->timestamp, def_pool, + status)) { + return false; + } + if (!cel_AnyWellKnownType_Initialize(&wkts->any, def_pool, status)) { + return false; + } + if (!cel_ValueWellKnownType_Initialize(&wkts->value, def_pool, status)) { + return false; + } + if (!cel_StructWellKnownType_Initialize(&wkts->struct_value, def_pool, + status)) { + return false; + } + if (!cel_ListValueWellKnownType_Initialize(&wkts->list_value, def_pool, + status)) { + return false; + } + if (!cel_FieldMaskWellKnownType_Initialize(&wkts->field_mask, def_pool, + status) && + !cel_Status_Ok(status)) { + return false; + } + return true; +}