/* CLASS: Virtual Smart File Object
  Provides intelligent behaviors for a VirtFileObject. Add extra cached info
  for caller.  Specialization for browsing source file associated with load module.
  Also, contains breakpoints information to set/clear and display.

  REQUIRE: VIRTDATA.CLS, VIRTFILE.CLS, BKPTINFO.CLS
*/!!

inherit(VirtFileObject, #VirtSmartFileObject, #(
bkptDict      /* breakpoints dict of module */
callersCache  /* save current position */
moduleInfo    /* a ModuleInfo objects - from currentLoadFile */
modulePath    /* file path as opened */
), 2, nil)!!

now(class(VirtSmartFileObject))!!

now(VirtSmartFileObject)!!

/* 7/14/1992 15:06 - PUBLIC (to ancestor) */
Def setModuleTotalLines(self, numLines)
{
  if moduleInfo then
    setSourceLocations(moduleInfo, rememberedLocations);
    setSourceHighestChunk(moduleInfo, highestChunkSeen);
    setTotalSourceLines(moduleInfo, numLines, numLines);
  endif;
}
!!

/* 3/29/1992 13:58 - PUBLIC
  Disable breakpoint that matched with the #(lineNum & (StartChar < colNum < EndChar).
  This also support Statement breakpoint.
*/
Def disableStmtBkpt(self, lineNum, colNum, txtStr | bkptInfo, mapLineNum )
{
  /* Map selected Line to actual symbolic line */
  mapLineNum := mapLineNum(self, lineNum, nil);

  /* Do associate to disable breakpoint */
  assocsDo (bkptDict,
    {using(bkpt)
      /* if mapLineNum == [bkptDescID->#(lineNum, colorTag)] then do it */
      if (mapLineNum = value(bkpt)[0]) then
        if (colNum > 0) then /* Request to remove a statement breakpoint */
          if not(bkptInfo := getSourceBkpt(HLBreakLibClass$Inst, key(bkpt))) then
            ^nil;
          endif;
          /* NOTE: bkptInfo = #(state, life, module, lineNum, colStart, colEnd) */
          if between(colNum, bkptInfo[4], bkptInfo[5]) then
            ^disableBkpt(HLBreakLibClass$Inst, key(bkpt));
          endif;
        else
          /* disable line breakpoint */
          ^disableBkpt(HLBreakLibClass$Inst, key(bkpt));
        endif;
      endif;
    });
  /* Can't find breakpoint */
  reportBkptError(self, SRC_BKPT_DISABLE_BKPT);
  ^nil;
}
!!

/* 3/29/1992 13:58 - PUBLIC
  Enable breakpoint that matched with the #(lineNum & (StartChar < colNum < EndChar).
  This also support Statement breakpoint.
*/
Def enableStmtBkpt(self, lineNum, colNum, txtStr | bkptInfo, mapLineNum)
{
  /* Map selected Line to actual symbolic line */
  mapLineNum := mapLineNum(self, lineNum, nil);

  /* Do associate to enable breakpoint */
  assocsDo (bkptDict,
    {using(bkpt)
      /* if mapLineNum == [bkptDescID->#(lineNum, colorTag)] then do it */
      if (mapLineNum = value(bkpt)[0]) then
        if (colNum > 0) then /* Request to remove a statement breakpoint */
          if not(bkptInfo := getSourceBkpt(HLBreakLibClass$Inst, key(bkpt))) then
            ^nil; /* Can't get bkptInfo */
          endif;
          /* NOTE: bkptInfo = #(state, life, module, lineNum, colStart, colEnd) */
          if between(colNum, bkptInfo[4], bkptInfo[5]) then
            ^enableBkpt(HLBreakLibClass$Inst, key(bkpt));
          endif;
        else
          /* Enable line breakpoint */
          ^enableBkpt(HLBreakLibClass$Inst, key(bkpt));
        endif;
      endif;
    });
  /* Can't find breakpoint */
  reportBkptError(self, SRC_BKPT_ENABLE_BKPT);
  ^nil;
}
!!

