/* Copyright (c) 1998, 1999, 2001 John E. Davis * This file is part of the S-Lang library. * * You may distribute under the terms of either the GNU General Public * License or the Perl Artistic License. */ #include "slinclud.h" #define SL_APP_WANTS_FOREACH #include "slang.h" #include "_slang.h" #define USE_NEW_ANYTYPE_CODE 1 typedef struct _SLAssoc_Array_Element_Type { char *key; /* slstring */ struct _SLAssoc_Array_Element_Type *next; SLang_Object_Type value; } _SLAssoc_Array_Element_Type; typedef struct { _SLAssoc_Array_Element_Type *elements[SLASSOC_HASH_TABLE_SIZE]; SLang_Object_Type default_value; unsigned int num_elements; #define HAS_DEFAULT_VALUE 1 unsigned int flags; unsigned char type; } SLang_Assoc_Array_Type; #define USE_CACHED_STRING 1 #if USE_CACHED_STRING static char *Cached_String; static SLang_Object_Type *Cached_Obj; static SLang_Assoc_Array_Type *Cached_Array; #endif static SLang_Assoc_Array_Type *alloc_assoc_array (unsigned char type, int has_default_value) { SLang_Assoc_Array_Type *a; a = (SLang_Assoc_Array_Type *)SLmalloc (sizeof (SLang_Assoc_Array_Type)); if (a == NULL) { if (has_default_value) SLdo_pop_n (1); return NULL; } memset ((char *) a, 0, sizeof (SLang_Assoc_Array_Type)); a->type = type; if (has_default_value) { if ( #if USE_NEW_ANYTYPE_CODE ((type != SLANG_ANY_TYPE) && (-1 == SLclass_typecast (type, 1, 1))) #else (-1 == SLclass_typecast (type, 1, 1)) #endif || (-1 == SLang_pop (&a->default_value))) { SLfree ((char *) a); return NULL; } a->flags |= HAS_DEFAULT_VALUE; } return a; } static void free_element (_SLAssoc_Array_Element_Type *e) { if (e == NULL) return; SLang_free_object (&e->value); SLang_free_slstring (e->key); #if USE_CACHED_STRING if (e->key == Cached_String) Cached_String = NULL; #endif SLfree ((char *)e); } static void delete_assoc_array (SLang_Assoc_Array_Type *a) { unsigned int i; if (a == NULL) return; for (i = 0; i < SLASSOC_HASH_TABLE_SIZE; i++) { _SLAssoc_Array_Element_Type *e; e = a->elements[i]; while (e != NULL) { _SLAssoc_Array_Element_Type *next_e; next_e = e->next; free_element (e); e = next_e; } } if (a->flags & HAS_DEFAULT_VALUE) SLang_free_object (&a->default_value); SLfree ((char *) a); } _INLINE_ static SLang_Object_Type * find_element (SLang_Assoc_Array_Type *a, char *str, unsigned long hash) { unsigned int h; _SLAssoc_Array_Element_Type *e; h = (unsigned int) (hash % SLASSOC_HASH_TABLE_SIZE); e = a->elements[h]; while (e != NULL) { if (str == e->key) /* slstrings can be compared this way */ { #if USE_CACHED_STRING Cached_String = str; Cached_Obj = &e->value; Cached_Array = a; #endif return &e->value; } e = e->next; } return NULL; } static _SLAssoc_Array_Element_Type * create_element (SLang_Assoc_Array_Type *a, char *str, unsigned long hash) { unsigned int h; _SLAssoc_Array_Element_Type *e; e = (_SLAssoc_Array_Element_Type *) SLmalloc (sizeof (_SLAssoc_Array_Element_Type)); if (e == NULL) return NULL; memset ((char *) e, 0, sizeof (_SLAssoc_Array_Element_Type)); h = (unsigned int) (hash % SLASSOC_HASH_TABLE_SIZE); if (NULL == (str = _SLstring_dup_hashed_string (str, hash))) { SLfree ((char *) e); return NULL; } e->key = str; e->next = a->elements[h]; a->elements[h] = e; a->num_elements += 1; #if USE_CACHED_STRING Cached_String = str; Cached_Obj = &e->value; Cached_Array = a; #endif return e; } static int store_object (SLang_Assoc_Array_Type *a, char *s, SLang_Object_Type *obj) { unsigned long hash; SLang_Object_Type *v; #if USE_CACHED_STRING if ((s == Cached_String) && (a == Cached_Array)) { v = Cached_Obj; SLang_free_object (v); } else { #endif hash = _SLcompute_string_hash (s); if (NULL != (v = find_element (a, s, hash))) SLang_free_object (v); else { _SLAssoc_Array_Element_Type *e; e = create_element (a, s, hash); if (e == NULL) return -1; v = &e->value; } #if USE_CACHED_STRING } #endif *v = *obj; return 0; } static void assoc_destroy (unsigned char type, VOID_STAR ptr) { (void) type; delete_assoc_array ((SLang_Assoc_Array_Type *) ptr); } static int pop_index (unsigned int num_indices, SLang_MMT_Type **mmt, SLang_Assoc_Array_Type **a, char **str) { if (NULL == (*mmt = SLang_pop_mmt (SLANG_ASSOC_TYPE))) { *a = NULL; *str = NULL; return -1; } if ((num_indices != 1) || (-1 == SLang_pop_slstring (str))) { SLang_verror (SL_NOT_IMPLEMENTED, "Assoc_Type arrays require a single string index"); SLang_free_mmt (*mmt); *mmt = NULL; *a = NULL; *str = NULL; return -1; } *a = (SLang_Assoc_Array_Type *) SLang_object_from_mmt (*mmt); return 0; } static int assoc_aget (unsigned char type, unsigned int num_indices) { SLang_MMT_Type *mmt; char *str; SLang_Assoc_Array_Type *a; SLang_Object_Type *obj; int ret; (void) type; if (-1 == pop_index (num_indices, &mmt, &a, &str)) return -1; #if USE_CACHED_STRING if ((str == Cached_String) && (a == Cached_Array)) obj = Cached_Obj; else #endif obj = find_element (a, str, _SLcompute_string_hash (str)); if ((obj == NULL) && (a->flags & HAS_DEFAULT_VALUE)) obj = &a->default_value; if (obj == NULL) { SLang_verror (SL_INTRINSIC_ERROR, "No such element in Assoc Array: %s", str); ret = -1; } else { #if _SLANG_OPTIMIZE_FOR_SPEED if (SLANG_CLASS_TYPE_SCALAR == _SLclass_Class_Type[obj->data_type]) ret = SLang_push (obj); #endif else ret = _SLpush_slang_obj (obj); } SLang_free_slstring (str); SLang_free_mmt (mmt); return ret; } static int assoc_aput (unsigned char type, unsigned int num_indices) { SLang_MMT_Type *mmt; char *str; SLang_Assoc_Array_Type *a; SLang_Object_Type obj; int ret; (void) type; if (-1 == pop_index (num_indices, &mmt, &a, &str)) return -1; ret = -1; if (0 == SLang_pop (&obj)) { if ((obj.data_type != a->type) #if USE_NEW_ANYTYPE_CODE && (a->type != SLANG_ANY_TYPE) #endif ) { (void) SLang_push (&obj); if ((-1 == SLclass_typecast (a->type, 1, 1)) || (-1 == SLang_pop (&obj))) goto the_return; } if (-1 == store_object (a, str, &obj)) SLang_free_object (&obj); else ret = 0; } the_return: SLang_free_slstring (str); SLang_free_mmt (mmt); return ret; } static int assoc_anew (unsigned char type, unsigned int num_dims) { SLang_MMT_Type *mmt; SLang_Assoc_Array_Type *a; int has_default_value; has_default_value = 0; switch (num_dims) { case 0: type = SLANG_ANY_TYPE; break; case 2: (void) SLreverse_stack (2); has_default_value = 1; /* drop */ case 1: if (0 == _SLang_pop_datatype (&type)) break; num_dims--; /* drop */ default: SLdo_pop_n (num_dims); SLang_verror (SL_SYNTAX_ERROR, "Usage: Assoc_Type [DataType_Type]"); return -1; } a = alloc_assoc_array (type, has_default_value); if (a == NULL) return -1; if (NULL == (mmt = SLang_create_mmt (SLANG_ASSOC_TYPE, (VOID_STAR) a))) { delete_assoc_array (a); return -1; } if (-1 == SLang_push_mmt (mmt)) { SLang_free_mmt (mmt); return -1; } return 0; } static void assoc_get_keys (SLang_Assoc_Array_Type *a) { SLang_Array_Type *at; int num; unsigned int i, j; char **data; /* Note: If support for threads is added, then we need to modify this * algorithm to prevent another thread from modifying the array. * However, that should be handled in inner_interp. */ num = a->num_elements; if (NULL == (at = SLang_create_array (SLANG_STRING_TYPE, 0, NULL, &num, 1))) return; data = (char **)at->data; i = 0; for (j = 0; j < SLASSOC_HASH_TABLE_SIZE; j++) { _SLAssoc_Array_Element_Type *e; e = a->elements[j]; while (e != NULL) { /* Next cannot fail because it is an slstring */ data [i] = SLang_create_slstring (e->key); e = e->next; i++; } } (void) SLang_push_array (at, 1); } static int transfer_element (SLang_Class_Type *cl, VOID_STAR dest_data, SLang_Object_Type *obj) { unsigned int sizeof_type; VOID_STAR src_data; #if USE_NEW_ANYTYPE_CODE if (cl->cl_data_type == SLANG_ANY_TYPE) { SLang_Any_Type *any; if ((-1 == _SLpush_slang_obj (obj)) || (-1 == SLang_pop_anytype (&any))) return -1; *(SLang_Any_Type **)dest_data = any; return 0; } #endif /* Optimize for scalar */ if (cl->cl_class_type == SLANG_CLASS_TYPE_SCALAR) { sizeof_type = cl->cl_sizeof_type; memcpy ((char *) dest_data, (char *)&obj->v, sizeof_type); return 0; } src_data = _SLclass_get_ptr_to_value (cl, obj); if (-1 == (*cl->cl_acopy) (cl->cl_data_type, src_data, dest_data)) return -1; return 0; } static void assoc_get_values (SLang_Assoc_Array_Type *a) { SLang_Array_Type *at; int num; unsigned int i, j; char *dest_data; unsigned char type; SLang_Class_Type *cl; unsigned int sizeof_type; /* Note: If support for threads is added, then we need to modify this * algorithm to prevent another thread from modifying the array. * However, that should be handled in inner_interp. */ num = a->num_elements; type = a->type; cl = _SLclass_get_class (type); sizeof_type = cl->cl_sizeof_type; if (NULL == (at = SLang_create_array (type, 0, NULL, &num, 1))) return; dest_data = (char *)at->data; i = 0; for (j = 0; j < SLASSOC_HASH_TABLE_SIZE; j++) { _SLAssoc_Array_Element_Type *e; e = a->elements[j]; while (e != NULL) { if (-1 == transfer_element (cl, (VOID_STAR) dest_data, &e->value)) { SLang_free_array (at); return; } dest_data += sizeof_type; e = e->next; i++; } } (void) SLang_push_array (at, 1); } static int assoc_key_exists (SLang_Assoc_Array_Type *a, char *key) { return (NULL != find_element (a, key, _SLcompute_string_hash (key))); } static void assoc_delete_key (SLang_Assoc_Array_Type *a, char *key) { unsigned int h; _SLAssoc_Array_Element_Type *v, *v0; h = (unsigned int) (_SLcompute_string_hash (key) % SLASSOC_HASH_TABLE_SIZE); v0 = NULL; v = a->elements[h]; while (v != NULL) { if (v->key == key) { if (v0 != NULL) v0->next = v->next; else a->elements[h] = v->next; free_element (v); a->num_elements -= 1; return; } v0 = v; v = v->next; } /* No such element. Let it pass with no error. */ } #define A SLANG_ASSOC_TYPE #define S SLANG_STRING_TYPE static SLang_Intrin_Fun_Type Assoc_Table [] = { MAKE_INTRINSIC_1("assoc_get_keys", assoc_get_keys, SLANG_VOID_TYPE, A), MAKE_INTRINSIC_1("assoc_get_values", assoc_get_values, SLANG_VOID_TYPE, A), MAKE_INTRINSIC_2("assoc_key_exists", assoc_key_exists, SLANG_INT_TYPE, A, S), MAKE_INTRINSIC_2("assoc_delete_key", assoc_delete_key, SLANG_VOID_TYPE, A, S), SLANG_END_INTRIN_FUN_TABLE }; #undef A #undef S static int assoc_length (unsigned char type, VOID_STAR v, unsigned int *len) { SLang_Assoc_Array_Type *a; (void) type; a = (SLang_Assoc_Array_Type *) SLang_object_from_mmt (*(SLang_MMT_Type **)v); *len = a->num_elements; return 0; } struct _SLang_Foreach_Context_Type { SLang_MMT_Type *mmt; SLang_Assoc_Array_Type *a; unsigned int this_hash_index; unsigned int next_same_hash_index; #define CTX_WRITE_KEYS 1 #define CTX_WRITE_VALUES 2 unsigned char flags; }; static SLang_Foreach_Context_Type * cl_foreach_open (unsigned char type, unsigned int num) { SLang_Foreach_Context_Type *c; unsigned char flags; SLang_MMT_Type *mmt; (void) type; if (NULL == (mmt = SLang_pop_mmt (SLANG_ASSOC_TYPE))) return NULL; flags = 0; while (num--) { char *s; if (-1 == SLang_pop_slstring (&s)) { SLang_free_mmt (mmt); return NULL; } if (0 == strcmp (s, "keys")) flags |= CTX_WRITE_KEYS; else if (0 == strcmp (s, "values")) flags |= CTX_WRITE_VALUES; else { SLang_verror (SL_NOT_IMPLEMENTED, "using '%s' not supported by SLassoc_Type", s); SLang_free_slstring (s); SLang_free_mmt (mmt); return NULL; } SLang_free_slstring (s); } if (NULL == (c = (SLang_Foreach_Context_Type *) SLmalloc (sizeof (SLang_Foreach_Context_Type)))) { SLang_free_mmt (mmt); return NULL; } memset ((char *) c, 0, sizeof (SLang_Foreach_Context_Type)); if (flags == 0) flags = CTX_WRITE_VALUES|CTX_WRITE_KEYS; c->flags = flags; c->mmt = mmt; c->a = (SLang_Assoc_Array_Type *) SLang_object_from_mmt (mmt); return c; } static void cl_foreach_close (unsigned char type, SLang_Foreach_Context_Type *c) { (void) type; if (c == NULL) return; SLang_free_mmt (c->mmt); SLfree ((char *) c); } static int cl_foreach (unsigned char type, SLang_Foreach_Context_Type *c) { SLang_Assoc_Array_Type *a; _SLAssoc_Array_Element_Type *e; unsigned int i, j; (void) type; if (c == NULL) return -1; a = c->a; i = c->this_hash_index; if (i >= SLASSOC_HASH_TABLE_SIZE) return 0; e = a->elements[i]; j = c->next_same_hash_index; c->next_same_hash_index = j + 1; while ((j > 0) && (e != NULL)) { j--; e = e->next; } if (e == NULL) { do { i++; if (i >= SLASSOC_HASH_TABLE_SIZE) return 0; /* no more */ } while (a->elements [i] == NULL); e = a->elements[i]; c->this_hash_index = i; c->next_same_hash_index = 1; } if ((c->flags & CTX_WRITE_KEYS) && (-1 == SLang_push_string (e->key))) return -1; if ((c->flags & CTX_WRITE_VALUES) && (-1 == _SLpush_slang_obj (&e->value))) return -1; /* keep going */ return 1; } int SLang_init_slassoc (void) { SLang_Class_Type *cl; if (SLclass_is_class_defined (SLANG_ASSOC_TYPE)) return 0; if (NULL == (cl = SLclass_allocate_class ("Assoc_Type"))) return -1; (void) SLclass_set_destroy_function (cl, assoc_destroy); (void) SLclass_set_aput_function (cl, assoc_aput); (void) SLclass_set_aget_function (cl, assoc_aget); (void) SLclass_set_anew_function (cl, assoc_anew); cl->cl_length = assoc_length; cl->cl_foreach_open = cl_foreach_open; cl->cl_foreach_close = cl_foreach_close; cl->cl_foreach = cl_foreach; if (-1 == SLclass_register_class (cl, SLANG_ASSOC_TYPE, sizeof (SLang_Assoc_Array_Type), SLANG_CLASS_TYPE_MMT)) return -1; if (-1 == SLadd_intrin_fun_table (Assoc_Table, "__SLASSOC__")) return -1; return 0; }