|
27 | 27 | #include "absl/base/attributes.h" |
28 | 28 | #include "absl/base/nullability.h" |
29 | 29 | #include "absl/base/optimization.h" |
| 30 | +#include "absl/cleanup/cleanup.h" |
30 | 31 | #include "absl/container/flat_hash_map.h" |
31 | 32 | #include "absl/status/statusor.h" |
32 | 33 | #include "absl/strings/ascii.h" |
@@ -72,7 +73,7 @@ class ParserWorker { |
72 | 73 | const cel::ParserOptions& options() const { return options_; } |
73 | 74 | // Token stream management |
74 | 75 | void InitTokenStream(); |
75 | | - Token NextSignificantToken(); |
| 76 | + Token NextSignificantToken(bool report_error = true); |
76 | 77 | Token NextToken(); |
77 | 78 | bool Expect(TokenType type, absl::string_view msg = ""); |
78 | 79 | std::string GetTokenText(const Token& tok) const; |
@@ -317,6 +318,8 @@ class PrattParserWorker : public ParserWorker { |
317 | 318 | std::optional<ExprNode> target, |
318 | 319 | std::vector<ExprNode> arguments); |
319 | 320 |
|
| 321 | + int CountGroupingParentheses(); |
| 322 | + |
320 | 323 | AstFactoryInterface<ExprNode>& ast_factory_; |
321 | 324 | absl::flat_hash_map<int64_t, ExprNode> macro_calls_; |
322 | 325 | }; |
@@ -652,9 +655,14 @@ PrattParserWorker<ExprNode>::ParsePrimary() { |
652 | 655 | ExprNode expr; |
653 | 656 | TokenType tok_type = peek_token_.type; |
654 | 657 | if (tok_type == TokenType::kLeftParen) { |
655 | | - NextToken(); |
| 658 | + int grouping_paren_count = CountGroupingParentheses(); |
| 659 | + for (int i = 0; i < grouping_paren_count; ++i) { |
| 660 | + NextToken(); |
| 661 | + } |
656 | 662 | expr = ParseExpr(); |
657 | | - Expect(TokenType::kRightParen); |
| 663 | + for (int i = 0; i < grouping_paren_count; ++i) { |
| 664 | + Expect(TokenType::kRightParen); |
| 665 | + } |
658 | 666 | } else if (tok_type == TokenType::kNull) { |
659 | 667 | Token tok = NextToken(); |
660 | 668 | expr = ast_factory_.NewNullConst(NextId(tok)); |
@@ -1172,6 +1180,75 @@ void PrattParserWorker<ExprNode>::RecordMacroCall( |
1172 | 1180 | macro_calls_.insert({macro_id, std::move(call_expr)}); |
1173 | 1181 | } |
1174 | 1182 |
|
| 1183 | +// Scans ahead in the token stream to detect contiguous grouping |
| 1184 | +// parentheses (e.g., `((((expr))))`). By determining the number of outermost |
| 1185 | +// parentheses that enclose the exact same expression and close contiguously, |
| 1186 | +// the parser unnests them in a single C++ stack frame, avoiding deep recursive |
| 1187 | +// descent. |
| 1188 | +template <typename ExprNode> |
| 1189 | +int PrattParserWorker<ExprNode>::CountGroupingParentheses() { |
| 1190 | + if (peek_token_.type != TokenType::kLeftParen) { |
| 1191 | + return 0; |
| 1192 | + } |
| 1193 | + |
| 1194 | + // Save lexer position to restore after scanning ahead. |
| 1195 | + const Lexer::Position saved_pos = lexer_.SavePosition(); |
| 1196 | + auto restore_lexer = absl::MakeCleanup( |
| 1197 | + [this, saved_pos] { lexer_.RestorePosition(saved_pos); }); |
| 1198 | + |
| 1199 | + int leading_open_parens = 1; |
| 1200 | + Token tok = this->NextSignificantToken(/*report_error=*/false); |
| 1201 | + while (tok.type == TokenType::kLeftParen) { |
| 1202 | + leading_open_parens++; |
| 1203 | + tok = this->NextSignificantToken(/*report_error=*/false); |
| 1204 | + } |
| 1205 | + if (leading_open_parens == 1) { |
| 1206 | + return 1; |
| 1207 | + } |
| 1208 | + |
| 1209 | + int open_parens = leading_open_parens; |
| 1210 | + int consecutive_leading_closed = 0; |
| 1211 | + |
| 1212 | + while (open_parens > 0) { |
| 1213 | + if (tok.type == TokenType::kEnd || tok.type == TokenType::kError) { |
| 1214 | + // Return 1 to ensure the parser consumes '(' and standard error handling |
| 1215 | + // catches incomplete expressions like `(ident`. |
| 1216 | + return 1; |
| 1217 | + } |
| 1218 | + |
| 1219 | + if (tok.type == TokenType::kLeftParen) { |
| 1220 | + // An inner parenthesis opens within the expression |
| 1221 | + // (e.g. `(x` in `((1 + (x) ))`). |
| 1222 | + open_parens++; |
| 1223 | + consecutive_leading_closed = 0; |
| 1224 | + } else if (tok.type == TokenType::kRightParen) { |
| 1225 | + if (leading_open_parens == open_parens) { |
| 1226 | + // All inner parentheses are balanced, so this ')' closes one of the |
| 1227 | + // initial leading '(' parentheses (e.g. trailing ')' in `(((expr)))`). |
| 1228 | + leading_open_parens--; |
| 1229 | + consecutive_leading_closed++; |
| 1230 | + } else { |
| 1231 | + // This ')' closes an inner nested parenthesis (e.g. `(1 + 2)` in |
| 1232 | + // `((1 + 2) * 3)`), not one of the outermost leading parentheses. |
| 1233 | + consecutive_leading_closed = 0; |
| 1234 | + } |
| 1235 | + open_parens--; |
| 1236 | + } else { |
| 1237 | + // Non-parenthesis token (identifier, operator, literal, etc.). Any |
| 1238 | + // preceding ')' did not close the entire expression, so reset the |
| 1239 | + // contiguous outer closing count. |
| 1240 | + consecutive_leading_closed = 0; |
| 1241 | + } |
| 1242 | + |
| 1243 | + if (open_parens > 0) { |
| 1244 | + tok = this->NextSignificantToken(/*report_error=*/false); |
| 1245 | + } |
| 1246 | + } |
| 1247 | + |
| 1248 | + // Return at least 1 to make sure we catch unclosed expressions like `(ident`. |
| 1249 | + return std::max(1, consecutive_leading_closed); |
| 1250 | +} |
| 1251 | + |
1175 | 1252 | } // namespace cel::parser_internal |
1176 | 1253 |
|
1177 | 1254 | #endif // THIRD_PARTY_CEL_CPP_PARSER_INTERNAL_PRATT_PARSER_WORKER_H_ |
0 commit comments