 /* GNU Objective C Runtime archiving
   Copyright (C) 1993, 1995, 1996, 1997, 2002, 2004, 2009 Free Software Foundation, Inc.
   Contributed by Kresten Krab Thorup

This file is part of GCC.

GCC is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation; either version 3, or (at your option) any later version.

GCC is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
details.

Under Section 7 of GPL version 3, you are granted additional
permissions described in the GCC Runtime Library Exception, version
3.1, as published by the Free Software Foundation.

You should have received a copy of the GNU General Public License and
a copy of the GCC Runtime Library Exception along with this program;
see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
<http://www.gnu.org/licenses/>.  */

#include "tconfig.h"
#include "objc/runtime.h"
#include "objc/typedstream.h"
#include "objc/encoding.h"
#include <stdlib.h>

extern int fflush (FILE *);

#define ROUND(V, A) \
  ({ typeof (V) __v = (V); typeof (A) __a = (A);  \
     __a * ((__v + __a - 1)/__a); })

#define PTR2LONG(P) (((char *) (P))-(char *) 0)
#define LONG2PTR(L) (((char *) 0) + (L))

/* Declare some functions... */

static int
objc_read_class (struct objc_typed_stream *stream, Class *class);

int objc_sizeof_type (const char *type);

static int
objc_write_use_common (struct objc_typed_stream *stream, unsigned long key);

static int
objc_write_register_common (struct objc_typed_stream *stream,
			    unsigned long key);

static int 
objc_write_class (struct objc_typed_stream *stream,
			 struct objc_class *class);

const char *objc_skip_type (const char *type);

static void __objc_finish_write_root_object (struct objc_typed_stream *);
static void __objc_finish_read_root_object (struct objc_typed_stream *);

static inline int
__objc_code_unsigned_char (unsigned char *buf, unsigned char val)
{
  if ((val&_B_VALUE) == val)
    {
      buf[0] = val|_B_SINT;
      return 1;
    }
  else
    {
      buf[0] = _B_NINT|0x01;
      buf[1] = val;
      return 2;
    }
}

int
objc_write_unsigned_char (struct objc_typed_stream *stream,
			  unsigned char value)
{
  unsigned char buf[sizeof (unsigned char) + 1];
  int len = __objc_code_unsigned_char (buf, value);
  return (*stream->write) (stream->physical, (char*)buf, len);
}

static inline int
__objc_code_char (unsigned char *buf, signed char val)
{
  if (val >= 0)
    return __objc_code_unsigned_char (buf, val);
  else
    {
      buf[0] = _B_NINT|_B_SIGN|0x01;
      buf[1] = -val;
      return 2;
    }
}

int
objc_write_char (struct objc_typed_stream *stream, signed char value)
{
  unsigned char buf[sizeof (char) + 1];
  int len = __objc_code_char (buf, value);
  return (*stream->write) (stream->physical, (char*)buf, len);
}

static inline int
__objc_code_unsigned_short (unsigned char *buf, unsigned short val)
{
  if ((val&_B_VALUE) == val)
    {
      buf[0] = val|_B_SINT;
      return 1;
    }
  else 
    {
      int c, b;

      buf[0] = _B_NINT;

      for (c = sizeof (short); c != 0; c -= 1)
	if (((val >> (8*(c - 1)))%0x100) != 0)
	  break;

      buf[0] |= c;

      for (b = 1; c != 0; c--, b++)
	{
	  buf[b] = (val >> (8*(c - 1)))%0x100;
	}

      return b;
    }
}

int
objc_write_unsigned_short (struct objc_typed_stream *stream, 
			   unsigned short value)
{
  unsigned char buf[sizeof (unsigned short) + 1];
  int len = __objc_code_unsigned_short (buf, value);
  return (*stream->write) (stream->physical, (char*)buf, len);
}
      
static inline int
__objc_code_short (unsigned char *buf, short val)
{
  int sign = (val < 0);
  int size = __objc_code_unsigned_short (buf, sign ? -val : val);
  if (sign)
    buf[0] |= _B_SIGN;
  return size;
}

int
objc_write_short (struct objc_typed_stream *stream, short value)
{
  unsigned char buf[sizeof (short) + 1];
  int len = __objc_code_short (buf, value);
  return (*stream->write) (stream->physical, (char*)buf, len);
}
      

static inline int
__objc_code_unsigned_int (unsigned char *buf, unsigned int val)
{
  if ((val&_B_VALUE) == val)
    {
      buf[0] = val|_B_SINT;
      return 1;
    }
  else 
    {
      int c, b;

      buf[0] = _B_NINT;

      for (c = sizeof (int); c != 0; c -= 1)
	if (((val >> (8*(c - 1)))%0x100) != 0)
	  break;

      buf[0] |= c;

      for (b = 1; c != 0; c--, b++)
	{
	  buf[b] = (val >> (8*(c-1)))%0x100;
	}

      return b;
    }
}

int
objc_write_unsigned_int (struct objc_typed_stream *stream, unsigned int value)
{
  unsigned char buf[sizeof (unsigned int) + 1];
  int len = __objc_code_unsigned_int (buf, value);
  return (*stream->write) (stream->physical, (char*)buf, len);
}

static inline int
__objc_code_int (unsigned char *buf, int val)
{
  int sign = (val < 0);
  int size = __objc_code_unsigned_int (buf, sign ? -val : val);
  if (sign)
    buf[0] |= _B_SIGN;
  return size;
}

int
objc_write_int (struct objc_typed_stream *stream, int value)
{
  unsigned char buf[sizeof (int) + 1];
  int len = __objc_code_int (buf, value);
  return (*stream->write) (stream->physical, (char*)buf, len);
}

static inline int
__objc_code_unsigned_long (unsigned char *buf, unsigned long val)
{
  if ((val&_B_VALUE) == val)
    {
      buf[0] = val|_B_SINT;
      return 1;
    }
  else 
    {
      int c, b;

      buf[0] = _B_NINT;

      for (c = sizeof (long); c != 0; c -= 1)
	if (((val >> (8*(c - 1)))%0x100) != 0)
	  break;

      buf[0] |= c;

      for (b = 1; c != 0; c--, b++)
	{
	  buf[b] = (val >> (8*(c - 1)))%0x100;
	}

      return b;
    }
}