/* 3/29/1992 13:58 - PUBLIC
  Remove breakpoint that matched with the #(lineNum & (StartChar < colNum < EndChar).
  This also support Statement breakpoint.
*/
Def clearStmtBkpt(self, lineNum, colNum, txtStr | bkptInfo, mapLineNum )
{
  /* Map selected Line to actual symbolic line */
  mapLineNum := mapLineNum(self, lineNum, nil);

  /* Do associate to remove breakpoint */
  assocsDo (bkptDict,
    {using(bkpt)
      /* if mapLineNum == [bkptDescID->#(lineNum, colorTag)] then remove it */
      if (mapLineNum = value(bkpt)[0]) then
        if (colNum > 0) then /* Request to remove a statement breakpoint */
          if not(bkptInfo := getSourceBkpt(HLBreakLibClass$Inst, key(bkpt))) then
            ^nil;
          endif;
          /* NOTE: bkptInfo = #(state, life, module, lineNum, colStart, colEnd) */
          if between(colNum, bkptInfo[4], bkptInfo[5]) then
            ^removeBkptDesc(self, key(bkpt));
          endif;
        else
          /* Remove line breakpoint */
          ^removeBkptDesc(self, key(bkpt));
        endif;
      endif;
    });
  /* Can't find breakpoint */
  reportBkptError(self, SRC_BKPT_CLR_BKPT);
  ^nil;
}
!!

/* 7/1/1992 11:42 - PUBLIC (to its browser only)
  Map a virtual line to its equivalent object context value (line # in this case).
*/
Def mapLineNum(self, lineNum, ignoreArg | lineInfo )
{
  /* Remap tokenInfo's LineNum to its actual lineNum */
  if not(lineInfo := getLineNumInfo(SymbolLibClass$Inst, moduleDescriptor(self),
     lineNum)) then
     ^lineNum;
  endif;

  /* NOTES: lineInfo = #(addrDesc, lineNum, colNum, lineNumDesc) */
  destroyAddress(AddressLibClass$Inst, lineInfo[0]);

  ^lineInfo[1];
}
!!

/* 7/17/1992 14:53 PRIVATE
  return the address range descriptor of module.
  NOTES: Caller must clean up the return address descriptor when done.
*/
Def addressRange(self)
{
  if not(moduleInfo) then
    ^nil;
  endif;
  ^duplicateAddress(AddressLibClass$Inst, addressRange(moduleInfo));
}
!!

/* 3/25/1992 11:30 - PUBLIC */
Def bkptDict(self)
{
  ^bkptDict;
}
!!

/* 06/20/92 - PUBLIC
  Return the callersCache - last position the caller had remembered.
*/
Def callersCache(self)
{
  ^callersCache;
}
!!

/* 06/20/92 - PUBLIC
  Close self.
*/
Def close(self)
{
  currentExecPoint := nil;
  close(self:ancestor);
  if moduleInfo  then
    setSourceLocations(moduleInfo, rememberedLocations);
    setSourceHighestChunk(moduleInfo, highestChunkSeen);
    setTotalSourceLines(moduleInfo, totalLinesGuess, totalLinesExact);
  endif;
}
 !!

/* 3/24/1992 14:11 - PUBLIC
  Destroy object and its data components.
*/
Def destroyDataObj(self)
{
  if not(closed?) then close(self); endif;
  callersCache := nil;
  bkptDict     := nil; /* NOTES: do not remove breakpoints - free Actor memory */
  moduleInfo   := nil;
  modulePath   := nil;
}
!!

