Skip to content

Commit 781ecf1

Browse files
dmitriplotnikovcopybara-github
authored andcommitted
[Pratt Parser] Add PrattParser for core grammar.
Macro expansion is not implemented yet PiperOrigin-RevId: 953044015
1 parent 54a98f2 commit 781ecf1

6 files changed

Lines changed: 2817 additions & 0 deletions

File tree

parser/internal/BUILD

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,56 @@ cc_library(
6464
],
6565
)
6666

67+
cc_library(
68+
name = "pratt_parser_worker",
69+
srcs = ["pratt_parser_worker.cc"],
70+
hdrs = ["pratt_parser_worker.h"],
71+
deps = [
72+
":ast_factory_interface",
73+
":lexer",
74+
"//common:operators",
75+
"//common:source",
76+
"//internal:lexis",
77+
"//internal:strings",
78+
"//parser:options",
79+
"//parser:parser_interface",
80+
"@com_google_absl//absl/base:nullability",
81+
"@com_google_absl//absl/container:flat_hash_map",
82+
"@com_google_absl//absl/status:statusor",
83+
"@com_google_absl//absl/strings",
84+
"@com_google_absl//absl/strings:str_format",
85+
"@com_google_absl//absl/types:optional",
86+
"@com_google_absl//absl/types:span",
87+
],
88+
)
89+
90+
cc_library(
91+
name = "pratt_parser",
92+
srcs = ["pratt_parser.cc"],
93+
hdrs = ["pratt_parser.h"],
94+
deps = [
95+
":ast_factory",
96+
":pratt_parser_worker",
97+
"//common:ast",
98+
"//common:expr",
99+
"//common:source",
100+
"//internal:status_macros",
101+
"//parser:macro",
102+
"//parser:macro_registry",
103+
"//parser:options",
104+
"//parser:parser_interface",
105+
"@com_google_absl//absl/base:nullability",
106+
"@com_google_absl//absl/cleanup",
107+
"@com_google_absl//absl/container:flat_hash_map",
108+
"@com_google_absl//absl/container:flat_hash_set",
109+
"@com_google_absl//absl/status",
110+
"@com_google_absl//absl/status:statusor",
111+
"@com_google_absl//absl/strings",
112+
"@com_google_absl//absl/strings:str_format",
113+
"@com_google_absl//absl/types:span",
114+
],
115+
)
116+
67117
cc_test(
68118
name = "ast_factory_test",
69119
srcs = ["ast_factory_test.cc"],
@@ -84,3 +134,29 @@ cc_test(
84134
"//internal:testing",
85135
],
86136
)
137+
138+
cc_test(
139+
name = "pratt_parser_test",
140+
srcs = ["pratt_parser_test.cc"],
141+
deps = [
142+
":lexer",
143+
":pratt_parser",
144+
":pratt_parser_worker",
145+
"//common:ast",
146+
"//common:constant",
147+
"//common:expr",
148+
"//common:source",
149+
"//internal:status_macros",
150+
"//internal:testing",
151+
"//parser:options",
152+
"//parser:parser_interface",
153+
"//testutil:expr_printer",
154+
"@com_google_absl//absl/algorithm:container",
155+
"@com_google_absl//absl/status",
156+
"@com_google_absl//absl/status:status_matchers",
157+
"@com_google_absl//absl/status:statusor",
158+
"@com_google_absl//absl/strings",
159+
"@com_google_absl//absl/strings:str_format",
160+
"@com_google_absl//absl/strings:string_view",
161+
],
162+
)

parser/internal/pratt_parser.cc

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
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

parser/internal/pratt_parser.h

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
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+
#ifndef THIRD_PARTY_CEL_CPP_PARSER_INTERNAL_PRATT_PARSER_H_
16+
#define THIRD_PARTY_CEL_CPP_PARSER_INTERNAL_PRATT_PARSER_H_
17+
18+
#include <memory>
19+
#include <string>
20+
#include <utility>
21+
#include <vector>
22+
23+
#include "absl/base/nullability.h"
24+
#include "absl/container/flat_hash_set.h"
25+
#include "absl/status/statusor.h"
26+
#include "common/ast.h"
27+
#include "common/source.h"
28+
#include "parser/macro_registry.h"
29+
#include "parser/options.h"
30+
#include "parser/parser_interface.h"
31+
32+
namespace cel::parser_internal {
33+
34+
// PrattParserImpl implements the Pratt parsing algorithm for CEL expressions.
35+
//
36+
// WARNING: Since this implementation uses recursive descent to parse
37+
// expressions, its stack consumption depends on expression nesting and
38+
// recursion limits (`ParserOptions::max_recursion_depth`). Note that:
39+
// In production builds (e.g., inside fibers with small default stacks such as
40+
// 64KB), the available stack space may be much tighter than in default thread
41+
// stacks.
42+
// Consequently, `ParserOptions::max_recursion_depth` may need to be tuned
43+
// depending on the caller environment to prevent stack overflow.
44+
class PrattParserImpl final : public cel::Parser {
45+
public:
46+
explicit PrattParserImpl(const cel::ParserOptions& options,
47+
cel::MacroRegistry macro_registry,
48+
absl::flat_hash_set<std::string> library_ids)
49+
: options_(options),
50+
macro_registry_(std::move(macro_registry)),
51+
library_ids_(std::move(library_ids)) {}
52+
53+
~PrattParserImpl() override = default;
54+
55+
absl::StatusOr<std::unique_ptr<cel::Ast>> ParseImpl(
56+
const cel::Source& source,
57+
std::vector<cel::ParseIssue>* absl_nullable parse_issues) const override;
58+
59+
std::unique_ptr<cel::ParserBuilder> ToBuilder() const override;
60+
61+
private:
62+
cel::ParserOptions options_;
63+
cel::MacroRegistry macro_registry_;
64+
absl::flat_hash_set<std::string> library_ids_;
65+
};
66+
67+
std::unique_ptr<cel::ParserBuilder> NewPrattParserBuilder(
68+
const cel::ParserOptions& options = cel::ParserOptions());
69+
70+
} // namespace cel::parser_internal
71+
72+
#endif // THIRD_PARTY_CEL_CPP_PARSER_INTERNAL_PRATT_PARSER_H_

0 commit comments

Comments
 (0)