int
objc_write_unsigned_long (struct objc_typed_stream *stream, 
			  unsigned long value)
{
  unsigned char buf[sizeof (unsigned long) + 1];
  int len = __objc_code_unsigned_long (buf, value);
  return (*stream->write) (stream->physical, (char*)buf, len);
}

static inline int
__objc_code_long (unsigned char *buf, long val)
{
  int sign = (val < 0);
  int size = __objc_code_unsigned_long (buf, sign ? -val : val);
  if (sign)
    buf[0] |= _B_SIGN;
  return size;
}

int
objc_write_long (struct objc_typed_stream *stream, long value)
{
  unsigned char buf[sizeof (long) + 1];
  int len = __objc_code_long (buf, value);
  return (*stream->write) (stream->physical, (char*)buf, len);
}


int
objc_write_string (struct objc_typed_stream *stream,
		   const unsigned char *string, unsigned int nbytes)
{
  unsigned char buf[sizeof (unsigned int) + 1];
  int len = __objc_code_unsigned_int (buf, nbytes);
  
  if ((buf[0]&_B_CODE) == _B_SINT)
    buf[0] = (buf[0]&_B_VALUE)|_B_SSTR;

  else /* _B_NINT */
    buf[0] = (buf[0]&_B_VALUE)|_B_NSTR;

  if ((*stream->write) (stream->physical, (char*)buf, len) != 0)
    return (*stream->write) (stream->physical, (char*)string, nbytes);
  else
    return 0;
}

int
objc_write_string_atomic (struct objc_typed_stream *stream,
			  unsigned char *string, unsigned int nbytes)
{
  unsigned long key;
  if ((key = PTR2LONG(objc_hash_value_for_key (stream->stream_table, string))))
    return objc_write_use_common (stream, key);
  else
    {
      int length;
      objc_hash_add (&stream->stream_table,
		     LONG2PTR(key=PTR2LONG(string)), string);
      if ((length = objc_write_register_common (stream, key)))
	return objc_write_string (stream, string, nbytes);
      return length;
    }
}

static int
objc_write_register_common (struct objc_typed_stream *stream, 
			    unsigned long key)
{
  unsigned char buf[sizeof (unsigned long)+2];
  int len = __objc_code_unsigned_long (buf + 1, key);
  if (len == 1)
    {
      buf[0] = _B_RCOMM|0x01;
      buf[1] &= _B_VALUE;
      return (*stream->write) (stream->physical, (char*)buf, len + 1);
    }
  else
    {
      buf[1] = (buf[1]&_B_VALUE)|_B_RCOMM;
      return (*stream->write) (stream->physical, (char*)buf + 1, len);
    }
}

static int
objc_write_use_common (struct objc_typed_stream *stream, unsigned long key)
{
  unsigned char buf[sizeof (unsigned long)+2];
  int len = __objc_code_unsigned_long (buf + 1, key);
  if (len == 1)
    {
      buf[0] = _B_UCOMM|0x01;
      buf[1] &= _B_VALUE;
      return (*stream->write) (stream->physical, (char*)buf, 2);
    }
  else
    {
      buf[1] = (buf[1]&_B_VALUE)|_B_UCOMM;
      return (*stream->write) (stream->physical, (char*)buf + 1, len);
    }
}

static inline int
__objc_write_extension (struct objc_typed_stream *stream, unsigned char code)
{
  if (code <= _B_VALUE)
    {
      unsigned char buf = code|_B_EXT;
      return (*stream->write) (stream->physical, (char*)&buf, 1);
    }
  else 
    {
      objc_error (nil, OBJC_ERR_BAD_OPCODE,
		  "__objc_write_extension: bad opcode %c\n", code);
      return -1;
    }
}

inline int
__objc_write_object (struct objc_typed_stream *stream, id object)
{
  unsigned char buf = '\0';
  SEL write_sel = sel_get_any_uid ("write:");
  if (object)
    {
      __objc_write_extension (stream, _BX_OBJECT);
      objc_write_class (stream, object->class_pointer);
      (*objc_msg_lookup (object, write_sel)) (object, write_sel, stream);
      return (*stream->write) (stream->physical, (char*)&buf, 1);
    }
  else
    return objc_write_use_common (stream, 0);
}

int 
objc_write_object_reference (struct objc_typed_stream *stream, id object)
{
  unsigned long key;
  if ((key = PTR2LONG(objc_hash_value_for_key (stream->object_table, object))))
    return objc_write_use_common (stream, key);

  __objc_write_extension (stream, _BX_OBJREF);
  return objc_write_unsigned_long (stream, PTR2LONG (object));
}

int 
objc_write_root_object (struct objc_typed_stream *stream, id object)
{
  int len = 0;
  if (stream->writing_root_p)
    objc_error (nil, OBJC_ERR_RECURSE_ROOT, 
		"objc_write_root_object called recursively");
  else
    {
      stream->writing_root_p = 1;
      __objc_write_extension (stream, _BX_OBJROOT);
      if ((len = objc_write_object (stream, object)))
	__objc_finish_write_root_object (stream);
      stream->writing_root_p = 0;
    }
  return len;
}

int 
objc_write_object (struct objc_typed_stream *stream, id object)
{
  unsigned long key;
  if ((key = PTR2LONG(objc_hash_value_for_key (stream->object_table, object))))
    return objc_write_use_common (stream, key);

  else if (object == nil)
    return objc_write_use_common (stream, 0);

  else
    {
      int length;
      objc_hash_add (&stream->object_table,
		     LONG2PTR(key=PTR2LONG(object)), object);
      if ((length = objc_write_register_common (stream, key)))
	return __objc_write_object (stream, object);
      return length;
    }
}

