/* CLASS: VIRTDASM
   This Virtual Dasm Object is a  VirtualDataObject which specializes
   in storing memory disassembly data.
   It implements the VirtDataObject protocol for memory disassembly using
   fixed-sized chunks of memory addresses range (i.e. startAddr - endAddr).

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

inherit(VirtDataObject, #VirtDasmObject, #(
dasmSession /* Server session ID */
aMemoryRange /* Addr Range Descriptor */
), 2, nil)!!

now(class(VirtDasmObject))!!

/* 5/29/1992 10:32 - PUBLIC 
  Open a new Virtual Dasm Object representing the specified memory range.
  NOTES: Do not destroy anAddrRangeDesc after calling open(). It will will
    destroy by the object when it's done.
  Ex: 
    open(VirtDasmObject, anAddrRangeDesc, addrSize). 
 */
Def open(self, anAddrRange, addrSize | aVirtualDasmObject)
{ 
  aVirtualDasmObject := new(self:ancestor);
  ^init(aVirtualDasmObject, anAddrRange, addrSize);
}!!

now(VirtDasmObject)!!

/* 3/18/1994 15:19 - PUBLIC
  Set the new Operand/Address Size for self.
*/
Def setNewAddressSize(self, newAddrSize)
{ 
  /* Set the Dasm session according to the current viewAddressSize */
  ^setDasmAddressSize(DisAsmLibClass$Inst, dasmSession, newAddrSize); 
}
!!

/* 3/10/1994 13:03 - PRIVATE
  Move the start address of the DASM server to the specified position.
  new location = startAddress + offsetFromStart.
  Return an <addrDesc> if successful, else nil.
  NOTES: 
    - Caller is responsible to destroy the returned <addrDesc>.
*/
Def moveFromStart(self, offsetFromStart | addrDesc)
{ 
  /* Need to be the same type of the original addressDesc */
  if not(addrDesc := duplicateAddress(AddressLibClass$Inst, aMemoryRange)) then
    ^nil;
  endif; 
  /* move to position */
  if not(moveTo(self, addrDesc, offsetFromStart)) then
     destroyAddress(AddressLibClass$Inst, addrDesc);
    ^nil;
  endif;
  ^addrDesc;  
}
!!

/* 3/10/1994 13:03 - PRIVATE
  Move the start address of the DASM server to the specified position.
  Return GOOD if successful, else nil.
  NOTES: 
    - This method modifies the content of <addrDesc>.
    - It does not destroy the input <addrDesc>.
*/
Def moveTo(self, addrDesc, offsetFromCurrent | err)
{ 
  /* add the offsetFromCurrent to the input addrDesc and set it */
  if not(dasmSession) cor (not(addrDesc)) 
    ^nil;
  endif;  
  if (offsetFromCurrent > 0) then
    if not(err := addToAddressOverFlow(AddressLibClass$Inst, addrDesc, 
      offsetFromCurrent)) cor (err = ER_ADR_RESULT_OVERFLOW) then
      ^nil;
    endif;   
  endif;
  /* Set the disassembly location - return GOOD or nil */
  ^setAddress(DisAsmLibClass$Inst, dasmSession, addrDesc);
}
!!

/* 6/26/1992 8:55 - PUBLIC 
   Calculate number of lines in chunk.
*/
Def getChunkSize(self, chunkNumber)
{ 
  /* rememberedLocations = #(offsetFromStart, numLinesInChunk, startLineInChunk) */
  ^at(rememberedLocations,chunkNumber)[1];
}

!!

/* 6/26/1992 8:55 - PUBLIC 
   Calculate starting line number of chunk.
   The 1st chunks's line numbers range from 1 to linesPerChunk, and so on...
*/
Def getChunkStartLine(self, chunkNumber)
{ 
  /* rememberedLocations = #(offsetFromStart, numLinesInChunk, startLineInChunk) */
  ^at(rememberedLocations,chunkNumber)[2];
}

!!

