root/src/apps/debugger/user_interface/cli/CommandLineUserInterface.cpp
/*
 * Copyright 2011-2016, Rene Gollent, rene@gollent.com.
 * Copyright 2012, Ingo Weinhold, ingo_weinhold@gmx.de.
 * Distributed under the terms of the MIT License.
 */


#include "CommandLineUserInterface.h"

#include <stdio.h>

#include <algorithm>

#include <ArgumentVector.h>
#include <AutoDeleter.h>
#include <AutoLocker.h>
#include <Referenceable.h>

#include "CliContext.h"
#include "CliContinueCommand.h"
#include "CliDebugReportCommand.h"
#include "CliDumpMemoryCommand.h"
#include "CliDumpStringCommand.h"
#include "CliPrintVariableCommand.h"
#include "CliQuitCommand.h"
#include "CliStackFrameCommand.h"
#include "CliStackTraceCommand.h"
#include "CliStopCommand.h"
#include "CliThreadCommand.h"
#include "CliThreadsCommand.h"
#include "CliVariablesCommand.h"
#include "CliWriteCoreFileCommand.h"


static const char* kDebuggerPrompt = "debugger> ";


// #pragma mark - CommandEntry


struct CommandLineUserInterface::CommandEntry {
        CommandEntry(const BString& name, CliCommand* command)
                :
                fName(name),
                fCommand(command)
        {
        }

        const BString& Name() const
        {
                return fName;
        }

        CliCommand* Command() const
        {
                return fCommand.Get();
        }

private:
        BString                                 fName;
        BReference<CliCommand>  fCommand;
};


// #pragma mark - HelpCommand


struct CommandLineUserInterface::HelpCommand : CliCommand {
        HelpCommand(CommandLineUserInterface* userInterface)
                :
                CliCommand("print help for a command or a list of all commands",
                        "%s [ <command> ]\n"
                        "Prints help for command <command>, if given, or a list of all "
                                "commands\n"
                        "otherwise."),
                fUserInterface(userInterface)
        {
        }

        virtual void Execute(int argc, const char* const* argv, CliContext& context)
        {
                if (argc > 2) {
                        PrintUsage(argv[0]);
                        return;
                }

                fUserInterface->_PrintHelp(argc == 2 ? argv[1] : NULL);
        }

private:
        CommandLineUserInterface* fUserInterface;
};


// #pragma mark - CommandLineUserInterface


CommandLineUserInterface::CommandLineUserInterface()
        :
        fContext(new CliContext()),
        fCommands(20),
        fShowSemaphore(-1),
        fShown(false),
        fTerminating(false)
{
}


CommandLineUserInterface::~CommandLineUserInterface()
{
        if (fShowSemaphore >= 0)
                delete_sem(fShowSemaphore);
}


const char*
CommandLineUserInterface::ID() const
{
        return "BasicCommandLineUserInterface";
}


status_t
CommandLineUserInterface::Init(Team* team, UserInterfaceListener* listener)
{
        status_t error = fContext->Init(team, listener);
        if (error != B_OK)
                return error;

        error = _RegisterCommands();
        if (error != B_OK)
                return error;

        fShowSemaphore = create_sem(0, "show CLI");
        if (fShowSemaphore < 0)
                return fShowSemaphore;

        return B_OK;
}


void
CommandLineUserInterface::Show()
{
        fShown = true;
        release_sem(fShowSemaphore);
}


void
CommandLineUserInterface::Terminate()
{
        fTerminating = true;

        if (fShown) {
                fContext->Terminating();

                // Wait for input loop to finish.
                while (acquire_sem(fShowSemaphore) == B_INTERRUPTED) {
                }
        } else {
                // The main thread will still be blocked in Run(). Unblock it.
                delete_sem(fShowSemaphore);
                fShowSemaphore = -1;
        }

        fContext->Cleanup();

        BMessage message(B_QUIT_REQUESTED);
        fContext->PostMessage(&message);
}


