/* CLASS: Virtual Smart Mixed Object
   Provides intelligent behaviors for a Virtual Mixed Object.
   Contains breakpoints information of the module.

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

inherit(VirtMixedObject, #VirtSMixedObject, #(
callersCache /* save current position */
bkptDict     /* breakpoints dictionary */
moduleInfo   /* a ModuleInfo object - from the currentLoadFile */
modulePath   /* module file path */
), 2, nil)!!

now(class(VirtSMixedObject))!!

now(VirtSMixedObject)!!

/* 12/4/1992 15:05 - PRIVATE
  return the address offset of the specified line. Return nil if error.
*/
Def getBkptAddrOffset(self, lineNum, colNum, txtStr | addrSym, addrDesc, addrOffset)
{
  /* Check if it's not dasm Line then convert it to address */
  if not(addrOffset := getAddressField(self, txtStr)) then
    /* Remap lineNum from txtStr to Source line Num */
    if not(lineNum := getLineNum(self, txtStr)) then
      ^nil;
    endif;
    addrSym := "#"+moduleName(moduleInfo)+"#"+asString(lineNum)+"#"+asString(colNum);
    if not(addrDesc := convertTextToAddress(AddressLibClass$Inst, addrSym)) then
      ^nil;
    endif;
    if not(addrOffset := getOffset(AddressLibClass$Inst, addrDesc)) then
      destroyAddress(AddressLibClass$Inst, addrDesc);
      ^nil;
    endif;
    /* get rid of the unused addrDesc */
    destroyAddress(AddressLibClass$Inst, addrDesc);
  endif;
  ^addrOffset;
}
!!

/* 10/15/1992 13:58 - PUBLIC
   Disable the specified breakpoint.
*/
Def disableStmtBkpt(self, lineNum, colNum, txtStr | addrOffset)
{
  if addrOffset := getBkptAddrOffset(self, lineNum, colNum, txtStr) then
    /* Do associate to disable - bkptDict = [bkptID->#(addrOffset, colorTag) */
    assocsDo (bkptDict,
      {using(bkpt)
        if (compareOffsets(AddressLibClass$Inst, addrOffset,
                           value(bkpt)[0]) = ADRLIB_ADDR_EQUAL) then
          ^disableBkpt(HLBreakLibClass$Inst, key(bkpt));
        endif;
      });
  endif;
  /* Can't find breakpoint */
  reportBkptError(self, SRC_BKPT_DISABLE_BKPT);
  ^nil;
}
!!

/* 10/15/1992 13:58 - PUBLIC
   Enable the specified breakpoint.
*/
Def enableStmtBkpt(self, lineNum, colNum, txtStr | addrOffset)
{
  if addrOffset := getBkptAddrOffset(self, lineNum, colNum, txtStr) then
    /* Do associate to enable */
    assocsDo (bkptDict,
    {using(bkpt)
      if (compareOffsets(AddressLibClass$Inst, addrOffset,
                         value(bkpt)[0]) = ADRLIB_ADDR_EQUAL) then
        ^enableBkpt(HLBreakLibClass$Inst, key(bkpt));
      endif;
    });
  endif;
  /* Can't find breakpoint */
  reportBkptError(self, SRC_BKPT_ENABLE_BKPT);
  ^nil;
}
!!

/* 3/29/1992 13:58 - PUBLIC
   Clear the specified breakpoint.
*/
Def clearStmtBkpt(self, lineNum, colNum, txtStr | addrOffset)
{
  if addrOffset := getBkptAddrOffset(self, lineNum, colNum, txtStr) then
    /* Do associate to remove - bkptDict = [bkptID->#(addrOffset, colorTag)] */
    assocsDo (bkptDict,
      {using(bkpt)
        if (compareOffsets(AddressLibClass$Inst, addrOffset,
                           value(bkpt)[0]) = ADRLIB_ADDR_EQUAL) then
          ^removeBkptDesc(self, key(bkpt));
        endif;
      });
  endif;
  /* Can't find breakpoint */
  reportBkptError(self, SRC_BKPT_CLR_BKPT);
  ^nil;
}
!!

/* 10/14/1992 16:00 - PRIVATE
  Return the lineNumber of the txtStr.
*/
Def getLineNum(self, txtStr | lineParts)
{
  /* Parse the line num out - txtStr = [xxxxxx]bttttttttt.... */
  if (lineParts := findStrings(txtStr, "[]")) then
    ^asInt(lineParts[0], 10); /* Convert number string to int */
 endif;
 ^nil;
}
!!

