/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 *
 * The contents of this file are subject to the Netscape Public License
 * Version 1.0 (the "NPL"); you may not use this file except in
 * compliance with the NPL.  You may obtain a copy of the NPL at
 * http://www.mozilla.org/NPL/
 *
 * Software distributed under the NPL is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
 * for the specific language governing rights and limitations under the
 * NPL.
 *
 * The Initial Developer of this code under the NPL is Netscape
 * Communications Corporation.  Portions created by Netscape are
 * Copyright (C) 1998 Netscape Communications Corporation.  All Rights
 * Reserved.
 */

/*
* Standard initialization of the debugger
*/

/* Top level code is run when this file is first loaded... (duh) */

var noisy = false
//var noisy = true

jsd.SetScriptHook(scriptHook);
jsd.SetExecutionHook(execHook);
jsd.SetErrorReporterHook(errorReporterHook);

function scriptHook(handle, creating)
{
    if(creating) {
        JSDScript.add(new JSDScript(handle,
                                    jsd.GetScriptFilename(handle),
                                    jsd.GetScriptFunctionName(handle),
                                    jsd.GetScriptBaseLineNumber(handle),
                                    jsd.GetScriptLineExtent(handle)));
        setAllBreakpointsForScript(handle);

        if(jsd.GetScriptLineExtent(handle) < 1) {
            debugger;
            jsd.GetScriptLineExtent(handle);
        }

    }
    else {
        JSDScript.remove(JSDScript.find(handle));
    }
    return true;
}

function errorReporterHook(msg, filename, lineno, lineBuf, tokenOffset)
{
    if(jsd.Evaluating)
        return jsd.JSD_ERROR_REPORTER_PASS_ALONG;

    print("............................");
    print("msg = "+msg);
    print("filename = "+filename);
    print("lineno = "+lineno);
    print("lineBuf = "+lineBuf);
    print("tokenOffset = "+tokenOffset);
    print("............................");

    var answer = -1
    while(-1 == answer) {
        switch(prompt("[E]at [i]gnore [p]ass along [d]ebug ? ")) {
            case "I":
            case "i":
                answer = jsd.JSD_ERROR_REPORTER_RETURN;
                break;
            case "E":
            case "e":
                answer = jsd.JSD_ERROR_REPORTER_CLEAR_RETURN;
                break;
            case "P":
            case "p":
                answer = jsd.JSD_ERROR_REPORTER_PASS_ALONG;
                break;
            case "D":
            case "d":
                answer = jsd.JSD_ERROR_REPORTER_DEBUG;
                break;
            // let's simplify the user's life
            default:
                answer = jsd.JSD_ERROR_REPORTER_CLEAR_RETURN;
                break;
        }
    }
    return answer;
}

function execHook(type)
{
    // reset these globals
    stack = null;
    currentFrame = 0;

    buildStack();

    // save the reason for stopping
    hookType = type;

    if(jsd.InterruptSet)
        jsd.ClearInterrupt();
    switch(type) {
        case jsd.JSD_HOOK_INTERRUPTED:
            break;
        case jsd.JSD_HOOK_BREAKPOINT:
            break;
        case jsd.JSD_HOOK_DEBUG_REQUESTED:
            break;
        case jsd.JSD_HOOK_DEBUGGER_KEYWORD:
            break;
        case jsd.JSD_HOOK_THROW:
            break;
        default:
            print("hit unknown hook type!")
            return jsd.JSD_HOOK_RETURN_CONTINUE;
    }
    writeln("");
    why();
    writeln("");
    where();
    writeln("");
    listc();
    writeln("");
    return doEvalLoop();
}

/***************************************************************************/
/* Debug Objects */

function JSDScript(handle, url, funname, base, extent)
{
    this.handle = handle;
    this.url = url;
    this.funname = funname;
    this.base = base;
    this.extent = extent;
    this.toString = function()
            {return "JSDScript: "+url+":"+
                    (funname!=null&&funname.length?funname:"_TOP_LEVEL_")+
                    ":"+base+":"+extent;}
}