UserInterface*
CommandLineUserInterface::Clone() const
{
        return new(std::nothrow) CommandLineUserInterface;
}


bool
CommandLineUserInterface::IsInteractive() const
{
        return true;
}


status_t
CommandLineUserInterface::LoadSettings(const TeamUiSettings* settings)
{
        return B_OK;
}


status_t
CommandLineUserInterface::SaveSettings(TeamUiSettings*& settings) const
{
        return B_OK;
}


void
CommandLineUserInterface::NotifyUser(const char* title, const char* message,
        user_notification_type type)
{
}


void
CommandLineUserInterface::NotifyBackgroundWorkStatus(const char* message)
{
}


int32
CommandLineUserInterface::SynchronouslyAskUser(const char* title,
        const char* message, const char* choice1, const char* choice2,
        const char* choice3)
{
        return -1;
}


status_t
CommandLineUserInterface::SynchronouslyAskUserForFile(entry_ref* _ref)
{
        return B_UNSUPPORTED;
}


void
CommandLineUserInterface::Run()
{
        // Wait for the Show() semaphore to be released.
        status_t error;
        do {
                error = acquire_sem(fShowSemaphore);
        } while (error == B_INTERRUPTED);

        if (error != B_OK)
                return;

        fContext->Run();
        _InputLoop();
        // Release the Show() semaphore to signal Terminate().
        release_sem(fShowSemaphore);
}


status_t
CommandLineUserInterface::_InputLoop()
{
        thread_id currentThread = -1;

        while (!fTerminating) {
                // Print the active thread, if it changed.
                if (fContext->CurrentThreadID() != currentThread) {
                        fContext->PrintCurrentThread();
                        currentThread = fContext->CurrentThreadID();
                }

                // read a command line
                const char* line = fContext->PromptUser(kDebuggerPrompt);
                if (line == NULL)
                        break;

                // parse the command line
                ArgumentVector args;
                const char* parseErrorLocation;
                switch (args.Parse(line, &parseErrorLocation)) {
                        case ArgumentVector::NO_ERROR:
                                break;
                        case ArgumentVector::NO_MEMORY:
                                printf("Insufficient memory parsing the command line.\n");
                                continue;
                        case ArgumentVector::UNTERMINATED_QUOTED_STRING:
                                printf("Parse error: Unterminated quoted string starting at "
                                        "character %zu.\n", parseErrorLocation - line + 1);
                                continue;
                        case ArgumentVector::TRAILING_BACKSPACE:
                                printf("Parse error: trailing backspace.\n");
                                continue;
                }

                if (args.ArgumentCount() == 0)
                        continue;

                // add line to history
                fContext->AddLineToInputHistory(line);

                // execute command
                _ExecuteCommand(args.ArgumentCount(), args.Arguments());
        }

        return B_OK;
}


status_t
CommandLineUserInterface::_RegisterCommands()
{
        if (_RegisterCommand("bt sc", new(std::nothrow) CliStackTraceCommand)
                && _RegisterCommand("continue", new(std::nothrow) CliContinueCommand)
                && _RegisterCommand("db", new(std::nothrow)
                        CliDumpMemoryCommand(1, "byte", 16))
                && _RegisterCommand("ds", new(std::nothrow)
                        CliDumpMemoryCommand(2, "short", 8))
                && _RegisterCommand("dw", new(std::nothrow)
                        CliDumpMemoryCommand(4, "word", 4))
                && _RegisterCommand("dl", new(std::nothrow)
                        CliDumpMemoryCommand(8, "long", 2))
                && _RegisterCommand("frame", new(std::nothrow) CliStackFrameCommand)
                && _RegisterCommand("help", new(std::nothrow) HelpCommand(this))
                && _RegisterCommand("print", new(std::nothrow) CliPrintVariableCommand)
                && _RegisterCommand("quit", new(std::nothrow) CliQuitCommand)
                && _RegisterCommand("save-report",
                        new(std::nothrow) CliDebugReportCommand)
                && _RegisterCommand("stop", new(std::nothrow) CliStopCommand)
                && _RegisterCommand("string", new(std::nothrow)
                        CliDumpStringCommand())
                && _RegisterCommand("thread", new(std::nothrow) CliThreadCommand)
                && _RegisterCommand("threads", new(std::nothrow) CliThreadsCommand)
                && _RegisterCommand("variables",
                        new(std::nothrow) CliVariablesCommand)
                && _RegisterCommand("write-core",
                        new(std::nothrow) CliWriteCoreFileCommand)) {
                fCommands.SortItems(&_CompareCommandEntries);
                return B_OK;
        }

        return B_NO_MEMORY;
}


