/* Array manipulation routines for S-Lang */
/*
Copyright (C) 2004-2021,2022 John E. Davis

This file is part of the S-Lang Library.

The S-Lang Library 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 2 of the
License, or (at your option) any later version.

The S-Lang Library 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.

You should have received a copy of the GNU General Public License
along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
USA.
*/

#include "slinclud.h"
#include <math.h>
#include <limits.h>

/* #define SL_APP_WANTS_FOREACH */
#include "slang.h"
#include "_slang.h"

typedef struct Range_Array_Type SLarray_Range_Array_Type;

struct Range_Array_Type
{
   SLindex_Type first_index;
   SLindex_Type last_index;
   SLindex_Type delta;
   int has_first_index;
   int has_last_index;
   int (*to_linear_fun) (SLang_Array_Type *, SLarray_Range_Array_Type *, VOID_STAR);
};

static SLang_Array_Type *inline_implicit_index_array (SLindex_Type *, SLindex_Type *, SLindex_Type *);

/* Use SLang_pop_array when a linear array is required. */
static int pop_array (SLang_Array_Type **at_ptr, int convert_scalar)
{
   SLang_Array_Type *at;
   SLindex_Type one = 1;
   int type;

   *at_ptr = NULL;
   type = SLang_peek_at_stack ();

   switch (type)
     {
      case -1:
	return -1;

      case SLANG_ARRAY_TYPE:
	return SLclass_pop_ptr_obj (SLANG_ARRAY_TYPE, (VOID_STAR *) at_ptr);

      case SLANG_NULL_TYPE:
	/* convert_scalar = 0; */  /* commented out for 2.0.5 to fix array_map NULL bug */
	/* fall through */
      default:
	if (convert_scalar == 0)
	  {
	     SLdo_pop ();
	     _pSLang_verror (SL_TYPE_MISMATCH, "Context requires an array.  Scalar not converted");
	     return -1;
	  }
	break;
     }

   if (NULL == (at = SLang_create_array ((SLtype) type, 0, NULL, &one, 1)))
     return -1;

   if (at->flags & SLARR_DATA_VALUE_IS_POINTER)
     {
	/* The act of creating the array could have initialized the array
	 * with pointers to an object of the type.  For example, this could
	 * happen with user-defined structs.
	 */
	if (*(VOID_STAR *)at->data != NULL)
	  {
	     at->cl->cl_destroy ((SLtype) type, at->data);
	     *(VOID_STAR *) at->data = NULL;
	  }
     }

   if (-1 == at->cl->cl_apop ((SLtype) type, at->data))
     {
	SLang_free_array (at);
	return -1;
     }
   at->flags |= SLARR_DERIVED_FROM_SCALAR;
   *at_ptr = at;

   return 0;
}

static void throw_size_error (int e)
{
   _pSLang_verror (e, "Unable to create a multi-dimensional array of the desired size");
}

static VOID_STAR linear_get_data_addr (SLang_Array_Type *at, SLindex_Type *dims)
{
   size_t ofs;

   if (at->num_dims == 1)
     {
	if (*dims < 0)
	  ofs = (size_t) (*dims + at->dims[0]);
	else
	  ofs = (size_t)*dims;
     }
   else
     {
	unsigned int i;
	SLindex_Type *max_dims = at->dims;
	unsigned int num_dims = at->num_dims;
	ofs = 0;
	for (i = 0; i < num_dims; i++)
	  {
	     SLindex_Type d = dims[i];
	     if ((d < 0) || (d >= max_dims[i]))
	       {
		  if (d < 0)
		    d = d + max_dims[i];
		  if ((d < 0) || (d >= max_dims[i]))
		    {
		       SLang_set_error (SL_Index_Error);
		       return NULL;
		    }
	       }
	     ofs = ofs * (size_t)max_dims [i] + (size_t) d;
	  }
     }
   if (ofs >= at->num_elements)
     {
	SLang_set_error (SL_Index_Error);
	return NULL;
     }
   return (VOID_STAR) ((char *)at->data + (ofs * at->sizeof_type));
}

_INLINE_
static VOID_STAR get_data_addr (SLang_Array_Type *at, SLindex_Type *dims)
{
   VOID_STAR data;

   data = at->data;
   if (data == NULL)
     {
	_pSLang_verror (SL_UNKNOWN_ERROR, "Array has no data");
	return NULL;
     }

   data = (*at->index_fun) (at, dims);

   if (data == NULL)
     {
	_pSLang_verror (SL_UNKNOWN_ERROR, "Unable to access array element");
	return NULL;
     }

   return data;
}

void _pSLarray_free_array_elements (SLang_Class_Type *cl, VOID_STAR s, SLuindex_Type num)
{
   size_t sizeof_type;
   void (*f) (SLtype, VOID_STAR);
   char *p;
   SLtype type;

   if ((cl->cl_class_type == SLANG_CLASS_TYPE_SCALAR)
       || (cl->cl_class_type == SLANG_CLASS_TYPE_VECTOR))
     return;

   f = cl->cl_destroy;
   sizeof_type = cl->cl_sizeof_type;
   type = cl->cl_data_type;

   p = (char *) s;
   while (num != 0)
     {
	if (NULL != *(VOID_STAR *)p)
	  {
	     (*f) (type, (VOID_STAR)p);
	     *(VOID_STAR *) p = NULL;
	  }
	p += sizeof_type;
	num--;
     }
}

static int destroy_element (SLang_Array_Type *at,
			    SLindex_Type *dims,
			    VOID_STAR data)
{
   data = get_data_addr (at, dims);
   if (data == NULL)
     return -1;

   /* This function should only get called for arrays that have
    * pointer elements.  Do not call the destroy method if the element
    * is NULL.
    */
   if (NULL != *(VOID_STAR *)data)
     {
	(*at->cl->cl_destroy) (at->data_type, data);
	*(VOID_STAR *) data = NULL;
     }
   return 0;
}

/* This function only gets called when a new array is created.  Thus there
 * is no need to destroy the object first.
 */
static int new_object_element (SLang_Array_Type *at,
			       SLindex_Type *dims,
			       VOID_STAR data)
{
   data = get_data_addr (at, dims);
   if (data == NULL)
     return -1;

   return (*at->cl->cl_init_array_object) (at->data_type, data);
}

static int next_index (SLindex_Type *dims, SLindex_Type *max_dims,
		       unsigned int num_dims, unsigned int *changed_indexp)
{
   while (num_dims)
     {
	SLindex_Type dims_i;

	num_dims--;

	dims_i = dims [num_dims] + 1;
	if (dims_i < (SLindex_Type) max_dims [num_dims])
	  {
	     dims [num_dims] = dims_i;
	     *changed_indexp = num_dims;
	     return 0;
	  }
	dims [num_dims] = 0;
     }

   return -1;
}

int _pSLarray_next_index (SLindex_Type *dims, SLindex_Type *max_dims, unsigned int num_dims)
{
   unsigned int changed_index;
   return next_index (dims, max_dims, num_dims, &changed_index);
}

static int do_method_for_all_elements (SLang_Array_Type *at,
				       int (*method)(SLang_Array_Type *,
						     SLindex_Type *,
						     VOID_STAR),
				       VOID_STAR client_data)
{
   SLindex_Type dims [SLARRAY_MAX_DIMS];
   SLindex_Type *max_dims;
   unsigned int num_dims, changed_index;

   if (at->num_elements == 0)
     return 0;

   max_dims = at->dims;
   num_dims = at->num_dims;

   SLMEMSET((char *)dims, 0, sizeof(dims));

   do
     {
	if (-1 == (*method) (at, dims, client_data))
	  return -1;
     }
   while (0 == next_index (dims, max_dims, num_dims, &changed_index));

   return 0;
}

static void free_array (SLang_Array_Type *at)
{
   unsigned int flags;

   if (at == NULL) return;

   if (at->num_refs > 1)
     {
	at->num_refs -= 1;
	return;
     }

   flags = at->flags;

   if (flags & SLARR_DATA_VALUE_IS_INTRINSIC)
     return;			       /* not to be freed */

   if (flags & SLARR_DATA_VALUE_IS_POINTER)
     (void) do_method_for_all_elements (at, destroy_element, NULL);

   if (at->free_fun != NULL)
     at->free_fun (at);
   else
     SLfree ((char *) at->data);

   SLfree ((char *) at);
}

void SLang_free_array (SLang_Array_Type *at)
{
   free_array (at);
}

/* Here, a and b are assumed to be non-negative */
static int check_overflow_mult_i (SLindex_Type a, SLindex_Type b, SLindex_Type *cp)
{
   if ((a < 0) || (b < 0) || ((b > 0) && (a > INT_MAX/b)))
     return -1;

   *cp = a*b;

   return 0;
}

static int check_overflow_mult_ui (SLuindex_Type a, SLindex_Type b, SLuindex_Type *cp)
{
   if ((b < 0) || ((b > 0) && (a > UINT_MAX/(SLuindex_Type)b)))
     return -1;

   *cp = a*(SLuindex_Type)b;
   return 0;
}

SLang_Array_Type *
SLang_create_array1 (SLtype type, int read_only, VOID_STAR data,
		     SLindex_Type *dims, unsigned int num_dims, int no_init)
{
   SLang_Class_Type *cl;
   SLang_Array_Type *at;
   unsigned int i;
   SLindex_Type num_elements;
   SLindex_Type size;
   int sizeof_type;

   if ((num_dims == 0) || (num_dims > SLARRAY_MAX_DIMS))
     {
	_pSLang_verror (SL_NOT_IMPLEMENTED, "%u dimensional arrays are not supported", num_dims);
	return NULL;
     }

   for (i = 0; i < num_dims; i++)
     {
	if (dims[i] < 0)
	  {
	     _pSLang_verror (SL_INVALID_PARM, "Size of array dim %u is less than 0", i);
	     return NULL;
	  }
     }

   cl = _pSLclass_get_class (type);

   at = (SLang_Array_Type *) SLmalloc (sizeof(SLang_Array_Type));
   if (at == NULL)
     return NULL;

   memset ((char*) at, 0, sizeof(SLang_Array_Type));

   at->data_type = type;
   at->cl = cl;
   at->num_dims = num_dims;
   at->num_refs = 1;

   if (read_only) at->flags = SLARR_DATA_VALUE_IS_READ_ONLY;

   if ((cl->cl_class_type != SLANG_CLASS_TYPE_SCALAR)
       && (cl->cl_class_type != SLANG_CLASS_TYPE_VECTOR))
     at->flags |= SLARR_DATA_VALUE_IS_POINTER;

   /* The elements of an array must be addressable by a single index of
    * SLindex_Type, even for multi-dimensional arrays.  E.g.,
    *   a = Int_Type[m, n];
    *   i = where (a < 0);
    *   a[i] = 0;
    */
   num_elements = 1;
   for (i = 0; i < num_dims; i++)
     {
	at->dims[i] = dims[i];

	if (-1 == check_overflow_mult_i (num_elements, dims[i], &num_elements))
	  {
	     throw_size_error (SL_Index_Error);
	     free_array (at);
	     return NULL;
	  }
     }

   /* Now set the rest of the unused dimensions to 1.  This makes it easier
    * when transposing arrays and when indexing arrays derived from scalars.
    */
   while (i < SLARRAY_MAX_DIMS)
     at->dims[i++] = 1;

   at->num_elements = num_elements;
   at->index_fun = linear_get_data_addr;
   sizeof_type = (int) cl->cl_sizeof_type;
   at->sizeof_type = (unsigned int) sizeof_type;

   if (data != NULL)
     {
	at->data = data;
	return at;
     }

   /* SLmalloc is currently limited to the use of unsigned integers.
    * So include the size of the type as well.
    */
   if (-1 == check_overflow_mult_i (num_elements, sizeof_type, &size))
     {
	throw_size_error (SL_Index_Error);
	free_array (at);
	return NULL;
     }

   if (size == 0) size = 1;

   if (NULL == (data = (VOID_STAR) SLmalloc (size)))
     {
	free_array (at);
	return NULL;
     }

   at->data = data;

   if ((no_init == 0) || (at->flags & SLARR_DATA_VALUE_IS_POINTER))
     memset ((char *) data, 0, size);

   if ((no_init == 0)
       && (cl->cl_init_array_object != NULL)
       && (-1 == do_method_for_all_elements (at, new_object_element, NULL)))
     {
	free_array (at);
	return NULL;
     }

   return at;
}

SLang_Array_Type *
SLang_create_array (SLtype type, int read_only, VOID_STAR data,
		    SLindex_Type *dims, unsigned int num_dims)
{
   return SLang_create_array1 (type, read_only, data, dims, num_dims, 0);
}

int SLang_add_intrinsic_array (SLFUTURE_CONST char *name,
			       SLtype type,
			       int read_only,
			       VOID_STAR data,
			       unsigned int num_dims, ...)
{
   va_list ap;
   unsigned int i;
   SLindex_Type dims[SLARRAY_MAX_DIMS];
   SLang_Array_Type *at;

   if ((num_dims > SLARRAY_MAX_DIMS)
       || (name == NULL)
       || (data == NULL))
     {
	_pSLang_verror (SL_INVALID_PARM, "Unable to create intrinsic array");
	return -1;
     }

   va_start (ap, num_dims);
   for (i = 0; i < num_dims; i++)
     dims [i] = va_arg (ap, int);
   va_end (ap);

   at = SLang_create_array (type, read_only, data, dims, num_dims);
   if (at == NULL)
     return -1;
   at->flags |= SLARR_DATA_VALUE_IS_INTRINSIC;

   /* Note: The variable that refers to the intrinsic array is regarded as
    * read-only.  That way, Array_Name = another_array; will fail.
    */
   if (-1 == SLadd_intrinsic_variable (name, (VOID_STAR) at, SLANG_ARRAY_TYPE, 1))
     {
	free_array (at);
	return -1;
     }
   return 0;
}

static int pop_array_indices (SLindex_Type *dims, unsigned int num_dims)
{
   unsigned int n;
   int i;

   if (num_dims > SLARRAY_MAX_DIMS)
     {
	_pSLang_verror (SL_INVALID_PARM, "Array size not supported");
	return -1;
     }

   n = num_dims;
   while (n != 0)
     {
	n--;
	if (-1 == SLang_pop_integer (&i))
	  return -1;

	dims[n] = i;
     }

   return 0;
}

/* This function gets called via expressions such as Double_Type[10, 20];
 */
static int push_create_new_array (unsigned int num_dims)
{
   SLang_Array_Type *at;
   SLtype type;
   SLindex_Type dims [SLARRAY_MAX_DIMS];
   int (*anew) (SLtype, unsigned int);

   if (-1 == SLang_pop_datatype (&type))
     return -1;

   anew = (_pSLclass_get_class (type))->cl_anew;
   if (anew != NULL)
     return (*anew) (type, num_dims);

   if (-1 == pop_array_indices (dims, num_dims))
     return -1;

   if (NULL == (at = SLang_create_array (type, 0, NULL, dims, num_dims)))
     return -1;

   return SLang_push_array (at, 1);
}

static int push_element_at_addr (SLang_Array_Type *at,
				 VOID_STAR data, int allow_null)
{
   SLang_Class_Type *cl;

   cl = at->cl;
   if ((at->flags & SLARR_DATA_VALUE_IS_POINTER)
       && (*(VOID_STAR *) data == NULL))
     {
	if (allow_null)
	  return SLang_push_null ();

	_pSLang_verror (SL_VARIABLE_UNINITIALIZED,
		      "%s array has uninitialized element", cl->cl_name);
	return -1;
     }

   return (*cl->cl_apush)(at->data_type, data);
}

static int coerse_array_to_linear (SLang_Array_Type *at)
{
   SLarray_Range_Array_Type *range;
   VOID_STAR vdata;
   SLuindex_Type imax;

   /* FIXME: Priority = low.  This assumes that if an array is not linear, then
    * it is a range.
    */
   if (0 == (at->flags & SLARR_DATA_VALUE_IS_RANGE))
     return 0;

   range = (SLarray_Range_Array_Type *) at->data;
   if ((range->has_last_index == 0) || (range->has_first_index == 0))
     {
	_pSLang_verror (SL_INVALID_PARM, "Invalid context for a range array of indeterminate size");
	return -1;
     }

   imax = at->num_elements;

   vdata = (VOID_STAR) _SLcalloc (imax, at->sizeof_type);
   if (vdata == NULL)
     return -1;
   (void) (*range->to_linear_fun)(at, range, vdata);
   SLfree ((char *) range);
   at->data = (VOID_STAR) vdata;
   at->flags &= ~SLARR_DATA_VALUE_IS_RANGE;
   at->index_fun = linear_get_data_addr;
   return 0;
}

static void
free_index_objects (SLang_Object_Type *index_objs, unsigned int num_indices)
{
   unsigned int i;

   for (i = 0; i < num_indices; i++)
     {
	SLang_Object_Type *obj = index_objs + i;
	if (obj->o_data_type != 0)
	  SLang_free_object (obj);
     }
}

static void do_index_error (SLuindex_Type i, SLindex_Type n)
{
   _pSLang_verror (SL_Index_Error, "Array index %lu out of allowed range 0<=index<%ld",
		 (unsigned long)i, (long)n);
}


/* If *is_index_array!=0, then only one index object is returned, which is
 * to index all the elements, and not just a single dimension.
 */
static int
pop_indices (unsigned num_dims, SLindex_Type *dims, SLuindex_Type num_elements,
	     SLang_Object_Type *index_objs, unsigned int num_indices,
	     int *is_index_array)
{
   unsigned int i;

   memset((char *) index_objs, 0, num_indices * sizeof (SLang_Object_Type));

   *is_index_array = 0;

   if (num_indices != num_dims)
     {
	if (num_indices != 1)	       /* when 1, it is an index array */
	  {
	     _pSLang_verror (SL_INVALID_PARM, "wrong number of indices for array");
	     return -1;
	  }
     }

   i = num_indices;
   while (i != 0)
     {
	SLang_Object_Type *obj;
	SLtype data_type;
	SLang_Array_Type *at;

	i--;
	obj = index_objs + i;
	if (SLANG_ARRAY_TYPE != _pSLang_peek_at_stack2 (&data_type))
	  {
	     if (-1 == _pSLang_pop_object_of_type (SLANG_ARRAY_INDEX_TYPE, obj, 0))
	       goto return_error;

	     continue;
	  }
	if (data_type != SLANG_ARRAY_INDEX_TYPE)
	  {
	     if (-1 == SLclass_typecast (SLANG_ARRAY_INDEX_TYPE, 1, 1))
	       return -1;
	  }
	if (-1 == SLang_pop (obj))
	  goto return_error;

	at = obj->v.array_val;
	if (at->flags & SLARR_DATA_VALUE_IS_RANGE)
	  {
	     SLarray_Range_Array_Type *r = (SLarray_Range_Array_Type *) at->data;
	     if ((r->has_last_index == 0) || (r->has_first_index == 0))
	       {
		  /* Cases to consider (positive increment)
		   *   [:]  ==> [0:n-1] all elements
		   *   [:i] ==> [0:i] for i>=0, [0:n+i] for i<0
		   *   [i:] ==> [i:n-1] for i>=0, [i+n:n-1] for i<0
		   * This will allow: [:-3] to index all but last 3, etc.
		   * Also consider cases with a negative increment:
		   *   [::-1] = [n-1,n-2,...0] = [n-1:0:-1]
		   *   [:i:-1] = [n-1,n-2,..i] = [n-1:i:-1]
		   *   [i::-1] = [i,i-1,...0] = [i:0:-1]
		   */
		  SLang_Array_Type *new_at;
		  SLindex_Type first_index, last_index;
		  SLindex_Type delta = r->delta;
		  SLindex_Type n;
		  int ok = 1;

		  if (num_indices == 1)/* could be index array */
		    n = (SLindex_Type)num_elements;
		  else
		    n = dims[i];

		  if (r->has_first_index)
		    {
		       /* Case 3 */
		       first_index = r->first_index;
		       if (first_index < 0) first_index += n;
		       if (delta > 0)
			 {
			    last_index = n-1;   /* [-i:n-1] not ok */
			    ok = (first_index >= 0);
			 }
		       else
			 last_index = 0;   /* [-i:0:-1] ==> empty array, which is ok */
		    }
		  else if (r->has_last_index)
		    {
		       /* case 2 */
		       last_index = r->last_index;
		       if (last_index < 0)
			 last_index += n;
		       if (delta > 0)
			 first_index = 0;   /* [0:-i:1] ==> empty, which is ok */
		       else
			 {
			    first_index = n-1;
			    ok = (last_index >= 0);   /* [n:-i: -1]  not ok */
			 }
		    }
		  else
		    {
		       /* case 0 */
		       if (delta > 0)
			 {
			    first_index = 0;
			    last_index = n - 1;
			 }
		       else
			 {
			    first_index = n-1;
			    last_index = 0;
			 }
		    }

		  if (!ok)
		    {
		       do_index_error (i, n);
		       goto return_error;
		    }

		  if (NULL == (new_at = inline_implicit_index_array (&first_index, &last_index, &delta)))
		    goto return_error;

		  free_array (at);
		  obj->v.array_val = new_at;
	       }
	  }
	if (num_indices == 1)
	  {
	     *is_index_array = 1;
	     return 0;
	  }
     }
   return 0;

   return_error:
   free_index_objects (index_objs, num_indices);
   return -1;
}