JSDScript.scripts = new Object;

JSDScript.add = function(script)
{
    JSDScript.scripts[script.handle] = script;
    if(noisy)print("Created "+script);
    ScriptCreatedHook(script);
    return "";
}

JSDScript.remove = function(script)
{
    if(!script)
        return;
    if(noisy)print("Deleting "+script);
    ScriptAboutToBeDeletedHook(script);
    delete JSDScript.scripts[script.handle];
    return "";
}

JSDScript.find = function(handle)
{
    return JSDScript.scripts[handle];
}

JSDScript.dump = function(handle)
{
    for(var p in JSDScript.scripts)
        print("JSDScript.scripts['"+p+"'] = "+JSDScript.scripts[p]);
    return "";
}

// in case this file is reloaded, rebuild the script array
jsd.IterateScripts(scriptHook, true);

/* For convienence - overload if you want... */
function ScriptCreatedHook(script)          {}
function ScriptAboutToBeDeletedHook(script) {}

/************************************************/

function JSDFrame(handle)
{
    this.handle = handle;
    this.script = JSDScript.find(jsd.GetScriptForStackFrame(handle));
    this.pc = jsd.GetPCForStackFrame(handle);
    this.lineno = jsd.GetClosestLine(this.script.handle, this.pc);
}

function buildStack()
{
    var count = jsd.GetCountOfStackFrames();
    stack = new Array(count);

    var v = jsd.GetStackFrame();
    var vv = new JSDFrame(v);
    stack[0] = vv;

//    stack[0] = new JSDFrame(jsd.GetStackFrame());
    for(var i = 0; i < count-1; i++)
        stack[i+1] = new JSDFrame(jsd.GetCallingStackFrame(stack[i].handle));
}

function where()
{
    if(! stack)
        print("no stack!");

    for(var i = 0; i < stack.length; i++) {
        print((i == currentFrame ? " >":"  ")+
               "line "+lpad(""+stack[i].lineno, 2)+" of "+stack[i].script);
    }
    return "";
}

function up()
{
    if(currentFrame < stack.length-1)
        currentFrame++ ;
    where();
    writeln("");
    listc();
    writeln("");
    return "";
}

function down()
{
    if(currentFrame > 0)
        currentFrame-- ;
    where();
    writeln("");
    listc();
    writeln("");
    return "";
}


/***************************************************************************/
/* Help System */

var helpLines = new Array;
helpLines.toString = function() {return "[object HelpStrings]";}

function help()
{
    var _
    var _ = "------------type help() to get help--------------";
    var _
    show_help(arguments);
    return "";
}
function show_help(args)
{
    if(args.length == 0) {
        for(var i = 0; i < helpLines.length; i++)
            print(helpLines[i]);
    }
    else {
        // by convention these are going to be the heading lines...
        print(helpLines[0]);
        print(helpLines[1]);
        for(var j = 0; j < args.length; j++) {
            for(var i = 0; i < helpLines.length; i++) {
                if( 0 == String(helpLines[i]).indexOf(args[j]) )
                    print(helpLines[i]);
            }
        }
    }
}

function addHelp(str)
{
    helpLines[helpLines.length] = str;
}

