| // Copyright 2018 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "src/developer/debug/zxdb/expr/expr_parser.h" |
| |
| #include <sstream> |
| |
| #include <gtest/gtest.h> |
| |
| #include "src/developer/debug/zxdb/common/string_util.h" |
| #include "src/developer/debug/zxdb/expr/expr_tokenizer.h" |
| #include "src/developer/debug/zxdb/symbols/collection.h" |
| |
| namespace zxdb { |
| |
| namespace { |
| |
| // This name looker-upper declares anything beginning with "Namespace" is a namespace, anything |
| // beginning with "Template" is a template, and anything beginning with "Type" is a type. |
| FoundName TestLookupName(const ParsedIdentifier& ident, const FindNameOptions& opts) { |
| const ParsedIdentifierComponent& comp = ident.components().back(); |
| const std::string& name = comp.name(); |
| |
| if (opts.find_namespaces && StringStartsWith(name, "Namespace")) |
| return FoundName(FoundName::kNamespace, ident.GetFullName()); |
| if (opts.find_types && StringStartsWith(name, "Type")) { |
| // Make up a random class to go with the type. |
| auto type = fxl::MakeRefCounted<Collection>(DwarfTag::kClassType); |
| type->set_assigned_name("Type"); |
| |
| // NOTE: This doesn't qualify the type with namespaces or classes present in the identifier so |
| // qualified names ("Namespace::Type") won't convert to strings properly. This could be added if |
| // necessary. |
| return FoundName(std::move(type)); |
| } |
| if (opts.find_templates && StringStartsWith(name, "Template")) { |
| if (comp.has_template()) { |
| // Assume templates with arguments are types. |
| auto collection = fxl::MakeRefCounted<Collection>(DwarfTag::kClassType); |
| // This name won't always be correct if the input has a namespace or other qualifier, but is |
| // good enough for this test. |
| collection->set_assigned_name(ident.GetFullName()); |
| return FoundName(std::move(collection)); |
| } |
| return FoundName(FoundName::kTemplate, ident.GetFullName()); |
| } |
| return FoundName(); |
| } |
| |
| } // namespace |
| |
| class ExprParserTest : public testing::Test { |
| public: |
| ExprParserTest() = default; |
| |
| // Valid after Parse() is called. |
| ExprParser& parser() { return *parser_; } |
| |
| fxl::RefPtr<ExprNode> Parse(const char* input, NameLookupCallback name_lookup) { |
| return Parse(input, ExprLanguage::kC, std::move(name_lookup)); |
| } |
| |
| fxl::RefPtr<ExprNode> Parse(const char* input, ExprLanguage lang = ExprLanguage::kC, |
| NameLookupCallback name_lookup = NameLookupCallback()) { |
| parser_.reset(); |
| |
| tokenizer_ = std::make_unique<ExprTokenizer>(input, lang); |
| if (!tokenizer_->Tokenize()) { |
| ADD_FAILURE() << "Tokenization failure: " << input; |
| return nullptr; |
| } |
| |
| parser_ = std::make_unique<ExprParser>(tokenizer_->TakeTokens(), tokenizer_->language(), |
| std::move(name_lookup)); |
| return parser_->ParseExpression(); |
| } |
| |
| // Does the parse and returns the string dump of the structure. |
| std::string GetParseString(const char* input, |
| NameLookupCallback name_lookup = NameLookupCallback()) { |
| return GetParseString(input, ExprLanguage::kC, std::move(name_lookup)); |
| } |
| |
| std::string GetParseString(const char* input, ExprLanguage lang, |
| NameLookupCallback name_lookup = NameLookupCallback()) { |
| auto root = Parse(input, lang, std::move(name_lookup)); |
| if (!root) { |
| // Expect calls to this to parse successfully. |
| if (parser_.get()) |
| ADD_FAILURE() << "Parse failure: " << parser_->err().msg(); |
| return std::string(); |
| } |
| |
| std::ostringstream out; |
| root->Print(out, 0); |
| return out.str(); |
| } |
| |
| private: |
| std::unique_ptr<ExprTokenizer> tokenizer_; |
| std::unique_ptr<ExprParser> parser_; |
| }; |
| |
| TEST_F(ExprParserTest, Empty) { |
| auto result = Parse(""); |
| ASSERT_FALSE(result); |
| EXPECT_EQ("Expected expression instead of end of input.", parser().err().msg()); |
| |
| result = Parse(" "); |
| ASSERT_FALSE(result); |
| EXPECT_EQ("Expected expression instead of end of input.", parser().err().msg()); |
| |
| result = Parse("; "); |
| ASSERT_FALSE(result); |
| EXPECT_EQ("Empty expression not permitted here.", parser().err().msg()); |
| } |
| |
| TEST_F(ExprParserTest, Block) { |
| EXPECT_EQ("BLOCK\n", GetParseString("{}")); |
| EXPECT_EQ( |
| "BLOCK\n" |
| " LITERAL(1)\n", |
| GetParseString("{ 1; }")); |
| EXPECT_EQ( |
| "BLOCK\n" |
| " BINARY_OP(+)\n" |
| " LITERAL(1)\n" |
| " IDENTIFIER(\"n\")\n" |
| " LITERAL(2)\n" |
| " LITERAL(3)\n", |
| GetParseString("{ 1+n;2; \n3 ;}")); |
| |
| // Last Rust expression doesn't require a semicolon. |
| EXPECT_EQ( |
| "BLOCK\n" |
| " LITERAL(1)\n", |
| GetParseString("{1}", ExprLanguage::kRust)); |
| EXPECT_EQ( |
| "BLOCK\n" |
| " LITERAL(1)\n" |
| " LITERAL(2)\n", |
| GetParseString("{1;2}", ExprLanguage::kRust)); |
| |
| // Duplicate statement separators. |
| EXPECT_EQ( |
| "BLOCK\n" |
| " LITERAL(1)\n" |
| " LITERAL(2)\n", |
| GetParseString("{;1;;2;}", ExprLanguage::kRust)); |
| |
| // Nested blocks. |
| EXPECT_EQ( |
| "BLOCK\n" |
| " BLOCK\n" |
| " BLOCK\n" |
| " LITERAL(1)\n" |
| " LITERAL(2)\n" |
| " BLOCK\n", |
| GetParseString("{{{}};1;;2;{;}}", ExprLanguage::kRust)); |
| |
| // C requires a semicolon as the last element. |
| auto result = Parse("{1}"); |
| EXPECT_FALSE(result); |
| EXPECT_EQ("Expected ';'.", parser().err().msg()); |
| result = Parse("{1;2}"); |
| EXPECT_FALSE(result); |
| EXPECT_EQ("Expected ';'.", parser().err().msg()); |
| |
| // Rust blocks as expressions. Our parser will currenly accept this in C as well but it will get |
| // rejected during execution since blocks don't return anything. |
| EXPECT_EQ( |
| "BINARY_OP(=)\n" |
| " IDENTIFIER(\"a\")\n" |
| " BLOCK\n" |
| " LITERAL(1)\n" |
| " LITERAL(2)\n", |
| GetParseString("a = {1;2}", ExprLanguage::kRust)); |
| |
| // Missing terminating "}". |
| result = Parse("{1;"); |
| EXPECT_FALSE(result); |
| EXPECT_EQ("Expected '}'. Hit the end of input instead.", parser().err().msg()); |
| |
| // No separator between elements. |
| result = Parse("{1 {}}"); |
| EXPECT_FALSE(result); |
| EXPECT_EQ("Expected ';'.", parser().err().msg()); |
| } |
| |
| TEST_F(ExprParserTest, Identifier) { |
| auto result = Parse("name"); |
| ASSERT_TRUE(result); |
| |
| const IdentifierExprNode* ident = result->AsIdentifier(); |
| ASSERT_TRUE(ident); |
| EXPECT_EQ("name", ident->ident().GetFullName()); |
| } |
| |
| TEST_F(ExprParserTest, Dot) { |
| auto result = Parse("base.member"); |
| ASSERT_TRUE(result); |
| |
| const MemberAccessExprNode* access = result->AsMemberAccess(); |
| ASSERT_TRUE(access); |
| EXPECT_EQ(ExprTokenType::kDot, access->accessor().type()); |
| EXPECT_EQ(".", access->accessor().value()); |
| |
| // Left side is the "base" identifier. |
| const IdentifierExprNode* base = access->left()->AsIdentifier(); |
| ASSERT_TRUE(base); |
| EXPECT_EQ("base", base->ident().GetFullName()); |
| |
| // Member name. |
| EXPECT_EQ("member", access->member().GetFullName()); |
| } |
| |
| TEST_F(ExprParserTest, DotNumber) { |
| auto result = Parse("base.0", ExprLanguage::kRust); |
| ASSERT_TRUE(result); |
| |
| const MemberAccessExprNode* access = result->AsMemberAccess(); |
| ASSERT_TRUE(access); |
| EXPECT_EQ(ExprTokenType::kDot, access->accessor().type()); |
| EXPECT_EQ(".", access->accessor().value()); |
| |
| // Left side is the "base" identifier. |
| const IdentifierExprNode* base = access->left()->AsIdentifier(); |
| ASSERT_TRUE(base); |
| EXPECT_EQ("base", base->ident().GetFullName()); |
| |
| // Member name. |
| EXPECT_EQ("0", access->member().GetFullName()); |
| } |
| |
| TEST_F(ExprParserTest, DotNumberNoHex) { |
| auto result = Parse("base.0xA", ExprLanguage::kRust); |
| ASSERT_FALSE(result); |
| |
| EXPECT_EQ("Expected identifier for right-hand-side of \".\".", parser().err().msg()); |
| EXPECT_EQ(4u, parser().error_token().byte_offset()); |
| EXPECT_EQ(".", parser().error_token().value()); |
| } |
| |
| TEST_F(ExprParserTest, DotNumberNoRust) { |
| auto result = Parse("base.0"); |
| ASSERT_FALSE(result); |
| |
| // This is parsed as an identifier followed by a floating-point number. |
| EXPECT_EQ("Unexpected input, did you forget an operator?", parser().err().msg()); |
| EXPECT_EQ(4u, parser().error_token().byte_offset()); |
| EXPECT_EQ(".0", parser().error_token().value()); |
| } |
| |
| TEST_F(ExprParserTest, AccessorAtEnd) { |
| auto result = Parse("base. "); |
| ASSERT_FALSE(result); |
| |
| EXPECT_EQ("Expected identifier for right-hand-side of \".\".", parser().err().msg()); |
| |
| EXPECT_EQ(4u, parser().error_token().byte_offset()); |
| EXPECT_EQ(".", parser().error_token().value()); |
| } |
| |
| TEST_F(ExprParserTest, BadAccessorMemberName) { |
| auto result = Parse("base->23"); |
| ASSERT_FALSE(result); |
| |
| EXPECT_EQ("Expected identifier for right-hand-side of \"->\".", parser().err().msg()); |
| |
| // This error reports the "->" as the location, one could also imagine reporting the right-side |
| // token (if any) instead. |
| EXPECT_EQ(4u, parser().error_token().byte_offset()); |
| EXPECT_EQ("->", parser().error_token().value()); |
| } |
| |
| TEST_F(ExprParserTest, Arrow) { |
| auto result = Parse("base->member"); |
| ASSERT_TRUE(result); |
| |
| const MemberAccessExprNode* access = result->AsMemberAccess(); |
| ASSERT_TRUE(access); |
| EXPECT_EQ(ExprTokenType::kArrow, access->accessor().type()); |
| EXPECT_EQ("->", access->accessor().value()); |
| |
| // Left side is the "base" identifier. |
| const IdentifierExprNode* base = access->left()->AsIdentifier(); |
| ASSERT_TRUE(base); |
| EXPECT_EQ("base", base->ident().GetFullName()); |
| |
| // Member name. |
| EXPECT_EQ("member", access->member().GetFullName()); |
| |
| // Arrow with no name. |
| result = Parse("base->"); |
| ASSERT_FALSE(result); |
| EXPECT_EQ("Expected identifier for right-hand-side of \"->\".", parser().err().msg()); |
| } |
| |
| TEST_F(ExprParserTest, NestedDotArrow) { |
| // These should be left-associative so do the "." first, then the "->". When evaluating the tree, |
| // one first evaluates the left side of the accessor, then the right, which is why it looks |
| // backwards in the dump. |
| EXPECT_EQ( |
| "ACCESSOR(->)\n" |
| " ACCESSOR(.)\n" |
| " IDENTIFIER(\"foo\")\n" |
| " bar\n" |
| " baz\n", |
| GetParseString("foo.bar->baz")); |
| } |
| |
| TEST_F(ExprParserTest, UnexpectedInput) { |
| auto result = Parse("foo 5"); |
| ASSERT_FALSE(result); |
| |
| EXPECT_EQ("Unexpected input, did you forget an operator?", parser().err().msg()); |
| EXPECT_EQ(4u, parser().error_token().byte_offset()); |
| } |
| |
| TEST_F(ExprParserTest, ArrayAccess) { |
| EXPECT_EQ( |
| "ARRAY_ACCESS\n" |
| " ARRAY_ACCESS\n" |
| " ACCESSOR(->)\n" |
| " ACCESSOR(.)\n" |
| " IDENTIFIER(\"foo\")\n" |
| " bar\n" |
| " baz\n" |
| " LITERAL(34)\n" |
| " IDENTIFIER(\"bar\")\n", |
| GetParseString("foo.bar->baz[34][bar]")); |
| |
| // Empty array access is an error. |
| auto result = Parse("foo[]"); |
| ASSERT_FALSE(result); |
| EXPECT_EQ("Unexpected token ']'.", parser().err().msg()); |
| } |
| |
| TEST_F(ExprParserTest, DereferenceAndAddress) { |
| EXPECT_EQ( |
| "DEREFERENCE\n" |
| " IDENTIFIER(\"foo\")\n", |
| GetParseString("*foo")); |
| |
| EXPECT_EQ( |
| "ADDRESS_OF\n" |
| " IDENTIFIER(\"foo\")\n", |
| GetParseString("&foo")); |
| |
| // "*" and "&" should be right-associative with respect to each other but lower precedence than -> |
| // and []. |
| EXPECT_EQ( |
| "ADDRESS_OF\n" |
| " DEREFERENCE\n" |
| " ADDRESS_OF\n" |
| " ARRAY_ACCESS\n" |
| " ACCESSOR(->)\n" |
| " IDENTIFIER(\"foo\")\n" |
| " bar\n" |
| " LITERAL(1)\n", |
| GetParseString("&*&foo->bar[1]")); |
| |
| // "*" by itself is an error. |
| auto result = Parse("*"); |
| ASSERT_FALSE(result); |
| EXPECT_EQ("Expected expression instead of end of input.", parser().err().msg()); |
| EXPECT_EQ(0u, parser().error_token().byte_offset()); |
| } |
| |
| // () should override the default precedence of other operators. |
| TEST_F(ExprParserTest, Parens) { |
| EXPECT_EQ( |
| "ADDRESS_OF\n" |
| " ARRAY_ACCESS\n" |
| " DEREFERENCE\n" |
| " ACCESSOR(->)\n" |
| " ADDRESS_OF\n" |
| " IDENTIFIER(\"foo\")\n" |
| " bar\n" |
| " LITERAL(1)\n", |
| GetParseString("(&(*(&foo)->bar)[1])")); |
| } |
| |
| // This test covers that the extent of number tokens are identified properly. Actually converting |
| // the strings to integers should be covered by the number parser tests. |
| TEST_F(ExprParserTest, IntegerLiterals) { |
| EXPECT_EQ("LITERAL(5)\n", GetParseString("5")); |
| EXPECT_EQ("LITERAL(5ull)\n", GetParseString(" 5ull")); |
| EXPECT_EQ("LITERAL(0xAbC)\n", GetParseString("0xAbC")); |
| EXPECT_EQ("LITERAL(0o551)\n", GetParseString(" 0o551 ")); |
| EXPECT_EQ("LITERAL(0123)\n", GetParseString("0123")); |
| } |
| |
| // Similar to IntegerLiterals tests, this just covers basic integration. |
| TEST_F(ExprParserTest, StringLiterals) { |
| EXPECT_EQ("LITERAL(foo)\n", GetParseString("\"foo\"")); |
| EXPECT_EQ("LITERAL(foo\"bar)\n", GetParseString("R\"(foo\"bar)\"")); |
| EXPECT_EQ( |
| "BINARY_OP(+)\n" |
| " LITERAL(hello)\n" |
| " LITERAL(world)\n", |
| GetParseString(" \"hello\" + \"world\"")); |
| } |
| |
| TEST_F(ExprParserTest, UnaryMath) { |
| EXPECT_EQ( |
| "UNARY(-)\n" |
| " LITERAL(5)\n", |
| GetParseString("-5")); |
| EXPECT_EQ( |
| "UNARY(-)\n" |
| " LITERAL(5)\n", |
| GetParseString(" - 5 ")); |
| |
| EXPECT_EQ( |
| "UNARY(!)\n" |
| " IDENTIFIER(\"foo\")\n", |
| GetParseString("!foo ")); |
| |
| EXPECT_EQ( |
| "UNARY(-)\n" |
| " DEREFERENCE\n" |
| " IDENTIFIER(\"foo\")\n", |
| GetParseString("-*foo")); |
| |
| EXPECT_EQ("IDENTIFIER(\"~foo\")\n", GetParseString("~foo")); |
| EXPECT_EQ( |
| "UNARY(~)\n" |
| " LITERAL(21)\n", |
| GetParseString("~21")); |
| EXPECT_EQ( |
| "UNARY(~)\n" |
| " IDENTIFIER(\"foo\")\n", |
| GetParseString("~ foo")); |
| |
| // "-" by itself is an error. |
| auto result = Parse("-"); |
| ASSERT_FALSE(result); |
| EXPECT_EQ("Expected expression for '-'.", parser().err().msg()); |
| EXPECT_EQ(0u, parser().error_token().byte_offset()); |
| } |
| |
| TEST_F(ExprParserTest, AndOr) { |
| EXPECT_EQ( |
| "BINARY_OP(&&)\n" |
| " BINARY_OP(&)\n" |
| " ADDRESS_OF\n" |
| " IDENTIFIER(\"a\")\n" |
| " ADDRESS_OF\n" |
| " IDENTIFIER(\"b\")\n" |
| " BINARY_OP(&)\n" |
| " IDENTIFIER(\"c\")\n" |
| " IDENTIFIER(\"d\")\n", |
| GetParseString("& a & & b && c & d")); |
| EXPECT_EQ( |
| "BINARY_OP(||)\n" |
| " BINARY_OP(|)\n" |
| " IDENTIFIER(\"a\")\n" |
| " IDENTIFIER(\"b\")\n" |
| " BINARY_OP(|)\n" |
| " IDENTIFIER(\"c\")\n" |
| " IDENTIFIER(\"d\")\n", |
| GetParseString("a | b || c | d")); |
| EXPECT_EQ( |
| "BINARY_OP(||)\n" |
| " BINARY_OP(&&)\n" |
| " IDENTIFIER(\"a\")\n" |
| " IDENTIFIER(\"b\")\n" |
| " BINARY_OP(&&)\n" |
| " IDENTIFIER(\"c\")\n" |
| " IDENTIFIER(\"d\")\n", |
| GetParseString("a && b || c && d")); |
| } |
| |
| TEST_F(ExprParserTest, Identifiers) { |
| EXPECT_EQ("IDENTIFIER(\"foo\")\n", GetParseString("foo")); |
| EXPECT_EQ("IDENTIFIER(::\"foo\")\n", GetParseString("::foo")); |
| EXPECT_EQ("IDENTIFIER(::\"foo\"; ::\"~bar\")\n", GetParseString("::foo :: ~bar")); |
| |
| auto result = Parse("::"); |
| ASSERT_FALSE(result); |
| EXPECT_EQ("Expected name after '::'.", parser().err().msg()); |
| |
| result = Parse(":: :: name"); |
| ASSERT_FALSE(result); |
| EXPECT_EQ("Could not identify thing to the left of '::' as a type or namespace.", |
| parser().err().msg()); |
| |
| result = Parse("foo bar"); |
| ASSERT_FALSE(result); |
| EXPECT_EQ("Unexpected identifier, did you forget an operator?", parser().err().msg()); |
| |
| // It's valid to have identifiers with colons in them to access class members (this is how you |
| // provide an explicit base class). |
| /* TODO(brettw) convert an accessor to use an Identifier for the name so this |
| test works. |
| EXPECT_EQ( |
| "ACCESSOR(->)\n" |
| " IDENTIFIER(\"foo\")\n", |
| GetParseString("foo->Baz::bar")); |
| */ |
| } |
| |
| TEST_F(ExprParserTest, SpecialIdentifiers) { |
| // Most special identifier parsing is tested in the dedicated special identifier parser tests. |
| // This checks that the integration is correct. |
| EXPECT_EQ("IDENTIFIER(\"ns\"; ::\"$anon\"; ::\"Foo\")\n", GetParseString("ns::$anon::Foo")); |
| EXPECT_EQ("IDENTIFIER(\"$main\")\n", GetParseString("$main")); |
| EXPECT_EQ( |
| "BINARY_OP(+)\n" |
| " BINARY_OP(+)\n" |
| " LITERAL(2)\n" |
| " IDENTIFIER(\"$plt(foo_bar)\")\n" |
| " LITERAL(2)\n", |
| GetParseString("2+$plt(foo_bar)+2")); |
| |
| // For escaping, the identifier won't contain the escaping characters, these are for parsing and |
| // output only. |
| EXPECT_EQ("IDENTIFIER(\"{{impl}}\"; ::\"some(crazyness)$here)\")\n", |
| GetParseString("$({{impl}})::$(some(crazyness)$here\\))")); |
| } |
| |
| TEST_F(ExprParserTest, CppOperators) { |
| EXPECT_EQ("IDENTIFIER(\"Class\"; ::\"operator>>\")\n", GetParseString("Class::operator>>")); |
| EXPECT_EQ("IDENTIFIER(\"Class\"; ::\"operator()\")\n", GetParseString("Class :: operator ()")); |
| EXPECT_EQ( |
| "BINARY_OP(>)\n" |
| " IDENTIFIER(\"Class\"; ::\"operator>>\")\n" |
| " IDENTIFIER(\"operator<<\")\n", |
| GetParseString("Class::operator>>>operator<<")); |
| |
| // Type conversion operator. To parse this correctly both the class name and the destination |
| // type name must be known as types to the parser. |
| EXPECT_EQ("IDENTIFIER(\"Type\"; ::\"operator const Type*\")\n", |
| GetParseString("Type::operator const Type *", &TestLookupName)); |
| |
| // We allow invalid operator names and just treat them as a literal "operator" identifier to allow |
| // C variables using that name. |
| EXPECT_EQ("IDENTIFIER(\"operator\")\n", GetParseString("operator")); |
| EXPECT_EQ("IDENTIFIER(\"operator\")\n", GetParseString("(operator)")); |
| EXPECT_EQ( |
| "ACCESSOR(.)\n" |
| " IDENTIFIER(\"foo\")\n" |
| " operator\n", |
| GetParseString("foo.operator")); |
| } |
| |
| // Tests << and >> operators. They are challenging because this can also appear in template |
| // expressions and are parsed as something else. |
| TEST_F(ExprParserTest, Shift) { |
| // This is parsed as (2 << 2) < (static_cast<Template<int>>(2) >> 2) > 2" with the < and > then |
| // parsed left-to-right. |
| EXPECT_EQ( |
| "BINARY_OP(>)\n" |
| " BINARY_OP(<)\n" |
| " BINARY_OP(<<)\n" |
| " LITERAL(2)\n" |
| " LITERAL(2)\n" |
| " BINARY_OP(>>)\n" |
| " CAST(static_cast)\n" |
| " TYPE(Template<int>)\n" |
| " LITERAL(2)\n" |
| " LITERAL(2)\n" |
| " LITERAL(2)\n", |
| GetParseString("2<<2<static_cast<Template<int>>(2)>>2>2", &TestLookupName)); |
| |
| EXPECT_EQ( |
| "BINARY_OP(>>=)\n" |
| " BINARY_OP(<<=)\n" |
| " IDENTIFIER(\"i\")\n" |
| " LITERAL(5)\n" |
| " LITERAL(6)\n", |
| GetParseString("i <<= 5 >>= 6", &TestLookupName)); |
| |
| // Some invalid shift combinations. |
| auto result = Parse("a << = 2"); |
| ASSERT_FALSE(result); |
| EXPECT_EQ("Unexpected token '='.", parser().err().msg()); |
| |
| result = Parse("a > >= 2"); |
| ASSERT_FALSE(result); |
| EXPECT_EQ("Unexpected token '>='.", parser().err().msg()); |
| |
| result = Parse("a>>>2"); |
| ASSERT_FALSE(result); |
| EXPECT_EQ("Unexpected token '>'.", parser().err().msg()); |
| } |
| |
| TEST_F(ExprParserTest, FunctionCall) { |
| // Simple call with no args. |
| EXPECT_EQ( |
| "FUNCTIONCALL\n" |
| " IDENTIFIER(\"Call\")\n", |
| GetParseString("Call()")); |
| |
| // Scoped call with namespaces and templates. |
| EXPECT_EQ( |
| "FUNCTIONCALL\n" |
| " IDENTIFIER(\"ns\"; ::\"Foo\",<\"int\">; ::\"GetCurrent\")\n", |
| GetParseString("ns::Foo<int>::GetCurrent()")); |
| |
| // One arg. |
| EXPECT_EQ( |
| "FUNCTIONCALL\n" |
| " IDENTIFIER(\"Call\")\n" |
| " LITERAL(42)\n", |
| GetParseString("Call(42)")); |
| |
| // Several complex args and a nested call. |
| EXPECT_EQ( |
| "FUNCTIONCALL\n" |
| " IDENTIFIER(\"Call\")\n" |
| " IDENTIFIER(\"a\")\n" |
| " BINARY_OP(=)\n" |
| " IDENTIFIER(\"b\")\n" |
| " LITERAL(5)\n" |
| " FUNCTIONCALL\n" |
| " IDENTIFIER(\"OtherCall\")\n", |
| GetParseString("Call(a, b = 5, OtherCall())")); |
| |
| // Function call on an object with ".". |
| EXPECT_EQ( |
| "FUNCTIONCALL\n" |
| " ACCESSOR(.)\n" |
| " ACCESSOR(.)\n" |
| " IDENTIFIER(\"foo\")\n" |
| " bar\n" |
| " Baz\n" |
| " IDENTIFIER(\"a\")\n", |
| GetParseString("foo.bar.Baz(a)")); |
| |
| // Function call on nested stuff with "->". |
| EXPECT_EQ( |
| "FUNCTIONCALL\n" |
| " ACCESSOR(->)\n" |
| " BINARY_OP(+)\n" |
| " IDENTIFIER(\"a\")\n" |
| " IDENTIFIER(\"b\")\n" |
| " Call\n", |
| GetParseString("(a + b)-> Call()")); |
| |
| // Unmatched "(" error. |
| auto result = Parse("Call(a, "); |
| ASSERT_FALSE(result); |
| EXPECT_EQ("Expected expression instead of end of input.", parser().err().msg()); |
| |
| // Arguments not separated by commas. |
| result = Parse("Call(a b)"); |
| ASSERT_FALSE(result); |
| EXPECT_EQ("Unexpected identifier, did you forget an operator?", parser().err().msg()); |
| |
| // Empty parameter |
| result = Parse("Call(a, , b)"); |
| ASSERT_FALSE(result); |
| EXPECT_EQ("Unexpected token ','.", parser().err().msg()); |
| |
| // Trailing comma. |
| result = Parse("Call(a,)"); |
| ASSERT_FALSE(result); |
| EXPECT_EQ("Unexpected token ')'.", parser().err().msg()); |
| |
| // Thing on the left is not an identifier. |
| result = Parse("5()"); |
| ASSERT_FALSE(result); |
| EXPECT_EQ("Unexpected '('.", parser().err().msg()); |
| } |
| |
| TEST_F(ExprParserTest, Templates) { |
| EXPECT_EQ(R"(IDENTIFIER("foo",<>))" |
| "\n", |
| GetParseString("foo<>")); |
| EXPECT_EQ(R"(IDENTIFIER("foo",<"Foo">))" |
| "\n", |
| GetParseString("foo<Foo>")); |
| EXPECT_EQ(R"(IDENTIFIER("foo",<"Foo", "5">))" |
| "\n", |
| GetParseString("foo< Foo,5 >")); |
| EXPECT_EQ( |
| R"(IDENTIFIER("std"; ::"map",<"Key", "Value", "std::less<Key>", "std::allocator<std::pair<Key const, Value>>">; ::"insert"))" |
| "\n", |
| GetParseString("std::map<Key, Value, std::less<Key>, " |
| "std::allocator<std::pair<Key const, Value>>>::insert")); |
| |
| // Unmatched "<" error. This is generated by the parser because it's expecting the match for the |
| // outer level. |
| auto result = Parse("std::map<Key, Value"); |
| ASSERT_FALSE(result); |
| EXPECT_EQ("Expected '>' to match. Hit the end of input instead.", parser().err().msg()); |
| |
| // This unmatched token is generated by the template type skipper which is why the error message |
| // is slightly different (both are OK). |
| result = Parse("std::map<key[, value"); |
| ASSERT_FALSE(result); |
| EXPECT_EQ("Unmatched '['.", parser().err().msg()); |
| |
| // Duplicate template spec. This will actually be parsed as "less than" and "greater than" and it |
| // will look like "greater than" is missing the right-hand-side. |
| result = Parse("Foo<Bar><Baz>"); |
| ASSERT_FALSE(result); |
| EXPECT_EQ("Expected expression after '>'.", parser().err().msg()); |
| |
| // Empty value. |
| result = Parse("Foo<1,,2>"); |
| ASSERT_FALSE(result); |
| EXPECT_EQ("Expected template parameter.", parser().err().msg()); |
| |
| // Trailing comma. |
| result = Parse("Foo<Bar,>"); |
| ASSERT_FALSE(result); |
| EXPECT_EQ("Expected template parameter.", parser().err().msg()); |
| } |
| |
| TEST_F(ExprParserTest, BinaryOp) { |
| EXPECT_EQ( |
| "BINARY_OP(=)\n" |
| " IDENTIFIER(\"a\")\n" |
| " BINARY_OP(+)\n" |
| " LITERAL(23)\n" |
| " BINARY_OP(*)\n" |
| " IDENTIFIER(\"j\")\n" |
| " BINARY_OP(+)\n" |
| " IDENTIFIER(\"a\")\n" |
| " LITERAL(1)\n", |
| GetParseString("a = 23 + j * (a + 1)")); |
| } |
| |
| TEST_F(ExprParserTest, ArraySize) { |
| // This is converting something to an array of size 3, getting a value out, and adding to it. |
| EXPECT_EQ( |
| "BINARY_OP(+)\n" |
| " BINARY_OP(@)\n" |
| " IDENTIFIER(\"a\")\n" |
| " ARRAY_ACCESS\n" |
| " LITERAL(3)\n" |
| " LITERAL(1)\n" |
| " LITERAL(2)\n", |
| GetParseString("a@3[1] + 2")); |
| |
| EXPECT_EQ( |
| "BINARY_OP(@)\n" |
| " IDENTIFIER(\"a\")\n" |
| " BINARY_OP(+)\n" |
| " IDENTIFIER(\"m\")\n" |
| " LITERAL(2)\n", |
| GetParseString("a@(m+2)")); |
| } |
| |
| TEST_F(ExprParserTest, Comparison) { |
| EXPECT_EQ( |
| "BINARY_OP(!=)\n" |
| " IDENTIFIER(\"a\",<\"int\">; ::\"foo\")\n" |
| " LITERAL(0)\n", |
| GetParseString("a<int>::foo != 0")); |
| |
| EXPECT_EQ( |
| "BINARY_OP(&&)\n" |
| " BINARY_OP(<)\n" |
| " LITERAL(1)\n" |
| " LITERAL(2)\n" |
| " BINARY_OP(>)\n" |
| " IDENTIFIER(\"a\")\n" |
| " LITERAL(4)\n", |
| GetParseString("1 < 2 && a > 4")); |
| |
| EXPECT_EQ( |
| "BINARY_OP(||)\n" |
| " BINARY_OP(<=)\n" |
| " LITERAL(1)\n" |
| " LITERAL(2.3)\n" |
| " BINARY_OP(>=)\n" |
| " IDENTIFIER(\"a\")\n" |
| " LITERAL(4e9f)\n", |
| GetParseString("1 <= 2.3 || a >= 4e9f")); |
| |
| EXPECT_EQ( |
| "BINARY_OP(<=>)\n" |
| " IDENTIFIER(\"a\")\n" |
| " LITERAL(4)\n", |
| GetParseString("a <=> 4")); |
| } |
| |
| // Tests parsing identifier names that require lookups from the symbol system. See TestLookupName |
| // above for the mocked symbol rules. |
| TEST_F(ExprParserTest, NamesWithSymbolLookup) { |
| // Bare namespace is an error. |
| auto result = Parse("Namespace", &TestLookupName); |
| ASSERT_FALSE(result); |
| EXPECT_EQ("Expected expression after namespace name.", parser().err().msg()); |
| |
| // Bare template is an error. |
| result = Parse("Template", &TestLookupName); |
| ASSERT_FALSE(result); |
| EXPECT_EQ("Expected template args after template name.", parser().err().msg()); |
| |
| // Nothing after "::" |
| result = Parse("Namespace::", &TestLookupName); |
| ASSERT_FALSE(result); |
| EXPECT_EQ("Expected name after '::'.", parser().err().msg()); |
| |
| // Can't put a template on a type that's not a template. |
| result = Parse("Type<int>", &TestLookupName); |
| ASSERT_FALSE(result); |
| // This error message might change with future type support because it might look like a |
| // comparison between a type and an int. |
| EXPECT_EQ("Template parameters not valid on this object type.", parser().err().msg()); |
| |
| // Can't put a template on a namespace. |
| result = Parse("Namespace<int>", &TestLookupName); |
| ASSERT_FALSE(result); |
| EXPECT_EQ("Template parameters not valid on this object type.", parser().err().msg()); |
| |
| // Good type name. |
| EXPECT_EQ( |
| "FUNCTIONCALL\n" |
| " IDENTIFIER(\"Namespace\"; ::\"Template\",<\"int\">; ::\"fn\")\n", |
| GetParseString("Namespace::Template<int>::fn()", &TestLookupName)); |
| } |
| |
| TEST_F(ExprParserTest, TrueFalse) { |
| EXPECT_EQ("LITERAL(true)\n", GetParseString("true")); |
| EXPECT_EQ("LITERAL(false)\n", GetParseString("false")); |
| EXPECT_EQ( |
| "BINARY_OP(&&)\n" |
| " LITERAL(false)\n" |
| " LITERAL(true)\n", |
| GetParseString("false&&true")); |
| } |
| |
| TEST_F(ExprParserTest, Types) { |
| EXPECT_EQ("TYPE(Type)\n", GetParseString("Type", &TestLookupName)); |
| EXPECT_EQ("IDENTIFIER(\"NotType\")\n", GetParseString("NotType", &TestLookupName)); |
| |
| EXPECT_EQ("TYPE(const Type)\n", GetParseString("const Type", &TestLookupName)); |
| EXPECT_EQ("TYPE(const Type)\n", GetParseString("Type const", &TestLookupName)); |
| |
| // It would be better it this printed as "const volatile Type" but our heuristic for moving |
| // modifiers to the beginning isn't good enough. |
| EXPECT_EQ("TYPE(volatile Type const)\n", GetParseString("const volatile Type", &TestLookupName)); |
| |
| // Duplicate const qualifications. |
| auto result = Parse("const Type const", &TestLookupName); |
| ASSERT_FALSE(result); |
| EXPECT_EQ("Duplicate 'const' type qualification.", parser().err().msg()); |
| result = Parse("const const Type", &TestLookupName); |
| ASSERT_FALSE(result); |
| EXPECT_EQ("Duplicate 'const' type qualification.", parser().err().msg()); |
| |
| EXPECT_EQ("TYPE(Type*)\n", GetParseString("Type*", &TestLookupName)); |
| EXPECT_EQ("TYPE(Type**)\n", GetParseString("Type * *", &TestLookupName)); |
| EXPECT_EQ("TYPE(Type&&)\n", GetParseString("Type &&", &TestLookupName)); |
| EXPECT_EQ("TYPE(Type&**)\n", GetParseString("Type&**", &TestLookupName)); |
| EXPECT_EQ("TYPE(volatile Type* restrict* const)\n", |
| GetParseString("Type volatile *restrict* const", &TestLookupName)); |
| |
| // "const" should force us into type mode. |
| result = Parse("const NonType", &TestLookupName); |
| ASSERT_FALSE(result); |
| EXPECT_EQ("Expected a type name but could not find a type named 'NonType'.", |
| parser().err().msg()); |
| |
| // Try sizeof() with both a type and a non-type. |
| EXPECT_EQ( |
| "SIZEOF\n" |
| " TYPE(Type*)\n", |
| GetParseString("sizeof(Type*)", &TestLookupName)); |
| EXPECT_EQ( |
| "SIZEOF\n" |
| " IDENTIFIER(\"foo\")\n", |
| GetParseString("sizeof(foo)", &TestLookupName)); |
| } |
| |
| TEST_F(ExprParserTest, C_Cast) { |
| EXPECT_EQ( |
| "CAST(C)\n" |
| " TYPE(Type)\n" |
| " IDENTIFIER(\"a\")\n", |
| GetParseString("(Type)(a)", &TestLookupName)); |
| |
| EXPECT_EQ( |
| "BINARY_OP(&&)\n" |
| " CAST(C)\n" |
| " TYPE(Type*)\n" |
| " IDENTIFIER(\"a\")\n" |
| " IDENTIFIER(\"b\")\n", |
| GetParseString("(Type*)a && b", &TestLookupName)); |
| |
| EXPECT_EQ( |
| "CAST(C)\n" |
| " TYPE(Type)\n" |
| " ACCESSOR(->)\n" |
| " ARRAY_ACCESS\n" |
| " IDENTIFIER(\"a\")\n" |
| " LITERAL(0)\n" |
| " b\n", |
| GetParseString("(Type)a[0]->b", &TestLookupName)); |
| |
| // Looks like a cast but it's not a type. |
| auto result = Parse("(NotType)a", &TestLookupName); |
| EXPECT_FALSE(result); |
| EXPECT_EQ("Unexpected input, did you forget an operator?", parser().err().msg()); |
| } |
| |
| TEST_F(ExprParserTest, RustCast) { |
| EXPECT_EQ( |
| "CAST(Rust)\n" |
| " TYPE(Type)\n" |
| " IDENTIFIER(\"a\")\n", |
| GetParseString("a as Type", ExprLanguage::kRust, &TestLookupName)); |
| |
| EXPECT_EQ( |
| "BINARY_OP(&&)\n" |
| " CAST(Rust)\n" |
| " TYPE(Type*)\n" |
| " IDENTIFIER(\"a\")\n" |
| " IDENTIFIER(\"b\")\n", |
| GetParseString("a as *Type && b", ExprLanguage::kRust, &TestLookupName)); |
| |
| EXPECT_EQ( |
| "BINARY_OP(&&)\n" |
| " CAST(Rust)\n" |
| " TYPE(Type*******)\n" |
| " IDENTIFIER(\"a\")\n" |
| " IDENTIFIER(\"b\")\n", |
| GetParseString("a as &mut &mut && mut &*&Type && b", ExprLanguage::kRust, &TestLookupName)); |
| |
| EXPECT_EQ( |
| "CAST(Rust)\n" |
| " TYPE(Type)\n" |
| " ACCESSOR(->)\n" |
| " ARRAY_ACCESS\n" |
| " IDENTIFIER(\"a\")\n" |
| " LITERAL(0)\n" |
| " b\n", |
| GetParseString("a[0]->b as Type", ExprLanguage::kRust, &TestLookupName)); |
| |
| // We can't actually cast to tuple, so these wouldn't evaluate, but we want to test the type |
| // parsing anyway. |
| EXPECT_EQ( |
| "CAST(Rust)\n" |
| " TYPE((Type, Type, Type))\n" |
| " IDENTIFIER(\"a\")\n", |
| GetParseString("a as (Type, Type, Type)", ExprLanguage::kRust, &TestLookupName)); |
| |
| EXPECT_EQ( |
| "CAST(Rust)\n" |
| " TYPE(())\n" |
| " IDENTIFIER(\"a\")\n", |
| GetParseString("a as ()", ExprLanguage::kRust, &TestLookupName)); |
| |
| EXPECT_EQ( |
| "CAST(Rust)\n" |
| " TYPE((Type,))\n" |
| " IDENTIFIER(\"a\")\n", |
| GetParseString("a as (Type,)", ExprLanguage::kRust, &TestLookupName)); |
| |
| EXPECT_EQ( |
| "CAST(Rust)\n" |
| " TYPE(Type)\n" |
| " IDENTIFIER(\"a\")\n", |
| GetParseString("a as (Type)", ExprLanguage::kRust, &TestLookupName)); |
| |
| EXPECT_EQ( |
| "CAST(Rust)\n" |
| " TYPE(Type[23])\n" |
| " IDENTIFIER(\"a\")\n", |
| GetParseString("a as [Type; 23]", ExprLanguage::kRust, &TestLookupName)); |
| |
| EXPECT_EQ( |
| "CAST(Rust)\n" |
| " TYPE(Type[])\n" |
| " IDENTIFIER(\"a\")\n", |
| GetParseString("a as [Type]", ExprLanguage::kRust, &TestLookupName)); |
| |
| EXPECT_EQ( |
| "CAST(Rust)\n" |
| " TYPE(Type[23]*)\n" |
| " IDENTIFIER(\"a\")\n", |
| GetParseString("a as &[Type; 23]", ExprLanguage::kRust, &TestLookupName)); |
| |
| // Looks like a cast but it's not a type. |
| auto result = Parse("a as NotType", ExprLanguage::kRust, &TestLookupName); |
| EXPECT_FALSE(result); |
| EXPECT_EQ("Expected a type name but could not find a type named 'NotType'.", |
| parser().err().msg()); |
| |
| // Rust cast but we're not in rust |
| result = Parse("a as Type", ExprLanguage::kC, &TestLookupName); |
| EXPECT_FALSE(result); |
| EXPECT_EQ("Unexpected identifier, did you forget an operator?", parser().err().msg()); |
| } |
| |
| TEST_F(ExprParserTest, BadRustArrays) { |
| auto result = Parse("a as [NotType]", ExprLanguage::kRust, &TestLookupName); |
| EXPECT_FALSE(result); |
| EXPECT_EQ("Expected a type name but could not find a type named 'NotType'.", |
| parser().err().msg()); |
| |
| result = Parse("a as [NotType; 23]", ExprLanguage::kRust, &TestLookupName); |
| EXPECT_FALSE(result); |
| EXPECT_EQ("Expected a type name but could not find a type named 'NotType'.", |
| parser().err().msg()); |
| |
| result = Parse("a as [Type; 23", ExprLanguage::kRust, &TestLookupName); |
| EXPECT_FALSE(result); |
| EXPECT_EQ("Expected ']' before end of input.", parser().err().msg()); |
| |
| result = Parse("a as [Type;", ExprLanguage::kRust, &TestLookupName); |
| EXPECT_FALSE(result); |
| EXPECT_EQ("Expected element count before end of input.", parser().err().msg()); |
| |
| result = Parse("a as [Type", ExprLanguage::kRust, &TestLookupName); |
| EXPECT_FALSE(result); |
| EXPECT_EQ("Expected ']' before end of input.", parser().err().msg()); |
| } |
| |
| TEST_F(ExprParserTest, BadRustTuples) { |
| auto result = Parse("a as (Type, NotType)", ExprLanguage::kRust, &TestLookupName); |
| EXPECT_FALSE(result); |
| EXPECT_EQ("Expected a type name but could not find a type named 'NotType'.", |
| parser().err().msg()); |
| |
| // Missing comma |
| result = Parse("a as (Type Type)", ExprLanguage::kRust, &TestLookupName); |
| EXPECT_FALSE(result); |
| EXPECT_EQ("This looks like a declaration which is not supported.", parser().err().msg()); |
| |
| // Missing end |
| result = Parse("a as (Type, Type", ExprLanguage::kRust, &TestLookupName); |
| EXPECT_FALSE(result); |
| EXPECT_EQ("Expected ')' or ',' before end of input.", parser().err().msg()); |
| |
| // Missing end with comma |
| result = Parse("a as (Type,", ExprLanguage::kRust, &TestLookupName); |
| EXPECT_FALSE(result); |
| EXPECT_EQ("Expected ')' or ',' before end of input.", parser().err().msg()); |
| |
| // Missing end, no groupings. |
| result = Parse("a as (Type", ExprLanguage::kRust, &TestLookupName); |
| EXPECT_FALSE(result); |
| EXPECT_EQ("Expected ')' or ',' before end of input.", parser().err().msg()); |
| } |
| |
| TEST_F(ExprParserTest, CppCast) { |
| EXPECT_EQ( |
| "CAST(reinterpret_cast)\n" |
| " TYPE(Type*)\n" |
| " IDENTIFIER(\"a\")\n", |
| GetParseString("reinterpret_cast<Type*>(a)", &TestLookupName)); |
| |
| EXPECT_EQ( |
| "CAST(static_cast)\n" |
| " TYPE(Type*)\n" |
| " IDENTIFIER(\"a\")\n", |
| GetParseString("static_cast<Type*>(a)", &TestLookupName)); |
| |
| EXPECT_EQ( |
| "CAST(reinterpret_cast)\n" |
| " TYPE(const Type&&)\n" |
| " BINARY_OP(&&)\n" |
| " IDENTIFIER(\"x\")\n" |
| " IDENTIFIER(\"y\")\n", |
| GetParseString("reinterpret_cast< const Type&& >( x && y)", &TestLookupName)); |
| |
| auto result = Parse("reinterpret_cast<", &TestLookupName); |
| EXPECT_FALSE(result); |
| EXPECT_EQ("Expected type name before end of input.", parser().err().msg()); |
| } |
| |
| TEST_F(ExprParserTest, ParseIdentifier) { |
| Err err; |
| |
| // Empty input. |
| ParsedIdentifier empty_ident; |
| err = ExprParser::ParseIdentifier("", &empty_ident); |
| EXPECT_TRUE(err.has_error()); |
| EXPECT_EQ("Expected expression instead of end of input.", err.msg()); |
| EXPECT_EQ("", empty_ident.GetDebugName()); |
| |
| // Normal word. |
| ParsedIdentifier word_ident; |
| err = ExprParser::ParseIdentifier("foo", &word_ident); |
| EXPECT_FALSE(err.has_error()) << err.msg(); |
| EXPECT_EQ("\"foo\"", word_ident.GetDebugName()); |
| |
| // Destructor. |
| ParsedIdentifier destr_ident; |
| err = ExprParser::ParseIdentifier("Foo::~Foo", &destr_ident); |
| EXPECT_FALSE(err.has_error()) << err.msg(); |
| EXPECT_EQ(R"("Foo"; ::"~Foo")", destr_ident.GetDebugName()); |
| |
| // Complicated identifier (copied from STL). |
| ParsedIdentifier complex_ident; |
| err = ExprParser::ParseIdentifier( |
| "std::unordered_map<" |
| "std::__2::basic_string<char>, " |
| "unsigned long, " |
| "std::__2::hash<std::__2::basic_string<char> >, " |
| "std::__2::equal_to<std::__2::basic_string<char> >, " |
| "std::__2::allocator<std::__2::pair<const std::__2::basic_string<char>, " |
| "unsigned long> >>", |
| &complex_ident); |
| EXPECT_FALSE(err.has_error()); |
| EXPECT_EQ( |
| "\"std\"; " |
| "::" |
| "\"unordered_map\"," |
| "<\"std::__2::basic_string<char>\", " |
| "\"unsigned long\", " |
| "\"std::__2::hash<std::__2::basic_string<char>>\", " |
| "\"std::__2::equal_to<std::__2::basic_string<char>>\", " |
| "\"std::__2::allocator<std::__2::pair<" |
| "const std::__2::basic_string<char>, unsigned long>>\">", |
| complex_ident.GetDebugName()); |
| } |
| |
| TEST_F(ExprParserTest, FromStringError) { |
| // Error from input. |
| Identifier bad_ident; |
| Err err = ExprParser::ParseIdentifier("Foo<Bar", &bad_ident); |
| EXPECT_TRUE(err.has_error()); |
| EXPECT_EQ("Expected '>' to match. Hit the end of input instead.", err.msg()); |
| EXPECT_EQ("", bad_ident.GetDebugName()); |
| } |
| |
| TEST_F(ExprParserTest, If) { |
| EXPECT_EQ( |
| "CONDITION\n" |
| " IF\n" |
| " BINARY_OP(==)\n" |
| " IDENTIFIER(\"i\")\n" |
| " LITERAL(1)\n" |
| " THEN\n" |
| " BINARY_OP(=)\n" |
| " IDENTIFIER(\"b\")\n" |
| " LITERAL(1)\n", |
| GetParseString("if (i == 1) b = 1;")); |
| |
| EXPECT_EQ( |
| "CONDITION\n" |
| " IF\n" |
| " IDENTIFIER(\"i\")\n", |
| GetParseString("if (i) ;")); |
| |
| EXPECT_EQ( |
| "CONDITION\n" |
| " IF\n" |
| " IDENTIFIER(\"i\")\n" |
| " THEN\n" |
| " BLOCK\n", |
| GetParseString("if (i) {} else ;")); |
| |
| EXPECT_EQ( |
| "CONDITION\n" |
| " IF\n" |
| " LITERAL(1)\n" |
| " THEN\n" |
| " IDENTIFIER(\"foo\")\n" |
| " ELSEIF\n" |
| " LITERAL(0)\n" |
| " THEN\n" |
| " IDENTIFIER(\"bar\")\n", |
| GetParseString("if (1) foo; else if (0) bar;")); |
| |
| // Rust with no parens is OK. |
| EXPECT_EQ( |
| "CONDITION\n" |
| " IF\n" |
| " LITERAL(1)\n" |
| " THEN\n" |
| " BLOCK\n" |
| " IDENTIFIER(\"foo\")\n" |
| " ELSEIF\n" |
| " LITERAL(0)\n" |
| " THEN\n" |
| " BLOCK\n" |
| " IDENTIFIER(\"bar\")\n", |
| GetParseString("if 1 {foo} else if 0 {bar}", ExprLanguage::kRust)); |
| |
| // C with no parens is a failure. |
| auto result = Parse("if 1 foo; else if 0 bar;", ExprLanguage::kC); |
| EXPECT_FALSE(result); |
| EXPECT_EQ("Expected '(' for if.", parser().err().msg()); |
| |
| // Rust requires {} for the blocks. |
| result = Parse("if 1 foo;", ExprLanguage::kRust); |
| EXPECT_FALSE(result); |
| EXPECT_EQ("Expected '{'.", parser().err().msg()); |
| |
| // Missing semicolons. |
| result = Parse("if (1) foo else bar;", ExprLanguage::kC); |
| EXPECT_FALSE(result); |
| EXPECT_EQ("Expected ';'.", parser().err().msg()); |
| result = Parse("if (1) foo; else bar", ExprLanguage::kC); |
| EXPECT_FALSE(result); |
| EXPECT_EQ("Expected ';'. Hit the end of input instead.", parser().err().msg()); |
| } |
| |
| TEST_F(ExprParserTest, CTernaryIf) { |
| EXPECT_EQ( |
| "CONDITION\n" |
| " IF\n" |
| " LITERAL(1)\n" |
| " THEN\n" |
| " LITERAL(2)\n" |
| " ELSE\n" |
| " LITERAL(3)\n", |
| GetParseString("1 ? 2 : 3")); |
| |
| EXPECT_EQ( |
| "CONDITION\n" |
| " IF\n" |
| " BINARY_OP(==)\n" |
| " LITERAL(2)\n" |
| " BINARY_OP(-)\n" |
| " LITERAL(3)\n" |
| " LITERAL(1)\n" |
| " THEN\n" |
| " CONDITION\n" |
| " IF\n" |
| " LITERAL(2)\n" |
| " THEN\n" |
| " LITERAL(1)\n" |
| " ELSE\n" |
| " IDENTIFIER(\"a\")\n" |
| " ELSE\n" |
| " BINARY_OP(+)\n" |
| " LITERAL(3)\n" |
| " LITERAL(9)\n", |
| GetParseString("2==3-1 ? 2?1:a : 3+9")); |
| |
| // Missing colon. |
| auto result = Parse(" 3 ? 1"); |
| EXPECT_FALSE(result); |
| EXPECT_EQ("Expected ':' for previous '?'. Hit the end of input instead.", parser().err().msg()); |
| |
| // Missing condition |
| result = Parse("? a : b"); |
| EXPECT_FALSE(result); |
| EXPECT_EQ("Unexpected token '?'.", parser().err().msg()); |
| |
| // Not supported in Rust. |
| result = Parse("a ? b : c", ExprLanguage::kRust); |
| EXPECT_FALSE(result); |
| EXPECT_EQ("Rust '?' operators are not supported.", parser().err().msg()); |
| } |
| |
| // Tests that comments are ignored. |
| TEST_F(ExprParserTest, Comments) { |
| EXPECT_EQ( |
| "BINARY_OP(=)\n" |
| " IDENTIFIER(\"a\")\n" |
| " BINARY_OP(+)\n" |
| " LITERAL(3)\n" |
| " LITERAL(2)\n", |
| GetParseString("a = /* no */ 3 +// no\n 2")); |
| |
| // Unmatched */ token. |
| auto result = Parse(" 3 + */"); |
| EXPECT_FALSE(result); |
| EXPECT_EQ("Unexpected */", parser().err().msg()); |
| } |
| |
| } // namespace zxdb |