int _pSLarray_pop_index (unsigned int num_elements, SLang_Array_Type **ind_atp, SLindex_Type *ind)
{
   SLang_Object_Type index_obj;
   SLindex_Type dims;
   int is_index_array = 0;
   SLang_Array_Type *ind_at;

   *ind_atp = NULL;
   dims = (SLindex_Type) num_elements;
   if (dims < 0)
     {
	SLang_verror (SL_Index_Error, "Object is too large to be indexed");
	return -1;
     }

   if (-1 == pop_indices (1, &dims, num_elements, &index_obj, 1, &is_index_array))
     return -1;

   if (index_obj.o_data_type != SLANG_ARRAY_TYPE)
     {
	*ind = index_obj.v.index_val;
	return 0;
     }

   ind_at = index_obj.v.array_val;

   if (-1 == coerse_array_to_linear (ind_at))
     {
	SLang_free_array (ind_at);
	return -1;
     }
   *ind_atp = ind_at;
   return 0;
}

static int
transfer_n_elements (SLang_Array_Type *at, VOID_STAR dest_data, VOID_STAR src_data,
		     size_t sizeof_type, SLuindex_Type n, int is_ptr)
{
   SLtype data_type;
   SLang_Class_Type *cl;

   if (is_ptr == 0)
     {
	SLMEMCPY ((char *) dest_data, (char *)src_data, n * sizeof_type);
	return 0;
     }

   data_type = at->data_type;
   cl = at->cl;

   while (n != 0)
     {
	if (*(VOID_STAR *)dest_data != NULL)
	  {
	     (*cl->cl_destroy) (data_type, dest_data);
	     *(VOID_STAR *) dest_data = NULL;
	  }

	if (*(VOID_STAR *) src_data == NULL)
	  *(VOID_STAR *) dest_data = NULL;
	else
	  {
	     if (-1 == (*cl->cl_acopy) (data_type, src_data, dest_data))
	       /* No need to destroy anything */
	       return -1;
	  }

	src_data = (VOID_STAR) ((char *)src_data + sizeof_type);
	dest_data = (VOID_STAR) ((char *)dest_data + sizeof_type);

	n--;
     }

   return 0;
}

_INLINE_
int
_pSLarray_aget_transfer_elem (SLang_Array_Type *at, SLindex_Type *indices,
			     VOID_STAR new_data, size_t sizeof_type, int is_ptr)
{
   VOID_STAR at_data;

   /* Since 1 element is being transferred, there is no need to coerce
    * the array to linear.
    */
   if (NULL == (at_data = get_data_addr (at, indices)))
     return -1;

   if (is_ptr == 0)
     {
	memcpy ((char *) new_data, (char *)at_data, sizeof_type);
	return 0;
     }

   return transfer_n_elements (at, new_data, at_data, sizeof_type, 1, is_ptr);
}

static int
aget_transfer_n_elems (SLang_Array_Type *at, SLuindex_Type num, SLindex_Type *start_indices,
		       VOID_STAR new_data, size_t sizeof_type, int is_ptr)
{
   SLuindex_Type i;
   SLuindex_Type last_index = at->num_dims-1;
   SLindex_Type indices[SLARRAY_MAX_DIMS];

   if (num == 0)
     return 0;

   for (i = 0; i <= last_index; i++)
     indices[i] = start_indices[i];

   if ((at->data != NULL)
       && (at->index_fun == linear_get_data_addr))
     {
	VOID_STAR addr_start;
	if (NULL == (addr_start = linear_get_data_addr (at, indices)))
	  return -1;
	indices[last_index] += (num-1);
	if (NULL == linear_get_data_addr (at, indices))
	  return -1;

	if (is_ptr == 0)
	  {
	     memcpy ((char *) new_data, (char *)addr_start, num * sizeof_type);
	     return 0;
	  }

	return transfer_n_elements (at, new_data, (char *)addr_start, sizeof_type, num, is_ptr);
     }

   for (i = 0; i < num; i++)
     {
	VOID_STAR at_data;

	/* Since 1 element is being transferred, there is no need to coerce
	 * the array to linear.
	 */
	if (NULL == (at_data = get_data_addr (at, indices)))
	  return -1;

	if (is_ptr == 0)
	  memcpy ((char *) new_data, (char *)at_data, sizeof_type);
	else if (-1 == transfer_n_elements (at, new_data, at_data, sizeof_type, 1, is_ptr))
	  return -1;

	new_data = (VOID_STAR) ((char *)new_data + sizeof_type);
	indices[last_index]++;
     }
   return 0;
}

#define CHECK_INDEX(_i,_n,_then) \
   if ((_i) < 0) \
   { \
      (_i) += (_n); \
      if ((_i) < 0) (_i) = (_n); \
   } \
   if ((_i) >= (_n)) \
   { \
      do_index_error((_i),(_n)); \
      _then; \
   }(void)0

/* Check that 0 <= | idx, idx + 1*delta, ..., idx + jmax-1 | <= num_elements
 * If jmax == 0, then there are no indices to check.
 */
static int check_range_indices (SLindex_Type idx, SLindex_Type delta, SLindex_Type jmax,
				SLindex_Type num_elements, int *isposp)
{
   SLindex_Type idx_0, idx_1;

   if (jmax == 0)
     {
	if (isposp != NULL) *isposp = 1;
	return 0;
     }

   idx_0 = idx;
   idx_1 = idx + delta*(jmax-1);

   if ((idx_0 >= num_elements) || (idx_1 >= num_elements))
     {
	SLang_set_error (SL_Index_Error);
	return -1;
     }

   if (isposp != NULL) *isposp = (idx_0 >= 0) && (idx_1 >= 0);
   CHECK_INDEX(idx_0, num_elements, return -1);
   CHECK_INDEX(idx_1, num_elements, return -1);
   return 0;
}

#if SLANG_OPTIMIZE_FOR_SPEED
# if SLANG_HAS_FLOAT
#  define GENERIC_TYPE double
#  define AGET_FROM_INDEX_ARRAY_FUN aget_doubles_from_index_array
#  define APUT_FROM_INDEX_ARRAY_FUN aput_doubles_from_index_array
#  include "slagetput.inc"
#  define GENERIC_TYPE float
#  define AGET_FROM_INDEX_ARRAY_FUN aget_floats_from_index_array
#  define APUT_FROM_INDEX_ARRAY_FUN aput_floats_from_index_array
#  include "slagetput.inc"
# endif

# define GENERIC_TYPE int
# define AGET_FROM_INDEX_ARRAY_FUN aget_ints_from_index_array
# define APUT_FROM_INDEX_ARRAY_FUN aput_ints_from_index_array
# include "slagetput.inc"
# if LONG_IS_INT
#  define aget_longs_from_index_array aget_ints_from_index_array
#  define aput_longs_from_index_array aput_ints_from_index_array
# else
#  define GENERIC_TYPE long
#  define AGET_FROM_INDEX_ARRAY_FUN aget_longs_from_index_array
#  define APUT_FROM_INDEX_ARRAY_FUN aput_longs_from_index_array
#  include "slagetput.inc"
# endif
# define GENERIC_TYPE char
# define AGET_FROM_INDEX_ARRAY_FUN aget_chars_from_index_array
# define APUT_FROM_INDEX_ARRAY_FUN aput_chars_from_index_array
# include "slagetput.inc"
# define GENERIC_TYPE short
# define AGET_FROM_INDEX_ARRAY_FUN aget_shorts_from_index_array
# define APUT_FROM_INDEX_ARRAY_FUN aput_shorts_from_index_array
# include "slagetput.inc"
#endif

static int aget_generic_from_index_array (SLang_Array_Type *at,
					  SLang_Array_Type *at_ind, int is_range,
					  unsigned char *dest_data)
{
   SLindex_Type *indices, *indices_max;
   unsigned char *src_data = (unsigned char *) at->data;
   SLindex_Type num_elements = (SLindex_Type) at->num_elements;
   size_t sizeof_type = at->sizeof_type;
   int is_ptr = at->flags & SLARR_DATA_VALUE_IS_POINTER;

   if (is_range)
     {
	SLarray_Range_Array_Type *r = (SLarray_Range_Array_Type *)at_ind->data;
	SLindex_Type idx = r->first_index, delta = r->delta;
	SLuindex_Type j, jmax = at_ind->num_elements;

	if (-1 == check_range_indices (idx, delta, jmax, num_elements, NULL))
	  return -1;

	for (j = 0; j < jmax; j++)
	  {
	     size_t offset;
	     SLindex_Type i = idx;

	     if (i < 0) i += num_elements;
	     offset = sizeof_type * (SLuindex_Type)i;
	     if (-1 == transfer_n_elements (at, (VOID_STAR) dest_data,
					    (VOID_STAR) (src_data + offset),
					    sizeof_type, 1, is_ptr))
	       return -1;

	     dest_data += sizeof_type;
	     idx += delta;
	  }

	return 0;
     }

   /* Since the index array is linear, I can address it directly */
   indices = (SLindex_Type *) at_ind->data;
   indices_max = indices + at_ind->num_elements;
   while (indices < indices_max)
     {
	size_t offset;
	SLindex_Type i = *indices;

	CHECK_INDEX(i, num_elements, return -1);
	offset = sizeof_type * (SLuindex_Type)i;
	if (-1 == transfer_n_elements (at, (VOID_STAR) dest_data,
				       (VOID_STAR) (src_data + offset),
				       sizeof_type, 1, is_ptr))
	  return -1;

	dest_data += sizeof_type;
	indices++;
     }
   return 0;
}

/* Here the ind_at index-array is an n-d array of indices.  This function
 * creates an n-d array of made up of values of 'at' at the locations
 * specified by the indices.  The result is pushed.
 */
static int
aget_from_index_array (SLang_Array_Type *at, SLang_Array_Type *ind_at)
{
   SLang_Array_Type *new_at;
   SLindex_Type num_elements;
   unsigned char *new_data, *src_data;
   int is_ptr, is_range;

   if (-1 == coerse_array_to_linear (at))
     return -1;

   is_range = ind_at->flags & SLARR_DATA_VALUE_IS_RANGE;

   if ((is_range == 0)
       && (-1 == coerse_array_to_linear (ind_at)))
     return -1;

   is_ptr = (at->flags & SLARR_DATA_VALUE_IS_POINTER);
   /* Only initialize the elements of the array if it contains pointers.  If
    * an error occurs when filling the array, the array and its elements will
    * be freed.  Hence, to avoid freeing garbage, the array should be initialized
    */
   if (NULL == (new_at = SLang_create_array1 (at->data_type, 0, NULL, ind_at->dims, ind_at->num_dims, !is_ptr)))
     return -1;

   src_data = (unsigned char *) at->data;
   new_data = (unsigned char *) new_at->data;
   num_elements = (SLindex_Type) at->num_elements;

   if (num_elements < 0)
     {
	_pSLang_verror (SL_Index_Error, "Array is too large to be indexed using an index-array");
	goto return_error;
     }

   switch (at->data_type)
     {
#if SLANG_OPTIMIZE_FOR_SPEED
# if SLANG_HAS_FLOAT
      case SLANG_DOUBLE_TYPE:
	if (-1 == aget_doubles_from_index_array ((double *)src_data, num_elements,
						 ind_at, is_range, (double *)new_data))
	  goto return_error;
	break;
      case SLANG_FLOAT_TYPE:
	if (-1 == aget_floats_from_index_array ((float *)src_data, num_elements,
						ind_at, is_range, (float *)new_data))
	  goto return_error;
	break;
# endif
      case SLANG_CHAR_TYPE:
      case SLANG_UCHAR_TYPE:
	if (-1 == aget_chars_from_index_array ((char *)src_data, num_elements,
					       ind_at, is_range, (char *)new_data))
	  goto return_error;
	break;
      case SLANG_SHORT_TYPE:
      case SLANG_USHORT_TYPE:
	if (-1 == aget_shorts_from_index_array ((short *)src_data, num_elements,
					      ind_at, is_range, (short *)new_data))
	  goto return_error;
	break;

      case SLANG_LONG_TYPE:
      case SLANG_ULONG_TYPE:
	/* fall through */
# if LONG_IS_NOT_INT
	if (-1 == aget_longs_from_index_array ((long *)src_data, num_elements,
					      ind_at, is_range, (long *)new_data))
	  goto return_error;
	break;
# endif
      case SLANG_INT_TYPE:
      case SLANG_UINT_TYPE:
	if (-1 == aget_ints_from_index_array ((int *)src_data, num_elements,
					      ind_at, is_range, (int *)new_data))
	  goto return_error;
	break;
#endif				       /* SLANG_OPTIMIZE_FOR_SPEED */
      default:
	if (-1 == aget_generic_from_index_array (at, ind_at, is_range, new_data))
	  goto return_error;
     }

   return SLang_push_array (new_at, 1);

   return_error:
   free_array (new_at);
   return -1;
}

/* This is extremely ugly.  It is due to the fact that the index_objects
 * may contain ranges.  This is a utility function for the aget/aput
 * routines
 */
static int
convert_nasty_index_objs (SLang_Array_Type *at,
			  SLang_Object_Type *index_objs,
			  unsigned int num_indices,
			  SLindex_Type **index_data,
			  SLindex_Type *range_buf, SLindex_Type *range_delta_buf,
			  SLindex_Type *max_dims,
			  SLuindex_Type *num_elements,
			  int *is_array, int is_dim_array[SLARRAY_MAX_DIMS])
{
   SLuindex_Type i, total_num_elements;
   SLang_Array_Type *ind_at;

   if (num_indices != at->num_dims)
     {
	_pSLang_verror (SL_INVALID_PARM, "Array requires %u indices", at->num_dims);
	return -1;
     }

   *is_array = 0;
   total_num_elements = 1;
   for (i = 0; i < num_indices; i++)
     {
	SLang_Object_Type *obj = index_objs + i;
	range_delta_buf [i] = 0;

	if (obj->o_data_type == SLANG_ARRAY_INDEX_TYPE)
	  {
	     range_buf [i] = obj->v.index_val;
	     max_dims [i] = 1;
	     index_data[i] = range_buf + i;
	     is_dim_array[i] = 0;
	  }
#if SLANG_ARRAY_INDEX_TYPE != SLANG_INT_TYPE
	else if (obj->o_data_type == SLANG_INT_TYPE)
	  {
	     range_buf [i] = obj->v.index_val;
	     max_dims [i] = 1;
	     index_data[i] = range_buf + i;
	     is_dim_array[i] = 0;
	  }
#endif
	else
	  {
	     *is_array = 1;
	     is_dim_array[i] = 1;
	     ind_at = obj->v.array_val;

	     if (ind_at->flags & SLARR_DATA_VALUE_IS_RANGE)
	       {
		  SLarray_Range_Array_Type *r;

		  r = (SLarray_Range_Array_Type *) ind_at->data;
		  range_buf[i] = r->first_index;
		  range_delta_buf [i] = r->delta;
		  max_dims[i] = (SLindex_Type) ind_at->num_elements;
	       }
	     else
	       {
		  index_data [i] = (SLindex_Type *) ind_at->data;
		  max_dims[i] = (SLindex_Type) ind_at->num_elements;
	       }
	  }

	if (-1 == check_overflow_mult_ui (total_num_elements, max_dims[i], &total_num_elements))
	  {
	     throw_size_error (SL_Index_Error);
	     return -1;
	  }
     }

   *num_elements = total_num_elements;
   return 0;
}

/* This routine pushes a 1-d vector of values from 'at' indexed by
 * the objects 'index_objs'.  These objects can either be integers or
 * 1-d integer arrays.  The fact that the 1-d arrays can be ranges
 * makes this look ugly.
 */
static int
aget_from_indices (SLang_Array_Type *at,
		   SLang_Object_Type *index_objs, unsigned int num_indices)
{
   SLindex_Type *index_data [SLARRAY_MAX_DIMS];
   SLindex_Type range_buf [SLARRAY_MAX_DIMS];
   SLindex_Type range_delta_buf [SLARRAY_MAX_DIMS];
   SLindex_Type max_dims [SLARRAY_MAX_DIMS];
   SLuindex_Type num_elements;
   SLang_Array_Type *new_at;
   SLindex_Type map_indices[SLARRAY_MAX_DIMS];
   SLindex_Type indices [SLARRAY_MAX_DIMS];
   SLindex_Type *at_dims;
   size_t sizeof_type;
   unsigned int i, last_changed_index;
   int is_ptr, ret, is_array;
   char *new_data;
   SLang_Class_Type *cl;
   int is_dim_array[SLARRAY_MAX_DIMS];
   SLuindex_Type last_index;
   SLindex_Type last_index_num;
   int fast_ok = 0;

   if (-1 == convert_nasty_index_objs (at, index_objs, num_indices,
				       index_data, range_buf, range_delta_buf,
				       max_dims, &num_elements, &is_array,
				       is_dim_array))
     return -1;

   is_ptr = (at->flags & SLARR_DATA_VALUE_IS_POINTER);
   sizeof_type = at->sizeof_type;

   /* cl = _pSLclass_get_class (at->data_type); */
   cl = at->cl;

   if ((is_array == 0) && (num_elements == 1))
     {
	new_data = (char *)cl->cl_transfer_buf;
	memset (new_data, 0, sizeof_type);
	new_at = NULL;
     }
   else
     {
	SLindex_Type i_num_elements = (SLindex_Type)num_elements;

	new_at = SLang_create_array (at->data_type, 0, NULL, &i_num_elements, 1);
	if (NULL == new_at)
	  return -1;

	new_data = (char *)new_at->data;

	if (num_elements == 0)
	  goto fixup_dims;
     }

   at_dims = at->dims;
   memset ((char *) map_indices, 0, sizeof(map_indices));

   last_index = num_indices - 1;
   /* if last_index_num is non-zero, then that many can be transferred quickly */
   if ((range_delta_buf[last_index] == 1) && (range_buf[last_index] >= 0))
     last_index_num = max_dims[last_index];
   else
     {
	last_index_num = 0;
	fast_ok = ((is_ptr == 0) && (at->index_fun == linear_get_data_addr));
     }

   last_changed_index = 0;
   while (1)
     {
	for (i = last_changed_index; i < num_indices; i++)
	  {
	     SLindex_Type j = map_indices[i];
	     SLindex_Type indx;

	     if (0 != range_delta_buf[i])
	       indx = range_buf[i] + j * range_delta_buf[i];
	     else
	       indx = index_data [i][j];

	     CHECK_INDEX (indx, at_dims[i], free_array(new_at); return -1);
	     indices[i] = indx;
	  }

	if (last_index_num)
	  {
	     if (-1 == aget_transfer_n_elems (at, last_index_num, indices, (VOID_STAR)new_data, sizeof_type, is_ptr))
	       {
		  free_array (new_at);
		  return -1;
	       }
	     new_data += last_index_num * sizeof_type;
	     map_indices[last_index] = last_index_num;

	     if (0 != next_index (map_indices, max_dims, num_indices, &last_changed_index))
	       break;
	  }
	else
	  {
	     if (fast_ok)
	       {
		  size_t ofs = indices[0];
		  /* Index ranges have been checked above, no pointers, and a linear range */
		  for (i = 1; i < num_indices; i++)
		    ofs = ofs*at_dims[i] + indices[i];

		  if (ofs >= at->num_elements)
		    {
		       SLang_set_error (SL_Index_Error);
		       free_array (new_at);
		       return -1;
		    }
		  memcpy ((VOID_STAR)new_data, (char *)at->data + ofs*sizeof_type, sizeof_type);
	       }
	     else if (-1 == _pSLarray_aget_transfer_elem (at, indices, (VOID_STAR)new_data, sizeof_type, is_ptr))
	       {
		  free_array (new_at);
		  return -1;
	       }

	     new_data += sizeof_type;

	     if (num_indices == 1)
	       {
		  map_indices[0]++;
		  if (map_indices[0] == max_dims[0])
		    break;
	       }
	     else if (0 != next_index (map_indices, max_dims, num_indices, &last_changed_index))
	       break;
	  }
     }

fixup_dims:

   if (new_at != NULL)
     {
	int num_dims = 0;
	/* Fixup dimensions on array */
	for (i = 0; i < num_indices; i++)
	  {
	     if (is_dim_array[i]) /* was: (max_dims[i] > 1) */
	       {
		  new_at->dims[num_dims] = max_dims[i];
		  num_dims++;
	       }
	  }

	if (num_dims != 0) new_at->num_dims = num_dims;
	return SLang_push_array (new_at, 1);
     }

   /* Here new_data is a whole new copy, so free it after the push */
   new_data -= sizeof_type;
   if (is_ptr && (*(VOID_STAR *)new_data == NULL))
     ret = SLang_push_null ();
   else
     {
	ret = (*cl->cl_apush) (at->data_type, (VOID_STAR)new_data);
	(*cl->cl_adestroy) (at->data_type, (VOID_STAR)new_data);
     }

   return ret;
}

static void do_nothing (SLang_Array_Type *at)
{
   (void) at;
}

static int push_string_as_array (unsigned char *s, SLstrlen_Type len, int is_transient)
{
   SLindex_Type ilen;
   SLang_Array_Type *at;

   ilen = (SLindex_Type) len;

   if (is_transient)
     {
	if (NULL == (at = SLang_create_array (SLANG_UCHAR_TYPE, 0, s, &ilen, 1)))
	  return -1;
	at->free_fun = do_nothing;
     }
   else
     {
	if (NULL ==  (at = SLang_create_array (SLANG_UCHAR_TYPE, 0, NULL, &ilen, 1)))
	  return -1;

	memcpy ((char *)at->data, (char *)s, len);
     }

   return SLang_push_array (at, 1);
}

