/* CLASS: VIRTSMRTDASMOBJECT
    Provides intelligent behaviors for a VirtDasmObject.  Specialization for
    browsing disassembly text associated with an address range.  Also,
    contains breakpoints information to set/clear and display.
    Used by SOURCECB.

    REQUIRE: VIRTDATA.CLS, VIRTDASM.CLS
*/!!

inherit(VirtDasmObject, #VirtSmrtDasmObject, #(moduleDescriptor
callersCache /* save current Position  */
bkptDict /* breakpoints dictionary */), 2, nil)!!

now(class(VirtSmrtDasmObject))!!

now(VirtSmrtDasmObject)!!

/* 6/6/1994 13:00 - PRIVATE
  return nil if lastLineTxt contains address < searchAddrDesc.
  NOTES: caller responsible for destroying input address descriptors.
*/
Def skipChunk(self, lastLineTxt, dasmAddr, searchAddrDesc | result) {
  /* convert the address text to an address descriptor */
  if (mapLineUsingAddrDesc(self, nil, lastLineTxt, dasmAddr) cand
     (result := compareAddressesNoError(AddressLibClass$Inst, 
      dasmAddr, searchAddrDesc))) then
    if (result = ADRLIB_ADDR_LESS_THAN) 
      ^result; /* skip this chunk */
    endif;                 
  endif;
  /* do not skip chunk */
  ^nil;
}
!!

/* 3/11/1994 5:25 - PUBLIC (to its parent) */
Def setCursorLinked(self, newLinked)
{ 
  clearCursorLinked(self);  
  cursorLinked := newLinked;
}
!!

/* 3/11/1994 11:27 - PUBLIC (to its browser)
  Check if there is a breakpoint setting at the input <addrDesc> then clear it.
  Else set a breakpoint at the input <addrDesc>.
  NOTES:  This method consumes the <addrDesc>.
*/
Def bkptSetOrClear(self, bkptType, addrDesc, selTxt | bkptId)
{ 
  if (bkptId := findBkptInDict(self, addrDesc)) then
    /* destroy the input <addrDesc> */
    destroyAddress(AddressLibClass$Inst, addrDesc);
    ^removeBkptDesc(self, bkptId);
  else
    /* check for instruction boundary */
    if (isInstrBoundAddress?(self, selTxt)) then
       /* Set breakpoint to server - Server comsumes addrDesc */
      ^setAsmBkpt(HLBreakLibClass$Inst,
                  HLB_BKPT_STATE_ENABLED, /* state: enable */
                  bkptType,               /* life: Permanent | Temporary */
                  addrDesc);              /* address descriptor */
    endif;
    /* Cannot set breakpoint on an non-instruction boundary */
    destroyAddress(AddressLibClass$Inst, addrDesc);    
  endif;  
  ^nil;
}
!!

/* 7/1/1992 11:09 - PUBLIC
  Reset the currentExecPoint of self to nil.
*/
Def clearCurrentExecPoint(self)
{
  if (currentExecPoint) then
    destroyAddress(AddressLibClass$Inst, currentExecPoint);
    currentExecPoint := nil;
  endif;  
  ^currentExecPoint;
}
!!

/* 7/1/1992 11:38 - PUBLIC (to its browser only)
  Convert the address of the input <txtStr> into the specified <addrDesc>.
  Caller must provides the <addrDesc>.  
  Return GOOD if successful, else return nil.
  NOTES:
    - This method will not destroy the <addrDesc> in any case.
*/
Def mapLineUsingAddrDesc(self, ignore, txtStr, addrDesc | addrTxt)
{
  /* <txtStr> = "###### MOV D0, D1" */
 if (addrTxt := getAddressField(self, txtStr)) then
   ^convertTextToAddressWithAddrDesc(AddressLibClass$Inst, addrTxt, addrDesc);
 endif;
 ^nil;   
}
!!

/* 7/1/1992 11:38 - PUBLIC (to its browser only)
  Convert the address text of the input <txtStr> into and Address Descriptor.
  Return the address descriptor if successful, else return nil.
  NOTES: 
    - Caller must destroy the returned <addrDesc> when done
*/
Def mapLine(self, ignoreArg, txtStr | addrDesc)
{
  /* map the txtStr contains address offset to a descriptor */
  if not(addrDesc := duplicateAddress(AddressLibClass$Inst, aMemoryRange)) then
    ^nil;
  endif;
  if not(mapLineUsingAddrDesc(self, ignoreArg, txtStr, addrDesc)) then
    /* failed to map, destroy the created addrDesc */ 
    destroyAddress(AddressLibClass$Inst, addrDesc);
    ^nil;  
 endif;
 ^addrDesc;   
}
!!

