Skip to content

Commit 749e785

Browse files
jnthntatumcopybara-github
authored andcommitted
Add option to enable/disable repeated unary op folding in parser.
PiperOrigin-RevId: 968569494
1 parent 5053f46 commit 749e785

7 files changed

Lines changed: 89 additions & 31 deletions

File tree

parser/internal/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ cc_test(
117117
deps = [
118118
":antlr_parser",
119119
"//common:ast",
120+
"//common:navigable_ast",
120121
"//common:source",
121122
"//internal:status_macros",
122123
"//internal:testing",

parser/internal/antlr_parser.cc

Lines changed: 37 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -615,7 +615,8 @@ class ParserVisitor final : public CelBaseVisitor,
615615
bool add_macro_calls = false,
616616
bool enable_optional_syntax = false,
617617
bool enable_quoted_identifiers = false,
618-
bool enable_variadic_logical_operators = false)
618+
bool enable_variadic_logical_operators = false,
619+
bool fold_unary_operators = false)
619620
: source_(source),
620621
factory_(source_, max_expression_node_count),
621622
macro_registry_(macro_registry),
@@ -624,7 +625,8 @@ class ParserVisitor final : public CelBaseVisitor,
624625
add_macro_calls_(add_macro_calls),
625626
enable_optional_syntax_(enable_optional_syntax),
626627
enable_quoted_identifiers_(enable_quoted_identifiers),
627-
enable_variadic_logical_operators_(enable_variadic_logical_operators) {}
628+
enable_variadic_logical_operators_(enable_variadic_logical_operators),
629+
fold_unary_operators_(fold_unary_operators) {}
628630

629631
~ParserVisitor() override = default;
630632

@@ -700,6 +702,9 @@ class ParserVisitor final : public CelBaseVisitor,
700702
const Expr& e);
701703

702704
std::string NormalizeIdentifier(CelParser::EscapeIdentContext* ctx);
705+
std::any VisitUnaryOps(const std::vector<antlr4::Token*>& ops,
706+
CelParser::MemberContext* member,
707+
absl::string_view op_name);
703708
// Attempt to unnest parse context.
704709
//
705710
// Walk the parse tree to the first complex term to reduce recursive depth in
@@ -716,6 +721,7 @@ class ParserVisitor final : public CelBaseVisitor,
716721
const bool enable_optional_syntax_;
717722
const bool enable_quoted_identifiers_;
718723
const bool enable_variadic_logical_operators_;
724+
const bool fold_unary_operators_;
719725
};
720726

721727
template <typename T, typename = std::enable_if_t<
@@ -989,24 +995,37 @@ std::any ParserVisitor::visitUnary(CelParser::UnaryContext* ctx) {
989995
factory_.NextId(SourceRangeFromParserRuleContext(ctx)), "<<error>>"));
990996
}
991997

992-
std::any ParserVisitor::visitLogicalNot(CelParser::LogicalNotContext* ctx) {
993-
if (ctx->ops.size() % 2 == 0) {
994-
return visit(ctx->member());
998+
std::any ParserVisitor::VisitUnaryOps(const std::vector<antlr4::Token*>& ops,
999+
CelParser::MemberContext* member,
1000+
absl::string_view op_name) {
1001+
if (fold_unary_operators_) {
1002+
if (ops.size() % 2 == 0) {
1003+
return visit(member);
1004+
}
1005+
int64_t op_id = factory_.NextId(SourceRangeFromToken(ops[0]));
1006+
auto target = ExprFromAny(visit(member));
1007+
return ExprToAny(GlobalCallOrMacro(op_id, op_name, std::move(target)));
9951008
}
996-
int64_t op_id = factory_.NextId(SourceRangeFromToken(ctx->ops[0]));
997-
auto target = ExprFromAny(visit(ctx->member()));
998-
return ExprToAny(
999-
GlobalCallOrMacro(op_id, CelOperator::LOGICAL_NOT, std::move(target)));
1009+
1010+
std::vector<int64_t> op_ids;
1011+
op_ids.reserve(ops.size());
1012+
for (const auto* op : ops) {
1013+
op_ids.push_back(factory_.NextId(SourceRangeFromToken(op)));
1014+
}
1015+
1016+
auto target = ExprFromAny(visit(member));
1017+
for (int i = static_cast<int>(op_ids.size()) - 1; i >= 0; --i) {
1018+
target = GlobalCallOrMacro(op_ids[i], op_name, std::move(target));
1019+
}
1020+
return ExprToAny(std::move(target));
1021+
}
1022+
1023+
std::any ParserVisitor::visitLogicalNot(CelParser::LogicalNotContext* ctx) {
1024+
return VisitUnaryOps(ctx->ops, ctx->member(), CelOperator::LOGICAL_NOT);
10001025
}
10011026

10021027
std::any ParserVisitor::visitNegate(CelParser::NegateContext* ctx) {
1003-
if (ctx->ops.size() % 2 == 0) {
1004-
return visit(ctx->member());
1005-
}
1006-
int64_t op_id = factory_.NextId(SourceRangeFromToken(ctx->ops[0]));
1007-
auto target = ExprFromAny(visit(ctx->member()));
1008-
return ExprToAny(
1009-
GlobalCallOrMacro(op_id, CelOperator::NEGATE, std::move(target)));
1028+
return VisitUnaryOps(ctx->ops, ctx->member(), CelOperator::NEGATE);
10101029
}
10111030

10121031
std::string ParserVisitor::NormalizeIdentifier(
@@ -1684,7 +1703,8 @@ absl::StatusOr<std::unique_ptr<cel::Ast>> AntlrParseImpl(
16841703
source, options.max_recursion_depth, options.expression_node_limit,
16851704
registry, options.add_macro_calls, options.enable_optional_syntax,
16861705
options.enable_quoted_identifiers,
1687-
options.enable_variadic_logical_operators);
1706+
options.enable_variadic_logical_operators,
1707+
options.fold_unary_operators);
16881708

16891709
lexer.removeErrorListeners();
16901710
parser.removeErrorListeners();

parser/internal/antlr_parser_test.cc

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include "absl/status/statusor.h"
2424
#include "absl/strings/string_view.h"
2525
#include "common/ast.h"
26+
#include "common/navigable_ast.h"
2627
#include "common/source.h"
2728
#include "internal/status_macros.h"
2829
#include "internal/testing.h"
@@ -72,5 +73,20 @@ TEST(AntlrParserTest, RecursionDepthExceeded) {
7273
HasSubstr("Exceeded max recursion depth of 6 when parsing."));
7374
}
7475

76+
TEST(AntlrParserTest, UnaryOperatorsUnfoldedOption) {
77+
ParserOptions options;
78+
options.fold_unary_operators = false;
79+
80+
ASSERT_OK_AND_ASSIGN(auto ast, Parse("---a", "", options));
81+
auto nav_ast = cel::NavigableAst::Build(ast->root_expr());
82+
EXPECT_EQ(nav_ast.Root().height(), 4);
83+
84+
for (const auto& node : nav_ast.Root().DescendantsPostorder()) {
85+
if (node.node_kind() == cel::NodeKind::kCall) {
86+
EXPECT_EQ(node.expr()->call_expr().function(), "-_");
87+
}
88+
}
89+
}
90+
7591
} // namespace
7692
} // namespace cel::parser_internal

