399 lines
9.6 KiB
C++
399 lines
9.6 KiB
C++
/* -----------------------------------------------------------------------------
|
|
GSFramework
|
|
Copyright 2001-2013 Emmanuel Julien. All Rights Reserved.
|
|
----------------------------------------------------------------------------- */
|
|
|
|
|
|
#include "script_squirrel/squirrel_debugger.h"
|
|
#include "script_squirrel/cobject/cobject.h"
|
|
#include "script_squirrel/cobject/vector_decl.h"
|
|
#include "math/vector.h"
|
|
#include "log/log.h"
|
|
|
|
using namespace GS;
|
|
using namespace GS::Script;
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
String SquirrelDebugger::FormatParameter(DebuggerVariable *var)
|
|
{
|
|
switch (var->type)
|
|
{
|
|
case OT_STRING:
|
|
return String::Format("%s=%s", var->id.c_str(), var->v_string.c_str());
|
|
case OT_FLOAT:
|
|
return String::Format("%s=%.4f", var->id.c_str(), var->v_float);
|
|
case OT_INTEGER:
|
|
return String::Format("%s=%d", var->id.c_str(), var->v_int);
|
|
case OT_BOOL:
|
|
return String::Format("%s=%s", var->id.c_str(), var->v_bool ? "True" : "False");
|
|
}
|
|
return var->id;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
|
|
//------------------------------------------------------------------------------
|
|
static int DebuggerCompareVariable(DebuggerVariable *&v, const char *s) { return !String::Compare(v->id, s); }
|
|
//------------------------------------------------------------------------------
|
|
|
|
//------------------------------------------------------------------------------
|
|
DebuggerVariable *SquirrelDebugger::InsertVariable(const char *name, AutoList <DebuggerVariable *> &debug_var, int level)
|
|
{
|
|
// Catch nesting limit.
|
|
if (level == 3)
|
|
return NULL;
|
|
|
|
// Try to locate variable in the current watch.
|
|
DebuggerVariable *var = ListFindEx(debug_var, DebuggerCompareVariable, name);
|
|
|
|
// Retrieve stack variable type.
|
|
bool check_change = false, has_changed = false;
|
|
|
|
SQObjectType sq_type = sq_gettype(vm, -1);
|
|
|
|
if (var)
|
|
{
|
|
// If type has changed, drop the whole member watch structure.
|
|
if (var->type != (uint)sq_type)
|
|
var->member_list.Clear();
|
|
else
|
|
check_change = true;
|
|
}
|
|
else
|
|
{
|
|
var = new DebuggerVariable;
|
|
if (!var)
|
|
__ERR__(__LOG_E__ << "Failed to allocate debugger watch structure.\n", NULL);
|
|
|
|
debug_var.Add(var);
|
|
var->id = name;
|
|
}
|
|
|
|
// Update the variable.
|
|
var->referenced = true;
|
|
var->type = sq_type;
|
|
|
|
String type = "Unknown", value;
|
|
int limit = 64 / (level + 1); // Increasingly limit array exploration the deeper in the hierarchy we get.
|
|
|
|
switch (sq_type)
|
|
{
|
|
case OT_NULL:
|
|
type = "Null";
|
|
break;
|
|
|
|
case OT_TABLE:
|
|
{
|
|
type = "Table";
|
|
|
|
// Check item slot.
|
|
sq_pushnull(vm); // Iterator.
|
|
while (SQ_SUCCEEDED(sq_next(vm, -2)))
|
|
{
|
|
// Here -1 is the value and -2 is the key.
|
|
const SQChar *slot_name;
|
|
if (sq_getstring(vm, -2, &slot_name) == SQ_OK)
|
|
InsertVariable(slot_name, var->member_list, level + 1);
|
|
|
|
sq_pop(vm, 2);
|
|
}
|
|
sq_pop(vm, 1);
|
|
}
|
|
break;
|
|
|
|
case OT_ARRAY:
|
|
{
|
|
type = "Array";
|
|
SQInteger array_size = sq_getsize(vm, -1);
|
|
value = String::Format("{ Length=%d, [...] }", array_size);
|
|
|
|
// Check item slot.
|
|
sq_pushnull(vm); // Iterator.
|
|
while (SQ_SUCCEEDED(sq_next(vm, -2)))
|
|
{
|
|
int idx;
|
|
sq_getinteger(vm, -2, (SQInteger *)&idx);
|
|
String slot_name = String::Format("[%d]", idx);
|
|
InsertVariable(slot_name, var->member_list, level + 1);
|
|
sq_pop(vm, 2);
|
|
|
|
if (--limit == 0)
|
|
break;
|
|
}
|
|
sq_pop(vm, 1);
|
|
}
|
|
break;
|
|
|
|
case OT_CLOSURE:
|
|
type = "Function";
|
|
break;
|
|
|
|
case OT_NATIVECLOSURE:
|
|
type = "C/C++";
|
|
break;
|
|
|
|
case OT_OUTER:
|
|
type = "Outer";
|
|
break;
|
|
|
|
case OT_USERDATA:
|
|
type = "User data";
|
|
break;
|
|
|
|
case OT_GENERATOR:
|
|
type = "Generator";
|
|
break;
|
|
|
|
case OT_USERPOINTER:
|
|
break;
|
|
|
|
case OT_THREAD:
|
|
type = "Thread";
|
|
break;
|
|
|
|
case OT_FUNCPROTO:
|
|
type = "Prototype";
|
|
break;
|
|
|
|
case OT_CLASS:
|
|
type = "Class definition";
|
|
break;
|
|
|
|
case OT_INSTANCE:
|
|
{
|
|
type = "Class instance";
|
|
|
|
Vector4 *v = NULL;
|
|
|
|
CObjectType typetag;
|
|
if (CObject::GetType(vm, -1, typetag))
|
|
{
|
|
type = CObjectTypeToString(typetag);
|
|
value = FormatUserObjectParameter(typetag);
|
|
}
|
|
else if (SQ_SUCCEEDED(sq_getinstanceup(vm, -1, (SQUserPointer*)&v, (SQUserPointer)&__Vector_decl)))
|
|
{
|
|
type = "Vector";
|
|
value = String::Format("{%.2f, %.2f, %.2f, %.2f}", v->x, v->y, v->z, v->w);
|
|
}
|
|
else
|
|
{
|
|
// Trying iterating over instance members.
|
|
AutoList <String *> member_list;
|
|
|
|
sq_getclass(vm, -1);
|
|
sq_pushnull(vm); // Iterator.
|
|
while (SQ_SUCCEEDED(sq_next(vm, -2)))
|
|
{
|
|
switch (sq_gettype(vm, -1))
|
|
{
|
|
case OT_CLOSURE:
|
|
case OT_NATIVECLOSURE:
|
|
break;
|
|
|
|
default:
|
|
{
|
|
const SQChar *slot_name;
|
|
if (sq_getstring(vm, -2, &slot_name) == SQ_OK)
|
|
member_list.Add(new String(slot_name));
|
|
}
|
|
break;
|
|
}
|
|
sq_pop(vm, 2);
|
|
}
|
|
sq_pop(vm, 2);
|
|
|
|
// Get actual instance values.
|
|
ListForeachPtr(String *, m, member_list)
|
|
{
|
|
sq_pushstring(vm, m->c_str(), m->Len());
|
|
if (sq_get(vm, -2) == SQ_OK)
|
|
InsertVariable(m->c_str(), var->member_list, level + 1);
|
|
sq_pop(vm, 1);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case OT_WEAKREF:
|
|
type = "Weakref";
|
|
break;
|
|
|
|
case OT_BOOL:
|
|
{
|
|
type = "Bool";
|
|
SQBool b;
|
|
sq_getbool(vm, -1, &b);
|
|
value = b ? "True" : "False";
|
|
has_changed = check_change ? var->v_bool != asbool(b) : false;
|
|
var->v_bool = b ? true : false;
|
|
}
|
|
break;
|
|
|
|
case OT_STRING:
|
|
{
|
|
type = "String";
|
|
const SQChar *s;
|
|
sq_getstring(vm, -1, &s);
|
|
value = String::Format("%s", s);
|
|
has_changed = check_change ? var->v_string != s : false;
|
|
var->v_string = s;
|
|
}
|
|
break;
|
|
|
|
case OT_INTEGER:
|
|
{
|
|
type = "Integer";
|
|
SQInteger i;
|
|
sq_getinteger(vm, -1, &i);
|
|
value = String::Format("%d", i);
|
|
has_changed = check_change ? var->v_int != i : false;
|
|
var->v_int = (int)i;
|
|
}
|
|
break;
|
|
|
|
case OT_FLOAT:
|
|
{
|
|
type = "Float";
|
|
SQFloat f;
|
|
sq_getfloat(vm, -1, &f);
|
|
value = String::Format("%.4f", f);
|
|
has_changed = check_change ? var->v_float != f : false;
|
|
var->v_float = f;
|
|
}
|
|
break;
|
|
}
|
|
|
|
// Value for item with sub-items.
|
|
if (var->member_list.GetCount() && value.IsEmpty())
|
|
{
|
|
value = "{ ";
|
|
|
|
int max = 4;
|
|
ListForeachPtr(DebuggerVariable *, v, var->member_list)
|
|
{
|
|
value += max > 0 ? FormatParameter(v).c_str() : "...";
|
|
value += (iterator.Next() == NULL) || (max == 0) ? " }" : ", ";
|
|
|
|
if (!max--)
|
|
break;
|
|
}
|
|
}
|
|
|
|
var->type_string = type;
|
|
var->modified = has_changed;
|
|
var->value = value;
|
|
|
|
return var;
|
|
}
|
|
void SquirrelDebugger::RefreshStackFrameLocalsCache()
|
|
{
|
|
// Mark all variables as unreferenced.
|
|
DereferenceVarTree(local_var_tree);
|
|
|
|
// Update local variables.
|
|
const char *var_name;
|
|
for (int n = 0; (var_name = sq_getlocal(vm, debug_stack_frame, n)) != NULL; ++n)
|
|
InsertVariable(var_name, local_var_tree, 0);
|
|
|
|
// Drop unreferenced variables.
|
|
ListForeachPtr(DebuggerVariable *, v, local_var_tree)
|
|
if (!v->referenced)
|
|
local_var_tree.Remove(v);
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
|
|
//------------------------------------------------------------------------------
|
|
void SquirrelDebugger::VariableToMetatagString(DebuggerVariable *v, String &s)
|
|
{
|
|
s += String::Format("<Variable=<Name=\"%s\">\n", v->id.c_str());
|
|
|
|
switch (v->type)
|
|
{
|
|
case OT_NULL: s += "<ScriptType=\"Null\">\n"; break;
|
|
case OT_TABLE: s += "<ScriptType=\"Table\">\n"; break;
|
|
case OT_ARRAY: s += "<ScriptType=\"Array\">\n"; break;
|
|
case OT_USERDATA: s += "<ScriptType=\"UserData\">\n"; break;
|
|
case OT_CLOSURE: s += "<ScriptType=\"Closure\">\n"; break;
|
|
case OT_NATIVECLOSURE: s += "<ScriptType=\"NativeClosure\">\n"; break;
|
|
case OT_GENERATOR: s += "<ScriptType=\"Generator\">\n"; break;
|
|
case OT_USERPOINTER: s += "<ScriptType=\"UserPointer\">\n"; break;
|
|
case OT_THREAD: s += "<ScriptType=\"Thread\">\n"; break;
|
|
case OT_FUNCPROTO: s += "<ScriptType=\"FuncProto\">\n"; break;
|
|
case OT_CLASS: s += "<ScriptType=\"Class\">\n"; break;
|
|
case OT_INSTANCE: s += "<ScriptType=\"Instance\">\n"; break;
|
|
case OT_WEAKREF: s += "<ScriptType=\"Weakref\">\n"; break;
|
|
case OT_BOOL: s += "<ScriptType=\"Bool\">\n"; break;
|
|
case OT_STRING: s += "<ScriptType=\"String\">\n"; break;
|
|
case OT_INTEGER: s += "<ScriptType=\"Integer\">\n"; break;
|
|
case OT_FLOAT: s += "<ScriptType=\"Float\">\n"; break;
|
|
}
|
|
|
|
// Display type.
|
|
s += String::Format("<Type=\"%s\">\n", v->type_string.c_str());
|
|
|
|
// Value.
|
|
if (!v->value.IsEmpty())
|
|
s += String::Format("<ValueString=\"%s\">\n", v->value.c_str());
|
|
|
|
if (v->modified)
|
|
s += "<Modified>";
|
|
|
|
// Members.
|
|
if (v->member_list.GetCount())
|
|
{
|
|
s += "<Members=\n";
|
|
ListForeachPtr(DebuggerVariable *, m, v->member_list)
|
|
VariableToMetatagString(m, s);
|
|
s += ">\n";
|
|
}
|
|
|
|
s += ">\n";
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
|
|
//------------------------------------------------------------------------------
|
|
void SquirrelDebugger::GetStackFrameSource(const char *&source, int &line)
|
|
{
|
|
SQStackInfos si;
|
|
|
|
if (SQ_SUCCEEDED(sq_stackinfos(vm, debug_stack_frame, &si)))
|
|
{
|
|
source = (const char *)si.source;
|
|
line = si.line;
|
|
}
|
|
else
|
|
{
|
|
source = NULL;
|
|
line = -1;
|
|
}
|
|
}
|
|
int SquirrelDebugger::GetStackFrameIndex()
|
|
{
|
|
int depth = 0;
|
|
|
|
SQStackInfos si;
|
|
for (int c = 0; SQ_SUCCEEDED(sq_stackinfos(vm, c, &si)); ++c)
|
|
if (String(si.source) != "NATIVE") // Skip native C frame.
|
|
{
|
|
depth = c;
|
|
break;
|
|
}
|
|
|
|
return depth;
|
|
}
|
|
int SquirrelDebugger::GetCallstackDepth()
|
|
{
|
|
int depth = 0;
|
|
SQStackInfos si;
|
|
while (SQ_SUCCEEDED(sq_stackinfos(vm, depth, &si)))
|
|
depth++;
|
|
return depth;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
|
|
SquirrelDebugger::SquirrelDebugger(SquirrelVM &svm)
|
|
{
|
|
vm = svm.VM();
|
|
}
|