addHelp("Command   Usage                  Description");
addHelp("=======   =====                  ===========");
addHelp("help      help([name ...])       Display usage and help messages");
addHelp("resume    resume()               Leave JSDB and resume program");
addHelp("quit      quit()                 Leave JSDB and terminate script");
addHelp("version   version([number])      Get or set JavaScript version number");
addHelp("load      load(['foo.js' ...])   Load files named by string arguments");
addHelp("print     print([expr,...])      Evaluate and print expressions");
addHelp("write     write(expr)            Evaluate and print one expr. w/o \\n");
addHelp("writeln   writeln(expr)          Evaluate and print one expr. w/ \\n");
addHelp("props     props(object)          Display properties of debugger object");
addHelp("rprops    rprops('object')       Display properties of target object");
addHelp("suspend   suspend()              Send Interrupt");
addHelp("unsuspend unsuspend()            Clear Interrupt");
addHelp("step      step()                 Simple instruction step");
addHelp("why       why()                  Display reason for stopping");
addHelp("where     where()                Show current Stack");
addHelp("up        up()                   Set caller as current frame");
addHelp("down      down()                 Set callee as current frame");
addHelp("show      show('expression')     Evaluate expression in current frame");
addHelp("sources   sources()              Show list of source files");
addHelp("list      list([url,line,count]) Show the source text");
addHelp("listc     listc()                Show current source line with context");
addHelp("listcw    listcw()               Show current source line with wide context");
addHelp("trap      trap([url],lineno)     Set a breakpoint");
addHelp("untrap    untrap([url],lineno)   Clear a breakpoint");
addHelp("safeEval  safeEval('expr'[,url,lineno]) Eval w/o terminate on failure");
addHelp("gets      gets()                 Get string from console");
addHelp("prompt    prompt(str)            Show string and return user input");
addHelp("returnVal returnVal(val)         Resume and force script to return this val");
addHelp("throwVal  throwVal(val)          Resume and force script to throw this val");
addHelp("locals    locals()               Show function local variables");
addHelp("args      args()                 Show function arguments");

/* More can be added anywhere */

/***************************************************************************/
/* Misc */

function pad(str, len)
{
    if(str.length >= len)
        return str;
    /* hideous recursion */
    return pad(str+" ", len);
}

function lpad(str, len, c)
{
    if(str.length >= len)
        return str;
    /* hideous recursion */
    return lpad(" "+str, len);
}

function lzpad(str, len)
{
    if(str.length >= len)
        return str;
    /* hideous recursion */
    return lzpad("0"+str, len);
}

function props(ob)
{
    for(var p in ob)
    {
        var text;
        if(typeof ob[p] == 'function')
            text = "[function "+String(ob[p]).match(/function (\w*)/)[1]+"]";
        else
            text = ob[p];
        print("['"+p+"'] = "+text);
    }
    return "";
}

function unsuspend()
{
    jsd.ClearInterrupt();
    return "";
}

function suspend()
{
    jsd.SendInterrupt();
    return "";
}

function step()
{
    jsd.SendInterrupt();
    resume();
    return "";
}

/***************************************************************************/

function rprops(text)
{
    var name = "___UNIQUE_NAME__";
    var fun = ""+
"for(var p in ob)"+
"{"+
"    var text;"+
"    if(typeof ob[p] == 'function')"+
"        text = \\\"[function \\\"+String(ob[p]).match(/function (\\\\w*)/)[1]+\\\"]\\\";"+
"    else"+
"        text = ob[p];"+
"    if(p!=\\\"___UNIQUE_NAME__\\\")"+
"        print(\\\"['\\\"+p+\\\"'] = \\\"+text);"+
"}"+
"return \\\"\\\";";

    reval(name+" = new Function(\"ob\",\""+fun+"\")");
//    show(name);
    reval(name+"("+text+")");
    reval("delete "+name);

    return "";
}


function show(text,filename,lineno)
{
    print(_reval(arguments));
    return "";
}

function reval(text,filename,lineno)
{
    _reval(arguments);
    return "";
}

function _reval(args)
{
    if(!args.length)
        return print("_reval requires at least one arg");

    var a = new Array(args.length+1);
    a[0] = stack[currentFrame].handle;
    for(var i = 0; i < args.length; i++)
        a[i+1] = args[i];

    return jsd.EvaluateScriptInStackFrame.apply(this,a);
}

function revalToValue(text,filename,lineno)
{
    return _revalToValue(arguments);
}

function _revalToValue(args)
{
    if(!args.length)
        return print("_revalToValue requires at least one arg");

    var a = new Array(args.length+1);
    a[0] = stack[currentFrame].handle;
    for(var i = 0; i < args.length; i++)
        a[i+1] = args[i];

    return new JSDValue(jsd.EvaluateScriptInStackFrameToValue.apply(this,a));
}

