diff --git a/lexer/token_registry.def b/lexer/token_registry.def index 8e99016baaed..62698d1ae11b 100644 --- a/lexer/token_registry.def +++ b/lexer/token_registry.def @@ -143,7 +143,7 @@ CARBON_KEYWORD_TOKEN(RefKeyword, "ref") CARBON_KEYWORD_TOKEN(ReturnKeyword, "return") CARBON_KEYWORD_TOKEN(StaticKeyword, "static") CARBON_KEYWORD_TOKEN(StructKeyword, "struct") -CARBON_KEYWORD_TOKEN(ThroKeyword, "throw") +CARBON_KEYWORD_TOKEN(ThrowKeyword, "throw") CARBON_KEYWORD_TOKEN(TryKeyword, "try") CARBON_KEYWORD_TOKEN(UnderscoreKeyword, "_") CARBON_KEYWORD_TOKEN(VarKeyword, "var") diff --git a/parser/BUILD b/parser/BUILD index a21960c11cfe..8335f5c47845 100644 --- a/parser/BUILD +++ b/parser/BUILD @@ -36,6 +36,7 @@ cc_library( hdrs = ["parse_tree.h"], deps = [ ":parse_node_kind", + ":precedence", "//diagnostics:diagnostic_emitter", "//lexer:token_kind", "//lexer:tokenized_buffer", @@ -85,3 +86,25 @@ cc_fuzz_test( "@llvm-project//llvm:Support", ], ) + +cc_library( + name = "precedence", + srcs = ["precedence.cpp"], + hdrs = ["precedence.h"], + deps = [ + "//lexer:token_kind", + "@llvm-project//llvm:Support", + ], +) + +cc_test( + name = "precedence_test", + srcs = ["precedence_test.cpp"], + deps = [ + ":precedence", + "//lexer:token_kind", + "@llvm-project//llvm:gmock", + "@llvm-project//llvm:gtest", + "@llvm-project//llvm:gtest_main", + ], +) diff --git a/parser/parse_node_kind.def b/parser/parse_node_kind.def index 8a4985ad537b..70ef0a4ed249 100644 --- a/parser/parse_node_kind.def +++ b/parser/parse_node_kind.def @@ -37,5 +37,8 @@ CARBON_PARSE_NODE_KIND(DesignatedName) CARBON_PARSE_NODE_KIND(CallExpression) CARBON_PARSE_NODE_KIND(CallExpressionComma) CARBON_PARSE_NODE_KIND(CallExpressionEnd) +CARBON_PARSE_NODE_KIND(PrefixOperator) +CARBON_PARSE_NODE_KIND(InfixOperator) +CARBON_PARSE_NODE_KIND(PostfixOperator) #undef CARBON_PARSE_NODE_KIND diff --git a/parser/parse_tree_test.cpp b/parser/parse_tree_test.cpp index fbda71a7835f..4fe01ffec68d 100644 --- a/parser/parse_tree_test.cpp +++ b/parser/parse_tree_test.cpp @@ -408,6 +408,82 @@ TEST_F(ParseTreeTest, InvalidDesignators) { MatchFileEnd()})); } +TEST_F(ParseTreeTest, Operators) { + TokenizedBuffer tokens = GetTokenizedBuffer( + "fn F() {\n" + " n = a * b + c * d = d * d << e & f - not g;\n" + " ++++n;\n" + " n++++;\n" + " a and b and c;\n" + " a and b or c;\n" + " a or b and c;\n" + " not a and not b and not c;\n" + "}"); + ParseTree tree = ParseTree::Parse(tokens, consumer); + EXPECT_TRUE(tree.HasErrors()); + + EXPECT_THAT( + tree, + MatchParseTreeNodes( + {MatchFunctionDeclaration( + MatchDeclaredName("F"), + MatchParameterList(MatchParameterListEnd()), + MatchCodeBlock( + MatchExpressionStatement(MatchInfixOperator( + MatchNameReference("n"), "=", + MatchInfixOperator( + MatchInfixOperator( + MatchInfixOperator(MatchNameReference("a"), "*", + MatchNameReference("b")), + "+", + MatchInfixOperator(MatchNameReference("c"), "*", + MatchNameReference("d"))), + "=", + MatchInfixOperator( + HasError, + MatchInfixOperator( + HasError, + MatchInfixOperator( + HasError, + MatchInfixOperator( + MatchNameReference("d"), "*", + MatchNameReference("d")), + "<<", MatchNameReference("e")), + "&", MatchNameReference("f")), + "-", + MatchPrefixOperator("not", + MatchNameReference("g")))))), + MatchExpressionStatement(MatchPrefixOperator( + "++", + MatchPrefixOperator("++", MatchNameReference("n")))), + MatchExpressionStatement(MatchPostfixOperator( + MatchPostfixOperator(MatchNameReference("n"), "++"), + "++")), + MatchExpressionStatement(MatchInfixOperator( + MatchInfixOperator(MatchNameReference("a"), "and", + MatchNameReference("b")), + "and", MatchNameReference("c"))), + MatchExpressionStatement(MatchInfixOperator( + HasError, + MatchInfixOperator(MatchNameReference("a"), "and", + MatchNameReference("b")), + "or", MatchNameReference("c"))), + MatchExpressionStatement(MatchInfixOperator( + HasError, + MatchInfixOperator(MatchNameReference("a"), "or", + MatchNameReference("b")), + "and", MatchNameReference("c"))), + MatchExpressionStatement(MatchInfixOperator( + MatchInfixOperator( + MatchPrefixOperator("not", MatchNameReference("a")), + "and", + MatchPrefixOperator("not", MatchNameReference("b"))), + "and", + MatchPrefixOperator("not", MatchNameReference("c")))), + MatchCodeBlockEnd())), + MatchFileEnd()})); +} + auto GetAndDropLine(llvm::StringRef& s) -> std::string { auto newline_offset = s.find_first_of('\n'); llvm::StringRef line = s.slice(0, newline_offset); diff --git a/parser/parser_impl.cpp b/parser/parser_impl.cpp index ba32635b6910..3849ab2f3c6e 100644 --- a/parser/parser_impl.cpp +++ b/parser/parser_impl.cpp @@ -86,6 +86,13 @@ struct UnexpectedTokenInFunctionArgs "Unexpected token in function argument list."; }; +struct OperatorRequiresParentheses + : SimpleDiagnostic { + static constexpr llvm::StringLiteral ShortName = "syntax-error"; + static constexpr llvm::StringLiteral Message = + "Parentheses are required to disambiguate operator precedence."; +}; + ParseTree::Parser::Parser(ParseTree& tree_arg, TokenizedBuffer& tokens_arg, TokenDiagnosticEmitter& emitter) : tree(tree_arg), @@ -111,7 +118,11 @@ auto ParseTree::Parser::Parse(TokenizedBuffer& tokens, Parser parser(tree, tokens, emitter); while (!parser.AtEndOfFile()) { - parser.ParseDeclaration(); + if (!parser.ParseDeclaration()) { + // We don't have an enclosing parse tree node to mark as erroneous, so + // just mark the tree as a whole. + tree.has_errors = true; + } } parser.AddLeafNode(ParseNodeKind::FileEnd(), *parser.position); @@ -441,9 +452,7 @@ auto ParseTree::Parser::ParseDeclaration() -> llvm::Optional { return *found_semi_n; } - // Nothing, not even a semicolon found. We still need to mark that an error - // occurred though. - tree.has_errors = true; + // Nothing, not even a semicolon found. return llvm::None; } @@ -583,8 +592,75 @@ auto ParseTree::Parser::ParsePostfixExpression() -> llvm::Optional { } } +auto ParseTree::Parser::ParseOperatorExpression( + llvm::Optional ambient_precedence) + -> llvm::Optional { + auto start = StartSubtree(); + + llvm::Optional lhs; + llvm::Optional lhs_precedence; + + // Check for a prefix operator. + if (auto operator_precedence = + PrecedenceGroup::ForLeading(tokens.GetKind(*position)); + !operator_precedence) { + lhs = ParsePostfixExpression(); + } else { + if (ambient_precedence && PrecedenceGroup::GetPriority( + *ambient_precedence, *operator_precedence) != + OperatorPriority::RightFirst) { + // The precedence rules don't permit this prefix operator in this + // context. Diagnose this, but carry on and parse it anyway. + emitter.EmitError(*position); + } + + auto operator_token = Consume(tokens.GetKind(*position)); + bool has_errors = !ParseOperatorExpression(*operator_precedence); + lhs = AddNode(ParseNodeKind::PrefixOperator(), operator_token, start, + has_errors); + lhs_precedence = *operator_precedence; + } + + // Consume a sequence of infix and postfix operators. + while (auto trailing_operator = + PrecedenceGroup::ForTrailing(tokens.GetKind(*position))) { + auto [operator_precedence, is_binary] = *trailing_operator; + if (ambient_precedence && PrecedenceGroup::GetPriority( + *ambient_precedence, operator_precedence) != + OperatorPriority::RightFirst) { + // The precedence rules don't permit this operator in this context. Try + // again in the enclosing expression context. + return lhs; + } + + if (lhs_precedence && + PrecedenceGroup::GetPriority(*lhs_precedence, operator_precedence) != + OperatorPriority::LeftFirst) { + // Either the LHS operator and this operator are ambiguous, or the + // LHS operaor is a unary operator that can't be nested within + // this operator. Either way, parentheses are required. + emitter.EmitError(*position); + lhs = llvm::None; + } + + auto operator_token = Consume(tokens.GetKind(*position)); + + if (is_binary) { + auto rhs = ParseOperatorExpression(operator_precedence); + lhs = AddNode(ParseNodeKind::InfixOperator(), operator_token, start, + /*has_error=*/!lhs || !rhs); + } else { + lhs = AddNode(ParseNodeKind::PostfixOperator(), operator_token, start, + /*has_error=*/!lhs); + } + lhs_precedence = operator_precedence; + } + + return lhs; +} + auto ParseTree::Parser::ParseExpression() -> llvm::Optional { - return ParsePostfixExpression(); + return ParseOperatorExpression(llvm::None); } auto ParseTree::Parser::ParseExpressionStatement() -> llvm::Optional { diff --git a/parser/parser_impl.h b/parser/parser_impl.h index 11c15cd55094..8cd12f9e58b7 100644 --- a/parser/parser_impl.h +++ b/parser/parser_impl.h @@ -11,6 +11,7 @@ #include "llvm/ADT/Optional.h" #include "parser/parse_node_kind.h" #include "parser/parse_tree.h" +#include "parser/precedence.h" namespace Carbon { @@ -154,6 +155,11 @@ class ParseTree::Parser { // - designators auto ParsePostfixExpression() -> llvm::Optional; + // Parses an expression involving operators, in a context with the given + // precedence. + auto ParseOperatorExpression(llvm::Optional precedence) + -> llvm::Optional; + // Parses an expression. auto ParseExpression() -> llvm::Optional; diff --git a/parser/precedence.cpp b/parser/precedence.cpp new file mode 100644 index 000000000000..8db1614f1e8d --- /dev/null +++ b/parser/precedence.cpp @@ -0,0 +1,267 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include "parser/precedence.h" + +#include + +namespace Carbon { + +namespace { +enum PrecedenceLevel : int8_t { + // Numeric. + NumericPrefix, + NumericPostfix, + Modulo, + Multiplicative, + Additive, + // Bitwise. + BitwisePrefix, + BitwiseAnd, + BitwiseOr, + BitwiseXor, + BitShift, + // Logical. + LogicalPrefix, + Relational, + LogicalAnd, + LogicalOr, + // Assignment. + SimpleAssignment, + CompoundAssignment, +}; +constexpr int8_t NumPrecedenceLevels = CompoundAssignment + 1; + +// A precomputed lookup table determining the relative precedence of two +// precedence groups. +struct OperatorPriorityTable { + constexpr OperatorPriorityTable() : table{} { + // Start with a list of , + // relationships. + MarkHigherThan({NumericPrefix, NumericPostfix}, + {Modulo, Multiplicative, BitShift}); + MarkHigherThan({Multiplicative}, {Additive}); + MarkHigherThan({BitwisePrefix}, + {BitwiseAnd, BitwiseOr, BitwiseXor, BitShift}); + MarkHigherThan( + {Modulo, Additive, BitwiseAnd, BitwiseOr, BitwiseXor, BitShift}, + {SimpleAssignment, CompoundAssignment, Relational}); + MarkHigherThan({Relational, LogicalPrefix}, {LogicalAnd, LogicalOr}); + + // Compute the transitive closure of the above relationships: if we parse + // `a $ b @ c` as `(a $ b) @ c` and parse `b @ c % d` as `(b @ c) % d`, + // then we will parse `a $ b @ c % d` as `((a $ b) @ c) % d` and should + // also parse `a $ bc % d` as `(a $ bc) % d`. + MakeTransitivelyClosed(); + + // Make the relation symmetric. If we parse `a $ b @ c` as `(a $ b) @ c` + // then we want to parse `a @ b $ c` as `a @ (b $ c)`. + MakeSymmetric(); + + // Fill in the diagonal, which represents operator associativity. + AddAssociativityRules(); + } + + constexpr void MarkHigherThan( + std::initializer_list higher_group, + std::initializer_list lower_group) { + for (auto higher : higher_group) { + for (auto lower : lower_group) { + table[higher][lower] = OperatorPriority::LeftFirst; + } + } + } + + constexpr void MakeTransitivelyClosed() { + // A naive algorithm compiles acceptably fast for now (~0.5s). This should + // be revisited if we see compile time problems after adding precedence + // groups; it's easy to do this faster. + bool changed = false; + do { + changed = false; + for (int8_t a = 0; a != NumPrecedenceLevels; ++a) { + for (int8_t b = 0; b != NumPrecedenceLevels; ++b) { + if (table[a][b] == OperatorPriority::LeftFirst) { + for (int8_t c = 0; c != NumPrecedenceLevels; ++c) { + if (table[b][c] == OperatorPriority::LeftFirst && + table[a][c] != OperatorPriority::LeftFirst) { + table[a][c] = OperatorPriority::LeftFirst; + changed = true; + } + } + } + } + } + } while (changed); + } + + constexpr void MakeSymmetric() { + for (int8_t a = 0; a != NumPrecedenceLevels; ++a) { + for (int8_t b = 0; b != NumPrecedenceLevels; ++b) { + if (table[a][b] == OperatorPriority::LeftFirst) { + if (table[b][a] == OperatorPriority::LeftFirst) { + throw "inconsistent lookup table entries"; + } + table[b][a] = OperatorPriority::RightFirst; + } + } + } + } + + constexpr void AddAssociativityRules() { + // Associativity rules occupy the diagonal + + // For prefix operators, RightFirst would mean `@@x` is `@(@x)` and + // Ambiguous would mean it's an error. LeftFirst is meaningless. For now we + // allow all prefix operators to be repeated. + for (PrecedenceLevel prefix : + {NumericPrefix, BitwisePrefix, LogicalPrefix}) { + table[prefix][prefix] = OperatorPriority::RightFirst; + } + + // Postfix operators are symmetric with prefix operators. + for (PrecedenceLevel postfix : {NumericPostfix}) { + table[postfix][postfix] = OperatorPriority::LeftFirst; + } + + // Traditionally-associative operators are given left-to-right + // associativity. + for (PrecedenceLevel assoc : + {Multiplicative, Additive, BitwiseAnd, BitwiseOr, BitwiseXor, + LogicalAnd, LogicalOr}) { + table[assoc][assoc] = OperatorPriority::LeftFirst; + } + + // Assignment is given right-to-left associativity in order to support + // chained assignment. + table[SimpleAssignment][SimpleAssignment] = OperatorPriority::RightFirst; + + // For other operators, there isn't an obvious answer and we require + // explicit parentheses. + } + + OperatorPriority table[NumPrecedenceLevels][NumPrecedenceLevels]; +}; +} // namespace + +auto PrecedenceGroup::ForLeading(TokenKind kind) + -> llvm::Optional { + switch (kind) { + case TokenKind::NotKeyword(): + return PrecedenceGroup(LogicalPrefix); + + case TokenKind::Minus(): + case TokenKind::MinusMinus(): + case TokenKind::PlusPlus(): + return PrecedenceGroup(NumericPrefix); + + case TokenKind::Tilde(): + return PrecedenceGroup(BitwisePrefix); + + default: + return llvm::None; + } +} + +auto PrecedenceGroup::ForTrailing(TokenKind kind) -> llvm::Optional { + switch (kind) { + // Assignment operators. + case TokenKind::Equal(): + return Trailing{.level = SimpleAssignment, .is_binary = true}; + case TokenKind::PlusEqual(): + case TokenKind::MinusEqual(): + case TokenKind::StarEqual(): + case TokenKind::SlashEqual(): + case TokenKind::PercentEqual(): + case TokenKind::AmpEqual(): + case TokenKind::PipeEqual(): + case TokenKind::GreaterGreaterEqual(): + case TokenKind::LessLessEqual(): + return Trailing{.level = CompoundAssignment, .is_binary = true}; + + // Logical operators. + case TokenKind::AndKeyword(): + return Trailing{.level = LogicalAnd, .is_binary = true}; + case TokenKind::OrKeyword(): + return Trailing{.level = LogicalOr, .is_binary = true}; + + // Bitwise operators. + case TokenKind::Amp(): + return Trailing{.level = BitwiseAnd, .is_binary = true}; + case TokenKind::Pipe(): + return Trailing{.level = BitwiseOr, .is_binary = true}; + case TokenKind::XorKeyword(): + return Trailing{.level = BitwiseXor, .is_binary = true}; + case TokenKind::GreaterGreater(): + case TokenKind::LessLess(): + return Trailing{.level = BitShift, .is_binary = true}; + + // Relational operators. + case TokenKind::EqualEqual(): + case TokenKind::ExclaimEqual(): + case TokenKind::Less(): + case TokenKind::LessEqual(): + case TokenKind::Greater(): + case TokenKind::GreaterEqual(): + case TokenKind::LessEqualGreater(): + return Trailing{.level = Relational, .is_binary = true}; + + // Addative operators. + case TokenKind::Plus(): + case TokenKind::Minus(): + return Trailing{.level = Additive, .is_binary = true}; + + // Multiplicative operators. + case TokenKind::Star(): + case TokenKind::Slash(): + return Trailing{.level = Multiplicative, .is_binary = true}; + case TokenKind::Percent(): + return Trailing{.level = Modulo, .is_binary = true}; + + // Postfix operators. + case TokenKind::MinusMinus(): + case TokenKind::PlusPlus(): + return Trailing{.level = NumericPostfix, .is_binary = false}; + + // Prefix-only operators. + case TokenKind::Tilde(): + case TokenKind::NotKeyword(): + break; + + // Symbolic tokens that might be operators eventually. + case TokenKind::Backslash(): + case TokenKind::Caret(): + case TokenKind::CaretEqual(): + case TokenKind::Comma(): + case TokenKind::TildeEqual(): + case TokenKind::Exclaim(): + case TokenKind::LessGreater(): + case TokenKind::Question(): + case TokenKind::Colon(): + break; + + // Symbolic tokens that are intentionally not operators. + case TokenKind::At(): + case TokenKind::LessMinus(): + case TokenKind::MinusGreater(): + case TokenKind::EqualGreater(): + case TokenKind::ColonEqual(): + case TokenKind::Period(): + case TokenKind::Semi(): + break; + + default: + break; + } + + return llvm::None; +} + +auto PrecedenceGroup::GetPriority(PrecedenceGroup left, PrecedenceGroup right) + -> OperatorPriority { + static constexpr OperatorPriorityTable lookup; + return lookup.table[left.level][right.level]; +} + +} // namespace Carbon diff --git a/parser/precedence.h b/parser/precedence.h new file mode 100644 index 000000000000..8038e96189fc --- /dev/null +++ b/parser/precedence.h @@ -0,0 +1,83 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#ifndef PARSER_PRECEDENCE_H_ +#define PARSER_PRECEDENCE_H_ + +#include "lexer/token_kind.h" +#include "llvm/ADT/Optional.h" + +namespace Carbon { + +// Given two operators `$` and `@`, and an expression `a $ b @ c`, how should +// the expression be parsed? +enum class OperatorPriority : int8_t { + // The left operator has higher precedence: `(a $ b) @ c`. + LeftFirst = -1, + // The expression is ambiguous. + Ambiguous = 0, + // The right operator has higher precedence: `a $ (b @ c)`. + RightFirst = 1, +}; + +enum class Associativity : int8_t { + LeftToRight = -1, + None = 0, + RightToLeft = 1 +}; + +// A precedence group associated with an operator or expression. +class PrecedenceGroup { + private: + PrecedenceGroup(int8_t level) : level(level) {} + + public: + // Objects of this type should only be constructed using the static factory + // functions below. + PrecedenceGroup() = delete; + + // Look up the operator information of the given prefix operator token, or + // return llvm::None if the given token is not a prefix operator. + static auto ForLeading(TokenKind kind) -> llvm::Optional; + + struct Trailing; + + // Look up the operator information of the given infix or postfix operator + // token, or return llvm::None if the given token is not an infix or postfix + // operator. + static auto ForTrailing(TokenKind kind) -> llvm::Optional; + + friend auto operator==(PrecedenceGroup lhs, PrecedenceGroup rhs) -> bool { + return lhs.level == rhs.level; + } + friend auto operator!=(PrecedenceGroup lhs, PrecedenceGroup rhs) -> bool { + return lhs.level != rhs.level; + } + + // Compare the precedence levels for two adjacent operators. + static auto GetPriority(PrecedenceGroup left, PrecedenceGroup right) + -> OperatorPriority; + + // Get the associativity of this precedence group. + Associativity GetAssociativity() const { + return static_cast(GetPriority(*this, *this)); + } + + private: + // The precedence level. + int8_t level; +}; + +// Precedence information for a trailing operator. +struct PrecedenceGroup::Trailing { + // The precedence level. + PrecedenceGroup level; + // `true` if this is an infix binary operator, `false` if this is a postfix + // unary operator. + bool is_binary; +}; + +} // namespace Carbon + +#endif // PARSER_PRECEDENCE_H_ diff --git a/parser/precedence_test.cpp b/parser/precedence_test.cpp new file mode 100644 index 000000000000..feafbf165f31 --- /dev/null +++ b/parser/precedence_test.cpp @@ -0,0 +1,116 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include "parser/precedence.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "lexer/token_kind.h" + +namespace Carbon { +namespace { + +using ::testing::Eq; +using ::testing::Ne; + +TEST(PrecedenceTest, OperatorsAreRecognized) { + EXPECT_TRUE(PrecedenceGroup::ForLeading(TokenKind::Minus()).hasValue()); + EXPECT_TRUE(PrecedenceGroup::ForLeading(TokenKind::Tilde()).hasValue()); + EXPECT_FALSE(PrecedenceGroup::ForLeading(TokenKind::Slash()).hasValue()); + EXPECT_FALSE(PrecedenceGroup::ForLeading(TokenKind::Identifier()).hasValue()); + + EXPECT_TRUE(PrecedenceGroup::ForTrailing(TokenKind::Minus()).hasValue()); + EXPECT_FALSE(PrecedenceGroup::ForTrailing(TokenKind::Tilde()).hasValue()); + EXPECT_TRUE(PrecedenceGroup::ForTrailing(TokenKind::Slash()).hasValue()); + EXPECT_FALSE( + PrecedenceGroup::ForTrailing(TokenKind::Identifier()).hasValue()); + + EXPECT_TRUE(PrecedenceGroup::ForTrailing(TokenKind::Minus())->is_binary); + EXPECT_FALSE( + PrecedenceGroup::ForTrailing(TokenKind::MinusMinus())->is_binary); +} + +TEST(PrecedenceTest, Associativity) { + EXPECT_THAT( + PrecedenceGroup::ForLeading(TokenKind::Minus())->GetAssociativity(), + Eq(Associativity::RightToLeft)); + EXPECT_THAT(PrecedenceGroup::ForTrailing(TokenKind::PlusPlus()) + ->level.GetAssociativity(), + Eq(Associativity::LeftToRight)); + EXPECT_THAT( + PrecedenceGroup::ForTrailing(TokenKind::Plus())->level.GetAssociativity(), + Eq(Associativity::LeftToRight)); + EXPECT_THAT(PrecedenceGroup::ForTrailing(TokenKind::Equal()) + ->level.GetAssociativity(), + Eq(Associativity::RightToLeft)); + EXPECT_THAT(PrecedenceGroup::ForTrailing(TokenKind::PlusEqual()) + ->level.GetAssociativity(), + Eq(Associativity::None)); +} + +TEST(PrecedenceTest, DirectRelations) { + EXPECT_THAT(PrecedenceGroup::GetPriority( + PrecedenceGroup::ForTrailing(TokenKind::Star())->level, + PrecedenceGroup::ForTrailing(TokenKind::Plus())->level), + Eq(OperatorPriority::LeftFirst)); + EXPECT_THAT(PrecedenceGroup::GetPriority( + PrecedenceGroup::ForTrailing(TokenKind::Plus())->level, + PrecedenceGroup::ForTrailing(TokenKind::Star())->level), + Eq(OperatorPriority::RightFirst)); + + EXPECT_THAT(PrecedenceGroup::GetPriority( + PrecedenceGroup::ForTrailing(TokenKind::Amp())->level, + PrecedenceGroup::ForTrailing(TokenKind::Less())->level), + Eq(OperatorPriority::LeftFirst)); + EXPECT_THAT(PrecedenceGroup::GetPriority( + PrecedenceGroup::ForTrailing(TokenKind::Less())->level, + PrecedenceGroup::ForTrailing(TokenKind::Amp())->level), + Eq(OperatorPriority::RightFirst)); +} + +TEST(PrecedenceTest, IndirectRelations) { + EXPECT_THAT(PrecedenceGroup::GetPriority( + PrecedenceGroup::ForTrailing(TokenKind::Star())->level, + PrecedenceGroup::ForTrailing(TokenKind::OrKeyword())->level), + Eq(OperatorPriority::LeftFirst)); + EXPECT_THAT(PrecedenceGroup::GetPriority( + PrecedenceGroup::ForTrailing(TokenKind::OrKeyword())->level, + PrecedenceGroup::ForTrailing(TokenKind::Star())->level), + Eq(OperatorPriority::RightFirst)); + + EXPECT_THAT(PrecedenceGroup::GetPriority( + *PrecedenceGroup::ForLeading(TokenKind::Tilde()), + PrecedenceGroup::ForTrailing(TokenKind::Equal())->level), + Eq(OperatorPriority::LeftFirst)); + EXPECT_THAT(PrecedenceGroup::GetPriority( + PrecedenceGroup::ForTrailing(TokenKind::Equal())->level, + *PrecedenceGroup::ForLeading(TokenKind::Tilde())), + Eq(OperatorPriority::RightFirst)); +} + +TEST(PrecedenceTest, IncomparableOperators) { + EXPECT_THAT(PrecedenceGroup::GetPriority( + *PrecedenceGroup::ForLeading(TokenKind::Tilde()), + *PrecedenceGroup::ForLeading(TokenKind::NotKeyword())), + Eq(OperatorPriority::Ambiguous)); + EXPECT_THAT(PrecedenceGroup::GetPriority( + *PrecedenceGroup::ForLeading(TokenKind::Tilde()), + *PrecedenceGroup::ForLeading(TokenKind::Minus())), + Eq(OperatorPriority::Ambiguous)); + EXPECT_THAT(PrecedenceGroup::GetPriority( + *PrecedenceGroup::ForLeading(TokenKind::Minus()), + PrecedenceGroup::ForTrailing(TokenKind::Amp())->level), + Eq(OperatorPriority::Ambiguous)); + EXPECT_THAT(PrecedenceGroup::GetPriority( + PrecedenceGroup::ForTrailing(TokenKind::Equal())->level, + PrecedenceGroup::ForTrailing(TokenKind::PipeEqual())->level), + Eq(OperatorPriority::Ambiguous)); + EXPECT_THAT(PrecedenceGroup::GetPriority( + PrecedenceGroup::ForTrailing(TokenKind::Plus())->level, + PrecedenceGroup::ForTrailing(TokenKind::Amp())->level), + Eq(OperatorPriority::Ambiguous)); +} + +} // namespace +} // namespace Carbon