/* 4/6/1992 21:43 - PUBLIC
  Find a breakpoint in self dictionary. return breakpoint ID or nil;
     NOTES: bkptDict = [bkptID->#(lineNum, colorTag)],...
*/
Def findBkptInDict(self, lineNum)
{
  assocsDo(bkptDict,
    {using(bkpt)
      if (value(bkpt)[0] = lineNum) then
        ^key(bkpt);
      endif;
     });
  ^nil;
}
!!

/* 3/25/1992 11:36 - PUBLIC
  Check and see if the input lineNum is a special lines
  (e.g. Breakkpoints setting, and so on).
*/
Def findSpecialLine(self, lineNum | bkptId)
{
  if lineNum cand (size(bkptDict) > 0) then
    /* NOTES: bkptDict = ([bkptDescId->#(lineNum, colorTag)], ...) */
    if (bkptId := findBkptInDict(self, lineNum)) then
      ^(bkptDict[bkptId][1]); /* return the ColorTag */
    endif;
  endif;
  ^nil;
}
!!

/* 06/20/92 - PUBLIC
   If aFile has been reOpen()'ed, cached info may be stale.  The user
   can call this function to flush cached info, or not call it to keep
   cached info.
   NOTES: caller should flush its own callersCache.
*/
Def flushCachedMemory(self)
{
  flushCachedMemory( self:ancestor );
}
!!

/* 3/29/1992 11:47 - PRIVATE
  Get the symbolic information of the input address descriptor -
    NOTES: return tuple: #(moduleDesc, moduleAddrRangeDesc, lineNum, colNum) or nil
*/
Def getAddrInfo(self, addrDesc | addrInfo)
{
  /* Get information */
  if not(addrInfo := addr2lineAndModule(SymbolLibClass$Inst, addrDesc)) then
    ^nil;
  endif;
  /* NOTES: addrInfo = #(moduleDesc, moduleAddrDesc, lineNum, colNum, lineAddrDesc, lineNumDesc) */

  /* destroy the unused address descriptors */
  destroyAddress(AddressLibClass$Inst, addrInfo[4]);
  addrInfo[4] := nil;

  ^tuple(addrInfo[0], addrInfo[1], addrInfo[2], addrInfo[3]);
}
!!

/* 4/2/1992 14:41 - PUBLIC
  Get the current program counter and set its corresponding line in the source.
*/
Def getCurrentExecPoint(self | pcAddr, addrInfo)
{
  if moduleDescriptor(self) cand (pcAddr := getProgramCounter(CpuLibClass$Inst)) then
    if (addrInfo := getAddrInfo(self, pcAddr)) then
      /* NOTES: addrInfo = #(moduleDesc, moduleAddrDesc, lineNum, colNum, nil, lineNumDesc) */
      destroyAddress(AddressLibClass$Inst, addrInfo[1]);
      /* If they are the same module then set current execute point */
      if (addrInfo[0] = moduleDescriptor(moduleInfo)) then
        currentExecPoint := addrInfo[2];
      else
        currentExecPoint := nil;
      endif;
    endif;
    /* Get rid of the unused address */
    destroyAddress(AddressLibClass$Inst, pcAddr);
  endif;
  /* Return either the old or new current execution line */
  ^currentExecPoint;
}
!!

