// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0

#include "eventcallback.h"
#include "extensioncontext.h"
#include "gdbmihelpers.h"
#include "outputcallback.h"
#include "stringutils.h"
#include "symbolgroup.h"
#include "symbolgroupvalue.h"

#ifdef WITH_PYTHON
#include <Python.h>
#include "pystdoutredirect.h"
#include "pycdbextmodule.h"
#endif

#include <cstdio>
#include <sstream>
#include <list>
#include <iterator>

/*!
    \group qtcreatorcdbext
    \title Qt Creator CDB Extensions

    Qt Creator CDB extensions are loaded into CDB.exe (see cdbengine.cpp).
    They provide the following features:

    \list
    \li Notification about the state of the debugging session:
    \list
        \li idle: (hooked with .idle_cmd) debuggee stopped
        \li accessible: Debuggee stopped, cdb.exe accepts commands
        \li inaccessible: Debuggee runs, no way to post commands
        \li session active/inactive: Lost debuggee, terminating.
    \endlist
    \li Hook up with output/event callbacks and produce formatted output
    \li Provide some extension commands that produce output in a standardized (GDBMI)
       format that ends up in handleExtensionMessage().
    \endlist
*/

// Data struct and helpers for formatting help
struct CommandDescription {
    const char *name;
    const char *description;
    const char *usage;
};

// Single line of usage: For reporting usage errors back as a single line
static std::string singleLineUsage(const CommandDescription &d)
{
    std::string rc = "Usage: ";
    const char *endOfLine = strchr(d.usage, '\n');
    rc += endOfLine ? std::string(d.usage, endOfLine -  d.usage) : std::string(d.usage);
    return rc;
}

// Format description of a command
std::ostream &operator<<(std::ostream &str, const CommandDescription &d)
{
    str << "Command '" << d.name << "': " << d.description << '\n';
    if (d.usage[0])
        str << "Usage: " << d.name << ' ' << d.usage << '\n';
    str << '\n';
    return str;
}

enum Command {
    CmdPid,
    CmdExpandlocals,
    CmdLocals,
    CmdWatches,
    CmdDumplocal,
    CmdTypecast,
    CmdAddsymbol,
    CmdAssign,
    CmdThreads,
    CmdRegisters,
    CmdModules,
    CmdIdle,
    CmdHelp,
    CmdMemory,
    CmdExpression,
    CmdStack,
    CmdQmlStack,
    CmdShutdownex,
    CmdAddWatch,
    CmdWidgetAt,
    CmdBreakPoints,
    CmdTest,
    CmdSetParameter,
    CmdScript
};

static const CommandDescription commandDescriptions[] = {
{"pid",
 "Prints inferior process id and hooks up output callbacks.",
 "[-t token]"},
{"expandlocals", "Expands local variables by iname in symbol group.",
 "[-t token] [-c] <frame-number> <iname1-list>\n"
 "iname1-list: Comma-separated list of inames\n"
 "-c complex dumpers"},
{"locals",
 "Prints local variables of symbol group in GDBMI or debug format",
 "[-t token] [-v] [T formats] [-I formats] [-f debugfilter] [-a] [-c] [-h] [-d]\n[-e expand-list] [-u uninitialized-list]\n"
 "[-W] [-w watch-iname watch-expression] <frame-number> [iname]\n"
 "-h human-readable output\n"
 "-v increase verboseness of dumping\n"
 "-d debug output\n"
 "-f debug_filter\n"
 "-c complex dumpers\n"
 "-a sort alphabetically\n"
 "-e expand-list        Comma-separated list of inames to be expanded beforehand\n"
 "-u uninitialized-list Comma-separated list of uninitialized inames\n"
 "-I formatmap          map of 'hex-encoded-iname=typecode'\n"
 "-T formatmap          map of 'hex-encoded-type-name=typecode'\n"
 "-D                    Discard existing symbol group\n"
 "-W                    Synchronize watch items (-w)\n"
 "-w iname expression   Watch item"},
{"watches",
 "Prints watches variables of symbol group in GDBMI or debug format",
 "[-t token] [-v] [T formats] [-I formats] [-f debugfilter] [-c] [-h] [-d] <iname>\n"
 "-h human-readable output\n"
 "-v increase verboseness of dumping\n"
 "-d debug output\n"
 "-f debug_filter\n"
 "-c complex dumpers\n"
 "-I formatmap          map of 'hex-encoded-iname=typecode'\n"
 "-T formatmap          map of 'hex-encoded-type-name=typecode'"},
{"dumplocal", "Dumps local variable using simple dumpers (testing command).",
 "[-t token] <frame-number> <iname>"},
{"typecast","Performs a type cast on an unexpanded iname of symbol group.",
 "[-t token] <frame-number> <iname> <desired-type>"},
{"addsymbol","Adds a symbol to symbol group (testing command).",
 "[-t token] <frame-number> <name-expression> [optional-iname]"},
{"assign","Assigns a value to a variable in current symbol group.",
 "[-t token] [-h] <iname=value>\n"
 "-h    Data are hex-encoded, binary data\n"
 "-e    iname is an hex-encoded expression to be evaluated "
},
{"threads","Lists threads in GDBMI format.","[-t token]"},
{"registers","Lists registers in GDBMI format","[-t token]"},
{"modules","Lists modules in GDBMI format.","[-t token]"},
{"idle",
 "Reports stop reason in GDBMI format.\n"
 "Intended to be used with .idle_cmd to obtain proper stop notification.",""},
{"help","Prints help.",""},
{"memory","Prints memory contents in Base64 encoding.","[-t token] <address> <length>"},
{"expression","Prints expression value.","[-t token] <expression>"},
{"stack","Prints stack in GDBMI format.","[-t token] [<max-frames>|unlimited]"},
{"qmlstack","Prints QML stack in GDBMI format.","[-t token] [js-executioncontext]"},
{"shutdownex","Unhooks output callbacks.\nNeeds to be called explicitly only in case of remote debugging.",""},
{"addwatch","Add watch expression","<iname> <expression>"},
{"widgetat","Return address of widget at position","<x> <y>"},
{"breakpoints","List breakpoints with modules","[-h] [-v]"},
{"test","Testing command","-T type | -w watch-expression"},
{"setparameter","Set parameter",
 "maxStringLength=value maxArraySize=value maxStackDepth=value stateNotification=1,0"},
{"script", "Run Python command", "[-t token]"}
};

typedef std::vector<std::string> StringVector;
typedef std::list<std::string> StringList;

static inline bool isOption(const std::string &opt)
{
    return opt.size() == 2 && opt.at(0) == '-' && opt != "--";
}

struct CommandToken
{
    int majorPart = 0;
    int minorPart = 0;
};