inline int
__objc_write_class (struct objc_typed_stream *stream, struct objc_class *class)
{
  __objc_write_extension (stream, _BX_CLASS);
  objc_write_string_atomic (stream, (unsigned char *) class->name,
			   strlen ((char *) class->name));
  return objc_write_unsigned_long (stream, class->version);
}


static int 
objc_write_class (struct objc_typed_stream *stream,
			 struct objc_class *class)
{
  unsigned long key;
  if ((key = PTR2LONG(objc_hash_value_for_key (stream->stream_table, class))))
    return objc_write_use_common (stream, key);
  else
    {
      int length;
      objc_hash_add (&stream->stream_table,
		     LONG2PTR(key = PTR2LONG(class)), class);
      if ((length = objc_write_register_common (stream, key)))
	return __objc_write_class (stream, class);
      return length;
    }
}


inline int 
__objc_write_selector (struct objc_typed_stream *stream, SEL selector)
{
  const char *sel_name;
  __objc_write_extension (stream, _BX_SEL);
  /* to handle NULL selectors */
  if ((SEL)0 == selector)
    return objc_write_string (stream, (unsigned char*)"", 0);
  sel_name = sel_get_name (selector);
  return objc_write_string (stream, (unsigned char*)sel_name, strlen ((char*)sel_name));
}

int 
objc_write_selector (struct objc_typed_stream *stream, SEL selector)
{
  const char *sel_name;
  unsigned long key;

  /* to handle NULL selectors */
  if ((SEL)0 == selector)
    return __objc_write_selector (stream, selector);

  sel_name = sel_get_name (selector);
  if ((key = PTR2LONG(objc_hash_value_for_key (stream->stream_table,
					       sel_name))))
    return objc_write_use_common (stream, key);
  else
    {
      int length;
      objc_hash_add (&stream->stream_table, 
		LONG2PTR(key = PTR2LONG(sel_name)), (char *) sel_name);
      if ((length = objc_write_register_common (stream, key)))
	return __objc_write_selector (stream, selector);
      return length;
    }
}



/*
** Read operations 
*/

inline int
objc_read_char (struct objc_typed_stream *stream, char *val)
{
  unsigned char buf;
  int len;
  len = (*stream->read) (stream->physical, (char*)&buf, 1);
  if (len != 0)
    {
      if ((buf & _B_CODE) == _B_SINT)
	(*val) = (buf & _B_VALUE);

      else if ((buf & _B_NUMBER) == 1)
	{
	  len = (*stream->read) (stream->physical, val, 1);
	  if (buf&_B_SIGN)
	    (*val) = -1 * (*val);
	}

      else
	objc_error (nil, OBJC_ERR_BAD_DATA,
		    "expected 8bit signed int, got %dbit int",
		    (int) (buf&_B_NUMBER)*8);
    }
  return len;
}


inline int
objc_read_unsigned_char (struct objc_typed_stream *stream, unsigned char *val)
{
  unsigned char buf;
  int len;
  if ((len = (*stream->read) (stream->physical, (char*)&buf, 1)))
    {
      if ((buf & _B_CODE) == _B_SINT)
	(*val) = (buf & _B_VALUE);

      else if ((buf & _B_NUMBER) == 1)
	len = (*stream->read) (stream->physical, (char*)val, 1);

      else
	objc_error (nil, OBJC_ERR_BAD_DATA,
		    "expected 8bit unsigned int, got %dbit int",
		    (int) (buf&_B_NUMBER)*8);
    }
  return len;
}

inline int
objc_read_short (struct objc_typed_stream *stream, short *value)
{
  unsigned char buf[sizeof (short) + 1];
  int len;
  if ((len = (*stream->read) (stream->physical, (char*)buf, 1)))
    {
      if ((buf[0] & _B_CODE) == _B_SINT)
	(*value) = (buf[0] & _B_VALUE);

      else
	{
	  int pos = 1;
	  int nbytes = buf[0] & _B_NUMBER;
	  if (nbytes > (int) sizeof (short))
	    objc_error (nil, OBJC_ERR_BAD_DATA,
		        "expected short, got bigger (%dbits)", nbytes*8);
	  len = (*stream->read) (stream->physical, (char*)buf + 1, nbytes);
	  (*value) = 0;
	  while (pos <= nbytes)
	    (*value) = ((*value)*0x100) + buf[pos++];
	  if (buf[0] & _B_SIGN)
	    (*value) = -(*value);
	}
    }
  return len;
}

inline int
objc_read_unsigned_short (struct objc_typed_stream *stream,
			  unsigned short *value)
{
  unsigned char buf[sizeof (unsigned short) + 1];
  int len;
  if ((len = (*stream->read) (stream->physical, (char*)buf, 1)))
    {
      if ((buf[0] & _B_CODE) == _B_SINT)
	(*value) = (buf[0] & _B_VALUE);

      else
	{
	  int pos = 1;
	  int nbytes = buf[0] & _B_NUMBER;
	  if (nbytes > (int) sizeof (short))
	    objc_error (nil, OBJC_ERR_BAD_DATA,
		        "expected short, got int or bigger");
	  len = (*stream->read) (stream->physical, (char*)buf + 1, nbytes);
	  (*value) = 0;
	  while (pos <= nbytes)
	    (*value) = ((*value)*0x100) + buf[pos++];
	}
    }
  return len;
}


inline int
objc_read_int (struct objc_typed_stream *stream, int *value)
{
  unsigned char buf[sizeof (int) + 1];
  int len;
  if ((len = (*stream->read) (stream->physical, (char*)buf, 1)))
    {
      if ((buf[0] & _B_CODE) == _B_SINT)
	(*value) = (buf[0] & _B_VALUE);

      else
	{
	  int pos = 1;
	  int nbytes = buf[0] & _B_NUMBER;
	  if (nbytes > (int) sizeof (int))
	    objc_error (nil, OBJC_ERR_BAD_DATA, "expected int, got bigger");
	  len = (*stream->read) (stream->physical, (char*)buf + 1, nbytes);
	  (*value) = 0;
	  while (pos <= nbytes)
	    (*value) = ((*value)*0x100) + buf[pos++];
	  if (buf[0] & _B_SIGN)
	    (*value) = -(*value);
	}
    }
  return len;
}