/* 06/20/92 - PUBLIC
  Search the dataObject for the virtual line number of the searchAddr.
  Return lineNumber or nil.
*/
Def searchAddress(self, searchAddr, startLine
   | chunk, chunkNum, chunkAddrRange, subStr, lineInfo,
     addrDesc, bestSoFar, dasmAddrL, dasmAddrH, dasmAddr )
{

  /*
  ** algorithm:
  **   Call symbol table to find the source line which contains the requested address.
  **   Call VirtMixedObject to find the chunk number containing this source line.
  **   Get the chunk.
  **   Search the chunk textually for the address.
  */
  /* If self is close then  need to reopen the dataObject */
  if closed? then
    chunk := reOpen(self) ;  /* if chunk is nil, return nil */
  endif;

  /* Get source line number containing requested address offset */
  if not(addrDesc := createAndSetAddress(AddressLibClass$Inst, searchAddr))
    ^nil;
  endif;

  if not(lineInfo := addr2lineAndModule(SymbolLibClass$Inst, addrDesc))
    /* WHERE: lineInfo = #( moduleDesc, moduleAddrDesc, lineNum, colNum, lineAddrDesc, lineNumDesc ) */
    ^nil;
  endif;
  destroyAddress(AddressLibClass$Inst, lineInfo[1]);
  destroyAddress(AddressLibClass$Inst, lineInfo[4]);
  destroyAddress(AddressLibClass$Inst, addrDesc);

  /* Get chunk number containing source line number */
  if not(chunkNum := chunkNumberFromSourceLine(self, lineInfo[2]))
    ^nil;
  endif;

  /* Get chunk text and search for address */
  loop while (chunk := getChunk( self, chunkNum)) begin
    /* NOTES: chunk = #(startLineNum, textColl) or nil */
    /* Loop over the chunk and check for a match */
    do(overBy(0, size(chunk[1]), 1),
      {using(i)
        subStr := subString(chunk[1][i], 9, addressSize+9);
        if (subStr) then
          /* Why is there no asLong(string, base) ??? */
          dasmAddrH := asInt(subString(subStr,0,addressSize-4),16);
          dasmAddrL := asInt(subString(subStr,addressSize-4,addressSize),16);
          if (dasmAddrL cand dasmAddrH) then
            dasmAddr := dasmAddrL + (dasmAddrH*65536);
            if dasmAddr = searchAddr then
              /* RETURN:  (start-line + line-offset) */
              ^(chunk[0] + i);
            endif;
            if dasmAddr > searchAddr then
              ^bestSoFar;
            endif;
            if dasmAddr < searchAddr then
              bestSoFar := chunk[0] + i;
            endif;
          endif;
        endif;
      });
    /* For out-of-order code, dasm could be shoved into the next chunk (or a much later chunk).
       Try looking there if we didn't find it in the chunk with its source. */
    chunkNum := chunkNum + 1;
  endLoop;
  ^bestSoFar;  /* Can't find addr...return closest match */
}
!!

/* 8/26/1992 12:50 - PUBLIC
  Map a xtext string  in form of "[XXXXXX] ZZZZZZ.." to the
  equivalent offset of lineNum XXXXXX or nil.
*/
Def mapLineNumToAddress(self, lineNumTxt | srcLineNum, lineNum, lineInfo, offset)
{
  /* NOTES:
  ** text = [xxxxxx] xxxxx... - convert substring of lineNum to get its offset
  ** 6 = size of lineNum string.
  */
  srcLineNum := findStrings(lineNumTxt, "[]");
  if not(lineNum := asInt(srcLineNum[0], 10)) then
    ^nil;
  endif;
  if not(lineInfo := getLineNumInfo(SymbolLibClass$Inst, moduleDescriptor(self), lineNum)) then
    ^nil;
  endif;
  /* NOTES: lineInfo = #(addrRangeDesc, actualLine#, indexDesc) */
  offset := getOffset(AddressLibClass$Inst, lineInfo[0]);
  destroyAddress(AddressLibClass$Inst, lineInfo[0]);
  ^offset;
}
!!

/* 06/20/92 - PUBLIC
  Close self and save the browsing information to moduleInfo object for later use.
*/
Def close(self)
{
  currentExecPoint := nil;
  close(self:ancestor);
  if moduleInfo then
    setMixedLocations(moduleInfo, rememberedLocations);
    setMixedHighestChunk(moduleInfo, highestChunkSeen);
    setTotalMixedLines(moduleInfo, totalLinesGuess, totalLinesExact);
  endif;
}
 !!

