# $Id: pydbsupt.py,v 1.2 1998/08/26 09:26:33 zeller Exp $
"""Support functions for pydb, the Python debugger that works with ddd"""

import string

"""Breakpoint class for interface with ddd.

   Implements temporary breakpoints, ignore counts, disabling and
   (re)-enabling, and conditionals.

   Breakpoints are indexed by number through bpbynumber and by the
   file,line tuple using bplist.  The former points to a single
   instance of class Breakpoint.  The latter points to a list of
   such instances since there may be more than one breakpoint per line.
   """

class Breakpoint:
    next = 1      # Next bp to be assigned
    bplist = {}   # indexed by (file, lineno) tuple
    bpbynumber = [None]    # Each entry is None or an instance of Bpt
                           # index 0 is unused, except for marking an
                           # effective break .... see effective()

    def __init__(self, file, line, temporary=0):
        self.file = file
        self.line = line
        self.cond = None
        self.temporary = temporary
        self.enabled = 1
        self.ignore = 0
        self.hits = 0
        self.number = Breakpoint.next
        Breakpoint.next = Breakpoint.next + 1
        # Build the two lists
        self.bpbynumber.append(self)
        if self.bplist.has_key((file, line)):
            self.bplist[file, line].append(self)
        else:
            self.bplist[file, line] = [self]

        
    def deleteMe(self):
        index = (self.file, self.line)
        self.bpbynumber[self.number] = None   # No longer in list
        self.bplist[index].remove(self)
        if not self.bplist[index]:
            # No more bp for this f:l combo
            del self.bplist[index]

    def enable(self):
        self.enabled = 1

    def disable(self):
        self.enabled = 0

    def bpprint(self):
        if self.temporary:
           disp = 'del  '
        else:
           disp = 'keep '
        if self.enabled:
           disp = disp + 'yes'
        else:
           disp = disp + 'no '
        print '%-4dbreakpoint     %s at %s:%d' % (self.number, disp, self.file, self.line)
        if self.cond:
            print '\tstop only if %s' % (self.cond,)
        if self.ignore:
            print '\tignore next %d hits' % (self.ignore)
        if (self.hits):
            if (self.hits > 1): ss = 's'
            else: ss = ''
            print '\tbreakpoint already hit %d time%s' % (self.hits,ss)

# Determines if there is an effective (active) breakpoint at this
# line of code.  Returns breakpoint number or 0 if none
def effective(file, line, frame):
    """Determine which breakpoint for this file:line is to be
       acted upon.  Called only if we know there is a bpt at this
       location.  Returns breakpoint that was triggered and a flag
       that indicates if it is ok to delete a temporary bp."""
    possibles = Breakpoint.bplist[file,line]
    for i in range(0, len(possibles)):
        b = possibles[i]
        if b.enabled == 0:
            continue
        # Count every hit when bp is enabled
        b.hits = b.hits + 1
        if not b.cond:
            # If unconditional, and ignoring, go on to next, else break
            if b.ignore > 0:
                b.ignore = b.ignore -1
                continue
            else:
                # breakpoint and marker that's ok to delete if temporary
                return (b,1)
        else:
            # Conditional bp.
            # Ignore count applies only to those bpt hits where the
            # condition evaluates to true.
            try:
                val = eval(b.cond, frame.f_globals, frame.f_locals) 
                if val:
                    if b.ignore > 0:
                        b.ignore = b.ignore -1
                        # continue
                    else:
                        return (b,1)
                # else:
                #    continue
            except:
                # if eval fails, most conservative thing is to stop
                # on breakpoint regardless of ignore count.
                # Don't delete temporary, as another hint to user.
                return (b,0)
    return (None, None)

class Display:
    displayNext = 1
    displayList = []

    def __init__(self):
        pass

    def displayIndex(self, frame):
        if not frame:
            return None
        # return suitable index for displayList
        code = frame.f_code
        return (code.co_name, code.co_filename, code.co_firstlineno)

    def displayAny(self, frame):
        # display any items that are active
        if not frame:
            return
        index = self.displayIndex(frame)
        for dp in Display.displayList:
            if dp.code == index and dp.enabled:
                print dp.displayMe(frame)

    def displayAll(self):
        # List all display items; return 0 if none
        any = 0
        for dp in Display.displayList:
            if not any:
                print """Auto-display expressions now in effect:
Num Enb Expression"""
                any = 1
            dp.params()
        return any

    def deleteOne(self, i):
        for dp in Display.displayList:
            if i == dp.number:
                dp.deleteMe()
                return

    def enable(self, i, flag):
        for dp in Display.displayList:
            if i == dp.number:
                if flag:
                   dp.enable()
                else:
                   dp.disable()
                return

class DisplayNode(Display):

    def __init__(self, frame, arg, format):
        self.code = self.displayIndex(frame)
        self.format = format
        self.arg = arg
        self.enabled = 1
        self.number = Display.displayNext
        Display.displayNext = Display.displayNext + 1
        Display.displayList.append(self)

    def displayMe(self, frame):
        if not frame:
            return 'No symbol "' + self.arg + '" in current context.'
        try:
            val = eval(self.arg, frame.f_globals, frame.f_locals)
        except:
            return 'No symbol "' + self.arg + '" in current context.'
        #format and print
        what = self.arg
        if self.format:
            what = self.format + ' ' + self.arg
            val = self.printf(val, self.format)
        return '%d: %s = %s' % (self.number, what, val)

    pconvert = {'c':chr, 'x': hex, 'o': oct, 'f': float, 's': str}
    twos = ('0000', '0001', '0010', '0011', '0100', '0101', '0110', '0111',
            '1000', '1001', '1010', '1011', '1100', '1101', '1110', '1111')

    def printf(self, val, fmt):
        if not fmt:
            fmt = ' ' # not 't' nor in pconvert
        # Strip leading '/'
        if fmt[0] == '/':
            fmt = fmt[1:]
        f = fmt[0]
        if f in self.pconvert.keys():
            try:
                return apply(self.pconvert[f], (val,))
            except:
                return str(val)
        # binary (t is from 'twos')
        if f == 't':
            try:
                res = ''
                while val:
                    res = self.twos[val & 0xf] + res
                    val = val >> 4
                return res
            except:
                return str(val)
        return str(val)
        
    def checkValid(self, frame):
        # Check if valid for this frame, and if not, delete display
        # To be used by code that creates a displayNode and only then.
        res = self.displayMe(frame)
        if string.split(res)[0] == 'No':
            self.deleteMe()
            # reset counter
            Display.displayNext = Display.displayNext - 1
        return res

    def params(self):
        #print format and item to display
        pad = ' ' * (3 - len(`self.number`))
        if self.enabled:
           what = ' y  '
        else:
           what = ' n  '
        if self.format:
           what = what + self.format + ' '
        what = pad + what + self.arg
        print '%d:%s' % (self.number, what)

    def deleteMe(self):
        Display.displayList.remove(self)

    def disable(self):
        self.enabled = 0

    def enable(self):
        self.enabled = 1