// Helper for commandTokens() below:
// Simple splitting of command lines allowing for '"'-quoted tokens
// 'typecast local.i "class QString *"' -> ('typecast','local.i','class QString *')
template<class Inserter>
static inline void splitCommand(PCSTR args, Inserter it)
{
    enum State { WhiteSpace, WithinToken, WithinQuoted };

    State state = WhiteSpace;
    std::string current;
    for (PCSTR p = args; *p; p++) {
        char c = *p;
        switch (state) {
        case WhiteSpace:
            switch (c) {
            case ' ':
                break;
            case '"':
                state = WithinQuoted;
                current.clear();
                break;
            default:
                state = WithinToken;
                current.clear();
                current.push_back(c);
                break;
            }
            break;
        case WithinToken:
            switch (c) {
            case ' ':
                state = WhiteSpace;
                *it = current;
                ++it;
                break;
            default:
                current.push_back(c);
                break;
            }
            break;
        case WithinQuoted:
            switch (c) {
            case '"':
                state = WhiteSpace;
                *it = current;
                ++it;
                break;
            default:
                current.push_back(c);
                break;
            }
            break;
        }
    }
    if (state == WithinToken) {
        *it = current;
        ++it;
    }
}

// check whether arguments start with a "-t 23.1" or "-t 3" token identifier
CommandToken extractToken(PCSTR args, PCSTR *afterToken)
{
    CommandToken token;
    while (*args == ' ') // skip whitespace
        ++args;
    if (*args != '-')
        return {};
    if (*(++args) != 't')
        return {};
    ++args;
    while (*args == ' ') // skip whitespace
        ++args;
    PSTR end = nullptr;
    token.majorPart = strtol(args, &end, 10);
    if (args >= end)
        return {};
    args = end;
    if (*args == '.') {
        ++args;
        end = nullptr;
        token.minorPart = strtol(args, &end, 10);
        if (args >= end)
            return {};
        args = end;
    }
    while (*args == ' ') // skip whitespace
        ++args;
    *afterToken = args;
    return token;
}

// Split & Parse the arguments of a command and extract the
// optional first integer token argument ('command -t <number> remaining arguments')
// Each command takes the 'token' argument and includes it in its output
// so that the calling CDB engine in Qt Creator can match the callback.

template<class StringContainer>
static inline StringContainer commandTokens(PCSTR args, int *token = 0)
{
    static std::map<int, std::string> s_commandBuffer;

    if (token)
        *token = -1; // Handled as 'display' in engine, so that user can type commands

    StringContainer tokens;
    CommandToken commandToken = extractToken(args, &args);
    if (commandToken.majorPart != 0) {
        s_commandBuffer[commandToken.majorPart].append(args);
        if (commandToken.minorPart == 0) {
            DebugPrint() << commandToken.majorPart << ':' << s_commandBuffer[commandToken.majorPart];
            splitCommand(s_commandBuffer[commandToken.majorPart].data(), std::back_inserter(tokens));
            if (token)
                *token = commandToken.majorPart;
        } else if (token) {
            *token = 0;
        }
    } else {
        splitCommand(args, std::back_inserter(tokens));
    }
    return tokens;
}

// Extension command 'pid':
// Report back PID so that Qt Creator engine knows whom to DebugBreakProcess.
extern "C" HRESULT CALLBACK pid(CIDebugClient *client, PCSTR args)
{
    ExtensionContext::instance().hookCallbacks(client);

    int token;
    commandTokens<StringList>(args, &token);
    if (token == 0) // partial message
        return S_OK;
    dprintf("Qt Creator CDB extension version 4.3 %d bit.\n",
            sizeof(void *) * 8);
    if (const ULONG pid = currentProcessId(client))
        ExtensionContext::instance().report('R', token, 0, "pid", "%u", pid);
    else
        ExtensionContext::instance().report('N', token, 0, "pid", "0");
    return S_OK;
}

// Extension command 'expandlocals':
// Expand a comma-separated iname-list of local variables.

extern "C" HRESULT CALLBACK expandlocals(CIDebugClient *client, PCSTR args)
{
    ExtensionCommandContext exc(client);
    unsigned frame = 0;
    SymbolGroup *symGroup = 0;
    std::string errorMessage;

    int token;
    StringList tokens = commandTokens<StringList>(args, &token);
    if (token == 0) // partial message
        return S_OK;

    StringVector inames;
    bool runComplexDumpers = false;
    do {
        if (!tokens.empty() && tokens.front() == "-c") {
            runComplexDumpers = true;
            tokens.pop_front();
        }
        if (tokens.empty() || !integerFromString(tokens.front(), &frame))  {
            errorMessage = singleLineUsage(commandDescriptions[CmdExpandlocals]);
            break;
        }
        tokens.pop_front();
        if (tokens.empty()) {
            errorMessage = singleLineUsage(commandDescriptions[CmdExpandlocals]);
            break;
        }
        split(tokens.front(), ',', std::back_inserter(inames));
    } while (false);

    if (errorMessage.empty())
        symGroup = ExtensionContext::instance().symbolGroup(exc.symbols(), exc.threadId(), frame, &errorMessage);

    if (!symGroup) {
        ExtensionContext::instance().report('N', token, 0, "expandlocals", errorMessage.c_str());
        return S_OK;
    }

    const unsigned succeeded = runComplexDumpers ?
        symGroup->expandListRunComplexDumpers(inames, SymbolGroupValueContext(exc.dataSpaces(), exc.symbols()), &errorMessage) :
        symGroup->expandList(inames, &errorMessage);

    ExtensionContext::instance().report('R', token, 0, "expandlocals", "%u/%u %s",
                                        succeeded, unsigned(inames.size()), errorMessage.c_str());
    return S_OK;
}

// Parameters to be shared between watch/locals dump commands
struct DumpCommandParameters
{
    DumpCommandParameters() : debugOutput(0), verbose(0) {}

    // Check option off front and remove if parsed
    enum ParseOptionResult { Ok, Error, OtherOption, EndOfOptions };
    ParseOptionResult parseOption(StringList *options);

    unsigned debugOutput;
    std::string debugFilter;
    DumpParameters dumpParameters;
    unsigned verbose;
};