/* 3/9/1994 11:34 - PRIVATE
  Destroy all address descriptor of breakpoint set in data object.
  clear the content of the dictionary.
  NOTES: bkptDict = ([bkptID->#(addrDesc, colorTag)],...)
*/
Def clearBkptDict(self)
{ 
  if (size(bkptDict) > 0) then
    assocsDo (bkptDict,
      {using(bkpt)
        /* destroy the breakpoint address */
        destroyAddress(AddressLibClass$Inst, value(bkpt)[0]);
      });
    clear(bkptDict);
  endif;
}
!!

/* 3/29/1992 13:58 - PUBLIC
   Disable the specified breakpoint.
*/
Def disableStmtBkpt(self, lineNumIgnored, colNumIgnored, txtStr 
   | addrDesc, bkptId)
{
  if (addrDesc := mapLine(self, nil, txtStr)) then
    /* find Bkpt in bkptDict */
    bkptId := findBkptInDict(self, addrDesc);
    destroyAddress(AddressLibClass$Inst, addrDesc);
    if (bkptId) then
      ^disableBkpt(HLBreakLibClass$Inst, bkptId);
    endif;
  endif;
  /* Can't find breakpoint */
  reportBkptError(self, SRC_BKPT_DISABLE_BKPT);
  ^nil;
}
!!

/* 3/29/1992 13:58 - PUBLIC
   Enable the specified breakpoint.
*/
Def enableStmtBkpt(self, lineNumIgnored, colNumIgnored, txtStr 
   | addrDesc, bkptId)
{
  if (addrDesc := mapLine(self, nil, txtStr)) then
    /* find Bkpt in bkptDict */
    bkptId := findBkptInDict(self, addrDesc);
    destroyAddress(AddressLibClass$Inst, addrDesc);
    if (bkptId) then
      ^enableBkpt(HLBreakLibClass$Inst, bkptId);
    endif;
  endif;
  /* Can't find breakpoint */
  reportBkptError(self, SRC_BKPT_ENABLE_BKPT);
  ^nil;
}
!!

/* 3/29/1992 13:58 - PUBLIC
   Clear the specified breakpoint from the breakpoint dictionary.
*/
Def clearStmtBkpt(self, lineNumIgnored, colNumIgnored, txtStr 
   | bkptId, addrDesc)
{
  if (addrDesc := mapLine(self, nil, txtStr)) then
    /* find Bkpt in bkptDict */
    bkptId := findBkptInDict(self, addrDesc);
    destroyAddress(AddressLibClass$Inst, addrDesc);
    if (bkptId)
      ^removeBkptDesc(self, bkptId);
    endif;
  endif;
  /* Can't find breakpoint */
  reportBkptError(self, SRC_BKPT_CLR_BKPT);
  ^nil;
}
!!