static int pop_array_as_string (char **sp)
{
   SLang_Array_Type *at;
   int ret;

   *sp = NULL;

   if (-1 == SLang_pop_array_of_type (&at, SLANG_UCHAR_TYPE))
     return -1;

   ret = 0;

   if (NULL == (*sp = SLang_create_nslstring ((char *) at->data, at->num_elements)))
     ret = -1;

   free_array (at);
   return ret;
}

static int pop_array_as_bstring (SLang_BString_Type **bs)
{
   SLang_Array_Type *at;
   int ret;

   *bs = NULL;

   if (-1 == SLang_pop_array_of_type (&at, SLANG_UCHAR_TYPE))
     return -1;

   ret = 0;

   if (NULL == (*bs = SLbstring_create ((unsigned char *) at->data, at->num_elements)))
     ret = -1;

   free_array (at);
   return ret;
}

/* This routine assumes that the array is 1d */
int _pSLarray1d_push_elem (SLang_Array_Type *at, SLindex_Type idx)
{
   VOID_STAR data;
   char *new_data;
   size_t sizeof_type;
   int is_ptr, ret;
   SLang_Class_Type *cl;

   switch (at->data_type)
     {
      case SLANG_CHAR_TYPE:
	if (NULL == (data = at->index_fun(at, &idx))) return -1;
	return SLclass_push_char_obj (SLANG_CHAR_TYPE, *(signed char *)data);

      case SLANG_INT_TYPE:
	if (NULL == (data = at->index_fun(at, &idx))) return -1;
	return SLclass_push_int_obj (SLANG_INT_TYPE, *(int *)data);

#if SLANG_HAS_FLOAT
      case SLANG_DOUBLE_TYPE:
	if (NULL == (data = at->index_fun(at, &idx))) return -1;
	return SLclass_push_double_obj (SLANG_DOUBLE_TYPE, *(double *)data);
#endif
     }

   is_ptr = (at->flags & SLARR_DATA_VALUE_IS_POINTER);
   sizeof_type = at->sizeof_type;
   cl = at->cl;
   new_data = (char *)cl->cl_transfer_buf;
   memset (new_data, 0, sizeof_type);

   if (-1 == _pSLarray_aget_transfer_elem (at, &idx, (VOID_STAR)new_data, sizeof_type, is_ptr))
     return -1;

   if (is_ptr && (*(VOID_STAR *)new_data == NULL))
     return SLang_push_null ();

   ret = (*cl->cl_apush) (at->data_type, (VOID_STAR)new_data);
   (*cl->cl_adestroy) (at->data_type, (VOID_STAR)new_data);
   return ret;
}

static int aget_from_array (unsigned int num_indices)
{
   SLang_Array_Type *at;
   SLang_Object_Type index_objs [SLARRAY_MAX_DIMS];
   unsigned int i;
   int is_index_array, free_indices, ret;

   /* Implementation note: The push_string_element function calls this with
    * num_indices==1, and assumes that the pop_array call below will happen.
    * No code should be added between these checks and the pop_array call.
    */
   if (num_indices == 0)
     {
	SLang_set_error (SL_Index_Error);
	return -1;
     }
   if (num_indices > SLARRAY_MAX_DIMS)
     {
	_pSLang_verror (SL_INVALID_PARM, "Number of dims must be less than %d", 1+SLARRAY_MAX_DIMS);
	return -1;
     }
   if (-1 == pop_array (&at, 1))
     return -1;

   /* Allow a scalar to be indexed using any number of indices, e.g.,
    *    x = 2;  a = x[0]; b = x[0,0];
    */
   if ((at->flags & SLARR_DERIVED_FROM_SCALAR)
       && (at->num_refs == 1))
     {
	at->num_dims = num_indices;
     }

   if (-1 == pop_indices (at->num_dims, at->dims, at->num_elements, index_objs, num_indices, &is_index_array))
     {
	free_array (at);
	return -1;
     }
   free_indices = 1;

   if (is_index_array == 0)
     {
#if SLANG_OPTIMIZE_FOR_SPEED
	SLindex_Type indices[SLARRAY_MAX_DIMS];

	for (i = 0; i < num_indices; i++)
	  {
	     if (index_objs[i].o_data_type != SLANG_ARRAY_INDEX_TYPE)
	       break;
	     indices[i] = index_objs[i].v.index_val;
	  }
	if (i == num_indices)
	  {
	     VOID_STAR addr = (*at->index_fun)(at, indices);

	     if (addr == NULL)
	       ret = -1;
	     else
	       ret = push_element_at_addr (at, addr, 1);
	     free_array (at);
	     return ret;
	  }
#endif
	ret = aget_from_indices (at, index_objs, num_indices);
     }
   else
     ret = aget_from_index_array (at, index_objs[0].v.array_val);

   free_array (at);
   if (free_indices)
     {
	for (i = 0; i < num_indices; i++)
	  SLang_free_object (index_objs + i);
     }

   return ret;
}

static int push_string_element (SLtype type, unsigned char *s, SLuindex_Type len)
{
   SLindex_Type i;

   if (SLang_peek_at_stack () == SLANG_ARRAY_TYPE)
     {
	char *str;

	/* The indices are array values.  So, do this: */
	if (-1 == push_string_as_array (s, len, 1))
	  return -1;

	if (-1 == aget_from_array (1))
	  return -1;

	if (type == SLANG_BSTRING_TYPE)
	  {
	     SLang_BString_Type *bs;
	     int ret;

	     if (-1 == pop_array_as_bstring (&bs))
	       return -1;

	     ret = SLang_push_bstring (bs);
	     SLbstring_free (bs);
	     return ret;
	  }

	if (-1 == pop_array_as_string (&str))
	  return -1;
	return _pSLang_push_slstring (str);   /* frees s upon error */
     }

   if (-1 == SLang_pop_array_index (&i))
     return -1;

   if (i < 0) i = i + (SLindex_Type)len;
   if ((SLuindex_Type) i > len)
     i = len;			       /* get \0 character --- bstrings include it as well */

   return SLang_push_uchar (s[(SLuindex_Type)i]);
}

/* ARRAY[i, j, k] generates code: __args i j ...k ARRAY __aput/__aget
 * Here i, j, ... k may be a mixture of integers and 1-d arrays, or
 * a single array of indices.  The index array is generated by the
 * 'where' function.
 *
 * If ARRAY is of type DataType, then this function will create an array of
 * the appropriate type.  In that case, the indices i, j, ..., k must be
 * integers.
 */
int _pSLarray_aget1 (unsigned int num_indices)
{
   int type;
   int (*aget_fun) (SLtype, unsigned int);

   type = SLang_peek_at_stack ();
   switch (type)
     {
      case -1:
	return -1;		       /* stack underflow */

      case SLANG_DATATYPE_TYPE:
	return push_create_new_array (num_indices);

      case SLANG_BSTRING_TYPE:
	if (1 == num_indices)
	  {
	     SLang_BString_Type *bs;
	     int ret;
	     SLstrlen_Type len;
	     unsigned char *s;

	     if (-1 == SLang_pop_bstring (&bs))
	       return -1;

	     if (NULL == (s = SLbstring_get_pointer (bs, &len)))
	       ret = -1;
	     else
	       ret = push_string_element (type, s, len);

	     SLbstring_free (bs);
	     return ret;
	  }
	break;

      case SLANG_STRING_TYPE:
	if (1 == num_indices)
	  {
	     char *s;
	     int ret;

	     if (-1 == SLang_pop_slstring (&s))
	       return -1;

	     ret = push_string_element (type, (unsigned char *)s, _pSLstring_bytelen (s));
	     _pSLang_free_slstring (s);
	     return ret;
	  }
	break;

      case SLANG_ARRAY_TYPE:
	break;

      case SLANG_ASSOC_TYPE:
	return _pSLassoc_aget (type, num_indices);

      default:
	aget_fun = _pSLclass_get_class (type)->cl_aget;
	if (NULL != aget_fun)
	  return (*aget_fun) (type, num_indices);
     }

   return aget_from_array (num_indices);
}

int _pSLarray_aget (void)
{
   return _pSLarray_aget1 ((unsigned int)(SLang_Num_Function_Args-1));
}

/* A[indices...indices+num] = data... */
static int
aput_transfer_n_elems (SLang_Array_Type *at, SLindex_Type num, SLindex_Type *start_indices,
		       char *data_to_put, SLuindex_Type data_increment, size_t sizeof_type, int is_ptr)
{
   VOID_STAR addr_start;
   SLindex_Type i;
   int last_index = (int)at->num_dims-1;
   SLindex_Type indices[SLARRAY_MAX_DIMS];

   if (num == 0)
     return 0;

   for (i = 0; i <= (int) last_index; i++)
     indices[i] = start_indices[i];

   if ((at->data != NULL)
       && (at->index_fun == linear_get_data_addr))
     {
	VOID_STAR addr_end;
	if (NULL == (addr_start = linear_get_data_addr (at, indices)))
	  return -1;
	indices[last_index] += (num-1);
	if (NULL == (addr_end = linear_get_data_addr (at, indices)))
	  return -1;

	if (is_ptr == 0)
	  {
	     while (addr_start <= addr_end)
	       {
		  memcpy ((char *) addr_start, data_to_put, sizeof_type);
		  data_to_put += data_increment;
		  addr_start = (VOID_STAR) ((char *)addr_start + sizeof_type);
	       }
	     return 0;
	  }

	while (addr_start <= addr_end)
	  {
	     if (-1 == transfer_n_elements (at, addr_start, data_to_put, sizeof_type, 1, is_ptr))
	       return -1;
	     data_to_put += data_increment;
	     addr_start = (VOID_STAR) ((char *)addr_start + sizeof_type);
	  }
	return 0;
     }

   for (i = 0; i < num; i++)
     {
	/* Since 1 element is being transferred, there is no need to coerce
	 * the array to linear.
	 */
	if (NULL == (addr_start = get_data_addr (at, indices)))
	  return -1;

	if (is_ptr == 0)
	  memcpy ((char *) addr_start, data_to_put, sizeof_type);
	else if (-1 == transfer_n_elements (at, addr_start, (VOID_STAR)data_to_put, sizeof_type, 1, is_ptr))
	  return -1;

	data_to_put += data_increment;
	indices[last_index]++;
     }
   return 0;
}

_INLINE_ int
_pSLarray_aput_transfer_elem (SLang_Array_Type *at, SLindex_Type *indices,
			     VOID_STAR data_to_put, size_t sizeof_type, int is_ptr)
{
   VOID_STAR at_data;

   /*
    * A range array is not allowed here.  I should add a check for it.  At
    * the moment, one will not get here.
    */
   if (NULL == (at_data = get_data_addr (at, indices)))
     return -1;

   if (is_ptr == 0)
     {
	memcpy ((char *) at_data, (char *)data_to_put, sizeof_type);
	return 0;
     }

   return transfer_n_elements (at, at_data, data_to_put, sizeof_type, 1, is_ptr);
}

static int
aput_get_data_to_put (SLang_Class_Type *cl, SLuindex_Type num_elements, int allow_array,
		      SLang_Array_Type **at_ptr, char **data_to_put, SLuindex_Type *data_increment)
{
   SLtype data_type;
   int type;
   SLang_Array_Type *at;

   *at_ptr = NULL;

   data_type = cl->cl_data_type;
   type = SLang_peek_at_stack ();

   if ((SLtype)type != data_type)
     {
	if ((type != SLANG_NULL_TYPE)
	    || ((cl->cl_class_type != SLANG_CLASS_TYPE_PTR)
		&& (cl->cl_class_type != SLANG_CLASS_TYPE_MMT)))
	  {
	     if (-1 == SLclass_typecast (data_type, 1, allow_array))
	       return -1;
	  }
	else
	  {
	     /* This bit of code allows, e.g., a[10] = NULL; */
	     *data_increment = 0;
	     *data_to_put = (char *) cl->cl_transfer_buf;
	     *((char **)cl->cl_transfer_buf) = NULL;
	     return SLdo_pop ();
	  }
     }

   if (allow_array
       && (data_type != SLANG_ARRAY_TYPE)
       && (data_type != SLANG_ANY_TYPE)
       && (SLANG_ARRAY_TYPE == SLang_peek_at_stack ()))
     {
	if (-1 == SLang_pop_array (&at, 0))
	  return -1;

	if (at->num_elements != num_elements)
	  {
	     _pSLang_verror (SL_Index_Error, "Array size is inappropriate for use with index-array");
	     free_array (at);
	     return -1;
	  }

	*data_to_put = (char *) at->data;
	*data_increment = at->sizeof_type;
	*at_ptr = at;
	return 0;
     }

   *data_increment = 0;
   *data_to_put = (char *) cl->cl_transfer_buf;

   if (-1 == (*cl->cl_apop)(data_type, (VOID_STAR) *data_to_put))
     return -1;

   return 0;
}

static int
aput_from_index_objs (SLang_Array_Type *at,
		   SLang_Object_Type *index_objs, unsigned int num_indices)
{
   SLindex_Type *index_data [SLARRAY_MAX_DIMS];
   SLindex_Type range_buf [SLARRAY_MAX_DIMS];
   SLindex_Type range_delta_buf [SLARRAY_MAX_DIMS];
   SLindex_Type max_dims [SLARRAY_MAX_DIMS];
   SLindex_Type *at_dims;
   SLuindex_Type num_elements;
   SLang_Array_Type *bt;
   SLindex_Type map_indices[SLARRAY_MAX_DIMS];
   SLindex_Type indices [SLARRAY_MAX_DIMS];
   size_t sizeof_type;
   unsigned int i, last_changed_index;
   int is_ptr, is_array, ret, fast_ok = 0;
   char *data_to_put;
   SLuindex_Type data_increment;
   SLang_Class_Type *cl;
   int is_dim_array [SLARRAY_MAX_DIMS];
   SLindex_Type last_index_num;
   unsigned int last_index;

   if (-1 == convert_nasty_index_objs (at, index_objs, num_indices,
				       index_data, range_buf, range_delta_buf,
				       max_dims, &num_elements, &is_array,
				       is_dim_array))
     return -1;

   cl = at->cl;

   if (-1 == aput_get_data_to_put (cl, num_elements, is_array,
				    &bt, &data_to_put, &data_increment))
     return -1;

   sizeof_type = at->sizeof_type;
   is_ptr = (at->flags & SLARR_DATA_VALUE_IS_POINTER);

   ret = -1;

   at_dims = at->dims;
   SLMEMSET((char *) map_indices, 0, sizeof(map_indices));

   last_index = num_indices - 1;
   /* if last_index_num is non-zero, then that many can be transferred quickly */
   if ((range_delta_buf[last_index] == 1) && (range_buf[last_index] >= 0))
     last_index_num = max_dims[last_index];
   else
     {
	last_index_num = 0;
	fast_ok = ((is_ptr == 0) && (at->index_fun == linear_get_data_addr));
     }

   last_changed_index = 0;
   if (num_elements) while (1)
     {
	for (i = last_changed_index; i < num_indices; i++)
	  {
	     SLindex_Type j = map_indices[i];
	     SLindex_Type indx;

	     if (0 != range_delta_buf[i])
	       indx = range_buf[i] + j * range_delta_buf[i];
	     else
	       indx = index_data [i][j];

	     CHECK_INDEX(indx, at_dims[i], goto return_error);
	     indices[i] = indx;
	  }

	if (last_index_num)
	  {
	     if (-1 == aput_transfer_n_elems (at, last_index_num, indices,
					      data_to_put, data_increment,
					      sizeof_type, is_ptr))
	       goto return_error;

	     data_to_put += last_index_num * data_increment;
	     map_indices[last_index] = last_index_num;

	     if (0 != next_index (map_indices, max_dims, num_indices, &last_changed_index))
	       break;
	  }
	else
	  {
	     if (fast_ok)
	       {
		  size_t ofs = indices[0];
		  /* Index ranges have been checked above, no pointers, and a linear range */
		  for (i = 1; i < num_indices; i++)
		    ofs = ofs*at_dims[i] + indices[i];

		  if (ofs >= at->num_elements)
		    {
		       SLang_set_error (SL_Index_Error);
		       goto return_error;
		    }
		  memcpy ((char *)at->data + ofs*sizeof_type, (VOID_STAR)data_to_put, sizeof_type);
	       }
	     else if (-1 == _pSLarray_aput_transfer_elem (at, indices, (VOID_STAR)data_to_put, sizeof_type, is_ptr))
	       goto return_error;

	     data_to_put += data_increment;

	     if (num_indices == 1)
	       {
		  map_indices[0]++;
		  if (map_indices[0] == max_dims[0])
		    break;
	       }
	     else if (0 != next_index (map_indices, max_dims, num_indices, &last_changed_index))
	       break;
	  }
     }

   ret = 0;

   /* fall through */

return_error:
   if (bt == NULL)
     {
	if (is_ptr)
	  (*cl->cl_destroy) (cl->cl_data_type, (VOID_STAR) data_to_put);
     }
   else free_array (bt);

   return ret;
}

static int
aput_generic_from_index_array (char *src_data,
			       SLuindex_Type data_increment,
			       SLang_Array_Type *ind_at, int is_range,
			       SLang_Array_Type *dest_at)
{
   SLindex_Type num_elements = (SLindex_Type) dest_at->num_elements;
   size_t sizeof_type = dest_at->sizeof_type;
   int is_ptr = dest_at->flags & SLARR_DATA_VALUE_IS_POINTER;
   char *dest_data = (char *)dest_at->data;
   SLindex_Type *indices, *indices_max;

   if (is_range)
     {
	SLarray_Range_Array_Type *r = (SLarray_Range_Array_Type *)ind_at->data;
	SLindex_Type idx = r->first_index, delta = r->delta;
	SLuindex_Type j, jmax = ind_at->num_elements;

	if (-1 == check_range_indices (idx, delta, jmax, num_elements, NULL))
	  return -1;

	for (j = 0; j < jmax; j++)
	  {
	     size_t offset;
	     SLindex_Type i = idx;

	     if (i < 0) i += num_elements;
	     offset = sizeof_type * (SLuindex_Type)i;
	     if (-1 == transfer_n_elements (dest_at, (VOID_STAR) (dest_data + offset),
					    (VOID_STAR) src_data, sizeof_type,
					    1, is_ptr))
	       return -1;

	     src_data += data_increment;
	     idx += delta;
	  }
	return 0;
     }

   /* Since the index array is linear, I can address it directly */
   indices = (SLindex_Type *) ind_at->data;
   indices_max = indices + ind_at->num_elements;

   while (indices < indices_max)
     {
	size_t offset;
	SLindex_Type i = *indices;

	CHECK_INDEX(i,num_elements,return -1);
	offset = sizeof_type * (SLuindex_Type)i;

	if (-1 == transfer_n_elements (dest_at, (VOID_STAR) (dest_data + offset),
				       (VOID_STAR) src_data, sizeof_type, 1,
				       is_ptr))
	  return -1;

	indices++;
	src_data += data_increment;
     }

   return 0;
}

static int
aput_from_index_array (SLang_Array_Type *at, SLang_Array_Type *ind_at)
{
   char *data_to_put, *dest_data;
   SLuindex_Type data_increment;
   SLindex_Type num_elements;
   int is_ptr, is_range;
   SLang_Array_Type *bt;
   SLang_Class_Type *cl;
   int ret;

   if (-1 == coerse_array_to_linear (at))
     return -1;

   is_range = ind_at->flags & SLARR_DATA_VALUE_IS_RANGE;

   if ((is_range == 0)
       && (-1 == coerse_array_to_linear (ind_at)))
     return -1;

   cl = at->cl;

   /* Note that if bt is returned as non NULL, then the array is a linear
    * one.
    */
   if (-1 == aput_get_data_to_put (cl, ind_at->num_elements, 1,
				    &bt, &data_to_put, &data_increment))
     return -1;

   is_ptr = (at->flags & SLARR_DATA_VALUE_IS_POINTER);
   dest_data = (char *) at->data;
   num_elements = (SLindex_Type) at->num_elements;

   ret = -1;
   switch (at->data_type)
     {
#if SLANG_OPTIMIZE_FOR_SPEED
# if SLANG_HAS_FLOAT
      case SLANG_DOUBLE_TYPE:
	if (-1 == aput_doubles_from_index_array (data_to_put, data_increment,
						 ind_at, is_range,
						 (double*)dest_data, num_elements))
	  goto return_error;
	break;

      case SLANG_FLOAT_TYPE:
	if (-1 == aput_floats_from_index_array (data_to_put, data_increment,
						ind_at, is_range,
						(float*)dest_data, num_elements))
	  goto return_error;
	break;
# endif

      case SLANG_CHAR_TYPE:
      case SLANG_UCHAR_TYPE:
	if (-1 == aput_chars_from_index_array (data_to_put, data_increment,
					       ind_at, is_range,
					       (char*)dest_data, num_elements))
	  goto return_error;
	break;
      case SLANG_SHORT_TYPE:
      case SLANG_USHORT_TYPE:
	if (-1 == aput_shorts_from_index_array (data_to_put, data_increment,
						ind_at, is_range,
						(short*)dest_data, num_elements))
	  goto return_error;
	break;

      case SLANG_LONG_TYPE:
      case SLANG_ULONG_TYPE:
	/* fall through */
#if LONG_IS_NOT_INT
	if (-1 == aput_longs_from_index_array (data_to_put, data_increment,
					      ind_at, is_range,
					      (long*)dest_data, num_elements))
	  goto return_error;
	break;
#endif
      case SLANG_INT_TYPE:
      case SLANG_UINT_TYPE:
	if (-1 == aput_ints_from_index_array (data_to_put, data_increment,
					      ind_at, is_range,
					      (int*)dest_data, num_elements))
	  goto return_error;
	break;
#endif
      default:
	if (-1 == aput_generic_from_index_array (data_to_put, data_increment,
						 ind_at, is_range, at))
	  goto return_error;
     }

   ret = 0;
   /* fall through */

   return_error:

   if (bt == NULL)
     {
	if (is_ptr)
	  (*cl->cl_destroy) (cl->cl_data_type, (VOID_STAR)data_to_put);
     }
   else free_array (bt);

   return ret;
}