DumpCommandParameters::ParseOptionResult DumpCommandParameters::parseOption(StringList *options)
{
    // Parse all options and erase valid ones from the list
    if (options->empty())
        return DumpCommandParameters::EndOfOptions;

    const std::string &opt = options->front();
    if (!isOption(opt))
        return DumpCommandParameters::EndOfOptions;
    const char option = opt.at(1);
    bool knownOption = true;
    switch (option) {
    case 'd':
        debugOutput++;
        break;
    case 'h':
        dumpParameters.dumpFlags |= DumpParameters::DumpHumanReadable;
        break;
    case 'c':
        dumpParameters.dumpFlags |= DumpParameters::DumpComplexDumpers;
        break;
    case 'a':
        dumpParameters.dumpFlags |= DumpParameters::DumpAlphabeticallySorted;
        break;
    case 'f':
        if (options->size() < 2)
            return Error;
        options->pop_front();
        debugFilter = options->front();
        break;
    case 'v':
        verbose++;
        break;
    case 'T': // typeformats: 'hex'ed name = formatnumber,...'
        if (options->size() < 2)
            return Error;
        options->pop_front();
        if (options->front().empty())
            return Error;
        dumpParameters.typeFormats =
            DumpParameters::decodeFormatArgument(options->front(), true);
        break;
    case 'I': // individual formats: iname= formatnumber,...'
        if (options->size() < 2)
            return Error;
        options->pop_front();
        dumpParameters.individualFormats =
            DumpParameters::decodeFormatArgument(options->front(), false);
        break;
    default:
        knownOption = false;
        break;
    } // case option
    if (knownOption)
        options->pop_front();
    return knownOption ? Ok : OtherOption;
}

// Extension command 'locals':
// Display local variables of symbol group in GDBMI or debug output form.
// Takes an optional iname which is expanded before displaying (for updateWatchData)

static std::string commandLocals(ExtensionCommandContext &commandExtCtx,PCSTR args, int *token, std::string *errorMessage)
{
    typedef WatchesSymbolGroup::InameExpressionMap InameExpressionMap;
    typedef InameExpressionMap::value_type InameExpressionMapEntry;

    // Parse the command
    StringList tokens = commandTokens<StringList>(args, token);
    if (token == 0) // partial arguments
        return {};
    ExtensionContext &extCtx = ExtensionContext::instance();
    DumpCommandParameters parameters;
    std::string iname;
    StringVector expandedInames;
    StringVector uninitializedInames;
    InameExpressionMap watcherInameExpressionMap;
    bool watchSynchronization = false;
    bool discardSymbolGroup = false;
    // Parse away options
    for (bool optionLeft = true;  optionLeft && !tokens.empty(); ) {
        switch (parameters.parseOption(&tokens)) {
        case DumpCommandParameters::Ok:
            break;
        case DumpCommandParameters::Error:
            *errorMessage = singleLineUsage(commandDescriptions[CmdLocals]);
            return std::string();
        case DumpCommandParameters::OtherOption: {
            const char option = tokens.front().at(1);
            tokens.pop_front();
            switch (option) {
            case 'u':
                if (tokens.empty()) {
                    *errorMessage = singleLineUsage(commandDescriptions[CmdLocals]);
                    return std::string();
                }
                split(tokens.front(), ',', std::back_inserter(uninitializedInames));
                tokens.pop_front();
                break;
            case 'e':
                if (tokens.empty()) {
                    *errorMessage = singleLineUsage(commandDescriptions[CmdLocals]);
                    return std::string();
                }
                split(tokens.front(), ',', std::back_inserter(expandedInames));
                tokens.pop_front();
                break;
            case 'w':  { // Watcher iname exp
                if (tokens.size() < 2) {
                    *errorMessage = singleLineUsage(commandDescriptions[CmdLocals]);
                    return std::string();
                }
                const std::string iname = tokens.front();
                tokens.pop_front();
                const std::string expression = tokens.front();
                tokens.pop_front();
                watcherInameExpressionMap.insert(InameExpressionMapEntry(iname, expression));
            }
            break;
            case 'W':
                watchSynchronization = true;
                break;
            case 'D':
                discardSymbolGroup = true;
                break;
            } // case option
        }
        break;
        case DumpCommandParameters::EndOfOptions:
            optionLeft = false;
            break;
        }
    }
    // Frame and iname
    unsigned frame;
    if (tokens.empty() || !integerFromString(tokens.front(), &frame)) {
        *errorMessage = singleLineUsage(commandDescriptions[CmdLocals]);
        return std::string();
    }

    tokens.pop_front();
    if (!tokens.empty())
        iname = tokens.front();

    const SymbolGroupValueContext dumpContext(commandExtCtx.dataSpaces(), commandExtCtx.symbols());
    if (discardSymbolGroup)
        extCtx.discardSymbolGroup();
    SymbolGroup * const symGroup = extCtx.symbolGroup(commandExtCtx.symbols(), commandExtCtx.threadId(), frame, errorMessage);
    if (!symGroup)
        return std::string();

    if (!uninitializedInames.empty())
        symGroup->markUninitialized(uninitializedInames);

    SymbolGroupValue::verbose = parameters.verbose;
    if (SymbolGroupValue::verbose)
        DebugPrint() << parameters.dumpParameters;

    // Synchronize watches if desired.
    WatchesSymbolGroup *watchesSymbolGroup = extCtx.watchesSymbolGroup();
    if (watchSynchronization) {
        watchesSymbolGroup = 0;
        extCtx.discardWatchesSymbolGroup();
    }

    if (watchesSymbolGroup == 0
            && (!watcherInameExpressionMap.empty() || WatchesSymbolGroup::isWatchIname(iname))) {
        // Force group into existence
        watchesSymbolGroup = extCtx.watchesSymbolGroup(commandExtCtx.symbols(), errorMessage);
        if (!watchesSymbolGroup || !watchesSymbolGroup->synchronize(commandExtCtx.symbols(),
                                                                    watcherInameExpressionMap,
                                                                    errorMessage)) {
            return std::string();
        }
    }

    // Pre-expand.
    if (!expandedInames.empty()) {
        if (parameters.dumpParameters.dumpFlags & DumpParameters::DumpComplexDumpers) {
            symGroup->expandListRunComplexDumpers(expandedInames, dumpContext, errorMessage);
            if (watchesSymbolGroup)
                watchesSymbolGroup->expandListRunComplexDumpers(expandedInames, dumpContext, errorMessage);
        } else {
            symGroup->expandList(expandedInames, errorMessage);
            if (watchesSymbolGroup)
                watchesSymbolGroup->expandList(expandedInames, errorMessage);
        }
    }

    // Debug output: Join the 2 symbol groups if no iname is given.
    if (parameters.debugOutput) {
        std::string debugRc = symGroup->debug(iname, parameters.debugFilter, parameters.debugOutput - 1);
        if (iname.empty() && watchesSymbolGroup) {
            debugRc.push_back('\n');
            debugRc += watchesSymbolGroup->debug(iname, parameters.debugFilter, parameters.debugOutput - 1);
        }
        return debugRc;
    }

    // Dump all: Join the 2 symbol groups '[local.x][watch.0]'->'[local.x,watch.0]'
    if (iname.empty()) {
        std::string dumpRc = symGroup->dump(dumpContext, parameters.dumpParameters);
        if (!dumpRc.empty() && watchesSymbolGroup) {
            std::string watchesDump = watchesSymbolGroup->dump(dumpContext, parameters.dumpParameters);
            if (!watchesDump.empty()) {
                dumpRc.erase(dumpRc.size() - 1, 1); // Strip ']'
                watchesDump[0] = ','; // '[' -> '.'
                dumpRc += watchesDump;
            }
        }
        return dumpRc;
    }

    if (WatchesSymbolGroup::isWatchIname(iname))
        return watchesSymbolGroup->dump(iname, dumpContext, parameters.dumpParameters, errorMessage);
    else
        return symGroup->dump(iname, dumpContext, parameters.dumpParameters, errorMessage);
}

