Skip to content

Commit

Permalink
[lldb/Lua] Implement a Simple Lua Script Interpreter Prototype
Browse files Browse the repository at this point in the history
This implements a very elementary Lua script interpreter. It supports
running a single command as well as running interactively. It uses
editline if available. It's still missing a bunch of stuff though. Some
things that I intentionally ingored for now are that I/O isn't properly
hooked up (so every print goes to stdout) and the non-editline support
which is not handling a bunch of corner cases. The latter is a matter of
reusing existing code in the Python interpreter.

Discussion on the mailing list:
http://lists.llvm.org/pipermail/lldb-dev/2019-December/015812.html

Differential revision: https://reviews.llvm.org/D71234
  • Loading branch information
JDevlieghere committed Dec 20, 2019
1 parent 2a42a5a commit 2861324
Show file tree
Hide file tree
Showing 14 changed files with 235 additions and 12 deletions.
3 changes: 3 additions & 0 deletions lldb/cmake/modules/LLDBConfig.cmake
Expand Up @@ -113,6 +113,9 @@ if ((NOT MSVC) OR MSVC12)
add_definitions( -DHAVE_ROUND )
endif()

if (LLDB_ENABLE_LUA)
find_package(Lua REQUIRED)
endif()

if (LLDB_ENABLE_LIBEDIT)
find_package(LibEdit REQUIRED)
Expand Down
1 change: 1 addition & 0 deletions lldb/include/lldb/Core/IOHandler.h
Expand Up @@ -52,6 +52,7 @@ class IOHandler {
REPL,
ProcessIO,
PythonInterpreter,
LuaInterpreter,
PythonCode,
Other
};
Expand Down
6 changes: 5 additions & 1 deletion lldb/source/Plugins/ScriptInterpreter/Lua/CMakeLists.txt
@@ -1,7 +1,11 @@
add_lldb_library(lldbPluginScriptInterpreterLua PLUGIN
Lua.cpp
ScriptInterpreterLua.cpp

LINK_LIBS
lldbCore
lldbInterpreter
)
)

target_include_directories(lldbPluginScriptInterpreterLua PUBLIC ${LUA_INCLUDE_DIR})
target_link_libraries(lldbPluginScriptInterpreterLua INTERFACE ${LUA_LIBRARIES})
27 changes: 27 additions & 0 deletions lldb/source/Plugins/ScriptInterpreter/Lua/Lua.cpp
@@ -0,0 +1,27 @@
//===-- Lua.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 "Lua.h"
#include "llvm/Support/FormatVariadic.h"

using namespace lldb_private;

llvm::Error Lua::Run(llvm::StringRef buffer) {
int error =
luaL_loadbuffer(m_lua_state, buffer.data(), buffer.size(), "buffer") ||
lua_pcall(m_lua_state, 0, 0, 0);
if (!error)
return llvm::Error::success();

llvm::Error e = llvm::make_error<llvm::StringError>(
llvm::formatv("{0}\n", lua_tostring(m_lua_state, -1)),
llvm::inconvertibleErrorCode());
// Pop error message from the stack.
lua_pop(m_lua_state, 1);
return e;
}
39 changes: 39 additions & 0 deletions lldb/source/Plugins/ScriptInterpreter/Lua/Lua.h
@@ -0,0 +1,39 @@
//===-- ScriptInterpreterLua.h ----------------------------------*- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//

#ifndef liblldb_Lua_h_
#define liblldb_Lua_h_

#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Error.h"

#include "lua.hpp"

namespace lldb_private {

class Lua {
public:
Lua() : m_lua_state(luaL_newstate()) {
assert(m_lua_state);
luaL_openlibs(m_lua_state);
}

~Lua() {
assert(m_lua_state);
luaL_openlibs(m_lua_state);
}

llvm::Error Run(llvm::StringRef buffer);

private:
lua_State *m_lua_state;
};

} // namespace lldb_private

#endif // liblldb_Lua_h_
60 changes: 49 additions & 11 deletions lldb/source/Plugins/ScriptInterpreter/Lua/ScriptInterpreterLua.cpp
Expand Up @@ -7,35 +7,73 @@
//===----------------------------------------------------------------------===//

#include "ScriptInterpreterLua.h"
#include "Lua.h"
#include "lldb/Core/Debugger.h"
#include "lldb/Core/PluginManager.h"
#include "lldb/Core/StreamFile.h"
#include "lldb/Interpreter/CommandReturnObject.h"
#include "lldb/Utility/Stream.h"
#include "lldb/Utility/StringList.h"