inline int
objc_read_long (struct objc_typed_stream *stream, long *value)
{
  unsigned char buf[sizeof (long) + 1];
  int len;
  if ((len = (*stream->read) (stream->physical, (char*)buf, 1)))
    {
      if ((buf[0] & _B_CODE) == _B_SINT)
	(*value) = (buf[0] & _B_VALUE);

      else
	{
	  int pos = 1;
	  int nbytes = buf[0] & _B_NUMBER;
	  if (nbytes > (int) sizeof (long))
	    objc_error (nil, OBJC_ERR_BAD_DATA, "expected long, got bigger");
	  len = (*stream->read) (stream->physical, (char*)buf + 1, nbytes);
	  (*value) = 0;
	  while (pos <= nbytes)
	    (*value) = ((*value)*0x100) + buf[pos++];
	  if (buf[0] & _B_SIGN)
	    (*value) = -(*value);
	}
    }
  return len;
}

inline int
__objc_read_nbyte_uint (struct objc_typed_stream *stream,
			unsigned int nbytes, unsigned int *val)
{
  int len;
  unsigned int pos = 0;
  unsigned char buf[sizeof (unsigned int) + 1];

  if (nbytes > sizeof (int))
    objc_error (nil, OBJC_ERR_BAD_DATA, "expected int, got bigger");

  len = (*stream->read) (stream->physical, (char*)buf, nbytes);
  (*val) = 0;
  while (pos < nbytes)
    (*val) = ((*val)*0x100) + buf[pos++];
  return len;
}
  

inline int
objc_read_unsigned_int (struct objc_typed_stream *stream,
			unsigned int *value)
{
  unsigned char buf[sizeof (unsigned int) + 1];
  int len;
  if ((len = (*stream->read) (stream->physical, (char*)buf, 1)))
    {
      if ((buf[0] & _B_CODE) == _B_SINT)
	(*value) = (buf[0] & _B_VALUE);

      else
	len = __objc_read_nbyte_uint (stream, (buf[0] & _B_VALUE), value);

    }
  return len;
}

int
__objc_read_nbyte_ulong (struct objc_typed_stream *stream,
		       unsigned int nbytes, unsigned long *val)
{
  int len;
  unsigned int pos = 0;
  unsigned char buf[sizeof (unsigned long) + 1];

  if (nbytes > sizeof (long))
    objc_error (nil, OBJC_ERR_BAD_DATA, "expected long, got bigger");

  len = (*stream->read) (stream->physical, (char*)buf, nbytes);
  (*val) = 0;
  while (pos < nbytes)
    (*val) = ((*val)*0x100) + buf[pos++];
  return len;
}
  

inline int
objc_read_unsigned_long (struct objc_typed_stream *stream,
			 unsigned long *value)
{
  unsigned char buf[sizeof (unsigned long) + 1];
  int len;
  if ((len = (*stream->read) (stream->physical, (char*)buf, 1)))
    {
      if ((buf[0] & _B_CODE) == _B_SINT)
	(*value) = (buf[0] & _B_VALUE);

      else
	len = __objc_read_nbyte_ulong (stream, (buf[0] & _B_VALUE), value);

    }
  return len;
}

inline int
objc_read_string (struct objc_typed_stream *stream,
		  char **string)
{
  unsigned char buf[sizeof (unsigned int) + 1];
  int len;
  if ((len = (*stream->read) (stream->physical, (char*)buf, 1)))
    {
      unsigned long key = 0;

      if ((buf[0]&_B_CODE) == _B_RCOMM)	/* register following */
	{
	  len = __objc_read_nbyte_ulong (stream, (buf[0] & _B_VALUE), &key);
	  len = (*stream->read) (stream->physical, (char*)buf, 1);
	}

      switch (buf[0]&_B_CODE) {
      case _B_SSTR:
	{
	  int length = buf[0]&_B_VALUE;
	  (*string) = (char*)objc_malloc (length + 1);
	  if (key)
	    objc_hash_add (&stream->stream_table, LONG2PTR(key), *string);
	  len = (*stream->read) (stream->physical, *string, length);
	  (*string)[length] = '\0';
	}
	break;

      case _B_UCOMM:
	{
	  char *tmp;
	  len = __objc_read_nbyte_ulong (stream, (buf[0] & _B_VALUE), &key);
	  tmp = objc_hash_value_for_key (stream->stream_table, LONG2PTR (key));
	  *string = objc_malloc (strlen (tmp) + 1);
	  strcpy (*string, tmp);
	}
	break;

      case _B_NSTR:
	{
	  unsigned int nbytes = buf[0]&_B_VALUE;
	  len = __objc_read_nbyte_uint (stream, nbytes, &nbytes);
	  if (len) {
	    (*string) = (char*)objc_malloc (nbytes + 1);
	    if (key)
	      objc_hash_add (&stream->stream_table, LONG2PTR(key), *string);
	    len = (*stream->read) (stream->physical, *string, nbytes);
	    (*string)[nbytes] = '\0';
	  }
	}
	break;
	
      default:
	objc_error (nil, OBJC_ERR_BAD_DATA,
		    "expected string, got opcode %c\n", (buf[0]&_B_CODE));
      }
    }

  return len;
}