extern "C" HRESULT CALLBACK script(CIDebugClient *client, PCSTR argsIn)
{
    ExtensionCommandContext exc(client);
    int token;
#ifdef WITH_PYTHON
    const StringList args = commandTokens<StringList>(argsIn, &token);
    if (token == 0) // partial arguments
        return {};
    std::stringstream command;
    for (std::string arg : args)
        command << arg << ' ';

    PyObject *ptype      = NULL;
    PyObject *pvalue     = NULL;
    PyObject *ptraceback = NULL;
    PyErr_Fetch(&ptype, &pvalue, &ptraceback);
    startCapturePyStdout();
    const char result = (PyRun_SimpleString(command.str().c_str()) == 0) ? 'R' : 'N';
    if (PyErr_Occurred())
        PyErr_Print();
    ExtensionContext::instance().reportLong(result, token, "script", collectOutput().c_str());
    endCapturePyStdout();
    PyErr_Restore(ptype, pvalue, ptraceback);
#else
    commandTokens<StringList>(argsIn, &token);
    if (token == 0) // partial arguments
        return {};
    ExtensionContext::instance().report('N', token, 0, "script",
            "Python is not supported in this CDB extension.\n"
            "You need to define PYTHON_INSTALL_DIR in your creator build environment "
            "pointing to a Python 3.8 installation.");
#endif
    return S_OK;
}

extern "C" HRESULT CALLBACK locals(CIDebugClient *client, PCSTR args)
{
    ExtensionCommandContext exc(client);
    std::string errorMessage;
    int token;
    const std::string output = commandLocals(exc, args, &token, &errorMessage);
    if (token == 0) // partial message
        return S_OK;
    SymbolGroupValue::verbose = 0;
    if (output.empty())
        ExtensionContext::instance().report('N', token, 0, "locals", errorMessage.c_str());
    else
        ExtensionContext::instance().reportLong('R', token, "locals", output);
    return S_OK;
}

// Extension command 'locals':
// Display local variables of symbol group in GDBMI or debug output form.
// Takes an optional iname which is expanded before displaying (for updateWatchData)

static std::string commmandWatches(ExtensionCommandContext &exc, PCSTR args, int *token, std::string *errorMessage)
{
    // Parse the command
    DumpCommandParameters parameters;
    StringList tokens = commandTokens<StringList>(args, token);
    if (token == 0) // partial message
        return {};

    // Parse away options
    for (bool optionLeft = true;  optionLeft && !tokens.empty(); ) {
        switch (parameters.parseOption(&tokens)) {
        case DumpCommandParameters::Ok:
            break;
        case DumpCommandParameters::Error:
        case DumpCommandParameters::OtherOption:
            *errorMessage = singleLineUsage(commandDescriptions[CmdWatches]);
            return std::string();
        case DumpCommandParameters::EndOfOptions:
            optionLeft = false;
            break;
        }
    }
    const std::string iname = tokens.empty() ? std::string() : tokens.front();
    if (iname.empty() && !parameters.debugOutput) {
        *errorMessage = singleLineUsage(commandDescriptions[CmdWatches]);
        return std::string();
    }

    const SymbolGroupValueContext dumpContext(exc.dataSpaces(), exc.symbols());
    SymbolGroup * const symGroup = ExtensionContext::instance().watchesSymbolGroup(exc.symbols(), errorMessage);
    if (!symGroup)
        return std::string();
    SymbolGroupValue::verbose = parameters.verbose;

    if (parameters.debugOutput)
        return symGroup->debug(iname, parameters.debugFilter, parameters.debugOutput - 1);
    return symGroup->dump(iname, dumpContext, parameters.dumpParameters, errorMessage);
}

extern "C" HRESULT CALLBACK watches(CIDebugClient *client, PCSTR args)
{
    ExtensionCommandContext exc(client);
    std::string errorMessage = "e";
    int token = 0;
    const std::string output = commmandWatches(exc, args, &token, &errorMessage);
    if (token == 0) // partial message
        return S_OK;

    SymbolGroupValue::verbose = 0;
    if (output.empty())
        ExtensionContext::instance().report('N', token, 0, "locals", errorMessage.c_str());
    else
        ExtensionContext::instance().reportLong('R', token, "locals", output);
    return S_OK;
}

// Extension command 'dumplocal':
// Dump a local variable using dumpers (testing command).

static std::string dumplocalHelper(ExtensionCommandContext &exc,PCSTR args, int *token, std::string *errorMessage)
{
    // Parse the command
    StringList tokens = commandTokens<StringList>(args, token);
    if (token == 0) // partial message
        return {};

    // Frame and iname
    unsigned frame;
    if (tokens.empty() || integerFromString(tokens.front(), &frame)) {
        *errorMessage = singleLineUsage(commandDescriptions[CmdDumplocal]);
        return std::string();
    }
    tokens.pop_front();
    if (tokens.empty()) {
        *errorMessage = singleLineUsage(commandDescriptions[CmdDumplocal]);
        return std::string();
    }
    const std::string iname = tokens.front();

    SymbolGroup * const symGroup = ExtensionContext::instance().symbolGroup(exc.symbols(), exc.threadId(), frame, errorMessage);
    if (!symGroup)
        return std::string();

    AbstractSymbolGroupNode *n = symGroup->find(iname);
    if (!n || !n->asSymbolGroupNode()) {
        *errorMessage = "No such iname " + iname;
        return std::string();
    }
    std::wstring value;
    std::string tmp;
    if (!dumpSimpleType(n->asSymbolGroupNode(), SymbolGroupValueContext(exc.dataSpaces(), exc.symbols()), &value, &tmp)) {
        *errorMessage = "Cannot dump " + iname;
        return std::string();
    }
    return wStringToString(value);
}

extern "C" HRESULT CALLBACK dumplocal(CIDebugClient *client, PCSTR  argsIn)
{
    ExtensionCommandContext exc(client);
    std::string errorMessage;
    int token = 0;
    const std::string value = dumplocalHelper(exc,argsIn, &token, &errorMessage);
    if (token == 0) // partial message
        return S_OK;

    if (value.empty())
        ExtensionContext::instance().report('N', token, 0, "dumplocal", errorMessage.c_str());
    else
        ExtensionContext::instance().reportLong('R', token, "dumplocal", value);
    return S_OK;
}