parser/internal/pratt_parser_test.cc

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -434,9 +434,7 @@ std::vector<TestCase> GetParserTestCases() {
434434
TestCase{
435435
.source = "- -1",
436436
.expected_ast = R"(
437-
-_(
438-
-1^#2:int64#
439-
)^#1:Expr.Call#
437+
1^#3:int64#
440438
)",
441439
},
442440
TestCase{
@@ -454,11 +452,7 @@ std::vector<TestCase> GetParserTestCases() {
454452
.source = "---a",
455453
.expected_ast = R"(
456454
-_(
457-
-_(
458-
-_(
459-
a^#4:Expr.Ident#
460-
)^#3:Expr.Call#
461-
)^#2:Expr.Call#
455+
a^#4:Expr.Ident#
462456
)^#1:Expr.Call#
463457
)",
464458
},

parser/internal/pratt_parser_worker.h

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -544,7 +544,15 @@ ExprNode PrattParserWorker<ExprNode>::ParseUnaryOpsChain(Token first_op) {
544544

545545
ExprNode operand;
546546
if (!ops.empty() && ops.back().type == TokenType::kMinus) {
547-
if (peek_token_.type == TokenType::kInt) {
547+
if (options_.fold_unary_operators && ops.size() > 1 &&
548+
ops[ops.size() - 2].type == TokenType::kMinus) {
549+
// Match the ANTLR parser behavior where `-(-)+` prefers to match as
550+
// repeated negate operators instead of a negation of an int literal.
551+
// ---9223372036854775808 will fail to parse.
552+
ops.pop_back();
553+
ops.pop_back();
554+
operand = ParseSelectorChain();
555+
} else if (peek_token_.type == TokenType::kInt) {
548556
int64_t op_id = ops.back().id;
549557
ops.pop_back();
550558
operand = ParseNegativeIntLiteral(op_id);
@@ -561,6 +569,13 @@ ExprNode PrattParserWorker<ExprNode>::ParseUnaryOpsChain(Token first_op) {
561569

562570
for (int i = static_cast<int>(ops.size()) - 1; i >= 0; --i) {
563571
std::vector<ExprNode> args;
572+
if (options_.fold_unary_operators && i > 0) {
573+
if (ops[i - 1].type == ops[i].type) {
574+
i--;
575+
continue;
576+
}
577+
}
578+
564579
args.push_back(std::move(operand));
565580
absl::string_view op_name = (ops[i].type == TokenType::kExclamation)
566581
? CelOperator::LOGICAL_NOT

parser/options.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,22 @@ struct ParserOptions final {
8585
// early testing of the Pratt parser.
8686
// TODO(b/527638023): Remove this option once the ANTLR parser is removed.
8787
bool enable_pratt_parser = false;
88+
89+
// Folds repeated unary operators (!, -).
90+
//
91+
// If the operator appears repeatedly, the parser will ignore every contiguous
92+
// pair.
93+
//
94+
// This makes it possible to parse some semantically invalid expressions as
95+
// valid ones, though they are not particularly harmful.
96+
//
97+
// Examples that parse to the same AST:
98+
//
99+
// `---1` : `-(1)`
100+
// `!!!!!true` : !`true`
101+
// `--0u` : `0u`
102+
// `!!"hello"` : `"hello"`).
103+
bool fold_unary_operators = true;
88104
};
89105

90106
} // namespace cel

parser/parser_test.cc

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -820,11 +820,7 @@ std::vector<TestInfo> test_cases = {
820820
"", "", "", "",
821821
// PRATT PARSER AST
822822
"-_(\n"
823-
" -_(\n"
824-
" -_(\n"
825-
" a^#4:Expr.Ident#\n"
826-
" )^#3:Expr.Call#\n"
827-
" )^#2:Expr.Call#\n"
823+
" a^#4:Expr.Ident#\n"
828824
")^#1:Expr.Call#"},
829825
{"1 + +", "",
830826
"ERROR: <input>:1:5: Syntax error: mismatched input '+' expecting {'[', "

0 commit comments

Comments
 (0)