/* ARRAY[i, j, k] = generates code: __args i j k ARRAY __aput
 */
int _pSLarray_aput1 (unsigned int num_indices)
{
   SLang_Array_Type *at;
   SLang_Object_Type index_objs [SLARRAY_MAX_DIMS];
   int ret;
   int is_index_array;
   int (*aput_fun) (SLtype, unsigned int);
   int type;

   ret = -1;

   type = SLang_peek_at_stack ();
   switch (type)
     {
      case -1:
	return -1;

      case SLANG_ARRAY_TYPE:
	break;

      case SLANG_ASSOC_TYPE:
	return _pSLassoc_aput (type, num_indices);

      default:
	if (NULL != (aput_fun = _pSLclass_get_class (type)->cl_aput))
	  return (*aput_fun) (type, num_indices);
	break;
     }

   if (-1 == SLang_pop_array (&at, 0))
     return -1;

   if (at->flags & SLARR_DATA_VALUE_IS_READ_ONLY)
     {
	_pSLang_verror (SL_READONLY_ERROR, "%s Array is read-only",
		      SLclass_get_datatype_name (at->data_type));
	free_array (at);
	return -1;
     }

   if (-1 == pop_indices (at->num_dims, at->dims, at->num_elements, index_objs, num_indices, &is_index_array))
     {
	free_array (at);
	return -1;
     }

   if (is_index_array == 0)
     {
#if SLANG_OPTIMIZE_FOR_SPEED
	if ((0 == (at->flags & (SLARR_DATA_VALUE_IS_RANGE|SLARR_DATA_VALUE_IS_POINTER)))
	    && (at->data != NULL))
	  {
	     SLindex_Type indices[SLARRAY_MAX_DIMS];
	     unsigned int i;

	     for (i = 0; i < num_indices; i++)
	       {
		  if (index_objs[i].o_data_type != SLANG_ARRAY_INDEX_TYPE)
		    break;
		  indices[i] = index_objs[i].v.index_val;
	       }
	     if (i == num_indices)
	       {
		  VOID_STAR addr = (*at->index_fun)(at, indices);

		  if (addr == NULL)
		    ret = -1;
		  else switch (at->data_type)
		    {
		     case SLANG_CHAR_TYPE:
		       ret = SLang_pop_char ((char *)addr);
		       break;
		     case SLANG_INT_TYPE:
		       ret = SLang_pop_integer ((int *)addr);
		       break;
# if SLANG_HAS_FLOAT
		     case SLANG_DOUBLE_TYPE:
		       ret = SLang_pop_double ((double *)addr);
		       break;
# endif
		     default:
		       ret = aput_from_index_objs (at, index_objs, num_indices);
		    }
		  free_array (at);
		  return ret;
	       }

	     /* drop */
	  }
#endif
	ret = aput_from_index_objs (at, index_objs, num_indices);
     }
   else
     ret = aput_from_index_array (at, index_objs[0].v.array_val);

   free_array (at);
   free_index_objects (index_objs, num_indices);
   return ret;
}

int _pSLarray_aput (void)
{
   return _pSLarray_aput1 ((unsigned int)(SLang_Num_Function_Args-1));
}

static int _pSLmergesort (void *obj,
			  SLindex_Type *sort_indices, SLindex_Type n,
			  int (*cmp) (void *, SLindex_Type, SLindex_Type))
{
   SLindex_Type i, j, k, kmax, n1, m;
   SLindex_Type *tmp;
   int try_quick_merge;

   if (n < 0)
     {
	SLang_verror (SL_INVALID_PARM, "_pSLmergesort: The number of elements must be non-negative");
	return -1;
     }

   for (i = 0; i < n; i++)
     sort_indices[i] = i;

   /* Insertion sort for 4 elements */
   n1 = n-1;
   m = 4; i = 0;
   while (i < n1)
     {
	kmax = i + m - 1;
	if (kmax >= n)
	  kmax = n1;
	for (k = i+1; k <= kmax; k++)
	  {
	     j = k;
	     while (j > i)
	       {
		  SLindex_Type t;
		  j--;
		  if ((*cmp)(obj, sort_indices[j], k) <= 0)
		    break;
		  t = sort_indices[j];
		  sort_indices[j] = sort_indices[j+1];
		  sort_indices[j+1] = t;
	       }
	  }
	i += m;
     }

   if (m >= n)
     return 0;

   /* Note that 1073741824*2 < 0 */
   i = (n <= 65536) ? m : 65536;
   while ((i*2 < n) && (i*2 > 0))
     i *= 2;

   if (NULL == (tmp = (SLindex_Type *)_SLcalloc (i, sizeof(SLindex_Type))))
     return -1;

   try_quick_merge = 0;
   /* Now do a bottom-up merge sort */
   while ((m < n) && (m > 0))
     {
	i = 0;
	while (i < n-m)
	  {
	     SLindex_Type imax, jmax, l, e_j, e_k;
	     SLindex_Type *sort_indices_i;

	     sort_indices_i = sort_indices + i;
	     k = m;
	     e_k = sort_indices_i[k];
	     if (try_quick_merge)
	       {
		  if ((*cmp)(obj, sort_indices_i[k-1], e_k) <= 0)
		    goto next_i;
	       }

	     j = 0; jmax = m;
	     kmax = k+m;
	     imax = i + kmax;

	     if (imax > n)
	       {
		  imax = n;
		  kmax = imax - i;
	       }

	     /* Only need to copy the left group */
	     memcpy (tmp, sort_indices_i, jmax*sizeof(SLindex_Type));
	     e_j = tmp[j];

	     l = i;
	     while (1)
	       {
		  if ((*cmp)(obj, e_j, e_k) <= 0)
		    {
		       sort_indices[l] = e_j; l++; j++;
		       if (j == jmax)
			 break;
		       e_j = tmp[j];
		       continue;
		    }
		  sort_indices[l] = e_k; l++; k++;
		  if (k == kmax)
		    {
		       memcpy (sort_indices+l, tmp+j, (imax-l)*sizeof(SLindex_Type));
		       break;
		    }
		  e_k = sort_indices_i[k];
	       }
	     try_quick_merge = (k == m);
next_i:
	     i = i + 2*m;
	  }
	m = m * 2;
     }

   SLfree ((char *)tmp);
   return 0;
}

/* Sorting Functions */

typedef struct
{
   SLang_Name_Type *func;
   SLang_Object_Type obj;
   int dir;			       /* +1=ascend, -1=descend */
}
Sort_Object_Type;
static void *QSort_Obj = NULL;

/* This is for 1-d matrices only.  It is used by the sort function */
static int push_element_at_index (SLang_Array_Type *at, SLindex_Type indx)
{
   VOID_STAR data;

   if (NULL == (data = get_data_addr (at, &indx)))
     return -1;

   return push_element_at_addr (at, (VOID_STAR) data, 1);
}

#if SLANG_OPTIMIZE_FOR_SPEED
#define MS_SCALAR_CMP(func, type) \
   static int func(void *obj, SLindex_Type i, SLindex_Type j) \
   { \
      type *data = (type *)obj; \
      if (data[i] > data[j]) \
	return 1; \
      if (data[i] < data[j]) \
	return -1; \
      if (i > j) return 1; \
      if (i < j) return -1; \
      return 0; \
   }

#define MS_SCALAR_CMP_DOWN(func, type) \
   static int func(void *obj, SLindex_Type i, SLindex_Type j) \
   { \
      type *data = (type *)obj; \
      if (data[i] > data[j]) \
	return -1; \
      if (data[i] < data[j]) \
	return 1; \
      if (i > j) return 1; \
      if (i < j) return -1; \
      return 0; \
   }

#define QS_SCALAR_CMP(func, type) \
   static int func(const void *ip, const void *jp) \
   { \
      SLindex_Type i = *(SLindex_Type *)ip, j = *(SLindex_Type *)jp; \
      type *data = (type *)QSort_Obj; \
      if (data[i] > data[j]) \
	return 1; \
      if (data[i] < data[j]) \
	return -1; \
      if (i > j) return 1; \
      if (i < j) return -1; \
      return 0; \
   }

#define QS_SCALAR_CMP_DOWN(func, type) \
   static int func(const void *ip, const void *jp) \
   { \
      SLindex_Type i = *(SLindex_Type *)ip, j = *(SLindex_Type *)jp; \
      type *data = (type *)QSort_Obj; \
      if (data[i] > data[j]) \
	return -1; \
      if (data[i] < data[j]) \
	return 1; \
      if (i > j) return 1; \
      if (i < j) return -1; \
      return 0; \
   }

#if SLANG_HAS_FLOAT
MS_SCALAR_CMP(ms_double_sort_cmp, double)
MS_SCALAR_CMP_DOWN(ms_double_sort_down_cmp, double)
MS_SCALAR_CMP(ms_float_sort_cmp, float)
MS_SCALAR_CMP_DOWN(ms_float_sort_down_cmp, float)

QS_SCALAR_CMP(qs_double_sort_cmp, double)
QS_SCALAR_CMP_DOWN(qs_double_sort_down_cmp, double)
QS_SCALAR_CMP(qs_float_sort_cmp, float)
QS_SCALAR_CMP_DOWN(qs_float_sort_down_cmp, float)
#endif

MS_SCALAR_CMP(ms_int_sort_cmp, int)
MS_SCALAR_CMP_DOWN(ms_int_sort_down_cmp, int)
QS_SCALAR_CMP(qs_int_sort_cmp, int)
QS_SCALAR_CMP_DOWN(qs_int_sort_down_cmp, int)

#endif				       /* SLANG_OPTIMIZE_FOR_SPEED */

static int ms_sort_cmp_fun (void *vobj, SLindex_Type i, SLindex_Type j)
{
   int cmp;
   SLang_Array_Type *at;
   Sort_Object_Type *sort_obj = (Sort_Object_Type *)vobj;

   at = sort_obj->obj.v.array_val;

   if (SLang_get_error ()
       || (-1 == push_element_at_index (at, i))
       || (-1 == push_element_at_index (at, j))
       || (-1 == SLexecute_function (sort_obj->func))
       || (-1 == SLang_pop_integer (&cmp)))
     {
	/* error: return something meaningful */
	if (i > j) return 1;
	if (i < j) return -1;
	return 0;
     }
   if (cmp == 0)
     {
	if (i > j) return 1;
	if (i < j) return -1;
	return 0;
     }
   return cmp*sort_obj->dir;
}

static int ms_sort_opaque_cmp_fun (void *vobj, SLindex_Type i, SLindex_Type j)
{
   int cmp;
   Sort_Object_Type *sort_obj = (Sort_Object_Type *)vobj;

   if (SLang_get_error ()
       || (-1 == _pSLpush_slang_obj (&sort_obj->obj))
       || (-1 == SLang_push_array_index (i))
       || (-1 == SLang_push_array_index (j))
       || (-1 == SLexecute_function (sort_obj->func))
       || (-1 == SLang_pop_integer (&cmp)))
     {
	/* error: return something meaninful */
	if (i > j) return 1;
	if (i < j) return -1;
	return 0;
     }
   if (cmp == 0)
     {
	if (i > j) return 1;
	if (i < j) return -1;
	return 0;
     }
   return sort_obj->dir*cmp;
}

static int ms_builtin_sort_cmp_fun (void *vobj, SLindex_Type i, SLindex_Type j)
{
   VOID_STAR a_data;
   VOID_STAR b_data;
   Sort_Object_Type *sort_obj = (Sort_Object_Type *)vobj;
   SLang_Array_Type *at;
   SLang_Class_Type *cl;

   at = sort_obj->obj.v.array_val;
   cl = at->cl;

   if ((SLang_get_error () == 0)
       && (NULL != (a_data = get_data_addr (at, &i)))
       && (NULL != (b_data = get_data_addr (at, &j))))
     {
	int cmp;

	if ((at->flags & SLARR_DATA_VALUE_IS_POINTER)
	    && ((*(VOID_STAR *) a_data == NULL) || (*(VOID_STAR *) b_data == NULL)))
	  {
	     _pSLang_verror (SL_VARIABLE_UNINITIALIZED,
			   "%s array has uninitialized element", cl->cl_name);
	  }
	else if (0 == (*cl->cl_cmp)(at->data_type, a_data, b_data, &cmp))
	  {
	     if (cmp == 0)
	       {
		  if (i > j) return 1;
		  if (i < j) return -1;
		  return 0;
	       }
	     return cmp * sort_obj->dir;
	  }
     }

   if (i > j) return 1;
   if (i < j) return -1;
   return 0;
}

static void ms_sort_array_internal (void *vobj, SLindex_Type n,
				    int (*sort_cmp)(void *, SLindex_Type, SLindex_Type))
{
   SLang_Array_Type *ind_at;
   SLindex_Type *indx;

   if (NULL == (ind_at = SLang_create_array1 (SLANG_ARRAY_INDEX_TYPE, 0, NULL, &n, 1, 1)))
     return;

   indx = (SLindex_Type *) ind_at->data;
   if (-1 == _pSLmergesort (vobj, indx, n, sort_cmp))
     {
	free_array (ind_at);
	return;
     }

   (void) SLang_push_array (ind_at, 1);
}

static int qs_builtin_sort_cmp_fun (const void *ip, const void *jp)
{
   return ms_builtin_sort_cmp_fun (QSort_Obj, *(SLindex_Type *)ip, *(SLindex_Type *)jp);
}
static int qs_sort_opaque_cmp_fun (const void *ip, const void *jp)
{
   return ms_sort_opaque_cmp_fun (QSort_Obj, *(SLindex_Type *)ip, *(SLindex_Type *)jp);
}
static int qs_sort_cmp_fun (const void *ip, const void *jp)
{
   return ms_sort_cmp_fun (QSort_Obj, *(SLindex_Type *)ip, *(SLindex_Type *)jp);
}

static void qs_sort_array_internal (void *vobj, SLindex_Type n,
				    int (*sort_cmp)(const void *, const void *))
{
   SLang_Array_Type *ind_at;
   SLindex_Type *indx;
   SLindex_Type i;
   void *save_vobj;

   if (NULL == (ind_at = SLang_create_array1 (SLANG_ARRAY_INDEX_TYPE, 0, NULL, &n, 1, 1)))
     return;

   indx = (SLindex_Type *) ind_at->data;
   for (i = 0; i < n; i++)
     indx[i] = i;

   save_vobj = QSort_Obj;
   QSort_Obj = vobj;
   qsort ((void *)indx, n, sizeof (SLindex_Type), sort_cmp);
   QSort_Obj = save_vobj;

   (void) SLang_push_array (ind_at, 1);
}

static int pop_1d_array (SLang_Array_Type **atp)
{
   SLang_Array_Type *at;

   if (-1 == SLang_pop_array (&at, 1))
     return -1;

   if (at->num_dims != 1)
     {
	_pSLang_verror (SL_INVALID_PARM, "sort is restricted to 1 dim arrays");
	free_array (at);
	return -1;
     }
   *atp = at;
   return 0;
}

#define SORT_METHOD_MSORT	0
#define SORT_METHOD_QSORT	1
static int Default_Sort_Method = SORT_METHOD_MSORT;
static void get_default_sort_method (void)
{
   char SLFUTURE_CONST *method = NULL;
   switch (Default_Sort_Method)
     {
      case SORT_METHOD_QSORT: method = "qsort"; break;
      case SORT_METHOD_MSORT: method = "msort"; break;
     }
   (void) SLang_push_string (method);
}
static void set_default_sort_method (char *method)
{
   if (0 == strcmp (method, "qsort"))
     {
	Default_Sort_Method = SORT_METHOD_QSORT;
	return;
     }
   Default_Sort_Method = SORT_METHOD_MSORT;
}

/* Usage Forms:
 *    i = sort (a);
 *    i = sort (a, &fun);       % sort using function fun(a[i],a[j])
 *    i = sort (a, &fun, n);    % sort using fun(a, i, j); 0 <= i,j < n
 */
static void array_sort_intrin (void)
{
   SLang_Array_Type *at;
   Sort_Object_Type sort_obj;
   void *vobj;
   SLindex_Type n;
   int nargs = SLang_Num_Function_Args;
   int dir = 1;
   int use_qsort = 0;
   char *method;

   if (-1 == SLang_get_int_qualifier ("dir", &dir, 1))
     return;
   dir = (dir >= 0) ? 1 : -1;
   use_qsort = (Default_Sort_Method == SORT_METHOD_QSORT);
   if (SLang_qualifier_exists ("qsort")) use_qsort = 1;
   if (-1 == SLang_get_string_qualifier ("method", &method, NULL))
     return;
   if (method != NULL)
     {
	if (0 == strcmp(method, "qsort"))
	  use_qsort = 1;
	SLang_free_slstring (method);
     }

   if (nargs == 1)		       /* i = sort (a) */
     {
	int (*msort_fun)(void *, SLindex_Type, SLindex_Type);
	int (*qsort_fun)(const void *, const void *);

	if (-1 == pop_1d_array (&at))
	  return;

	switch (at->data_type)
	  {
#if SLANG_OPTIMIZE_FOR_SPEED
# if SLANG_HAS_FLOAT
	   case SLANG_DOUBLE_TYPE:
	     msort_fun = (dir > 0) ? ms_double_sort_cmp : ms_double_sort_down_cmp;
	     qsort_fun = (dir > 0) ? qs_double_sort_cmp : qs_double_sort_down_cmp;
	     vobj = at->data;
	     break;
	   case SLANG_FLOAT_TYPE:
	     msort_fun = (dir > 0) ? ms_float_sort_cmp : ms_float_sort_down_cmp;
	     qsort_fun = (dir > 0) ? qs_float_sort_cmp : qs_float_sort_down_cmp;
	     vobj = at->data;
	     break;
# endif
	   case SLANG_INT_TYPE:
	     msort_fun = (dir > 0) ? ms_int_sort_cmp : ms_int_sort_down_cmp;
	     qsort_fun = (dir > 0) ? qs_int_sort_cmp : qs_int_sort_down_cmp;
	     vobj = at->data;
	     break;
#endif
	   default:
	     if (at->cl->cl_cmp == NULL)
	       {
		  _pSLang_verror (SL_NOT_IMPLEMENTED,
				  "%s does not have a predefined sorting method",
				  at->cl->cl_name);
		  free_array (at);
		  return;
	       }
	     msort_fun = ms_builtin_sort_cmp_fun;
	     qsort_fun = qs_builtin_sort_cmp_fun;
	     sort_obj.obj.o_data_type = SLANG_ARRAY_TYPE;
	     sort_obj.obj.v.array_val = at;
	     sort_obj.dir = dir;
	     vobj = (void *)&sort_obj;
	  }

	n = (SLindex_Type) at->num_elements;
	if (use_qsort)
	  qs_sort_array_internal (vobj, n, qsort_fun);
	else
	  ms_sort_array_internal (vobj, n, msort_fun);
	free_array (at);
	return;
     }

   if (nargs == 2)		       /* i = sort (a, &fun) */
     {
	SLang_Name_Type *entry;

	if (NULL == (entry = SLang_pop_function ()))
	  return;

	if (-1 == pop_1d_array (&at))
	  {
	     SLang_free_function (entry);
	     return;
	  }

	sort_obj.func = entry;
	sort_obj.obj.o_data_type = SLANG_ARRAY_TYPE;
	sort_obj.obj.v.array_val = at;
	sort_obj.dir = dir;
	vobj = (void *)&sort_obj;

	n = (SLindex_Type) at->num_elements;
	if (use_qsort)
	  qs_sort_array_internal (vobj, n, qs_sort_cmp_fun);
	else
	  ms_sort_array_internal (vobj, n, ms_sort_cmp_fun);
	free_array (at);
	SLang_free_function (entry);
	return;
     }

   if (nargs == 3)		       /* i = sort (a, fun, n) */
     {
	SLang_Name_Type *entry;

	if (-1 == SLang_pop_array_index (&n))
	  return;

	if (n < 0)
	  {
	     SLang_verror (SL_Index_Error, "Sort object cannot have a negative size");
	     return;
	  }

	if (NULL == (entry = SLang_pop_function ()))
	  return;

	if (-1 == SLang_pop (&sort_obj.obj))
	  {
	     SLang_free_function (entry);
	     return;
	  }
	sort_obj.func = entry;
	sort_obj.dir = dir;
	vobj = (void *)&sort_obj;

	if (use_qsort)
	  qs_sort_array_internal (vobj, n, qs_sort_opaque_cmp_fun);
	else
	  ms_sort_array_internal (vobj, n, ms_sort_opaque_cmp_fun);
	SLang_free_object (&sort_obj.obj);
	SLang_free_function (entry);
	return;
     }

   SLang_verror (SL_Usage_Error, "\
Usage: i = array_sort(a);\n\
       i = array_sort(a, &func);        %% cmp = func(a[i], b[j]);\n\
       i = array_sort(obj, &func, n);   %% cmp = func(obj, i, j)\n");
}