#include "llvm/Support/Threading.h"

#include <mutex>
#include "lldb/Utility/Timer.h"

using namespace lldb;
using namespace lldb_private;

class IOHandlerLuaInterpreter : public IOHandlerDelegate,
public IOHandlerEditline {
public:
IOHandlerLuaInterpreter(Debugger &debugger)
: IOHandlerEditline(debugger, IOHandler::Type::LuaInterpreter, "lua",
">>> ", "..> ", true, debugger.GetUseColor(), 0,
*this, nullptr),
m_lua() {}

void IOHandlerInputComplete(IOHandler &io_handler,
std::string &data) override {
if (llvm::Error error = m_lua.Run(data)) {
*GetOutputStreamFileSP() << llvm::toString(std::move(error));
}
}

private:
Lua m_lua;
};

ScriptInterpreterLua::ScriptInterpreterLua(Debugger &debugger)
: ScriptInterpreter(debugger, eScriptLanguageLua) {}

ScriptInterpreterLua::~ScriptInterpreterLua() {}

bool ScriptInterpreterLua::ExecuteOneLine(llvm::StringRef command,
CommandReturnObject *,
const ExecuteScriptOptions &) {
m_debugger.GetErrorStream().PutCString(
"error: the lua script interpreter is not yet implemented.\n");
return false;
CommandReturnObject *result,
const ExecuteScriptOptions &options) {
Lua l;
if (llvm::Error e = l.Run(command)) {
result->AppendErrorWithFormatv(
"lua failed attempting to evaluate '{0}': {1}\n", command,
llvm::toString(std::move(e)));
return false;
}
return true;
}

void ScriptInterpreterLua::ExecuteInterpreterLoop() {
m_debugger.GetErrorStream().PutCString(
"error: the lua script interpreter is not yet implemented.\n");
static Timer::Category func_cat(LLVM_PRETTY_FUNCTION);
Timer scoped_timer(func_cat, LLVM_PRETTY_FUNCTION);

Debugger &debugger = m_debugger;

// At the moment, the only time the debugger does not have an input file
// handle is when this is called directly from lua, in which case it is
// both dangerous and unnecessary (not to mention confusing) to try to embed
// a running interpreter loop inside the already running lua interpreter
// loop, so we won't do it.

if (!debugger.GetInputFile().IsValid())
return;

IOHandlerSP io_handler_sp(new IOHandlerLuaInterpreter(debugger));
debugger.PushIOHandler(io_handler_sp);
}