function why()
{
    var s;
    var e;

    switch(hookType) {
        case jsd.JSD_HOOK_INTERRUPTED:
            print("hit interrupt hook");
            break;
        case jsd.JSD_HOOK_BREAKPOINT:
            print("hit breakpoint hook");
            break;
        case jsd.JSD_HOOK_DEBUG_REQUESTED:
            print("hit debug break hook");
            break;
        case jsd.JSD_HOOK_DEBUGGER_KEYWORD:
            print("hit debugger keyword hook");
            break;
        case jsd.JSD_HOOK_THROW:
            e = new JSDValue(jsd.GetException());
            if(e)
               s = " => " + e.GetValueString();
            else
                s = "";
            print("hit throw hook"+s);
            break;
        default:
            print("hit unknown hook type!")
    }
    return "";
}

/***************************************************************************/
/* Source functions */
function sourceIterator(handle)
{
    print("  "+jsd.GetSourceURL(handle));
    return true;
}

function sources()
{
    return print(jsd.IterateSources(sourceIterator)+" source files(s) found");
}

function listc()
{
    return list(-2,5);
}

function listcw()
{
    return list(-20,41);
}

function list()
{
    var argsLength = arguments.length;
    var baseArg = 0;
    var filename;
    var lineno = 0;
    var count = 1;
    var usingStack = false;
    if(arguments.length && typeof(arguments[0]) == 'string') {
        filename = arguments[0];
    }
    else {
        filename = stack[currentFrame].script.url;
        usingStack = true;
        baseArg = -1;
    }

    if(arguments.length > baseArg+1) {
        lineno = arguments[baseArg+1];
        if(lineno < 0) {
            if(usingStack) {
                lineno = Math.max(0, stack[currentFrame].lineno) + lineno;
            }
            else {
                return print("can't use a negative lineno on explicit url")
            }
        }
        else if(lineno == 0){
            lineno = 1;
            count = Number.MAX_VALUE/2; /* i.e. a really big number */
        }
        if(arguments.length > baseArg+2) {
            count = arguments[baseArg+2];
            if(count < 1)
                count = Number.MAX_VALUE/2; /* i.e. a really big number */
        }
    }
    else if(usingStack) {
        lineno = stack[currentFrame].lineno;
        count = 1;
    }
    else
        count = Number.MAX_VALUE/2; /* i.e. a really big number */

    listWithFilename(filename, lineno, count);
    return "";
}

function listWithFilename(filename, lineno, count)
{
    var handle = jsd.FindSourceForURL(filename);
    if(handle == null)
        return print("unable to find source for "+filename);

    var fulltext = jsd.GetSourceText(handle);
    var re = new RegExp(".*\n", "g");
    var result;
    var i;
    for(var i = 1; i < lineno+count; i++) {
        if(null == (result = re.exec(fulltext)))
            break;
        if(i >= lineno)
            printLineFormatted(result[0].substr(0,result[0].length-1),
                               filename, i);
        if(0 == re.lastIndex)
            break;
    }
    return "";
}

function printLineFormatted(text, filename, lineno)
{
    var lead0;
    var lead1;

    if(hasBreakpoint(filename, lineno))
        lead0 = "*";
    else
        lead0 = " ";

    if(filename == stack[currentFrame].script.url &&
       lineno == stack[currentFrame].lineno)
        lead1 = ">";
    else
        lead1 = " ";

    print(lead0+lead1+lzpad(""+lineno,4)+": "+text);
    return "";
}

function scripts()
{
    return props(JSDScript.scripts);
}

/***************************************************************************/
/* breakpoints */

function Breakpoint(url, line)
{
    this.url = url;
    this.line = line;
    this.toString = function()
                {return "Breakpoint at line "+this.line+" of "+this.url;}
}

// Only create the breakpoint array the first time we are loaded
if(!this.breakpoints) {
    breakpoints = new Array();
    breakpoints[breakpoints.length] = null;
}

function hasBreakpoint(url, line)
{
    for(var i = 0; i < breakpoints.length; i++)
        if(breakpoints[i] &&
           breakpoints[i].line == line &&
           breakpoints[i].url == url)
            return i;
    return 0;
}