static void bstring_to_array (SLang_BString_Type *bs)
{
   unsigned char *s;
   SLstrlen_Type len;

   if (NULL == (s = SLbstring_get_pointer (bs, &len)))
     (void) SLang_push_null ();
   else
     (void) push_string_as_array (s, len, 0);
}

static void array_to_bstring (SLang_Array_Type *at)
{
   size_t nbytes;
   SLang_BString_Type *bs;

   nbytes = at->num_elements * at->sizeof_type;
   bs = SLbstring_create ((unsigned char *)at->data, nbytes);
   (void) SLang_push_bstring (bs);
   SLbstring_free (bs);
}

static void init_char_array (void)
{
   SLang_Array_Type *at;
   char *s;
   SLstrlen_Type n, ndim;

   if (SLang_pop_slstring (&s)) return;

   if (-1 == SLang_pop_array (&at, 0))
     goto free_and_return;

   if ((at->data_type != SLANG_CHAR_TYPE) && (at->data_type != SLANG_UCHAR_TYPE))
     {
	_pSLang_verror (SL_TYPE_MISMATCH, "Operation requires a character array");
	goto free_and_return;
     }

   n = _pSLstring_bytelen (s);
   ndim = at->num_elements;
   if (n > ndim)
     {
	_pSLang_verror (SL_INVALID_PARM, "String too big to initialize array");
	goto free_and_return;
     }

   strncpy((char *) at->data, s, ndim);
   /* fall through */

   free_and_return:
   free_array (at);
   _pSLang_free_slstring (s);
}

static VOID_STAR range_get_data_addr (SLang_Array_Type *at, SLindex_Type *dims)
{
   static int value;
   SLarray_Range_Array_Type *r;
   SLindex_Type d;

   d = *dims;
   r = (SLarray_Range_Array_Type *)at->data;

   if (d < 0)
     d += at->dims[0];

   if ((SLuindex_Type)d >= at->num_elements)
     {
	SLang_set_error (SL_Index_Error);
	return NULL;
     }
   value = r->first_index + d * r->delta;
   return (VOID_STAR) &value;
}

static SLang_Array_Type
  *create_range_array (SLarray_Range_Array_Type *range, SLindex_Type num,
		       SLtype type, int (*to_linear_fun) (SLang_Array_Type *, SLarray_Range_Array_Type *, VOID_STAR))
{
   SLarray_Range_Array_Type *r;
   SLang_Array_Type *at;

   r = (SLarray_Range_Array_Type *) SLmalloc (sizeof (SLarray_Range_Array_Type));
   if (r == NULL)
     return NULL;
   memset((char *) r, 0, sizeof (SLarray_Range_Array_Type));

   if (NULL == (at = SLang_create_array (type, 0, (VOID_STAR) range, &num, 1)))
     {
	SLfree ((char *)r);
	return NULL;
     }
   r->first_index = range->first_index;
   r->last_index = range->last_index;
   r->delta = range->delta;
   r->has_first_index = range->has_first_index;
   r->has_last_index = range->has_last_index;
   r->to_linear_fun = to_linear_fun;
   at->data = (VOID_STAR) r;
   at->index_fun = range_get_data_addr;
   at->flags |= SLARR_DATA_VALUE_IS_RANGE;
   return at;
}

static int get_range_array_limits (SLindex_Type *first_indexp, SLindex_Type *last_indexp, SLindex_Type *deltap,
				   SLarray_Range_Array_Type *r, SLindex_Type *nump)
{
   SLindex_Type first_index, last_index, delta;
   SLindex_Type num;

   if (deltap == NULL) delta = 1;
   else delta = *deltap;

   if (delta == 0)
     {
	_pSLang_verror (SL_INVALID_PARM, "range-array increment must be non-zero");
	return -1;
     }

   r->has_first_index = (first_indexp != NULL);
   if (r->has_first_index)
     first_index = *first_indexp;
   else
     first_index = 0;

   r->has_last_index = (last_indexp != NULL);
   if (r->has_last_index)
     last_index = *last_indexp;
   else
     last_index = -1;

   num = 0;
   if (delta > 0)
     {
	/* Case: [20:10:11] --> 0 elements, [10:20:11] --> 1 element */
	if (last_index >= first_index)
	  num = 1 + (last_index - first_index) / delta;
     }
   else
     {
	/* Case: [20:10:-11] -> 1 element, [20:30:-11] -> none */
	if (last_index <= first_index)
	  num = 1 + (last_index - first_index) / delta;
     }

   r->first_index = first_index;
   r->last_index = last_index;
   r->delta = delta;
   *nump = num;

   return 0;
}

static int index_range_to_linear (SLang_Array_Type *at, SLarray_Range_Array_Type *range, VOID_STAR buf)
{
   SLindex_Type *data = (SLindex_Type *)buf;
   SLuindex_Type i, imax;
   SLindex_Type xmin, dx;

   imax = at->num_elements;
   xmin = range->first_index;
   dx = range->delta;
   for (i = 0; i < imax; i++)
     {
	data [i] = xmin;
	xmin += dx;
     }
   return 0;
}

static SLang_Array_Type *inline_implicit_index_array (SLindex_Type *xminptr, SLindex_Type *xmaxptr, SLindex_Type *dxptr)
{
   SLarray_Range_Array_Type r;
   SLindex_Type num;

   if (-1 == get_range_array_limits (xminptr, xmaxptr, dxptr, &r, &num))
     return NULL;

   return create_range_array (&r, num, SLANG_ARRAY_INDEX_TYPE, index_range_to_linear);
}

#if (SLANG_ARRAY_INDEX_TYPE == SLANG_INT_TYPE)
# define int_range_to_linear index_range_to_linear
# define inline_implicit_int_array inline_implicit_index_array
#else
static int int_range_to_linear (SLang_Array_Type *at, SLarray_Range_Array_Type *range, VOID_STAR buf)
{
   int *data = (int *)buf;
   unsigned int i, imax;
   int xmin, dx;

   imax = (unsigned int)at->num_elements;
   xmin = (int) range->first_index;
   dx = (int) range->delta;
   for (i = 0; i < imax; i++)
     {
	data [i] = xmin;
	xmin += dx;
     }
   return 0;
}

static SLang_Array_Type *inline_implicit_int_array (SLindex_Type *xminptr, SLindex_Type *xmaxptr, SLindex_Type *dxptr)
{
   SLarray_Range_Array_Type r;
   SLindex_Type num;

   if (-1 == get_range_array_limits (xminptr, xmaxptr, dxptr, &r, &num))
     return NULL;

   return create_range_array (&r, num, SLANG_INT_TYPE, int_range_to_linear);
}
#endif

#if SLANG_HAS_FLOAT
static double compute_range_multiplier (double xmin, double xmax, double dx)
{
   double multiplier = 1.0;

   (void) xmax;
   while (multiplier < 1e9)
     {
	volatile double xmin1, dx1;    /* avoid agressive compiler optimization */

	xmin1 = multiplier * xmin;
	dx1 = multiplier * dx;

	if ((xmin1 == (int)xmin1) && (dx1 == (int)dx1))
	  return multiplier;
	multiplier *= 10.0;
     }
   return 1.0;
}


static SLang_Array_Type *inline_implicit_floating_array (SLtype type,
							 double *xminptr, double *xmaxptr, double *dxptr,
							 int ntype, SLindex_Type nels)
{
   SLindex_Type n, i;
   SLang_Array_Type *at;
   SLindex_Type dims;
   double xmin, xmax, dx;
   double multiplier = 1.0;

   if ((xminptr == NULL) || (xmaxptr == NULL))
     {
	_pSLang_verror (SL_INVALID_PARM, "range-array has unknown size");
	return NULL;
     }
   xmin = *xminptr;
   xmax = *xmaxptr;

   if (ntype)
     {
	/* [a:b:#n] == a + [0:(n-1)]*(b-a)/(n-1)
	 * ==> dx = (b-a)/(n-1)
	 */
	n = nels;
	if (n <= 0)
	  {
	     n = 0;
	     dx = 1.0;
	  }
	else
	  {
	     if (n == 1)
	       dx = 0.0;
	     else
	       dx = (xmax-xmin)/(n-1);
	  }
     }
   else
     {
	if (dxptr == NULL) dx = 1.0;
	else dx = *dxptr;

	if (dx == 0.0)
	  {
	     _pSLang_verror (SL_INVALID_PARM, "range-array increment must be non-zero");
	     return NULL;
	  }

	/* I have convinced myself that it is better to use semi-open intervals
	 * because of less ambiguities.  So, [a:b:c] will represent the set of
	 * values a, a + c, a + 2c ... a + nc
	 * such that a + nc < b.  That is, b lies outside the interval.
	 */

	if (((xmax <= xmin) && (dx >= 0.0))
	    || ((xmax >= xmin) && (dx <= 0.0)))
	  n = 0;
	else
	  {
	     double last;

	     if ((xmin + dx == (volatile double)xmin) || (xmax + dx == (volatile double)xmax))
	       n = 0;
	     else
	     /* Allow for roundoff by adding 0.5 before truncation */
	       n = (int)(1.5 + ((xmax - xmin) / dx));

	     if (n <= 0)
	       {
		  _pSLang_verror (SL_INVALID_PARM, "range-array increment is too small");
		  return NULL;
	       }

	     multiplier = compute_range_multiplier (xmin, xmax, dx);
	     last = ((xmin*multiplier) + (n-1) * (dx*multiplier))/multiplier;

	     if (dx > 0.0)
	       {
		  if (last >= xmax)
		    n -= 1;
	       }
	     else if (last <= xmax)
	       n -= 1;
	  }
     }

   dims = n;
   if (NULL == (at = SLang_create_array1 (type, 0, NULL, &dims, 1, 1)))
     return NULL;

   if (type == SLANG_DOUBLE_TYPE)
     {
	double *ptr;

	ptr = (double *) at->data;

	if (multiplier != 1.0)
	  {
	     int ixmin = (floor)(multiplier*xmin+0.5);
	     int idx = (floor)(multiplier*dx+0.5);
	     for (i = 0; i < n; i++)
	       ptr[i] = (ixmin + (double)i * idx)/multiplier;
	  }
	else for (i = 0; i < n; i++)
	  ptr[i] = (xmin + i*dx);

	/* Explicitly set the last element to xmax to avoid roundoff error */
	if (ntype && (n > 1))
	  ptr[n-1] = xmax;
     }
   else
     {
	float *ptr;

	ptr = (float *) at->data;

	for (i = 0; i < n; i++)
	  ptr[i] = (float) (xmin + i * dx);

	if (ntype && (n > 0))
	  ptr[n-1] = (float) xmax;
     }
   return at;
}
#endif

static int pop_range_int (SLindex_Type *ip)
{
   return SLang_pop_array_index (ip);
}

/* FIXME: Priority=medium
 * This needs to be updated to work with all integer types.
 * Adding support for other types is going to require a generalization
 * of the Range_Array_Type object.
 */
/* If ntype is non-zero, the array was specified using [a:b:#c] */
static int inline_implicit_array (int ntype)
{
   SLindex_Type index_vals[3];
#if SLANG_HAS_FLOAT
   double double_vals[3];
   int is_int;
#endif
   int has_vals[3];
   unsigned int i, count;
   SLindex_Type n = 0;
   SLang_Array_Type *at;
   int precedence;
   SLtype type;

   count = SLang_Num_Function_Args;

   if ((count == 2) && (ntype == 0))
     has_vals [2] = 0;
   else if (count != 3)
     {
	_pSLang_verror (SL_NUM_ARGS_ERROR, "wrong number of arguments to __implicit_inline_array");
	return -1;
     }

#if SLANG_HAS_FLOAT
   is_int = 1;
#endif

   type = 0;
   precedence = 0;

   if (ntype)
     {
	if (-1 == pop_range_int (&n))
	  return -1;
	has_vals[2] = 0;
	count--;
     }
   i = count;

   while (i--)
     {
	int this_type, this_precedence;
	SLindex_Type itmp;

	if (-1 == (this_type = SLang_peek_at_stack ()))
	  return -1;

	this_precedence = _pSLarith_get_precedence ((SLtype) this_type);
	if (precedence < this_precedence)
	  {
	     type = (SLtype) this_type;
	     precedence = this_precedence;
	  }

	has_vals [i] = 1;

	switch (this_type)
	  {
	   case SLANG_NULL_TYPE:
	     if (ntype)
	       {
		  _pSLang_verror (SL_Syntax_Error, "Arrays of the form [a:b:#c] must be fully specified");
		  return -1;
	       }
	     has_vals[i] = 0;
	     (void) SLdo_pop ();
	     break;

#if SLANG_HAS_FLOAT
	   case SLANG_DOUBLE_TYPE:
	   case SLANG_FLOAT_TYPE:
	     if (-1 == SLang_pop_double (double_vals + i))
	       return -1;
	     is_int = 0;
	     break;
#endif
	   default:
	     if (-1 == pop_range_int (&itmp))
	       return -1;
	     index_vals[i] = itmp;
#if SLANG_HAS_FLOAT
	     double_vals[i] = (double) itmp;
#endif
	  }
     }

#if SLANG_HAS_FLOAT
   if (ntype)
     {
	is_int = 0;
	if (type != SLANG_FLOAT_TYPE)
	  type = SLANG_DOUBLE_TYPE;
     }

   if (is_int == 0)
     at = inline_implicit_floating_array (type,
					  (has_vals[0] ? &double_vals[0] : NULL),
					  (has_vals[1] ? &double_vals[1] : NULL),
					  (has_vals[2] ? &double_vals[2] : NULL),
					  ntype, n);
   else
#endif
     at = inline_implicit_int_array ((has_vals[0] ? &index_vals[0] : NULL),
				     (has_vals[1] ? &index_vals[1] : NULL),
				     (has_vals[2] ? &index_vals[2] : NULL));

   if (at == NULL)
     return -1;

   return SLang_push_array (at, 1);
}

int _pSLarray_inline_implicit_array (void)
{
   return inline_implicit_array (0);
}

int _pSLarray_inline_implicit_arrayn (void)
{
   return inline_implicit_array (1);
}

static int try_typecast_range_array (SLang_Array_Type *at, SLtype to_type,
				     SLang_Array_Type **btp)
{
   SLang_Array_Type *bt;

   *btp = NULL;
   if (to_type == SLANG_ARRAY_INDEX_TYPE)
     {
	if (at->data_type == SLANG_INT_TYPE)
	  {
	     SLarray_Range_Array_Type *range;

	     range = (SLarray_Range_Array_Type *)at->data;
	     bt = create_range_array (range, at->num_elements,
				      to_type, index_range_to_linear);
	     if (bt == NULL)
	       return -1;
	     *btp = bt;
	     return 1;
	  }
     }
   return 0;
}

int _pSLarray_wildcard_array (void)
{
   SLang_Array_Type *at;

   if (NULL == (at = inline_implicit_int_array (NULL, NULL, NULL)))
     return -1;

   return SLang_push_array (at, 1);
}

/* FIXME: The type-promotion routine needs to be made more generic and
 * better support user-defined types.
 */

/* Test if the type cannot be promoted further */
_INLINE_ static int nowhere_to_promote (SLtype type)
{
   switch (type)
     {
      case SLANG_COMPLEX_TYPE:
      case SLANG_BSTRING_TYPE:
      case SLANG_ARRAY_TYPE:
	return 1;
     }

   return 0;
}

static int promote_to_common_type (SLtype a, SLtype b, SLtype *c)
{
   if (a == b)
     {
	*c = a;
	return 0;
     }
   if (nowhere_to_promote (a))
     {
	/* a type can always be converted to an array: T -> [T] */
	if (b == SLANG_ARRAY_TYPE)
	  *c = b;
	else
	  *c = a;
	return 0;
     }
   if (nowhere_to_promote (b))
     {
	*c = b;
	return 0;
     }

   if (_pSLang_is_arith_type (a) && _pSLang_is_arith_type (b))
     {
	if (_pSLarith_get_precedence (a) > _pSLarith_get_precedence (b))
	  *c = a;
	else
	  *c = b;
	return 0;
     }

   if (a == SLANG_NULL_TYPE)
     {
	*c = b;
	return 0;
     }
   if (b == SLANG_NULL_TYPE)
     {
	*c = a;
	return 0;
     }

   *c = a;
   return 0;
}

static SLtype get_type_for_concat (SLang_Array_Type **arrays, unsigned int n)
{
   SLtype type;
   unsigned int i;

   type = arrays[0]->data_type;

   for (i = 1; i < n; i++)
     {
	SLtype this_type = arrays[i]->data_type;

	if (this_type == type)
	  continue;

	if (-1 == promote_to_common_type (type, this_type, &type))
	  return SLANG_UNDEFINED_TYPE;
     }
   return type;
}

static SLang_Array_Type *concat_arrays (unsigned int count)
{
   SLang_Array_Type **arrays;
   SLang_Array_Type *at, *bt;
   unsigned int i;
   SLindex_Type num_elements;
   SLtype type;
   char *src_data, *dest_data;
   int is_ptr;
   size_t sizeof_type;
   SLindex_Type max_dims, min_dims, max_rows, min_rows;

   arrays = (SLang_Array_Type **)_SLcalloc (count, sizeof (SLang_Array_Type *));
   if (arrays == NULL)
     {
	SLdo_pop_n (count);
	return NULL;
     }
   memset ((char *) arrays, 0, count * sizeof(SLang_Array_Type *));

   at = NULL;

   num_elements = 0;
   i = count;

   while (i != 0)
     {
	SLindex_Type last_num_elements = num_elements;

	i--;

	if (-1 == SLang_pop_array (&bt, 1))   /* bt is now linear */
	  goto free_and_return;

	arrays[i] = bt;
	num_elements += (SLindex_Type) bt->num_elements;
	if (num_elements < last_num_elements)
	  {
	     SLang_verror (SL_LimitExceeded_Error, "Unable to address concatenated arrays");
	     goto free_and_return;
	  }
     }

   /* From here on, arrays[*] are linear */

   /* type = arrays[0]->data_type; */
   type = get_type_for_concat (arrays, count);

   max_dims = min_dims = arrays[0]->num_dims;
   min_rows = max_rows = arrays[0]->dims[0];

   for (i = 0; i < count; i++)
     {
	SLang_Array_Type *ct;
	SLindex_Type num;

	bt = arrays[i];

	num = bt->num_dims;
	if (num > max_dims) max_dims = num;
	if (num < min_dims) min_dims = num;

	num = bt->dims[0];
	if (num > max_rows) max_rows = num;
	if (num < min_rows) min_rows = num;

	if (type == bt->data_type)
	  continue;

	if (1 != _pSLarray_typecast (bt->data_type, (VOID_STAR) &bt, 1,
				    type, (VOID_STAR) &ct, 1))
	  goto free_and_return;

	free_array (bt);
	arrays [i] = ct;
     }

   if (NULL == (at = SLang_create_array (type, 0, NULL, &num_elements, 1)))
     goto free_and_return;

   is_ptr = (at->flags & SLARR_DATA_VALUE_IS_POINTER);
   sizeof_type = at->sizeof_type;
   dest_data = (char *) at->data;

   for (i = 0; i < count; i++)
     {
	bt = arrays[i];

	src_data = (char *) bt->data;
	num_elements = bt->num_elements;

	if (-1 == transfer_n_elements (bt, (VOID_STAR)dest_data, (VOID_STAR)src_data, sizeof_type,
				       num_elements, is_ptr))
	  {
	     free_array (at);
	     at = NULL;
	     goto free_and_return;
	  }

	dest_data += num_elements * sizeof_type;
     }

free_and_return:

   for (i = 0; i < count; i++)
     free_array (arrays[i]);
   SLfree ((char *) arrays);

   return at;
}

int _pSLarray_inline_array (void)
{
   SLtype type;
   unsigned int count;
   SLang_Array_Type *at;

   count = SLang_Num_Function_Args;
   type = 0;

   while (count > 0)
     {
	int this_type;

	count--;
	if (-1 == (this_type = SLang_peek_at_stack_n (count)))
	  return -1;

	if (type == 0) type = (SLtype) this_type;
	else if (type != (SLtype) this_type)
	  {
	     if (-1 == promote_to_common_type (type, (SLtype) this_type, &type))
	       {
		  _pSLclass_type_mismatch_error (type, (SLtype) this_type);
		  return -1;
	       }
	  }
     }

   count = SLang_Num_Function_Args;

   if (count == 0)
     {
	_pSLang_verror (SL_NOT_IMPLEMENTED, "Empty inline-arrays not supported");
	return -1;
     }

   if (type == SLANG_ARRAY_TYPE)
     {
	if (count == 1)
	  return 0;		       /* no point in going on */

	if (NULL == (at = concat_arrays (count)))
	  return -1;
     }
   else
     {
	SLang_Object_Type index_obj;
	SLindex_Type icount = (SLindex_Type) count;

	if (NULL == (at = SLang_create_array (type, 0, NULL, &icount, 1)))
	  return -1;

	index_obj.o_data_type = SLANG_ARRAY_INDEX_TYPE;
	while (count != 0)
	  {
	     count--;
	     index_obj.v.index_val = count;
	     if (-1 == aput_from_index_objs (at, &index_obj, 1))
	       {
		  free_array (at);
		  SLdo_pop_n (count);
		  return -1;
	       }
	  }
     }

   return SLang_push_array (at, 1);
}