/* 7/1/1992 11:38 - PUBLIC (to its browser only)
  Map a virtual lineNumber to its equivalent object context value
  (an address offset in this case).
*/
Def mapLineNum(self, ignored /* lineNum */, txtStr | addrOffset)
{
  /* The offset is more interesting than the line # itself */
 if (addrOffset := getAddressField(self, txtStr)) then
   ^addrOffset;
 endif;
 /* Mixed data object contains both dasm and source text line */
 /* txtStr is a Source text line - convert its line # to address */
 ^mapLineNumToAddress(self, txtStr);
}
!!

/* 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(moduleInfo) then
    ^nil;
  endif;
  ^duplicateAddress(AddressLibClass$Inst, addressRange(moduleInfo));
}
!!

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

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

/* 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;
  languageName := nil;
}
!!

/* 6/30/1992 14:12 - PUBLIC
  Find a breakpoint in self dictionary. return breakpoint ID or nil;
  NOTES: bkptDict = [bkptID->#(addrOffset, colorTag)],...
*/
Def findBkptInDict(self, offset)
{
  assocsDo(bkptDict,
    {using(bkpt)
      if (compareOffsets(AddressLibClass$Inst, offset,
                               value(bkpt)[0]) = ADRLIB_ADDR_EQUAL) then
        ^key(bkpt);
      endif;
     });
  ^nil;
}

!!

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

/* 5/29/1992 14:23 - PRIVATE
  return the address offset given a dasm line.
*/
Def getAddressField(self, sourceTxt | offset, subStr tmpDesc )
{
  if size(sourceTxt) = 0 then ^nil; endif;
  subStr := sourceTxt;
  if sourceTxt[0] == '[' then
    subStr := copyFrom(sourceTxt, 9, size(sourceTxt));
  endif;
  subStr := leftJustify(subStr);
  if (size(subStr) = 0) cor 
    not(tmpDesc := convertTextToAddressNoError(AddressLibClass$Inst,
                     asciiz("0x" + subString(subStr, 0, addressSize))))
    ^nil;
  endif;

  if (offset := getOffset(AddressLibClass$Inst, tmpDesc)) then
    destroyAddress(AddressLibClass$Inst, tmpDesc);
    ^asLong(offset);
  endif;
  destroyAddress(AddressLibClass$Inst, tmpDesc);
  ^nil;
}!!

/* 4/2/1992 14:41 - PUBLIC
  Get the current program counter and set its corresponding current execution point.
*/
Def getCurrentExecPoint(self | pcAddr, addrInfo)
{
  /* NOTES: addressRange(moduleInfo) does not create addrDesc */
  if addressRange(moduleInfo) cand (pcAddr := getProgramCounter(CpuLibClass$Inst)) then
    if (maskAddressMSB(AddressLibClass$Inst, pcAddr) <> nil) cand
        (isAddrInAddressRange(AddressLibClass$Inst, pcAddr,
       addressRange(moduleInfo)) ) then
      currentExecPoint := getOffset(AddressLibClass$Inst, pcAddr);
    else
      currentExecPoint := nil;
    endif;
    /* Destroy pcAddr */
    destroyAddress(AddressLibClass$Inst, pcAddr);
  endif;
  /* Return either the old or new current execution point */
  ^currentExecPoint;
}
!!

/* 6/26/1992 10:06 - PRIVATE
  Initialize a VirtSMixedObject.
*/
Def init(self, newModuleInfo)
{
  /* Allocate holder for breakpoints */
  bkptDict := new(Dictionary, 2);
  clear(bkptDict);
  moduleInfo := newModuleInfo;
  languageName := languageName(newModuleInfo);
  currentExecPoint := nil;
  ^init(self:ancestor, newModuleInfo);
}
!!

/* 7/1/1992 11:38 - PUBLIC (to its browser only)
  Map a virtual lineNumber to its equivalent object context value
  (an address offset in this case).
*/
Def lineNum(self, ignored /* lineNum */, txtStr)
{
  /* The offset is more interesting than the line # itself */
 ^getAddressField(self, txtStr);
}
!!

/* 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;
}!!

/* 7/17/1992 16:41 - PRIVATE
  Open a mixed data object - Ancestor do the dirty work.
*/
Def openMixed(self, aModuleInfo | newObj)
{
  if (newObj := openMixed(self:ancestor, aModuleInfo )) <> self then
    ^newObj /* error case */
  endif;
  getCurrentExecPoint(self);
  rebuildBkptDict(self);
}!!