function addBreakpoint(url, line)
{
    if(hasBreakpoint(url, line))
        return print("breakpoint already set at line "+line+" of "+url);
    var bp = new Breakpoint(url, line);
    breakpoints[breakpoints.length] = bp;
    jsd.IterateScripts(breakpointManipulatingCallback, url, line, true);
    return "";
}

function clearBreakpoint(url, line)
{
    var which = hasBreakpoint(url, line);
    if(!which)
        return print("no breakpoint set at line "+line+" of "+url);
    jsd.IterateScripts(breakpointManipulatingCallback, url, line, false);
    delete breakpoints[which];
    return "";
}


function breakpointManipulatingCallback(handle, url, lineno, set)
{
    var script = JSDScript.find(handle);
    if(script && script.url == url &&
       script.base <= lineno && script.base+script.extent > lineno)
    {
        var pc = jsd.GetClosestPC(handle,lineno);
        if(jsd.GetClosestLine(handle, pc) == lineno)
        {
//            print(""+(set?"setting":"clearing")+" trap at line "+lineno+" of "+url);
            if(set)
                jsd.SetTrap(handle, pc);
            else
                jsd.ClearTrap(handle, pc);
        }
    }
    return true;
}

function setAllBreakpointsForScript(handle)
{
    for(var i = 0; i < breakpoints.length; i++)
        if(breakpoints[i])
            breakpointManipulatingCallback(handle, breakpoints[i].url,
                                           breakpoints[i].line, true);
    return "";
}

function bp()
{
    for(var i = 0; i < breakpoints.length; i++)
        if(breakpoints[i])
            print(breakpoints[i]);
    return "";
}

function getUrlAndLineFor(args, o)
{
    var filename;
    var lineno = 0;
    var baseArg = 0;
    var usingStack = false;

    if(args.length && typeof(args[0]) == 'string') {
        o.filename = args[0];
    }
    else {
        o.filename = stack[currentFrame].script.url;
        usingStack = true;
        baseArg = -1;
    }
    if(args.length > baseArg+1) {
        o.lineno = args[baseArg+1];
        return true;
    }
    print("need a line number");
    return false;
}

function trap()
{
    o = new Object();
    if(getUrlAndLineFor(arguments, o))
        addBreakpoint(o.filename, o.lineno);
    return "";
}
function untrap()
{
    o = new Object();
    if(getUrlAndLineFor(arguments, o))
        clearBreakpoint(o.filename, o.lineno);
    return "";
}

/***************************************************************************/

function writeln(str)
{
    write(str+"\n");
    return "";
}

function print()
{
    var i;
    var str = "";
    for(var i = 0; i < arguments.length; i++) {
        str += (i!=0? " ":"")+arguments[i];
    }
    if(i && !(arguments.length == 1 &&
              typeof(arguments[0]) == 'string' &&
              arguments[0] == ""))
        str += "\n";
    write(str);
    return "";
}

var evalLoopLineno = 1;
var evalLoopPrompt = "jsdb"+jsd.DebuggerDepth+">";
var evalLoopFilename = "jsdb_evalLoop"+jsd.DebuggerDepth;

function doEvalLoop()
{
    resumeCode = -1;

    while(-1 == resumeCode) {
        write(evalLoopPrompt);
        var str = gets();
        if(str.length)
            print(safeEval(str+"\n", evalLoopFilename, evalLoopLineno++));
    }
    return resumeCode;
}

function prompt(str)
{
    write(str);
    return gets();
}

function abort()
{
    return resume(jsd.JSD_HOOK_RETURN_ABORT);
}

function quit()
{
    return resume(jsd.JSD_HOOK_RETURN_HOOK_ERROR);
}

function returnVal(val)
{
    jsd.ReturnExpression = val;
    resume(jsd.JSD_HOOK_RETURN_RET_WITH_VAL);
    return "";
}

function throwVal(val)
{
    jsd.ReturnExpression = val;
    resume(jsd.JSD_HOOK_RETURN_THROW_WITH_VAL);
    return "";
}


