2005/5/9

     
 

ScriptDebug.cpp

artefaktur
// -*- mode:C++; tab-width:2; c-basic-offset:2; indent-tabs-mode:nil -*- 
//
// Copyright (C) 2000-2005 by Roger Rene Kommer / artefaktur, Kassel, Germany.
// 
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public License (LGPL).
// 
// 
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the 
// License ACDK-FreeLicense document enclosed in the distribution
// for more for more details.
// This file is part of the Artefaktur Component Development Kit:
//                         ACDK
// 
// Please refer to
// - http://www.acdk.de
// - http://www.artefaktur.com
// - http://acdk.sourceforge.net
// for more information.
// 


#include "Script.h"
#include "ScriptDebug.h"
#include "ScriptException.h"
#include "ChDir.h"
#include "ScriptEval.h"
#include "ScriptExpr.h"
#include <acdk/lang/System.h>

namespace acdk {
namespace cfgscript {

using namespace acdk::lang::dmi;

bool 
FunctionEnterDebugPoint::doBreak(int action, IN(RExecutionStackFrame) frame)
{
  if ((action & DRAEnterFunction) == 0)
    return false;
  if (frame->_currentClazzMethodInfo == 0)
    return  false;
  const ClazzMethodInfo* mi = frame->_currentClazzMethodInfo;
  RString currentMethod = mi->toTypeString((const ClazzInfo*)mi->_scopeParent, TpFtJavaType | TpFtFqName | TpFtTypeDef);
  if (currentMethod->indexOf(_functionName) == -1)
    return  false;
  System::out->println("Break [" + _functionName + "] breaked at: " + currentMethod);
  // TODO break now
  return true;
}

bool  
SourceLineDebugPoint::doBreak(int action, IN(RExecutionStackFrame) frame)
{
  if (frame->getSourceLine() != _sourceLine)
    return false;
  if (_sourceName == Nil)
    return true;
  if (frame->getSourceFile()->indexOf(_sourceName) != -1)
    return true;
  return false;
}


//static 
RSourceLineDebugPoint 
SourceLineDebugPoint::parse(IN(RString) ident)
{
  if (ident == Nil || ident->length() == 0)
    return Nil;
  int idx = ident->indexOf(':');
  if (idx == -1)
  {
    RNumber num = Number::parseToIntegerNumber(ident, 10, true);
    if (num == Nil)
      return Nil;
    return new SourceLineDebugPoint(Nil, num->intValue());
  }
  RString source = ident->substr(0, idx);
  RString line = ident->substr(idx + 1);
  RNumber num = Number::parseToIntegerNumber(line, 10, true);
  if (num == Nil)
    return Nil;
  return new SourceLineDebugPoint(source, num->intValue());
}

ThrowExceptionDebugPoint::ThrowExceptionDebugPoint(IN(RString) exName)
: _exceptionName(exName)
{
  if (_exceptionName == Nil)
    _exceptionName = String::emptyString();
  Throwable::registerThrowListener(this);
}
ThrowExceptionDebugPoint::~ThrowExceptionDebugPoint()
{
  Throwable::unregisterThrowListener(this);
}

bool 
ThrowExceptionDebugPoint::onThrow(IN(RThrowable) ex, int line, IN(RString) file)
{
  bool doBreak = false;
  if (_exceptionName->length() == 0)
    doBreak = true;
  else
  {
    RString throwEx = ex->getClass()->getName();
    if (throwEx->indexOf(_exceptionName) != -1)
      doBreak = true;
  }
  if (doBreak == false)
    return true;
  RExecutionStackFrame frame = ExecutionStack::get()->top();
  frame->_scopeProps->setObjectVal("__activeException", &ex);
  
  PEStack stack(frame->_script, frame->_scopeProps);
  DebugNextAction action = DebugBreakPoints::get()->getDebugger()->onBreak(DRAThrowException, stack, frame);
  if (action == DbgNANoThrow)
    return false;
  return true;
}

WalkDebugPoint::WalkDebugPoint(IN(RExecutionStack) stack, DebugNextAction nextAction)
: _frame(stack->top())
, _nextAction(nextAction)
, _sourceLine(stack->top()->getSourceLine())
{
  if (_nextAction == DbgNAUntilReturn)
  {
    _frame = stack->getFrameFromTop(1);
  }
}

bool  
WalkDebugPoint::doBreak(int action, IN(RExecutionStackFrame) frame)
{
  if (_doBreakInternal(action, frame) == false)
    return false;
  DebugBreakPoints::get()->removeBreakpoint(this);
  return true;
}

bool isFrameInStack(IN(RExecutionStackFrame) frame)
{
  RExecutionStackFrameArray frames = ExecutionStack::get()->getFrames();
  for (int i = 0; i < frames->length(); ++i)
  {
    if (frames[i] == frame)
      return true;
  }
  return false;
}

bool 
WalkDebugPoint::_doBreakInternal(int action, IN(RExecutionStackFrame) frame)
{
  if (_nextAction == DbgNAStepOver)
  {
    if (_frame != frame)
    {
      if (isFrameInStack(_frame) == true)
        return false;
      // returned since _frame
      return true;
    }
      
    if (frame->getSourceLine() != _sourceLine)
      return true;
    return false;
  }
  else if (_nextAction == DbgNAStepInto)
  {
    if (_frame != frame)
      return true;
    if (frame->getSourceLine() != _sourceLine)
      return true;
    return false;
  }
  else if (_nextAction == DbgNAUntilReturn)
  {
    if (_frame == frame)
      return true;
    return false;
  }
  return false;
}


void debugHelp()
{
  System::out->println(
    "Debugging commands:\n"
    "running:\n"
    "c|continue           continue running\n"
    "n|next               execute next line\n"
    "s|step               next statement (step into functions)\n"
    "r|return             run until return\n"
    "q|quit               quit debugging/interpreter\n"
    
    "\nBreakpoints:\n"
    "br[f|l] <cmd> [<substr>] break at function or line\n"
    "    cmd: new|list|delete\n"
    "bre <cmd> [<ExceptionType>] break at throwing exception\n"
    
    "\nView Data:\n"
    "bt                   print backtrace\n"
    "l|list               view source\n"
    "df|dumpframe [frame] dump current scope or scope of frame number\n"
    "de|dumpenv           dump current environment\n"
    "trace [on|off]       activate or deactivate tracing\n"

    "\nview/modify:\n"
    "p|print <expr>       print expression\n"
    "i|input              interactive mode\n"
    
    "\n"
    "(Please refer to the CfgScript handbook for more information)\n"

  );
}

void printSourceLoc(PEStack& stack, int before = 0, int after = 0)
{
  RScript script = stack.script;
  int curTokenIdx = stack.getCurTokenIndex();
  //int curToken2 = ExecutionStack::getCurrentTokenIndex();
  //curToken = curToken2;
  RTokenizedSource tin = script->getTokenized();

  SourceToken& st = tin->getSourceToken(curTokenIdx);
  int lineno = st.sourcePos.linePos;
  int i;
  RString l;
  for (i = before; i >= 1; --i)
  {
    l = tin->getCodeOfLine(lineno - i);
    if (l != Nil)
    {
      System::out->println(SBSTR(lineno - i << ": " << l));
    }
  }
  l = tin->getCodeOfLine(lineno);
  l = l->trim();
  System::out->println("! " + l);
  for (i = 1; i < after; ++i)
  {
    l = tin->getCodeOfLine(lineno + i);
    if (l != Nil)
    {
      l = l->trim();
      System::out->println(SBSTR(lineno + i << ": " << l));
    }
  }
} 


void _getKeys(::acdk::util::TreeSet& keys, IN(RProps) scope, IN(RProps) frame)
{
  acdk::util::RIterator it = scope->keys();
  while (it->hasNext() == true)
  {
    keys.add(it->next());
  }
  if (scope == frame)
    return;
  RPropsArray pa = scope->getParentsProps();
  if (pa == Nil)
    return;
  for (int i = 0; i < pa->length(); ++i)
  {
    _getKeys(keys, pa[i], frame);
  }
}

void dumpProps(IN(RProps) scope, IN(RProps) frame)
{
  ::acdk::util::TreeSet keys;
  
  _getKeys(keys, scope, frame);
  
  int flags = PropsParentWrite;
  // copy from Props.cpp
  acdk::util::RIterator it = keys.iterator();
  RString ident = "  ";
  while (it->hasNext() == true)
  {
    RString k = RString(it->next());
    RDmiObject v = scope->get(k, flags);
    if (v->isStringType() == true)
    {
      System::out->println(ident + k + "=[\"" + v->toString() + "\"]");
    }
    /*else if (v->isObjectType() == true && instanceof(v->getObjectVar(), Props) == true)
    {
      
      if (dumpFlags & DumpWithChilds ||
          ((dumpFlags & DumpWithParent) && k->equals("_parent") == true))
      {
      if (usedkeys == Nil || k->equals("_parent") == true || usedkeys->contains(&k) == false)
      {
        if (usedkeys == Nil)
          usedkeys = new acdk::util::TreeSet();
        usedkeys->add(&k);
        System::out->println(ident + k + "=[");
        RProps(v->getObjectVar())->dump(dumpFlags, usedkeys, ident + "  ");
        System::out->println(ident + "]");
      }
      }
      
    }
    else*/
      System::out->println(ident + k + "=(" + v->getClass()->getName() + ")[" + v->toString() + "]");
  }

}

void dumpFrameProps(IN(RExecutionStackFrame) frame)
{
  dumpProps(frame->_scopeProps, frame->_frameProps);

}

void dumpProps(IN(RProps) props)
{
  props->dump();
}




OUT(RDebugBreakPoints) 
DebugBreakPoints::get()
{
  ACDK_STATIC_INSTANCE0(DebugBreakPoints, _breakPoints);
  return _breakPoints;
}

void traceCurLine(PEStack& stack)
{
  RScript script = stack.script;
  RTokenizedSource tin = script->getTokenized();
  int lineno = stack.curSourceToken().sourcePos.linePos;
  RString l = tin->getCodeOfLine(lineno + 1);
  if (l != Nil)
  {
    System::out->println(SBSTR(lineno + 1 << ": " << l));
  }
}
//foreign virtual 
bool 
ConsoleDebugger::doBreak(int action, PEStack& stack)
{
  return DebugBreakPoints::checkBreakPoints(action, stack);
}

//static 
bool 
DebugBreakPoints::doBreak(int action, PEStack& stack)
{
  RDebugger dbg = get()->_debugger;
  if (dbg == Nil)
    return false;
  bool erg =  dbg->doBreak(action, stack);
  if (erg == false)
    return false;
  return DebugBreakPoints::get()->onBreak(action, stack, ExecutionStack::getTop());
}

bool 
DebugBreakPoints::checkBreakPoints(int action, PEStack& stack)
{
  int debugFlags = ExecutionStack::getDebugFlags();
  if (debugFlags & DbgExitAll)
    return false;
  RExecutionStackFrame frame  = ExecutionStack::getTop();
  RDebugBreakPoints brk = get();
  if (brk->_wantBreak(action, frame) == true)
  {
    return true;
  }
  int dbgFlags = ExecutionStack::getDebugFlags();
  if (dbgFlags & DbgPrintEachLine)
  {
    traceCurLine(stack);
  }
  return false;
}

bool 
DebugBreakPoints::doBreakOnBreakPoint(int action, IN(RExecutionStackFrame) frame)
 {
  if (_debugPoints->length() == 0)
    return false;
  if (frame->_executionFlags & ESFFirstStatement)
    action |= DRAEnterFunction;

  for (int i = 0; i < _debugPoints->length(); ++i)
  {
    if (_debugPoints[i]->doBreak(action, frame) == true)
      return true;
  }
  return false;
}

RDebugPoint
DebugBreakPoints::removeBreakpoint(IN(RString) ident)
{
  RDebugPointArray da =  getBreakPoints();
  for (int i = 0; i < da->length(); ++i)
  {
    if (da[i]->isBreakPoint(ident) == true)
    {
      RDebugPoint ret = da[i];
      da->remove(i);
      return ret;
    }
  }
  return Nil;
}
void 
DebugBreakPoints::removeBreakpoint(IN(RDebugPoint) dbgPoint)
{
  RDebugPointArray da =  getBreakPoints();
  for (int i = 0; i < da->length(); ++i)
  {
    if (da[i] == dbgPoint)
    {
      da->remove(i);
      return;
    }
  }
}

bool 
DebugBreakPoints::_wantBreak(int action, IN(RExecutionStackFrame) frame)
{
  int dbgFlags = ExecutionStack::getDebugFlags();
  if (dbgFlags & DbgBreakStatements && action & DRANextStatement)
    return true;
  if (doBreakOnBreakPoint(action, frame) == true)
  {
    return true;
  }
  return false;
}

void interactive(PEStack& stack)
{
  System::out->println("enter code and end it with a single . in the line");
  StringBuffer sb;
  while (true)
  {
    RString inp = System::in->readLine();
    if (inp->equals(".") == true)
      break;
    sb << inp;
  }
  RScript dbgscript = new Script("<debug>", stack.script);
  int debugFlags = ExecutionStack::getDebugFlags();
  debugFlags &= ~DbgBreakStatements;
  ScopedDbgFlags scopeDbg(debugFlags);
  dbgscript->eval(sb.toString(), stack.props, PropsParentRead | PropsParentWrite);
}

namespace {
bool getCmdLine(IN(RString) line, IN(RString) shortCmd, IN(RString) longCmd, OUT(RString) argument, bool hasArg, bool trimArg)
{
  if (hasArg == false)
  {
    if (line->trim()->equals(shortCmd) == true)
      return true;
    if (longCmd != Nil && line->trim()->equals(longCmd) == true)
      return true;
  }
  bool found = false;
  if (line->startsWith(shortCmd) == true)
  {
    argument = line->substr(shortCmd->length());
  }
  else if (longCmd != Nil && line->startsWith(longCmd) == true)
  {
    argument = line->substr(longCmd->length());
  }
  else
    return false;
  if (trimArg == true)
    argument = argument->trim();
  return true;
}
} // anon namespace

bool
DebugBreakPoints::onBreak(int action, PEStack& stack, IN(RExecutionStackFrame) frame)
{
  if (_debugger == Nil)
    return false;
  
  DebugNextAction nextAction = _debugger->onBreak(action, stack, ExecutionStack::getTop());
  setContinue();
  if (nextAction == DbgNAContinue)
    return false;
  else if (nextAction == DbgNAStepOver || nextAction == DbgNAStepInto || nextAction == DbgNAUntilReturn)
  {
    setContinue();
    DebugBreakPoints::get()->addBreakPoint(new WalkDebugPoint(ExecutionStack::get(), nextAction));
    return true;
  }
  else if (nextAction == DbgTerminate)
  { 

    ExecutionStack::get()->addDbgFlag(DbgExitAll);
    return true;
  }
  return false;
}

DebugNextAction
ConsoleDebugger::onBreak(int action, PEStack& stack, IN(RExecutionStackFrame) frame)
{
    RScript script = stack.script;
    RDebugBreakPoints dbg = DebugBreakPoints::get();
    dbg->setBreakEachStmt();
    if (action == DRAThrowException)
    {
      RThrowable ex = (RThrowable)stack.props->getObjectVal("__activeException");
      System::out->println("Active Exception: " + ex->getClass()->getName() + ": " + ex->getMessage());
    }
    static RString lastInput = "";
nextLine:
    printSourceLoc(stack, 3, 0);
    
    while (true)
    {
      
nextInput:
    try {
      System::out->print("> ");
      System::out->flush();
      RString inp = System::in->readLine();
      if (inp->length() == 0)
        inp = lastInput;
      lastInput = inp;
      RString argument;
      if (getCmdLine(inp, "c", "continue", argument, false, true) == true)
      {
        DebugBreakPoints::get()->setContinue();
        return DbgNAContinue;
      }
      else if (getCmdLine(inp, "s", "step", argument, false, true) == true)
      {
        return DbgNAStepInto;
      }
      else if (getCmdLine(inp, "n", "next", argument, false, true) == true)
      {
        //dbg->addDbgFlag(DbgStepOverNextStmt);
        return DbgNAStepOver;
      }
       else if (getCmdLine(inp, "r", "return", argument, false, true) == true)
      {
        return DbgNAUntilReturn;
      }
      else if (getCmdLine(inp, "bt", "backtrace", argument, false, true) == true)
      {
        System::out->println(Script::getScriptBackTrace());
        goto nextInput;
      }
      else if (getCmdLine(inp, "l", "list", argument, false, true) == true)
      {
        printSourceLoc(stack, 5, 5);
        continue;
      }
      else if (getCmdLine(inp, "p ", "print ", argument, true, true) == true)
      {
        RString expr = argument;
        if (stack.props->hasValue(expr) == true)
        {
          if (instanceof(stack.props->get(expr)->getObjectVar(), Props) == true)
          {
            //RProps(stack.props->get(expr)->getObjectVar())->dump();
            dumpProps(RProps(stack.props->get(expr)->getObjectVar()));
            continue;
          }
        }
        expr = "acdk.lang.System.out.println((" + expr + ").toString());";
        RScript dbgscript = new Script("<debug>", stack.script);
        ScopedDbgFlags _nobreak(ExecutionStack::getDebugFlags() & ~DbgBreakStatements);
        dbgscript->eval(expr, stack.props, PropsParentRead | PropsParentWrite);
        continue;
      }
      else if (getCmdLine(inp, "df", "dumpframe", argument, true, true) == true)
      {
        RString num = argument;
        if (num == Nil || num->length() == 0)
        {
          dumpFrameProps(frame);
        }
        else
        {
          try {
            int st = Integer::parseInt(num);
            RExecutionStackFrame sf = ExecutionStack::get()->getFrameFromTop(st);
            if (sf == Nil)
            {
              System::out->println("index out of bounds");
            }
            else
              dumpFrameProps(sf);
          } catch (RNumberFormatException ex) {
            System::out->println(ex->getMessage());
          }
        }
        goto nextInput;
      }
      else if (getCmdLine(inp, "de", "dumpenv", argument, false, true) == true)
      //else if (inp->equals("de") == true)
      {
        stack.props->dump();
        goto nextInput;
      }
      else if (getCmdLine(inp, "brf ", Nil, argument, true, false) == true ||
              getCmdLine(inp, "brl ", Nil, argument, true, false) == true  ||
              getCmdLine(inp, "bre ", Nil, argument, true, false) == true )
      //else if (inp->startsWith("brf ") == true)
      {
        bool isBreakFunc = false;
        bool isBreakEx = false;
        if (inp->startsWith("brf") == true)
          isBreakFunc = true;
        if (inp->startsWith("bre") == true)
          isBreakEx = true;

        int idx = argument->indexOf(' ');
        RString arg;
        RString cmd = argument;
        if (idx != -1)
        {
          cmd = argument->substr(0, idx);
          arg = argument->substr(idx + 1);
        }
        if (cmd->equals("list") == true)
        {
          RDebugPointArray da =  DebugBreakPoints::get()->getBreakPoints();
          for (int i = 0; i < da->length(); ++i)
          {
            System::out->println(da[i]->toString());
          }
        }
        else if (cmd->equals("new") == true)
        {
          RDebugPoint dbp = Nil;
          if (isBreakFunc == true)
          {
            dbp = new FunctionEnterDebugPoint(arg);
            DebugBreakPoints::get()->addBreakPoint(dbp);
          }
          else if (isBreakEx == true)
          {
            dbp = new ThrowExceptionDebugPoint(arg);
            DebugBreakPoints::get()->addBreakPoint(dbp);
          }
          else
          {
            RSourceLineDebugPoint brp = SourceLineDebugPoint::parse(arg);
            if (brp == Nil)
            {
              System::out->println("Cannot create source line breakpoint, wrong format: " + arg);
              goto nextInput;
            }
            else
            {
              DebugBreakPoints::get()->addBreakPoint(&brp);
              dbp = &brp;
            }
          } 
          if (dbp != Nil)
          {
            System::out->println("Inserted new Debug break point [" + dbp->getIdentifier() + "]");
          }
        }
        else if (cmd->equals("delete") == true)
        {
          if (arg == Nil || arg->length() == 0)
          {
            DebugBreakPoints::get()->getBreakPoints()->resize(0);
            System::out->println("delete all break points");
          }
          else
          {
            RDebugPointArray da =  DebugBreakPoints::get()->getBreakPoints();
            for (int i = 0; i < da->length(); ++i)
            {
              if (da[i]->isBreakPoint(arg) == true)
              {
                System::out->println("Delete break point: " + da[i]->toString());
                da->remove(i);
                --i;
              }
            }
          }
        }
        else
        {
          System::out->println("unknown cmd for brf|brl|bre");
        }
        goto nextInput;
      }
      else if (inp->equals("trace off") == true)
      {
        dbg->removeDbgFlag(DbgPrintEachLine);
        continue;
      }
      else if (inp->equals("trace on") == true)
      {
        dbg->addDbgFlag(DbgPrintEachLine);
        continue;
      }
      else if (getCmdLine(inp, "i", "input", argument, false, false) == true)
      //else if (inp->equals("i") == true)
      {
        interactive(stack);
      }
      else if (getCmdLine(inp, "q", "quit", argument, false, false) == true)
      //else if (inp->equals("q") == true)
      {
        //dbg->addDbgFlag(DbgExitAll);
        return DbgTerminate;
      }
      else if (getCmdLine(inp, "_dumptoken", "_dumptoken", argument, false, false) == true)
      {
       
        RTokenizedSource tin = script->getTokenized();
        System::out->println(tin->_dumpTokens());
      } 
      else
      {
        debugHelp();
      }
    } catch (RThrowable ex) {
      System::out->println("Exception in evaluation: " + ex->getMessage());
    }
    }
  return DbgNAContinue; // never reached
}


} // namespace cfgscript
} // namespace acdk