int _pSLarray_convert_to_array (VOID_STAR cd,
				int (*get_type)(VOID_STAR, SLuindex_Type, SLtype *),
				int (*push)(VOID_STAR, SLuindex_Type),
			        SLuindex_Type num_objects, SLtype type)
{
   SLtype this_type;
   SLang_Array_Type *at;
   SLuindex_Type i;
   SLindex_Type dims;
   SLang_Object_Type index_obj;

   at = NULL;

   if (type == 0) for (i = 0; i < num_objects; i++)
     {
	if (-1 == (*get_type)(cd, i, &this_type))
	  goto unknown_error;

	if (type == 0)
	  type = this_type;
	else if (type != this_type)
	  {
	     if (-1 == promote_to_common_type (type, this_type, &type))
	       {
		  _pSLclass_type_mismatch_error (type, this_type);
		  return -1;
	       }
	  }
     }

   if (type == 0)
     {
	SLang_verror (SL_TypeMismatch_Error, "Cannot convert an empty container object to an untyped array");
	return -1;
     }

   dims = (SLindex_Type) num_objects;

   if (NULL == (at = SLang_create_array (type, 0, NULL, &dims, 1)))
     return -1;

   index_obj.o_data_type = SLANG_ARRAY_INDEX_TYPE;
   for (i = 0; i < num_objects; i++)
     {
	if (-1 == (*push)(cd, i))
	  goto unknown_error;

	index_obj.v.index_val = i;
	if (-1 == aput_from_index_objs (at, &index_obj, 1))
	  goto return_error;
     }

   return SLang_push_array (at, 1);

unknown_error:
   SLang_verror (SL_Unknown_Error, "Unknown array conversion error");
return_error:
   if (at != NULL)
     free_array (at);

   return -1;
}

static int array_binary_op_result (int op, SLtype a, SLtype b,
				   SLtype *c)
{
   (void) op;
   (void) a;
   (void) b;
   *c = SLANG_ARRAY_TYPE;
   return 1;
}

static int try_range_int_binary (SLang_Array_Type *at, int op, int x, int swap, VOID_STAR cp)
{
   SLarray_Range_Array_Type *at_r;
   SLarray_Range_Array_Type rbuf;
   SLindex_Type first_index, last_index, delta;
   SLindex_Type num;

   at_r = (SLarray_Range_Array_Type *)at->data;
   if ((at_r->has_first_index == 0)
       || (at_r->has_last_index == 0))
     return 0;

   switch (op)
     {
      case SLANG_MINUS:
	if (swap)
	  {
	     first_index = x - at_r->first_index;
	     last_index = x - at_r->last_index;
	     delta = -at_r->delta;
	     break;
	  }
	x = -x;
	/* fall through */
      case SLANG_PLUS:
	first_index = at_r->first_index + x;
	last_index = at_r->last_index + x;
	delta = at_r->delta;
	break;

      case SLANG_TIMES:
	if (x == 0)
	  return 0;
	first_index = at_r->first_index*x;
	last_index = at_r->last_index*x;
	delta = at_r->delta*x;
	break;

      default:
	return 0;
     }

   if (-1 == get_range_array_limits (&first_index, &last_index, &delta, &rbuf, &num))
     return -1;
   if ((SLuindex_Type)num != at->num_elements)
     return 0; /* This can happen if the integer arithmetic wrapped */

   if (NULL == (at = create_range_array (&rbuf, num, SLANG_INT_TYPE, int_range_to_linear)))
     return -1;

   *(SLang_Array_Type **)cp = at;
   return 1;
}

static int array_binary_op (int op,
			    SLtype a_type, VOID_STAR ap, SLuindex_Type na,
			    SLtype b_type, VOID_STAR bp, SLuindex_Type nb,
			    VOID_STAR cp)
{
   SLang_Array_Type *at, *bt, *ct;
   int (*binary_fun) (int,
		      SLtype, VOID_STAR, SLuindex_Type,
		      SLtype, VOID_STAR, SLuindex_Type,
		      VOID_STAR);
   SLang_Class_Type *a_cl, *b_cl, *c_cl;
   int ret;

   if (a_type == SLANG_ARRAY_TYPE)
     {
	if (na != 1)
	  {
	     _pSLang_verror (SL_NOT_IMPLEMENTED, "Binary operation on multiple arrays not implemented");
	     return -1;
	  }

	at = *(SLang_Array_Type **) ap;
	if ((b_type == SLANG_INT_TYPE)
	    && (nb == 1)
	    && (at->flags & SLARR_DATA_VALUE_IS_RANGE)
	    && (at->data_type == b_type))
	  {
	     int status = try_range_int_binary (at, op, *(int *)bp, 0, cp);
	     if (status)
	       return status;
	     /* fall through */
	  }

	if (-1 == coerse_array_to_linear (at))
	  return -1;
	ap = at->data;
	a_type = at->data_type;
	na = at->num_elements;
     }
   else
     {
	at = NULL;
     }

   if (b_type == SLANG_ARRAY_TYPE)
     {
	if (nb != 1)
	  {
	     _pSLang_verror (SL_NOT_IMPLEMENTED, "Binary operation on multiple arrays not implemented");
	     return -1;
	  }

	bt = *(SLang_Array_Type **) bp;

	if ((a_type == SLANG_INT_TYPE)
	    && (na == 1)
	    && (bt->flags & SLARR_DATA_VALUE_IS_RANGE)
	    && (bt->data_type == a_type))
	  {
	     int status = try_range_int_binary (bt, op, *(int *)ap, 1, cp);
	     if (status)
	       return status;
	     /* fall through */
	  }

	if (-1 == coerse_array_to_linear (bt))
	  return -1;
	bp = bt->data;
	b_type = bt->data_type;
	nb = bt->num_elements;
     }
   else
     {
	bt = NULL;
     }

   if ((at != NULL) && (bt != NULL))
     {
	SLuindex_Type i, num_dims;

	num_dims = at->num_dims;

	if (num_dims != bt->num_dims)
	  {
	     _pSLang_verror (SL_TYPE_MISMATCH, "Arrays must have same dimensions for binary operation");
	     return -1;
	  }

	for (i = 0; i < num_dims; i++)
	  {
	     if (at->dims[i] != bt->dims[i])
	       {
		  _pSLang_verror (SL_TYPE_MISMATCH, "Arrays must be the same for binary operation");
		  return -1;
	       }
	  }
     }

   a_cl = _pSLclass_get_class (a_type);
   if (a_type == b_type)
     b_cl = a_cl;
   else
     b_cl = _pSLclass_get_class (b_type);

   if (NULL == (binary_fun = _pSLclass_get_binary_fun (op, a_cl, b_cl, &c_cl, 1)))
     return -1;

   ct = NULL;

#if SLANG_USE_TMP_OPTIMIZATION
   /* If we are dealing with scalar (or vector) objects, and if the object
    * appears to be owned by the stack, then use it instead of creating a
    * new version.  This can happen with code such as:
    * @  x = [1,2,3,4];
    * @  x = __tmp(x) + 1;
    */
   if ((c_cl->cl_class_type == SLANG_CLASS_TYPE_SCALAR)
       || (c_cl->cl_class_type == SLANG_CLASS_TYPE_VECTOR))
     {
	if ((at != NULL)
	    && (at->num_refs == 1)
	    && (at->data_type == c_cl->cl_data_type)
	    && (0 == (at->flags & SLARR_DATA_VALUE_IS_READ_ONLY)))
	  {
	     ct = at;
	     ct->num_refs = 2;
	  }
	else if ((bt != NULL)
		 && (bt->num_refs == 1)
		 && (bt->data_type == c_cl->cl_data_type)
		 && (0 == (bt->flags & SLARR_DATA_VALUE_IS_READ_ONLY)))
	  {
	     ct = bt;
	     ct->num_refs = 2;
	  }
     }
#endif				       /* SLANG_USE_TMP_OPTIMIZATION */

   if (ct == NULL)
     {
	if (at != NULL) ct = at; else ct = bt;
	ct = SLang_create_array1 (c_cl->cl_data_type, 0, NULL, ct->dims, ct->num_dims, 1);
	if (ct == NULL)
	  return -1;
     }

   if ((na == 0) || (nb == 0))	       /* allow empty arrays */
     {
	*(SLang_Array_Type **) cp = ct;
	return 1;
     }

   if (a_cl->cl_inc_ref != NULL)(*a_cl->cl_inc_ref)(a_type, ap, 1);
   if (b_cl->cl_inc_ref != NULL)(*b_cl->cl_inc_ref)(b_type, bp, 1);
   ret = (*binary_fun) (op, a_type, ap, na, b_type, bp, nb, ct->data);
   if (a_cl->cl_inc_ref != NULL)(*a_cl->cl_inc_ref)(a_type, ap, -1);
   if (b_cl->cl_inc_ref != NULL)(*b_cl->cl_inc_ref)(b_type, bp, -1);

   if (ret == 1)
     {
	*(SLang_Array_Type **) cp = ct;
	return 1;
     }

   free_array (ct);
   return -1;
}

#if SLANG_OPTIMIZE_FOR_SPEED
int _pSLarray_bin_op (SLang_Object_Type *a, SLang_Object_Type *b, int op)
{
   SLang_Array_Type *c;

   if (-1 == array_binary_op (op, SLANG_ARRAY_TYPE, (VOID_STAR) &a->v, 1,
			      SLANG_ARRAY_TYPE, (VOID_STAR) &b->v, 1,
			      (VOID_STAR) &c))
     return -1;

   return _pSLang_push_array (c, 1);
}
#endif

static int array_eqs_method (SLtype a_type, VOID_STAR ap, SLtype b_type, VOID_STAR bp)
{
   SLang_Array_Type *at, *bt, *ct;
   SLuindex_Type i, num_dims, num_elements;
   SLang_Class_Type *a_cl, *b_cl, *c_cl;
   int is_eqs;
   int *ip, *ipmax;

   if ((a_type != SLANG_ARRAY_TYPE) || (b_type != SLANG_ARRAY_TYPE))
     return 0;

   at = *(SLang_Array_Type **) ap;
   bt = *(SLang_Array_Type **) bp;

   if (at == bt)
     return 1;

   if ((at->num_elements != (num_elements = bt->num_elements))
       || (at->num_dims != (num_dims = bt->num_dims)))
     return 0;

   for (i = 0; i < num_dims; i++)
     {
	if (at->dims[i] != bt->dims[i])
	  return 0;
     }

   a_type = at->data_type;
   b_type = bt->data_type;

   /* Check for an array of arrays.  If so, the arrays must reference the same set arrays */
   if ((a_type == SLANG_ARRAY_TYPE) || (b_type == SLANG_ARRAY_TYPE))
     {
	if (a_type != b_type)
	  return 0;

	return !memcmp ((char *)at->data, (char *)bt->data, num_elements*sizeof(SLang_Array_Type*));
     }

   a_cl = _pSLclass_get_class (a_type);
   if (a_type == b_type)
     b_cl = a_cl;
   else
     b_cl = _pSLclass_get_class (b_type);

   if ((a_cl == b_cl)
       && ((a_cl->cl_class_type == SLANG_CLASS_TYPE_SCALAR)
	   || (a_cl->cl_class_type == SLANG_CLASS_TYPE_VECTOR)))
     {
	if ((-1 == coerse_array_to_linear (at))
	    || (-1 == coerse_array_to_linear (bt)))
	  return -1;

	return !memcmp ((char *)at->data, (char *)bt->data, num_elements*at->sizeof_type);
     }

   /* Do it the hard way */

   if (NULL == _pSLclass_get_binary_fun (SLANG_EQ, a_cl, b_cl, &c_cl, 0))
     return 0;

   if (num_elements == 0)
     return 1;

   if (-1 == array_binary_op (SLANG_EQ, SLANG_ARRAY_TYPE, ap, 1, SLANG_ARRAY_TYPE, bp, 1,
			      (VOID_STAR) &ct))
     return -1;

   /* ct is linear */
   num_elements = ct->num_elements;
   is_eqs = 1;
   if ((ct->data_type == SLANG_CHAR_TYPE) || (ct->data_type == SLANG_UCHAR_TYPE))
     {
	unsigned char *p, *pmax;

	p = (unsigned char *)ct->data;
	pmax = p + num_elements;

	while (p < pmax)
	  {
	     if (*p == 0)
	       {
		  is_eqs = 0;
		  break;
	       }
	     p++;
	  }
	free_array (ct);
	return is_eqs;
     }

   if (ct->data_type != SLANG_INT_TYPE)
     {
	SLang_Array_Type *tmp;
	if (1 != _pSLarray_typecast (ct->data_type, (VOID_STAR) &ct, 1,
				    SLANG_INT_TYPE, (VOID_STAR) &tmp, 1))
	  {
	     free_array (ct);
	     return -1;
	  }
	free_array (ct);
	ct = tmp;
     }

   ip = (int *)ct->data;
   ipmax = ip + num_elements;

   while (ip < ipmax)
     {
	if (*ip == 0)
	  {
	     is_eqs = 0;
	     break;
	  }
	ip++;
     }
   free_array (ct);
   return is_eqs;
}

static void is_null_intrinsic_intern (SLang_Array_Type *at)
{
   SLang_Array_Type *bt;

   bt = SLang_create_array (SLANG_CHAR_TYPE, 0, NULL, at->dims, at->num_dims);
   if (bt == NULL)
     return;

   if (at->data_type == SLANG_NULL_TYPE)
     memset ((char *)bt->data, 1, bt->num_elements);
   else if (at->flags & SLARR_DATA_VALUE_IS_POINTER)
     {
	char *cdata, *cdata_max;
	char **data;

	if (-1 == coerse_array_to_linear (at))
	  {
	     free_array (bt);
	     return;
	  }

	cdata = (char *)bt->data;
	cdata_max = cdata + bt->num_elements;
	data = (char **)at->data;

	while (cdata < cdata_max)
	  {
	     if (*data == NULL)
	       *cdata = 1;

	     data++;
	     cdata++;
	  }
     }

   SLang_push_array (bt, 1);
}

static void is_null_intrinsic (void)
{
   char ret = 0;
   SLang_Array_Type *at;

   switch (SLang_peek_at_stack ())
     {
      case SLANG_ARRAY_TYPE:
	if (-1 == SLang_pop_array (&at, 0))
	  return;
	is_null_intrinsic_intern (at);
	free_array (at);
	break;

      case SLANG_NULL_TYPE:
	ret = 1;
	/* fall through */
      default:
	(void) SLdo_pop();
	(void) SLang_push_char (ret);
     }
}

static SLang_Array_Type *pop_bool_array (void)
{
   SLang_Array_Type *at;
   SLang_Array_Type *tmp_at;
   int zero;

   if (-1 == SLang_pop_array (&at, 1))
     return NULL;

   if (at->data_type == SLANG_CHAR_TYPE)
     return at;

   tmp_at = at;
   zero = 0;
   if (1 != array_binary_op (SLANG_NE,
			     SLANG_ARRAY_TYPE, (VOID_STAR) &at, 1,
			     SLANG_CHAR_TYPE, (VOID_STAR) &zero, 1,
			     (VOID_STAR) &tmp_at))
     {
	free_array (at);
	return NULL;
     }

   free_array (at);
   at = tmp_at;
   if (at->data_type != SLANG_CHAR_TYPE)
     {
	free_array (at);
	SLang_set_error (SL_TYPE_MISMATCH);
	return NULL;
     }
   return at;
}

static int pop_bool_array_and_start (int nargs, SLang_Array_Type **atp, SLindex_Type *sp)
{
   SLang_Array_Type *at;
   SLindex_Type istart = *sp;
   SLindex_Type num_elements;

   if (nargs == 2)
     {
	if (-1 == SLang_pop_array_index (&istart))
	  return -1;
     }

   if (NULL == (at = pop_bool_array ()))
     return -1;

   num_elements = (SLindex_Type) at->num_elements;

   if (istart < 0)
     istart += num_elements;

   if (istart < 0)
     {
	if (num_elements == 0)
	  istart = 0;
	else
	  {
	     SLang_set_error (SL_Index_Error);
	     free_array (at);
	     return -1;
	  }
     }

   *atp = at;
   *sp = istart;
   return 0;
}

/* Usage: i = wherefirst (at [,startpos]); */
static void array_where_first (void)
{
   SLang_Array_Type *at;
   char *a_data;
   SLindex_Type i, num_elements;
   SLindex_Type istart = 0;

   istart = 0;
   if (-1 == pop_bool_array_and_start (SLang_Num_Function_Args, &at, &istart))
     return;

   a_data = (char *) at->data;
   num_elements = (SLindex_Type) at->num_elements;

   for (i = istart; i < num_elements; i++)
     {
	if (a_data[i] == 0)
	  continue;

	(void) SLang_push_array_index (i);
	free_array (at);
	return;
     }
   free_array (at);
   SLang_push_null ();
}

/* Usage: i = wherelast (at [,startpos]); */
static void array_where_last (void)
{
   SLang_Array_Type *at;
   char *a_data;
   SLindex_Type i;
   SLindex_Type istart = 0;

   istart = -1;
   if (-1 == pop_bool_array_and_start (SLang_Num_Function_Args, &at, &istart))
     return;

   a_data = (char *) at->data;

   i = istart + 1;
   if (i > (SLindex_Type)at->num_elements)
     i = (SLindex_Type) at->num_elements;
   while (i > 0)
     {
	i--;
	if (a_data[i] == 0)
	  continue;

	(void) SLang_push_array_index (i);
	free_array (at);
	return;
     }
   free_array (at);
   SLang_push_null ();
}

static void array_where_intern (int cmp)
{
   SLang_Array_Type *at, *bt;
   char *a_data;
   SLindex_Type *b_data;
   SLuindex_Type i, num_elements;
   SLindex_Type b_num;
   SLang_Ref_Type *ref = NULL;

   if (SLang_Num_Function_Args == 2)
     {
	if (-1 == SLang_pop_ref (&ref))
	  return;
     }

   if (NULL == (at = pop_bool_array ()))
     return;

   a_data = (char *) at->data;
   num_elements = at->num_elements;

   b_num = 0;
   for (i = 0; i < num_elements; i++)
     if (cmp == (a_data[i] != 0)) b_num++;

   if (NULL == (bt = SLang_create_array1 (SLANG_ARRAY_INDEX_TYPE, 0, NULL, &b_num, 1, 1)))
     goto return_error;

   b_data = (SLindex_Type *) bt->data;

   if (ref != NULL)
     {
	SLindex_Type *c_data;
	SLindex_Type c_num;
	SLang_Array_Type *ct;

	c_num = num_elements - b_num;
	if (NULL == (ct = SLang_create_array1 (SLANG_ARRAY_INDEX_TYPE, 0, NULL, &c_num, 1, 1)))
	  goto return_error;
	c_data = (SLindex_Type *) ct->data;

	for (i = 0; i < num_elements; i++)
	  {
	     if (cmp == (a_data[i] != 0))
	       *b_data++ = i;
	     else
	       *c_data++ = i;
	  }
	(void) SLang_assign_to_ref (ref, SLANG_ARRAY_TYPE, &ct);
	/* Let any error propagate */
	free_array (ct);
	/* fall through */
     }
   else
     {
	i = 0;
	while (b_num)
	  {
	     if (cmp == (a_data[i] != 0))
	       {
		  *b_data++ = i;
		  b_num--;
	       }
	     i++;
	  }
     }

   (void) SLang_push_array (bt, 0);
   /* fall through */

   return_error:
   free_array (at);
   free_array (bt);
   if (ref != NULL)
     SLang_free_ref (ref);
}

static void array_where (void)
{
   array_where_intern (1);
}
static void array_wherenot (void)
{
   array_where_intern (0);
}