/* 06/07/94 - PRIVATE 
  At end of virtual data object, set the new range if the range is larger than the
  original range.
*/
Def resetEndOffsetRange(self, newEndAddr | result, newRange)
{ 
  /* DEBUG 
  printLine("Old Range: "+asAddrRange(AddressLibClass$Inst, aMemoryRange));
  */
  /* Check aMemoryRange > newEndAddr (upper bits are ON) then reset startOffset */
  if not(result := compareAddressesNoError(AddressLibClass$Inst, aMemoryRange, newEndAddr)) then
    ^nil;
  endif;  
  /* aMemoryRange > newEndAddr */
  if (result = ADRLIB_ADDR_GREATER_THAN) then 
     maskAddressMSB(AddressLibClass$Inst, aMemoryRange);
  endif;   
  /* Get the range of [aMemoryRange.startOffset - newEndAddr] to set the new range */ 
  if (newRange := rangeOfAddresses(AddressLibClass$Inst, aMemoryRange, newEndAddr)) cand
     (newRange > 0) then
    /* Set the new address range for <aMemoryRange> */ 
    setAddrRangeLength(AddressLibClass$Inst, aMemoryRange, newRange);
  endif;      
  /* DEBUG 
  printLine("New Range: "+asAddrRange(AddressLibClass$Inst, aMemoryRange));   
  */
}
!!

/* 6/25/1992 13:22 - PRIVATE
  Check to see if an current address is at the end of self addressRange.
  Return #true if atEnd, else nil.
  NOTES: Simulating the atEnd() function for the virtDasmObject.
*/
Def atEnd(self, currentAddr | result)
{ 
  /* DEBUG 
  printLine("AtEnd: "+asAddr(AddressLibClass$Inst, currentAddr)+
            asAddrRange(AddressLibClass$Inst, aMemoryRange));
  */
  /* If <currentAddr> is outside the <memoryRange> the atEnd is BOOL_FALSE */
  ^not(isAddrInDasmRangeNoError(AddressLibClass$Inst, currentAddr, aMemoryRange));   
}
!!

/* 7/2/1992 14:03 - PRIVATE 
  Build a dasm chunk from a know dasm position 
  #(offsetFromStart, numLinesInChunk, startLineOfChunk).
  NOTES: dasmPos = #(offsetFromStart, numLinesInChunk, startLineInChunk)
  Return tuple: #(StartLineNumber, TextCollection). 
*/
Def buildKnownChunk(self, dasmPos, chunkNum | dasmCol, cloneDesc)
{   
  /* Move to the desired offset position, using the instance address Range */
  if not(cloneDesc := moveFromStart(self, dasmPos[0])) then
    ^nil;
  endif;    
  /* Get the numLinesInChunk @ (startOffset+offsetFromStart) */  
  if (dasmCol := getTextOfChunk(self, dasmPos[1], cloneDesc)) then
    destroyAddress(AddressLibClass$Inst, cloneDesc);
    ^tuple(firstLineOfChunk(self, chunkNum), dasmCol);
  endif;
  destroyAddress(AddressLibClass$Inst, cloneDesc); 
  ^nil; /* Something wrong here */
}
!!

/* 5/29/1992 13:04 - PRIVATE 
  Get a randomized dasm chunk. Memorize chunks for later accessing (in rememberedLocations).
  If find AtEnd, set totalLinesExact. If beyond AtEnd, return nil.
  NOTES: 
    - dasmPos = #(offsetFromStart, numLinesInChunk, startLineInChunk)
*/
Def buildRandomChunk(self, chunkWanted | chunkNum, oldLoc, dasmCol,
  line, dasmLine, newOffset, linesInChunk, newAddrDesc)
{ 
  /* Move as far as we know how */
  if chunkNum := highestChunkSeen then
    /* NOTES: oldLoc = #(offsetFromStart, NumLinesInChunk, startLineOfChunk) */
    if not(oldLoc := at(rememberedLocations, chunkNum)) then
      ^nil;
    endif;
    newOffset    := oldLoc[0];
    dasmLine     := oldLoc[2];  /* StartLine of chunk */
    linesInChunk := oldLoc[1];
  else
    /* Begin with chunk #1 and the start offset of address range - Move to location */
    chunkNum := 1; 
    dasmLine := 1; 
    linesInChunk := linesPerChunk;
    newOffset  := 0L;
    /* Remember the 1st one*/
    add(rememberedLocations, chunkNum, tuple(newOffset, linesPerChunk, dasmLine));
    highestChunkSeen := chunkNum;
  endif;

  /* Locate the chunk position, get the dasm text of chunk */
  if (newAddrDesc := locateRandomChunk(self, chunkWanted, chunkNum,
      dasmLine, newOffset, linesInChunk)) cand
    (dasmCol := getTextOfChunk(self, linesInChunk, newAddrDesc)) cand
    (size(dasmCol) >0) then
    /* At end of data source, set the totallinesExact */
    if atEnd(self, newAddrDesc) and not(totalLinesExact) then  
      setTotalLinesExact(self, (size(dasmCol) - 1 + firstLineOfChunk(self, chunkWanted)));
      /* Reset the endAddress range to the new boundary */
      resetEndOffsetRange(self, newAddrDesc);
    endif;

    /* Save the total Lines of chunk and return the chunk */
    (rememberedLocations[chunkWanted])[1] := size(dasmCol);
    destroyAddress(AddressLibClass$Inst, newAddrDesc);
    ^tuple(firstLineOfChunk(self, chunkWanted), dasmCol);
  endif;
  ^nil;
}
!!