// Extension command 'typecast':
// Change the type of a symbol group entry (testing purposes)

extern "C" HRESULT CALLBACK typecast(CIDebugClient *client, PCSTR args)
{
    ExtensionCommandContext exc(client);
    unsigned frame = 0;
    SymbolGroup *symGroup = 0;
    std::string errorMessage;

    int token;
    const StringVector tokens = commandTokens<StringVector>(args, &token);
    if (token == 0) // partial message
        return S_OK;

    std::string iname;
    std::string desiredType;
    if (tokens.size() == 3u && integerFromString(tokens.front(), &frame)) {
        symGroup = ExtensionContext::instance().symbolGroup(exc.symbols(), exc.threadId(), frame, &errorMessage);
        iname = tokens.at(1);
        desiredType = tokens.at(2);
    } else {
        errorMessage = singleLineUsage(commandDescriptions[CmdTypecast]);
    }
    if (symGroup != 0 && symGroup->typeCast(iname, desiredType, &errorMessage))
        ExtensionContext::instance().report('R', token, 0, "typecast", "OK");
    else
        ExtensionContext::instance().report('N', token, 0, "typecast", errorMessage.c_str());
    return S_OK;
}

// Extension command 'addsymbol':
// Adds a symbol to a symbol group by name (testing purposes)

extern "C" HRESULT CALLBACK addsymbol(CIDebugClient *client, PCSTR args)
{
    ExtensionCommandContext exc(client);
    unsigned frame = 0;
    SymbolGroup *symGroup = 0;
    std::string errorMessage;

    int token;
    const StringVector tokens = commandTokens<StringVector>(args, &token);
    if (token == 0) // partial message
        return S_OK;

    std::string name;
    std::string iname;
    if (tokens.size() >= 2u && integerFromString(tokens.front(), &frame)) {
        symGroup = ExtensionContext::instance().symbolGroup(exc.symbols(), exc.threadId(), frame, &errorMessage);
        name = tokens.at(1);
        if (tokens.size() >= 3)
            iname = tokens.at(2);
    } else {
        errorMessage = singleLineUsage(commandDescriptions[CmdAddsymbol]);
    }
    if (symGroup != 0 && symGroup->addSymbol(std::string(), name, iname, &errorMessage))
        ExtensionContext::instance().report('R', token, 0, "addsymbol", "OK");
    else
        ExtensionContext::instance().report('N', token, 0, "addsymbol", errorMessage.c_str());
    return S_OK;
}

extern "C" HRESULT CALLBACK addwatch(CIDebugClient *client, PCSTR argsIn)
{
    ExtensionCommandContext exc(client);

    std::string errorMessage;
    std::string watchExpression;
    std::string iname;

    int token = 0;
    bool success = false;
    do {
        StringList tokens = commandTokens<StringList>(argsIn, &token);
        if (token == 0) // partial message
            return S_OK;

        if (tokens.size() != 2) {
            errorMessage = singleLineUsage(commandDescriptions[CmdAddWatch]);
            break;
        }
        iname = tokens.front();
        tokens.pop_front();
        watchExpression = tokens.front();

        WatchesSymbolGroup *watchesSymGroup = ExtensionContext::instance().watchesSymbolGroup(exc.symbols(), &errorMessage);
        if (!watchesSymGroup)
            break;
        success = watchesSymGroup->addWatch(exc.symbols(), iname, watchExpression, &errorMessage);
    } while (false);

    if (success)
        ExtensionContext::instance().report('R', token, 0, "addwatch", "Ok");
    else
        ExtensionContext::instance().report('N', token, 0, "addwatch", errorMessage.c_str());
    return S_OK;
}

// Extension command 'assign':
// Assign locals by iname: 'assign locals.x=5'

extern "C" HRESULT CALLBACK assign(CIDebugClient *client, PCSTR argsIn)
{
    ExtensionCommandContext exc(client);

    std::string errorMessage;
    bool encoded = false;
    bool evaluateExpression = false;
    int token = 0;
    StringList tokens = commandTokens<StringList>(argsIn, &token);
    while (!tokens.empty()) {
        if (tokens.front() == "-h") {
            encoded = true;
            tokens.pop_front();
            continue;
        }

        if (tokens.front() == "-e") {
            evaluateExpression = true;
            tokens.pop_front();
            continue;
        }
        break;
    };

    // Parse 'assign locals.x=5'
    const std::string::size_type equalsPos = tokens.empty() ? std::string::npos
                                                            : tokens.front().find('=');
    if (equalsPos == std::string::npos) {
        errorMessage = singleLineUsage(commandDescriptions[CmdAssign]);
    } else {
        std::string iname = tokens.front().substr(0, equalsPos);
        const std::string value = tokens.front().substr(equalsPos + 1,
                                                        tokens.front().size() - equalsPos - 1);
        SymbolGroup *symGroup = nullptr;
        if (evaluateExpression) {
            WatchesSymbolGroup *watchesSymGroup
                = ExtensionContext::instance().watchesSymbolGroup(exc.symbols(), &errorMessage);
            std::string tempAssignIname = "watch.tmpassign";
            if (watchesSymGroup) {
                if (watchesSymGroup->addWatch(exc.symbols(),
                                              tempAssignIname,
                                              stringFromHex(iname),
                                              &errorMessage)) {
                    iname = tempAssignIname;
                    symGroup = watchesSymGroup;
                }
            }
        } else {
            // get the symbolgroup
            int currentFrame = ExtensionContext::instance().symbolGroupFrame();
            if (currentFrame < 0) {
                CIDebugControl *control = ExtensionCommandContext::instance()->control();
                DEBUG_STACK_FRAME frame;
                if (FAILED(control->GetStackTrace(0, 0, 0, &frame, 1, NULL)))
                    errorMessage = "No current frame.";
                else
                    currentFrame = frame.FrameNumber;
            }
            if (currentFrame >= 0) {
                symGroup = ExtensionContext::instance().symbolGroup(exc.symbols(),
                                                                    exc.threadId(),
                                                                    currentFrame,
                                                                    &errorMessage);
            }
        }
        if (symGroup
            && symGroup->assign(iname,
                                encoded ? stringFromHex(value) : value,
                                SymbolGroupValueContext(exc.dataSpaces(), exc.symbols()),
                                &errorMessage)) {
            ExtensionContext::instance().report('R', token, 0, "assign", "Ok");
            return S_OK;
        }
    }

    ExtensionContext::instance().report('N', token, 0, "assign", errorMessage.c_str());
    return S_OK;
}

// Extension command 'threads':
// List all thread info in GDBMI syntax

