//
// Time-stamp: <94/12/01 20:23:30 stephan>
//
//	MiscClassDecoder.m -- an Object subclass that is able to analyze
//	the definition of an Objective-C class.
//	
//		Written by Stephan Wacker <stephan@rodion.muc.de>
//		Copyright (c) 1994 by Stephan Wacker.
//		Version 1.0  All rights reserved.
//		This notice may not be removed from this source code.
//
//	This object is included in the MiscKit by permission from the author
//	and its use is governed by the MiscKit license, found in the file
//	"LICENSE.rtf" in the MiscKit distribution.  Please refer to that file
//	for a list of all applicable permissions and restrictions.
//	


#import "MiscClassDecoder.h"

#import <objc/Protocol.h>
#import <objc/objc-class.h>
#import <objc/objc-runtime.h>
#import <ctype.h>


#define K_IndentInstanceVar	4
#define K_IndentStructMember	2


//====<   Private methods   >====//

@interface MiscClassDecoder ( Private )

- (void)clearText;
- (void)appendText: (NSString *) aString, ...;
- (void)appendLine: (NSString *) aString, ...;
- (void)finishOutput;

- (const char *)showType:(const char *)aTypeString;
- (void)showMethod: (Method) aMethod prefix: (NSString *) prefix;

- (void)showInheritance:(Class)aClass;
- (void)showProtocols:(Class)aClass;
- (void)showInstanceVars:(Class)aClass;
- (void)showInstanceMethods:(Class)aClass;
- (void)showClassMethods:(Class)aMetaclass;

+ (void)initialize;
- (id)initWithCoder:(NSCoder *)aDecoder;
- (void)encodeWithCoder:(NSCoder *)aCoder;

@end // MiscClassDecoder ( Private )


//====<   Auxiliary functions   >====//

static
NSString * type_name( char type )
// Decode single character `type'.
{
    NSString	*result = NULL;
    
    switch( type )
    {
	case _C_ID	: result = @"id";		break;
	case _C_CLASS	: result = @"Class";		break;
	case _C_SEL	: result = @"SEL";		break;
	case _C_CHR	: result = @"char";		break;
	case _C_UCHR	: result = @"unsigned char";	break;
	case _C_SHT	: result = @"short";		break;
	case _C_USHT	: result = @"unsigned short";	break;
	case _C_INT	: result = @"int";		break;
	case _C_UINT	: result = @"unsigned int";	break;
	case _C_LNG	: result = @"long";		break;
	case _C_ULNG	: result = @"unsigned long";	break;
	case _C_FLT	: result = @"float";		break;
	case _C_DBL	: result = @"double";		break;
	case _C_VOID	: result = @"void";		break;
	case _C_UNDEF	: result = @"<undef>";		break;
	case _C_CHARPTR	: result = @"char *";		break;
    }
    
    return result;

} // type_name()


static
NSString * struct_name( NSString *name )
// Check for well-known struct names.
{
    static NSSet* struct_set = 0;
    if (struct_set == 0)
	{
	struct_set = [[NSSet alloc] initWithObjects:
		@"_NSFSM",
		@"_NSLay",
		@"_NSRun",
		@"_NSRect",
		@"_NSSize",
		@"_NSZone",
		@"_NSPoint",
		@"_NSSelPt",
		@"_NSLayInfo",
		@"_NSTabStop",
		@"_NSLayArray",
		@"_NSRunArray",
		@"_NSTextChunk",
		@"_NSCharArray",
		@"_NSTextBlock",
		@"_NSTextCache",
		@"_NSTextStyle",
		@"_NSBreakArray",
		@"_NSHeightInfo",
		@"_NSWidthArray",
		@"_NSHeightChange",
		@"_NSModalSession",
		0 ];
	}

    if ([struct_set containsObject:name])
	return [name substringFromIndex:1];
    else
	return 0;

} // structName()


@implementation MiscClassDecoder


//====<   Writing text   >====//

- (id)init
    {
    [super init];
    classNameField = 0;
    text = 0;
    string = [[NSMutableString allocWithZone:[self zone]] init];
    indent = 0;
    bol = YES;
    lineCount = 0;
    return self;
    }

- (void) dealloc
    {
    [string release];
    [super dealloc];
    }

- (void)clearText
// Delete contents of `text'.
{
    [string setString:@""];
    [text setString:@""];

    indent = 0, bol = YES, lineCount = 0; 
} // clearText