function resume(code)
{
    if(arguments.length &&
       code >= jsd.JSD_HOOK_RETURN_HOOK_ERROR &&
       code <= jsd.JSD_HOOK_RETURN_CONTINUE_THROW) {
        resumeCode = code;
    }
    else {
        if(hookType == jsd.JSD_HOOK_THROW)
            resumeCode = jsd.JSD_HOOK_RETURN_CONTINUE_THROW;
        else
            resumeCode = jsd.JSD_HOOK_RETURN_CONTINUE;
    }
    return "";
}

/***************************************************************************/
/* property and value objects */

function JSDValueProto()
{
    this.RefreshValue          = function() {this.flags = null; jsd.RefreshValue(this.handle);}
    this.IsValueObject         = function() {return jsd.IsValueObject(this.handle);}
    this.IsValueNumber         = function() {return jsd.IsValueNumber(this.handle);}
    this.IsValueInt            = function() {return jsd.IsValueInt(this.handle);}
    this.IsValueDouble         = function() {return jsd.IsValueDouble(this.handle);}
    this.IsValueString         = function() {return jsd.IsValueString(this.handle);}
    this.IsValueBoolean        = function() {return jsd.IsValueBoolean(this.handle);}
    this.IsValueNull           = function() {return jsd.IsValueNull(this.handle);}
    this.IsValueVoid           = function() {return jsd.IsValueVoid(this.handle);}
    this.IsValuePrimitive      = function() {return jsd.IsValuePrimitive(this.handle);}
    this.IsValueFunction       = function() {return jsd.IsValueFunction(this.handle);}
    this.IsValueNative         = function() {return jsd.IsValueNative(this.handle);}
    this.GetValueBoolean       = function() {return jsd.GetValueBoolean(this.handle);}
    this.GetValueInt           = function() {return jsd.GetValueInt(this.handle);}
    this.GetValueDouble        = function() {return jsd.GetValueDouble(this.handle);}
    this.GetValueString        = function() {return jsd.GetValueString(this.handle);}
    this.GetValueFunctionName  = function() {return jsd.GetValueFunctionName(this.handle);}
    this.GetCountOfProperties  = function() {return jsd.GetCountOfProperties(this.handle);}
    this.GetValuePrototype     = function() {return new JSDValue(jsd.GetValuePrototype(this.handle));}
    this.GetValueParent        = function() {return new JSDValue(jsd.GetValueParent(this.handle));}
    this.GetValueConstructor   = function() {return new JSDValue(jsd.GetValueConstructor(this.handle));}
    this.GetValueClassName     = function() {return jsd.GetValueClassName(this.handle);}
    this.GetValueProperty      = function(name) {return new JSDProperty(jsd.GetValueProperty(this.handle, name));}

    this.GetProperties = function() {
        var a = new Array();
        function cb(ob, prop, a)
        {
//            print(new JSDProperty(prop).GetPropertyName().GetValueString());
            a.push(new JSDProperty(prop));
            return true;
        }
        jsd.IterateProperties(this.handle, cb, a);
        return a;
    }

    this.toString = function()
    {
        return this.flagString() + 
               " : " + 
               this.GetValueString();
    }

    this.flags = null;
    this.flagString = function()
    {
        if(!this.flags)
        {
            this.flags = (this.IsValueObject()    ? "o" : ".") +
                         (this.IsValueNumber()    ? "n" : ".") +
                         (this.IsValueInt()       ? "i" : ".") +
                         (this.IsValueDouble()    ? "d" : ".") +
                         (this.IsValueString()    ? "s" : ".") +
                         (this.IsValueBoolean()   ? "b" : ".") +
                         (this.IsValueNull()      ? "N" : ".") +
                         (this.IsValueVoid()      ? "V" : ".") +
                         (this.IsValuePrimitive() ? "p" : ".") +
                         (this.IsValueFunction()  ? "f" : ".") +
                         (this.IsValueNative()    ? "n" : ".");
        }
        return this.flags;
    }
}

function JSDValue(handle)
{
    if(!handle)
        return null;
    this.handle = handle;
}
JSDValue.prototype = new JSDValueProto;