int
objc_read_object (struct objc_typed_stream *stream, id *object)
{
  unsigned char buf[sizeof (unsigned int)];
  int len;
  if ((len = (*stream->read) (stream->physical, (char*)buf, 1)))
    {
      SEL read_sel = sel_get_any_uid ("read:");
      unsigned long key = 0;

      if ((buf[0]&_B_CODE) == _B_RCOMM)	/* register common */
	{
	  len = __objc_read_nbyte_ulong (stream, (buf[0] & _B_VALUE), &key);
	  len = (*stream->read) (stream->physical, (char*)buf, 1);
	}

      if (buf[0] == (_B_EXT | _BX_OBJECT))
	{
	  Class class;

	  /* get class */
	  len = objc_read_class (stream, &class);

	  /* create instance */
	  (*object) = class_create_instance (class);

	  /* register? */
	  if (key)
	    objc_hash_add (&stream->object_table, LONG2PTR(key), *object);

	  /* send -read: */
	  if (__objc_responds_to (*object, read_sel))
	    (*get_imp (class, read_sel)) (*object, read_sel, stream);

	  /* check null-byte */
	  len = (*stream->read) (stream->physical, (char*)buf, 1);
	  if (buf[0] != '\0')
	    objc_error (nil, OBJC_ERR_BAD_DATA,
		        "expected null-byte, got opcode %c", buf[0]);
	}

      else if ((buf[0]&_B_CODE) == _B_UCOMM)
	{
	  if (key)
	    objc_error (nil, OBJC_ERR_BAD_KEY, "cannot register use upcode...");
	  len = __objc_read_nbyte_ulong (stream, (buf[0] & _B_VALUE), &key);
	  (*object) = objc_hash_value_for_key (stream->object_table,
					       LONG2PTR(key));
	}

      else if (buf[0] == (_B_EXT | _BX_OBJREF))	/* a forward reference */
	{
	  struct objc_list *other;
	  len = objc_read_unsigned_long (stream, &key);
	  other 
	    = (struct objc_list *) objc_hash_value_for_key (stream->object_refs, 
							   LONG2PTR(key));
	  objc_hash_add (&stream->object_refs, LONG2PTR(key), 
			 (void *)list_cons (object, other));
	}

      else if (buf[0] == (_B_EXT | _BX_OBJROOT)) /* a root object */
	{
	  if (key)
	    objc_error (nil, OBJC_ERR_BAD_KEY,
		        "cannot register root object...");
	  len = objc_read_object (stream, object);
	  __objc_finish_read_root_object (stream);
	}

      else
	objc_error (nil, OBJC_ERR_BAD_DATA,
		    "expected object, got opcode %c", buf[0]);
    }
  return len;
}

static int
objc_read_class (struct objc_typed_stream *stream, Class *class)
{
  unsigned char buf[sizeof (unsigned int)];
  int len;
  if ((len = (*stream->read) (stream->physical, (char*)buf, 1)))
    {
      unsigned long key = 0;

      if ((buf[0]&_B_CODE) == _B_RCOMM)	/* register following */
	{
	  len = __objc_read_nbyte_ulong (stream, (buf[0] & _B_VALUE), &key);
	  len = (*stream->read) (stream->physical, (char*)buf, 1);
	}

      if (buf[0] == (_B_EXT | _BX_CLASS))
	{
	  char temp[1] = "";
	  char *class_name = temp;
	  unsigned long version;

	  /* get class */
	  len = objc_read_string (stream, &class_name);
	  (*class) = objc_get_class (class_name);
	  objc_free (class_name);

	  /* register */
	  if (key)
	    objc_hash_add (&stream->stream_table, LONG2PTR(key), *class);

	  objc_read_unsigned_long (stream, &version);
	  objc_hash_add (&stream->class_table,
			 (*class)->name, (void *) ((size_t) version));
	}

      else if ((buf[0]&_B_CODE) == _B_UCOMM)
	{
	  if (key)
	    objc_error (nil, OBJC_ERR_BAD_KEY, "cannot register use upcode...");
	  len = __objc_read_nbyte_ulong (stream, (buf[0] & _B_VALUE), &key);
	  *class = objc_hash_value_for_key (stream->stream_table,
					    LONG2PTR(key));
	  if (! *class)
	    objc_error (nil, OBJC_ERR_BAD_CLASS,
		        "cannot find class for key %lu", key);
	}

      else
	objc_error (nil, OBJC_ERR_BAD_DATA,
		    "expected class, got opcode %c", buf[0]);
    }
  return len;
}

int
objc_read_selector (struct objc_typed_stream *stream, SEL* selector)
{
  unsigned char buf[sizeof (unsigned int)];
  int len;
  if ((len = (*stream->read) (stream->physical, (char*)buf, 1)))
    {
      unsigned long key = 0;

      if ((buf[0]&_B_CODE) == _B_RCOMM)	/* register following */
	{
	  len = __objc_read_nbyte_ulong (stream, (buf[0] & _B_VALUE), &key);
	  len = (*stream->read) (stream->physical, (char*)buf, 1);
	}

      if (buf[0] == (_B_EXT|_BX_SEL)) /* selector! */
	{
	  char temp[1] = "";
	  char *selector_name = temp;

	  /* get selector */
	  len = objc_read_string (stream, &selector_name);
	  /* To handle NULL selectors */
	  if (0 == strlen (selector_name))
	    {
	      (*selector) = (SEL)0;
	      return 0;
	    }
	  else 
	    (*selector) = sel_get_any_uid (selector_name);
	  objc_free (selector_name);

	  /* register */
	  if (key)
	    objc_hash_add (&stream->stream_table,
			   LONG2PTR(key), (void *) *selector);
	}

      else if ((buf[0]&_B_CODE) == _B_UCOMM)
	{
	  if (key)
	    objc_error (nil, OBJC_ERR_BAD_KEY, "cannot register use upcode...");
	  len = __objc_read_nbyte_ulong (stream, (buf[0] & _B_VALUE), &key);
	  (*selector) = objc_hash_value_for_key (stream->stream_table, 
						 LONG2PTR(key));
	}

      else
	objc_error (nil, OBJC_ERR_BAD_DATA,
		    "expected selector, got opcode %c", buf[0]);
    }
  return len;
}

/*
** USER LEVEL FUNCTIONS
*/

/*
** Write one object, encoded in TYPE and pointed to by DATA to the
** typed stream STREAM.  
*/

