MoreDebugging/MoreBBLog.c

/*
    File:       MoreBBLog.c
 
    Contains:   Module to log debug traces to BBEdit.
 
    Written by: Quinn
 
    Copyright:  Copyright © 2000 by Apple Computer, Inc., all rights reserved.
 
                You may incorporate this Apple sample source code into your program(s) without
                restriction. This Apple sample source code has been provided "AS IS" and the
                responsibility for its operation is yours. You are not permitted to redistribute
                this Apple sample source code as "Apple sample source code" after having made
                changes. If you're going to re-distribute the source, we require that you make
                it clear in the source that the code was descended from Apple sample source
                code, but that you've made changes.
 
    Change History (most recent first):
 
         <4>     27/3/00    Quinn   Add some more coercions from basic types to text.  Don't assert
                                    if we can't AESend to BBEdit because BBEdit isn't running.
         <3>     20/3/00    Quinn   Added coercions from built-in types to typeText to make
                                    descriptor logging more useful.  Changed BBLogDesc to bypass
                                    Str255 limit.
         <2>      3/9/00    gaw     API changes for MoreAppleEvents
         <1>      6/3/00    Quinn   First checked in.
*/
 
/////////////////////////////////////////////////////////////////
 
// MoreIsBetter Setup
 
#include "MoreSetup.h"
 
// Mac OS Interfaces
 
#include <AERegistry.h>
#include <PLStringFuncs.h>
#include <TextUtils.h>
#include <Aliases.h>
 
// MIB Prototypes
 
#include "MoreAppleEvents.h"
#include "MoreMemory.h"
 
// Our Prototypes
 
#include "MoreBBLog.h"
 
/////////////////////////////////////////////////////////////////
 