/* 5/29/1992 13:04 - PRIVATE 
   Scan a randomized dasm chunk. Memorize chunks for later accessing
   (in rememberedLocations).
   Return the start <AddrDesc> of the wanted chunk if successful, else nil.
   NOTES: 
     - The caller is responsible for destroying the returned <addrDesc>.
*/
Def locateRandomChunk(self, chunkWanted, chunkNum, dasmLine, newOffset, linesInChunk 
| dasmCol, line, addrDesc)
{ 
  /* Set the start offset for the DASM Server to start disassemble - return <addrDesc> */
  if not(addrDesc := moveFromStart(self, newOffset)) then
     ^nil;
  endif;
  /* Locate the chunkWanted from the highestChunkSeen, find starting line, 
  ** remembering along the way. Not for the 1st chunk. 
  */
  /* DEBUG - printLine("Chunk Wanted: "+asString(chunkWanted)); */
   
  loop
  while (chunkNum < chunkWanted) cand not(atEnd(self, addrDesc))
  begin
    line := 0 ;
    if checkAbort(self) then 
      destroyAddress(AddressLibClass$Inst, addrDesc);
      ^nil; 
    endif;
    loop  /* Find out the next linesInChunk offset from the current offset */
    while (line < linesInChunk) cand not(atEnd(self, addrDesc))
    begin 
      if not(dasmCol := getTextOfChunk(self, linesInChunk, addrDesc)) then
        destroyAddress(AddressLibClass$Inst, addrDesc);
        ^nil; /* Failed to disassemble memory */  
      endif;   
      line := line + size(dasmCol);
      /* get the current position */
      if not(currentDasmAddress(DisAsmLibClass$Inst, dasmSession, addrDesc)) then
        destroyAddress(AddressLibClass$Inst, addrDesc);
        ^nil;
      endif;   
      /* DEBUG
      print("Dasm address: "); printAddr(AddressLibClass$Inst, addrDesc);
      */
      dasmCol := nil;
    endLoop;
    /* Save the total Lines of remembered chunk */
    if (line cand line > 0) then
      (rememberedLocations[chunkNum])[1] := line;
    else
      displayFormattedError(ErrorTextLibClass$Inst, 
         ER_DASM_CANT, FORCE_POPUP, nil, nil, nil);
      destroyAddress(AddressLibClass$Inst, addrDesc);
      ^nil;
    endif;    
    /* Check termination conditions */
    if atEnd(self, addrDesc) then
      if not(totalLinesExact) then
        /* SetTotalLineExact by callBack to browser to set numLines */
        setTotalLinesExact(self, (line-1 + firstLineOfChunk(self, chunkNum)) );
        /* Reset the endAddress range to the new boundary */
        resetEndOffsetRange(self, addrDesc);
      endif;
      destroyAddress(AddressLibClass$Inst, addrDesc);  
      ^nil ; /* ran out of memory range */
    else
      /* Advance to next chunk */
      chunkNum := chunkNum + 1 ;   /* must read through next unwanted chunk */
      dasmLine := dasmLine + line; /* Keep track of dasm lines */
      /* Save offset from the startOffset to rememberedLocations[] */
      if not(newOffset := rangeOfAddresses(AddressLibClass$Inst, aMemoryRange, addrDesc)) then
        destroyAddress(AddressLibClass$Inst, addrDesc);
        ^nil;
      endif;
      newOffset := newOffset-1;
      add(rememberedLocations, chunkNum, tuple(newOffset, linesPerChunk, dasmLine));
      highestChunkSeen := chunkNum;
      /* DEBUG - printLine("Chunk: "+asString(chunkNum)+" bytes offset: "+asString(newOffset)); */
    endif ;
  endLoop;
  ^addrDesc;
}
!!