int
objc_write_type (TypedStream *stream, const char *type, const void *data)
{
  switch (*type) {
  case _C_ID:
    return objc_write_object (stream, *(id *) data);
    break;

  case _C_CLASS:
    return objc_write_class (stream, *(Class *) data);
    break;

  case _C_SEL:
    return objc_write_selector (stream, *(SEL *) data);
    break;

  case _C_CHR:
    return objc_write_char (stream, *(signed char *) data);
    break;
    
  case _C_UCHR:
    return objc_write_unsigned_char (stream, *(unsigned char *) data);
    break;

  case _C_SHT:
    return objc_write_short (stream, *(short *) data);
    break;

  case _C_USHT:
    return objc_write_unsigned_short (stream, *(unsigned short *) data);
    break;

  case _C_INT:
    return objc_write_int (stream, *(int *) data);
    break;

  case _C_UINT:
    return objc_write_unsigned_int (stream, *(unsigned int *) data);
    break;

  case _C_LNG:
    return objc_write_long (stream, *(long *) data);
    break;

  case _C_ULNG:
    return objc_write_unsigned_long (stream, *(unsigned long *) data);
    break;

  case _C_CHARPTR:
    return objc_write_string (stream,
			      *(unsigned char **) data, strlen (*(char **) data));
    break;

  case _C_ATOM:
    return objc_write_string_atomic (stream, *(unsigned char **) data, 
				     strlen (*(char **) data));
    break;

  case _C_ARY_B:
    {
      int len = atoi (type + 1);
      while (isdigit ((unsigned char) *++type))
	;
      return objc_write_array (stream, type, len, data);
    }
    break; 

  case _C_STRUCT_B:
    {
      int acc_size = 0;
      int align;
      while (*type != _C_STRUCT_E && *type++ != '=')
	; /* skip "<name>=" */
      while (*type != _C_STRUCT_E)
	{
	  align = objc_alignof_type (type);       /* padd to alignment */
	  acc_size = ROUND (acc_size, align);
	  objc_write_type (stream, type, ((char *) data) + acc_size);
	  acc_size += objc_sizeof_type (type);   /* add component size */
	  type = objc_skip_typespec (type);	 /* skip component */
	}
      return 1;
    }

  default:
    {
      objc_error (nil, OBJC_ERR_BAD_TYPE,
		  "objc_write_type: cannot parse typespec: %s\n", type);
      return 0;
    }
  }
}

/*
** Read one object, encoded in TYPE and pointed to by DATA to the
** typed stream STREAM.  DATA specifies the address of the types to
** read.  Expected type is checked against the type actually present
** on the stream. 
*/

int
objc_read_type(TypedStream *stream, const char *type, void *data)
{
  char c;
  switch (c = *type) {
  case _C_ID:
    return objc_read_object (stream, (id*)data);
    break;

  case _C_CLASS:
    return objc_read_class (stream, (Class*)data);
    break;

  case _C_SEL:
    return objc_read_selector (stream, (SEL*)data);
    break;

  case _C_CHR:
    return objc_read_char (stream, (char*)data);
    break;
    
  case _C_UCHR:
    return objc_read_unsigned_char (stream, (unsigned char*)data);
    break;

  case _C_SHT:
    return objc_read_short (stream, (short*)data);
    break;

  case _C_USHT:
    return objc_read_unsigned_short (stream, (unsigned short*)data);
    break;

  case _C_INT:
    return objc_read_int (stream, (int*)data);
    break;

  case _C_UINT:
    return objc_read_unsigned_int (stream, (unsigned int*)data);
    break;

  case _C_LNG:
    return objc_read_long (stream, (long*)data);
    break;

  case _C_ULNG:
    return objc_read_unsigned_long (stream, (unsigned long*)data);
    break;

  case _C_CHARPTR:
  case _C_ATOM:
    return objc_read_string (stream, (char**)data);
    break;

  case _C_ARY_B:
    {
      int len = atoi (type + 1);
      while (isdigit ((unsigned char) *++type))
	;
      return objc_read_array (stream, type, len, data);
    }
    break; 

  case _C_STRUCT_B:
    {
      int acc_size = 0;
      int align;
      while (*type != _C_STRUCT_E && *type++ != '=')
	; /* skip "<name>=" */
      while (*type != _C_STRUCT_E)
	{
	  align = objc_alignof_type (type);       /* padd to alignment */
	  acc_size = ROUND (acc_size, align);
	  objc_read_type (stream, type, ((char*)data)+acc_size);
	  acc_size += objc_sizeof_type (type);   /* add component size */
	  type = objc_skip_typespec (type);	 /* skip component */
	}
      return 1;
    }

  default:
    {
      objc_error (nil, OBJC_ERR_BAD_TYPE,
		  "objc_read_type: cannot parse typespec: %s\n", type);
      return 0;
    }
  }
}

/*
** Write the object specified by the template TYPE to STREAM.  Last
** arguments specify addresses of values to be written.  It might 
** seem surprising to specify values by address, but this is extremely
** convenient for copy-paste with objc_read_types calls.  A more
** down-to-the-earth cause for this passing of addresses is that values
** of arbitrary size is not well supported in ANSI C for functions with
** variable number of arguments.
*/