#if MORE_DEBUG
 
    static AECoercePtrUPP gBasicToTextCoerceUPP;            // -> BasicToTextCoerceProc
    
    static pascal OSErr BasicToTextCoerceProc(DescType typeCode, const void *dataPtr, Size dataSize, DescType toType, SInt32 handlerRefCon, AEDesc *result)
        // A coercion handler to convert some basic Apple event types
        // to text.  I added these types on demand, as I noticed that my
        // MoreOSL test program needed them.
    {
        #pragma unused(handlerRefCon)
        OSStatus        err;
        ccntTokenRecPtr tokenData;
        AEDesc          tokenAsText;
        Handle          resultH;
        AliasHandle     aliasH;
        Str63           tmpStr63;
        Str255          tmpStr;
        Str255          tmpStr2;
        FSSpecPtr       fssP;
        
        MoreAssertQ(toType == typeText);
        MoreAssertQ(result != nil);
        
        MoreAENullDesc(result);
        
        switch (typeCode) {
            case typeNull:
            case typeCurrentContainer:
            case typeObjectBeingExamined:
                // YouÕd think that the following assert would be useful, but it fires all the time.
                // MoreAssertQ(dataPtr == nil);
                MoreAssertQ(dataSize == 0);
                err = AECreateDesc(typeText, &typeCode, sizeof(typeCode), result);
                break;
                
            case typeToken:
                MoreAssertQ(dataSize == sizeof(ccntTokenRecord));
                MoreAssertQ(dataPtr  != nil);
                
                // The data for a token is a ccntTokenRecord.  We extract the data,
                // then coerce the embedded descriptor to text, then add a header
                // that identifies the token as a token.
                
                MoreAENullDesc(&tokenAsText);
                resultH = nil;
                
                tokenData = (ccntTokenRecPtr) dataPtr;
                err = AECoerceDesc(&tokenData->token, typeText, &tokenAsText);
                if (err == noErr) {
                    err = MoreAECopyDescriptorDataToHandle(&tokenAsText, &resultH);
                }
                if (err == noErr) {
                    // Add the string "'toke-'" to the front of the handle.
                    (void) Munger(resultH, 0, nil, 0, "'toke'-", 7);
                    err = MemError();
                }
                if (err == noErr) {
                    HLock(resultH);
                    err = AECreateDesc(typeText, *resultH, GetHandleSize(resultH), result);
                }
                
                MoreAEDisposeDesc(&tokenAsText);
                if (resultH != nil) {
                    DisposeHandle(resultH);
                    MoreAssertQ(MemError() == noErr);
                }
                break;
            
            case typeAbsoluteOrdinal:
                MoreAssertQ(dataSize == sizeof(DescType));
                err = AECreateDesc(typeText, dataPtr, sizeof(DescType), result);
                break;
            
            case typeAlias:
                aliasH = nil;
                
                err = PtrToHand(dataPtr, (Handle *) &aliasH, dataSize);
                if (err == noErr) {
                    err = GetAliasInfo(aliasH, asiAliasName, tmpStr63);
                }
                if (err == noErr) {
                    PLstrcpy(tmpStr, "\palias(");
                    PLstrcat(tmpStr, tmpStr63);
                    PLstrcat(tmpStr, "\p)");
                    err = AECreateDesc(typeText, &tmpStr[1], tmpStr[0], result);
                }
                
                if (aliasH != nil) {
                    DisposeHandle( (Handle) aliasH );
                }
                break;
 
            case typeFSS:
                MoreAssertQ(dataPtr != nil);
                MoreAssertQ(dataSize == sizeof(FSSpec));
                
                fssP = (FSSpecPtr) dataPtr;
 
                PLstrcpy(tmpStr, "\pfss(");
                PLstrcat(tmpStr, fssP->name);
                PLstrcat(tmpStr, "\p)");
 
                err = AECreateDesc(typeText, &tmpStr[1], tmpStr[0], result);
                break;
 
            case typeBoolean:
                if ( *((Boolean *) dataPtr) ) {
                    err = AECreateDesc(typeText, "true", 4, result);
                } else {
                    err = AECreateDesc(typeText, "false", 5, result);
                }
                break;
 
            case typeTrue:
                err = AECreateDesc(typeText, "typeTrue", 8, result);
                break;
 
            case typeFalse:
                err = AECreateDesc(typeText, "typeFalse", 9, result);
                break;
                
            case typeQDPoint:
                PLstrcpy(tmpStr, "\pPoint(");
                NumToString( ((PointPtr) dataPtr)->h, tmpStr2);
                PLstrcat(tmpStr, tmpStr2);
                PLstrcat(tmpStr, "\p, ");
                NumToString( ((PointPtr) dataPtr)->v, tmpStr2);
                PLstrcat(tmpStr, tmpStr2);
                PLstrcat(tmpStr, "\p)");
 
                err = AECreateDesc(typeText, &tmpStr[1], tmpStr[0], result);
                break;
                
            case typeQDRectangle:
                PLstrcpy(tmpStr, "\pRect(");
                NumToString( ((RectPtr) dataPtr)->left, tmpStr2);
                PLstrcat(tmpStr, tmpStr2);
                PLstrcat(tmpStr, "\p, ");
                NumToString( ((RectPtr) dataPtr)->top, tmpStr2);
                PLstrcat(tmpStr, tmpStr2);
                PLstrcat(tmpStr, "\p, ");
                NumToString( ((RectPtr) dataPtr)->right, tmpStr2);
                PLstrcat(tmpStr, tmpStr2);
                PLstrcat(tmpStr, "\p, ");
                NumToString( ((RectPtr) dataPtr)->bottom, tmpStr2);
                PLstrcat(tmpStr, tmpStr2);
                PLstrcat(tmpStr, "\p)");
 
                err = AECreateDesc(typeText, &tmpStr[1], tmpStr[0], result);
                break;
 
            default:
                MoreAssertQ(false);
                err = errAECoercionFail;
                break;
        }
        
        return err;
    }
 
    static AECoerceDescUPP gRecordToTextCoerceUPP;          // -> RecordToTextCoerceProc
    
    static pascal OSErr RecordToTextCoerceProc(const AEDesc *fromDesc, DescType toType, long handlerRefCon, AEDesc *toDesc)
        // A coercion handler to convert lists, records, and other things
        // coercible to records, to text.
    {
        #pragma unused(handlerRefCon)
        OSStatus  err;
        Handle    result;
        AEDesc    fromDescAsRecord;
        SInt32    itemIndex;
        SInt32    itemCount;
        AEKeyword thisKeyword;
        AEDesc    thisElement;
        AEDesc    thisElementAsText;
        Size      textSize;
 
        MoreAssertQ(fromDesc != nil);
        MoreAssertQ(toType == typeText);
        MoreAssertQ(toDesc   != nil);
 
        MoreAENullDesc(toDesc);
        MoreAENullDesc(&fromDescAsRecord);
        
        // Create a handle for the resulting text.
        
        result = NewHandle(0);
        err = MoreMemError(result);
        
        // If the incoming data is a record or a list, we handle it natively
        // so just duplicate it into the fromDescAsRecord.  OTOH, if the incoming
        // data is something weird (an object specifier, say), use AECoerceDesc to 
        // coerce it to a record.
        
        if (err == noErr) {
            if (fromDesc->descriptorType == typeAERecord || fromDesc->descriptorType == typeAEList) {
                err = AEDuplicateDesc(fromDesc, &fromDescAsRecord);
            } else {
                err = AECoerceDesc(fromDesc, typeAERecord, &fromDescAsRecord);
            }
        }
        
        // Iterate through each element in the record/list, coercing the element
        // to text and appending its text to the handle.
        
        if (err == noErr) {
            err = AECountItems(&fromDescAsRecord, &itemCount);
        }
        if (err == noErr) {
            err = PtrAndHand("{", result, 1);
            for (itemIndex = 1; itemIndex <= itemCount; itemIndex++) {
                MoreAENullDesc(&thisElement);
                MoreAENullDesc(&thisElementAsText);
 
                err = AEGetNthDesc(&fromDescAsRecord, itemIndex, typeWildCard, &thisKeyword, &thisElement);
                if (err == noErr) {
                    err = AECoerceDesc(&thisElement, typeText, &thisElementAsText);
                    // While writing this code, I spent lots of time figuring out
                    // exactly what type of data couldnÕt be coerced to text and then
                    // filling in that particular coercion.  The following assert
                    // helps detect these problems quickly.
                    MoreAssertQ(err != errAECoercionFail);
                }
                
                // If weÕre iteratintg through a record, the keyword is meaningful
                // so add it to the output handle.
                
                if (err == noErr && fromDescAsRecord.descriptorType == typeAERecord) {
                    err = PtrAndHand("'", result, 1);
                    if (err == noErr) {
                        err = PtrAndHand(&thisKeyword, result, sizeof(thisKeyword));
                    }
                    if (err == noErr) {
                        err = PtrAndHand("':", result, 2);
                    }
                }
                
                // Grow the handle to make room for the text for this element,
                // then copy the text to the handle.
                
                if (err == noErr) {
                    textSize = AEGetDescDataSize(&thisElementAsText);
                    SetHandleSize(result, GetHandleSize(result) + textSize);
                    err = MemError();
                }
                if (err == noErr) {
                    HLock(result);
                    err = AEGetDescData(&thisElementAsText, (*result) + GetHandleSize(result) - textSize, textSize);
                    HUnlock(result);
                }
                
                // If weÕre not the last element, add a separator.
                
                if (err == noErr && itemIndex < itemCount) {
                    err = PtrAndHand(", ", result, 2);
                }
                
                MoreAEDisposeDesc(&thisElement);
                MoreAEDisposeDesc(&thisElementAsText);
                
                if (err != noErr) {
                    break;
                }
            }
        }
        if (err == noErr) {
            err = PtrAndHand("}", result, 1);
        }
        
        // Create a descriptor containing the data from the text handle.
        
        if (err == noErr) {
            HLock(result);
            err = AECreateDesc(typeText, *result, GetHandleSize(result), toDesc);
        }
        
        // Clean up.
        
        if (result != nil) {
            DisposeHandle(result);
            MoreAssertQ(MemError() == noErr);
        }
        MoreAEDisposeDesc(&fromDescAsRecord);
        
        return err;
    }
 
    static void InstallTextCoercionHandlers(void)
        // This routine installs a bunch of coercion handles for
        // coercing various common data types to text.  By adding these
        // coercions, we increase the utility of the rest of MoreBBLog, 
        // which relies on being able to coerce various descriptors to text.
    {
        OSStatus junk;
 
        // First install all the basic coercions.
                
        gBasicToTextCoerceUPP = NewAECoercePtrUPP(BasicToTextCoerceProc);
        MoreAssertQ(gBasicToTextCoerceUPP != nil);
 
        junk = AEInstallCoercionHandler(typeNull, typeText, (AECoercionHandlerUPP) gBasicToTextCoerceUPP, 0, false, false);
        MoreAssertQ(junk == noErr);
 
        junk = AEInstallCoercionHandler(typeObjectBeingExamined, typeText, (AECoercionHandlerUPP) gBasicToTextCoerceUPP, 0, false, false);
        MoreAssertQ(junk == noErr);
 
        junk = AEInstallCoercionHandler(typeCurrentContainer, typeText, (AECoercionHandlerUPP) gBasicToTextCoerceUPP, 0, false, false);
        MoreAssertQ(junk == noErr);
 
        junk = AEInstallCoercionHandler(typeToken, typeText, (AECoercionHandlerUPP) gBasicToTextCoerceUPP, 0, false, false);
        MoreAssertQ(junk == noErr);
 
        junk = AEInstallCoercionHandler(typeAbsoluteOrdinal, typeText, (AECoercionHandlerUPP) gBasicToTextCoerceUPP, 0, false, false);
        MoreAssertQ(junk == noErr);
 
        junk = AEInstallCoercionHandler(typeAlias, typeText, (AECoercionHandlerUPP) gBasicToTextCoerceUPP, 0, false, false);
        MoreAssertQ(junk == noErr);
 
        junk = AEInstallCoercionHandler(typeFSS, typeText, (AECoercionHandlerUPP) gBasicToTextCoerceUPP, 0, false, false);
        MoreAssertQ(junk == noErr);
 
        junk = AEInstallCoercionHandler(typeBoolean, typeText, (AECoercionHandlerUPP) gBasicToTextCoerceUPP, 0, false, false);
        MoreAssertQ(junk == noErr);
 
        junk = AEInstallCoercionHandler(typeTrue, typeText, (AECoercionHandlerUPP) gBasicToTextCoerceUPP, 0, false, false);
        MoreAssertQ(junk == noErr);
 
        junk = AEInstallCoercionHandler(typeFalse, typeText, (AECoercionHandlerUPP) gBasicToTextCoerceUPP, 0, false, false);
        MoreAssertQ(junk == noErr);
 
        junk = AEInstallCoercionHandler(typeQDPoint, typeText, (AECoercionHandlerUPP) gBasicToTextCoerceUPP, 0, false, false);
        MoreAssertQ(junk == noErr);
 
        junk = AEInstallCoercionHandler(typeQDRectangle, typeText, (AECoercionHandlerUPP) gBasicToTextCoerceUPP, 0, false, false);
        MoreAssertQ(junk == noErr);
 
        // Then install the coercions for lists, records, and things coercible
        // to records.
        
        gRecordToTextCoerceUPP = NewAECoerceDescUPP(RecordToTextCoerceProc);
        MoreAssertQ(gRecordToTextCoerceUPP != nil);
 
        junk = AEInstallCoercionHandler(typeAERecord, typeText, (AECoercionHandlerUPP) gRecordToTextCoerceUPP, 0, true, false);
        MoreAssertQ(junk == noErr);
 
        junk = AEInstallCoercionHandler(typeAEList, typeText, (AECoercionHandlerUPP) gRecordToTextCoerceUPP, 0, true, false);
        MoreAssertQ(junk == noErr);
 
        junk = AEInstallCoercionHandler(typeAppleEvent, typeText, (AECoercionHandlerUPP) gRecordToTextCoerceUPP, 0, true, false);
        MoreAssertQ(junk == noErr);
 
        junk = AEInstallCoercionHandler(typeObjectSpecifier, typeText, (AECoercionHandlerUPP) gRecordToTextCoerceUPP, 0, true, false);
        MoreAssertQ(junk == noErr);
 
        junk = AEInstallCoercionHandler(typeRangeDescriptor, typeText, (AECoercionHandlerUPP) gRecordToTextCoerceUPP, 0, true, false);
        MoreAssertQ(junk == noErr);
 
        junk = AEInstallCoercionHandler(typeCompDescriptor, typeText, (AECoercionHandlerUPP) gRecordToTextCoerceUPP, 0, true, false);
        MoreAssertQ(junk == noErr);
 
        junk = AEInstallCoercionHandler(typeLogicalDescriptor, typeText, (AECoercionHandlerUPP) gRecordToTextCoerceUPP, 0, true, false);
        MoreAssertQ(junk == noErr);
    }
 
    static const OSType kBBEditCreator = 'R*ch';
 
    static UInt32  gIndent;     // records the number of levels of indent, as controlled by BBLogIndent etc
    static Boolean gLogging;    // determines whether logging is enabled (true) or not (false)
    
    extern pascal void BBLogStart(Boolean logging)
        // See comment in header file.
    {
        OSStatus    err;
        AppleEvent  theEvent;
        AppleEvent  junkReply;
        DescType    cDoc;
 
        // Start up by installing our coercion handles.  This enables the rest
        // of the routines to provide much nicer output.
        
        InstallTextCoercionHandlers();
        
        gIndent = 0;
        gLogging = logging;
        if (gLogging) {
        
            // Send BBEdit an Apple event to make a new text window.  This
            // will error if BBEdit is running, but the code to launch it
            // is an unnecessary complication.  If you want to see logging,
            // you have to have BBEdit running.
            
            MoreAENullDesc(&theEvent);
            err = MoreAECreateAppleEventCreatorTarget(kAECoreSuite, kAECreateElement, kBBEditCreator, &theEvent);
            if (err == noErr) {
                cDoc = cDocument;
                err = AEPutParamPtr(&theEvent, keyAEObjectClass, typeType, &cDoc, sizeof(cDoc));
            }
            if (err == noErr) {
                err = AESend(&theEvent, &junkReply, kAENoReply, kAENormalPriority, kAEDefaultTimeout, nil, nil);
                if (err == connectionInvalid) {     // BBEdit not running
                    err = noErr;
                }
            }
            MoreAEDisposeDesc(&theEvent);
            MoreAssertQ(err == noErr);
 
        }
    }
    
    extern pascal void BBLogSetState(Boolean logging)
        // See comment in header file.
    {
        gLogging = logging;
    }
        
    extern pascal void BBLogText(void *textPtr, Size textSize)
        // See comment in header file.
    {
        OSStatus    err;
        AppleEvent theEvent;
        AppleEvent junkReply;
 
        MoreAssertQ(textPtr != nil);
 
        if (gLogging) {
        
            // Send BBEdit an insert text ('Nsrt') event.  The text will go
            // into the front window, which is typically the window we created
            // during BBLogStart.
            
            MoreAENullDesc(&theEvent);
            err = MoreAECreateAppleEventCreatorTarget(kBBEditCreator, 'Nsrt', kBBEditCreator, &theEvent);
            if (err == noErr) {
                err = AEPutParamPtr(&theEvent, keyDirectObject, typeText, textPtr, textSize);
            }
            if (err == noErr) {
                err = AESend(&theEvent, &junkReply, kAENoReply, kAENormalPriority, kAEDefaultTimeout, nil, nil);
                if (err == connectionInvalid) {     // BBEdit not running
                    err = noErr;
                }
            }
            MoreAEDisposeDesc(&theEvent);
            MoreAssertQ(err == noErr);
        }
    }
 
    static void BuildLineHeader(Str255 str)
    {
        UInt32 dateTime;
        Str255 tmpStr;
        static Str255 indentStr = "\p                                                                                                  ";
        
        MoreAssertQ(str != nil);
 
        GetDateTime(&dateTime);
        TimeString(dateTime, true, tmpStr, nil);
        (void) PLstrcpy(str, tmpStr);
        (void) PLstrcat(str, "\p ");
        DateString(dateTime, shortDate, tmpStr, nil);
        (void) PLstrcat(str, tmpStr);
        (void) PLstrcat(str, "\p - ");
        indentStr[0] = gIndent * 2;
        (void) PLstrcat(str, indentStr);
    }
    
    extern pascal void BBLogLine(ConstStr255Param str)
        // See comment in header file.
    {
        Str255 tmpStr;
 
        MoreAssertQ(str != nil);
 
        if (gLogging) {
            
            // Add the date and time prefix, the appropriate indent, and the CR suffix.
 
            BuildLineHeader(tmpStr);
            (void) PLstrcat(tmpStr, str);
            (void) PLstrcat(tmpStr, "\p\r");
 
            BBLogText(&tmpStr[1], tmpStr[0]);
        }
    }
    
    extern pascal void BBLogIndent(void)
        // See comment in header file.
    {
        gIndent += 1;
    }
    
    extern pascal void BBLogOutdent(void)
        // See comment in header file.
    {
        MoreAssertQ(gIndent > 0);
        gIndent -= 1;
    }
    
    extern pascal void BBLogOutdentWithErr(OSStatus errNum)
        // See comment in header file.
    {
        Str255 tmpStr;
        Str255 tmpStr2;
        
        BBLogOutdent();
        if (gLogging) {
            if (errNum == noErr) {
                (void) PLstrcpy(tmpStr, "\perr=noErr");
            } else {
                (void) PLstrcpy(tmpStr, "\perr=");
                NumToString(errNum, tmpStr2);
                (void) PLstrcat(tmpStr, tmpStr2);
            }
            BBLogLine(tmpStr);
        }
    }
 
    extern pascal void BBLogAppleEvent(ConstStr255Param tag, const AppleEvent *theEvent)
        // See comment in header file.
    {
        OSStatus     junk;
        AEEventClass classID;
        AEEventID    eventID;
        DescType     junkType;
        Size         junkSize;
        Str255       tmpStr;
        Str255       tmpStr2;
        
        MoreAssertQ(tag      != nil);
        MoreAssertQ(theEvent != nil);
 
        if (gLogging) {
        
            // Get the Apple event class and event IDs.
            
            classID = '????';
            junk = AEGetAttributePtr(theEvent, keyEventClassAttr, typeType, &junkType, &classID, sizeof(classID), &junkSize);
            MoreAssertQ(junk == noErr);
 
            eventID = '????';
            junk = AEGetAttributePtr(theEvent, keyEventIDAttr, typeType, &junkType, &eventID, sizeof(eventID), &junkSize);
            MoreAssertQ(junk == noErr);
 
            // Create a log string out of those IDs.  Currently this
            // is the only information about the event that we log;
            // we might want to extend this in the future, with possibly
            // the parameter list (or at least the direct object).
            
            (void) PLstrcpy(tmpStr, tag);
            (void) PLstrcat(tmpStr, "\p='");
            tmpStr2[0] = 4;
            *((OSType *) &tmpStr2[1]) = classID;
            (void) PLstrcat(tmpStr, tmpStr2);
            (void) PLstrcat(tmpStr, "\p', '");
            tmpStr2[0] = 4;
            *((OSType *) &tmpStr2[1]) = eventID;
            (void) PLstrcat(tmpStr, tmpStr2);
            (void) PLstrcat(tmpStr, "\p'");
 
            BBLogLine(tmpStr);
        }
    }
    
    extern pascal void BBLogLong(ConstStr255Param tag,       SInt32 l)
        // See comment in header file.
    {
        Str255 tmpStr;
        Str255 tmpStr2;
        
        MoreAssertQ(tag != nil);
 
        if (gLogging) {
            (void) PLstrcpy(tmpStr, tag);
            (void) PLstrcat(tmpStr, "\p=");
            NumToString(l, tmpStr2);
            (void) PLstrcat(tmpStr, tmpStr2);
 
            BBLogLine(tmpStr);
        }
    }
    
    extern pascal void BBLogDesc(ConstStr255Param tag,       const AEDesc *desc)
        // See comment in header file.
    {
        OSStatus    err;
        AEDesc      coercedDesc;
        Str255      descStr;
        Str255      tmpStr;
        Handle      descText;
        
        MoreAssertQ(tag  != nil);
        MoreAssertQ(desc != nil);
        
        if (gLogging) {
            MoreAENullDesc(&coercedDesc);
            descText = nil;
 
            // First try coercing the descriptor to text.
            // If we succeed, then we use the resulting textual
            // description of the descriptor.  If we fail,
            // we just log the descriptor type and size
            
            err = AECoerceDesc(desc, typeText, &coercedDesc);
            if (err == noErr) {
                err = MoreAECopyDescriptorDataToHandle(&coercedDesc, &descText);
                
                // If the original descriptor type was text, add
                // some Ò Ó around the text.
                
                if (err == noErr && desc->descriptorType == typeText) {
                    (void) Munger(descText, 0, nil, 0, "Ò", 1);
                    err = MemError();
                    
                    if (err == noErr) {
                        err = PtrAndHand("Ó", descText, 1);
                    }
                }
            } else {
                tmpStr[0] = 4;
                *((OSType *) &tmpStr[1]) = desc->descriptorType;
                (void) PLstrcpy(descStr, "\p'");
                (void) PLstrcat(descStr, tmpStr);
                (void) PLstrcat(descStr, "\p'");
                if (desc->dataHandle != nil) {
                    (void) PLstrcat(descStr, "\p, size=");
                    NumToString(AEGetDescDataSize(desc), tmpStr);
                    (void) PLstrcat(descStr, tmpStr);
                }
                err = PtrToHand(&descStr[1], &descText, descStr[0]);
            }
 
            // Now that descText is set up to contain the textual
            // representation of desc, we just log it.  We also
            // log the original type of the descriptor (in square
            // brackets).
            
            // First prepend the line header and the tag.
 
            if (err == noErr) {
                BuildLineHeader(tmpStr);
                (void) PLstrcat(tmpStr, tag);
                (void) PLstrcat(tmpStr, "\p=");
 
                (void) Munger(descText, 0, nil, 0, &tmpStr[1], tmpStr[0]);
                err = MemError();
            }
 
            // Then append the original descriptor type and the CR.
            
            if (err == noErr) {
                (void) PLstrcpy(tmpStr, "\p [");
                descStr[0] = 4;
                *((OSType *) &descStr[1]) = desc->descriptorType;
                (void) PLstrcat(tmpStr, descStr);
                (void) PLstrcat(tmpStr, "\p]\x0d");
                
                err = PtrAndHand(&tmpStr[1], descText, tmpStr[0]);
            }
 
            // Finally, log the text.
            
            if (err == noErr) {             
                HLock(descText);
                BBLogText(*descText, GetHandleSize(descText));
            }
 
            // Clean up.
            
            if (descText != nil) {
                DisposeHandle(descText);
                MoreAssertQ(MemError() == noErr);
            }
            MoreAEDisposeDesc(&coercedDesc);
        }
    }
    
    extern pascal void BBLogDescType(ConstStr255Param tag,   DescType theType)
        // See comment in header file.
    {
        Str255 tmpStr;
        Str255 tmpStr2;
 
        MoreAssertQ(tag != nil);
        
        if (gLogging) {
            (void) PLstrcpy(tmpStr, tag);
            (void) PLstrcat(tmpStr, "\p='");
            tmpStr2[0] = 4;
            *((OSType *) &tmpStr2[1]) = theType;
            (void) PLstrcat(tmpStr, tmpStr2);
            (void) PLstrcat(tmpStr, "\p'");
            
            BBLogLine(tmpStr);
        }
    }
 
#endif // MORE_DEBUG