text: //===-- REPL.cpp ----------------------------------------------------------===//
  //
  // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
  // See https://llvm.org/LICENSE.txt for license information.
  // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
  //
  //===----------------------------------------------------------------------===//
  
  #include "lldb/Expression/REPL.h"
  #include "lldb/Core/Debugger.h"
  #include "lldb/Core/PluginManager.h"
  #include "lldb/Core/StreamFile.h"
  #include "lldb/Expression/ExpressionVariable.h"
  #include "lldb/Expression/UserExpression.h"
  #include "lldb/Host/HostInfo.h"
  #include "lldb/Interpreter/CommandInterpreter.h"
  #include "lldb/Interpreter/CommandReturnObject.h"
  #include "lldb/Target/Thread.h"
  #include "lldb/Utility/AnsiTerminal.h"
  
  #include <memory>
  
  using namespace lldb_private;
  
  REPL::REPL(LLVMCastKind kind, Target &target) : m_target(target), m_kind(kind) {
    // Make sure all option values have sane defaults
    Debugger &debugger = m_target.GetDebugger();
    auto exe_ctx = debugger.GetCommandInterpreter().GetExecutionContext();
    m_format_options.OptionParsingStarting(&exe_ctx);
    m_varobj_options.OptionParsingStarting(&exe_ctx);
  }
  
  REPL::~REPL() = default;
  
  lldb::REPLSP REPL::Create(Status &err, lldb::LanguageType language,
                            Debugger *debugger, Target *target,
                            const char *repl_options) {
    uint32_t idx = 0;
    lldb::REPLSP ret;
  
    while (REPLCreateInstance create_instance =
               PluginManager::GetREPLCreateCallbackAtIndex(idx)) {
      LanguageSet supported_languages =
          PluginManager::GetREPLSupportedLanguagesAtIndex(idx++);
      if (!supported_languages[language])
        continue;
      ret = (*create_instance)(err, language, debugger, target, repl_options);
      if (ret) {
        break;
      }
    }
  
    return ret;
  }
  
  std::string REPL::GetSourcePath() {
    ConstString file_basename = GetSourceFileBasename();
    FileSpec tmpdir_file_spec = HostInfo::GetProcessTempDir();
    if (tmpdir_file_spec) {
      tmpdir_file_spec.GetFilename() = file_basename;
      m_repl_source_path = tmpdir_file_spec.GetPath();
    } else {
      tmpdir_file_spec = FileSpec("/tmp");
      tmpdir_file_spec.AppendPathComponent(file_basename.GetStringRef());
    }
  
    return tmpdir_file_spec.GetPath();
  }
  
  lldb::IOHandlerSP REPL::GetIOHandler() {
    if (!m_io_handler_sp) {
      Debugger &debugger = m_target.GetDebugger();
      m_io_handler_sp = std::make_shared<IOHandlerEditline>(
          debugger, IOHandler::Type::REPL,
          "lldb-repl",           // Name of input reader for history
          llvm::StringRef("> "), // prompt
          llvm::StringRef(". "), // Continuation prompt
          true,                  // Multi-line
          true,                  // The REPL prompt is always colored
          1,                     // Line number
          *this, nullptr);
  
      // Don't exit if CTRL+C is pressed
      static_cast<IOHandlerEditline *>(m_io_handler_sp.get())
          ->SetInterruptExits(false);
  
      if (m_io_handler_sp->GetIsInteractive() &&
          m_io_handler_sp->GetIsRealTerminal()) {
        m_indent_str.assign(debugger.GetTabSize(), ' ');
        m_enable_auto_indent = debugger.GetAutoIndent();
      } else {
        m_indent_str.clear();
        m_enable_auto_indent = false;
      }
    }
    return m_io_handler_sp;
  }
  
  void REPL::IOHandlerActivated(IOHandler &io_handler, bool interactive) {
    lldb::ProcessSP process_sp = m_target.GetProcessSP();
    if (process_sp && process_sp->IsAlive())
      return;
    lldb::StreamFileSP error_sp(io_handler.GetErrorStreamFileSP());
    error_sp->Printf("REPL requires a running target process.\n");
    io_handler.SetIsDone(true);
  }
  
  bool REPL::IOHandlerInterrupt(IOHandler &io_handler) { return false; }
  
  void REPL::IOHandlerInputInterrupted(IOHandler &io_handler, std::string &line) {
  }
  
  const char *REPL::IOHandlerGetFixIndentationCharacters() {
    return (m_enable_auto_indent ? GetAutoIndentCharacters() : nullptr);
  }
  
  ConstString REPL::IOHandlerGetControlSequence(char ch) {
    if (ch == 'd')
      return ConstString(":quit\n");
    return ConstString();
  }
  
  const char *REPL::IOHandlerGetCommandPrefix() { return ":"; }
  
  const char *REPL::IOHandlerGetHelpPrologue() {
    return "\nThe REPL (Read-Eval-Print-Loop) acts like an interpreter.  "
           "Valid statements, expressions, and declarations are immediately "
           "compiled and executed.\n\n"
           "The complete set of LLDB debugging commands are also available as "
           "described below.\n\nCommands "
           "must be prefixed with a colon at the REPL prompt (:quit for "
           "example.)  Typing just a colon "
           "followed by return will switch to the LLDB prompt.\n\n"
           "Type “< path” to read in code from a text file “path”.\n\n";
  }
  
  bool REPL::IOHandlerIsInputComplete(IOHandler &io_handler, StringList &lines) {
    // Check for meta command
    const size_t num_lines = lines.GetSize();
    if (num_lines == 1) {
      const char *first_line = lines.GetStringAtIndex(0);
      if (first_line[0] == ':')
        return true; // Meta command is a single line where that starts with ':'
    }
  
    // Check if REPL input is done
    std::string source_string(lines.CopyList());
    return SourceIsComplete(source_string);
  }
  
  int REPL::CalculateActualIndentation(const StringList &lines) {
    std::string last_line = lines[lines.GetSize() - 1];
  
    int actual_indent = 0;
    for (char &ch : last_line) {
      if (ch != ' ')
        break;
      ++actual_indent;
    }
  
    return actual_indent;
  }
  
  int REPL::IOHandlerFixIndentation(IOHandler &io_handler,
                                    const StringList &lines,
                                    int cursor_position) {
    if (!m_enable_auto_indent)
      return 0;
  
    if (!lines.GetSize()) {
      return 0;
    }
  
    int tab_size = io_handler.GetDebugger().GetTabSize();
  
    lldb::offset_t desired_indent =
        GetDesiredIndentation(lines, cursor_position, tab_size);
  
    int actual_indent = REPL::CalculateActualIndentation(lines);
  
    if (desired_indent == LLDB_INVALID_OFFSET)
      return 0;
  
    return (int)desired_indent - actual_indent;
  }
  
  static bool ReadCode(const std::string &path, std::string &code,
                       lldb::StreamFileSP &error_sp) {
    auto &fs = FileSystem::Instance();
    llvm::Twine pathTwine(path);
    if (!fs.Exists(pathTwine)) {
      error_sp->Printf("no such file at path '%s'\n", path.c_str());
      return false;
    }
    if (!fs.Readable(pathTwine)) {
      error_sp->Printf("could not read file at path '%s'\n", path.c_str());
      return false;
    }
    const size_t file_size = fs.GetByteSize(pathTwine);
    const size_t max_size = code.max_size();
    if (file_size > max_size) {
      error_sp->Printf("file at path '%s' too large: "
                       "file_size = %zu, max_size = %zu\n",
                       path.c_str(), file_size, max_size);
      return false;
    }
    auto data_sp = fs.CreateDataBuffer(pathTwine);
    if (data_sp == nullptr) {
      error_sp->Printf("could not create buffer for file at path '%s'\n",
                       path.c_str());
      return false;
    }
    code.assign((const char *)data_sp->GetBytes(), data_sp->GetByteSize());
    return true;
  }
  
  void REPL::IOHandlerInputComplete(IOHandler &io_handler, std::string &code) {
    lldb::StreamFileSP output_sp(io_handler.GetOutputStreamFileSP());
    lldb::StreamFileSP error_sp(io_handler.GetErrorStreamFileSP());
    bool extra_line = false;
    bool did_quit = false;
  
    if (code.empty()) {
      m_code.AppendString("");
      static_cast<IOHandlerEditline &>(io_handler)
          .SetBaseLineNumber(m_code.GetSize() + 1);
    } else {
      Debugger &debugger = m_target.GetDebugger();
      CommandInterpreter &ci = debugger.GetCommandInterpreter();
      extra_line = ci.GetSpaceReplPrompts();
  
      ExecutionContext exe_ctx(m_target.GetProcessSP()
                                   ->GetThreadList()
                                   .GetSelectedThread()
                                   ->GetSelectedFrame()
                                   .get());
  
      lldb::ProcessSP process_sp(exe_ctx.GetProcessSP());
  
      if (code[0] == ':') {
        // Meta command
        // Strip the ':'
        code.erase(0, 1);
        if (!llvm::StringRef(code).trim().empty()) {
          // "lldb" was followed by arguments, so just execute the command dump
          // the results
  
          // Turn off prompt on quit in case the user types ":quit"
          const bool saved_prompt_on_quit = ci.GetPromptOnQuit();
          if (saved_prompt_on_quit)
            ci.SetPromptOnQuit(false);
  
          // Execute the command
          CommandReturnObject result(debugger.GetUseColor());
          result.SetImmediateOutputStream(output_sp);
          result.SetImmediateErrorStream(error_sp);
          ci.HandleCommand(code.c_str(), eLazyBoolNo, result);
  
          if (saved_prompt_on_quit)
            ci.SetPromptOnQuit(true);
  
          if (result.GetStatus() == lldb::eReturnStatusQuit) {
            did_quit = true;
            io_handler.SetIsDone(true);
            if (debugger.CheckTopIOHandlerTypes(
                    IOHandler::Type::REPL, IOHandler::Type::CommandInterpreter)) {
              // We typed "quit" or an alias to quit so we need to check if the
              // command interpreter is above us and tell it that it is done as
              // well so we don't drop back into the command interpreter if we
              // have already quit
              lldb::IOHandlerSP io_handler_sp(ci.GetIOHandler());
              if (io_handler_sp)
                io_handler_sp->SetIsDone(true);
            }
          }
        } else {
          // ":" was followed by no arguments, so push the LLDB command prompt
          if (debugger.CheckTopIOHandlerTypes(
                  IOHandler::Type::REPL, IOHandler::Type::CommandInterpreter)) {
            // If the user wants to get back to the command interpreter and the
            // command interpreter is what launched the REPL, then just let the
            // REPL exit and fall back to the command interpreter.
            io_handler.SetIsDone(true);
          } else {
            // The REPL wasn't launched the by the command interpreter, it is the
            // base IOHandler, so we need to get the command interpreter and
            lldb::IOHandlerSP io_handler_sp(ci.GetIOHandler());
            if (io_handler_sp) {
              io_handler_sp->SetIsDone(false);
              debugger.RunIOHandlerAsync(ci.GetIOHandler());
            }
          }
        }
      } else {
        if (code[0] == '<') {
          // User wants to read code from a file.
          // Interpret rest of line as a literal path.
          auto path = llvm::StringRef(code.substr(1)).trim().str();
          if (!ReadCode(path, code, error_sp)) {
            return;
          }
        }
  
        // Unwind any expression we might have been running in case our REPL
        // expression crashed and the user was looking around
        if (m_dedicated_repl_mode) {
          Thread *thread = exe_ctx.GetThreadPtr();
          if (thread && thread->UnwindInnermostExpression().Success()) {
            thread->SetSelectedFrameByIndex(0, false);
            exe_ctx.SetFrameSP(thread->GetSelectedFrame());
          }
        }
  
        const bool colorize_err = error_sp->GetFile().GetIsTerminalWithColors();
  
        EvaluateExpressionOptions expr_options = m_expr_options;
        expr_options.SetCoerceToId(m_varobj_options.use_objc);
        expr_options.SetKeepInMemory(true);
        expr_options.SetUseDynamic(m_varobj_options.use_dynamic);
        expr_options.SetGenerateDebugInfo(true);
        expr_options.SetREPLEnabled(true);
        expr_options.SetColorizeErrors(colorize_err);
        expr_options.SetPoundLine(m_repl_source_path.c_str(),
                                  m_code.GetSize() + 1);
  
        expr_options.SetLanguage(GetLanguage());
  
        PersistentExpressionState *persistent_state =
            m_target.GetPersistentExpressionStateForLanguage(GetLanguage());
        if (!persistent_state)
          return;
  
        const size_t var_count_before = persistent_state->GetSize();
  
        const char *expr_prefix = nullptr;
        lldb::ValueObjectSP result_valobj_sp;
        Status error;
        lldb::ExpressionResults execution_results =
            UserExpression::Evaluate(exe_ctx, expr_options, code.c_str(),
                                     expr_prefix, result_valobj_sp, error,
                                     nullptr); // fixed expression
  
        // CommandInterpreter &ci = debugger.GetCommandInterpreter();
  
        if (process_sp && process_sp->IsAlive()) {
          bool add_to_code = true;
          bool handled = false;
          if (result_valobj_sp) {
            lldb::Format format = m_format_options.GetFormat();
  
            if (result_valobj_sp->GetError().Success()) {
              handled |= PrintOneVariable(debugger, output_sp, result_valobj_sp);
            } else if (result_valobj_sp->GetError().GetError() ==
                       UserExpression::kNoResult) {
              if (format != lldb::eFormatVoid && debugger.GetNotifyVoid()) {
                error_sp->PutCString("(void)\n");
                handled = true;
              }
            }
          }
  
          if (debugger.GetPrintDecls()) {
            for (size_t vi = var_count_before, ve = persistent_state->GetSize();
                 vi != ve; ++vi) {
              lldb::ExpressionVariableSP persistent_var_sp =
                  persistent_state->GetVariableAtIndex(vi);
              lldb::ValueObjectSP valobj_sp = persistent_var_sp->GetValueObject();
  
              PrintOneVariable(debugger, output_sp, valobj_sp,
                               persistent_var_sp.get());
            }
          }
  
          if (!handled) {
            bool useColors = error_sp->GetFile().GetIsTerminalWithColors();
            switch (execution_results) {
            case lldb::eExpressionSetupError:
            case lldb::eExpressionParseError:
              add_to_code = false;
              LLVM_FALLTHROUGH;
            case lldb::eExpressionDiscarded:
              error_sp->Printf("%s\n", error.AsCString());
              break;
  
            case lldb::eExpressionCompleted:
              break;
            case lldb::eExpressionInterrupted:
              if (useColors) {
                error_sp->Printf(ANSI_ESCAPE1(ANSI_FG_COLOR_RED));
                error_sp->Printf(ANSI_ESCAPE1(ANSI_CTRL_BOLD));
              }
              error_sp->Printf("Execution interrupted. ");
              if (useColors)
                error_sp->Printf(ANSI_ESCAPE1(ANSI_CTRL_NORMAL));
              error_sp->Printf("Enter code to recover and continue.\nEnter LLDB "
                               "commands to investigate (type :help for "
                               "assistance.)\n");
              break;
  
            case lldb::eExpressionHitBreakpoint:
              // Breakpoint was hit, drop into LLDB command interpreter
              if (useColors) {
                error_sp->Printf(ANSI_ESCAPE1(ANSI_FG_COLOR_RED));
                error_sp->Printf(ANSI_ESCAPE1(ANSI_CTRL_BOLD));
              }
              output_sp->Printf("Execution stopped at breakpoint.  ");
              if (useColors)
                error_sp->Printf(ANSI_ESCAPE1(ANSI_CTRL_NORMAL));
              output_sp->Printf("Enter LLDB commands to investigate (type help "
                                "for assistance.)\n");
              {
                lldb::IOHandlerSP io_handler_sp(ci.GetIOHandler());
                if (io_handler_sp) {
                  io_handler_sp->SetIsDone(false);
                  debugger.RunIOHandlerAsync(ci.GetIOHandler());
                }
              }
              break;
  
            case lldb::eExpressionTimedOut:
              error_sp->Printf("error: timeout\n");
              if (error.AsCString())
                error_sp->Printf("error: %s\n", error.AsCString());
              break;
            case lldb::eExpressionResultUnavailable:
              // Shoulnd't happen???
              error_sp->Printf("error: could not fetch result -- %s\n",
                               error.AsCString());
              break;
            case lldb::eExpressionStoppedForDebug:
              // Shoulnd't happen???
              error_sp->Printf("error: stopped for debug -- %s\n",
                               error.AsCString());
              break;
            case lldb::eExpressionThreadVanished:
              // Shoulnd't happen???
              error_sp->Printf("error: expression thread vanished -- %s\n",
                               error.AsCString());
              break;
            }
          }
  
          if (add_to_code) {
            const uint32_t new_default_line = m_code.GetSize() + 1;
  
            m_code.SplitIntoLines(code);
  
            // Update our code on disk
            if (!m_repl_source_path.empty()) {
              auto file = FileSystem::Instance().Open(
                  FileSpec(m_repl_source_path),
                  File::eOpenOptionWriteOnly | File::eOpenOptionTruncate |
                      File::eOpenOptionCanCreate,
                  lldb::eFilePermissionsFileDefault);
              if (file) {
                std::string code(m_code.CopyList());
                code.append(1, '\n');
                size_t bytes_written = code.size();
                file.get()->Write(code.c_str(), bytes_written);
                file.get()->Close();
              } else {
                std::string message = llvm::toString(file.takeError());
                error_sp->Printf("error: couldn't open %s: %s\n",
                                 m_repl_source_path.c_str(), message.c_str());
              }
  
              // Now set the default file and line to the REPL source file
              m_target.GetSourceManager().SetDefaultFileAndLine(
                  FileSpec(m_repl_source_path), new_default_line);
            }
            static_cast<IOHandlerEditline &>(io_handler)
                .SetBaseLineNumber(m_code.GetSize() + 1);
          }
          if (extra_line) {
            output_sp->Printf("\n");
          }
        }
      }
  
      // Don't complain about the REPL process going away if we are in the
      // process of quitting.
      if (!did_quit && (!process_sp || !process_sp->IsAlive())) {
        error_sp->Printf(
            "error: REPL process is no longer alive, exiting REPL\n");
        io_handler.SetIsDone(true);
      }
    }
  }
  
  void REPL::IOHandlerComplete(IOHandler &io_handler,
                               CompletionRequest &request) {
    // Complete an LLDB command if the first character is a colon...
    if (request.GetRawLine().startswith(":")) {
      Debugger &debugger = m_target.GetDebugger();
  
      // auto complete LLDB commands
      llvm::StringRef new_line = request.GetRawLine().drop_front();
      CompletionResult sub_result;
      CompletionRequest sub_request(new_line, request.GetRawCursorPos() - 1,
                                    sub_result);
      debugger.GetCommandInterpreter().HandleCompletion(sub_request);
      StringList matches, descriptions;
      sub_result.GetMatches(matches);
      // Prepend command prefix that was excluded in the completion request.
      if (request.GetCursorIndex() == 0)
        for (auto &match : matches)
          match.insert(0, 1, ':');
      sub_result.GetDescriptions(descriptions);
      request.AddCompletions(matches, descriptions);
      return;
    }
  
    // Strip spaces from the line and see if we had only spaces
    if (request.GetRawLine().trim().empty()) {
      // Only spaces on this line, so just indent
      request.AddCompletion(m_indent_str);
      return;
    }
  
    std::string current_code;
    current_code.append(m_code.CopyList());
  
    IOHandlerEditline &editline = static_cast<IOHandlerEditline &>(io_handler);
    const StringList *current_lines = editline.GetCurrentLines();
    if (current_lines) {
      const uint32_t current_line_idx = editline.GetCurrentLineIndex();
  
      if (current_line_idx < current_lines->GetSize()) {
        for (uint32_t i = 0; i < current_line_idx; ++i) {
          const char *line_cstr = current_lines->GetStringAtIndex(i);
          if (line_cstr) {
            current_code.append("\n");
            current_code.append(line_cstr);
          }
        }
      }
    }
  
    current_code.append("\n");
    current_code += request.GetRawLine();
  
    CompleteCode(current_code, request);
  }
  
  bool QuitCommandOverrideCallback(void *baton, const char **argv) {
    Target *target = (Target *)baton;
    lldb::ProcessSP process_sp(target->GetProcessSP());
    if (process_sp) {
      process_sp->Destroy(false);
      process_sp->GetTarget().GetDebugger().ClearIOHandlers();
    }
    return false;
  }
  
  Status REPL::RunLoop() {
    Status error;
  
    error = DoInitialization();
    m_repl_source_path = GetSourcePath();
  
    if (!error.Success())
      return error;
  
    Debugger &debugger = m_target.GetDebugger();
  
    lldb::IOHandlerSP io_handler_sp(GetIOHandler());
  
    FileSpec save_default_file;
    uint32_t save_default_line = 0;
  
    if (!m_repl_source_path.empty()) {
      // Save the current default file and line
      m_target.GetSourceManager().GetDefaultFileAndLine(save_default_file,
                                                        save_default_line);
    }
  
    debugger.RunIOHandlerAsync(io_handler_sp);
  
    // Check if we are in dedicated REPL mode where LLDB was start with the "--
    // repl" option from the command line. Currently we know this by checking if
    // the debugger already has a IOHandler thread.
    if (!debugger.HasIOHandlerThread()) {
      // The debugger doesn't have an existing IOHandler thread, so this must be
      // dedicated REPL mode...
      m_dedicated_repl_mode = true;
      debugger.StartIOHandlerThread();
      llvm::StringRef command_name_str("quit");
      CommandObject *cmd_obj =
          debugger.GetCommandInterpreter().GetCommandObjectForCommand(
              command_name_str);
      if (cmd_obj) {
        assert(command_name_str.empty());
        cmd_obj->SetOverrideCallback(QuitCommandOverrideCallback, &m_target);
      }
    }
  
    // Wait for the REPL command interpreter to get popped
    io_handler_sp->WaitForPop();
  
    if (m_dedicated_repl_mode) {
      // If we were in dedicated REPL mode we would have started the IOHandler
      // thread, and we should kill our process
      lldb::ProcessSP process_sp = m_target.GetProcessSP();
      if (process_sp && process_sp->IsAlive())
        process_sp->Destroy(false);
  
      // Wait for the IO handler thread to exit (TODO: don't do this if the IO
      // handler thread already exists...)
      debugger.JoinIOHandlerThread();
    }
  
    // Restore the default file and line
    if (save_default_file && save_default_line != 0)
      m_target.GetSourceManager().SetDefaultFileAndLine(save_default_file,
                                                        save_default_line);
    return error;
  }
  