Commit cc3dd439 authored by Friedemann Kleint's avatar Friedemann Kleint
Browse files

Debugger[New CDB]: Linked lists containers/container child expansion.

Introduce node hierarchy and move nodes to a separate file.
Introduce reference nodes that point to additional symbols and
symbols within the symbol tree (make deeply nested linked list
elements visible as array elements). Properly name container
elements as array elements 0..n. Fix pre-expansion of complex
dumpers.
parent 95731517
......@@ -34,7 +34,7 @@
#include <functional>
typedef SymbolGroupNode::SymbolGroupNodePtrVector SymbolGroupNodePtrVector;
typedef AbstractSymbolGroupNode::AbstractSymbolGroupNodePtrVector AbstractSymbolGroupNodePtrVector;
// Return size of container or -1
int containerSize(KnownType kt, SymbolGroupNode *n, const SymbolGroupValueContext &ctx)
......@@ -150,16 +150,16 @@ int containerSize(KnownType kt, const SymbolGroupValue &v)
/* Generate a list of children by invoking the functions to obtain the value
* and the next link */
template <class ValueFunction, class NextFunction>
SymbolGroupNodePtrVector linkedListChildList(SymbolGroupValue headNode,
int count,
ValueFunction valueFunc,
NextFunction nextFunc)
AbstractSymbolGroupNodePtrVector linkedListChildList(SymbolGroupValue headNode,
int count,
ValueFunction valueFunc,
NextFunction nextFunc)
{
SymbolGroupNodePtrVector rc;
AbstractSymbolGroupNodePtrVector rc;
rc.reserve(count);
for (int i =0; i < count && headNode; i++) {
if (const SymbolGroupValue value = valueFunc(headNode)) {
rc.push_back(value.node());
rc.push_back(ReferenceSymbolGroupNode::createArrayNode(i, value.node()));
headNode = nextFunc(headNode);
} else {
break;
......@@ -180,33 +180,33 @@ private:
};
// std::list<T>: Dummy head node and then a linked list of "_Next", "_Myval".
static inline SymbolGroupNodePtrVector stdListChildList(SymbolGroupNode *n, int count,
static inline AbstractSymbolGroupNodePtrVector stdListChildList(SymbolGroupNode *n, int count,
const SymbolGroupValueContext &ctx)
{
if (count)
if (const SymbolGroupValue head = SymbolGroupValue(n, ctx)[unsigned(0)][unsigned(0)]["_Myhead"]["_Next"])
return linkedListChildList(head, count, MemberByName("_Myval"), MemberByName("_Next"));
return SymbolGroupNodePtrVector();
return AbstractSymbolGroupNodePtrVector();
}
// QLinkedList<T>: Dummy head node and then a linked list of "n", "t".
static inline SymbolGroupNodePtrVector qLinkedListChildList(SymbolGroupNode *n, int count,
static inline AbstractSymbolGroupNodePtrVector qLinkedListChildList(SymbolGroupNode *n, int count,
const SymbolGroupValueContext &ctx)
{
if (count)
if (const SymbolGroupValue head = SymbolGroupValue(n, ctx)["e"]["n"])
return linkedListChildList(head, count, MemberByName("t"), MemberByName("n"));
return SymbolGroupNodePtrVector();
return AbstractSymbolGroupNodePtrVector();
}
/* Helper for array-type containers:
* Add a series of "*(innertype *)0x (address + n * size)" fake child symbols.
* for a function generating a sequence of addresses. */
template <class AddressFunc>
SymbolGroupNodePtrVector arrayChildList(SymbolGroup *sg, AddressFunc addressFunc,
AbstractSymbolGroupNodePtrVector arrayChildList(SymbolGroup *sg, AddressFunc addressFunc,
const std::string &innerType, int count)
{
SymbolGroupNodePtrVector rc;
AbstractSymbolGroupNodePtrVector rc;
if (!count)
return rc;
std::string errorMessage;
......@@ -217,8 +217,8 @@ SymbolGroupNodePtrVector arrayChildList(SymbolGroup *sg, AddressFunc addressFunc
if (!endsWith(innerType, '*'))
str << ' ';
str << "*)" << std::showbase << std::hex << addressFunc();
if (SymbolGroupNode *child = sg->addSymbol(str.str(), toString(i), &errorMessage)) {
rc.push_back(child);
if (SymbolGroupNode *child = sg->addSymbol(str.str(), std::string(), &errorMessage)) {
rc.push_back(ReferenceSymbolGroupNode::createArrayNode(i, child));
} else {
break;
}
......@@ -244,17 +244,17 @@ private:
const ULONG m_delta;
};
static inline SymbolGroupNodePtrVector arrayChildList(SymbolGroup *sg, ULONG64 address,
static inline AbstractSymbolGroupNodePtrVector arrayChildList(SymbolGroup *sg, ULONG64 address,
const std::string &innerType, int count)
{
if (const unsigned innerTypeSize = SymbolGroupValue::sizeOf(innerType.c_str()))
return arrayChildList(sg, AddressSequence(address, innerTypeSize),
innerType, count);
return SymbolGroupNodePtrVector();
return AbstractSymbolGroupNodePtrVector();
}
// std::vector<T>
static inline SymbolGroupNodePtrVector
static inline AbstractSymbolGroupNodePtrVector
stdVectorChildList(SymbolGroupNode *n, int count, const SymbolGroupValueContext &ctx)
{
if (count) {
......@@ -269,11 +269,11 @@ static inline SymbolGroupNodePtrVector
return arrayChildList(n->symbolGroup(), address,
SymbolGroupValue::stripPointerType(myFirst.type()), count);
}
return SymbolGroupNodePtrVector();
return AbstractSymbolGroupNodePtrVector();
}
// QVector<T>
static inline SymbolGroupNodePtrVector
static inline AbstractSymbolGroupNodePtrVector
qVectorChildList(SymbolGroupNode *n, int count, const SymbolGroupValueContext &ctx)
{
if (count) {
......@@ -284,7 +284,7 @@ static inline SymbolGroupNodePtrVector
if (const ULONG64 arrayAddress = firstElementV.address())
return arrayChildList(n->symbolGroup(), arrayAddress, firstElementV.type(), count);
}
return SymbolGroupNodePtrVector();
return AbstractSymbolGroupNodePtrVector();
}
// Helper function for arrayChildList() for use with QLists of large types that are an
......@@ -301,32 +301,32 @@ private:
};
// QList<>.
static inline SymbolGroupNodePtrVector
static inline AbstractSymbolGroupNodePtrVector
qListChildList(const SymbolGroupValue &v, int count)
{
// QList<T>: d/array is declared as array of void *[]. Dereference first
// element to obtain address.
if (!count)
return SymbolGroupNodePtrVector();
return AbstractSymbolGroupNodePtrVector();
const SymbolGroupValue dV = v["d"];
if (!dV)
return SymbolGroupNodePtrVector();
return AbstractSymbolGroupNodePtrVector();
const int begin = dV["begin"].intValue();
if (begin < 0)
return SymbolGroupNodePtrVector();
return AbstractSymbolGroupNodePtrVector();
const SymbolGroupValue firstElementV = dV["array"][unsigned(0)];
if (!firstElementV)
return SymbolGroupNodePtrVector();
return AbstractSymbolGroupNodePtrVector();
ULONG64 arrayAddress = firstElementV.address();
if (!arrayAddress)
return SymbolGroupNodePtrVector();
return AbstractSymbolGroupNodePtrVector();
const std::vector<std::string> innerTypes = v.innerTypes();
if (innerTypes.size() != 1)
return SymbolGroupNodePtrVector();
return AbstractSymbolGroupNodePtrVector();
const std::string &innerType = innerTypes.front();
const unsigned innerTypeSize = SymbolGroupValue::sizeOf(innerType.c_str());
if (!innerTypeSize)
return SymbolGroupNodePtrVector();
return AbstractSymbolGroupNodePtrVector();
/* QList<> is:
* 1) An array of 'void *[]' where T values are coerced into the elements for
* POD/pointer types and small, movable or primitive Qt types. That is, smaller
......@@ -355,10 +355,10 @@ static inline SymbolGroupNodePtrVector
const HRESULT hr = v.context().dataspaces->ReadVirtual(arrayAddress + begin * pointerSize, data, allocSize, &bytesRead);
if (FAILED(hr) || bytesRead != allocSize) {
delete [] data;
return SymbolGroupNodePtrVector();
return AbstractSymbolGroupNodePtrVector();
}
// Generate sequence of addresses from pointer array
const SymbolGroupNodePtrVector rc = pointerSize == 8 ?
const AbstractSymbolGroupNodePtrVector rc = pointerSize == 8 ?
arrayChildList(v.node()->symbolGroup(), AddressArraySequence<ULONG64>(reinterpret_cast<const ULONG64 *>(data)), innerType, count) :
arrayChildList(v.node()->symbolGroup(), AddressArraySequence<ULONG32>(reinterpret_cast<const ULONG32 *>(data)), innerType, count);
delete [] data;
......@@ -369,11 +369,11 @@ static inline SymbolGroupNodePtrVector
innerType, count);
}
SymbolGroupNodePtrVector containerChildren(SymbolGroupNode *node, int type,
int size, const SymbolGroupValueContext &ctx)
AbstractSymbolGroupNodePtrVector containerChildren(SymbolGroupNode *node, int type,
int size, const SymbolGroupValueContext &ctx)
{
if (!size)
return SymbolGroupNodePtrVector();
return AbstractSymbolGroupNodePtrVector();
if (size > 100)
size = 100;
switch (type) {
......@@ -400,5 +400,5 @@ SymbolGroupNodePtrVector containerChildren(SymbolGroupNode *node, int type,
case KT_StdList:
return stdListChildList(node, size , ctx);
}
return SymbolGroupNodePtrVector();
return AbstractSymbolGroupNodePtrVector();
}
......@@ -31,6 +31,7 @@
#define CONTAINERS_H
struct SymbolGroupValueContext;
class AbstractSymbolGroupNode;
class SymbolGroupNode;
class SymbolGroupValue;
......@@ -44,9 +45,9 @@ int containerSize(KnownType kt, const SymbolGroupValue &v);
int containerSize(KnownType kt, SymbolGroupNode *n, const SymbolGroupValueContext &ctx);
/* Create a list of children of containers. */
std::vector<SymbolGroupNode *> containerChildren(SymbolGroupNode *node,
int type,
int size,
const SymbolGroupValueContext &ctx);
std::vector<AbstractSymbolGroupNode *> containerChildren(SymbolGroupNode *node,
int type,
int size,
const SymbolGroupValueContext &ctx);
#endif // CONTAINERS_H
......@@ -48,6 +48,7 @@ QT -= core
SOURCES += qtcreatorcdbextension.cpp \
extensioncontext.cpp \
eventcallback.cpp \
symbolgroupnode.cpp \
symbolgroup.cpp \
common.cpp \
stringutils.cpp \
......@@ -68,4 +69,5 @@ HEADERS += extensioncontext.h \
base64.h \
symbolgroupvalue.h \
containers.h \
knowntype.h
knowntype.h \
symbolgroupnode.h
......@@ -100,8 +100,9 @@ static const CommandDescription commandDescriptions[] = {
"Prints inferior process id and hooks up output callbacks.",
"[-t token]"},
{"expandlocals", "Expands local variables by iname in symbol group.",
"[-t token] <frame-number> <iname1-list>\n"
"iname1-list: Comma-separated list of inames"},
"[-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] [T formats] [-I formats] [-c] [-h] [-d] [-e expand-list] [-u uninitialized-list]\n<frame-number> [iname]\n"
......@@ -247,21 +248,40 @@ extern "C" HRESULT CALLBACK expandlocals(CIDebugClient *client, PCSTR args)
std::string errorMessage;
int token;
const StringVector tokens = commandTokens<StringVector>(args, &token);
StringList tokens = commandTokens<StringList>(args, &token);
StringVector inames;
if (tokens.size() == 2u && integerFromString(tokens.front(), &frame)) {
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);
split(tokens.at(1), ',', std::back_inserter(inames));
} else {
errorMessage = singleLineUsage(commandDescriptions[CmdExpandlocals]);
}
if (symGroup) {
const unsigned succeeded = symGroup->expandList(inames, &errorMessage);
ExtensionContext::instance().report('R', token, "expandlocals", "%u/%u %s",
succeeded, unsigned(inames.size()), errorMessage.c_str());
} else {
if (!symGroup) {
ExtensionContext::instance().report('N', token, "expandlocals", errorMessage.c_str());
return S_OK;
}
const unsigned succeeded = runComplexDumpers ?
symGroup->expandListRunComplexDumpers(inames, SymbolGroupValueContext(exc.dataSpaces()), &errorMessage) :
symGroup->expandList(inames, &errorMessage);
ExtensionContext::instance().report('R', token, "expandlocals", "%u/%u %s",
succeeded, unsigned(inames.size()), errorMessage.c_str());
return S_OK;
}
......@@ -338,18 +358,25 @@ static std::string commmandLocals(ExtensionCommandContext &exc,PCSTR args, int *
if (!tokens.empty())
iname = tokens.front();
const SymbolGroupValueContext dumpContext(exc.dataSpaces());
SymbolGroup * const symGroup = ExtensionContext::instance().symbolGroup(exc.symbols(), exc.threadId(), frame, errorMessage);
if (!symGroup)
return std::string();
if (!expandedInames.empty())
symGroup->expandList(expandedInames, errorMessage);
if (!uninitializedInames.empty())
symGroup->markUninitialized(uninitializedInames);
if (!expandedInames.empty()) {
if (parameters.dumpFlags & DumpParameters::DumpComplexDumpers) {
symGroup->expandListRunComplexDumpers(expandedInames, dumpContext, errorMessage);
} else {
symGroup->expandList(expandedInames, errorMessage);
}
}
if (debugOutput)
return symGroup->debug(iname, debugOutput - 1);
const SymbolGroupValueContext dumpContext(exc.dataSpaces());
return iname.empty() ?
symGroup->dump(dumpContext, parameters) :
symGroup->dump(iname, dumpContext, parameters, errorMessage);
......@@ -393,13 +420,13 @@ static std::string dumplocalHelper(ExtensionCommandContext &exc,PCSTR args, int
if (!symGroup)
return std::string();
SymbolGroupNode *n = symGroup->find(iname);
if (!n) {
AbstractSymbolGroupNode *n = symGroup->find(iname);
if (!n || !n->asSymbolGroupNode()) {
*errorMessage = "No such iname " + iname;
return std::string();
}
std::wstring value;
if (!dumpSimpleType(n, SymbolGroupValueContext(exc.dataSpaces()), &value)) {
if (!dumpSimpleType(n->asSymbolGroupNode(), SymbolGroupValueContext(exc.dataSpaces()), &value)) {
*errorMessage = "Cannot dump " + iname;
return std::string();
}
......
This diff is collapsed.
......@@ -31,197 +31,7 @@
#define SYMBOLGROUP_H
#include "common.h"
#include <string>
#include <vector>
#include <map>
#include <iosfwd>
std::ostream &operator<<(std::ostream &, const DEBUG_SYMBOL_PARAMETERS&p);
class SymbolGroupNodeVisitor;
class SymbolGroup;
struct SymbolGroupValueContext;
// All parameters for dumping in one struct.
struct DumpParameters
{
typedef std::map<std::string, int> FormatMap; // type or iname to format
enum DumpFlags
{
DumpHumanReadable = 0x1,
DumpComplexDumpers = 0x2
};
DumpParameters();
bool humanReadable() const { return dumpFlags & DumpHumanReadable; }
// Helper to decode format option arguments.
static FormatMap decodeFormatArgument(const std::string &f);
bool recode(const std::string &type, const std::string &iname,
const SymbolGroupValueContext &ctx,
std::wstring *value, int *encoding) const;
int format(const std::string &type, const std::string &iname) const;
unsigned dumpFlags;
FormatMap typeFormats;
FormatMap individualFormats;
};
/* Thin wrapper around a symbol group entry. Provides accessors for fixed-up
* symbol group value and a dumping facility consisting of:
* - 'Simple' dumping done when running the DumpVisitor. This produces one
* line of formatted output shown for the class. These values
* values should are displayed, while still allowing for expansion of the structure
* in the debugger.
* It also pre-determines some information for complex dumping (type, container).
* - 'Complex' dumping: Obscures the symbol group children by fake children, for
* example container children, run when calling SymbolGroup::dump with an iname.
* The fake children are appended to the child list (other children are just marked as
* obscured for GDBMI dumping so that SymbolGroupValue expressions still work as before).
* The dumping is mostly based on SymbolGroupValue expressions.
* in the debugger. Evaluating those dumpers might expand symbol nodes, which are
* then marked as 'ExpandedByDumper'. This stops the dump recursion to prevent
* outputting data that were not explicitly expanded by the watch handler. */
class SymbolGroupNode {
SymbolGroupNode(const SymbolGroupNode&);
SymbolGroupNode& operator=(const SymbolGroupNode&);
explicit SymbolGroupNode(SymbolGroup *symbolGroup,
ULONG index,
const std::string &name,
const std::string &iname,
SymbolGroupNode *parent = 0);
public:
enum Flags {
Uninitialized = 0x1,
SimpleDumperNotApplicable = 0x2, // No dumper available for type
SimpleDumperOk = 0x4, // Internal dumper ran, value set
SimpleDumperFailed = 0x8, // Internal dumper failed
SimpleDumperMask = SimpleDumperNotApplicable|SimpleDumperOk|SimpleDumperFailed,
ExpandedByDumper = 0x10,
AdditionalSymbol = 0x20, // Introduced by addSymbol, should not be visible
Obscured = 0x40, // Symbol is obscured by (for example) fake container children
ComplexDumperOk = 0x80
};
typedef std::vector<DEBUG_SYMBOL_PARAMETERS> SymbolParameterVector;
typedef std::vector<SymbolGroupNode *> SymbolGroupNodePtrVector;
typedef SymbolGroupNodePtrVector::iterator SymbolGroupNodePtrVectorIterator;
typedef SymbolGroupNodePtrVector::const_iterator SymbolGroupNodePtrVectorConstIterator;
~SymbolGroupNode() { removeChildren(); }
// Indicate reference
void setReferencedBy(SymbolGroupNode *n);
void removeChildren();
void parseParameters(SymbolParameterVector::size_type index,
SymbolParameterVector::size_type parameterOffset,
const SymbolParameterVector &vec);
static SymbolGroupNode *create(SymbolGroup *sg, const std::string &name, const SymbolParameterVector &vec);
// For root nodes, only: Add a new symbol by name
SymbolGroupNode *addSymbolByName(const std::string &name, // Expression like 'myarray[1]'
const std::string &iname, // Desired iname, defaults to name
std::string *errorMessage);
const std::string &name() const { return m_name; }
std::string fullIName() const;
const std::string &iName() const { return m_iname; }
const SymbolGroupNodePtrVector &children() const { return m_children; }
SymbolGroupNode *childAt(unsigned) const;
unsigned indexByIName(const char *) const; // (unsigned(-1) on failure
SymbolGroupNode *childByIName(const char *) const;
const SymbolGroupNode *parent() const { return m_parent; }
const SymbolGroupNode *referencedParent() const { return m_referencedBy ? m_referencedBy : m_parent; }
SymbolGroup *symbolGroup() const { return m_symbolGroup; }
// I/O: Gdbmi dump for Visitors
void dump(std::ostream &str, const DumpParameters &p, const SymbolGroupValueContext &ctx);
// I/O: debug for Visitors
void debug(std::ostream &os, unsigned verbosity, unsigned depth) const;
std::wstring symbolGroupRawValue() const;
std::wstring symbolGroupFixedValue() const;
std::string type() const;
int dumperType() const { return m_dumperType; } // Valid after dumper run
int dumperContainerSize() { return m_dumperContainerSize; } // Valid after dumper run
unsigned size() const; // Size of value
ULONG64 address() const;
bool accept(SymbolGroupNodeVisitor &visitor, unsigned child, unsigned depth);
bool expand(std::string *errorMessage);
bool isExpanded() const { return !m_children.empty(); }
bool canExpand() const { return m_parameters.SubElements > 0; }
void runComplexDumpers(const SymbolGroupValueContext &ctx);
// Cast to a different type. Works only on unexpanded nodes
bool typeCast(const std::string &desiredType, std::string *errorMessage);
ULONG subElements() const { return m_parameters.SubElements; }
ULONG index() const { return m_index; }
unsigned flags() const { return m_flags; }
void setFlags(unsigned f) { m_flags = f; }
void addFlags(unsigned f) { m_flags |= f; }
void clearFlags(unsigned f) { m_flags &= ~f; }
private:
bool isArrayElement() const;
// Notify about expansion of a node, shift indexes
bool notifyExpanded(ULONG index, ULONG insertedCount);
std::wstring simpleDumpValue(const SymbolGroupValueContext &ctx,
const DumpParameters &p);
SymbolGroup *const m_symbolGroup;
SymbolGroupNode *m_parent;
// Indicates a fake child (container value). Used for the full iname
SymbolGroupNode *m_referencedBy;
ULONG m_index;
DEBUG_SYMBOL_PARAMETERS m_parameters; // Careful when using ParentSymbol. It might not be correct.
SymbolGroupNodePtrVector m_children;
const std::string m_name;
const std::string m_iname;
unsigned m_flags;
std::wstring m_dumperValue;
int m_dumperType;
int m_dumperContainerSize;
};
/* Visitor that takes care of iterating over the nodes
* visit() is not called for the (invisible) root node, but starting with the
* root's children with depth=0.
* Return true from visit() to terminate the recursion. */
class SymbolGroupNodeVisitor {
SymbolGroupNodeVisitor(const SymbolGroupNodeVisitor&);
SymbolGroupNodeVisitor& operator=(const SymbolGroupNodeVisitor&);
friend class SymbolGroupNode;
protected:
SymbolGroupNodeVisitor() {}
public:
virtual ~SymbolGroupNodeVisitor() {}
protected:
enum VisitResult {
VisitContinue,
VisitSkipChildren,
VisitStop
};
private:
virtual VisitResult visit(SymbolGroupNode *node, unsigned child, unsigned depth) = 0;
// Helper for formatting output.
virtual void childrenVisited(const SymbolGroupNode * /* node */, unsigned /* depth */) {}
};
#include "symbolgroupnode.h"
// Thin wrapper around a symbol group storing a tree of expanded symbols rooted on
// a fake "locals" root element.
......@@ -233,15 +43,15 @@ public:
typedef std::vector<DEBUG_SYMBOL_PARAMETERS> SymbolParameterVector;
private:
SymbolGroup(const SymbolGroup &);
SymbolGroup &operator=(const SymbolGroup &);
explicit SymbolGroup(CIDebugSymbolGroup *,
const SymbolParameterVector &vec,
ULONG threadId, unsigned frame);
SymbolGroup(const SymbolGroup &);
SymbolGroup &operator=(const SymbolGroup &);
public:
typedef SymbolGroupNode::SymbolGroupNodePtrVector SymbolGroupNodePtrVector;
typedef AbstractSymbolGroupNode::AbstractSymbolGroupNodePtrVector AbstractSymbolGroupNodePtrVector;
static SymbolGroup *create(CIDebugControl *control,
CIDebugSymbols *,
......@@ -262,17 +72,22 @@ public:
ULONG threadId() const { return m_threadId; }
SymbolGroupNode *root() { return m_root; }
const SymbolGroupNode *root() const { return m_root; }
SymbolGroupNode *find(const std::string &iname) const;
AbstractSymbolGroupNode *find(const std::string &iname) const;
// Expand a single node "locals.A.B" requiring that "locals.A.B" is already visible
// (think mkdir without -p).
bool expand(const std::string &node, std::string *errorMessage);
bool expandRunComplexDumpers(const std::string &node, const SymbolGroupValueContext &ctx, std::string *errorMessage);
// Expand a node list "locals.i1,locals.i2", expanding all nested child nodes
// (think mkdir -p).
unsigned expandList(const std::vector<std::string> &nodes, std::string *errorMessage);
unsigned expandListRunComplexDumpers(const std::vector<std::string> &nodes,
const SymbolGroupValueContext &ctx,
std::string *errorMessage);
// Mark uninitialized (top level only)
void markUninitialized(const std::vector<std::string> &nodes);
// Expand a single node "locals.A.B" requiring that "locals.A.B" is already visible
// (think mkdir without -p).
bool expand(const std::string &node, std::string *errorMessage);
// Cast an (unexpanded) node
bool typeCast(const std::string &iname, const std::string &desiredType, std::string *errorMessage);
// Add a symbol by name expression
......@@ -296,7 +111,7 @@ public:
std::string *errorMessage);
private:
inline SymbolGroupNode *findI(const std::string &iname) const;