/* 06/20/92 - PUBLIC
  Search the dataObject for the virtual line number of the <searchAddrDesc>.
  Return lineNumber or nil.
  NOTES: do not destroy the input address descriptor - <searchAddrDesc>
*/
Def searchAddress(self, searchAddrDesc, startLine 
  | lineNum, chunk, chunkNum, dasmAddr, bestSoFar, result, err, lastLine) {
  chunk := lineNum := bestSoFar := startLine;
  /* If self is close then  need to reopen the dataObject */
  if closed? then
    chunk := reOpen(self) ;  /* if chunk is nil, return nil */
  endif ;

  /* Create a temporary address descriptor to compare */
  if not(dasmAddr := addressRange(self)) then ^nil; endif;
  /* DEBUG 
  printLine("Search: "+asAddr(AddressLibClass$Inst, searchAddrDesc));
  */
  loop while chunk
  begin
    chunkNum := chunkNumberFromLineNumber(self, lineNum);
    /* Get data and search for it */
    if (chunkNum cand (chunk := getChunk( self, chunkNum))) then
      if (errReported(self) <> GOOD)
        destroyAddress(AddressLibClass$Inst, dasmAddr);      
        ^nil;
      endif;
      if not(chunk[1]) cor (size(chunk[1]) = 0) ^nil; endif;   
      /* NOTES: chunk = #(startLineNum, textColl) or nil */
      lastLine := max(size(chunk[1])-1, 0);
      if (result := skipChunk(self, chunk[1][lastLine], dasmAddr, searchAddrDesc)) then
        /* last dasmAddr < searchAddr - Skip the chunk */
        bestSoFar := chunk[0] + lastLine;          
      else
        /* Loop over the chunk and check for a match */
        do (overBy(0, size(chunk[1]), 1),
        {using(i)
          /* convert the address text to an address descriptor */
          if mapLineUsingAddrDesc(self, nil, chunk[1][i], dasmAddr) then
            if not(result := compareAddressesNoError(AddressLibClass$Inst, 
                dasmAddr, searchAddrDesc)) then
              /* DEBUG */  
              if ((err := lastError(AddressLibClass$Inst)) cand (err <> GOOD))
                displayFormattedError(ErrorTextLibClass$Inst, 
                  err, CHECK_MODE, nil, nil, nil);
              endif;  
              ^nil;
            endif;            
            /* DEBUG 
            print(" =  "+asAddr(AddressLibClass$Inst, dasmAddr));
            */ 
            if (result = ADRLIB_ADDR_EQUAL) then
              /* dasmAddr == searchAddr - Return (start-line + line-offset) */
              destroyAddress(AddressLibClass$Inst, dasmAddr);
              ^(chunk[0] + i);
            endif;   
            if (result = ADRLIB_ADDR_GREATER_THAN) then
              /* dasmAddr > searchAddr - Return the closest one so far  */
              destroyAddress(AddressLibClass$Inst, dasmAddr);
              ^bestSoFar;
            endif;
            if (result = ADRLIB_ADDR_LESS_THAN) then
              /* dasmAddr < searchAddr - Save the closest line to bestSoFar */
              bestSoFar := chunk[0] + i;
            endif;
          endif;  
        });
      endif;  
      /* Only move to the next chunk if the last compared result is < */
      if (result = ADRLIB_ADDR_LESS_THAN) then 
        /* NOTES: rememberedLocations = 
        ** #(startOffset,numLinesInChunk,startLineOfChunk) 
        */
        lineNum := (chunk[0] + rememberedLocations[chunkNum][1]) + 1;
      else
        chunk := nil; /* bail out */  
      endif;  
    endif;
   endLoop;
  /* Can't find addr...return closest match */
  destroyAddress(AddressLibClass$Inst, dasmAddr);
  ^bestSoFar;  
}
!!

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

/* 6/26/1992 9:45 - PUBLIC
  Return breakpoint dictionary.
*/
Def bkptDict(self)
{
  ^bkptDict;
}
!!
/* 6/26/1992 9:47 -PUBLIC
  Return the callerCache - last position the caller had remembered.
*/
Def callersCache(self)
{
  ^callersCache;
}
!!

/* 6/26/1992 9:53 - PUBLIC
  Close self.
*/
Def close(self)
{
  clearBkptDict(self);
  clearCurrentExecPoint(self);
  clearCursorLinked(self);
  close(self:ancestor);
}
!!

/* 6/26/1992 9:50 - PUBLIC
  Destroy object and its data components.
*/
Def destroyDataObj(self)
{
  if not(closed?) then close(self); endif;
  callersCache := nil;
  /* NOTES: do not remove bkpt from server - free Actor memory */
  bkptDict     := nil; 
  languageName := nil;
  /* Get rid of the address descriptor */
  if aMemoryRange then
    destroyAddress(AddressLibClass$Inst, aMemoryRange);
    aMemoryRange := nil;
  endif;
}

!!

/* 6/30/1992 14:12 - PUBLIC
  Find a breakpoint in self dictionary. return breakpoint ID or nil;
  NOTES: bkptDict = [bkptID->#(addrDesc, colorTag)],...
  This method does not destroys the input <bkptAddrDesc>
*/
Def findBkptInDict(self, bkptAddrDesc)
{
  /* validate input */
  if not(bkptAddrDesc) cor (size(bkptDict) = 0)then
    ^nil; 
  endif;
  
  assocsDo(bkptDict,
    {using(bkpt)
      /* comparePhysicalAddress will take care the rest */
      if (comparePhysicalAddressesNoError(AddressLibClass$Inst, 
          bkptAddrDesc, value(bkpt)[0]) = ADRLIB_ADDR_EQUAL) then
        ^key(bkpt);
      endif;
     });
  ^nil;
}

!!

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

/* 6/30/1992 14:17 - PUBLIC
   If a Dasm 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);
  moduleDescriptor := nil ;
}

!!

/* 5/29/1992 14:23 - PRIVATE
  return the address offset text of a given dasm line.
*/
Def getAddressField(self, sourceTxt | loc)
{
  if not(sourceTxt) ^nil; endif;
  /* sourceTxt = ##########bNNNNN..... */
  if not(loc := indexOf(sourceTxt, ' ', 0)) 
     /* use the addressSize for default location */
     loc := addressSize;
  endif;    
  ^subString(sourceTxt, 0, loc);
}!!