extern "C" HRESULT CALLBACK threads(CIDebugClient *client, PCSTR  argsIn)
{
    ExtensionCommandContext exc(client);
    std::string errorMessage;

    int token;
    commandTokens<StringList>(argsIn, &token);
    if (token == 0) // partial message
        return S_OK;

    const std::string gdbmi = gdbmiThreadList(exc.systemObjects(),
                                              exc.symbols(),
                                              exc.control(),
                                              exc.advanced(),
                                              &errorMessage);
    if (gdbmi.empty())
        ExtensionContext::instance().report('N', token, 0, "threads", errorMessage.c_str());
    else
        ExtensionContext::instance().reportLong('R', token, "threads", gdbmi);
    return S_OK;
}

// Extension command 'registers':
// List all registers in GDBMI syntax

extern "C" HRESULT CALLBACK registers(CIDebugClient *Client, PCSTR argsIn)
{
    ExtensionCommandContext exc(Client);
    std::string errorMessage;

    int token;
    const StringList tokens = commandTokens<StringList>(argsIn, &token);
    if (token == 0) // partial message
        return S_OK;

    const bool humanReadable = !tokens.empty() && tokens.front() == "-h";
    const std::string regs = gdbmiRegisters(exc.registers(), exc.control(), humanReadable, IncludePseudoRegisters, &errorMessage);
    if (regs.empty())
        ExtensionContext::instance().report('N', token, 0, "registers", errorMessage.c_str());
    else
        ExtensionContext::instance().reportLong('R', token, "registers", regs);
    return S_OK;
}

// Extension command 'modules':
// List all modules in GDBMI syntax

extern "C" HRESULT CALLBACK modules(CIDebugClient *Client, PCSTR argsIn)
{
    ExtensionCommandContext exc(Client);
    std::string errorMessage;

    int token;
    const StringList tokens = commandTokens<StringList>(argsIn, &token);
    if (token == 0) // partial message
        return S_OK;

    const bool humanReadable = !tokens.empty() && tokens.front() == "-h";
    const std::string modules = gdbmiModules(exc.symbols(), humanReadable, &errorMessage);
    if (modules.empty())
        ExtensionContext::instance().report('N', token, 0, "modules", errorMessage.c_str());
    else
        ExtensionContext::instance().reportLong('R', token, "modules", modules);
    return S_OK;
}

// Report stop of debuggee to Creator. This is hooked up as .idle_cmd command
// by the Creator engine to reliably get notified about stops.
extern "C" HRESULT CALLBACK idle(CIDebugClient *client, PCSTR)
{
    ExtensionContext::instance().notifyIdleCommand(client);
    return S_OK;
}

// Extension command 'setparameter':
// Parse a list of parameters: 'key=value'

extern "C" HRESULT CALLBACK setparameter(CIDebugClient *, PCSTR args)
{
    int token;
    StringVector tokens = commandTokens<StringVector>(args, &token);
    if (token == 0) // partial message
        return S_OK;

    const size_t count = tokens.size();
    size_t success = 0;
    for (size_t i = 0; i < count; ++i) {
        const std::string &token = tokens.at(i);
        const std::string::size_type equalsPos = token.find('=');
        if (equalsPos != std::string::npos) {
            const std::string value = token.substr(equalsPos + 1, token.size() - 1 - equalsPos);
            if (!token.compare(0, equalsPos, "maxStringLength")) {
                if (integerFromString(value, &ExtensionContext::instance().parameters().maxStringLength))
                    ++success;
            } else if (!token.compare(0, equalsPos, "maxArraySize")) {
                if (integerFromString(value, &ExtensionContext::instance().parameters().maxArraySize))
                    ++success;
            } else if (!token.compare(0, equalsPos, "firstChance")) {
                if (integerFromString(value, &ExtensionContext::instance().parameters().firstChanceException))
                    ++success;
            } else if (!token.compare(0, equalsPos, "secondChance")) {
                if (integerFromString(value, &ExtensionContext::instance().parameters().secondChanceException))
                    ++success;
            } else if (!token.compare(0, equalsPos, "maxStackDepth")) {
                if (integerFromString(value, &ExtensionContext::instance().parameters().maxStackDepth))
                    ++success;
            } else if (!token.compare(0, equalsPos, "stateNotification")) {
                int s; // Silence state notification (development purposes only)
                if (integerFromString(value, &s)) {
                    ExtensionContext::instance().setStateNotification(s != 0);
                    ++success;
                }
            }
        }
    }
    if (success != count)
        DebugPrint() << "Errors parsing setparameters command '" << args << '\'';
    return S_OK;
}

// Extension command 'help':
// Display version

extern "C" HRESULT CALLBACK help(CIDebugClient *, PCSTR)
{
    std::ostringstream str;
    str << "### Qt Creator CDB extension" << "\n\n";

    const size_t commandCount = sizeof(commandDescriptions)/sizeof(CommandDescription);
    std::copy(commandDescriptions, commandDescriptions + commandCount,
              std::ostream_iterator<CommandDescription>(str));
    dprintf("%s\n", str.str().c_str());
    return S_OK;
}

// Extension command 'memory':
// Display memory as hex

extern "C" HRESULT CALLBACK memory(CIDebugClient *Client, PCSTR argsIn)
{
    ExtensionCommandContext exc(Client);
    std::string errorMessage;
    std::string memory;

    int token;
    ULONG64 address = 0;
    ULONG length = 0;

    const StringVector tokens = commandTokens<StringVector>(argsIn, &token);
    if (token == 0) // partial message
        return S_OK;

    if (tokens.size()  == 2
            && integerFromString(tokens.front(), &address)
            && integerFromString(tokens.at(1), &length)) {
        memory = memoryToHex(exc.dataSpaces(), address, length, &errorMessage);
    } else {
        errorMessage = singleLineUsage(commandDescriptions[CmdMemory]);
    }

    if (memory.empty()) {
        ExtensionContext::instance().report('N', token, 0, "memory", errorMessage.c_str());
    } else {
        ExtensionContext::instance().reportLong('R', token, "memory", memory);
        if (!errorMessage.empty())
            ExtensionContext::instance().report('W', token, 0, "memory", errorMessage.c_str());
    }
    return S_OK;
}

extern "C" HRESULT CALLBACK expression(CIDebugClient *Client, PCSTR argsIn)
{
    ExtensionCommandContext exc(Client);
    std::string errorMessage;
    LONG64 value = 0;
    int token = 0;

    do {
        const StringVector tokens = commandTokens<StringVector>(argsIn, &token);
        if (token == 0) // partial message
            return S_OK;

        if (tokens.size()  != 1) {

            errorMessage = singleLineUsage(commandDescriptions[CmdExpression]);
            break;
        }
        if (!evaluateInt64Expression(exc.control(), tokens.front(), &value, &errorMessage))
            break;
    } while (false);

    if (errorMessage.empty())
        ExtensionContext::instance().reportLong('R', token, "expression", toString(value));
    else
        ExtensionContext::instance().report('N', token, 0, "expression", errorMessage.c_str());
    return S_OK;
}

