/*+++
 *  title:      objcTypeDecode.m
 *  abstract:   objc-encoded type decoding functions.
 *  author:     Tom Hageman <tom@basil.icce.rug.nl>
 *  created:    December 1998
 *  modified:
 *  copyleft:
 *
 *	Copyright (C) 1998  Tom R. Hageman.
 *
 *	You may freely copy, distribute, and reuse the code in this file.
 *
 *	NO WARRANTY:
 *	ANY IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR A
 *	PARTICULAR PURPOSE IS HEREBY DISCLAIMED.  IN NO EVENT WILL THE
 *	AFOREMENTIONED PARTIES BE LIABLE FOR DAMAGES, INCLUDING ANY
 *	GENERAL, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING
 *	OUT OF THE USE OF OR INABILITY TO USE THIS CODE.
 *
 *  description:
 *      (see corresponding *.h file)
 *---*/

#ifdef RCS_ID
static const char * const RCSid = ((void)&RCSid,
	"@(#)objcTypeDecode.m,v 1.7 1999/01/10 13:58:27 tom Exp");
#   define RCS_objcTypeDecode_ID
#endif

#import "objcTypeDecode.h"

#include <stdio.h>
#include <string.h>
#include <ctype.h>


/* Support functions. */

#if __GNUC__

/* Hack around the lack of long long support in NeXT's printf() */

#include <limits.h>

static void ulltoa(char *buf, unsigned long long value)
{
	if (value <= ULONG_MAX)
	{
		sprintf(buf, "%lu", (unsigned long)value);
	}
	else
	{
		/* Chicken out by printing hex value (assumes 32-bit long) */
		union { unsigned long long ull; unsigned long ul[2]; } u;
#if __BIG_ENDIAN__
		enum { HI=0, LO };
#else
		enum { LO=0, HI };
#endif
		u.ull = value;
		sprintf(buf, "0x%lx%08lx", u.ul[HI], u.ul[LO]);
	}
}

static void lltoa(char *buf, long long value)
{
	if (LONG_MIN <= value && value <= LONG_MAX)
	{
		sprintf(buf, "%ld", (long)value);
	}
	else
	{
		ulltoa(buf, value);
	}
}

#endif /* __GNUC__ */


static const char *decode_objc_encoded_struct_or_union(char *buf, size_t offset,
						       size_t bufsize,
						       const char *type,
						       char endc,
						       unsigned options)
{
	/* Assumes start typespec + name of struct/union has been skipped. */
	char t;
#if 0 // NOTYET
	size_t len;

	// XXX TODO handle verbose expansion.
	if (!(options & DECODE_OBJC_VERBOSE))
#endif
		bufsize = 0;

	// skip or expand field type specs.
	while ((t = *type) && t != endc)
	{
		type = decode_objc_encoded_type(buf + offset, bufsize, type, options);
#if 0 //NOTYET
		len += strlen(buf + offset);
		offset += len;
		bufsize -= len;
#endif
	}
	if (t) ++type;
	return type;
}

