text: // Copyright 2017-2020 The Verible Authors.
  //
  // Licensed under the Apache License, Version 2.0 (the "License");
  // you may not use this file except in compliance with the License.
  // You may obtain a copy of the License at
  //
  //      http://www.apache.org/licenses/LICENSE-2.0
  //
  // Unless required by applicable law or agreed to in writing, software
  // distributed under the License is distributed on an "AS IS" BASIS,
  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  // See the License for the specific language governing permissions and
  // limitations under the License.
  
  #include "verilog/CST/tasks.h"
  
  #include <memory>
  #include <string>
  #include <utility>
  #include <vector>
  
  #include "gmock/gmock.h"
  #include "gtest/gtest.h"
  #include "absl/strings/string_view.h"
  #include "common/analysis/syntax_tree_search.h"
  #include "common/analysis/syntax_tree_search_test_utils.h"
  #include "common/text/concrete_syntax_leaf.h"
  #include "common/text/concrete_syntax_tree.h"
  #include "common/text/symbol.h"
  #include "common/text/text_structure.h"
  #include "common/text/token_info.h"
  #include "common/text/tree_utils.h"
  #include "common/util/casts.h"
  #include "common/util/logging.h"
  #include "verilog/CST/identifier.h"
  #include "verilog/CST/match_test_utils.h"
  #include "verilog/CST/verilog_nonterminals.h"
  #include "verilog/analysis/verilog_analyzer.h"
  #include "verilog/parser/verilog_token_enum.h"
  
  #undef ASSERT_OK
  #define ASSERT_OK(value) ASSERT_TRUE((value).ok())
  
  namespace verilog {
  namespace {
  
  using verible::down_cast;
  using verible::SyntaxTreeSearchTestCase;
  using verible::TextStructureView;
  using verible::TreeSearchMatch;
  
  TEST(FindAllTaskDeclarationsTest, Various) {
    constexpr int kTag = 1;  // value doesn't matter
    const SyntaxTreeSearchTestCase kTestCases[] = {
        {""},
        {"class foo; endclass"},
        {"module m(); endmodule: m"},
        {"function f(); endfunction"},
        {{kTag, "task foo();\nendtask"}},
        {{kTag, "task foo();\nendtask : foo"}},
        {{kTag, "task t();\nint x = 1;\nendtask"}},
        {// task inside package
         "package pkg;\n",
         {kTag,
          "task t;\n "
          "int x = 1;"
          "\nendtask"},
         "\nendpackage"},
        {// multiple tasks
         {kTag, "task foo();\nendtask"},
         "\n",
         {kTag, "task foo2();\nendtask"}},
        {// task declaration as class method
         "class bar;\n",
         {kTag, "task foo();\nendtask"},
         " endclass"},
        {// task declaration inside module
         "module bar;\n",
         {kTag, "task foo();\nendtask"},
         " endmodule"},
    };
    for (const auto& test : kTestCases) {
      TestVerilogSyntaxRangeMatches(
          __FUNCTION__, test, [](const TextStructureView& text_structure) {
            const auto& root = text_structure.SyntaxTree();
            return FindAllTaskDeclarations(*ABSL_DIE_IF_NULL(root));
          });
    }
  }
  
  TEST(FindAllTaskPrototypesTest, Various) {
    constexpr int kTag = 1;  // value doesn't matter
    const SyntaxTreeSearchTestCase kTestCases[] = {
        {// task declaration is not considered a prototype
         "task foo();\n"
         "endtask"},
        {// task declaration is not considered a prototype
         "class bar;\n"
         "  task foo();\n"
         "  endtask\n"
         "endclass"},
        {// virtual task can be defined and is overridable
         "class bar;\n"
         "  virtual task foo();\n"
         "  endtask\n"
         "endclass"},
        {// pure virtual is a prototype
         "class bar;\n",
         "pure virtual ",
         {kTag, "task foo();"},
         "\nendclass"},
        {// extern is a prototype
         "class bar;\n",
         "extern ",
         {kTag, "task foo();"},
         "\nendclass"},
    };
    for (const auto& test : kTestCases) {
      TestVerilogSyntaxRangeMatches(
          __FUNCTION__, test, [](const TextStructureView& text_structure) {
            const auto& root = text_structure.SyntaxTree();
            return FindAllTaskPrototypes(*ABSL_DIE_IF_NULL(root));
          });
    }
    // Prototype header span the same range as their enclosing prototypes.
    for (const auto& test : kTestCases) {
      TestVerilogSyntaxRangeMatches(
          __FUNCTION__, test, [](const TextStructureView& text_structure) {
            const auto& root = text_structure.SyntaxTree();
            std::vector<TreeSearchMatch> headers;
            for (const auto& proto :
                 FindAllTaskPrototypes(*ABSL_DIE_IF_NULL(root))) {
              headers.push_back(TreeSearchMatch{
                  &GetTaskPrototypeHeader(*proto.match), /* no context */});
            }
            return headers;
          });
    }
  }
  
  TEST(TaskPrototypesIdsTest, Various) {
    constexpr int kTag = 1;  // value doesn't matter
    const SyntaxTreeSearchTestCase kTestCases[] = {
        {// pure virtual is a prototype
         "class bar;\n",
         "pure virtual task ",
         {kTag, "foo"},
         "();\n"
         "endclass"},
        {// extern is a prototype
         "class bar;\n",
         "extern task ",
         {kTag, "ggghhh"},
         "();\n"
         "endclass"},
    };
    for (const auto& test : kTestCases) {
      TestVerilogSyntaxRangeMatches(
          __FUNCTION__, test, [](const TextStructureView& text_structure) {
            const auto& root = text_structure.SyntaxTree();
            std::vector<TreeSearchMatch> ids;
            for (const auto& proto :
                 FindAllTaskPrototypes(*ABSL_DIE_IF_NULL(root))) {
              const auto& header = GetTaskPrototypeHeader(*proto.match);
              const auto* id = ABSL_DIE_IF_NULL(GetTaskHeaderId(header));
              EXPECT_TRUE(verible::SymbolCastToNode(*id).MatchesTag(
                  NodeEnum::kUnqualifiedId));
              ids.push_back(TreeSearchMatch{id, /* no context */});
            }
            return ids;
          });
    }
  }
  
  TEST(FindAllTaskHeadersTest, Various) {
    constexpr int kTag = 1;  // value doesn't matter
    const SyntaxTreeSearchTestCase kTestCases[] = {
        {""},
        {"class c; endclass"},
        {"package c; endpackage"},
        {"module m; endmodule"},
        {"function f; endfunction"},
        {{kTag, "task foo();"},
         "\n"
         "endtask"},
        {"class bar;\n"
         "  ",
         {kTag, "task foo();"},
         "\n"
         "  endtask\n"
         "endclass"},
        {"class bar;\n",
         {kTag, "virtual task foo();"},
         "\n"
         "  endtask\n"
         "endclass"},
        {// pure virtual is a prototype
         "class bar;\n",
         "pure virtual ",
         {kTag, "task foo();"},
         "\nendclass"},
        {// extern is a prototype
         "class bar;\n",
         "extern ",
         {kTag, "task foo();"},
         "\nendclass"},
    };
    for (const auto& test : kTestCases) {
      TestVerilogSyntaxRangeMatches(
          __FUNCTION__, test, [](const TextStructureView& text_structure) {
            const auto& root = text_structure.SyntaxTree();
            return FindAllTaskHeaders(*ABSL_DIE_IF_NULL(root));
          });
    }
  }
  
  TEST(GetTaskHeaderTest, DeclarationsHeader) {
    constexpr int kTag = 1;  // value doesn't matter
    const SyntaxTreeSearchTestCase kTestCases[] = {
        {{kTag, "task foo();"}, " endtask"},
        {{kTag, "task my_class::foo();"}, " endtask"},
        {"class c; ", {kTag, "task foo();"}, " endtask endclass"},
        {"module m; ", {kTag, "task foo();"}, " endtask endmodule"},
        {"package p; ", {kTag, "task foo();"}, " endtask endpackage"},
    };
    for (const auto& test : kTestCases) {
      TestVerilogSyntaxRangeMatches(
          __FUNCTION__, test, [](const TextStructureView& text_structure) {
            // Root node is a description list, not a task.
            const auto& root = text_structure.SyntaxTree();
            const auto task_declarations = FindAllTaskDeclarations(*root);
            std::vector<TreeSearchMatch> headers;
            for (const auto& decl : task_declarations) {
              const auto& task_node = verible::SymbolCastToNode(*decl.match);
              headers.push_back(
                  TreeSearchMatch{&GetTaskHeader(task_node), /* no context */});
            }
            return headers;
          });
    }
  }
  
  TEST(GetTaskLifetimeTest, NoLifetimeDeclared) {
    VerilogAnalyzer analyzer("task foo(); endtask", "");
    ASSERT_OK(analyzer.Analyze());
    // Root node is a description list, not a task.
    const auto& root = analyzer.Data().SyntaxTree();
    const auto task_declarations = FindAllTaskDeclarations(*root);
    ASSERT_EQ(task_declarations.size(), 1);
    const auto& task_node =
        verible::SymbolCastToNode(*task_declarations.front().match);
    const auto* lifetime = GetTaskLifetime(task_node);
    EXPECT_EQ(lifetime, nullptr);
  }
  
  TEST(GetTaskLifetimeTest, StaticLifetimeDeclared) {
    VerilogAnalyzer analyzer("task static foo(); endtask", "");
    ASSERT_OK(analyzer.Analyze());
    // Root node is a description list, not a task.
    const auto& root = analyzer.Data().SyntaxTree();
    const auto task_declarations = FindAllTaskDeclarations(*root);
    ASSERT_EQ(task_declarations.size(), 1);
    const auto& task_node =
        verible::SymbolCastToNode(*task_declarations.front().match);
    const auto* lifetime = GetTaskLifetime(task_node);
    const auto& leaf = verible::SymbolCastToLeaf(*ABSL_DIE_IF_NULL(lifetime));
    EXPECT_EQ(leaf.get().token_enum(), TK_static);
  }
  
  TEST(GetTaskLifetimeTest, AutomaticLifetimeDeclared) {
    VerilogAnalyzer analyzer("task automatic foo(); endtask", "");
    ASSERT_OK(analyzer.Analyze());
    // Root node is a description list, not a task.
    const auto& root = analyzer.Data().SyntaxTree();
    const auto task_declarations = FindAllTaskDeclarations(*root);
    ASSERT_EQ(task_declarations.size(), 1);
    const auto& task_node =
        verible::SymbolCastToNode(*task_declarations.front().match);
    const auto* lifetime = GetTaskLifetime(task_node);
    const auto& leaf = verible::SymbolCastToLeaf(*ABSL_DIE_IF_NULL(lifetime));
    EXPECT_EQ(leaf.get().token_enum(), TK_automatic);
  }
  
  TEST(GetTaskIdTest, UnqualifiedIds) {
    const std::pair<std::string, std::vector<absl::string_view>> kTestCases[] = {
        {"task foo(); endtask", {"foo"}},
        {"task automatic bar(); endtask", {"bar"}},
        {"task static baz(); endtask", {"baz"}},
        {"package p; task foo(); endtask endpackage", {"foo"}},
        {"class c; task zoo(); endtask endclass", {"zoo"}},
        {"task myclass::foo(); endtask", {"myclass", "foo"}},
    };
    for (const auto test : kTestCases) {
      VerilogAnalyzer analyzer(test.first, "");
      ASSERT_OK(analyzer.Analyze());
      // Root node is a description list, not a task.
      const auto& root = analyzer.Data().SyntaxTree();
      const auto task_declarations = FindAllTaskDeclarations(*root);
      ASSERT_EQ(task_declarations.size(), 1);
      const auto& task_node = down_cast<const verible::SyntaxTreeNode&>(
          *task_declarations.front().match);
      const auto* task_id = GetTaskId(task_node);
      const auto ids = FindAllUnqualifiedIds(*task_id);
      std::vector<absl::string_view> got_ids;
      for (const auto& id : ids) {
        const verible::SyntaxTreeLeaf* base = GetIdentifier(*id.match);
        got_ids.push_back(ABSL_DIE_IF_NULL(base)->get().text());
      }
      EXPECT_EQ(got_ids, test.second);
    }
  }
  
  TEST(GetTaskIdTest, QualifiedIds) {
    constexpr int kTag = 1;  // value doesn't matter
    const SyntaxTreeSearchTestCase kTestCases[] = {
        {"task foo(); endtask"},
        {"class c; task foo(); endtask endclass"},
        {"task ", {kTag, "myclass::foo"}, "(); endtask"},
    };
    for (const auto& test : kTestCases) {
      TestVerilogSyntaxRangeMatches(
          __FUNCTION__, test, [](const TextStructureView& text_structure) {
            // Root node is a description list, not a task.
            const auto& root = text_structure.SyntaxTree();
            const auto task_declarations = FindAllTaskDeclarations(*root);
            std::vector<TreeSearchMatch> ids;
            for (const auto& decl : task_declarations) {
              const auto& task_node = verible::SymbolCastToNode(*decl.match);
              const auto* task_id = GetTaskId(task_node);
              for (const auto id : FindAllQualifiedIds(*task_id)) {
                ids.push_back(id);
              }
            }
            return ids;
          });
    }
  }
  
  TEST(GetTaskHeaderTest, GetTaskName) {
    constexpr int kTag = 1;  // value doesn't matter
    const SyntaxTreeSearchTestCase kTestCases[] = {
        {""},
        {"module m(); endmodule: m"},
        {"task ", {kTag, "foo"}, "(int a, bit b);\nendtask"},
        {"task ",
         {kTag, "foo"},
         "();\n endtask\n task ",
         {kTag, "bar"},
         "()\n; endtask"},
        {"module my_module;\ntask ",
         {kTag, "inner_task"},
         "(int my_args);\n$display(my_arg2);\nendtask\nendmodule"},
        {"class task_class;\ntask ",
         {kTag, "my_task"},
         "(input int a, b, output int c);\nc = a + b;\nendtask\nendclass"},
        {"package my_pkg;\ntask automatic ",
         {kTag, "my_task"},
         "(input int a, b, output int c);\nc = a + b;\nendtask\nendpackage"},
        {"class m;\n virtual task ",
         {kTag, "my_task"},
         "();\n endtask\n  endclass"},
        {"class m;\n static task ",
         {kTag, "my_task"},
         "();\n endtask\n  endclass"},
    };
    for (const auto& test : kTestCases) {
      TestVerilogSyntaxRangeMatches(
          __FUNCTION__, test, [](const TextStructureView& text_structure) {
            const auto& root = text_structure.SyntaxTree();
  
            const auto decls = FindAllTaskDeclarations(*ABSL_DIE_IF_NULL(root));
  
            std::vector<TreeSearchMatch> types;
            for (const auto& decl : decls) {
              const auto* type = GetTaskName(*decl.match);
              types.push_back(TreeSearchMatch{type, {/* ignored context */}});
            }
            return types;
          });
    }
  }  // namespace
  
  TEST(GetTaskHeaderTest, GetTaskBody) {
    constexpr int kTag = 1;  // value doesn't matter
    const SyntaxTreeSearchTestCase kTestCases[] = {
        {""},
        {"module m(); endmodule: m"},
        {"task t();\n ", {kTag, "int x = 1;"}, "\nendtask"},
        {"task t;\n ", {kTag, "int x = 1;"}, "\nendtask"},
        {"package pkg;\ntask t;\n ",
         {kTag, "int x = 1;"},
         "\nendtask\nendpackage"},
        {"package pkg;\ntask t();\n ",
         {kTag, "int x = 1;"},
         "\nendtask\nendpackage"},
    };
    for (const auto& test : kTestCases) {
      TestVerilogSyntaxRangeMatches(
          __FUNCTION__, test, [](const TextStructureView& text_structure) {
            const auto& root = text_structure.SyntaxTree();
  
            const auto decls = FindAllTaskDeclarations(*ABSL_DIE_IF_NULL(root));
  
            std::vector<TreeSearchMatch> bodies;
            for (const auto& decl : decls) {
              const auto& body = GetTaskStatementList(*decl.match);
              bodies.push_back(TreeSearchMatch{&body, {/* ignored context */}});
            }
            return bodies;
          });
    }
  }
  
  }  // namespace
  }  // namespace verilog
  