void ScriptInterpreterLua::Initialize() {
Expand Down
1 change: 1 addition & 0 deletions lldb/test/CMakeLists.txt
Expand Up @@ -144,6 +144,7 @@ endif()
# These values are not canonicalized within LLVM.
llvm_canonicalize_cmake_booleans(
LLDB_ENABLE_PYTHON
LLDB_ENABLE_LUA
LLVM_ENABLE_ZLIB
LLVM_ENABLE_SHARED_LIBS
LLDB_IS_64_BITS)
Expand Down
3 changes: 3 additions & 0 deletions lldb/test/Shell/ScriptInterpreter/Lua/lua.test
@@ -0,0 +1,3 @@
# REQUIRES: lua
# RUN: %lldb --script-language lua -o 'script print(1000+100+10+1)' 2>&1 | FileCheck %s
# CHECK: 1111
3 changes: 3 additions & 0 deletions lldb/test/Shell/lit.cfg.py
Expand Up @@ -103,6 +103,9 @@ def calculate_arch_features(arch_string):
if config.lldb_enable_python:
config.available_features.add('python')

if config.lldb_enable_lua:
config.available_features.add('lua')

if config.lldb_enable_lzma:
config.available_features.add('lzma')

Expand Down
1 change: 1 addition & 0 deletions lldb/test/Shell/lit.site.cfg.py.in
Expand Up @@ -19,6 +19,7 @@ config.lldb_enable_lzma = @LLDB_ENABLE_LZMA@
config.host_triple = "@LLVM_HOST_TRIPLE@"
config.lldb_bitness = 64 if @LLDB_IS_64_BITS@ else 32
config.lldb_enable_python = @LLDB_ENABLE_PYTHON@
config.lldb_enable_lua = @LLDB_ENABLE_LUA@
config.lldb_build_directory = "@LLDB_TEST_BUILD_DIRECTORY@"
# The shell tests use their own module caches.
config.lldb_module_cache = os.path.join("@LLDB_TEST_MODULE_CACHE_LLDB@", "lldb-shell")
Expand Down
3 changes: 3 additions & 0 deletions lldb/unittests/ScriptInterpreter/CMakeLists.txt
@@ -1,3 +1,6 @@
if (LLDB_ENABLE_PYTHON)
add_subdirectory(Python)
endif()
if (LLDB_ENABLE_LUA)
add_subdirectory(Lua)
endif()
12 changes: 12 additions & 0 deletions lldb/unittests/ScriptInterpreter/Lua/CMakeLists.txt
@@ -0,0 +1,12 @@
add_lldb_unittest(ScriptInterpreterLuaTests
LuaTests.cpp
ScriptInterpreterTests.cpp

LINK_LIBS
lldbHost
lldbPluginScriptInterpreterLua
lldbPluginPlatformLinux
LLVMTestingSupport
LINK_COMPONENTS
Support
)
26 changes: 26 additions & 0 deletions lldb/unittests/ScriptInterpreter/Lua/LuaTests.cpp
@@ -0,0 +1,26 @@
//===-- LuaTests.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 "Plugins/ScriptInterpreter/Lua/Lua.h"
#include "gtest/gtest.h"

using namespace lldb_private;

TEST(LuaTest, RunValid) {
Lua lua;
llvm::Error error = lua.Run("foo = 1");
EXPECT_FALSE(static_cast<bool>(error));
}

TEST(LuaTest, RunInvalid) {
Lua lua;
llvm::Error error = lua.Run("nil = foo");
EXPECT_TRUE(static_cast<bool>(error));
EXPECT_EQ(llvm::toString(std::move(error)),
"[string \"buffer\"]:1: unexpected symbol near 'nil'\n");
}
62 changes: 62 additions & 0 deletions lldb/unittests/ScriptInterpreter/Lua/ScriptInterpreterTests.cpp
@@ -0,0 +1,62 @@
//===-- LuaTests.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 "Plugins/Platform/Linux/PlatformLinux.h"
#include "Plugins/ScriptInterpreter/Lua/ScriptInterpreterLua.h"
#include "lldb/Core/Debugger.h"
#include "lldb/Host/FileSystem.h"
#include "lldb/Host/HostInfo.h"
#include "lldb/Interpreter/CommandReturnObject.h"
#include "lldb/Target/Platform.h"
#include "lldb/Utility/Reproducer.h"
#include "gtest/gtest.h"

using namespace lldb_private;
using namespace lldb_private::repro;
using namespace lldb;

namespace {
class ScriptInterpreterTest : public ::testing::Test {
public:
void SetUp() override {
llvm::cantFail(Reproducer::Initialize(ReproducerMode::Off, llvm::None));
FileSystem::Initialize();
HostInfo::Initialize();

// Pretend Linux is the host platform.
platform_linux::PlatformLinux::Initialize();
ArchSpec arch("powerpc64-pc-linux");
Platform::SetHostPlatform(
platform_linux::PlatformLinux::CreateInstance(true, &arch));
}
void TearDown() override {
platform_linux::PlatformLinux::Terminate();
HostInfo::Terminate();
FileSystem::Terminate();
Reproducer::Terminate();
}
};
} // namespace

TEST_F(ScriptInterpreterTest, Plugin) {
EXPECT_EQ(ScriptInterpreterLua::GetPluginNameStatic(), "script-lua");
EXPECT_EQ(ScriptInterpreterLua::GetPluginDescriptionStatic(),
"Lua script interpreter");
}

TEST_F(ScriptInterpreterTest, ExecuteOneLine) {
DebuggerSP debugger_sp = Debugger::CreateInstance();
ASSERT_TRUE(debugger_sp);

ScriptInterpreterLua script_interpreter(*debugger_sp);
CommandReturnObject result;
EXPECT_TRUE(script_interpreter.ExecuteOneLine("foo = 1", &result));
EXPECT_FALSE(script_interpreter.ExecuteOneLine("nil = foo", &result));
EXPECT_TRUE(result.GetErrorData().startswith(
"error: lua failed attempting to evaluate 'nil = foo'"));
}

0 comments on commit 2861324

Please sign in to comment.