- (void)appendText: (NSString *) aString, ...
// Append `aString' to the contents of `text'.
{
    va_list	ap;
    NSString*	s;
    
    if( bol )
    {
	[string appendFormat:@"%*s", indent, ""];
	bol = NO;
    }

    va_start(ap, aString);
    s = [[NSString alloc] initWithFormat:aString arguments:ap];
    va_end(ap);

    [string appendString:s];
    [s release];

} // appendText:...


- (void)appendLine: (NSString *) aString, ...
// Append `aString' to `text' and start a new line.
{
    va_list	ap;
    NSString*	s;
    
    if( bol )
	[string appendFormat:@"%*s", indent, ""];
    else
	bol = YES;

    va_start(ap, aString);
    s = [[NSString alloc] initWithFormat:aString arguments:ap];
    va_end(ap);

    [string appendString:s];
    [s release];
    [string appendString:@"\n"];

    ++ lineCount;

} // appendLine:...


- (void)finishOutput
// Final actions after all text has been output.
{
    [text setString:string];
    [classNameField selectText:self];
    
} // finishOutput

//====<   Decoding type specifications   >====//

- (const char *)showType:(const char *)aTypeString
// Recursive interpretation of the encoded `aTypeString'.
// Return position after analyzed substring.
{
    NSString	*typeName;
    const char	*p = aTypeString;
    const char	*pe;
    const char	*memberName;
    int		i;

    switch( *p )
    {
      case _C_ID:
	if (*(p+1) == '"')
	    {
	    p = [self showType:p+1];
	    [self appendText: @" *"];
	    }
	else
	    {
	    p++;
	    [self appendText: @"id"];
	    }
	break;

      case _C_PTR:		// Pointer
	p = [self showType:p+1];
	[self appendText: @" *"];
	break;
	
      case _C_BFLD:		// Bitfield
	for( ++p, i = 0; isdigit(*p); p++ )
	{
	    i = i * 10 + (*p - '0');
	}
	// We don't really know the bitfield's type,
	// but `unsigned int' is a good guess.
	[self appendText: @"unsigned int : %d", i];
	break;
	
      case _C_ARY_B:		// Array begin
	for( ++p, i = 0; isdigit(*p); p++ )
	{
	    i = i * 10 + (*p - '0');
	}
	p = [self showType:p];
	[self appendText: @" [%d]", i];
	// TODO: correct syntax

	if( *p == _C_ARY_E )	// Array end
	{
	    ++p;
	}
	else
	{
	    // TODO: report error
	
	    // TODO: report error
	}
	break;
	
	
      case _C_STRUCT_B:		// Struct begin
	for( pe = ++p; *pe && *pe != '=' && *pe != _C_STRUCT_E; pe++ )
	{
	    // keep on going
	
	    // keep on going
	}
	    
	if( (typeName = struct_name(
		[NSString stringWithCString:p length:pe-p] )) != NULL )
				// it's a well-known struct
	{
	    [self appendText: @"%s", [typeName cString]];
	    for( p = pe-1, i = 1; *++p && i > 0; ) {
		switch( *p ) {
		  case _C_STRUCT_B: i++; break;
		  case _C_STRUCT_E: i--; break;
		}
	    } // for p
	}
	else
	{
	    [self appendText: @"struct %.*s", pe-p, p];
	    if( *(p=pe) == '=' ) {
		[self appendLine: @" {"];
		indent += K_IndentStructMember;
		for( i = 1, ++p; *p && *p != _C_STRUCT_E; i++ )
		{
		    if( *p == '"' )
		    {
			for( memberName = pe = p+1; *pe && *pe != '"'; pe++ )
			{
			    // keep on going
			
			    // keep on going
			}
			p = pe+1;
		    }
		    else
		    {
			memberName = NULL;
		    }
		    p = [self showType:p];
		    if( memberName )
		    {
			[self appendLine: @" %.*s;",
			 pe-memberName, memberName];
		    }
		    else
		    {
			[self appendLine: @" member_%d;", i];
		    }
		}
		indent -= K_IndentStructMember;
		[self appendText: @"}"];
	    }
	}

	if( *p == _C_STRUCT_E )	// Struct end
	{
	    ++p;
	}
	else
	{
	    // TODO: report error
	
	    // TODO: report error
	}
	break;
	
	
      case _C_UNION_B:		// Union begin
	[self appendLine: @"union {"];
	indent += K_IndentStructMember;
	for( i = 1, ++p; *p && *p != _C_UNION_E; i++ )
	{
	    p = [self showType:p];
	    [self appendLine: @" case_%d;", i];
	}
	indent -= K_IndentStructMember;
	[self appendText: @"}"];
	
	if( *p == _C_UNION_E )
	{
	    ++p;
	}
	else
	{
	    // TODO: report error
	
	    // TODO: report error
	}
	break;
	
      case '"':
	for( pe = ++p; *pe && *pe != '"'; pe++ ) /*empty*/ ;
	    [self appendText: @"%.*s", pe-p, p];
	p = pe + 1;
	break;
	
      default:
	if( ! (typeName = type_name( *p ) ))
	{
	    [self appendText: @"<?: %c>", *p ];
	}
	else
	{
	    [self appendText: @"%s", [typeName cString]];
	}
	++ p;
	break;
    
    } // switch( first char of aTypeString )
    
    return p;

} // showType:


- (void)showMethod: (Method) aMethod prefix: (NSString *) prefix
// Show declaration of `aMethod'.
// `prefix' is either "-" or "+".
{
    NSString	*name = NSStringFromSelector(aMethod->method_name);
    const char	*p, *pe;
    int		nArgs = method_getNumberOfArguments( aMethod );
    int		i;
    char	*type;
    int		offset;
    
    
    if( prefix )
    {
	[self appendText: @"%s ", [prefix cString]];
    }
    
    if( nArgs <= 2 )
    {
	[self appendLine: @"%s;", [name cString]];
    }
    else
    {
	for( i = 2, p = [name cString]; i < nArgs; i++, p = pe )
	{
	    for( pe = p; *pe && *pe++ != ':'; )
	    {
		// keep on going
	    
		// keep on going
	    }
	    [self appendText: @"%s%.*s", (i == 2 ? "" : " "), pe-p, p];
	    
	    method_getArgumentInfo( aMethod, i, &type, &offset );

	    [self appendText: @" ("];
	    [self showType:type];
	    [self appendText: @")"];

	    [self appendText: @" arg_%d", i-1];

	} // for i, p
	
	[self appendLine: @";"];
    }

} // showMethod:prefix:


//====<   Showing the class definition   >====//

- (void)showInheritance:(Class)aClass
// Show name of `aClass' and its superclass.
{
    Class	superClass = [aClass superclass];
    
    [self appendText: @"%s", [[aClass description] cString]];
    if( superClass )
    {
	[self appendLine: @" : %s", [[superClass description] cString]];
    }
    else
    {
	[self appendLine: @" // root class"];
    } 
} // showInheritance:


- (void)showProtocols:(Class)aClass
// Show all protocols adopted by `aClass'.
{
    if( ! aClass->protocols || aClass->protocols->count == 0 )
    {
	// do nothing
    
	// do nothing
    }
    else
    {
	int		i;
	Protocol	**pp = aClass->protocols->list;
	
	[self appendText: @"  < %s", [*pp name]];
	
	for( i = 1, pp++; i < aClass->protocols->count; i++, pp++ )
	{
	    [self appendText: @", %s", [*pp name]];
	}
	
	[self appendLine: @" >"];
    } 
} // showProtocols:


- (void)showInstanceVars:(Class)aClass
// Show definition of all instance variables defined in `aClass'.
{
    [self appendLine: @"{"];
    indent += K_IndentInstanceVar;
    
    if( ! aClass->ivars )
    {
	[self appendLine: @"// no instance variables"];
    }
    else
    {
	int	i;
	Ivar	ivp = aClass->ivars->ivar_list;
	
	for( i = 0; i < aClass->ivars->ivar_count; i++, ivp++ )
	{
	    // [self appendLine: @"// %s", ivp->ivar_type];
	    [self showType:ivp->ivar_type];
	    [self appendLine: @" %s;", ivp->ivar_name];
	} // for i
    }
    
    indent -= K_IndentInstanceVar;
    [self appendLine: @"}"]; 
} // showInstanceVars:


- (void)showInstanceMethods:(Class)aClass
// Show declaration of all instance methods defined in `aClass'.
{
    if( ! aClass->methodLists )
    {
	[self appendLine: @"// no instance methods"];
    }
    else
    {
	void	*iterator = 0;
	struct objc_method_list	*mlp;
	int	i, m;
	Method	mp;
	
	for( m = 1;
	     (mlp = class_nextMethodList(aClass, &iterator)) != NULL;
	     m++ )
	{
	    [self appendLine: @""];
	    [self appendLine: @"// Instance method block #%d holds %d method%s",
	     m, mlp->method_count, (mlp->method_count == 1 ? "" : "s")];

	    for( i = 0, mp = mlp->method_list;
	         i < mlp->method_count;
		 i++, mp++ )
	    {
		[self showMethod: mp prefix: @"-"];
	    } // for i
	} // for m, mlp
    } 
} // showInstanceMethods:


- (void)showClassMethods:(Class)aMetaclass
// Show declaration of all class methods defined in aMetaclass.
{
    if( ! aMetaclass->methodLists )
    {
	[self appendLine: @"// no class methods"];
    }
    else
    {
	struct objc_method_list	*mlp;
	void	*iterator = 0;
	int	i, m;
	Method	mp;
	
	for( m = 1;
	     (mlp = class_nextMethodList(aMetaclass, &iterator)) != NULL;
	     m++)
	{
	    [self appendLine: @""];
	    [self appendLine: @"// Class method block #%d holds %d method%s",
	     m, mlp->method_count, (mlp->method_count == 1 ? "" : "s")];

	    for( i = 0, mp = mlp->method_list;
	         i < mlp->method_count;
		 i++, mp++ )
	    {
		[self showMethod: mp prefix: @"+"];
	    } // for i
	} // for m, mlp
    } 
} // showClassMethods:


//====<   Actions   >====//

- (void)analyze:sender
// Analyze class specified in `classNameField'.
{
    NSString	*className = [classNameField stringValue];
    Class	class;

    [self clearText];
    
    if( !(class = NSClassFromString(className)) )
    {
	[self appendLine: @"No class named \"%s\".", [className cString]];
    }
    else
    {
	[self appendLine: @"// Definition of class %s, Version %d",
	 [className cString], class_getVersion(class)];
	[self appendLine: @""];
	[self appendText: @"@interface "];
	[self showInheritance:class];
	[self showProtocols:class];
	[self showInstanceVars:class];
	[self appendLine: @""];
	[self showClassMethods:class->isa];
	[self appendLine: @""];
	[self showInstanceMethods:class];
	[self appendLine: @""];
	[self appendLine: @"@end"];
    }

    [self finishOutput]; 
} // analyze:


- (void)list:sender
// List all known classes.
{
    NXHashTable	*classes = objc_getClasses();
    NXHashState	state = NXInitHashState(classes);
    Class	aClass;

    [self clearText];
    
    while( NXNextHashState(classes, &state, (void **)&aClass) )
    {
	NS_DURING
	    [self appendLine: @"%s", [[aClass description] cString]];
	NS_HANDLER
	NS_ENDHANDLER
    }

    [self finishOutput]; 
} // list:


//====<   Services   >====//

- (void)analyzeClass:(NSString *)className
// Analyze class given in `className'.
{
    [classNameField setStringValue:className];
    [self analyze:self];
}



//====<   Archiving   >====//

+ (void)initialize
{
    if( self == [MiscClassDecoder class] )
    {
	[self setVersion: K_MiscClassDecoderVersion];
    }

} // +initialize


- (id)initWithCoder:(NSCoder *)aDecoder
{
    int version = [aDecoder versionForClassName:@"MiscClassDecoder"];
    
    switch( version )
    {
      case 0:
	classNameField = 0;
	text = 0;
	break;

      case 1:
	[aDecoder decodeValuesOfObjCTypes:"@@", &classNameField, &text];
	break;

      default:
	classNameField = [[aDecoder decodeObject] retain];
	text = [[aDecoder decodeObject] retain];
	break;
    }

    // Initialize non-archived instance variables.
    //
    string = [[NSMutableString allocWithZone:[self zone]] init];
    indent = 0;
    bol = FALSE;
    lineCount = 0;
    
    return self;

} // initWithCoder:


- (void)encodeWithCoder:(NSCoder *)aCoder
{
    [aCoder encodeConditionalObject:classNameField];
    [aCoder encodeConditionalObject:text];
} // encodeWithCoder:

@end	// MiscClassDecoder