/* 4/2/1992 14:41 - PUBLIC
  Get the current program counter and set its corresponding current execution point.
  Return the current PC address descriptor
  NOTES:
    - Caller must NOTE destroy it.
*/
Def getCurrentExecPoint(self | pcAddr, currentAddrType) {

  if aMemoryRange cand (pcAddr := getProgramCounter(CpuLibClass$Inst)) then
    /* Convert PC to current address Type */
    if (TheProcFamily = PROC_FAMILY_X86) then
      if not(currentAddrType := getAddressType(AddressLibClass$Inst, aMemoryRange)) 
        cor not(convertAddressToType(AddressLibClass$Inst, pcAddr, currentAddrType))
        destroyAddress(AddressLibClass$Inst, pcAddr);
        ^nil;
      endif;         
    endif; 
    
    clearCurrentExecPoint(self);    
    /* Check if the new PC address is inside of the module address range */
    if ((maskAddressMSB(AddressLibClass$Inst, pcAddr) <> nil) cand
        isAddrInAddressRangeNoError(AddressLibClass$Inst, pcAddr, aMemoryRange)) then
      /* Get the offset of current Execution point of the data object */
      /* DEBUG 
      printLine("new dasm PC: "+ 
                  asHex(getOffset(AddressLibClass$Inst, pcAddr)));
      */
      currentExecPoint := pcAddr;
    endif;
  endif;
  /* Return either the old or new current execution point */
  ^currentExecPoint;
}
!!

/* 6/26/1992 10:06 - PRIVATE
  Initialize a VirtSmrtDasmObject.
*/
Def init(self, anAddrRange, addrSize)
{
  /* DEBUG 
  printLine("Object: "+asAddrRange(AddressLibClass$Inst, anAddrRange));
  */
  /* Allocate holder for breakpoints */
  bkptDict := new(Dictionary, 2);
  clear(bkptDict);
  currentExecPoint := nil;
  languageName := #Unknown;
  ^init(self:ancestor, anAddrRange, addrSize);
}
!!

/* 7/12/1992 13:50 - PUBLIC
  Return the moduleDescriptor of Self.
*/
Def moduleDescriptor(self)
{
  ^nil; /* There is no symbolic information for the dasm object */
}
!!

/* 7/22/1992 9:11 - PUBLIC
  Return the moduleInfo of self (in this case, it's nil.)
*/
Def moduleInfo(self)
{
  ^nil;
}
!!

/* 7/12/1992 13:54 - PUBLIC
  Open the dasm session of the memory range, then find out about it.
  Notes: aMemoryRange is an addrDesc - destroy by this object when it done.
*/
Def openDasm(self, aMemoryRange, addressSize | newObj)
{
  if not(aMemoryRange) ^nil; endif;
  
  if not(newObj := openDasm(self:ancestor, aMemoryRange, addressSize)) then
    destroyAddress(AddressLibClass$Inst, aMemoryRange);
    ^nil;
  endif;
  /* Get the current execution point and its breakpoint setting */
  getCurrentExecPoint(self);
  rebuildBkptDict(self);
}
!!

/* 6/26/1992 10:18 - PUBLIC
  Rebuild bkptDict in respond to event notification.
    NOTES: bkptDict = ([bkptID->#(addrDesc, colorTag)],...)
*/
Def rebuildBkptDict(self | bkptInfo, bkptResult, loopBkpt )
{
  if not(aMemoryRange) then
    displayFormattedError(ErrorTextLibClass$Inst,
       ER_INVALID_DATA_OBJECT, FORCE_POPUP, nil, nil, nil);
    ^nil;
  endif;

  clearBkptDict(self);
  loopBkpt := nil; /* initial starting point for the loop */
  loop
  while (bkptResult := getAllAddrBkptNext(HLBreakLibClass$Inst,
    aMemoryRange, loopBkpt))
    begin
      /* NOTES: bkptResult = #(loopDesc, bkptDesc) */
      loopBkpt := bkptResult[0];
      /* Get BkptInfo to add to dictionary 
      ** bkptInfo = #(state, life, addrDesc) 
      */
      if bkptInfo := getBkpt(HLBreakLibClass$Inst, bkptResult[1]) then
        add(bkptDict, bkptResult[1],
            tuple(bkptInfo[2], getBkptColor(self, bkptInfo[0], bkptInfo[1])));
      endif;
  endLoop;
  ^size(bkptDict);
}

