1//===-- ScriptInterpreterLua.cpp --------------------------------*- C++ -*-===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
9#include "ScriptInterpreterLua.h"
10#include "Lua.h"
11#include "lldb/Core/Debugger.h"
12#include "lldb/Core/PluginManager.h"
13#include "lldb/Core/StreamFile.h"
14#include "lldb/Interpreter/CommandReturnObject.h"
15#include "lldb/Utility/Stream.h"
16#include "lldb/Utility/StringList.h"
17#include "lldb/Utility/Timer.h"
18
19using namespace lldb;
20using namespace lldb_private;
21
22class IOHandlerLuaInterpreter : public IOHandlerDelegate,
23                                public IOHandlerEditline {
24public:
25  IOHandlerLuaInterpreter(Debugger &debugger,
26                          ScriptInterpreterLua &script_interpreter)
27      : IOHandlerEditline(debugger, IOHandler::Type::LuaInterpreter, "lua",
28                          ">>> ", "..> ", true, debugger.GetUseColor(), 0,
29                          *this, nullptr),
30        m_script_interpreter(script_interpreter) {
31    llvm::cantFail(m_script_interpreter.EnterSession(debugger.GetID()));
32  }
33
34  ~IOHandlerLuaInterpreter() {
35    llvm::cantFail(m_script_interpreter.LeaveSession());
36  }
37
38  void IOHandlerInputComplete(IOHandler &io_handler,
39                              std::string &data) override {
40    if (llvm::Error error = m_script_interpreter.GetLua().Run(data)) {
41      *GetOutputStreamFileSP() << llvm::toString(std::move(error));
42    }
43  }
44
45private:
46  ScriptInterpreterLua &m_script_interpreter;
47};
48
49ScriptInterpreterLua::ScriptInterpreterLua(Debugger &debugger)
50    : ScriptInterpreter(debugger, eScriptLanguageLua),
51      m_lua(std::make_unique<Lua>()) {}
52
53ScriptInterpreterLua::~ScriptInterpreterLua() {}
54
55bool ScriptInterpreterLua::ExecuteOneLine(llvm::StringRef command,
56                                          CommandReturnObject *result,
57                                          const ExecuteScriptOptions &options) {
58  if (llvm::Error e = m_lua->Run(command)) {
59    result->AppendErrorWithFormatv(
60        "lua failed attempting to evaluate '{0}': {1}\n", command,
61        llvm::toString(std::move(e)));
62    return false;
63  }
64  return true;
65}
66
67void ScriptInterpreterLua::ExecuteInterpreterLoop() {
68  static Timer::Category func_cat(LLVM_PRETTY_FUNCTION);
69  Timer scoped_timer(func_cat, LLVM_PRETTY_FUNCTION);
70
71  Debugger &debugger = m_debugger;
72
73  // At the moment, the only time the debugger does not have an input file
74  // handle is when this is called directly from lua, in which case it is
75  // both dangerous and unnecessary (not to mention confusing) to try to embed
76  // a running interpreter loop inside the already running lua interpreter
77  // loop, so we won't do it.
78
79  if (!debugger.GetInputFile().IsValid())
80    return;
81
82  IOHandlerSP io_handler_sp(new IOHandlerLuaInterpreter(debugger, *this));
83  debugger.PushIOHandler(io_handler_sp);
84}
85
86bool ScriptInterpreterLua::LoadScriptingModule(
87    const char *filename, bool init_session, lldb_private::Status &error,
88    StructuredData::ObjectSP *module_sp) {
89
90  if (llvm::Error e = m_lua->LoadModule(filename)) {
91    error.SetErrorStringWithFormatv("lua failed to import '{0}': {1}\n",
92                                    filename, llvm::toString(std::move(e)));
93    return false;
94  }
95  return true;
96}
97
98void ScriptInterpreterLua::Initialize() {
99  static llvm::once_flag g_once_flag;
100
101  llvm::call_once(g_once_flag, []() {
102    PluginManager::RegisterPlugin(GetPluginNameStatic(),
103                                  GetPluginDescriptionStatic(),
104                                  lldb::eScriptLanguageLua, CreateInstance);
105  });
106}
107
108void ScriptInterpreterLua::Terminate() {}
109
110llvm::Error ScriptInterpreterLua::EnterSession(user_id_t debugger_id) {
111  if (m_session_is_active)
112    return llvm::Error::success();
113
114  const char *fmt_str =
115      "lldb.debugger = lldb.SBDebugger.FindDebuggerWithID({0}); "
116      "lldb.target = lldb.debugger:GetSelectedTarget(); "
117      "lldb.process = lldb.target:GetProcess(); "
118      "lldb.thread = lldb.process:GetSelectedThread(); "
119      "lldb.frame = lldb.thread:GetSelectedFrame()";
120  return m_lua->Run(llvm::formatv(fmt_str, debugger_id).str());
121}
122
123llvm::Error ScriptInterpreterLua::LeaveSession() {
124  if (!m_session_is_active)
125    return llvm::Error::success();
126
127  m_session_is_active = false;
128
129  llvm::StringRef str = "lldb.debugger = nil; "
130                        "lldb.target = nil; "
131                        "lldb.process = nil; "
132                        "lldb.thread = nil; "
133                        "lldb.frame = nil";
134  return m_lua->Run(str);
135}
136
137lldb::ScriptInterpreterSP
138ScriptInterpreterLua::CreateInstance(Debugger &debugger) {
139  return std::make_shared<ScriptInterpreterLua>(debugger);
140}
141
142lldb_private::ConstString ScriptInterpreterLua::GetPluginNameStatic() {
143  static ConstString g_name("script-lua");
144  return g_name;
145}
146
147const char *ScriptInterpreterLua::GetPluginDescriptionStatic() {
148  return "Lua script interpreter";
149}
150
151lldb_private::ConstString ScriptInterpreterLua::GetPluginName() {
152  return GetPluginNameStatic();
153}
154
155uint32_t ScriptInterpreterLua::GetPluginVersion() { return 1; }
156
157Lua &ScriptInterpreterLua::GetLua() { return *m_lua; }
158