// Extension command 'stack'
// Report stack correctly as 'k' does not list instruction pointer
// correctly.
extern "C" HRESULT CALLBACK stack(CIDebugClient *Client, PCSTR argsIn)
{
    ExtensionCommandContext exc(Client);
    std::string errorMessage;

    int token;
    bool humanReadable = false;
    unsigned maxFrames = ExtensionContext::instance().parameters().maxStackDepth;

    StringList tokens = commandTokens<StringList>(argsIn, &token);
    if (token == 0) // partial message
        return S_OK;

    if (!tokens.empty() && tokens.front() == "-h") {
         humanReadable = true;
         tokens.pop_front();
    }
    if (!tokens.empty()) {
        if (tokens.front() == "unlimited")
            maxFrames = 1000;
        else
            integerFromString(tokens.front(), &maxFrames);
    }

    const std::string stack = gdbmiStack(exc.control(), exc.symbols(),
                                         maxFrames, humanReadable, &errorMessage);

    if (stack.empty())
        ExtensionContext::instance().report('N', token, 0, "stack", errorMessage.c_str());
    else
        ExtensionContext::instance().reportLong('R', token, "stack", stack);
    return S_OK;
}

// Extension command 'qmlstack'
// Report stack correctly as 'k' does not list instruction pointer
// correctly.
extern "C" HRESULT CALLBACK qmlstack(CIDebugClient *client, PCSTR argsIn)
{
    ExtensionCommandContext exc(client);
    std::string errorMessage;

    int token = 0;
    bool humanReadable = false;
    ULONG64 jsExecutionEngine = 0;
    std::string stackDump;

    do {
        StringList tokens = commandTokens<StringList>(argsIn, &token);
        if (token == 0) // partial message
            return S_OK;

        if (!tokens.empty() && tokens.front() == "-h") {
             humanReadable = true;
             tokens.pop_front();
        }
        if (!tokens.empty()) {
            if (!integerFromString(tokens.front(), &jsExecutionEngine)) {
                errorMessage = "Invalid address " + tokens.front();
                break;
            }
            tokens.pop_front();
        }
        ExtensionCommandContext exc(client);
        if (!jsExecutionEngine) { // Try to find execution engine unless it was given.
            jsExecutionEngine = ExtensionContext::instance().jsExecutionEngine(exc, &errorMessage);
            if (!jsExecutionEngine)
                break;
        }
        // call function to get stack trace. Call with exceptions handled right from
        // the start assuming this is invoked for crashed applications.
        // multiple function calls are needed, depending on the used Qt version
        // We always start from the latest Qt version
        std::ostringstream stringBuilder;
        std::wstring wOutput;
        const QtInfo &qtInfo = QtInfo::get(SymbolGroupValueContext(exc.dataSpaces(), exc.symbols()));
        do {
            stringBuilder << qtInfo.prependQtModule("qt_v4StackTraceForEngine(", QtInfo::Qml) << std::showbase << std::hex
                    << jsExecutionEngine << std::dec << std::noshowbase << ')';
            if (ExtensionContext::instance().call(stringBuilder.str(), ExtensionContext::CallWithExceptionsHandled, &wOutput, &errorMessage))
                break;

            // < Qt 5.15
            // We need to retrieve the current Context first
            std::string currentContextStr;

            // First try calling the currentContext() function
            std::wstring callResult;

            stringBuilder.str("");
            stringBuilder << qtInfo.prependQtModule("QV4::ExecutionEngine::currentContext(", QtInfo::Qml) << std::showbase << std::hex
                          << jsExecutionEngine << std::dec << std::noshowbase << ")";
            if (ExtensionContext::instance().call(stringBuilder.str(), ExtensionContext::CallWithExceptionsHandled, &callResult, &errorMessage)) {
                const std::string::size_type sPos = callResult.find(L"struct QV4::ExecutionContext * ") + 31 /*size of pattern*/;
                const std::string::size_type sEndPos = callResult.find(L'+');

                if (sPos == std::string::npos || sEndPos == std::string::npos || sEndPos < sPos) {
                    errorMessage = "Couldn't parse address from debugger output";
                    break;
                }
                currentContextStr = wStringToString(callResult.substr(sPos, sEndPos - sPos));
            } else {
                // < Qt 5.11 ????
                // currentContext is a member, not a function

                stringBuilder.str("");
                stringBuilder << "((QV4::ExecutionEngine*)" << std::showbase << std::hex
                              << jsExecutionEngine << std::dec << std::noshowbase << ")->currentContext";

                CIDebugControl *control = ExtensionCommandContext::instance()->control();
                ULONG oldExpressionSyntax;
                control->GetExpressionSyntax(&oldExpressionSyntax);
                control->SetExpressionSyntax(DEBUG_EXPR_CPLUSPLUS);

                IDebugSymbolGroup2 *symbolGroup = nullptr;
                CIDebugSymbols *symbols = ExtensionCommandContext::instance()->symbols();
                if (FAILED(symbols->GetScopeSymbolGroup2(DEBUG_SCOPE_GROUP_ALL, NULL,
                                                         &symbolGroup)))
                    break;

                ULONG index = DEBUG_ANY_ID;
                HRESULT hr = symbolGroup->AddSymbol(stringBuilder.str().c_str(), &index);
                control->SetExpressionSyntax(oldExpressionSyntax);
                if (SUCCEEDED(hr)) {
                    ULONG64 address = 0;
                    HRESULT hr = symbolGroup->GetSymbolOffset(index, &address);
                    if (SUCCEEDED(hr)) {
                        ExtensionCommandContext::instance()->dataSpaces()->ReadPointersVirtual(1, address, &address);
                        stringBuilder.str("");
                        stringBuilder << std::showbase << std::hex << address;
                        currentContextStr = stringBuilder.str();
                    }
                }
            }

            if (currentContextStr.empty()) {
                errorMessage = "Failed to retrieve currenContext from QML engine";
                break;
            }

            stringBuilder.str("");
            stringBuilder << qtInfo.prependQtModule("qt_v4StackTrace(", QtInfo::Qml) << currentContextStr << ')';
            if (ExtensionContext::instance().call(stringBuilder.str(), ExtensionContext::CallWithExceptionsHandled, &wOutput, &errorMessage))
                break;
        } while (false);

        if (wOutput.empty())
            break;

        // extract GDBMI info from call
        const std::string::size_type sPos = wOutput.find(L"stack=[");
        const std::string::size_type sEndPos = wOutput.rfind(L']');
        if (sPos == std::string::npos || sEndPos == std::string::npos || sEndPos < sPos) {
            errorMessage = "No stack returned";
            break;
        }
        stackDump = wStringToString(wOutput.substr(sPos, sEndPos - sPos + 1));
    } while (false);

    if (stackDump.empty())
        ExtensionContext::instance().report('N', token, 0, "qmlstack", errorMessage.c_str());
    else
        ExtensionContext::instance().reportLong('R', token, "qmlstack", stackDump);
    return S_OK;
}