/* 6/25/1992 11:35 - PUBLIC
   Given a (virtual) line number, return the chunk number which contains it.
   NOTES: rememberedLocations = #(startOffset, numLines, startLines)
*/
Def chunkNumberFromLineNumber(self, lineNumber | chunkNum)
{ 
  /* 
  ** Chunks have the different number of lines, so we have to search for location.
  ** Note that the 1st chunk number is 1. 
  */
  if size(rememberedLocations) = 0 then
    ^1; /* Start with chunk# 1 */
  endif;
  /* rememberLocation = chunk#->#(startOffset, NumLinesInChunk, startLineOfChunk) */
  assocsDo (rememberedLocations,     
    {using(chunk)
      /* If lineNumber is between startLine and endLine of Chunk then return chunk# */
      if (lineNumber >= value(chunk)[2]) cand (lineNumber <= (value(chunk)[1]+value(chunk)[2])) then
        ^key(chunk);
      endif;        
    });
  /* Not found in rememberedLocations */    
  ^1 + size(rememberedLocations);  
}!!

/* 6/25/1992 12:53 - PUBLIC 
  Close Dasm server session.
*/
Def close(self)
{ 
  closed? := #true;
  if dasmSession then
    closeDasm(DisAsmLibClass$Inst, dasmSession);
  endif;  
  dasmSession := nil;
}
!!

/* 6/26/1992 8:55 - PRIVATE 
   Calculate starting line number of chunk.
   The 1st chunks's line numbers range from 1 to linesPerChunk, and so on...
*/
Def firstLineOfChunk(self, chunkNumber)
{ 
  /* rememberedLocations = #(offsetFromStart, numLinesInChunk, startLineInChunk) */
  ^at(rememberedLocations,chunkNumber)[2];
}

!!

/* 6/25/1992 13:39 - PUBLIC (to it descendant)
   If a DataObject has been reOpen()'ed, cached info may be staled.  The user
   can call this function to flush cached info, or not call it to keep
   cached info.
*/
Def flushCachedMemory(self)
{
  highestChunkSeen := totalLinesExact  := nil;
  /* chunk#->(offsetFromStart, numLines, startLine) */
  if rememberedLocations then
    clear(rememberedLocations);
  else  
    rememberedLocations := new(Dictionary, 2); 
  endif; 
}

!!

/* 6/25/1992 11:39 - PUBLIC
  Given a chunkNumber >= 1, return the tuple: #(startingLineNum, TextCollection).
  If chunk number is beyond the actual address range, then return nil.
*/
Def getChunk(self, chunkNumber | dasmPos, chunk)
{ 
  /* NOTES: dasmPos = #(offsetFromStart, numLineInChunk, startLineOfChunk) */
  if checkAbort(self) then
    ^nil;
  endif;
  /* DEBUG 
  printLine("getChunk: "+asString(chunkNumber)); 
  */
  if (dasmPos := at(rememberedLocations, chunkNumber)) then
    chunk := buildKnownChunk(self, dasmPos, chunkNumber);
  else
    chunk := buildRandomChunk(self, chunkNumber);
  endif;   
  ^chunk;
}
!!

/* 7/10/1992 15:48 - PRIVATE
  Get the number of dasm lines at the current setting Dasm server offset position.
  return a dasm textCollection, or nil if error.
  NOTES: 
    - Caller needs to make sure that Dasm Server being set with the desired location.
    - Caller has ownership of the input <newAddrDesc>.
*/
Def getTextOfChunk(self, wantedLines, newAddrDesc | line, textCol, dasmCol)
{  
  /* Allocate buffer to store dasmCollection */
  dasmCol := new(TextCollection, wantedLines);
  line := 0;
  loop
  while (line < wantedLines) cand not(atEnd(self, newAddrDesc))
  begin 
    /* Get dasm text from the current offset setting of the Dasm Server */
    if not(textCol := getDasmInstByLines(DisAsmLibClass$Inst, dasmSession,
      wantedLines)) then
      /* return the dasm error buffer or nil -  Unable to diassemble memory */
      if not(dasmCol := getDasmErrorBuffer(self, newAddrDesc, 
         wantedLines))
        ^nil;
      endif;  
      /* set the end offset to the DASM Server to stop disassemble memory */
      if (errReported <> ER_ADR_RESULT_OVERFLOW) cand  
          not(moveTo(self, newAddrDesc, (2 * wantedLines)))
        ^nil;
      endif;
      errReported := GOOD;   
      ^dasmCol;
    endif;
    /* insert the new dasm text into the returned collection */
    insertAll(dasmCol, textCol, size(dasmCol));  
    line := line + size(textCol);
    /* get the current position */
    if not(currentDasmAddress(DisAsmLibClass$Inst, dasmSession, newAddrDesc)) 
       ^dasmCol;
    endif;   
    textCol := nil;
  endLoop;
  /* Clear old dasm server error */
  errReported := GOOD;
  ^dasmCol;
}
!!