int 
objc_write_types (TypedStream *stream, const char *type, ...)
{
  va_list args;
  const char *c;
  int res = 0;

  va_start(args, type);

  for (c = type; *c; c = objc_skip_typespec (c))
    {
      switch (*c) {
      case _C_ID:
	res = objc_write_object (stream, *va_arg (args, id*));
	break;

      case _C_CLASS:
	res = objc_write_class (stream, *va_arg (args, Class*));
	break;

      case _C_SEL:
	res = objc_write_selector (stream, *va_arg (args, SEL*));
	break;
	
      case _C_CHR:
	res = objc_write_char (stream, *va_arg (args, char*));
	break;
	
      case _C_UCHR:
	res = objc_write_unsigned_char (stream,
					*va_arg (args, unsigned char*));
	break;
	
      case _C_SHT:
	res = objc_write_short (stream, *va_arg (args, short*));
	break;

      case _C_USHT:
	res = objc_write_unsigned_short (stream,
					 *va_arg (args, unsigned short*));
	break;

      case _C_INT:
	res = objc_write_int(stream, *va_arg (args, int*));
	break;
	
      case _C_UINT:
	res = objc_write_unsigned_int(stream, *va_arg (args, unsigned int*));
	break;

      case _C_LNG:
	res = objc_write_long(stream, *va_arg (args, long*));
	break;
	
      case _C_ULNG:
	res = objc_write_unsigned_long(stream, *va_arg (args, unsigned long*));
	break;

      case _C_CHARPTR:
	{
	  unsigned char **str = va_arg (args, unsigned char **);
	  res = objc_write_string (stream, *str, strlen ((char*)*str));
	}
	break;

      case _C_ATOM:
	{
	  unsigned char **str = va_arg (args, unsigned char **);
	  res = objc_write_string_atomic (stream, *str, strlen ((char*)*str));
	}
	break;

      case _C_ARY_B:
	{
	  int len = atoi (c + 1);
	  const char *t = c;
	  while (isdigit ((unsigned char) *++t))
	    ;
	  res = objc_write_array (stream, t, len, va_arg (args, void *));
	  t = objc_skip_typespec (t);
	  if (*t != _C_ARY_E)
	    objc_error (nil, OBJC_ERR_BAD_TYPE, "expected `]', got: %s", t);
	}
	break; 
	
      default:
	objc_error (nil, OBJC_ERR_BAD_TYPE, 
		    "objc_write_types: cannot parse typespec: %s\n", type);
      }
    }
  va_end(args);
  return res;
}


/* 
** Last arguments specify addresses of values to be read.  Expected
** type is checked against the type actually present on the stream. 
*/

int 
objc_read_types(TypedStream *stream, const char *type, ...)
{
  va_list args;
  const char *c;
  int res = 0;

  va_start (args, type);

  for (c = type; *c; c = objc_skip_typespec(c))
    {
      switch (*c) {
      case _C_ID:
	res = objc_read_object(stream, va_arg (args, id*));
	break;

      case _C_CLASS:
	res = objc_read_class(stream, va_arg (args, Class*));
	break;

      case _C_SEL:
	res = objc_read_selector(stream, va_arg (args, SEL*));
	break;
	
      case _C_CHR:
	res = objc_read_char(stream, va_arg (args, char*));
	break;
	
      case _C_UCHR:
	res = objc_read_unsigned_char(stream, va_arg (args, unsigned char*));
	break;
	
      case _C_SHT:
	res = objc_read_short(stream, va_arg (args, short*));
	break;

      case _C_USHT:
	res = objc_read_unsigned_short(stream, va_arg (args, unsigned short*));
	break;

      case _C_INT:
	res = objc_read_int(stream, va_arg (args, int*));
	break;
	
      case _C_UINT:
	res = objc_read_unsigned_int(stream, va_arg (args, unsigned int*));
	break;

      case _C_LNG:
	res = objc_read_long(stream, va_arg (args, long*));
	break;
	
      case _C_ULNG:
	res = objc_read_unsigned_long(stream, va_arg (args, unsigned long*));
	break;

      case _C_CHARPTR:
      case _C_ATOM:
	{
	  char **str = va_arg (args, char **);
	  res = objc_read_string (stream, str);
	}
	break;

      case _C_ARY_B:
	{
	  int len = atoi (c + 1);
	  const char *t = c;
	  while (isdigit ((unsigned char) *++t))
	    ;
	  res = objc_read_array (stream, t, len, va_arg (args, void *));
	  t = objc_skip_typespec (t);
	  if (*t != _C_ARY_E)
	    objc_error (nil, OBJC_ERR_BAD_TYPE, "expected `]', got: %s", t);
	}
	break; 
	
      default:
	objc_error (nil, OBJC_ERR_BAD_TYPE, 
		    "objc_read_types: cannot parse typespec: %s\n", type);
      }
    }
  va_end (args);
  return res;
}

/*
** Write an array of COUNT elements of TYPE from the memory address DATA.
** This is equivalent of objc_write_type (stream, "[N<type>]", data)
*/

int
objc_write_array (TypedStream *stream, const char *type,
		  int count, const void *data)
{
  int off = objc_sizeof_type(type);
  const char *where = data;

  while (count-- > 0)
    {
      objc_write_type(stream, type, where);
      where += off;
    }
  return 1;
}

/*
** Read an array of COUNT elements of TYPE into the memory address
** DATA.  The memory pointed to by data is supposed to be allocated
** by the callee.  This is equivalent of 
**   objc_read_type (stream, "[N<type>]", data)
*/

int
objc_read_array (TypedStream *stream, const char *type,
		 int count, void *data)
{
  int off = objc_sizeof_type(type);
  char *where = (char*)data;

  while (count-- > 0)
    {
      objc_read_type(stream, type, where);
      where += off;
    }
  return 1;
}

static int 
__objc_fread (FILE *file, char *data, int len)
{
  return fread(data, len, 1, file);
}

static int 
__objc_fwrite (FILE *file, char *data, int len)
{
  return fwrite(data, len, 1, file);
}

static int
__objc_feof (FILE *file)
{
  return feof(file);
}

static int 
__objc_no_write (FILE *file __attribute__ ((__unused__)),
		 const char *data __attribute__ ((__unused__)),
		 int len __attribute__ ((__unused__)))
{
  objc_error (nil, OBJC_ERR_NO_WRITE, "TypedStream not open for writing");
  return 0;
}

static int 
__objc_no_read (FILE *file __attribute__ ((__unused__)),
		const char *data __attribute__ ((__unused__)),
		int len __attribute__ ((__unused__)))
{
  objc_error (nil, OBJC_ERR_NO_READ, "TypedStream not open for reading");
  return 0;
}

static int
__objc_read_typed_stream_signature (TypedStream *stream)
{
  char buffer[80];
  int pos = 0;
  do
    (*stream->read) (stream->physical, buffer+pos, 1);
  while (buffer[pos++] != '\0')
    ;
  sscanf (buffer, "GNU TypedStream %d", &stream->version);
  if (stream->version != OBJC_TYPED_STREAM_VERSION)
    objc_error (nil, OBJC_ERR_STREAM_VERSION,
		"cannot handle TypedStream version %d", stream->version);
  return 1;
}