static void array_wherediff (void)
{
   char *data, *isdiff;
   size_t sizeof_type;
   SLang_Class_Type *cl;
   SLuindex_Type i, num;
   SLindex_Type numdiff, *idx_ptr;
   SLang_Array_Type *at;
   SLang_Ref_Type *ref = NULL;

   if (SLang_Num_Function_Args == 2)
     {
	if (-1 == SLang_pop_ref (&ref))
	  return;
     }

   if (-1 == SLang_pop_array (&at, 1))
     {
	SLang_free_ref (ref);
	return;
     }
   num = at->num_elements;
   if (NULL == (isdiff = (char *) SLmalloc (num+1)))
     goto free_and_return;

   sizeof_type = at->sizeof_type;
   data = (char *) at->data;

   if (num == 0)
     numdiff = 0;
   else
     {
	isdiff[0] = 1;
	numdiff = 1;
     }

   cl = at->cl;

   if (0 == (at->flags & SLARR_DATA_VALUE_IS_POINTER))
     {
	for (i = 1; i < num; i++)
	  {
	     char *lastdata = data;
	     data += sizeof_type;
	     isdiff[i] = (0 != memcmp (data, lastdata, sizeof_type));
	     numdiff += isdiff[i];
	  }
     }
   else
     {
	VOID_STAR *ptr;

	if (NULL == cl->cl_cmp)
	  {
	     _pSLang_verror (SL_NOT_IMPLEMENTED,
			     "%s does not have a comparision function defined",
			     cl->cl_name);
	     goto free_and_return;
	  }
	ptr = (VOID_STAR *)at->data;
	for (i = 1; i < num; i++)
	  {
	     VOID_STAR *last_ptr = ptr;
	     ptr++;
	     if ((*ptr == NULL) || (*last_ptr == NULL))
	       isdiff[i] = (*ptr != *last_ptr);
	     else
	       {
		  int cmp;
		  if (-1 == cl->cl_cmp (at->data_type, ptr, last_ptr, &cmp))
		    goto free_and_return;

		  isdiff[i] = (cmp != 0);
	       }
	     numdiff += isdiff[i];
	  }
     }

   SLang_free_array (at);
   at = SLang_create_array1 (SLANG_ARRAY_INDEX_TYPE, 0, NULL, &numdiff, 1, 1);
   if (at == NULL)
     goto free_and_return;

   idx_ptr = (SLindex_Type *) at->data;
   for (i = 0; i < num; i++)
     {
	if (isdiff[i]) *idx_ptr++ = i;
     }
   if (-1 == SLang_push_array (at, 0))
     goto free_and_return;

   if (ref != NULL)
     {
	SLindex_Type numsame;
	numsame = (SLindex_Type)num - numdiff;

	SLang_free_array (at);	       /* already pushed, reuse variable */
	at = SLang_create_array1 (SLANG_ARRAY_INDEX_TYPE, 0, NULL, &numsame, 1, 1);
	if (at == NULL)
	  goto free_and_return;
	idx_ptr = (SLindex_Type *) at->data;
	for (i = 0; i < num; i++)
	  {
	     if (isdiff[i] == 0) *idx_ptr++ = i;
	  }
	(void) SLang_assign_to_ref (ref, SLANG_ARRAY_TYPE, &at);
	/* fall through */
     }

free_and_return:

   SLfree (isdiff);	       /* NULL ok */
   SLang_free_array (at);
   if (ref != NULL)
     SLang_free_ref (ref);
}

/* Up to the caller to ensure that ind_at is an index array */
static int do_array_reshape (SLang_Array_Type *at, SLang_Array_Type *ind_at)
{
   SLindex_Type *dims;
   SLuindex_Type i, num_dims;
   SLuindex_Type num_elements;

   num_dims = ind_at->num_elements;
   dims = (SLindex_Type *) ind_at->data;

   num_elements = 1;
   for (i = 0; i < num_dims; i++)
     {
	SLindex_Type d = dims[i];
	if (d < 0)
	  {
	     _pSLang_verror (SL_INVALID_PARM, "reshape: dimension is less then 0");
	     return -1;
	  }

	num_elements = (SLuindex_Type) (d * num_elements);
     }

   if ((num_elements != at->num_elements)
       || (num_dims > SLARRAY_MAX_DIMS))
     {
	_pSLang_verror (SL_INVALID_PARM, "Unable to reshape array to specified size");
	return -1;
     }

   for (i = 0; i < num_dims; i++)
     at->dims [i] = dims[i];

   while (i < SLARRAY_MAX_DIMS)
     {
	at->dims [i] = 1;
	i++;
     }

   at->num_dims = num_dims;
   return 0;
}

static int pop_1d_index_array (SLang_Array_Type **ind_atp)
{
   SLang_Array_Type *ind_at;

   *ind_atp = NULL;
   if (-1 == SLang_pop_array_of_type (&ind_at, SLANG_ARRAY_INDEX_TYPE))
     return -1;
   if (ind_at->num_dims != 1)
     {
	_pSLang_verror (SL_TYPE_MISMATCH, "Expecting 1-d array of indices");
	return -1;
     }
   *ind_atp = ind_at;
   return 0;
}

static int pop_reshape_args (SLang_Array_Type **atp, SLang_Array_Type **ind_atp)
{
   SLang_Array_Type *at, *ind_at;

   *ind_atp = *atp = NULL;

   if (-1 == pop_1d_index_array (&ind_at))
     return -1;

   if (-1 == SLang_pop_array (&at, 1))
     {
	free_array (ind_at);
	return -1;
     }

   *atp = at;
   *ind_atp = ind_at;
   return 0;
}

static void array_reshape (void)
{
   SLang_Array_Type *at, *ind_at;

   if (-1 == pop_reshape_args (&at, &ind_at))
     return;
   (void) do_array_reshape (at, ind_at);
   free_array (at);
   free_array (ind_at);
}

static void _array_reshape (void)
{
   SLang_Array_Type *at;
   SLang_Array_Type *new_at;
   SLang_Array_Type *ind_at;

   if (-1 == pop_reshape_args (&at, &ind_at))
     return;

   /* FIXME: Priority=low: duplicate_array could me modified to look at num_refs */

   /* Now try to avoid the overhead of creating a new array if possible */
   if (at->num_refs == 1)
     {
	/* Great, we are the sole owner of this array. */
	if ((-1 == do_array_reshape (at, ind_at))
	    || (-1 == SLclass_push_ptr_obj (SLANG_ARRAY_TYPE, (VOID_STAR)at)))
	  free_array (at);
	free_array (ind_at);
	return;
     }

   new_at = SLang_duplicate_array (at);
   if (new_at != NULL)
     {
	if (0 == do_array_reshape (new_at, ind_at))
	  (void) SLang_push_array (new_at, 0);

	free_array (new_at);
     }
   free_array (at);
   free_array (ind_at);
}

typedef struct
{
   SLang_Array_Type *at;
   int is_array;
   size_t increment;
   char *addr;
}
Map_Arg_Type;

typedef struct
{
   SLtype type;
   SLang_Array_Type *at;
   char *addr;
}
Map_Return_Type;

static void free_arraymap_retvals (Map_Return_Type *r, SLuindex_Type n)
{
   SLuindex_Type i;

   if (r == NULL)
     return;

   for (i = 0; i < n; i++)
     {
	if (r[i].at != NULL)
	  free_array (r[i].at);
     }
   SLfree ((char *)r);
}

static void free_arraymap_argvals (Map_Arg_Type *a, SLuindex_Type n)
{
   SLuindex_Type i;

   if (a == NULL)
     return;

   for (i = 0; i < n; i++)
     {
	if (a[i].at != NULL)
	  free_array (a[i].at);
     }
   SLfree ((char *)a);
}

static int pop_array_map_args (int num_arraymap_parms,
			       Map_Return_Type **retvalsp, SLuindex_Type *nretp,
			       SLang_Name_Type **funcp,
			       Map_Arg_Type **argvalsp, SLuindex_Type *nargsp,
			       SLang_Array_Type **at_controlp)
{
   Map_Return_Type *retvals = NULL;
   Map_Arg_Type *argvals = NULL;
   unsigned int i, nret, nargs=0;
   SLang_Array_Type *at_control;
   SLang_Name_Type *func = NULL;

   if (-1 == SLreverse_stack (num_arraymap_parms))
     return -1;

   nret = 0;
   while (nret < (unsigned int)num_arraymap_parms)
     {
	int type;

	if (-1 == (type = SLang_peek_at_stack_n (nret)))
	  goto return_error;

	if (type != SLANG_DATATYPE_TYPE)
	  break;

	nret++;
     }
   if (nret == 0)
     retvals = NULL;
   else
     {
	if (NULL == (retvals = (Map_Return_Type *)SLcalloc(nret,sizeof(Map_Return_Type))))
	  goto return_error;

	for (i = 0; i < nret; i++)
	  {
	     num_arraymap_parms--;
	     if (-1 == SLang_pop_datatype (&retvals[i].type))
	       goto return_error;
	  }
     }

   if (num_arraymap_parms != 0)
     {
	num_arraymap_parms--;
	func = SLang_pop_function ();
     }
   if (func == NULL)
     {
	SLang_verror (SL_INVALID_PARM, "Expecting a reference to a function");
	goto return_error;
     }

   if (num_arraymap_parms == 0)
     {
	SLang_verror (SL_NUM_ARGS_ERROR, "array_map requires at least one function argument");
	goto return_error;
     }

   nargs = num_arraymap_parms;
   if (NULL == (argvals = (Map_Arg_Type *)SLcalloc (nargs, sizeof(Map_Arg_Type))))
     goto return_error;

   at_control = NULL;
   for (i = 0; i < nargs; i++)
     {
	SLang_Array_Type *at;

	argvals[i].is_array = (SLANG_ARRAY_TYPE == SLang_peek_at_stack ());

	num_arraymap_parms--;
	if (-1 == SLang_pop_array (&at, 1))
	  goto return_error;

	if ((at_control == NULL) && (argvals[i].is_array))
	  at_control = at;
	argvals[i].at = at;
	argvals[i].addr = (char *)at->data;
     }

   if (at_control == NULL)
     at_control = argvals[0].at;

   for (i = 0; i < nret; i++)
     {
	SLtype t = retvals[i].type;

	if (t == SLANG_UNDEFINED_TYPE)   /* Void_Type */
	  {
	     retvals[i].at = NULL;
	     retvals[i].addr = NULL;
	  }
	else
	  {
	     SLang_Array_Type *at;
	     if (NULL == (at = SLang_create_array (t, 0, NULL, at_control->dims, at_control->num_dims)))
	       goto return_error;
	     retvals[i].at = at;
	     retvals[i].addr = (char *)at->data;
	  }
     }

   *nretp = nret;
   *retvalsp = retvals;
   *funcp = func;
   *nargsp = nargs;
   *argvalsp = argvals;
   *at_controlp = at_control;
   return 0;

return_error:
   (void) SLdo_pop_n (num_arraymap_parms);
   free_arraymap_retvals (retvals, nret);
   SLang_free_function (func);	       /* NULL ok */
   free_arraymap_argvals (argvals, nargs);
   return -1;
}

/* Usage: array_map ([Return-Type...,] &func, args...); */
static void array_map (void)
{
   Map_Arg_Type *argvals;
   Map_Return_Type *retvals;
   SLuindex_Type i, nrets, nargs;
   SLang_Array_Type *at_control;
   SLang_Name_Type *func;
   SLang_Struct_Type *q;
   SLuindex_Type num_elements;
   int num_arraymap_parms;

   num_arraymap_parms = SLang_Num_Function_Args;
   if (num_arraymap_parms < 2)
     {
	_pSLang_verror (SL_INVALID_PARM,
		      "Usage: array_map ([Return-Types...,] &func, args...)");
	SLdo_pop_n (num_arraymap_parms);
	return;
     }

   if (-1 == _pSLang_get_qualifiers (&q))
     return;

   if (-1 == pop_array_map_args (num_arraymap_parms, &retvals, &nrets, &func,
				 &argvals, &nargs, &at_control))
     return;

   num_elements = at_control->num_elements;

   for (i = 0; i < nargs; i++)
     {
	SLang_Array_Type *ati = argvals[i].at;
	/* FIXME: Priority = low: The actual dimensions should be compared. */
	if (ati->num_elements == num_elements)
	  argvals[i].increment = ati->sizeof_type;
	else
	  argvals[i].increment = 0;

	if ((num_elements != 0)
	    && (ati->num_elements == 0))
	  {
	     _pSLang_verror (SL_TypeMismatch_Error, "array_map: function argument %lu of %lu is an empty array",
			     (unsigned long) (i+1), (unsigned long)nargs);
	     goto return_error;
	  }
     }

   for (i = 0; i < num_elements; i++)
     {
	unsigned int j;
	Map_Return_Type *ret;

	if (-1 == SLang_start_arg_list ())
	  goto return_error;

	for (j = 0; j < nargs; j++)
	  {
	     if (-1 == push_element_at_addr (argvals[j].at,
					     (VOID_STAR) argvals[j].addr,
					     1))
	       {
		  SLdo_pop_n (j);
		  goto return_error;
	       }

	     argvals[j].addr += argvals[j].increment;
	  }

	if (-1 == SLang_end_arg_list ())
	  {
	     SLdo_pop_n (nargs);
	     goto return_error;
	  }

	if ((q != NULL) && (-1 == _pSLang_set_qualifiers (q)))
	  goto return_error;

	if (-1 == SLexecute_function (func))
	  goto return_error;

	ret = retvals + nrets;
	while (ret > retvals)
	  {
	     SLang_Array_Type *at;
	     SLang_Class_Type *cl;

	     ret--;
	     if (NULL == (at = ret->at))
	       continue;

	     cl = at->cl;

	     if (0 == (at->flags & SLARR_DATA_VALUE_IS_POINTER))
	       {
		  if (-1 == cl->cl_apop (at->data_type, (VOID_STAR) ret->addr))
		    goto return_error;
	       }
	     else
	       {
		  /* Use aput_get_data_to_put to allow NULLs */
		  SLuindex_Type nelements = 1;
		  int allow_array = 0;
		  SLang_Array_Type *unused_array;
		  char *data_to_put;
		  SLuindex_Type unused_data_increment;

		  if (-1 == aput_get_data_to_put (cl, nelements, allow_array, &unused_array, &data_to_put, &unused_data_increment))
		    goto return_error;

		  if (-1 == transfer_n_elements(at, ret->addr, data_to_put, at->sizeof_type, 1, 1))
		    {
		       (*cl->cl_destroy) (cl->cl_data_type, (VOID_STAR) data_to_put);
		       goto return_error;
		    }
		  (*cl->cl_destroy) (cl->cl_data_type, (VOID_STAR) data_to_put);
	       }
	     ret->addr += at->sizeof_type;
	  }
     }

   for (i = 0; i < nrets; i++)
     {
	if (retvals[i].at != NULL)
	  (void) SLang_push_array (retvals[i].at, 0);
     }

   /* fall through */

return_error:
   free_arraymap_argvals (argvals, nargs);
   if (q != NULL) SLang_free_struct (q);
   SLang_free_function (func);
   free_arraymap_retvals (retvals, nrets);
}

static int push_array_shape (SLang_Array_Type *at)
{
   SLang_Array_Type *bt;
   SLindex_Type num_dims;
   SLindex_Type *bdata, *a_dims;
   int i;

   num_dims = (SLindex_Type)at->num_dims;
   if (NULL == (bt = SLang_create_array (SLANG_ARRAY_INDEX_TYPE, 0, NULL, &num_dims, 1)))
     return -1;

   a_dims = at->dims;
   bdata = (SLindex_Type *) bt->data;
   for (i = 0; i < num_dims; i++) bdata [i] = a_dims [i];

   return SLang_push_array (bt, 1);
}

static void array_info (void)
{
   SLang_Array_Type *at;

   if (-1 == pop_array (&at, 1))
     return;

   if (0 == push_array_shape (at))
     {
	(void) SLang_push_integer ((int) at->num_dims);
	(void) SLang_push_datatype (at->data_type);
     }
   free_array (at);
}

static void array_shape (void)
{
   SLang_Array_Type *at;

   if (-1 == pop_array (&at, 1))
     return;

   (void) push_array_shape (at);
   free_array (at);
}

#define HAVE_AGET_PUT_INTRIN 0
#if HAVE_AGET_PUT_INTRIN
static int pop_int_indices (SLindex_Type *dims, unsigned int ndims)
{
   int i;

   if (ndims > SLARRAY_MAX_DIMS)
     {
	_pSLang_verror (SL_INVALID_PARM, "Too many dimensions specified");
	return -1;
     }
   for (i = (int)ndims-1; i >= 0; i--)
     {
	if (-1 == SLang_pop_integer (dims+i))
	  return -1;
     }
   return 0;
}
/* Usage: aput(v, x, i,..,k) */
static void aput_intrin (void)
{
   char *data_to_put;
   SLuindex_Type data_increment;
   SLindex_Type indices[SLARRAY_MAX_DIMS];
   int is_ptr;
   unsigned int ndims = SLang_Num_Function_Args-2;
   SLang_Array_Type *at, *bt_unused;

   if (-1 == pop_int_indices (indices, ndims))
     return;

   if (-1 == SLang_pop_array (&at, 1))
     return;

   if (at->num_dims != ndims)
     {
	SLang_set_error (SL_Index_Error);
	free_array (at);
	return;
     }

   is_ptr = (at->flags & SLARR_DATA_VALUE_IS_POINTER);

   if (-1 == aput_get_data_to_put (at->cl, 1, 0, &bt_unused, &data_to_put, &data_increment))
     {
	free_array (at);
	return;
     }

   (void) _pSLarray_aput_transfer_elem (at, indices, (VOID_STAR)data_to_put,
				       at->sizeof_type, is_ptr);
   if (is_ptr)
     (*at->cl->cl_destroy) (at->cl->cl_data_type, (VOID_STAR) data_to_put);

   free_array (at);
}

/* Usage: x_i..k = aget(x, i,..,k) */
static void aget_intrin (void)
{
   SLindex_Type dims[SLARRAY_MAX_DIMS];
   unsigned int ndims = (unsigned int) (SLang_Num_Function_Args-1);
   SLang_Array_Type *at;
   VOID_STAR data;

   if (-1 == pop_int_indices (dims, ndims))
     return;

   if (-1 == pop_array (&at, 1))
     return;

   if (at->num_dims != ndims)
     {
	SLang_set_error (SL_Index_Error);
	free_array (at);
	return;
     }

   if ((ndims == 1)
       && (at->index_fun == linear_get_data_addr))
     {
	SLindex_Type i = dims[0];
	if (i < 0)
	  {
	     i += at->dims[0];
	     if (i < 0)
	       i = at->dims[0];
	  }
	if (i >= at->dims[0])
	  {
	     SLang_set_error (SL_Index_Error);
	     free_array (at);
	     return;
	  }
	if (at->data_type == SLANG_INT_TYPE)
	  {
	     (void) SLclass_push_int_obj (SLANG_INT_TYPE, *((int *)at->data + i));
	     goto free_and_return;
	  }
#if SLANG_HAS_FLOAT
	if (at->data_type == SLANG_DOUBLE_TYPE)
	  {
	     (void) SLclass_push_double_obj (SLANG_DOUBLE_TYPE, *((double *)at->data + i));
	     goto free_and_return;
	  }
#endif
	if (at->data_type == SLANG_CHAR_TYPE)
	  {
	     (void) SLclass_push_int_obj (SLANG_UCHAR_TYPE, *((unsigned char *)at->data + i));
	     goto free_and_return;
	  }
	data = (VOID_STAR) ((char *)at->data + (SLuindex_Type)i * at->sizeof_type);
     }
   else data = get_data_addr (at, dims);

   if (data != NULL)
     (void) push_element_at_addr (at, (VOID_STAR) data, ndims);

   free_and_return:
   free_array (at);
}
#endif				       /* HAVE_AGET_PUT_INTRIN */

static int pop_byte_order (int *bop)
{
   int bo;

   if (-1 == SLang_pop_integer (&bo))
     return -1;

   switch (bo)
     {
      case 'n': case 'N': case '=': bo = _pSLANG_BYTEORDER_NATIVE; break;
      case 'b': case 'B': case '>': bo = _pSLANG_BYTEORDER_BIGE; break;
      case 'l': case 'L': case '<': bo = _pSLANG_BYTEORDER_LILE; break;
      default:
	SLang_verror (SL_InvalidParm_Error, "Invalid byte-order specifier, expecting one of 'B', 'L', or 'N'");
	return -1;
     }

   *bop = bo;
   return 0;
}

/* Usage b = _array_byteswap (a, from, to);
 */
static void byteswap_intrin (void)
{
   SLang_Array_Type *at, *bt;
   int from, to;
   int converted_scalar;

   if (SLang_Num_Function_Args != 3)
     {
	SLang_verror (SL_Usage_Error, "\
Usage: b = _array_byteswap (a, from, to);\
  from/to is one of: 'B' (big), 'L' (little), or 'N'(native) endian order");
	return;
     }
   if ((-1 == pop_byte_order (&to)) || (-1 == pop_byte_order (&from)))
     return;

   if (-1 == SLang_pop_array (&at, 1))
     return;

   converted_scalar = at->flags & SLARR_DERIVED_FROM_SCALAR;

   bt = _pSLpack_byteswap_array (at, from, to);
   SLang_free_array (at);
   if (bt == NULL)
     return;

   if (converted_scalar)
     (void) push_element_at_index (bt, 0);
   else
     (void) SLang_push_array (bt, 0);

   SLang_free_array (bt);
}


static SLang_Intrin_Fun_Type Array_Table [] =
{
   MAKE_INTRINSIC_0("array_map", array_map, SLANG_VOID_TYPE),
   MAKE_INTRINSIC_0("array_sort", array_sort_intrin, SLANG_VOID_TYPE),
   MAKE_INTRINSIC_0("get_default_sort_method", get_default_sort_method, SLANG_VOID_TYPE),
   MAKE_INTRINSIC_S("set_default_sort_method", set_default_sort_method, SLANG_VOID_TYPE),
   MAKE_INTRINSIC_1("array_to_bstring", array_to_bstring, SLANG_VOID_TYPE, SLANG_ARRAY_TYPE),
   MAKE_INTRINSIC_1("bstring_to_array", bstring_to_array, SLANG_VOID_TYPE, SLANG_BSTRING_TYPE),
   MAKE_INTRINSIC("init_char_array", init_char_array, SLANG_VOID_TYPE, 0),
   MAKE_INTRINSIC_0("_isnull", is_null_intrinsic, SLANG_VOID_TYPE),
   MAKE_INTRINSIC_0("array_info", array_info, SLANG_VOID_TYPE),
   MAKE_INTRINSIC_0("array_shape", array_shape, SLANG_VOID_TYPE),
   MAKE_INTRINSIC_0("where", array_where, SLANG_VOID_TYPE),
   MAKE_INTRINSIC_0("wherenot", array_wherenot, SLANG_VOID_TYPE),
   MAKE_INTRINSIC_0("wherefirst", array_where_first, SLANG_VOID_TYPE),
   MAKE_INTRINSIC_0("wherelast", array_where_last, SLANG_VOID_TYPE),
   MAKE_INTRINSIC_0("wherediff", array_wherediff, SLANG_VOID_TYPE),
   MAKE_INTRINSIC_0("reshape", array_reshape, SLANG_VOID_TYPE),
   MAKE_INTRINSIC_0("_reshape", _array_reshape, SLANG_VOID_TYPE),
   MAKE_INTRINSIC_0("_array_byteswap", byteswap_intrin, SLANG_VOID_TYPE),
#if HAVE_AGET_PUT_INTRIN
   MAKE_INTRINSIC_0("__aget", aget_intrin, SLANG_VOID_TYPE),
   MAKE_INTRINSIC_0("__aput", aput_intrin, SLANG_VOID_TYPE),
#endif
   SLANG_END_INTRIN_FUN_TABLE
};