// Extension command 'shutdownex' (shutdown is reserved):
// Unhook the output callbacks. This is normally done by the session
// inaccessible notification, however, this does not work for remote-controlled sessions.
extern "C" HRESULT CALLBACK shutdownex(CIDebugClient *, PCSTR)
{
    ExtensionContext::instance().unhookCallbacks();
    return S_OK;
}

extern "C" HRESULT CALLBACK widgetat(CIDebugClient *client, PCSTR argsIn)
{
    ExtensionCommandContext exc(client);
    int token = 0;
    std::string widgetAddress;
    std::string errorMessage;

    do {
        int x = -1;
        int y = -1;

        const StringVector tokens = commandTokens<StringVector>(argsIn, &token);
        if (token == 0) // partial message
            return S_OK;

        if (tokens.size() != 2) {
            errorMessage = singleLineUsage(commandDescriptions[CmdWidgetAt]);
            break;
        }
        if (!integerFromString(tokens.front(), &x) || !integerFromString(tokens.at(1), &y)) {
            errorMessage = singleLineUsage(commandDescriptions[CmdWidgetAt]);
            break;
        }
        widgetAddress = widgetAt(SymbolGroupValueContext(exc.dataSpaces(), exc.symbols()),
                                 x, y, &errorMessage);
    } while (false);

    if (widgetAddress.empty())
        ExtensionContext::instance().report('N', token, 0, "widgetat", errorMessage.c_str());
    else
        ExtensionContext::instance().reportLong('R', token, "widgetat", widgetAddress);
    return S_OK;
}

extern "C" HRESULT CALLBACK breakpoints(CIDebugClient *client, PCSTR argsIn)
{
    ExtensionCommandContext exc(client);
    int token;
    std::string errorMessage;
    bool humanReadable = false;
    unsigned verbose = 0;
    StringList tokens = commandTokens<StringList>(argsIn, &token);
    if (token == 0) // partial message
        return S_OK;

    while (!tokens.empty() && tokens.front().size() == 2 && tokens.front().at(0) == '-') {
        switch (tokens.front().at(1)) {
        case 'h':
            humanReadable = true;
            break;
        case 'v':
            verbose++;
            break;
        }
        tokens.pop_front();
    }
    const std::string bp = gdbmiBreakpoints(exc.control(), exc.symbols(), exc.dataSpaces(),
                                            humanReadable, verbose, &errorMessage);
    if (bp.empty())
        ExtensionContext::instance().report('N', token, 0, "breakpoints", errorMessage.c_str());
    else
        ExtensionContext::instance().reportLong('R', token, "breakpoints", bp);
    return S_OK;
}

extern "C" HRESULT CALLBACK test(CIDebugClient *client, PCSTR argsIn)
{
    enum Mode { Invalid, TestType, TestFixWatchExpression };
    ExtensionCommandContext exc(client);

    std::string testType;
    Mode mode = Invalid;
    int token = 0;
    StringList tokens = commandTokens<StringList>(argsIn, &token);
    if (token == 0) // partial message
        return S_OK;

    // Parse away options
    while (!tokens.empty() && tokens.front().size() == 2 && tokens.front().at(0) == '-') {
        const char option = tokens.front().at(1);
        tokens.pop_front();
        switch (option) {
        case 'T':
            mode = TestType;
            if (!tokens.empty()) {
                testType = tokens.front();
                tokens.pop_front();
            }
            break;
        case 'w':
            mode = TestFixWatchExpression;
            if (!tokens.empty()) {
                testType = tokens.front();
                tokens.pop_front();
            }
            break;
        } // case option
    }  // for options

    // Frame and iname
    if (mode == Invalid || testType.empty()) {
        ExtensionContext::instance().report('N', token, 0, "test", singleLineUsage(commandDescriptions[CmdTest]).c_str());
    } else {
        std::ostringstream str;
        switch (mode) {
        case Invalid:
            break;
        case TestType: {
            const KnownType kt = knownType(testType, 0);
            const std::string fixed = SymbolGroupValue::stripConst(testType);
            const unsigned size = SymbolGroupValue::sizeOf(fixed.c_str());
            str << '"' << testType <<  "\" (" << fixed << ") " << kt << " [";
            formatKnownTypeFlags(str, kt);
            str << "] size=" << size;
        }
            break;
        case TestFixWatchExpression:
            str << testType << " -> " << WatchesSymbolGroup::fixWatchExpression(exc.symbols(), testType);
            break;
        }
        ExtensionContext::instance().reportLong('R', token, "test", str.str());
    }
    return S_OK;
}

// Hook for dumping Known Structs.
// Shows up in 'dv' (appended) as well as IDebugSymbolGroup::GetValueText.

extern "C"  HRESULT CALLBACK KnownStructOutput(ULONG Flag, ULONG64 Address, PSTR StructName, PSTR Buffer, PULONG BufferSize)
{
    static const char knownTypesC[] = "HWND__\0"; // implicitly adds terminating 0
    if (Flag == DEBUG_KNOWN_STRUCT_GET_NAMES) {
        memcpy(Buffer, knownTypesC, sizeof(knownTypesC));
        return S_OK;
    }
    // Usually 260 chars buf
    if (!strcmp(StructName, "HWND__")) {
        // Dump a HWND. This is usually passed around as 'HWND*' with an (opaque) pointer value.
        // CDB dereferences it and passes it on here, which is why we get it as address.
        RECT rectangle;
        enum { BufSize = 1000 };
        char buf[BufSize];
        std::ostringstream str;

        HWND hwnd;
        memset(&rectangle, 0, sizeof(RECT));
        memset(&hwnd, 0, sizeof(HWND));
        // Coerce the address into a HWND (little endian)
        memcpy(&hwnd, &Address, SymbolGroupValue::pointerSize());
        // Dump geometry and class.
        if (GetWindowRect(hwnd, &rectangle) && GetClassNameA(hwnd, buf, BufSize))
            str << ' ' << rectangle.left << '+' << rectangle.top << '+'
                << (rectangle.right - rectangle.left) << 'x' << (rectangle.bottom - rectangle.top)
                << " '" << buf << '\'';
        // Check size for string + 1;
        const std::string rc = str.str();
        const ULONG requiredBufferLength = static_cast<ULONG>(rc.size()) + 1;
        if (requiredBufferLength >= *BufferSize) {
            *BufferSize = requiredBufferLength;
            return  S_FALSE;
        }
        strcpy(Buffer, rc.c_str());
    }

    return S_OK;
}