/* 3/24/1992 14:08 - PRIVATE
  initialize a VirtSmartFileObject
*/
Def init(self, newModuleInfo)
{
  /* Allocate holder for breakpoints */
  bkptDict := new(Dictionary, 2);
  clear(bkptDict);
  moduleInfo := newModuleInfo;
  modulePath := filePath(newModuleInfo);
  languageName := languageName(newModuleInfo);
  /* To be able to doule click on assembly code -- 08/29/96 Hera*/
  if(languageName = #Unknown) then
     languageName := #C;
  endif;   
  /* Hera */
  currentExecPoint := nil;
  /* added by cjchen 1996.11.26*/
  if not(size(modulePath)>0)
    ^nil;
  endif;
  /* added by cjchen EOF*/
  ^init(self:ancestor, newModuleInfo);
} 
!!

/* 7/1/1992 11:42 - PUBLIC (to its browser only)
  Map a virtual line to its equivalent object context value (line # in this case).
*/
Def lineNum(self, lineNum, ignoreArg )
{
  ^lineNum;
}
!!

/* 06/20/92 - PUBLIC
  Return the moduleDescriptor of self.
*/
Def moduleDescriptor(self)
{
  if not(moduleInfo) then
    ^nil;
  endif;
  ^moduleDescriptor(moduleInfo);
}
!!

/* 7/22/1992 9:11 - PUBLIC
  Return the moduleInfo of self.
*/
Def moduleInfo(self)
{
  ^moduleInfo;
}!!

/* 06/20/92 - PUBLIC
  Open the text file of the module, then find out about it
*/
Def openFile(self, aFileName | newObj)
{
  if (newObj := openFile(self:ancestor, aFileName )) <> self then
    ^newObj /* error case */
  endif;
  getCurrentExecPoint(self);
  rebuildBkptDict(self);
}
!!

/* 3/25/1992 13:21 - PUBLIC
  Rebuild bkptDict respond to event notification.
     NOTES: bkptDict = [bkptID->#(sourceLineNum, colorTag),...]
*/
Def rebuildBkptDict(self | bkptInfo, bkptResult, loopBkpt )
{
  if not(moduleInfo) then
    displayFormattedError(ErrorTextLibClass$Inst,
       ER_INVALID_DATA_OBJECT, FORCE_POPUP, nil, nil, nil);
    ^nil;
  endif;

  clear(bkptDict);
  loopBkpt := nil; /* initial starting point for the loop */
  loop
  while (bkptResult := getNextSourceBkpt(HLBreakLibClass$Inst,
    moduleDescriptor(moduleInfo), loopBkpt))
    begin
      /* NOTES: bkptResult = #(loopDesc, bkptDesc) */
      loopBkpt := bkptResult[0];
      /* Get breakpoint info to add to dictionary */
      if bkptInfo := getSourceBkpt(HLBreakLibClass$Inst, bkptResult[1]) then
        /* NOTES: bkptInfo = #(state, life, module, lineNum, colStart, colEnd) */
        add(bkptDict, bkptResult[1],
          tuple(bkptInfo[3], getBkptColor(self, bkptInfo[0], bkptInfo[1])) );
      endif;
  endLoop;
  ^size(bkptDict);
}
!!

/* 3/29/1992 13:44 - PUBLIC
  Remove all breakpoints in bkptDict of self.
*/
Def removeAllBkpt(self)
{
  if size(bkptDict) > 0 then
    keysDo (bkptDict,
      {using(bkptId)
        removeBkpt(HLBreakLibClass$Inst, bkptId);
    });
    clear(bkptDict);
  endif;
}
!!

/* 3/29/1992 17:00 - PRIVATE */
Def removeBkptDesc(self, bkptDescId)
{
  remove(bkptDict, bkptDescId);
  ^removeBkpt(HLBreakLibClass$Inst, bkptDescId);
}
!!

/* 06/20/92 - PUBLIC
   Reopen a previously opened file.
*/
Def reOpen(self)
{
  if reOpen(self:ancestor) then
    if not(moduleDescriptor(self)) then
      ^nil;
    endif;
    getCurrentExecPoint(self);
    rebuildBkptDict(self);
    ^self;
  endif;
  ^nil;
}!!

/* 06/20/92 - PUBLIC
   Look for text in file starting at line startLine, column 0.
   If not found, return nil, else return #(line, column).
    NOTES: getTxtFunc is a block context from parent browser.
    RETURN:  #((startLine + line-offset) , char-offset)
*/
Def searchText(self, searchText, startLine, getTxtFunc | lineNum, chunk, pointPos)
{
  chunk := lineNum := startLine;
  if closed? then
    chunk := reOpen(self);  /* if chunk is nil, return nil */
  endif ;

  loop while chunk
  begin
    /* Check Abort */
    if  queryAbort(TaskLibClass$Inst) then
      ^nil;
    endif;
    /* NOTES: chunk = #(startLineNum, textColl) or nil */
    if chunk := getChunk(self, chunkNumberFromLineNumber(self, lineNum)) then
      if (pointPos := findStringInChunk(self, chunk[1],
            searchText, (lineNum - chunk[0]), 0, getTxtFunc)) then
        ^tuple((chunk[0] + y(pointPos)), x(pointPos));
      endif ;
      lineNum := (chunk[0] + linesPerChunk);
    endif ;
   endLoop;

  ^nil
}
!!

/* 3/24/1992 14:19 - PUBLIC
  set a breakpoint to self and save the breakpoint ID to dictionary
*/
Def setBkpt(self, bkptType, lineNum, colNum, txtStr | mapLineNum, addrDesc,
   addrSpace )
{
  /* Map selected Line to actual symbolic line */
  mapLineNum := mapLineNum(self, lineNum, nil);
  /* use the current address range to preserve address information */ 
  if not(addrDesc := addressRange(self)) cor
     not(addrDesc := createAddress(AddressLibClass$Inst)) then
    ^nil;
  endif; 
  /* added by cjchen 1996.11.26*/
  if not(addrSpace:=ldrSpaceMode(LoaderLibClass$Inst)) then
    ^nil;
  endif;
  if (addrSpace=0)
    addrSpace:=6;
  else
    addrSpace:=2;
  endif;
  if not(setAddrSpace(AddressLibClass$Inst,addrDesc,addrSpace)) then
    ^nil;
  endif;
  /* added by cjchen EOF*/     
  /* Begin to set a break point on the mapped selectLine */
  if not(setSourceBkpt(HLBreakLibClass$Inst,
      HLB_BKPT_STATE_ENABLED,                /* state */
      bkptType,                              /* life */
      moduleDescriptor(self),                /* moduleDesc */
      mapLineNum,                            /* lineNum */
      colNum,                                /* column */
      txtStr,
      asLong(addrDesc)))
  then
    ^nil;
  endif;
  /* Let Event Notification update the bkptDict */
  ^GOOD;
}
!!

/* 06/20/92 - PUBLIC */
Def setCallersCache(self, newCacheObject)
{
  ^(callersCache := newCacheObject);
}
!!

/* 06/20/92 - PUBLIC */
Def setModulePath(self, newPath)
{
  modulePath := asLowerCase(newPath);
  if sourceOutOfSynch?(LoaderLibClass$Inst, modulePath) then
    if not(reportOutOfSynch(moduleInfo)) then
      displayFormattedError(ErrorTextLibClass$Inst,
         ER_SOURCE_NEWER, FORCE_POPUP, nil, nil, nil);
    endif;
    modulePath := modulePath+"*";
    setReportOutOfSynch(moduleInfo, #true);
  endif;
}
!!

/* 7/21/1992 11:32 - PUBLIC (to its Parent)
  Set the current PC to the corresponding address of the input line.
  NOTES: return nil if failed.
*/
Def setPC(self, lineNum, ignoreArg1, ignoreArg2 | lineInfo)
{
  /* Map a source line number to its equivalent address */
  if not(lineInfo := getLineNumInfo(SymbolLibClass$Inst, moduleDescriptor(self), lineNum)) then
    ^nil;
  endif;
  /* NOTES: lineInfo = #(addrDesc, lineNum, colNum, lineNumDesc) */
  ^setProgramCounter(CpuLibClass$Inst, lineInfo[0]);
}
!!

/* 06/20/92 - PUBLIC */
Def virtObjTitle(self | len)
{
  /* Truncate the title to be less than 25 characters */
  if (len := size(modulePath)) > 25 then
     ^"..."+subString(modulePath, len - 25, len);
  endif;
  ^modulePath;
}
!!