/* 6/26/1992 10:18 - PUBLIC
  Rebuild bkptDict in respond to event notification.
    NOTES: bkptDict = ([bkptID->#(addrOffset, colorTag)], ...)
*/
Def rebuildBkptDict(self | bkptInfo, bkptResult, loopBkpt )
{
  if not(addressRange(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 := getAllAddrBkptNext(HLBreakLibClass$Inst,
    addressRange(moduleInfo), 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(getOffset(AddressLibClass$Inst, bkptInfo[2]),
            getBkptColor(self, bkptInfo[0], bkptInfo[1])));
        /* Get rid of the unused address descriptor */
        destroyAddress(AddressLibClass$Inst, bkptInfo[2]);
      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;
}
!!

/* 7/28/1992 11:46 - PRIVATE */
Def removeBkptDesc(self, bkptDescId)
{
  /* Remove it from the dictionary first - otherwise, events will create problem */
  remove(bkptDict, bkptDescId);
  ^removeBkpt(HLBreakLibClass$Inst, bkptDescId);
}


!!

/* 6/26/1992 16:51 - PUBLIC
  Reopen the previouly opened Mixed Data object.
*/
Def reOpen(self)
{
  if (moduleInfo) cand (reOpen(self:ancestor, moduleInfo)) then
    if not(moduleDescriptor(self)) then
      ^nil;
    endif;
    getCurrentExecPoint(self);
    rebuildBkptDict(self);
    ^self;
  endif;
  ^nil;
}
!!

/* 06/20/92 - PUBLIC
   Look for text in mixed data object starting at line startLine, column 0.
   If not found, return nil, else return #(line, column).
*/
Def searchText(self, searchText, startLine, getTxtFunc 
   | chunk, chunkNum, pointPos)
{
  /* Get chunkNum from lineNum */
  if not(startLine) cor
     not(chunkNum := chunkNumberFromLineNumber(self, startLine)) cor
     not(chunk := getChunk(self, chunkNum))
    /* if chunk is nil, return nil */ 
    ^nil;
  endif;
  
  /* Do While */  
  loop 
    /* Check Abort */
    if queryAbort(TaskLibClass$Inst) then
      ^nil;
    endif;
    /* chunk is: #(startLineNum, textColl) */
    if (pointPos := findStringInChunk(self, chunk[1],
        searchText, 0, 0, getTxtFunc)) 
      /* DEBUG: printLine("found "+asString(chunk[0])); */
      /* RETURN:  #((start-line + line-offset) , char-offset) */
      ^tuple((chunk[0] + y(pointPos)), x(pointPos));
    endif ;
    chunkNum := chunkNum + 1;
  while (chunk := getChunk(self, chunkNum))
  begin
  endLoop;
  ^nil
}
!!

/* 6/26/1992 17:01 - PUBLIC
  Set a breakpoint to self and save the breakpoint ID to dictionary
    Return GOOD or nil.
*/
Def setBkpt(self, bkptType, lineNum, colNum, txtStr | addrDesc, addrOffset,
   addrSpace){

  /* Set Asm breakpoint - check for non-opcode asm line */
  if not(isInstrBoundAddress?(self, txtStr)) then
    ^nil;
  endif;
  /* Check if it's a source Line - Set source breakpoint then */
  if not(addrOffset := getBkptAddrOffset(self, lineNum, colNum, txtStr))
    cor not(addrDesc := addressRange(self)) then
    ^nil;
  endif;

  if not(setOffset(AddressLibClass$Inst, addrDesc, addrOffset)) then
    destroyAddress(AddressLibClass$Inst, addrDesc);
    ^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*/     
  /* Set Breakpoint at address */
  ^setAsmBkpt(HLBreakLibClass$Inst,
      HLB_BKPT_STATE_ENABLED, /* state: enable */
      bkptType,               /* life: Permanent | Temporary */
      addrDesc);
}
!!

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

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

/* 7/14/1992 15:06 - PUBLIC (to ancestor) */
Def setModuleTotalLines(self, numLines)
{
  if moduleInfo then
    setMixedLocations(moduleInfo, rememberedLocations);
    setMixedHighestChunk(moduleInfo, highestChunkSeen);
    setTotalMixedLines(moduleInfo, numLines, numLines);
  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, colNum, txtStr | addrOffset, addrDesc)
{
  /* Create an address descriptor to set PC */
  if not(addrOffset := getAddressField(self, txtStr)) cor
    not(addrDesc := createAddress(AddressLibClass$Inst)) then
    ^nil;
  endif;
  if not(setOffset(AddressLibClass$Inst, addrDesc, addrOffset)) then
    destroyAddress(AddressLibClass$Inst, addrDesc);
    ^nil;
  endif;

  /* Callee will consume the addrDesc */
  ^setProgramCounter(CpuLibClass$Inst, addrDesc);
}
!!

/* 06/20/92 - PUBLIC
  Return the title of data object (used the file name in this case.)
*/
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;
}
!!
