| // 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/expr/found_name.h" |
| #include "src/developer/debug/zxdb/expr/mock_eval_context.h" |
| #include "src/developer/debug/zxdb/symbols/collection.h" |
| |
| namespace zxdb { |
| |
| class ExprParserTest : public testing::Test { |
| public: |
| // Adds the built-in types. |
| ExprParserTest() { |
| // "Namespace" is a namespace. |
| ParsedIdentifier namespace_ident(ParsedIdentifierComponent("Namespace")); |
| eval_context().AddName(namespace_ident, FoundName(FoundName::kNamespace, namespace_ident)); |
| |
| // "Type" is a class type. |
| ParsedIdentifier type_ident(ParsedIdentifierComponent("Type")); |
| auto type = fxl::MakeRefCounted<Collection>(DwarfTag::kClassType); |
| type->set_assigned_name("Type"); |
| eval_context().AddName(type_ident, FoundName(std::move(type))); |
| |
| // Bare "Template" is a template (in FindName terms, a template is the name of a template with |
| // no template parameters, the fully specified thing is just a normal type -- see below). |
| ParsedIdentifier templ_ident(ParsedIdentifierComponent("Template")); |
| eval_context().AddName(templ_ident, FoundName(FoundName::kTemplate, templ_ident)); |
| |
| // Add a "Template<int>" specialization which is just a class type. |
| ParsedIdentifier templ_int_ident(ParsedIdentifierComponent("Template", {"int"})); |
| auto templ_int_type = fxl::MakeRefCounted<Collection>(DwarfTag::kClassType); |
| templ_int_type->set_assigned_name("Template<int>"); |
| eval_context().AddName(templ_int_ident, FoundName(std::move(templ_int_type))); |
| } |
| |
| MockEvalContext& eval_context() { return *eval_context_; } |
| |
| // Valid after Parse() is called. |
| ExprParser& parser() { return *parser_; } |
| |
| // Include_context controls whether the EvalContext is passed into the parser to provide type |
| // information. This is omitted for some cases where identifiers are parsed. |
| fxl::RefPtr<ExprNode> Parse(const char* input, ExprLanguage lang = ExprLanguage::kC, |
| bool include_context = true) { |
| parser_.reset(); |
| |
| tokenizer_ = std::make_unique<ExprTokenizer>(input, lang); |
| if (!tokenizer_->Tokenize()) { |
| ADD_FAILURE() << "Tokenization failure: " << input; |
| return nullptr; |
| } |
| |
| eval_context_->set_language(lang); |
| parser_ = std::make_unique<ExprParser>(tokenizer_->TakeTokens(), lang, |
| include_context ? eval_context_ : nullptr); |
| return parser_->ParseStandaloneExpression(); |
| } |
| |
| // Does the parse and returns the string dump of the structure. |
| std::string GetParseString(const char* input, bool include_context = true) { |
| return GetParseString(input, ExprLanguage::kC, include_context); |
| } |
| |
| std::string GetParseString(const char* input, ExprLanguage lang, bool include_context = true) { |
| auto root = Parse(input, lang, include_context); |
| 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_; |
| fxl::RefPtr<MockEvalContext> eval_context_ = fxl::MakeRefCounted<MockEvalContext>(); |
| }; |
| |
| 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; }")); |
| |
| // Our blocks allow the final statement to omit the semicolon (like Rust) even in C because |
| // it allows standalone expressions to be parsed in "block" mode (allowing multiple statements |
| // separated by semicolons) while not requiring a semicolon in the standalone expression case |
| // (you don't want to have to terminate every "print" command with a semicolon). |
| 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)); |
| |
| // 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 "}". |
| auto 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("Unexpected token, did you forget an operator or a semicolon?", 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 the internal "underscore" naming for tuples. |
| EXPECT_EQ( |
| "ACCESSOR(.)\n" |
| " ACCESSOR(.)\n" |
| " IDENTIFIER(\"channel\")\n" |
| " __0\n" |
| " handle\n", |
| GetParseString("channel.__0.handle", ExprLanguage::kRust)); |
| |
| // Test sequential dots with the numbered identifier. |
| EXPECT_EQ( |
| "ACCESSOR(.)\n" |
| " ACCESSOR(.)\n" |
| " ACCESSOR(.)\n" |
| " IDENTIFIER(\"channel\")\n" |
| " 0\n" |
| " 1\n" |
| " handle\n", |
| GetParseString("channel.0.1.handle", ExprLanguage::kRust)); |
| } |
| |
| 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 or a semicolon?", 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("Failed to parse 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("Failed to parse 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 or a semicolon?", 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", false)); |
| EXPECT_EQ("IDENTIFIER(::\"foo\")\n", GetParseString("::foo", false)); |
| EXPECT_EQ("IDENTIFIER(::\"foo\"; ::\"~bar\")\n", GetParseString("::foo :: ~bar", false)); |
| |
| 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 or a semicolon?", |
| 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. |
| // |
| // These take no context (pass "false" to GetParseString()) to put the parser into identifier |
| // mode. |
| EXPECT_EQ("IDENTIFIER(\"ns\"; ::\"$anon\"; ::\"Foo\")\n", |
| GetParseString("ns::$anon::Foo", false)); |
| EXPECT_EQ("IDENTIFIER(\"$main\")\n", GetParseString("$main", false)); |
| 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\\))", false)); |
| } |
| |
| TEST_F(ExprParserTest, CppOperators) { |
| EXPECT_EQ("IDENTIFIER(\"Class\"; ::\"operator>>\")\n", |
| GetParseString("Class::operator>>", false)); |
| EXPECT_EQ("IDENTIFIER(\"Class\"; ::\"operator()\")\n", |
| GetParseString("Class :: operator ()", false)); |
| EXPECT_EQ( |
| "BINARY_OP(>)\n" |
| " IDENTIFIER(\"Class\"; ::\"operator>>\")\n" |
| " IDENTIFIER(\"operator<<\")\n", |
| GetParseString("Class::operator>>>operator<<", false)); |
| |
| // Type conversion operator. To parse this correctly both the class name and the destination |
| // type name must be known as types to the parser. In this case, the parser looks up the type |
| // and uses its fully qualified name which is why "::" gets prepended (this isn't critical either |
| // way). |
| EXPECT_EQ("IDENTIFIER(::\"Type\"; ::\"operator const Type*\")\n", |
| GetParseString("Type::operator const Type *")); |
| |
| // 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")); |
| |
| EXPECT_EQ( |
| "BINARY_OP(>>=)\n" |
| " BINARY_OP(<<=)\n" |
| " IDENTIFIER(\"i\")\n" |
| " LITERAL(5)\n" |
| " LITERAL(6)\n", |
| GetParseString("i <<= 5 >>= 6")); |
| |
| // 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()", false)); |
| |
| // 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 or a semicolon?", |
| 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()); |
| } |
| |
| // These pass no context (false) to GetParseString() so it goes into the mode where it prefers |
| // identifiers and doesn't try to look up anything in the symbol system. |
| TEST_F(ExprParserTest, Templates) { |
| EXPECT_EQ(R"(IDENTIFIER("foo",<>))" |
| "\n", |
| GetParseString("foo<>", false)); |
| EXPECT_EQ(R"(IDENTIFIER("foo",<"Foo">))" |
| "\n", |
| GetParseString("foo<Foo>", false)); |
| EXPECT_EQ(R"(IDENTIFIER("foo",<"Foo", "5">))" |
| "\n", |
| GetParseString("foo< Foo,5 >", false)); |
| 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", |
| false)); |
| |
| // 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", ExprLanguage::kC, false); |
| 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", ExprLanguage::kC, false); |
| 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>", ExprLanguage::kC, false); |
| ASSERT_FALSE(result); |
| EXPECT_EQ("Expected expression after '>'.", parser().err().msg()); |
| |
| // Empty value. |
| result = Parse("Foo<1,,2>", ExprLanguage::kC, false); |
| ASSERT_FALSE(result); |
| EXPECT_EQ("Expected template parameter.", parser().err().msg()); |
| |
| // Trailing comma. |
| result = Parse("Foo<Bar,>", ExprLanguage::kC, false); |
| 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) { |
| // Passing no context (the "false" parameter) puts the parser into a mode where it prefers to |
| // parse identifiers. |
| EXPECT_EQ( |
| "BINARY_OP(!=)\n" |
| " IDENTIFIER(\"a\",<\"int\">; ::\"foo\")\n" |
| " LITERAL(0)\n", |
| GetParseString("a<int>::foo != 0", false)); |
| |
| // Doing the same thing with symbol context will try to look up "a" as a name which will fail |
| // so we won't think it's a template and get a completely different parsing. |
| EXPECT_EQ( |
| "BINARY_OP(!=)\n" |
| " BINARY_OP(>)\n" |
| " BINARY_OP(<)\n" |
| " IDENTIFIER(\"a\")\n" |
| " TYPE(int)\n" |
| " IDENTIFIER(::\"foo\")\n" |
| " LITERAL(0)\n", |
| GetParseString("a<int>::foo != 0", true)); |
| |
| 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. |
| TEST_F(ExprParserTest, NamesWithSymbolLookup) { |
| // Bare namespace is an error. |
| auto result = Parse("Namespace"); |
| ASSERT_FALSE(result); |
| EXPECT_EQ("Expected expression after namespace name.", parser().err().msg()); |
| |
| // Bare template is an error. |
| result = Parse("Template"); |
| ASSERT_FALSE(result); |
| EXPECT_EQ("Expected template args after template name.", parser().err().msg()); |
| |
| // Nothing after "::" |
| result = Parse("Namespace::"); |
| 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>"); |
| 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>"); |
| 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()", false)); |
| } |
| |
| 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")); |
| EXPECT_EQ("IDENTIFIER(\"NotType\")\n", GetParseString("NotType")); |
| |
| EXPECT_EQ("TYPE(const Type)\n", GetParseString("const Type")); |
| EXPECT_EQ("TYPE(const Type)\n", GetParseString("Type const")); |
| |
| // 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")); |
| |
| // Duplicate const qualifications. |
| auto result = Parse("const Type const"); |
| ASSERT_FALSE(result); |
| EXPECT_EQ("Duplicate 'const' type qualification.", parser().err().msg()); |
| result = Parse("const const Type"); |
| ASSERT_FALSE(result); |
| EXPECT_EQ("Duplicate 'const' type qualification.", parser().err().msg()); |
| |
| EXPECT_EQ("TYPE(Type*)\n", GetParseString("Type*")); |
| EXPECT_EQ("TYPE(Type**)\n", GetParseString("Type * *")); |
| EXPECT_EQ("TYPE(Type&&)\n", GetParseString("Type &&")); |
| EXPECT_EQ("TYPE(Type&**)\n", GetParseString("Type&**")); |
| EXPECT_EQ("TYPE(volatile Type* restrict* const)\n", |
| GetParseString("Type volatile *restrict* const")); |
| |
| // "const" should force us into type mode. |
| result = Parse("const NonType"); |
| 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*)")); |
| EXPECT_EQ( |
| "SIZEOF\n" |
| " IDENTIFIER(\"foo\")\n", |
| GetParseString("sizeof(foo)")); |
| } |
| |
| TEST_F(ExprParserTest, C_Cast) { |
| EXPECT_EQ( |
| "CAST(C)\n" |
| " TYPE(Type)\n" |
| " IDENTIFIER(\"a\")\n", |
| GetParseString("(Type)(a)")); |
| |
| EXPECT_EQ( |
| "BINARY_OP(&&)\n" |
| " CAST(C)\n" |
| " TYPE(Type*)\n" |
| " IDENTIFIER(\"a\")\n" |
| " IDENTIFIER(\"b\")\n", |
| GetParseString("(Type*)a && b")); |
| |
| 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")); |
| |
| // Looks like a cast but it's not a type. |
| auto result = Parse("(NotType)a"); |
| EXPECT_FALSE(result); |
| EXPECT_EQ("Unexpected input, did you forget an operator or a semicolon?", parser().err().msg()); |
| } |
| |
| TEST_F(ExprParserTest, RustCast) { |
| EXPECT_EQ( |
| "CAST(Rust)\n" |
| " TYPE(Type)\n" |
| " IDENTIFIER(\"a\")\n", |
| GetParseString("a as Type", ExprLanguage::kRust)); |
| |
| EXPECT_EQ( |
| "BINARY_OP(&&)\n" |
| " CAST(Rust)\n" |
| " TYPE(Type*)\n" |
| " IDENTIFIER(\"a\")\n" |
| " IDENTIFIER(\"b\")\n", |
| GetParseString("a as *Type && b", ExprLanguage::kRust)); |
| |
| 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)); |
| |
| 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)); |
| |
| // 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)); |
| |
| EXPECT_EQ( |
| "CAST(Rust)\n" |
| " TYPE(())\n" |
| " IDENTIFIER(\"a\")\n", |
| GetParseString("a as ()", ExprLanguage::kRust)); |
| |
| EXPECT_EQ( |
| "CAST(Rust)\n" |
| " TYPE((Type,))\n" |
| " IDENTIFIER(\"a\")\n", |
| GetParseString("a as (Type,)", ExprLanguage::kRust)); |
| |
| EXPECT_EQ( |
| "CAST(Rust)\n" |
| " TYPE(Type)\n" |
| " IDENTIFIER(\"a\")\n", |
| GetParseString("a as (Type)", ExprLanguage::kRust)); |
| |
| EXPECT_EQ( |
| "CAST(Rust)\n" |
| " TYPE(Type[23])\n" |
| " IDENTIFIER(\"a\")\n", |
| GetParseString("a as [Type; 23]", ExprLanguage::kRust)); |
| |
| EXPECT_EQ( |
| "CAST(Rust)\n" |
| " TYPE(Type[])\n" |
| " IDENTIFIER(\"a\")\n", |
| GetParseString("a as [Type]", ExprLanguage::kRust)); |
| |
| EXPECT_EQ( |
| "CAST(Rust)\n" |
| " TYPE(Type[23]*)\n" |
| " IDENTIFIER(\"a\")\n", |
| GetParseString("a as &[Type; 23]", ExprLanguage::kRust)); |
| |
| // Looks like a cast but it's not a type. |
| auto result = Parse("a as NotType", ExprLanguage::kRust); |
| 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); |
| EXPECT_FALSE(result); |
| EXPECT_EQ("Unexpected identifier, did you forget an operator or a semicolon?", |
| parser().err().msg()); |
| } |
| |
| TEST_F(ExprParserTest, BadRustArrays) { |
| auto result = Parse("a as [NotType]", ExprLanguage::kRust); |
| 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); |
| 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); |
| EXPECT_FALSE(result); |
| EXPECT_EQ("Expected ']' before end of input.", parser().err().msg()); |
| |
| result = Parse("a as [Type;", ExprLanguage::kRust); |
| EXPECT_FALSE(result); |
| EXPECT_EQ("Expected element count before end of input.", parser().err().msg()); |
| |
| result = Parse("a as [Type", ExprLanguage::kRust); |
| 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); |
| 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); |
| EXPECT_FALSE(result); |
| EXPECT_EQ("Expected ')' or ','.", parser().err().msg()); |
| |
| // Missing end |
| result = Parse("a as (Type, Type", ExprLanguage::kRust); |
| 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); |
| 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); |
| 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)")); |
| |
| EXPECT_EQ( |
| "CAST(static_cast)\n" |
| " TYPE(Type*)\n" |
| " IDENTIFIER(\"a\")\n", |
| GetParseString("static_cast<Type*>(a)")); |
| |
| 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)")); |
| |
| auto result = Parse("reinterpret_cast<"); |
| EXPECT_FALSE(result); |
| EXPECT_EQ("Expected type name before end of input.", parser().err().msg()); |
| |
| // Functional-style casts aren't currently supported. Make sure we report that properly. |
| result = Parse("double(5)"); |
| EXPECT_FALSE(result); |
| EXPECT_EQ("Functional-style casts are not currently supported.", parser().err().msg()); |
| |
| result = Parse("Type()"); |
| EXPECT_FALSE(result); |
| EXPECT_EQ("Functional-style casts are not currently supported.", 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()); |
| } |
| |
| TEST_F(ExprParserTest, RustIfLet) { |
| EXPECT_EQ( |
| "CONDITION\n" |
| " IF_LET(0)\n" |
| " \"Some\"\n" |
| " IDENTIFIER(\"b\")\n" |
| " THEN\n" |
| " BLOCK\n" |
| " LITERAL(12)\n", |
| GetParseString("if let Some(a) = b { 12 }", ExprLanguage::kRust)); |
| |
| // Enums with no values can be matched. |
| EXPECT_EQ( |
| "CONDITION\n" |
| " IF_LET()\n" |
| " \"Some\"\n" |
| " IDENTIFIER(\"b\")\n" |
| " THEN\n" |
| " BLOCK\n" |
| " LITERAL(12)\n", |
| GetParseString("if let Some = b { 12 }", ExprLanguage::kRust)); |
| |
| EXPECT_EQ( |
| "CONDITION\n" |
| " IF\n" |
| " BINARY_OP(==)\n" |
| " IDENTIFIER(\"a\")\n" |
| " IDENTIFIER(\"b\")\n" |
| " THEN\n" |
| " BLOCK\n" |
| " LITERAL(12)\n" |
| " ELSEIF_LET(0)\n" |
| " \"Namespace\"; ::\"Enum\"\n" |
| " IDENTIFIER(\"c\")\n" |
| " THEN\n" |
| " BLOCK\n" |
| " LITERAL(13)\n" |
| " ELSEIF_LET(1, 2)\n" |
| " \"Namespace\"; ::\"Other\"\n" |
| " IDENTIFIER(\"c\")\n" |
| " THEN\n" |
| " BLOCK\n" |
| " LOCAL_VAR(1)\n", |
| GetParseString("if a == b { 12 } " |
| "else if let Namespace::Enum(val) = c { 13 } " |
| "else if let Namespace::Other(val, other) = c { val }", |
| ExprLanguage::kRust)); |
| |
| // Test error message for tuple struct patterns (not supported yet). |
| auto result = Parse("if let Point{x, y} = a {}", ExprLanguage::kRust); |
| EXPECT_FALSE(result); |
| EXPECT_EQ("Struct pattern syntax is not supported yet, sorry.", parser().err().msg()); |
| } |
| |
| TEST_F(ExprParserTest, ForLoop) { |
| EXPECT_EQ( |
| "LOOP(for)\n" |
| " ;\n" |
| " ;\n" |
| " ;\n" |
| " ;\n" |
| " BLOCK\n", |
| GetParseString("for (;;) {}")); |
| |
| EXPECT_EQ( |
| "LOOP(for)\n" |
| " BINARY_OP(=)\n" |
| " IDENTIFIER(\"i\")\n" |
| " LITERAL(0)\n" |
| " ;\n" |
| " ;\n" |
| " ;\n" |
| " ;\n", |
| GetParseString("for (i = 0;;);")); |
| |
| // Note: we can't write "i < 100" here because in this test environment there is no variable or |
| // type information so the parser can't know what "i" is. When there is no such information, it |
| // assumes the < is for a template (this behavior is important since the same parser is used for |
| // parsing breakpoint names with no context; real expressions will have this context). |
| EXPECT_EQ( |
| "LOOP(for)\n" |
| " IDENTIFIER(\"i\")\n" |
| " BINARY_OP(<=)\n" |
| " IDENTIFIER(\"i\")\n" |
| " LITERAL(100)\n" |
| " ;\n" |
| " BINARY_OP(=)\n" |
| " IDENTIFIER(\"i\")\n" |
| " BINARY_OP(+)\n" |
| " IDENTIFIER(\"i\")\n" |
| " LITERAL(1)\n" |
| " BLOCK\n" |
| " FUNCTIONCALL\n" |
| " IDENTIFIER(\"print\")\n" |
| " IDENTIFIER(\"i\")\n" |
| " FUNCTIONCALL\n" |
| " IDENTIFIER(\"beep\")\n", |
| GetParseString("for (i; i <= 100; i = i + 1) {\n" |
| " print(i);\n" |
| " beep();\n" |
| "}")); |
| |
| // Variable declaration in the loop (this needs the name lookup to get the builtin types). |
| EXPECT_EQ( |
| "LOOP(for)\n" |
| " LOCAL_VAR_DECL(i, 0)\n" |
| " size_t\n" |
| " LITERAL(0)\n" |
| " BINARY_OP(<)\n" |
| " LOCAL_VAR(0)\n" |
| " LITERAL(100)\n" |
| " ;\n" |
| " BINARY_OP(=)\n" |
| " LOCAL_VAR(0)\n" |
| " BINARY_OP(+)\n" |
| " LOCAL_VAR(0)\n" |
| " LITERAL(1)\n" |
| " BLOCK\n", |
| GetParseString("for (size_t i = 0; i < 100; i = i + 1) {}")); |
| |
| // No loop body. |
| auto result = Parse("for (;;)"); |
| EXPECT_FALSE(result); |
| EXPECT_EQ("Expected expression instead of end of input.", parser().err().msg()); |
| |
| // Unterminated loop body. |
| result = Parse("for (;;) j"); |
| EXPECT_FALSE(result); |
| EXPECT_EQ("Expected ';'. Hit the end of input instead.", parser().err().msg()); |
| |
| // Not enough semicolons. |
| result = Parse("for (i = 0;)"); |
| EXPECT_FALSE(result); |
| EXPECT_EQ("Unexpected token ')'.", parser().err().msg()); |
| |
| result = Parse("for (i = 0 i <= 100; i++)"); |
| EXPECT_FALSE(result); |
| EXPECT_EQ("Expected ';'.", parser().err().msg()); |
| |
| // Missing "()" |
| result = Parse("for i = 0 {}"); |
| EXPECT_FALSE(result); |
| EXPECT_EQ("Expected '(' for 'for' loop.", parser().err().msg()); |
| |
| result = Parse("for (i = 0; i; j"); |
| EXPECT_FALSE(result); |
| EXPECT_EQ("Expected ')' to match. Hit the end of input instead.", parser().err().msg()); |
| } |
| |
| TEST_F(ExprParserTest, DoLoop) { |
| EXPECT_EQ( |
| "LOOP(do)\n" |
| " ;\n" |
| " ;\n" |
| " LITERAL(true)\n" |
| " ;\n" |
| " BLOCK\n", |
| GetParseString("do {} while (true);")); |
| |
| EXPECT_EQ( |
| "LOOP(do)\n" |
| " ;\n" |
| " ;\n" |
| " BINARY_OP(>=)\n" |
| " IDENTIFIER(\"i\")\n" |
| " LITERAL(0)\n" |
| " ;\n" |
| " BLOCK\n" |
| " FUNCTIONCALL\n" |
| " IDENTIFIER(\"print\")\n" |
| " IDENTIFIER(\"i\")\n", |
| GetParseString("do { print(i); } while (i >= 0);")); |
| |
| // Semicolon loop body. |
| auto result = Parse("do ; while (true);"); |
| EXPECT_FALSE(result); |
| EXPECT_EQ("Empty expression not permitted here.", parser().err().msg()); |
| |
| // No loop expression. |
| result = Parse("do {} while();"); |
| EXPECT_FALSE(result); |
| EXPECT_EQ("Unexpected token ')'.", parser().err().msg()); |
| } |
| |
| TEST_F(ExprParserTest, CWhileLoop) { |
| EXPECT_EQ( |
| "LOOP(while)\n" |
| " ;\n" |
| " LITERAL(true)\n" |
| " ;\n" |
| " ;\n" |
| " BLOCK\n", |
| GetParseString("while (true) {}")); |
| |
| EXPECT_EQ( |
| "LOOP(while)\n" |
| " ;\n" |
| " BINARY_OP(>=)\n" |
| " IDENTIFIER(\"i\")\n" |
| " LITERAL(0)\n" |
| " ;\n" |
| " ;\n" |
| " ;\n", |
| GetParseString("while (i >= 0);")); |
| |
| EXPECT_EQ( |
| "LOOP(while)\n" |
| " ;\n" |
| " IDENTIFIER(\"i\")\n" |
| " ;\n" |
| " ;\n" |
| " BINARY_OP(=)\n" |
| " IDENTIFIER(\"i\")\n" |
| " BINARY_OP(+)\n" |
| " IDENTIFIER(\"i\")\n" |
| " LITERAL(1)\n", |
| GetParseString("while (i) i = i + 1;")); |
| |
| EXPECT_EQ( |
| "LOOP(while)\n" |
| " ;\n" |
| " IDENTIFIER(\"i\")\n" |
| " ;\n" |
| " ;\n" |
| " BLOCK\n" |
| " FUNCTIONCALL\n" |
| " IDENTIFIER(\"print\")\n" |
| " IDENTIFIER(\"i\")\n" |
| " BINARY_OP(=)\n" |
| " IDENTIFIER(\"i\")\n" |
| " BINARY_OP(+)\n" |
| " IDENTIFIER(\"i\")\n" |
| " LITERAL(1)\n", |
| GetParseString("while (i) { print(i); i = i + 1; }")); |
| |
| // No loop body. |
| auto result = Parse("while (true)"); |
| EXPECT_FALSE(result); |
| EXPECT_EQ("Expected expression instead of end of input.", parser().err().msg()); |
| |
| // Unterminated loop body. |
| result = Parse("while (foo) j"); |
| EXPECT_FALSE(result); |
| EXPECT_EQ("Expected ';'. Hit the end of input instead.", parser().err().msg()); |
| |
| // No loop expression. |
| result = Parse("while () j;"); |
| EXPECT_FALSE(result); |
| EXPECT_EQ("Unexpected token ')'.", parser().err().msg()); |
| |
| // Semicolon loop expression |
| result = Parse("while (i;) j;"); |
| EXPECT_FALSE(result); |
| EXPECT_EQ("Expected ')' to match.", parser().err().msg()); |
| |
| // Break outside of loop. |
| result = Parse("break;"); |
| EXPECT_FALSE(result); |
| EXPECT_EQ("Use of 'break' in this context is not allowed.", parser().err().msg()); |
| } |
| |
| TEST_F(ExprParserTest, RustWhileLoop) { |
| EXPECT_EQ( |
| "LOOP(while)\n" |
| " ;\n" |
| " LITERAL(true)\n" |
| " ;\n" |
| " ;\n" |
| " BLOCK\n", |
| GetParseString("while true {}", ExprLanguage::kRust)); |
| |
| EXPECT_EQ( |
| "LOOP(while)\n" |
| " ;\n" |
| " BINARY_OP(>=)\n" |
| " IDENTIFIER(\"i\")\n" |
| " LITERAL(0)\n" |
| " ;\n" |
| " ;\n" |
| " BLOCK\n" |
| " BINARY_OP(=)\n" |
| " IDENTIFIER(\"i\")\n" |
| " BINARY_OP(-)\n" |
| " IDENTIFIER(\"i\")\n" |
| " LITERAL(1)\n", |
| GetParseString("while i >= 0 { i = i - 1; }", ExprLanguage::kRust)); |
| |
| // Parens are still permitted, and this omits the semicolon for the last block statement. |
| EXPECT_EQ( |
| "LOOP(while)\n" |
| " ;\n" |
| " IDENTIFIER(\"i\")\n" |
| " ;\n" |
| " ;\n" |
| " BLOCK\n" |
| " FUNCTIONCALL\n" |
| " IDENTIFIER(\"print\")\n" |
| " IDENTIFIER(\"i\")\n" |
| " BINARY_OP(=)\n" |
| " IDENTIFIER(\"i\")\n" |
| " BINARY_OP(+)\n" |
| " IDENTIFIER(\"i\")\n" |
| " LITERAL(1)\n" |
| " BREAK\n", |
| GetParseString("while (i) { print(i); i = i + 1; break; }", ExprLanguage::kRust)); |
| |
| // No loop body. |
| auto result = Parse("while true", ExprLanguage::kRust); |
| EXPECT_FALSE(result); |
| EXPECT_EQ("Expected '{'. Hit the end of input instead.", parser().err().msg()); |
| |
| // No {}. |
| result = Parse("while foo j;", ExprLanguage::kRust); |
| EXPECT_FALSE(result); |
| EXPECT_EQ("Unexpected identifier, did you forget an operator or a semicolon?", |
| parser().err().msg()); |
| } |
| |
| TEST_F(ExprParserTest, RustLoop) { |
| EXPECT_EQ( |
| "LOOP(loop)\n" |
| " ;\n" |
| " ;\n" |
| " ;\n" |
| " ;\n" |
| " BLOCK\n", |
| GetParseString("loop {}", ExprLanguage::kRust)); |
| |
| EXPECT_EQ( |
| "LOOP(loop)\n" |
| " ;\n" |
| " ;\n" |
| " ;\n" |
| " ;\n" |
| " BLOCK\n" |
| " BINARY_OP(=)\n" |
| " IDENTIFIER(\"i\")\n" |
| " BINARY_OP(-)\n" |
| " IDENTIFIER(\"i\")\n" |
| " LITERAL(1)\n" |
| " BREAK\n", |
| GetParseString("loop { i = i - 1; break; }", ExprLanguage::kRust)); |
| |
| // Extra expression. |
| auto result = Parse("loop true {}", ExprLanguage::kRust); |
| EXPECT_FALSE(result); |
| EXPECT_EQ("Expected '{'.", parser().err().msg()); |
| |
| // Missing body. |
| result = Parse("loop", ExprLanguage::kRust); |
| EXPECT_FALSE(result); |
| EXPECT_EQ("Expected '{'. Hit the end of input instead.", parser().err().msg()); |
| |
| // Unterminated body. |
| result = Parse("loop {", ExprLanguage::kRust); |
| EXPECT_FALSE(result); |
| EXPECT_EQ("Expected '}'. Hit the end of input instead.", 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()); |
| } |
| |
| TEST_F(ExprParserTest, CVariableDecl) { |
| EXPECT_EQ( |
| "LOCAL_VAR_DECL(i, 0)\n" |
| " int\n" |
| " BINARY_OP(+)\n" |
| " LITERAL(0)\n" |
| " LITERAL(76)\n", |
| GetParseString("int i = 0 + 76;", ExprLanguage::kC)); |
| |
| EXPECT_EQ( |
| "LOCAL_VAR_DECL(d, 0)\n" |
| " double\n" |
| " ;\n", |
| GetParseString("double d;", ExprLanguage::kC)); |
| |
| EXPECT_EQ( |
| "LOCAL_VAR_DECL(i, 0)\n" |
| " <C++-style auto>\n" |
| " LITERAL(76)\n", |
| GetParseString("auto i = 76", ExprLanguage::kC)); |
| |
| EXPECT_EQ( |
| "LOCAL_VAR_DECL(i, 0)\n" |
| " <C++-style auto&>\n" |
| " IDENTIFIER(\"some_var\")\n", |
| GetParseString("auto& i = some_var", ExprLanguage::kC)); |
| |
| EXPECT_EQ( |
| "LOCAL_VAR_DECL(i, 0)\n" |
| " <C++-style auto*>\n" |
| " ADDRESS_OF\n" |
| " IDENTIFIER(\"some_var\")\n", |
| GetParseString("auto* i = &some_var;", ExprLanguage::kC)); |
| |
| EXPECT_EQ( |
| "LOCAL_VAR_DECL(v, 0)\n" |
| " Type**\n" |
| " ;\n", |
| GetParseString("Type** v;", ExprLanguage::kC)); |
| |
| // Paren initialization. |
| EXPECT_EQ( |
| "LOCAL_VAR_DECL(v, 0)\n" |
| " Type**\n" |
| " LITERAL(0)\n", |
| GetParseString("Type** v(0);", ExprLanguage::kC)); |
| } |
| |
| TEST_F(ExprParserTest, RustVariableDecl) { |
| EXPECT_EQ( |
| "LOCAL_VAR_DECL(i, 0)\n" |
| " <Rust-style auto>\n" |
| " BINARY_OP(+)\n" |
| " LITERAL(0)\n" |
| " LITERAL(76)\n", |
| GetParseString("let i = 0 + 76;", ExprLanguage::kRust)); |
| |
| EXPECT_EQ( |
| "LOCAL_VAR_DECL(i, 0)\n" |
| " i32\n" |
| " ;\n", |
| GetParseString("let i:i32;", ExprLanguage::kRust)); |
| |
| // Note the type names in the output are formatted like C (i32*). This is just the default |
| // formatting of the type name system whose names aren't really designed for final consumption. |
| EXPECT_EQ( |
| "LOCAL_VAR_DECL(i, 0)\n" |
| " i32*\n" |
| " IDENTIFIER(\"something\")\n", |
| GetParseString("let i:&i32 = something", ExprLanguage::kRust)); |
| |
| EXPECT_EQ( |
| "LOCAL_VAR_DECL(i, 0)\n" |
| " Type*\n" |
| " ADDRESS_OF\n" |
| " IDENTIFIER(\"something\")\n", |
| GetParseString("let i:&mut Type = &something", ExprLanguage::kRust)); |
| } |
| |
| TEST_F(ExprParserTest, LocalVarAccess) { |
| EXPECT_EQ( |
| "BLOCK\n" |
| " BLOCK\n" |
| " LOCAL_VAR_DECL(i, 0)\n" |
| " int\n" |
| " LITERAL(54)\n" |
| " BINARY_OP(=)\n" |
| " LOCAL_VAR(0)\n" |
| " BINARY_OP(+)\n" |
| " LOCAL_VAR(0)\n" |
| " LITERAL(23)\n" |
| " BLOCK\n" |
| " BINARY_OP(=)\n" |
| " IDENTIFIER(\"i\")\n" |
| " LITERAL(2)\n" |
| " LOCAL_VAR_DECL(j, 0)\n" |
| " <C++-style auto>\n" |
| " LITERAL(1)\n" |
| " LOCAL_VAR_DECL(k, 1)\n" |
| " double\n" |
| " LITERAL(0)\n", |
| GetParseString( |
| "{\n" |
| " {\n" |
| " int i = 54;\n" |
| " i = i + 23;\n" |
| " }\n" |
| " {\n" |
| " i = 2;\n" // No local in scope w/ this name, should be an identifier. |
| " auto j = 1;\n" // Gets assigned the same slot as i above since i went out of scope. |
| " double k = 0;\n" // Gets assigned next slot. |
| " }" |
| "}", |
| ExprLanguage::kC)); |
| } |
| |
| } // namespace zxdb |