/* Returns updated position in type string. */
const char *decode_objc_encoded_type(char *buf, size_t bufsize,
				     const char *type,
				     unsigned options)
{
	const char *name;
	int namelen;
	size_t offset = 0;
	int pointers = 0;
	BOOL done = NO;
	char t;

	if (bufsize > 0) buf[0] = '\0';

	while (!done && *type)
	{
		name = NULL;
		namelen = 0;
		done = YES;

		switch (t = *type++)
		{
			// Simple types.
		case _C_CLASS:	name = "Class";				break;
		case _C_SEL:	name = "SEL";				break;
		case _C_CHR:	name = "char";				break;
		case _C_UCHR:	name = "unsigned char";			break;
		case _C_SHT:	name = "short";				break;
		case _C_USHT:	name = "unsigned short";		break;
		case _C_INT:	name = "int";				break;
		case _C_UINT:	name = "unsigned int";			break;
		case _C_LNG:	name = "long";				break;
		case _C_ULNG:	name = "unsigned long";			break;
		case _C_LLNG:	name = "long long";			break;
		case _C_ULLNG:	name = "unsigned long long";		break;
		case _C_FLT:	name = "float";				break;
		case _C_DBL:	name = "double";			break;
		case _C_VOID:	name = "void";				break;
		case _C_ATOM:	name = "NXAtom";			break;
		case _C_CHARPTR: name = "char *";			break;

			// Complicated types.
		case _C_UNDEF:
			name = (pointers) ? "/*UNKNOWN*/ void" : "/*UNKNOWN*/";
			break;

		case _C_ID:
			/* May be annotated with class name in GNU runtime;
			   not sure for NeXT, but probably won't hurt. */
			if (*type == '"')
			{
				name = ++type;
				while (*type && *type++ != '"') ;
				namelen = type - name - 1;
			}
			if (namelen <= 0) name = "id";
			break;

		case _C_PTR:
			++pointers;
			done = NO;
			continue;

		case _C_BFLD:
			name = type;
			while (isdigit(*type)) ++type;
			namelen = type - name - 1;
			if (offset + (offset > 0) + 8 + 1 + 1 + 1 > bufsize)
			{
				offset = bufsize;
				continue;
			}
			if (offset > 0) buf[offset++] = ' ';
			strcpy(buf + offset, "unsigned :");
			offset += 8 + 1 + 1;
			break;

		case _C_ARY_B:
			name = type;
			while (isdigit(*type)) ++type;
			namelen = type - name;
			type = decode_objc_encoded_type(buf + offset, bufsize - offset, type, options);
			while (*type && *type++ != _C_ARY_E) ;

			if (offset >= bufsize) continue;

			offset += strlen(buf + offset);
			if (offset + 1 + namelen + 1 + 1 > bufsize)
			{
				offset = bufsize;
				continue;
			}
			buf[offset++] = '[';
			strncpy(buf + offset, name, namelen);
			offset += namelen;
			buf[offset++] = ']';
			buf[offset] = '\0';

			continue;

		case _C_UNION_B:
			name = type;
#if NeXT_RUNTIME
			/* In NeXT runtime, union does not have name in
			   method definition, but it does in ivar def (Ugh!)
			   In GNU runtime, name is always present. */
		    if ((options & DECODE_OBJC_KIND_MASK) != DECODE_OBJC_METHOD_TYPE)
#endif
			while (*type && *type != '=' && *type != _C_STRUCT_E) ++type;
			namelen = type - name;
			if (*type == '=') ++type;
			if (namelen <= 0 || (namelen == 1 && *name == '?'))
			{
				name = "UNKNOWN", namelen = 7;
			}
			if (offset + (offset > 0) + 5 + 1 + namelen + 1 > bufsize)
			{
				offset = bufsize;
			}
			else
			{
				if (offset > 0) buf[offset++] = ' ';
				sprintf(buf + offset, "union %.*s", namelen, name);
				offset += 5 + 1 + namelen;
			}
			type = decode_objc_encoded_struct_or_union(buf, offset, bufsize, type, _C_UNION_E, options);
			if (offset < bufsize) offset += strlen(buf + offset);
			continue;

		case _C_STRUCT_B:
			name = type;
			while (*type && *type != '=' && *type != _C_STRUCT_E) ++type;
			namelen = type - name;
			if (*type == '=') ++type;
			if (namelen <= 0 || (namelen == 1 && *name == '?'))
			{
				name = "UNKNOWN", namelen = 7;
			}
			if (offset + (offset > 0) + 6 + 1 + namelen + 1 > bufsize)
			{
				offset = bufsize;
			}
			else
			{
				if (offset > 0) buf[offset++] = ' ';
				sprintf(buf + offset, "struct %.*s", namelen, name);
				offset += 6 + 1 + namelen;
			}
			type = decode_objc_encoded_struct_or_union(buf, offset, bufsize, type, _C_STRUCT_E, options);
			if (offset < bufsize) offset += strlen(buf + offset);
			continue;

			// XXX TODO: factor out more common code in struct/union.

			/* Protocol qualifier encodings: */
		case _C_CONST:	name = "const";		done = NO;	break;
		case _C_IN:	name = "in";		done = NO;	break;
		case _C_INOUT:	name = "inout";		done = NO;	break;
		case _C_OUT:	name = "out";		done = NO;	break;
		case _C_BYCOPY:	name = "bycopy";	done = NO;	break;
		case _C_ONEWAY:	name = "oneway";	done = NO;	break;

		default:
			--type;
			continue; // done == YES, so actually exit the loop.
		}

		/* Append name to buf, at offset.  Prepend blank if needed.*/

		if (namelen <= 0) namelen = strlen(name);

		if (offset + namelen + (offset > 0) + 1 > bufsize)
		{
			offset = bufsize;
			continue;
		}
		if (offset > 0) buf[offset++] = ' ';
		strncpy(buf + offset, name, namelen);
		offset += namelen;
		buf[offset] = '\0';
	}

	if (pointers)
	{
		if (offset == 0)
		{
			name = "void *", namelen = 6;
		}
		else if (buf[offset-1] != '*')
		{
			name = " *", namelen = 2;
		}
		else
		{
			name = "*", namelen = 1;
		}

		do
		{
			if (offset + namelen + 1 > bufsize)
			{
				offset = bufsize;
				break;
			}
			strcpy(buf + offset, name);
			offset += namelen;
			name = "*", namelen = 1;
		}
		while (--pointers);
	}

	return type;
}


#ifndef GNU_RUNTIME
static __inline__ const char *objc_skip_type_qualifiers(const char *type)
{
	for (;;)
	{
		switch (*type)
		{
		case _C_CONST:
		case _C_IN:
		case _C_INOUT:
		case _C_OUT:
		case _C_BYCOPY:
		case _C_ONEWAY:
			++type;
			continue;

		default:
			return type;
		}
	}
	/* NOTREACHED */
	return type;
}
#endif /* GNU_RUNTIME */


