|
| 1 | +// Copyright 2026 Google LLC |
| 2 | +// |
| 3 | +// Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | +// you may not use this file except in compliance with the License. |
| 5 | +// You may obtain a copy of the License at |
| 6 | +// |
| 7 | +// https://www.apache.org/licenses/LICENSE-2.0 |
| 8 | +// |
| 9 | +// Unless required by applicable law or agreed to in writing, software |
| 10 | +// distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | +// See the License for the specific language governing permissions and |
| 13 | +// limitations under the License. |
| 14 | + |
| 15 | +#include "parser/internal/pratt_parser.h" |
| 16 | + |
| 17 | +#include <cstdint> |
| 18 | +#include <memory> |
| 19 | +#include <string> |
| 20 | +#include <string_view> |
| 21 | +#include <utility> |
| 22 | +#include <vector> |
| 23 | + |
| 24 | +#include "absl/base/nullability.h" |
| 25 | +#include "absl/cleanup/cleanup.h" |
| 26 | +#include "absl/container/flat_hash_map.h" |
| 27 | +#include "absl/container/flat_hash_set.h" |
| 28 | +#include "absl/status/status.h" |
| 29 | +#include "absl/status/statusor.h" |
| 30 | +#include "absl/strings/str_cat.h" |
| 31 | +#include "absl/strings/str_format.h" |
| 32 | +#include "absl/strings/str_join.h" |
| 33 | +#include "absl/strings/string_view.h" |
| 34 | +#include "absl/types/span.h" |
| 35 | +#include "common/ast.h" |
| 36 | +#include "common/expr.h" |
| 37 | +#include "common/source.h" |
| 38 | +#include "internal/status_macros.h" |
| 39 | +#include "parser/internal/ast_factory.h" // IWYU pragma: keep |
| 40 | +#include "parser/internal/pratt_parser_worker.h" |
| 41 | +#include "parser/macro.h" |
| 42 | +#include "parser/macro_registry.h" |
| 43 | +#include "parser/options.h" |
| 44 | +#include "parser/parser_interface.h" |
| 45 | + |
| 46 | +namespace cel::parser_internal { |
| 47 | + |
| 48 | +namespace { |
| 49 | + |
| 50 | +std::string DisplayParserError(const cel::Source& source, |
| 51 | + SourceLocation location, |
| 52 | + std::string_view message) { |
| 53 | + return absl::StrCat( |
| 54 | + absl::StrFormat("ERROR: %s:%zu:%zu: %s", source.description(), |
| 55 | + location.line, location.column + 1, message), |
| 56 | + source.DisplayErrorLocation(location)); |
| 57 | +} |
| 58 | + |
| 59 | +std::string FormatIssues(const cel::Source& source, |
| 60 | + absl::Span<const cel::ParseIssue> issues) { |
| 61 | + return absl::StrJoin( |
| 62 | + issues, "\n", [&source](std::string* out, const cel::ParseIssue& issue) { |
| 63 | + absl::StrAppend( |
| 64 | + out, DisplayParserError(source, issue.location(), issue.message())); |
| 65 | + }); |
| 66 | +} |
| 67 | + |
| 68 | +class PrattParserBuilderImpl final : public cel::ParserBuilder { |
| 69 | + public: |
| 70 | + explicit PrattParserBuilderImpl(const cel::ParserOptions& options) |
| 71 | + : options_(options) {} |
| 72 | + |
| 73 | + cel::ParserOptions& GetOptions() override { return options_; } |
| 74 | + |
| 75 | + absl::Status AddMacro(const cel::Macro& macro) override { |
| 76 | + for (const cel::Macro& existing_macro : macros_) { |
| 77 | + if (existing_macro.key() == macro.key()) { |
| 78 | + return absl::AlreadyExistsError( |
| 79 | + absl::StrCat("macro already exists: ", macro.key())); |
| 80 | + } |
| 81 | + } |
| 82 | + macros_.push_back(macro); |
| 83 | + return absl::OkStatus(); |
| 84 | + } |
| 85 | + |
| 86 | + absl::Status AddLibrary(cel::ParserLibrary library) override { |
| 87 | + if (!library.id.empty()) { |
| 88 | + auto [it, inserted] = library_ids_.insert(library.id); |
| 89 | + if (!inserted) { |
| 90 | + return absl::AlreadyExistsError( |
| 91 | + absl::StrCat("parser library already exists: ", library.id)); |
| 92 | + } |
| 93 | + } |
| 94 | + libraries_.push_back(std::move(library)); |
| 95 | + return absl::OkStatus(); |
| 96 | + } |
| 97 | + |
| 98 | + absl::Status AddLibrarySubset(cel::ParserLibrarySubset subset) override { |
| 99 | + if (subset.library_id.empty()) { |
| 100 | + return absl::InvalidArgumentError("subset must have a library id"); |
| 101 | + } |
| 102 | + std::string library_id = subset.library_id; |
| 103 | + auto [it, inserted] = |
| 104 | + library_subsets_.insert({library_id, std::move(subset)}); |
| 105 | + if (!inserted) { |
| 106 | + return absl::AlreadyExistsError( |
| 107 | + absl::StrCat("parser library subset already exists: ", library_id)); |
| 108 | + } |
| 109 | + return absl::OkStatus(); |
| 110 | + } |
| 111 | + |
| 112 | + absl::StatusOr<std::unique_ptr<cel::Parser>> Build() override { |
| 113 | + using std::swap; |
| 114 | + std::vector<cel::Macro> individual_macros; |
| 115 | + swap(individual_macros, macros_); |
| 116 | + absl::Cleanup cleanup([&] { swap(macros_, individual_macros); }); |
| 117 | + |
| 118 | + cel::MacroRegistry macro_registry; |
| 119 | + |
| 120 | + for (const cel::ParserLibrary& library : libraries_) { |
| 121 | + CEL_RETURN_IF_ERROR(library.configure(*this)); |
| 122 | + if (!library.id.empty()) { |
| 123 | + auto it = library_subsets_.find(library.id); |
| 124 | + if (it != library_subsets_.end()) { |
| 125 | + const cel::ParserLibrarySubset& subset = it->second; |
| 126 | + for (const cel::Macro& macro : macros_) { |
| 127 | + if (subset.should_include_macro(macro)) { |
| 128 | + CEL_RETURN_IF_ERROR(macro_registry.RegisterMacro(macro)); |
| 129 | + } |
| 130 | + } |
| 131 | + macros_.clear(); |
| 132 | + continue; |
| 133 | + } |
| 134 | + } |
| 135 | + |
| 136 | + CEL_RETURN_IF_ERROR(macro_registry.RegisterMacros(macros_)); |
| 137 | + macros_.clear(); |
| 138 | + } |
| 139 | + |
| 140 | + absl::flat_hash_set<std::string> library_ids(library_ids_); |
| 141 | + |
| 142 | + if (!options_.disable_standard_macros && !library_ids_.contains("stdlib")) { |
| 143 | + CEL_RETURN_IF_ERROR(macro_registry.RegisterMacros(Macro::AllMacros())); |
| 144 | + library_ids.insert("stdlib"); |
| 145 | + } |
| 146 | + |
| 147 | + if (options_.enable_optional_syntax && !library_ids_.contains("optional")) { |
| 148 | + CEL_RETURN_IF_ERROR(macro_registry.RegisterMacro(cel::OptMapMacro())); |
| 149 | + CEL_RETURN_IF_ERROR(macro_registry.RegisterMacro(cel::OptFlatMapMacro())); |
| 150 | + library_ids.insert("optional"); |
| 151 | + } |
| 152 | + |
| 153 | + CEL_RETURN_IF_ERROR(macro_registry.RegisterMacros(individual_macros)); |
| 154 | + return std::make_unique<PrattParserImpl>( |
| 155 | + options_, std::move(macro_registry), std::move(library_ids)); |
| 156 | + } |
| 157 | + |
| 158 | + cel::ParserOptions options_; |
| 159 | + std::vector<cel::Macro> macros_; |
| 160 | + absl::flat_hash_set<std::string> library_ids_; |
| 161 | + std::vector<cel::ParserLibrary> libraries_; |
| 162 | + absl::flat_hash_map<std::string, cel::ParserLibrarySubset> library_subsets_; |
| 163 | +}; |
| 164 | + |
| 165 | +} // namespace |
| 166 | + |
| 167 | +template class PrattParserWorker<cel::Expr>; |
| 168 | + |
| 169 | +absl::StatusOr<std::unique_ptr<cel::Ast>> PrattParserImpl::ParseImpl( |
| 170 | + const cel::Source& source, |
| 171 | + std::vector<cel::ParseIssue>* absl_nullable parse_issues) const { |
| 172 | + if (source.content().size() > options_.expression_size_codepoint_limit) { |
| 173 | + return absl::InvalidArgumentError(absl::StrFormat( |
| 174 | + "expression size exceeds codepoint limit: %zu > %d", |
| 175 | + source.content().size(), options_.expression_size_codepoint_limit)); |
| 176 | + } |
| 177 | + std::vector<cel::ParseIssue> issues; |
| 178 | + PrattParserWorker<cel::Expr> worker(source, options_, &issues); |
| 179 | + Expr expr = worker.Parse(); |
| 180 | + if (worker.is_recursion_limit_exceeded()) { |
| 181 | + return absl::CancelledError( |
| 182 | + absl::StrFormat("Expression recursion limit exceeded. limit: %d", |
| 183 | + options_.max_recursion_depth)); |
| 184 | + } |
| 185 | + if (worker.has_errors()) { |
| 186 | + std::string err_msg = FormatIssues(source, issues); |
| 187 | + if (parse_issues != nullptr) { |
| 188 | + parse_issues->swap(issues); |
| 189 | + } |
| 190 | + return absl::InvalidArgumentError(err_msg); |
| 191 | + } |
| 192 | + |
| 193 | + cel::SourceInfo source_info; |
| 194 | + source_info.set_location(std::string(source.description())); |
| 195 | + for (const auto& [id, pos] : worker.GetNodePositions()) { |
| 196 | + source_info.mutable_positions().insert({id, pos}); |
| 197 | + } |
| 198 | + source_info.mutable_line_offsets().reserve(worker.GetLineOffsets().size()); |
| 199 | + for (int32_t offset : worker.GetLineOffsets()) { |
| 200 | + source_info.mutable_line_offsets().push_back(offset); |
| 201 | + } |
| 202 | + return std::make_unique<cel::Ast>(std::move(expr), std::move(source_info)); |
| 203 | +} |
| 204 | + |
| 205 | +std::unique_ptr<cel::ParserBuilder> PrattParserImpl::ToBuilder() const { |
| 206 | + auto ins = std::make_unique<PrattParserBuilderImpl>(options_); |
| 207 | + ins->library_ids_ = library_ids_; |
| 208 | + ins->macros_ = macro_registry_.ListMacros(); |
| 209 | + return ins; |
| 210 | +} |
| 211 | + |
| 212 | +std::unique_ptr<cel::ParserBuilder> NewPrattParserBuilder( |
| 213 | + const cel::ParserOptions& options) { |
| 214 | + return std::make_unique<PrattParserBuilderImpl>(options); |
| 215 | +} |
| 216 | + |
| 217 | +} // namespace cel::parser_internal |
0 commit comments