/* 7/2/1992 17:01 - PRIVATE
  Initialize self instance.  openDasm() does most of the work.
*/
Def init(self, anAddrRange, addrSize)
{ 
  init(self:ancestor);
  if not(addressSize := maxOutputAddrDigits(AddressLibClass$Inst,anAddrRange)) 
    addressSize := 8; /* Default max number of digits per address */
  endif;  
  ^openDasm(self, anAddrRange, addrSize);
}
!!

/* 5/29/1992 12:23 - PUBLIC
   Return the number if text lines in self.  Guess if don't know.
*/
Def numLinesIn(self)
{
  /* if we know how many lines exist, say so, else let give a guess */
  if totalLinesExact then
    ^totalLinesExact ;
  else
    ^totalLinesGuess ;
  endif ;
}
!!

/* 5/29/1992 12:11 - PUBLIC (to its child object)
  Open a Dasm Server session for a VirtDasmObject (Called by open()).
  Notes: return nil if error open dasm object.  
*/
Def openDasm(self, addrRangeDesc, addrSize | line, rangeLength)
{ 
  /* Open the DasmSession then init self data */ 
  if not(dasmSession := openDasm(DisAsmLibClass$Inst, addrRangeDesc)) then
    ^nil; 
  endif;
  /* Set the Dasm session according to the current viewAddressSize */
  setDasmAddressSize(DisAsmLibClass$Inst, dasmSession, addrSize);

  /* Save the specified memory range - addrDesc */
  aMemoryRange := addrRangeDesc; 
  /* NOTES: chunk#->(addrOffset, numLines, StartLine) */
  rememberedLocations := new(Dictionary, 2); 
  highestChunkSeen := nil;
  
  /* Give a guessing number of lines in the virtual dasm object. */
  if (rangeLength := getAddrRangeLength(AddressLibClass$Inst, addrRangeDesc)) then
    if (rangeLength = 0) then
      /* rangeLength = 0 is the max address range - internal error */
      displayFormattedError(ErrorTextLibClass$Inst, 
          ER_ADR_ADDRESS_TOO_LARGE, CHECK_MODE, nil, nil, nil);          
      /* Set both total lines to 0 to lock scrolling */     
      totalLinesGuess  := totalLinesExact := 0;
    else
      /* Guess the totalLineGuess = total bytes / 2 - Average 4 bytes per instruction */
      totalLinesGuess := asLong(rangeLength / 2);
    endif;  
  else
    /* Can't get the addressRange to guess */
    totalLinesGuess := totalLinesExact := 0;
    ^nil;
  endif; 
}
!!

/* 6/25/1992 13:42 - PUBLIC
  Reopen the previous opend DasmObject.
*/
Def reOpen(self, addressSize)
{ 
  if closed? then
    if not(aMemoryRange) cor 
       not(dasmSession := openDasm(DisAsmLibClass$Inst, aMemoryRange)) cor 
       not(setDasmAddressSize(DisAsmLibClass$Inst, dasmSession, addressSize)) 
    then
      displayFormattedError(ErrorTextLibClass$Inst, 
         ER_DASM_REOPEN, FORCE_POPUP, nil, nil, nil);
      ^nil;
    endif; 
    /* Reopen Ok */  
    closed? := nil; 
  endif;  
  ^self;
}
!!

/* 6/25/1992 13:49 - PUBLIC 
  Set resizeCallBack of the dataObject.
*/
Def setResizeCallback(self, aResizeBlock)
{ 
  resizeCallback := aResizeBlock ;
}

!!

/* 6/25/1992 13:54 - PRIVATE 
  Set the totalLineExact to numLines and resize it.
*/
Def setTotalLinesExact(self, numLines)
{ 
  totalLinesExact := numLines;
  if resizeCallback then
    eval( resizeCallback, numLines );
  endif;
}
 
!!