static const char *format_of_objc_encoded_type(const char *type)
{
	type = objc_skip_type_qualifiers(type);

	switch (*type)
	{
	case _C_ID:		return "0x%06lx";
	case _C_CLASS:		return "<%s>";
	case _C_SEL:		return "\"%s\"";
	case _C_CHR:		return "'%c'";
	case _C_UCHR:		return "'%c'";
	case _C_SHT:		return "%hd";
	case _C_USHT:		return "%hu";
	case _C_INT:		return "%d";
	case _C_UINT:		return "%u";
	case _C_LNG:		return "%ld";
	case _C_ULNG:		return "%lu";
	case _C_FLT:		return "%g";
	case _C_DBL:		return "%g";
	case _C_BFLD:		return "?";		// cannot really print bitfields.
	case _C_VOID:		return "?";		// should not happen.
	case _C_UNDEF:		return "0x%08lx";	// best we can do...
	case _C_PTR:		return "0x%06lx";
	case _C_ATOM:
	case _C_CHARPTR:	return "\"%s\"";
	case _C_ARY_B:		return "0x%06lx";
	case _C_UNION_B:	return "0x%08lx";	// Hmmm...
	case _C_STRUCT_B:	return "0x%08lx";	// ...could do better.

	case '\0':		return "<invalid type description>";

	default:		break;
	}
	return "0x%lx";
}


void decode_objc_encoded_value_for_type(char *buf, size_t bufsize,
					const char *type,
					const void *valptr,
					unsigned options)
{
	// XXX this does not yet use bufsize to limit result string length.
	const char *format;

	type = objc_skip_type_qualifiers(type);
	format = format_of_objc_encoded_type(type);

	if (bufsize) buf[0] = '\0';

	switch (*type)
	{
	case _C_ID:
		{
			id object = *(id *)valptr;

			if (object == nil)
			{
				strcpy(buf, "nil");
				break;
			}
			if (options & DECODE_OBJC_VERBOSE)
			{
				/* (id) arguments are habitually abused to pass non-pointer
				   values, so this, while nice, may in turn cause a crash. */
				if (IS_VALID_POINTER_OF_TYPE(object, id))
				{
					const char *className = object_getClassName(object);

					sprintf(buf, IS_CLASS(object) ? "<%s>" : "<%s:0x%06lx>",
						className ? className : "(unknown)",
						(unsigned long)object);
				}
				else
				{
					sprintf(buf, "0x%lx", (unsigned long)object);
				}
			}
			else
			{
				sprintf(buf, format, object);
			}
		}
		break;

	case _C_CLASS:
		{
			Class class = *(Class *)valptr;

			if (class == Nil) {
				strcpy(buf, "Nil");
				break;
			}
			sprintf(buf, format, object_getClassName(class));
		}
		break;

	case _C_SEL:
		{
			SEL sel = *(SEL *)valptr;

			if (sel == NULL) {
				strcpy(buf, "NULL");
				break;
			}
			sprintf(buf, format, sel_getName(sel));
		}
		break;

	case _C_CHR:
	case _C_UCHR:
		{
			unsigned char c = *(unsigned char *)valptr;

			if (!isprint(c)) {
				sprintf(buf, "'\\x%x'", c);
				break;
			}
			sprintf(buf, format, c);
		}
		break;

	case _C_SHT:
	case _C_USHT:
		sprintf(buf, format, *(unsigned short *)valptr);
		break;

	case _C_INT:
	case _C_UINT:
		sprintf(buf, format, *(unsigned int *)valptr);
		break;

	case _C_LNG:
	case _C_ULNG:
		sprintf(buf, format, *(unsigned long *)valptr);
		break;

#if __GNUC__
	case _C_LLNG:
		lltoa(buf, *(long long *)valptr);
		break;
	case _C_ULLNG:
		ulltoa(buf, *(unsigned long long *)valptr);
		break;
#endif

	case _C_FLT:
		sprintf(buf, format, *(float *)valptr);
		break;

	case _C_DBL:
		sprintf(buf, format, *(double *)valptr);
		break;

	case _C_BFLD:
	case _C_VOID:
		strcpy(buf, format);
		break;

	case _C_UNDEF:
	case _C_PTR:
		sprintf(buf, format, *(void **)valptr);
		break;

	case _C_ATOM:
	case _C_CHARPTR:
		{
			const char *str = *(const char **)valptr;

			if (str == NULL)
			{
				strcpy(buf, "NULL");
				break;
			}
			if (strlen(str) > (bufsize-2-1))
			{
				sprintf(buf, "\"%.*s...\"", (int)(bufsize-2-3-1), str);
				break;
			}
			sprintf(buf, format, str);
		}
		break;

	case _C_ARY_B:
		sprintf(buf, format, *(void **)valptr);
		break;

	case _C_UNION_B:
		sprintf(buf, format, *(long *)valptr);
		break;

	case _C_STRUCT_B:
		// This can in principle be handled a lot better, since the
		// type information is available.
		sprintf(buf, format, *(long *)valptr);
		break;

	default:
		sprintf(buf, format, *(void **)valptr);
		break;
	}
}