bool
CommandLineUserInterface::_RegisterCommand(const BString& name,
        CliCommand* command)
{
        BReference<CliCommand> commandReference(command, true);
        if (name.IsEmpty() || command == NULL)
                return false;

        BString nextName;
        int32 startIndex = 0;
        int32 spaceIndex;
        do {
                spaceIndex = name.FindFirst(' ', startIndex);
                if (spaceIndex == B_ERROR)
                        spaceIndex = name.Length();
                name.CopyInto(nextName, startIndex, spaceIndex - startIndex);

                CommandEntry* entry = new(std::nothrow) CommandEntry(nextName,
                        command);
                if (entry == NULL || !fCommands.AddItem(entry)) {
                        delete entry;
                        return false;
                }
                startIndex = spaceIndex + 1;
        } while (startIndex < name.Length());

        return true;
}


void
CommandLineUserInterface::_ExecuteCommand(int argc, const char* const* argv)
{
        CommandEntry* commandEntry = _FindCommand(argv[0]);
        if (commandEntry != NULL)
                commandEntry->Command()->Execute(argc, argv, *fContext);
}


CommandLineUserInterface::CommandEntry*
CommandLineUserInterface::_FindCommand(const char* commandName)
{
        size_t commandNameLength = strlen(commandName);

        // try to find an exact match first
        CommandEntry* commandEntry = NULL;
        for (int32 i = 0; CommandEntry* entry = fCommands.ItemAt(i); i++) {
                if (entry->Name() == commandName) {
                        commandEntry = entry;
                        break;
                }
        }

        // If nothing found yet, try partial matches, but only, if they are
        // unambiguous.
        if (commandEntry == NULL) {
                for (int32 i = 0; CommandEntry* entry = fCommands.ItemAt(i); i++) {
                        if (entry->Name().Compare(commandName, commandNameLength) == 0) {
                                if (commandEntry != NULL) {
                                        printf("Error: Ambiguous command \"%s\".\n", commandName);
                                        return NULL;
                                }

                                commandEntry = entry;
                        }
                }
        }

        if (commandEntry == NULL) {
                printf("Error: Unknown command \"%s\".\n", commandName);
                return NULL;
        }

        return commandEntry;
}


void
CommandLineUserInterface::_PrintHelp(const char* commandName)
{
        // If a command name is given, print the usage for that one.
        if (commandName != NULL) {
                CommandEntry* commandEntry = _FindCommand(commandName);
                if (commandEntry != NULL)
                        commandEntry->Command()->PrintUsage(commandEntry->Name().String());
                return;
        }

        // No command name given -- print a list of all commands.

        // determine longest command name
        int32 longestCommandName = 0;
        for (int32 i = 0; CommandEntry* entry = fCommands.ItemAt(i); i++) {
                longestCommandName
                        = std::max(longestCommandName, entry->Name().Length());
        }

        // print the command list
        for (int32 i = 0; CommandEntry* entry = fCommands.ItemAt(i); i++) {
                printf("%*s  -  %s\n", (int)longestCommandName, entry->Name().String(),
                        entry->Command()->Summary());
        }
}


/*static */
int
CommandLineUserInterface::_CompareCommandEntries(const CommandEntry* command1,
        const CommandEntry* command2)
{
        return ::Compare(command1->Name(), command2->Name());
}