blob: 02c19ed4ef0cd260a6984755f87b84fb22ef5ca8 [file]
//===-- CommandObjectScripting.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 "CommandObjectScripting.h"
#include "lldb/Core/Debugger.h"
#include "lldb/Core/PluginManager.h"
#include "lldb/DataFormatters/DataVisualization.h"
#include "lldb/Host/Config.h"
#include "lldb/Host/Host.h"
#include "lldb/Host/OptionParser.h"
#include "lldb/Interpreter/CommandInterpreter.h"
#include "lldb/Interpreter/CommandOptionArgumentTable.h"
#include "lldb/Interpreter/CommandReturnObject.h"
#include "lldb/Interpreter/Interfaces/ScriptedInterfaceUsages.h"
#include "lldb/Interpreter/OptionArgParser.h"
#include "lldb/Interpreter/ScriptInterpreter.h"
#include "lldb/Utility/AnsiTerminal.h"
#include "lldb/Utility/Args.h"
#include "lldb/Utility/LLDBLog.h"
#include "lldb/Utility/Log.h"
#include "llvm/ADT/StringMap.h"
using namespace lldb;
using namespace lldb_private;
#define LLDB_OPTIONS_scripting_run
#include "CommandOptions.inc"
class CommandObjectScriptingRun : public CommandObjectRaw {
public:
CommandObjectScriptingRun(CommandInterpreter &interpreter)
: CommandObjectRaw(
interpreter, "scripting run",
"Invoke the script interpreter with provided code and display any "
"results. Start the interactive interpreter if no code is "
"supplied.",
"scripting run [--language <scripting-language> --] "
"[<script-code>]") {}
~CommandObjectScriptingRun() override = default;
Options *GetOptions() override { return &m_options; }
class CommandOptions : public Options {
public:
CommandOptions() = default;
~CommandOptions() override = default;
Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg,
ExecutionContext *execution_context) override {
Status error;
const int short_option = m_getopt_table[option_idx].val;
switch (short_option) {
case 'l':
language = (lldb::ScriptLanguage)OptionArgParser::ToOptionEnum(
option_arg, GetDefinitions()[option_idx].enum_values,
eScriptLanguageNone, error);
if (!error.Success())
error = Status::FromErrorStringWithFormat(
"unrecognized value for language '%s'", option_arg.str().c_str());
break;
default:
llvm_unreachable("Unimplemented option");
}
return error;
}
void OptionParsingStarting(ExecutionContext *execution_context) override {
language = lldb::eScriptLanguageNone;
}
llvm::ArrayRef<OptionDefinition> GetDefinitions() override {
return llvm::ArrayRef(g_scripting_run_options);
}
lldb::ScriptLanguage language = lldb::eScriptLanguageNone;
};
protected:
void DoExecute(llvm::StringRef command,
CommandReturnObject &result) override {
// Try parsing the language option but when the command contains a raw part
// separated by the -- delimiter.
OptionsWithRaw raw_args(command);
if (raw_args.HasArgs()) {
if (!ParseOptions(raw_args.GetArgs(), result))
return;
command = raw_args.GetRawPart();
}
lldb::ScriptLanguage language =
(m_options.language == lldb::eScriptLanguageNone)
? m_interpreter.GetDebugger().GetScriptLanguage()
: m_options.language;
if (language == lldb::eScriptLanguageNone) {
result.AppendError(
"the script-lang setting is set to none - scripting not available");
return;
}
ScriptInterpreter *script_interpreter =
GetDebugger().GetScriptInterpreter(true, language);
if (script_interpreter == nullptr) {
result.AppendError("no script interpreter");
return;
}
// Script might change Python code we use for formatting. Make sure we keep
// up to date with it.
DataVisualization::ForceUpdate();
if (command.empty()) {
script_interpreter->ExecuteInterpreterLoop();
result.SetStatus(eReturnStatusSuccessFinishNoResult);
return;
}
// We can do better when reporting the status of one-liner script execution.
if (script_interpreter->ExecuteOneLine(command, &result))
result.SetStatus(eReturnStatusSuccessFinishNoResult);
else
result.SetStatus(eReturnStatusFailed);
}
private:
CommandOptions m_options;
};
#define LLDB_OPTIONS_scripting_extension_list
#include "CommandOptions.inc"
class CommandObjectScriptingExtensionList : public CommandObjectParsed {
public:
CommandObjectScriptingExtensionList(CommandInterpreter &interpreter)
: CommandObjectParsed(
interpreter, "scripting extension list",
"List all the available scripting extension templates. ",
"scripting extension list [--language <scripting-language> --] "
"[--json --] [<extension-name> ...]") {
AddSimpleArgumentList(eArgTypeScriptedExtension, eArgRepeatStar);
}
~CommandObjectScriptingExtensionList() override = default;
Options *GetOptions() override { return &m_options; }
void
HandleArgumentCompletion(CompletionRequest &request,
OptionElementVector &opt_element_vector) override {
lldb_private::CommandCompletions::InvokeCommonCompletionCallbacks(
GetCommandInterpreter(), lldb::eScriptedExtensionCompletion, request,
nullptr);
}
class CommandOptions : public Options {
public:
CommandOptions() = default;
~CommandOptions() override = default;
Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg,
ExecutionContext *execution_context) override {
Status error;
const int short_option = m_getopt_table[option_idx].val;
switch (short_option) {
case 'l':
m_language = (lldb::ScriptLanguage)OptionArgParser::ToOptionEnum(
option_arg, GetDefinitions()[option_idx].enum_values,
eScriptLanguageNone, error);
if (!error.Success())
error = Status::FromErrorStringWithFormatv(
"unrecognized value for language '{0}'", option_arg);
break;
case 'j':
m_json_format = true;
break;
default:
llvm_unreachable("Unimplemented option");
}
return error;
}
void OptionParsingStarting(ExecutionContext *execution_context) override {
m_language = lldb::eScriptLanguageDefault;
m_json_format = false;
}
llvm::ArrayRef<OptionDefinition> GetDefinitions() override {
return llvm::ArrayRef(g_scripting_extension_list_options);
}
lldb::ScriptLanguage m_language = lldb::eScriptLanguageDefault;
bool m_json_format = false;
};
protected:
void DoExecute(Args &command, CommandReturnObject &result) override {
llvm::StringMap<std::vector<size_t>> grouped_by_extension;
for (size_t i = 0; i < PluginManager::GetNumScriptedInterfaces(); i++) {
lldb::ScriptedExtension extension =
PluginManager::GetScriptedInterfaceExtensionAtIndex(i);
if (extension == eScriptedExtensionInvalid)
continue;
llvm::StringLiteral extension_name =
ScriptInterpreter::ExtensionToString(extension);
if (grouped_by_extension.contains(extension_name))
grouped_by_extension[extension_name].push_back(i);
else
grouped_by_extension[extension_name] = {i};
}
if (command.GetArgumentCount() > 0) {
llvm::StringMap<std::vector<size_t>> filtered;
for (const Args::ArgEntry &arg : command.entries()) {
lldb::ScriptedExtension extension =
ScriptInterpreter::StringToExtension(arg.ref());
if (extension == eScriptedExtensionInvalid) {
result.AppendErrorWithFormat("no scripted extension named '%s'",
arg.c_str());
return;
}
llvm::StringLiteral extension_name =
ScriptInterpreter::ExtensionToString(extension);
auto it = grouped_by_extension.find(extension_name);
if (it != grouped_by_extension.end())
filtered[extension_name] = it->second;
}
grouped_by_extension = std::move(filtered);
}
if (m_options.m_json_format)
OutputJsonFormat(grouped_by_extension, result);
else
OutputTextFormat(grouped_by_extension, result);
}
private:
std::vector<std::string>
GetLanguagesForExtension(const std::vector<size_t> &indices) {
std::vector<std::string> languages;
for (const size_t idx : indices) {
lldb::ScriptLanguage lang =
PluginManager::GetScriptedInterfaceLanguageAtIndex(idx);
if (lang != m_options.m_language)
continue;
languages.push_back(ScriptInterpreter::LanguageToString(lang));
}
return languages;
}
void OutputJsonFormat(
const llvm::StringMap<std::vector<size_t>> &grouped_by_extension,
CommandReturnObject &result) {
llvm::json::Array extensions;
for (const auto &extension_pair : grouped_by_extension) {
// llvm::json::Value's StringRef constructor does not copy the
// underlying characters, so every string handed to the JSON structure
// below must be an owned std::string -- otherwise it dangles by the
// time the object tree is serialized at the end of this function.
llvm::json::Array languages;
for (const std::string &lang :
GetLanguagesForExtension(extension_pair.second))
languages.push_back(lang);
if (languages.empty())
continue;
llvm::StringRef desc =
PluginManager::GetScriptedInterfaceDescriptionAtIndex(
extension_pair.second[0]);
ScriptedInterfaceUsages usages =
PluginManager::GetScriptedInterfaceUsagesAtIndex(
extension_pair.second[0]);
llvm::json::Array api_usages;
for (llvm::StringRef usage : usages.GetSBAPIUsages())
api_usages.push_back(usage.str());
llvm::json::Array cmd_usages;
for (llvm::StringRef usage : usages.GetCommandInterpreterUsages())
cmd_usages.push_back(usage.str());
extensions.push_back(llvm::json::Object{
{"name", extension_pair.first().str()},
{"description", desc.str()},
{"languages", std::move(languages)},
{"api_usages", std::move(api_usages)},
{"command_interpreter_usages", std::move(cmd_usages)},
});
}
std::string str;
llvm::raw_string_ostream os(str);
os << llvm::formatv("{0:2}", llvm::json::Value(std::move(extensions)));
result.AppendMessage(str);
result.SetStatus(eReturnStatusSuccessFinishResult);
}
void OutputTextFormat(
const llvm::StringMap<std::vector<size_t>> &grouped_by_extension,
CommandReturnObject &result) {
Stream &s = result.GetOutputStream();
const bool use_color = s.AsRawOstream().colors_enabled();
auto ansi_code = [use_color](llvm::StringRef code) {
return ansi::FormatAnsiTerminalCodes(code, use_color);
};
const std::string label_color = ansi_code("${ansi.fg.green}${ansi.bold}");
const std::string name_color = ansi_code("${ansi.fg.cyan}${ansi.bold}");
const std::string sep_color = ansi_code("${ansi.faint}");
const std::string reset = ansi_code("${ansi.normal}");
const std::string separator(
std::min<uint64_t>(GetDebugger().GetTerminalWidth(), 80), '-');
s.PutCString("Available scripted extension templates:");
auto print_field = [&](llvm::StringRef key, llvm::StringRef value,
const std::string &value_color = std::string()) {
if (value.empty())
return;
s.IndentMore();
s.Indent();
s << label_color << key << ": " << reset;
if (!value_color.empty())
s << value_color << value << reset;
else
s << value;
s << '\n';
s.IndentLess();
};
size_t num_listed_interface = 0;
for (const auto &extension_pair : grouped_by_extension) {
std::vector<std::string> languages =
GetLanguagesForExtension(extension_pair.second);
if (languages.empty())
continue;
num_listed_interface++;
s.EOL();
s << sep_color << separator << reset;
s.EOL();
llvm::StringRef desc =
PluginManager::GetScriptedInterfaceDescriptionAtIndex(
extension_pair.second[0]);
ScriptedInterfaceUsages usages =
PluginManager::GetScriptedInterfaceUsagesAtIndex(
extension_pair.second[0]);
print_field("Name", extension_pair.first(), name_color);
print_field("Description", desc);
print_field("Language", llvm::join(languages, ""));
usages.Dump(s, ScriptedInterfaceUsages::UsageKind::API, use_color);
usages.Dump(s, ScriptedInterfaceUsages::UsageKind::CommandInterpreter,
use_color);
}
if (!num_listed_interface)
s << " None\n";
result.SetStatus(eReturnStatusSuccessFinishResult);
}
private:
CommandOptions m_options;
};
#define LLDB_OPTIONS_scripting_extension_generate
#include "CommandOptions.inc"
class CommandObjectScriptingExtensionGenerate : public CommandObjectParsed {
public:
CommandObjectScriptingExtensionGenerate(CommandInterpreter &interpreter)
: CommandObjectParsed(interpreter, "scripting extension generate",
"Generate a scripting extension template. ",
"scripting extension generate") {
AddSimpleArgumentList(eArgTypeScriptedExtension, eArgRepeatPlus);
}
~CommandObjectScriptingExtensionGenerate() override = default;
Options *GetOptions() override { return &m_options; }
class CommandOptions : public Options {
public:
CommandOptions() = default;
~CommandOptions() override = default;
Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg,
ExecutionContext *execution_context) override {
Status error;
const char short_option =
g_scripting_extension_generate_options[option_idx].short_option;
switch (short_option) {
case 'a':
m_generate_non_abstract_methods = true;
break;
case 'l':
m_language = (lldb::ScriptLanguage)OptionArgParser::ToOptionEnum(
option_arg, GetDefinitions()[option_idx].enum_values,
eScriptLanguageNone, error);
if (!error.Success())
error = Status::FromErrorStringWithFormatv(
"unrecognized value for language '{0}'", option_arg);
break;
case 'n':
m_generated_class_prefix = option_arg.str();
break;
case 'o':
m_output_filepath = option_arg.str();
break;
case 'e': {
bool success;
m_open_editor = OptionArgParser::ToBoolean(option_arg, true, &success)
? eLazyBoolYes
: eLazyBoolNo;
if (!success)
error = Status::FromErrorStringWithFormatv(
"invalid boolean value for -e: '{0}'", option_arg);
} break;
default:
llvm_unreachable("Unimplemented option");
}
return error;
}
void OptionParsingStarting(ExecutionContext *execution_context) override {
m_generate_non_abstract_methods = false;
m_language = lldb::eScriptLanguageNone;
m_generated_class_prefix.clear();
m_output_filepath.clear();
m_open_editor = eLazyBoolCalculate;
}
llvm::ArrayRef<OptionDefinition> GetDefinitions() override {
return llvm::ArrayRef(g_scripting_extension_generate_options);
}
bool m_generate_non_abstract_methods = false;
lldb::ScriptLanguage m_language = lldb::eScriptLanguageNone;
std::string m_generated_class_prefix;
std::string m_output_filepath;
LazyBool m_open_editor = eLazyBoolCalculate;
};
void
HandleArgumentCompletion(CompletionRequest &request,
OptionElementVector &opt_element_vector) override {
// If `-l <lang>` was already given, complete only extensions available
// in that language. Bare `-l` (no value yet) and any parse failures
// fall back to language-agnostic completion.
lldb::ScriptLanguage language = lldb::eScriptLanguageUnknown;
llvm::ArrayRef<OptionDefinition> defs = m_options.GetDefinitions();
for (const OptionArgElement &elem : opt_element_vector) {
if (elem.opt_defs_index < 0 ||
static_cast<size_t>(elem.opt_defs_index) >= defs.size())
continue;
if (defs[elem.opt_defs_index].short_option != 'l' ||
elem.opt_arg_pos <= 0)
continue;
llvm::StringRef value =
request.GetParsedLine().GetArgumentAtIndex(elem.opt_arg_pos);
Status error;
auto candidate =
static_cast<lldb::ScriptLanguage>(OptionArgParser::ToOptionEnum(
value, defs[elem.opt_defs_index].enum_values,
lldb::eScriptLanguageUnknown, error));
if (error.Success())
language = candidate;
break;
}
PluginManager::AutoCompleteScriptedExtension(
request.GetCursorArgumentPrefix(), request, language);
lldb_private::CommandCompletions::InvokeCommonCompletionCallbacks(
GetCommandInterpreter(), lldb::eDiskFileCompletion, request, nullptr);
}
protected:
void DoExecute(Args &command, CommandReturnObject &result) override {
if (command.GetArgumentCount() == 0) {
result.SetError(
Status::FromErrorString("specify extension name to generate"));
return;
}
std::vector<ScriptInterpreter::ExtensionTemplateRequest> extension_requests;
for (size_t i = 0; i < command.GetArgumentCount(); i++) {
llvm::StringRef extension_name = command.GetArgumentAtIndex(i);
llvm::SmallVector<llvm::StringRef> extension_components;
extension_name.split(extension_components, ".");
lldb::ScriptedExtension extension =
ScriptInterpreter::StringToExtension(extension_components.back());
if (extension == eScriptedExtensionInvalid) {
result.SetError(Status::FromErrorStringWithFormatv(
"unknown scripted extension: '{0}'", extension_name));
return;
}
extension_requests.push_back({extension_name, extension_components});
}
lldb::ScriptLanguage language =
(m_options.m_language == lldb::eScriptLanguageNone)
? m_interpreter.GetDebugger().GetScriptLanguage()
: m_options.m_language;
if (language == lldb::eScriptLanguageNone) {
result.AppendError(
"the script-lang setting is set to none - scripting not available");
return;
}
ScriptInterpreter *script_interpreter =
GetDebugger().GetScriptInterpreter(true, language);
if (script_interpreter == nullptr) {
result.AppendError("no script interpreter");
return;
}
auto generated_file_or_err = script_interpreter->GenerateExtensionTemplate(
m_options.m_generated_class_prefix, extension_requests,
m_options.m_generate_non_abstract_methods, m_options.m_output_filepath);
if (!generated_file_or_err) {
result.SetError(generated_file_or_err.takeError());
return;
}
bool should_open_editor = false;
switch (m_options.m_open_editor) {
case eLazyBoolYes:
should_open_editor = true;
break;
case eLazyBoolNo:
should_open_editor = false;
break;
case eLazyBoolCalculate:
should_open_editor = result.GetInteractive();
break;
}
if (should_open_editor) {
if (llvm::Error err = Host::OpenFileInExternalEditor(
"", *generated_file_or_err, 1, true)) {
LLDB_LOG_ERROR(GetLog(LLDBLog::Host), std::move(err),
"OpenFileInExternalEditor failed: {0}");
}
}
result.AppendMessageWithFormatv(
"Generated scripting extension template: {0}",
generated_file_or_err->GetPath());
result.SetStatus(eReturnStatusSuccessFinishNoResult);
}
private:
CommandOptions m_options;
};
class CommandObjectMultiwordScriptingExtension : public CommandObjectMultiword {
public:
CommandObjectMultiwordScriptingExtension(CommandInterpreter &interpreter)
: CommandObjectMultiword(
interpreter, "scripting extension",
"Commands for operating on the scripting extensions.",
"scripting extension [<subcommand-options>]") {
LoadSubCommand(
"list",
CommandObjectSP(new CommandObjectScriptingExtensionList(interpreter)));
LoadSubCommand("generate",
CommandObjectSP(new CommandObjectScriptingExtensionGenerate(
interpreter)));
}
~CommandObjectMultiwordScriptingExtension() override = default;
};
CommandObjectMultiwordScripting::CommandObjectMultiwordScripting(
CommandInterpreter &interpreter)
: CommandObjectMultiword(
interpreter, "scripting",
"Commands for operating on the scripting functionalities.",
"scripting <subcommand> [<subcommand-options>]") {
LoadSubCommand("run",
CommandObjectSP(new CommandObjectScriptingRun(interpreter)));
LoadSubCommand("extension",
CommandObjectSP(new CommandObjectMultiwordScriptingExtension(
interpreter)));
}
CommandObjectMultiwordScripting::~CommandObjectMultiwordScripting() = default;