!!

/* 3/29/1992 13:44 - PUBLIC
  Remove all breakpoints in bkptDict of self.
    NOTES: bkptDict = ([bkptID->#(addrDesc, colorTag)],...)
*/
Def removeAllBkpt(self)
{
  if size(bkptDict) > 0 then
    assocsDo (bkptDict,
      {using(bkpt)   
        destroyAddress(AddressLibClass$Inst, value(bkpt)[0]);
        removeBkpt(HLBreakLibClass$Inst, key(bkpt));
      });
    clear(bkptDict);
  endif;
}
!!

/* 6/30/1992 14:32 - PRIVATE 
   NOTES: bkptDict = ([bkptID->#(addrDesc, colorTag)],...)
*/
Def removeBkptDesc(self, bkptId)
{
  /* Remove it from the dictionary first 
  ** otherwise, events notification will not work properly
  */
  destroyAddress(AddressLibClass$Inst, bkptDict[bkptId][0]);  
  remove(bkptDict, bkptId);
  ^removeBkpt(HLBreakLibClass$Inst, bkptId);
}

!!

/* 6/26/1992 16:51 - PUBLIC
  Reopen the previouly opened Dasm session
*/
Def reOpen(self, addressSize)
{
  if reOpen(self:ancestor, addressSize) then
    getCurrentExecPoint(self);
    rebuildBkptDict(self);
    ^self;
  endif;
  ^nil;
}
!!

/* 06/20/92 - PUBLIC
   Look for text in dasm starting at line startLine, column 0.
   If not found, return nil, else return #(line, column).
*/
Def searchText(self, searchText, startLine, getTxtFuncIgnore 
   | lineNum, chunk, chunkNum, pointPos)
{
  chunk := lineNum := startLine;
  if closed? then
    chunk := reOpen(self);  /* if chunk is nil, return nil */
  endif ;
  loop while chunk
  begin
    /* Check Abort */
    if TaskLibClass$Inst cand queryAbort(TaskLibClass$Inst) then
      ^nil;
    endif;
    chunkNum := chunkNumberFromLineNumber(self, lineNum);
    if chunk := getChunk( self,  chunkNum) then
      /* chunk is: #(startLineNum, textColl) or nil */
      if (pointPos := findStringInChunk(self, chunk[1],
          searchText, (lineNum-chunk[0]), 0, getTxtFuncIgnore)) then
        /* RETURN:  #((start-line + line-offset) , char-offset) */
        ^tuple((chunk[0] + y(pointPos)), x(pointPos));
      endif ;
      /* NOTES: rememberedLocations = 
      ** #(offsetFromStart, numLinesInChunk, startLineOfChunk) 
      */
      lineNum := (chunk[0] + rememberedLocations[chunkNum][1]) + 1;
    endif ;
   endLoop;
  /* not found */ 
  ^nil
}
!!

/* 6/26/1992 17:01 - PUBLIC
  Set a breakpoint to server and save the breakpoint ID to dictionary
  NOTES: ignored lineNum and colNum info.
*/
Def setBkpt(self, bkptType, lineNumIgnored, colNumIgnored, txtStr | addrDesc)
{
  /* Create an address descriptor to set break */
  if not(isInstrBoundAddress?(self, txtStr)) cor
     not(addrDesc := mapLine(self, nil, txtStr)) then
    ^nil;
  endif;

  /* Set breakpoint to server - Server comsumes addrDesc */
  ^setAsmBkpt(HLBreakLibClass$Inst,
      HLB_BKPT_STATE_ENABLED, /* state: enable */
      bkptType,               /* life: Permanent | Temporary */
      addrDesc);              /* address descriptor */
}
!!

/* 6/26/1992 17:18 - PUBLIC
  Set caller cached position.
*/
Def setCallersCache(self, newCacheObject)
{
  ^(callersCache := newCacheObject);
}
!!

/* 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, lineNumIgnore, ColNumIgnore, txtStr | addrDesc)
{
  /* Create an address descriptor to set PC */
  if (addrDesc := mapLine(self, nil, txtStr)) then
    /* CPU server will consume the addrDesc */
    ^setProgramCounter(CpuLibClass$Inst, addrDesc);
  endif;
  ^nil;
}
!!

/* 6/30/1992 16:39 - PUBLIC */
Def virtObjTitle(self)
{ ^"Disassembly";}
!!