static int
__objc_write_typed_stream_signature (TypedStream *stream)
{
  char buffer[80];
  sprintf(buffer, "GNU TypedStream %d", OBJC_TYPED_STREAM_VERSION);
  stream->version = OBJC_TYPED_STREAM_VERSION;
  (*stream->write) (stream->physical, buffer, strlen (buffer) + 1);
  return 1;
}

static void __objc_finish_write_root_object(struct objc_typed_stream *stream)
{
  objc_hash_delete (stream->object_table);
  stream->object_table = objc_hash_new (64,
					(hash_func_type) objc_hash_ptr,
					(compare_func_type) objc_compare_ptrs);
}

static void __objc_finish_read_root_object(struct objc_typed_stream *stream)
{
  node_ptr node;
  SEL awake_sel = sel_get_any_uid ("awake");
  cache_ptr free_list = objc_hash_new (64,
				       (hash_func_type) objc_hash_ptr,
				       (compare_func_type) objc_compare_ptrs);

  /* resolve object forward references */
  for (node = objc_hash_next (stream->object_refs, NULL); node;
       node = objc_hash_next (stream->object_refs, node))
    {
      struct objc_list *reflist = node->value;
      const void *key = node->key;
      id object = objc_hash_value_for_key (stream->object_table, key);
      while (reflist)
	{
	  *((id*) reflist->head) = object;
	  if (objc_hash_value_for_key (free_list,reflist) == NULL)
	    objc_hash_add (&free_list,reflist,reflist);

	  reflist = reflist->tail;
	}
    }
    
  /* apply __objc_free to all objects stored in free_list */
  for (node = objc_hash_next (free_list, NULL); node;
       node = objc_hash_next (free_list, node))
    objc_free ((void *) node->key);

  objc_hash_delete (free_list);

  /* empty object reference table */
  objc_hash_delete (stream->object_refs);
  stream->object_refs = objc_hash_new (8, (hash_func_type) objc_hash_ptr,
				       (compare_func_type) objc_compare_ptrs);
  
  /* call -awake for all objects read  */
  if (awake_sel)
    {
      for (node = objc_hash_next (stream->object_table, NULL); node;
	   node = objc_hash_next (stream->object_table, node))
	{
	  id object = node->value;
	  if (__objc_responds_to (object, awake_sel))
	    (*objc_msg_lookup (object, awake_sel)) (object, awake_sel);
	}
    }

  /* empty object table */
  objc_hash_delete (stream->object_table);
  stream->object_table = objc_hash_new(64,
				       (hash_func_type)objc_hash_ptr,
				       (compare_func_type)objc_compare_ptrs);
}

/*
** Open the stream PHYSICAL in MODE
*/

TypedStream *
objc_open_typed_stream (FILE *physical, int mode)
{
  TypedStream *s = (TypedStream *) objc_malloc (sizeof (TypedStream));

  s->mode = mode;
  s->physical = physical;
  s->stream_table = objc_hash_new (64,
				   (hash_func_type) objc_hash_ptr,
				   (compare_func_type) objc_compare_ptrs);
  s->object_table = objc_hash_new (64,
				   (hash_func_type) objc_hash_ptr,
				   (compare_func_type) objc_compare_ptrs);
  s->eof = (objc_typed_eof_func) __objc_feof;
  s->flush = (objc_typed_flush_func) fflush;
  s->writing_root_p = 0;
  if (mode == OBJC_READONLY)
    {
      s->class_table 
	= objc_hash_new (8, (hash_func_type) objc_hash_string,
			 (compare_func_type) objc_compare_strings);
      s->object_refs = objc_hash_new (8, (hash_func_type) objc_hash_ptr,
				      (compare_func_type) objc_compare_ptrs);
      s->read = (objc_typed_read_func) __objc_fread;
      s->write = (objc_typed_write_func) __objc_no_write;
      __objc_read_typed_stream_signature (s);
    }
  else if (mode == OBJC_WRITEONLY)
    {
      s->class_table = 0;
      s->object_refs = 0;
      s->read = (objc_typed_read_func) __objc_no_read;
      s->write = (objc_typed_write_func) __objc_fwrite;
      __objc_write_typed_stream_signature (s);
    }      
  else
    {
      objc_close_typed_stream (s);
      return NULL;
    }
  s->type = OBJC_FILE_STREAM;
  return s;
}

/*
** Open the file named by FILE_NAME in MODE
*/

TypedStream*
objc_open_typed_stream_for_file (const char *file_name, int mode)
{
  FILE *file = NULL;
  TypedStream *s;

  if (mode == OBJC_READONLY)
    file = fopen (file_name, "r");
  else
    file = fopen (file_name, "w");

  if (file)
    {
      s = objc_open_typed_stream (file, mode);
      if (s)
	s->type |= OBJC_MANAGED_STREAM;
      return s;
    }
  else
    return NULL;
}

/*
** Close STREAM freeing the structure it self.  If it was opened with 
** objc_open_typed_stream_for_file, the file will also be closed.
*/

void
objc_close_typed_stream (TypedStream *stream)
{
  if (stream->mode == OBJC_READONLY)
    {
      __objc_finish_read_root_object (stream); /* Just in case... */
      objc_hash_delete (stream->class_table);
      objc_hash_delete (stream->object_refs);
    }

  objc_hash_delete (stream->stream_table);
  objc_hash_delete (stream->object_table);

  if (stream->type == (OBJC_MANAGED_STREAM | OBJC_FILE_STREAM))
    fclose ((FILE *)stream->physical);

  objc_free(stream);
}

BOOL
objc_end_of_typed_stream (TypedStream *stream)
{
  return (*stream->eof) (stream->physical);
}

void
objc_flush_typed_stream (TypedStream *stream)
{
  (*stream->flush) (stream->physical);
}

long
objc_get_stream_class_version (TypedStream *stream, Class class)
{
  if (stream->class_table)
    return PTR2LONG(objc_hash_value_for_key (stream->class_table,
					     class->name));
  else
    return class_get_version (class);
}