static char *array_string (SLtype type, VOID_STAR v)
{
   SLang_Array_Type *at;
   char buf[512];
   unsigned int i, num_dims;
   SLindex_Type *dims;

   at = *(SLang_Array_Type **) v;
   type = at->data_type;
   num_dims = at->num_dims;
   dims = at->dims;

   sprintf (buf, "%s[%ld", SLclass_get_datatype_name (type), (long)at->dims[0]);

   for (i = 1; i < num_dims; i++)
     sprintf (buf + strlen(buf), ",%ld", (long)dims[i]);
   strcat (buf, "]");

   return SLmake_string (buf);
}

static void array_destroy (SLtype type, VOID_STAR v)
{
   SLang_Array_Type *at = *(SLang_Array_Type **) v;

   (void) type;
   if ((at != NULL)
       && (at->num_refs > 1))
     {
	at->num_refs -= 1;
	return;
     }
   free_array (*(SLang_Array_Type **) v);
}

static int array_push (SLtype type, VOID_STAR v)
{
   SLang_Array_Type *at;

   (void) type;

   at = *(SLang_Array_Type **) v;
   if (at == NULL)
     return SLang_push_null ();

   at->num_refs += 1;

   if (0 == SLclass_push_ptr_obj (SLANG_ARRAY_TYPE, (VOID_STAR) at))
     return 0;

   at->num_refs -= 1;
   return -1;
}

int SLang_push_array (SLang_Array_Type *at, int free_flag)
{
   if (at == NULL)
     return SLang_push_null ();

   return _pSLang_push_array (at, free_flag);
}

/* Intrinsic arrays are not stored in a variable. So, the address that
 * would contain the variable holds the array address.
 */
static int array_push_intrinsic (SLtype type, VOID_STAR v)
{
   (void) type;
   return SLang_push_array ((SLang_Array_Type *) v, 0);
}

int _pSLarray_add_bin_op (SLtype type)
{
   SL_OOBinary_Type *ab;
   SLang_Class_Type *cl;

   if (type == SLANG_VOID_TYPE)
     {
	cl = _pSLclass_get_class (SLANG_ARRAY_TYPE);
	if ((cl->cl_this_binary_void != NULL)
	    || (cl->cl_void_binary_this != NULL))
	  return 0;
     }
   else
     {
	cl = _pSLclass_get_class (type);
	ab = cl->cl_binary_ops;

	while (ab != NULL)
	  {
	     if (ab->data_type == SLANG_ARRAY_TYPE)
	       return 0;
	     ab = ab->next;
	  }
     }

   if ((-1 == SLclass_add_binary_op (SLANG_ARRAY_TYPE, type, array_binary_op, array_binary_op_result))
       || (-1 == SLclass_add_binary_op (type, SLANG_ARRAY_TYPE, array_binary_op, array_binary_op_result)))
     return -1;

   return 0;
}

static SLang_Array_Type *
do_array_math_op (int op, int unary_type,
		  SLang_Array_Type *at, SLuindex_Type na)
{
   SLtype a_type, b_type;
   int (*f) (int, SLtype, VOID_STAR, SLuindex_Type, VOID_STAR);
   SLang_Array_Type *bt;
   SLang_Class_Type *b_cl;

   if (na != 1)
     {
	_pSLang_verror (SL_NOT_IMPLEMENTED, "Operation restricted to 1 array");
	return NULL;
     }

   a_type = at->data_type;
   if (NULL == (f = _pSLclass_get_unary_fun (op, at->cl, &b_cl, unary_type)))
     return NULL;
   b_type = b_cl->cl_data_type;

   if (-1 == coerse_array_to_linear (at))
     return NULL;

#if SLANG_USE_TMP_OPTIMIZATION
   /* If we are dealing with scalar (or vector) objects, and if the object
    * appears to be owned by the stack, then use it instead of creating a
    * new version.  This can happen with code such as:
    * @  x = [1,2,3,4];
    * @  x = UNARY_OP(__tmp(x));
    */
   if (((b_cl->cl_class_type == SLANG_CLASS_TYPE_SCALAR)
	|| (b_cl->cl_class_type == SLANG_CLASS_TYPE_VECTOR))
       && (at->num_refs == 1)
       && (at->data_type == b_cl->cl_data_type)
       && (0 == (at->flags & SLARR_DATA_VALUE_IS_READ_ONLY)))
     {
	bt = at;
	bt->num_refs = 2;
     }
   else
#endif				       /* SLANG_USE_TMP_OPTIMIZATION */
     if (NULL == (bt = SLang_create_array1 (b_type, 0, NULL, at->dims, at->num_dims, 1)))
       return NULL;

   if (1 != (*f)(op, a_type, at->data, at->num_elements, bt->data))
     {
	free_array (bt);
	return NULL;
     }
   return bt;
}

static int
array_unary_op_result (int op, SLtype a, SLtype *b)
{
   (void) op;
   (void) a;
   *b = SLANG_ARRAY_TYPE;
   return 1;
}

static int
array_unary_op (int op,
		SLtype a, VOID_STAR ap, SLuindex_Type na,
		VOID_STAR bp)
{
   SLang_Array_Type *at;

   (void) a;
   at = *(SLang_Array_Type **) ap;
   if (NULL == (at = do_array_math_op (op, SLANG_BC_UNARY, at, na)))
     {
	if (SLang_get_error ()) return -1;
	return 0;
     }
   *(SLang_Array_Type **) bp = at;
   return 1;
}

static int
array_math_op (int op,
	       SLtype a, VOID_STAR ap, SLuindex_Type na,
	       VOID_STAR bp)
{
   SLang_Array_Type *at;

   (void) a;
   at = *(SLang_Array_Type **) ap;
   if (NULL == (at = do_array_math_op (op, SLANG_BC_MATH_UNARY, at, na)))
     {
	if (SLang_get_error ()) return -1;
	return 0;
     }
   *(SLang_Array_Type **) bp = at;
   return 1;
}

static int
array_app_op (int op,
	      SLtype a, VOID_STAR ap, SLuindex_Type na,
	      VOID_STAR bp)
{
   SLang_Array_Type *at;

   (void) a;
   at = *(SLang_Array_Type **) ap;
   if (NULL == (at = do_array_math_op (op, SLANG_BC_APP_UNARY, at, na)))
     {
	if (SLang_get_error ()) return -1;
	return 0;
     }
   *(SLang_Array_Type **) bp = at;
   return 1;
}

/* Typecast array from a_type to b_type */
int
_pSLarray_typecast (SLtype a_type, VOID_STAR ap, SLuindex_Type na,
		    SLtype b_type, VOID_STAR bp,
		    int is_implicit)
{
   SLang_Array_Type *at, *bt;
   SLang_Class_Type *b_cl;
   int no_init;
   int (*t) (SLtype, VOID_STAR, SLuindex_Type, SLtype, VOID_STAR);

   if (na != 1)
     {
	_pSLang_verror (SL_NOT_IMPLEMENTED, "typecast of multiple arrays not implemented");
	return -1;
     }

   at = *(SLang_Array_Type **) ap;
   a_type = at->data_type;

   if (a_type == b_type)
     {
	at->num_refs += 1;
	*(SLang_Array_Type **) bp = at;
	return 1;
     }

   /* check for alias */
   if (at->cl == (b_cl = _pSLclass_get_class (b_type)))
     {
	at->num_refs += 1;

	/* Force to the desired type.  Hopefully there will be no consequences
	 * from this.
	 */
	at->data_type = b_cl->cl_data_type;
	*(SLang_Array_Type **) bp = at;
	return 1;
     }

   if (at->flags & SLARR_DATA_VALUE_IS_RANGE)
     {
	if (-1 == try_typecast_range_array (at, b_type, &bt))
	  return -1;
	if (bt != NULL)
	  {
	     *(SLang_Array_Type **) bp = bt;
	     return 1;
	  }
	/* Couldn't do it, so drop */
     }

   /* Typecast NULL array to the desired type with elements set to NULL */
   if ((a_type == SLANG_NULL_TYPE)
       && ((b_cl->cl_class_type == SLANG_CLASS_TYPE_MMT)
	   || (b_cl->cl_class_type == SLANG_CLASS_TYPE_PTR)))
     {
	if (NULL == (bt = SLang_create_array1 (b_type, 0, NULL, at->dims, at->num_dims, 0)))
	  return -1;

	*(SLang_Array_Type **) bp = bt;
	return 1;
     }

   if (NULL == (t = _pSLclass_get_typecast (a_type, b_type, is_implicit)))
     return -1;

   if (-1 == coerse_array_to_linear (at))
     return -1;

   no_init = ((b_cl->cl_class_type == SLANG_CLASS_TYPE_SCALAR)
	      || (b_cl->cl_class_type == SLANG_CLASS_TYPE_VECTOR));

   if (NULL == (bt = SLang_create_array1 (b_type, 0, NULL, at->dims, at->num_dims, no_init)))
     return -1;

   if (1 == (*t) (a_type, at->data, at->num_elements, b_type, bt->data))
     {
	*(SLang_Array_Type **) bp = bt;
	return 1;
     }

   free_array (bt);
   return 0;
}

static SLang_Array_Type *duplicate_range_array (SLang_Array_Type *at)
{
   SLarray_Range_Array_Type *r = (SLarray_Range_Array_Type *)at->data;
   return create_range_array (r, (SLindex_Type)at->num_elements, at->data_type, r->to_linear_fun);
}

SLang_Array_Type *SLang_duplicate_array (SLang_Array_Type *at)
{
   SLang_Array_Type *bt;
   char *data, *a_data;
   SLuindex_Type i, num_elements;
   size_t sizeof_type, size;
   int (*cl_acopy) (SLtype, VOID_STAR, VOID_STAR);
   SLtype type;

   if (at->flags & SLARR_DATA_VALUE_IS_RANGE)
     return duplicate_range_array (at);

   if (-1 == coerse_array_to_linear (at))
     return NULL;

   type = at->data_type;
   num_elements = at->num_elements;
   sizeof_type = at->sizeof_type;

   if (NULL == (data = (char *)_SLcalloc (num_elements, sizeof_type)))
     return NULL;

   size = num_elements * sizeof_type;

   if (NULL == (bt = SLang_create_array (type, 0, (VOID_STAR)data, at->dims, at->num_dims)))
     {
	SLfree (data);
	return NULL;
     }

   a_data = (char *) at->data;
   if (0 == (at->flags & SLARR_DATA_VALUE_IS_POINTER))
     {
	SLMEMCPY (data, a_data, size);
	return bt;
     }

   SLMEMSET (data, 0, size);

   cl_acopy = at->cl->cl_acopy;
   for (i = 0; i < num_elements; i++)
     {
	if (NULL != *(VOID_STAR *) a_data)
	  {
	     if (-1 == (*cl_acopy) (type, (VOID_STAR) a_data, (VOID_STAR) data))
	       {
		  free_array (bt);
		  return NULL;
	       }
	  }

	data += sizeof_type;
	a_data += sizeof_type;
     }

   return bt;
}

static int array_dereference (SLtype type, VOID_STAR addr)
{
   SLang_Array_Type *at;

   (void) type;
   at = SLang_duplicate_array (*(SLang_Array_Type **) addr);
   if (at == NULL) return -1;
   return SLang_push_array (at, 1);
}

/* This function gets called via, e.g., @Array_Type (Double_Type, [10,20]);
 */
static int
array_datatype_deref (SLtype type)
{
   SLang_Array_Type *ind_at;
   SLang_Array_Type *at;

   if (-1 == pop_1d_index_array (&ind_at))
     goto return_error;

   if (-1 == SLang_pop_datatype (&type))
     goto return_error;

   if (NULL == (at = SLang_create_array (type, 0, NULL,
					 (SLindex_Type *) ind_at->data,
					 ind_at->num_elements)))
     goto return_error;

   free_array (ind_at);
   return SLang_push_array (at, 1);

   return_error:
   free_array (ind_at);
   return -1;
}

static int array_length (SLtype type, VOID_STAR v, SLuindex_Type *len)
{
   SLang_Array_Type *at;

   (void) type;
   at = *(SLang_Array_Type **) v;
   *len = at->num_elements;
   return 0;
}

static void array_inc_ref (SLtype type, VOID_STAR v, int dr)
{
   SLang_Array_Type *at = *(SLang_Array_Type **) v;
   (void) type;
   if (at != NULL)
     at->num_refs += dr;
}

int
_pSLarray_init_slarray (void)
{
   SLang_Class_Type *cl;

   if (-1 == SLadd_intrin_fun_table (Array_Table, NULL))
     return -1;

   if (NULL == (cl = SLclass_allocate_class ("Array_Type")))
     return -1;

   (void) SLclass_set_string_function (cl, array_string);
   (void) SLclass_set_destroy_function (cl, array_destroy);
   (void) SLclass_set_push_function (cl, array_push);
   (void) SLclass_set_length_function (cl, array_length);
   (void) SLclass_set_deref_function (cl, array_dereference);
   (void) SLclass_set_is_container (cl, 1);

   cl->cl_push_intrinsic = array_push_intrinsic;
   cl->cl_datatype_deref = array_datatype_deref;
   cl->cl_inc_ref = array_inc_ref;

   (void) SLclass_set_eqs_function (cl, array_eqs_method);

   if (-1 == SLclass_register_class (cl, SLANG_ARRAY_TYPE, sizeof (VOID_STAR),
				     SLANG_CLASS_TYPE_PTR))
     return -1;

   if ((-1 == SLclass_add_binary_op (SLANG_ARRAY_TYPE, SLANG_ARRAY_TYPE, array_binary_op, array_binary_op_result))
       || (-1 == SLclass_add_unary_op (SLANG_ARRAY_TYPE, array_unary_op, array_unary_op_result))
       || (-1 == SLclass_add_app_unary_op (SLANG_ARRAY_TYPE, array_app_op, array_unary_op_result))
       || (-1 == SLclass_add_math_op (SLANG_ARRAY_TYPE, array_math_op, array_unary_op_result)))
     return -1;

   return 0;
}

int SLang_pop_array (SLang_Array_Type **at_ptr, int convert_scalar)
{
   SLang_Array_Type *at;

   if (-1 == pop_array (&at, convert_scalar))
     {
	*at_ptr = NULL;
	return -1;
     }

   if (-1 == coerse_array_to_linear (at))
     {
	free_array (at);
	*at_ptr = NULL;
	return -1;
     }
   *at_ptr = at;
   return 0;
}

int SLang_pop_array_of_type (SLang_Array_Type **at, SLtype type)
{
   if (-1 == SLclass_typecast (type, 1, 1))
     return -1;

   return SLang_pop_array (at, 1);
}

void (*_pSLang_Matrix_Multiply)(void);

int _pSLarray_matrix_multiply (void)
{
   if (_pSLang_Matrix_Multiply != NULL)
     {
	(*_pSLang_Matrix_Multiply)();
	return 0;
     }
   _pSLang_verror (SL_NOT_IMPLEMENTED, "Matrix multiplication not available");
   return -1;
}

struct _pSLang_Foreach_Context_Type
{
   SLang_Array_Type *at;
   SLindex_Type next_element_index;
};

SLang_Foreach_Context_Type *
_pSLarray_cl_foreach_open (SLtype type, unsigned int num)
{
   SLang_Foreach_Context_Type *c;

   if (num != 0)
     {
	SLdo_pop_n (num + 1);
	_pSLang_verror (SL_NOT_IMPLEMENTED,
		      "%s does not support 'foreach using' form",
		      SLclass_get_datatype_name (type));
	return NULL;
     }

   if (NULL == (c = (SLang_Foreach_Context_Type *) SLmalloc (sizeof (SLang_Foreach_Context_Type))))
     return NULL;

   memset ((char *) c, 0, sizeof (SLang_Foreach_Context_Type));

   if (-1 == pop_array (&c->at, 1))
     {
	SLfree ((char *) c);
	return NULL;
     }

   return c;
}

void _pSLarray_cl_foreach_close (SLtype type, SLang_Foreach_Context_Type *c)
{
   (void) type;
   if (c == NULL) return;
   free_array (c->at);
   SLfree ((char *) c);
}

int _pSLarray_cl_foreach (SLtype type, SLang_Foreach_Context_Type *c)
{
   SLang_Array_Type *at;
   VOID_STAR data;

   (void) type;

   if (c == NULL)
     return -1;

   at = c->at;
   /* Use <= here to prepare for the time when arrays are permitted to change size */
   if ((SLindex_Type)at->num_elements <= c->next_element_index)
     return 0;

   /* FIXME: Priority = low.  The following assumes linear arrays
    * or Integer range arrays.  Fixing it right requires a method to get the
    * nth element of a multidimensional array.
    */

   if (at->flags & SLARR_DATA_VALUE_IS_RANGE)
     {
	SLindex_Type d = (SLindex_Type) c->next_element_index;
	data = range_get_data_addr (at, &d);
     }
   else
     data = (VOID_STAR) ((char *)at->data + (c->next_element_index * at->sizeof_type));

   c->next_element_index += 1;

   if ((at->flags & SLARR_DATA_VALUE_IS_POINTER)
       && (*(VOID_STAR *) data == NULL))
     {
	if (-1 == SLang_push_null ())
	  return -1;
     }
   else if (-1 == (*at->cl->cl_apush)(at->data_type, data))
     return -1;

   /* keep going */
   return 1;
}

/* References to array elements */
typedef struct
{
   SLang_Object_Type at;
   SLang_Object_Type index_objs [SLARRAY_MAX_DIMS];
   unsigned int num_indices;
}
Array_Elem_Ref_Type;

static int elem_ref_push_index_objs (Array_Elem_Ref_Type *ert)
{
   SLang_Object_Type *o, *omax;

   o = ert->index_objs;
   omax = o + ert->num_indices;

   while (o < omax)
     {
	if (-1 == _pSLpush_slang_obj (o))
	  return -1;
	o++;
     }
   if (-1 == _pSLpush_slang_obj (&ert->at))
     return -1;

   return 0;
}

static int elem_ref_deref_assign (VOID_STAR vdata)
{
   Array_Elem_Ref_Type *ert = (Array_Elem_Ref_Type *)vdata;

   if (-1 == elem_ref_push_index_objs (ert))
     return -1;

   return _pSLarray_aput1 (ert->num_indices);
}

static int elem_ref_deref (VOID_STAR vdata)
{
   Array_Elem_Ref_Type *ert = (Array_Elem_Ref_Type *)vdata;

   if (-1 == elem_ref_push_index_objs (ert))
     return -1;

   return _pSLarray_aget1 (ert->num_indices);
}

static void elem_ref_destroy (VOID_STAR vdata)
{
   Array_Elem_Ref_Type *ert = (Array_Elem_Ref_Type *)vdata;
   SLang_Object_Type *o, *omax;

   if (ert->at.o_data_type != 0)
     SLang_free_object (&ert->at);
   o = ert->index_objs;
   omax = o + ert->num_indices;
   while (o < omax)
     {
	if (o->o_data_type != 0)
	  SLang_free_object (o);
	o++;
     }
}

/* &A[i,...j] ==> __args i..j A ARRAY_REF */
int _pSLarray_push_elem_ref (void)
{
   unsigned int num_indices = (unsigned int) (SLang_Num_Function_Args-1);
   Array_Elem_Ref_Type *ert;
   SLang_Ref_Type *ref;
   unsigned int i;
   int ret;

   if (num_indices > SLARRAY_MAX_DIMS)
     {
	_pSLang_verror (SL_INVALID_PARM, "Number of dims must be less than %d", 1+SLARRAY_MAX_DIMS);
	return -1;
     }

   if (NULL == (ref = _pSLang_new_ref (sizeof (Array_Elem_Ref_Type))))
     return -1;

   ref->deref = elem_ref_deref;
   ref->deref_assign = elem_ref_deref_assign;
   ref->destroy = elem_ref_destroy;

   ert = (Array_Elem_Ref_Type *) ref->data;
   ert->num_indices = num_indices;
   if (-1 == SLang_pop (&ert->at))
     {
	SLang_free_ref (ref);
	return -1;
     }

   i = num_indices;
   while (i)
     {
	i--;
	if (-1 == SLang_pop (ert->index_objs + i))
	  {
	     SLang_free_ref (ref);
	     return -1;
	  }
     }
   ret = SLang_push_ref (ref);
   SLang_free_ref (ref);
   return ret;
}