/*********************************************/

function JSDPropertyProto()
{
    this.JSDPD_ENUMERATE = jsd.JSDPD_ENUMERATE;
    this.JSDPD_READONLY  = jsd.JSDPD_READONLY ;
    this.JSDPD_PERMANENT = jsd.JSDPD_PERMANENT;
    this.JSDPD_ALIAS     = jsd.JSDPD_ALIAS    ;
    this.JSDPD_ARGUMENT  = jsd.JSDPD_ARGUMENT ;
    this.JSDPD_VARIABLE  = jsd.JSDPD_VARIABLE ;
    this.JSDPD_HINTED    = jsd.JSDPD_HINTED   ;

    this.GetPropertyName       = function() {return new JSDValue(jsd.GetPropertyName(this.handle));}
    this.GetPropertyValue      = function() {return new JSDValue(jsd.GetPropertyValue(this.handle));}
    this.GetPropertyAlias      = function() {return new JSDValue(jsd.GetPropertyAlias(this.handle));}
    this.GetPropertyFlags      = function() {return jsd.GetPropertyFlags(this.handle);}
    this.GetPropertyVarArgSlot = function() {return jsd.GetPropertyVarArgSlot(this.handle);}

    this.toString = function()
    {
        return this.flagString() + 
               " : " + 
               this.GetPropertyName().toString() +
               " : " + 
               this.GetPropertyValue().toString();
    }
    this.flags = null;
    this.flagString = function()
    {
        var f;
        if(!this.flags)
        {
            f = this.GetPropertyFlags();
            this.flags = (f & jsd.JSDPD_ENUMERATE ? "e" : ".") +
                         (f & jsd.JSDPD_READONLY  ? "r" : ".") +
                         (f & jsd.JSDPD_PERMANENT ? "p" : ".") +
                         (f & jsd.JSDPD_ALIAS     ? "a" : ".") +
                         (f & jsd.JSDPD_ARGUMENT  ? "A" : ".") +
                         (f & jsd.JSDPD_VARIABLE  ? "V" : ".") +
                         (f & jsd.JSDPD_HINTED    ? "H" : ".");
        }
        return this.flags;
    }
}    

function JSDProperty(handle)
{
    if(!handle)
        return null;
    this.handle = handle;
}

JSDProperty.prototype = new JSDPropertyProto;

/***************************************************************************/

function locals()
{
    _localsOrArgs(true);
    return "";
}        

function args()
{
    _localsOrArgs(false);
    return "";
}        

function _localsOrArgs(loc)
{
    var found = 0;
    var frameHandle = stack[currentFrame].handle;
    var callObjHandle = jsd.GetCallObjectForStackFrame(frameHandle);

    if(callObjHandle)
    {
        var callObj = new JSDValue(callObjHandle);
        if(callObj)
        {
            var props = callObj.GetProperties();
            if(props)
            {
                for(var i = 0; i < props.length; i++)
                {
                    var prop = props[i];
                    var doit = loc ? 
                            prop.GetPropertyFlags() & jsd.JSDPD_VARIABLE :
                            prop.GetPropertyFlags() & jsd.JSDPD_ARGUMENT ;
                    if(doit)
                    {
                        var name = prop.GetPropertyName();
                        var val  = prop.GetPropertyValue();
                        print(name.GetValueString()+" : "+val.GetValueString());
                        found++ ;
                    }
                }
            }
        }
    }

    if(!found)
        print(loc ? "no locals" : "no args");
}



/***************************************************************************/
/* User defined... can use load() ! */

function f() {return load('f.js');}

/**
* function f(){
* //    for(var i = 0; i< 1000; i++) {
*     while(1) {
*         why();
*         show('arguments.length');
*         up()
*         show('arguments.length');
*         up()
*         show('arguments.length');
*         up()
*         show('arguments.length');
*         step();
*     }
* }
*/
/***************************************************************************/
/* Signal Success */

print("successfully loaded debugger.js for depth "+jsd.DebuggerDepth);
