/**
    implementation of dynamic type system

    Copyright (c) 2020-2022 The Creators of Simphone

    See the file COPYING.LESSER.txt for copying permission.
**/

#include "logger.h"
#include "table.h"
#include "utils.h"

#include <string.h>

#define SIM_MODULE SIM_MODULE_TABLE

#ifndef SIM_HASH_PRIME
#define SIM_HASH_PRIME 16777619
#endif

#ifndef SIM_HASH_INIT
#define SIM_HASH_INIT 0x811C9DC5
#endif

#ifndef SIM_MAX_MEMORY_SIZE
#define SIM_MAX_MEMORY_SIZE 16777216
#endif

static const simbyte type_code_bytes[256] = {
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  5, 5, 5, 5, 0, 0, 0, 0, 6, 6, 6, 0, 7, 7, 7, 0, 8, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 0, 0, 10, 10
};

static const int type_code_types[] = {
  -1, SIMSTRING, SIMTABLE, SIMNIL, SIMARRAY, SIMSTRING, SIMTABLE, SIMARRAY, SIMNUMBER, SIMNUMBER, SIMNUMBER
};
static const int type_code_masks[] = { 0x000, 0x32A, 0x144, 0x3FF, 0x3FD, 0x32A, 0x144, 0x3F9, 0x11B, 0x11B, 0x11B };

struct type_context {
  simstreamer *streamer; /* callback to read or write byte stream */
  void *context;         /* callback context */
  const simtype *proto;  /* additional protocol template (only for reading) */
  const char *file;      /* name of function which called this function */
  unsigned line;         /* line number where this function was called by the other function */
  int version;           /* byte-code version number */
};

static unsigned table_hash_salt = 0;

#ifdef SIM_MEMORY_CHECK
#define SIM_FUNCTION_FIXED_ARGS(FUNCTION, ...) _##FUNCTION (__VA_ARGS__, const char *file, unsigned line)
#define SIM_FUNCTION_VAR_ARGS(FUNCTION, ...) _##FUNCTION (const char *file, unsigned line, __VA_ARGS__)
#else
#define SIM_FUNCTION_FIXED_ARGS(FUNCTION, ...) FUNCTION (__VA_ARGS__)
#define SIM_FUNCTION_VAR_ARGS(FUNCTION, ...) FUNCTION (__VA_ARGS__)
#endif

#ifndef SIM_TYPE_CHECKS
#define SIM_TYPE_CHECKS 1
#endif

#if ! SIM_TYPE_CHECKS
#undef LOG_DEBUG_
#define LOG_DEBUG_(...)
#undef LOG_DEBUG_SIMTYPE_
#define LOG_DEBUG_SIMTYPE_(...)
#undef LOG_DEBUG_SIMTYPES_
#define LOG_DEBUG_SIMTYPES_(...)
#undef LOG_ERROR_
#define LOG_ERROR_(...)
#undef LOG_ERROR_SIMTYPE_
#define LOG_ERROR_SIMTYPE_(...)
#undef LOG_ERROR_SIMTYPES_
#define LOG_ERROR_SIMTYPES_(...)
#endif

#define TABLE_GET_KEY(element) ((simtype *) (&(element)->keytype - 1))
#define TABLE_GET_VALUE(element) ((simtype *) (&(element)->valtype - 1))

static const char *sim_type_get_name (int type) {
  static const char *type_names[] = { "=NUMBER", "=STRING", "=TABLE", "=ARRAY",
                                      "=ARRAY(NUMBER)", "=ARRAY(STRING)", "=ARRAY(TABLE)", "=ARRAY(ARRAY)",
                                      "=NIL", "=POINTER", "=", "=BUFFER" };
  return type < SIMNUMBER || type > SIMBUFFER ? "" : type_names[type - SIMNUMBER];
}

static int sim_type_get_type (const simtype value) {
  return value.typ == SIMNIL ? -1 : value.typ >= SIMARRAY_NUMBER && value.typ <= SIMARRAY_ARRAY ? SIMARRAY : value.typ;
}

simtype sim_nil (void) {
  simtype val;
  val.typ = SIMNIL;
  val.ptr = NULL;
  val.len = 0;
  return val;
}

simtype sim_number_new (simnumber number) {
  simtype val;
#if SIZEOF_VOID_P != 4
  val.len = 0; /* must be zero if val.num is zero */
#endif
  val.typ = SIMNUMBER;
  val.num = number;
  return val;
}

simtype sim_pointer_new_length (const void *string, unsigned length) {
  simtype val;
  val.typ = SIMPOINTER;
  val.ptr = (void *) string;
  val.len = length;
  return val;
}

simtype sim_pointer_new (const char *string) {
  return pointer_new_len (string, strlen (string));
}

simtype SIM_FUNCTION_FIXED_ARGS (sim_string_new, unsigned length) {
  simtype val;
  val.typ = SIMSTRING;
  val.str = _sim_new ((val.len = length) + 1, SIMNIL, file, line);
  return val;
}

simtype sim_string_new_pointer (void *pointer, unsigned length) {
  simtype val;
  val.typ = SIMSTRING;
  val.str = pointer;
  val.len = length;
  return val;
}

void _sim_string_free (simtype string, const char *file, unsigned line) {
  if (string.typ == SIMSTRING) {
    _sim_free (string.ptr, string.len + 1, file, line);
  } else if (string.typ != SIMPOINTER && string.typ != SIMNIL)
    LOG_ERROR_ ("string %s:%u free type %d%s\n", file, line, string.typ, sim_type_get_name (string.typ));
}

simtype SIM_FUNCTION_FIXED_ARGS (sim_string_buffer_new, unsigned length) {
  simtype val;
  val.typ = SIMBUFFER;
  val.ptr = _sim_new (val.len = length, SIMNIL, file, line);
  return val;
}

void _sim_string_buffer_free (simtype buffer, const char *file, unsigned line) {
  if (buffer.typ == SIMBUFFER) {
    _sim_free (buffer.ptr, buffer.len, file, line);
  } else if (buffer.typ != SIMNIL)
    LOG_ERROR_ ("buffer %s:%u free type %d%s\n", file, line, buffer.typ, sim_type_get_name (buffer.typ));
}

void _sim_string_buffer_append (simtype *buffer, unsigned oldlen, unsigned addlen, const char *file, unsigned line) {
  if (addlen + oldlen > buffer->len) {
    simtype oldbuf = *buffer;
    *buffer = _sim_string_buffer_new (oldbuf.len + (oldbuf.len > addlen ? oldbuf.len : addlen), file, line);
    memcpy (buffer->str, oldbuf.str, oldlen);
    _sim_string_buffer_free (oldbuf, file, line);
  }
}

simtype _sim_string_buffer_truncate (simtype buffer, unsigned length, const char *file, unsigned line) {
  if (buffer.typ == SIMBUFFER) {
    if (length > buffer.len)
      LOG_ERROR_ ("buffer %s:%u truncate length %u > %u\n", file, line, length, buffer.len);
    if (length + 1 != buffer.len) {
      simtype val = _sim_string_copy_length (buffer.str, length, file, line);
      _sim_free (buffer.ptr, buffer.len, file, line);
      return val;
    }
    buffer.str[buffer.len = length] = 0;
    buffer.typ = SIMSTRING;
  } else
    LOG_ERROR_ ("buffer %s:%u truncate type %d%s\n", file, line, buffer.typ, sim_type_get_name (buffer.typ));
  return buffer;
}

simtype SIM_FUNCTION_FIXED_ARGS (sim_string_copy, const char *string) {
  simtype val = _sim_string_new (strlen (string), file, line);
  memcpy (val.str, string, val.len + 1);
  return val;
}

simtype SIM_FUNCTION_FIXED_ARGS (sim_string_copy_length, const void *string, unsigned length) {
  simtype val = _sim_string_new (length, file, line);
  memcpy (val.str, string, length);
  val.str[length] = 0;
  return val;
}

simtype _sim_string_copy_string (const simtype string, const char *file, unsigned line) {
  simtype val;
#if SIM_TYPE_CHECKS
  if (string.typ != SIMSTRING && string.typ != SIMPOINTER) {
    LOG_ERROR_ ("string %s:%u type %d%s\n", file, line, string.typ, sim_type_get_name (string.typ));
    return nil ();
  }
#endif
  val = _sim_string_new (string.len, file, line);
  memcpy (val.str, string.str, string.len);
  val.str[string.len] = 0;
  return val;
}

simtype SIM_FUNCTION_FIXED_ARGS (sim_string_cat, const char *string1, const char *string2) {
  unsigned len1 = strlen (string1), len2 = strlen (string2);
  simtype val = _sim_string_new (len1 + len2, file, line);
  memcpy (val.str, string1, len1);
  memcpy (val.str + len1, string2, len2 + 1);
  return val;
}

simtype SIM_FUNCTION_FIXED_ARGS (sim_string_cat_length,
                                 const char *string1, unsigned length1, const char *string2, unsigned length2) {
  simtype val = _sim_string_new (length1 + length2, file, line);
  memcpy (val.str, string1, length1);
  memcpy (val.str + length1, string2, length2);
  val.str[length1 + length2] = 0;
  return val;
}

simtype SIM_FUNCTION_VAR_ARGS (sim_string_concat, const char *string, ...) {
  unsigned len = 0;
  simtype val;
  const char *arg = string;
  va_list args;
  if (! arg)
    return nil ();
  va_start (args, string);
  do {
    len += strlen (arg);
  } while ((arg = va_arg (args, const char *)) != NULL);
  va_end (args);
  va_start (args, string);
  val = _sim_string_new (len, file, line);
  len = 0;
  arg = string;
  do {
    unsigned l = strlen (arg);
    memcpy (&val.str[len], arg, l);
    len += l;
  } while ((arg = va_arg (args, const char *)) != NULL);
  val.str[val.len] = 0;
  va_end (args);
  return val;
}

simbool sim_string_check_diff_length (const simtype string1, const void *string2, unsigned length) {
  return string1.len != length || memcmp (string1.str, string2, length);
}

simbool sim_string_check_diff (const simtype string1, const char *string2) {
  return string_check_diff_len (string1, string2, strlen (string2));
}

static simbool _sim_table_check_diff (const simtype table1, const simtype table2, const char *file, unsigned line) {
  simtype key, val;
  simwalker ctx;
  for (_sim_table_walk_first (&ctx, table1, file, line); (val = table_walk_next (&ctx, &key)).typ != SIMNIL;)
    if (_sim_type_check_diff (_sim_table_get_key (table2, key, file, line), val, file, line))
      return true;
  return false;
}

simbool _sim_type_check_diff (const simtype value1, const simtype value2, const char *file, unsigned line) {
  unsigned i;
  int type = value1.typ == SIMPOINTER ? SIMSTRING : value1.typ;
  if ((value2.typ == SIMPOINTER ? SIMSTRING : value2.typ) != type)
    return true;
  switch (type) {
    case SIMNUMBER:
      return value1.num != value2.num;
    case SIMSTRING:
      return string_check_diff_len (value1, value2.str, value2.len);
    case SIMTABLE:
      return _sim_table_check_diff (value1, value2, file, line) || _sim_table_check_diff (value2, value1, file, line);
    case SIMARRAY_NUMBER:
    case SIMARRAY_STRING:
    case SIMARRAY_TABLE:
    case SIMARRAY_ARRAY:
      if (value1.len != value2.len)
        return true;
      for (i = 1; i <= value1.len; i++)
        if (_sim_type_check_diff (value1.arr[i], value2.arr[i], file, line))
          return true;
  }
  return false;
}

static simtype _sim_array_create (unsigned length, int type, const char *file, unsigned line) {
  simtype val;
#if SIM_TYPE_CHECKS
  if (length >= (unsigned) -1 / sizeof (*val.arr)) {
    LOG_ERROR_ ("array %s:%u length = %u\n", file, line, length);
    return nil ();
  }
#endif
  val.typ = type | SIMARRAY_NUMBER;
  val.ptr = _sim_new ((length + 1) * sizeof (*val.arr), SIMARRAY_ARRAY, file, line);
  val.arr[0].typ /* array_get_line (val) */ = line;
  array_get_name (val) = (char *) file;
  sim_array_size (val) = val.len = length;
  return val;
}

simtype _sim_array_new (unsigned length, int type, const char *file, unsigned line) {
#if SIM_TYPE_CHECKS
  simtype val, *ptr;
  if (type < SIMNUMBER || type > SIMARRAY || (val = _sim_array_create (length, type, file, line)).typ == SIMNIL) {
    LOG_ERROR_ ("array %s:%u new type %d%s\n", file, line, type, sim_type_get_name (type));
    return nil ();
  }
#else
  simtype val = _sim_array_create (length, type, file, line), *ptr;
#endif
  ptr = val.ptr;
  while (length--)
    *++ptr = nil ();
  return val;
}

simtype _sim_array_new_type (simtype value, unsigned length, const char *file, unsigned line) {
  simtype array = _sim_array_new (length, value.typ == SIMPOINTER ? SIMSTRING : sim_type_get_type (value), file, line);
  array.arr[1] = value;
  return array;
}

void _sim_array_free (simtype array, const char *file, unsigned line) {
  int type;
  if (array.typ != SIMNIL && (type = _sim_array_get_type (array, file, line)) != SIMNIL) {
    unsigned len = array.len;
    simtype *ptr = array.arr;
    while (len--) {
      _sim_type_free (*++ptr, file, line);
      if (ptr->typ != SIMNIL)
        if (type != SIMARRAY ? ptr->typ != type && (type != SIMSTRING || ptr->typ != SIMPOINTER) :
                               ptr->typ < SIMARRAY_NUMBER || ptr->typ > SIMARRAY_ARRAY)
          LOG_ERROR_ ("array %s:%u free [%d] type %d%s (wanted %d%s)\n", file, line, (int) (ptr - array.arr),
                      ptr->typ, sim_type_get_name (ptr->typ), type, sim_type_get_name (type));
    }
    _sim_free (array.arr, (sim_array_size (array) + 1) * sizeof (*array.arr), file, line);
  }
}

int _sim_array_get_type (const simtype array, const char *file, unsigned line) {
#if SIM_TYPE_CHECKS
  int type = array.typ;
  if (type == SIMARRAY_NUMBER || type == SIMARRAY_STRING || type == SIMARRAY_TABLE || type == SIMARRAY_ARRAY)
    return type & ~SIMARRAY_NUMBER;
  LOG_ERROR_ ("array %s:%u type %d%s\n", file, line, type, sim_type_get_name (type));
  return SIMNIL;
#else
  return array.typ & ~SIMARRAY_NUMBER;
#endif
}

void _sim_array_append (simtype *array, simtype value, const char *file, unsigned line) {
  const unsigned count = 1;
  simtype oldarray = *array;
  unsigned len = oldarray.len;
  int type = _sim_array_get_type (oldarray, file, line);
#if SIM_TYPE_CHECKS
  if (type == SIMNIL || count <= 0)
    return;
#endif
  if (len + count > sim_array_size (oldarray)) {
#if ! SIM_TYPE_CHECKS
    if (len + (len > count ? len : count) >= (unsigned) -1 / sizeof (*oldarray.arr)) {
      *array = nil ();
    } else
#endif
      *array = _sim_array_create (len + (len > count ? len : count), type, file, line);
    if (array->typ == SIMNIL)
      return; /* bad idea to free oldarray here: if it was triggered by the logger it will go recursive and crash */
    memcpy (&array->arr[1], &oldarray.arr[1], sizeof (*oldarray.arr) * len);
    _sim_free (oldarray.arr, (sim_array_size (oldarray) + 1) * sizeof (*oldarray.arr), file, line);
  }
  array->arr[++len] = value;
  array->len = len;
#if SIM_TYPE_CHECKS
  if (type != SIMARRAY ? value.typ != type && (type != SIMSTRING || value.typ != SIMPOINTER) :
                         value.typ < SIMARRAY_NUMBER || value.typ > SIMARRAY_ARRAY)
    LOG_ERROR_ ("array %s:%u append type %d%s (wanted %d%s)\n",
                file, line, value.typ, sim_type_get_name (value.typ), type, sim_type_get_name (type));
#endif
}

void _sim_array_delete (simtype *array, unsigned idx, unsigned count, const char *file, unsigned line) {
  int n = count;
#if SIM_TYPE_CHECKS
  int type = _sim_array_get_type (*array, file, line);
  if (type == SIMNIL || (int) count <= 0 || (int) idx <= 0 || idx + count - 1 > array->len) {
    LOG_ERROR_ ("array %s:%u delete type %d%s index = %d count = %d length = %d\n",
                file, line, type, sim_type_get_name (type), idx, count, array->len);
    return;
  }
#endif
  do {
    _sim_type_free (array->arr[idx++], file, line);
  } while (--n);
  while (idx <= array->len) {
    array->arr[idx - count] = array->arr[idx];
    idx++;
  }
  array->len -= count;
}

simtype _sim_array_copy (const simtype array, simbool strings, const char *file, unsigned line) {
  unsigned i = 0;
#if SIM_TYPE_CHECKS
  int type = _sim_array_get_type (array, file, line);
  simtype newarray;
  if (type == SIMNIL || (newarray = _sim_array_create (array.len, type, file, line)).typ == SIMNIL)
    return nil ();
#else
  simtype newarray = _sim_array_create (array.len, _sim_array_get_type (array, file, line), file, line);
#endif
  while (i++ < array.len)
    newarray.arr[i] = _sim_type_copy (array.arr[i], strings, file, line);
  return newarray;
}

simtype _sim_type_copy (const simtype value, simbool strings, const char *file, unsigned line) {
  switch (value.typ) {
    case SIMPOINTER:
      if (! strings)
        return *(simtype *) &value;
    case SIMSTRING:
      return _sim_string_copy_length (value.str, value.len, file, line);
    case SIMTABLE:
      return _sim_table_copy (value, _sim_table_count (value, file, line), strings, file, line);
    case SIMARRAY_NUMBER:
    case SIMARRAY_STRING:
    case SIMARRAY_TABLE:
    case SIMARRAY_ARRAY:
      return _sim_array_copy (value, strings, file, line);
    case SIMNUMBER:
      return *(simtype *) &value;
    default:
      return nil ();
  }
}

simtype _sim_table_copy (const simtype table, unsigned length, simbool strings, const char *file, unsigned line) {
  simtype key, val, newtable = nil ();
  simwalker ctx;
  if (_sim_table_walk_first (&ctx, table, file, line)) {
    newtable = _sim_table_new (length, table_get_table_type (table) & (SIMTABLE_CONST | SIMTABLE_RAND), file, line);
    while ((val = table_walk_next (&ctx, &key)).typ != SIMNIL)
      _sim_table_add_key (newtable, _sim_string_copy_length (key.str, key.len, file, line),
                          _sim_type_copy (val, strings, file, line), file, line);
  }
  return newtable;
}

simtype _sim_table_new (unsigned length, int type, const char *file, unsigned line) {
  simtype val;
#if SIM_TYPE_CHECKS
  if (length >= (unsigned) -1 / sizeof (*val.tbl)) {
    LOG_ERROR_ ("table %s:%u new length = %u\n", file, line, length);
    return nil ();
  }
#endif
  if (! length)
    length = 1;
  val.typ = SIMTABLE;
  val.ptr = _sim_new ((length + 1) * sizeof (*val.tbl), SIMTABLE, file, line);
  memset (val.ptr, 0, ((val.len = length) + 1) * sizeof (*val.tbl));
#if SIM_TYPE_CHECKS
  val.tbl->keylen = length;
#endif
  if (! (type & SIMTABLE_RAND)) {
    val.tbl->keytype = SIM_HASH_INIT;
  } else if ((val.tbl->keytype = table_hash_salt) == 0)
    LOG_ERROR_ ("table %s:%u non-random\n", file, line);
  table_get_table_type (val) = type;
  table_get_name (val) = (char *) file, table_get_line (val) = line;
  return val;
}

simtype _sim_table_new_const (unsigned length, int type, const char *file, unsigned line, const char *key, ...) {
  va_list args;
  simtype table = _sim_table_new (length, type, file, line), val;
#if SIM_TYPE_CHECKS
  if (table.typ == SIMNIL)
    return number_new (0);
#endif
  for (va_start (args, key); key; key = va_arg (args, const char *)) {
    int oldtype = _sim_table_add_key (table, pointer_new (key), val = va_arg (args, simtype), file, line).typ;
    if (oldtype != SIMNIL && oldtype != val.typ)
      LOG_ERROR_ ("add %s:%u key %s type %d%s (wanted %d%s)\n", file, line,
                  key, val.typ, sim_type_get_name (val.typ), oldtype, sim_type_get_name (oldtype));
  }
  va_end (args);
  return table;
}

#ifdef SIM_MEMORY_CHECK
#define TABLE_FREE_ELEMENT(element, file, line) _sim_free (element, sizeof (*(element)), NULL, 0)
#else
#define TABLE_FREE_ELEMENT(element, file, line) _sim_free (element, sizeof (*(element)), file, line)
#endif

void _sim_table_free (simtype table, const char *file, unsigned line) {
  unsigned len;
  struct _simelement *heads;
  if (table.typ == SIMTABLE) {
    heads = table.ptr;
    len = table.len;
    while (len--) {
      struct _simelement *chain = ++heads;
      if (chain->keyptr) {
        struct _simelement *next = chain->next;
        while (next) {
          struct _simelement *tmp = next;
          if (table_get_table_type (table) & SIMTABLE_CONST)
            LOG_ERROR_SIMTYPES_ (*TABLE_GET_KEY (chain), *TABLE_GET_KEY (next), 0, "collision %s:%u %s:%u ",
                                 file, line, (char *) table_get_name (table), table_get_line (table));
          _sim_string_free (*TABLE_GET_KEY (next), file, line);
          _sim_type_free (*TABLE_GET_VALUE (next), file, line);
          next = next->next;
          TABLE_FREE_ELEMENT (tmp, file, line);
        }
        _sim_string_free (*TABLE_GET_KEY (chain), file, line);
        _sim_type_free (*TABLE_GET_VALUE (chain), file, line);
      }
    }
#if SIM_TYPE_CHECKS
    if (table.tbl->keylen != table.len)
      LOG_ERROR_ ("table %s:%u %s:%u length %d (wanted %d)\n", file, line,
                  (char *) table_get_name (table), table_get_line (table), table.len, table.tbl->keylen);
#endif
    _sim_free (table.tbl, (table.len + 1) * sizeof (*table.tbl), file, line);
  } else if (table.typ != SIMNIL)
    LOG_ERROR_ ("free %s:%u type %d%s\n", file, line, table.typ, sim_type_get_name (table.typ));
}

unsigned _sim_table_count (const simtype table, const char *file, unsigned line) {
  unsigned count = 0;
#if SIM_TYPE_CHECKS
  if (table.typ != SIMTABLE) {
    LOG_ERROR_ ("count %s:%u type %d%s\n", file, line, table.typ, sim_type_get_name (table.typ));
  } else
#endif
  {
    unsigned len = table.len;
    struct _simelement *heads = table.ptr;
    while (len--) {
      struct _simelement *chain = ++heads;
      if (chain->keyptr)
        while (++count, (chain = chain->next) != NULL) {}
    }
  }
  return count;
}

unsigned table_init_hash (unsigned salt) {
  return (table_hash_salt = salt) == SIM_HASH_INIT ? 0 : salt;
}

unsigned sim_table_hash (const void *pointer, unsigned length, unsigned salt) {
  const simbyte *str = pointer;
  while (length--)
    salt = (salt ^ *str++) * SIM_HASH_PRIME;
  return salt;
}

static struct _simelement *_sim_table_hash (const simtype table, const simtype key, const char *file, unsigned line) {
#if SIM_TYPE_CHECKS
  if (table.typ != SIMTABLE) {
#if SIM_LOG_LEVEL <= SIM_LOG_ERROR
    log_error_ (! line || log_recursed ? NULL : SIM_MODULE, "hash %s:%u type %d%s\n",
                file, line, table.typ, sim_type_get_name (table.typ));
#endif
  } else if (key.typ == SIMSTRING || key.typ == SIMPOINTER) {
    if (table.len)
      return table.tbl + sim_table_hash (key.str, key.len, table.tbl->keytype) % table.len + 1;
#if SIM_LOG_LEVEL <= SIM_LOG_ERROR
    log_error_ (! line || log_recursed ? NULL : SIM_MODULE, "hash %s:%u %s:%u table = NULL\n",
                file, line, (char *) table_get_name (table), table_get_line (table));
#endif
  } else {
#if SIM_LOG_LEVEL <= SIM_LOG_ERROR
    log_error_ (! line || log_recursed ? NULL : SIM_MODULE, "hash %s:%u %s:%u type %d%s\n", file, line,
                (char *) table_get_name (table), table_get_line (table), key.typ, sim_type_get_name (key.typ));
#endif
  }
  return NULL;
#else
  return table.tbl + sim_table_hash (key.str, key.len, table.tbl->keytype) % table.len + 1;
#endif
}

simtype _sim_table_get_key (const simtype table, const simtype key, const char *file, unsigned line) {
  struct _simelement *chain = _sim_table_hash (table, key, file, line);
  if (chain && chain->keyptr)
    do {
      if (! string_check_diff_len (*TABLE_GET_KEY (chain), key.str, key.len))
        return *TABLE_GET_VALUE (chain);
      chain = chain->next;
    } while (chain);
  return nil ();
}

#if SIM_TYPE_CHECKS
simtype _sim_table_get_key_type (const simtype table, const simtype key, int type, const char *file, unsigned line) {
  simtype val = _sim_table_get_key (table, key, file, line);
  if (val.typ == SIMNIL || val.typ == type || (val.typ == SIMPOINTER && type == SIMSTRING))
    return val;
#if SIM_LOG_LEVEL <= SIM_LOG_ERROR
  log_simtype_ (log_recursed ? NULL : SIM_MODULE, SIM_LOG_ERROR, key, 0,
                "get %s:%u %s:%u type %d%s (wanted %d%s) ", file, line, (char *) table_get_name (table),
                table_get_line (table), val.typ, sim_type_get_name (val.typ), type, sim_type_get_name (type));
#endif
  return nil ();
}
#endif

static struct _simelement *_sim_table_put_key (simtype table, simtype key, simtype value,
                                               const char *file, unsigned line) {
  struct _simelement *chain = _sim_table_hash (table, key, file, line), *last = NULL;
  if (chain) {
#if SIM_TYPE_CHECKS
    if (value.typ == SIMNIL) {
      LOG_ERROR_SIMTYPE_ (key, 0, "set %s:%u %s:%u nil ",
                          file, line, (char *) table_get_name (table), table_get_line (table));
    } else
#endif
    {
      if (chain->keyptr)
        do {
          if (! string_check_diff_len (*TABLE_GET_KEY (chain), key.str, key.len))
            return chain;
          chain = (last = chain)->next;
        } while (chain);
      if (last)
        last->next = chain = _sim_new (sizeof (*chain), SIMNIL, NULL, 0);
      chain->next = NULL;
      chain->keytype = key.typ, chain->keyptr = key.ptr, chain->keylen = key.len;
      chain->valtype = value.typ, chain->valptr = value.ptr, chain->vallen = value.len;
    }
  }
  return NULL;
}

simtype _sim_table_set_key (simtype table, simtype key, simtype value, const char *file, unsigned line) {
  simtype old;
  struct _simelement *chain = _sim_table_put_key (table, key, value, file, line);
  if (! chain)
    return nil ();
  _sim_string_free (key, file, line);
  _sim_type_free (old = *TABLE_GET_VALUE (chain), file, line);
  chain->valtype = value.typ, chain->valptr = value.ptr, chain->vallen = value.len;
  return old;
}

simtype _sim_table_add_key (simtype table, simtype key, simtype value, const char *file, unsigned line) {
  struct _simelement *chain = _sim_table_put_key (table, key, value, file, line);
  if (! chain)
    return nil ();
  _sim_string_free (key, file, line);
  _sim_type_free (value, file, line);
  return *TABLE_GET_VALUE (chain);
}

simtype _sim_table_delete_key (simtype table, const simtype key, const char *file, unsigned line) {
  simtype old = _sim_table_detach_key (table, key, file, line);
  _sim_type_free (old, file, line);
  return old;
}

simtype _sim_table_detach_key (simtype table, const simtype key, const char *file, unsigned line) {
  struct _simelement *chain = _sim_table_hash (table, key, file, line), *last = NULL;
  if (chain && chain->keyptr)
    do {
      if (! string_check_diff_len (*TABLE_GET_KEY (chain), key.str, key.len)) {
        simtype oldval = *TABLE_GET_VALUE (chain);
        _sim_string_free (*TABLE_GET_KEY (chain), file, line);
        if (last) {
          last->next = chain->next;
          TABLE_FREE_ELEMENT (chain, file, line);
        } else {
          struct _simelement *old = chain->next;
          if (old) {
            *chain = *old;
            TABLE_FREE_ELEMENT (old, file, line);
          } else
            chain->keyptr = NULL;
        }
        return oldval;
      }
      chain = (last = chain)->next;
    } while (chain);
  return nil ();
}

#if SIM_TYPE_CHECKS
simtype _sim_table_detach_key_type (simtype table, const simtype key, int type, const char *file, unsigned line) {
  simtype val = _sim_table_detach_key (table, key, file, line);
  if (val.typ == SIMNIL || val.typ == type || (val.typ == SIMPOINTER && type == SIMSTRING))
    return val;
  LOG_ERROR_SIMTYPE_ (key, 0, "del %s:%u %s:%u type %d%s (wanted %d%s) ", file, line, (char *) table_get_name (table),
                      table_get_line (table), val.typ, sim_type_get_name (val.typ), type, sim_type_get_name (type));
  _sim_type_free (val, file, line);
  return nil ();
}
#endif

simtype _sim_array_detach (simtype array, unsigned idx, const char *file, unsigned line) {
  simtype old;
#if SIM_TYPE_CHECKS
  int type = _sim_array_get_type (array, file, line);
  if (type == SIMNIL || (int) idx <= 0 || idx > array.len) {
    LOG_ERROR_ ("array %s:%u detach type %d%s index = %d length = %d\n",
                file, line, type, sim_type_get_name (type), idx, array.len);
    return nil ();
  }
#endif
  old = array.arr[idx];
  array.arr[idx] = nil ();
  return old;
}

void _sim_array_detach_end (simtype *array, const char *file, unsigned line) {
  int type = _sim_array_get_type (*array, file, line);
  unsigned len = array->len, i = 0;
  if (type != SIMNIL)
    while (++i <= len)
      if (array->arr[i].typ == SIMNIL) {
        unsigned j = i;
        while (++i <= len)
          if (array->arr[i].typ != SIMNIL)
            array->arr[j++] = array->arr[i];
        array->len = j - 1;
        break;
      }
}

simbool _sim_table_walk_first (simwalker *context, const simtype table, const char *file, unsigned line) {
#if SIM_TYPE_CHECKS
  if (table.typ != SIMTABLE) {
    LOG_ERROR_ ("first %s:%u type %d%s\n", file, line, table.typ, sim_type_get_name (table.typ));
    table_walk_last (context);
    return false;
  }
#endif
  context->chain = table.tbl;
  context->next = NULL;
  context->first = table.tbl; /* only for name logging */
  context->length = table.len;
  return true;
}

simtype sim_table_walk_next (simwalker *context, simtype *key) {
  struct _simelement *chain = context->next;
  if (! chain)
    do {
      if (! context->length)
        return key ? (*key = nil ()) : nil ();
      context->length--;
      chain = ++context->chain;
    } while (! chain->keyptr);
  if (key)
    *key = *TABLE_GET_KEY (chain);
  context->next = chain->next;
  return *TABLE_GET_VALUE (chain);
}

#if SIM_TYPE_CHECKS
simtype _sim_table_walk_next_type (simwalker *context, simtype *key, int type, const char *file, unsigned line) {
  simtype val = table_walk_next (context, key);
  if (val.typ == SIMNIL || val.typ == type || (val.typ == SIMPOINTER && type == SIMSTRING))
    return val;
  LOG_ERROR_SIMTYPE_ (key ? *key : nil (), 0, "next %s:%u %s:%u type %d%s (wanted %d%s) ", file, line,
                      (char *) table_get_name_elements (context->first), table_get_line_elements (context->first),
                      val.typ, sim_type_get_name (val.typ), type, sim_type_get_name (type));
  return nil ();
}
#endif

void sim_table_walk_last (simwalker *context) {
  context->length = 0;
  context->chain = NULL;
  context->next = NULL;
}

static simtype sim_type_read (const struct type_context *context, const simtype *proto, const simtype *name);

static simbool sim_type_read_number (const struct type_context *context,
                                     int code, int base, int codes, simnumber *number) {
  simbyte buffer[sizeof (*number)], *buf = buffer;
  unsigned len = code - base;
  *number = 0;
  if (! context->streamer (context->context, pointer_new_len (buffer, len)))
    return false;
  if (len == 1 ? *buffer < codes : ! *buffer) {
    LOG_DEBUG_ ("invalid %s:%u code %02X:%02X\n", context->file, context->line, code, *buffer);
    return false;
  }
  do {
    *number = *number << 8 | *buf++;
  } while (--len);
  return true;
}

static simtype sim_type_read_numbers (simnumber number, int prototype) {
  if (prototype == SIMARRAY_NUMBER) {
    simtype array = _sim_array_create (1, SIMNUMBER, __FUNCTION__, __LINE__);
    array.arr[1] = number_new (number);
    return array;
  }
  return number_new (number);
}

static simtype sim_type_read_string (const struct type_context *context, unsigned length, int prototype) {
  if (prototype != SIMARRAY && prototype != SIMSTRING) {
    simtype val = _sim_string_new (length, context->file, context->line);
    if (! length || context->streamer (context->context, val)) {
      val.str[length] = 0;
      return prototype != SIMARRAY_STRING ? val : _sim_array_new_type (val, 1, context->file, context->line);
    }
    _sim_free (val.str, length + 1, context->file, context->line);
  } else if (! length || context->streamer (context->context, pointer_new_len (NULL, length)))
    return pointer_new ("");
  return nil ();
}

#define TABLE_CASE_SIZE(proto, length) ((proto)->typ != SIMNIL && (length) > (proto)->len ? (proto)->len : (length))
#define TABLE_CHECK_TYPE(proto) ((proto).typ == SIMNUMBER || (proto).typ == SIMPOINTER)
#define TABLE_GET_TYPE(proto) \
  ((proto)->typ != SIMNIL ? table_get_table_type (*(proto)) & SIMTABLE_LONG : SIMTABLE_NORMAL)

static simtype sim_type_read_table (const struct type_context *context, const simtype *proto, unsigned length,
                                    const simtype *name) {
  simtype table, key, val;
  table = _sim_table_new (TABLE_CASE_SIZE (proto, length), TABLE_GET_TYPE (proto), context->file, context->line);
  if (table.typ != SIMNIL)
    while (length--) {
      key = nil ();
      val = pointer_new ("");
      if ((key = sim_type_read (context, (const simtype *) &val, &key)).typ == SIMNIL) {
        LOG_DEBUG_SIMTYPE_ (*name, 0, "table %s:%u key ", context->file, context->line);
        _sim_table_free (table, context->file, context->line);
        return nil ();
      }
      if (proto->typ != SIMNIL && (val = table_get_key (*proto, key)).typ == SIMNIL) {
        LOG_XTRA_SIMTYPES_ (*name, key, 0, "table %s:%u key ", context->file, context->line);
        val.typ = SIMARRAY;
        val = sim_type_read (context, (const simtype *) &val, &key);
        if (val.typ != SIMNUMBER && val.typ != SIMPOINTER) {
          LOG_DEBUG_SIMTYPES_ (*name, key, 0, "table %s:%u type %d%s ",
                               context->file, context->line, val.typ, sim_type_get_name (val.typ));
          _sim_string_free (key, context->file, context->line);
          _sim_table_free (table, context->file, context->line);
          return nil ();
        }
        _sim_string_free (key, context->file, context->line);
      } else {
        if (proto->typ != SIMNIL && TABLE_CHECK_TYPE (val)) {
          simtype array = val.typ == SIMNUMBER ? number_new (0) : pointer_new ("");
          val = _sim_array_new_type (array, 1, context->file, context->line);
          array = sim_type_read (context, (const simtype *) &val, &key);
          _sim_array_free (val, context->file, context->line);
          val = array.typ != SIMNIL ? _sim_array_detach (array, 1, context->file, context->line) : nil ();
          _sim_array_free (array, context->file, context->line);
        } else
          val = sim_type_read (context, proto->typ == SIMNIL ? proto : (const simtype *) &val, &key);
        if (val.typ == SIMNIL) {
          LOG_DEBUG_SIMTYPES_ (*name, key, 0, "table %s:%u value ", context->file, context->line);
          _sim_string_free (key, context->file, context->line);
          _sim_table_free (table, context->file, context->line);
          return nil ();
        }
        if ((key = _sim_table_add_key (table, key, val, context->file, context->line)).typ != SIMNIL) {
          LOG_DEBUG_SIMTYPES_ (*name, key, 0, "table %s:%u duplicate ", context->file, context->line);
          _sim_table_free (table, context->file, context->line);
          return nil ();
        }
      }
    }
  return table;
}

static simtype sim_type_read_empty (const struct type_context *context, int prototype) {
  simbyte t;
  if (! context->streamer (context->context, pointer_new_len (&t, 1)))
    return nil ();
  if (t <= SIMARRAY_ARRAY && t >= SIMARRAY_NUMBER)
    if (prototype == SIMARRAY ? t == SIMARRAY_STRING || t == SIMARRAY_NUMBER : prototype == SIMNIL || prototype == t) {
      if (prototype == SIMARRAY || prototype == SIMSTRING)
        return pointer_new ("");
      return _sim_array_create (0, t, context->file, context->line);
    }
  LOG_DEBUG_ ("invalid %s:%u type %d%s (wanted %d%s)\n", context->file, context->line,
              t, sim_type_get_name (t), prototype, sim_type_get_name (prototype));
  return nil ();
}

static simtype sim_type_read_array (const struct type_context *context, const simtype *proto, unsigned length,
                                    const simtype *name) {
  int type, newtype;
  simtype array = *(simtype *) proto, key = number_new (1), val = nil ();
  if (array.typ == SIMARRAY)
    array.typ = SIMSTRING;
  if ((proto->typ == SIMARRAY || array.typ == SIMNIL || (array.len && (array = proto->arr[1]).typ != SIMNIL)))
    if ((type = sim_type_get_type ((val = sim_type_read (context, (const simtype *) &array, &key)))) >= 0) {
      if (proto->typ != SIMARRAY) {
        array = _sim_array_create (TABLE_CASE_SIZE (proto, length), type, context->file, context->line);
        if (array.typ == SIMNIL) {
          _sim_type_free (val, context->file, context->line);
          return array;
        }
      }
      if (proto->typ == SIMARRAY) {
        if (val.typ != SIMNUMBER && val.typ != SIMPOINTER) {
          newtype = 10;
          goto leak;
        }
        array = pointer_new ("");
      } else {
        if (proto->typ == SIMARRAY_ARRAY && (val.typ != SIMARRAY_NUMBER && val.typ != SIMARRAY_STRING)) {
          newtype = 10;
          goto leak;
        }
        array.arr[1] = val;
      }
      while (++key.num <= length) {
        if (proto->typ == SIMARRAY_ARRAY) {
          if (key.num > array.len) {
            val = nil ();
            val.typ = SIMARRAY;
            if ((val = sim_type_read (context, (const simtype *) &val, &key)).typ != SIMNIL)
              continue;
          } else
            array.arr[key.num] = val = sim_type_read (context, (const simtype *) &proto->arr[key.num], &key);
        } else if (proto->typ != SIMARRAY && (key.num <= array.len || ! TABLE_CHECK_TYPE (proto->arr[1]))) {
          val = sim_type_read (context, proto->typ == SIMNIL ? proto : (const simtype *) &proto->arr[1], &key);
          if (key.num > array.len) {
            _sim_type_free (val, context->file, context->line);
          } else
            array.arr[key.num] = val;
        } else { /* key.num > array.len implies proto->typ != SIMNIL because array.len == length if proto->typ == SIMNIL */
          val = *(simtype *) proto;
          val.typ = SIMSTRING;
          if ((val = sim_type_read (context, (const simtype *) &val, &key)).typ == SIMPOINTER && proto->typ != SIMARRAY)
            val.typ = SIMSTRING; /* so that the following type check will pass */
        }
        if ((newtype = sim_type_get_type (val)) != type) {
        leak:
          LOG_DEBUG_SIMTYPE_ (*name, 0, "array %s:%u [%lld] type %d%s (wanted %d%s) ", context->file, context->line,
                              key.num, newtype, sim_type_get_name (newtype), type, sim_type_get_name (type));
          if (proto->typ != SIMARRAY) {
            if (key.num < array.len)
              array.len = (unsigned) key.num;
            _sim_array_free (array, context->file, context->line);
          }
          return nil ();
        }
      }
      return array;
    }
  LOG_DEBUG_SIMTYPE_ (*name, 0, "array %s:%u [1] type %d%s (wanted %d%s) ", context->file, context->line,
                      val.typ, sim_type_get_name (val.typ), array.typ, sim_type_get_name (array.typ));
  return nil ();
}

static simtype sim_type_read (const struct type_context *context, const simtype *proto, const simtype *name) {
  simbyte code, typecode;
  if (context->streamer (context->context, pointer_new_len (&code, 1))) {
    simnumber len;
    if (type_code_masks[typecode = type_code_bytes[code]] & (1 << proto->typ))
      switch (typecode) {
        case 1:
          return sim_type_read_string (context, code - 0x40, proto->typ);
        case 2:
          return sim_type_read_table (context, proto, code - 0x80, name);
        case 3:
          return sim_type_read_empty (context, proto->typ);
        case 4:
          if (proto->typ == SIMTABLE && ((proto = context->proto) == NULL || name->typ != SIMNUMBER || name->num))
            break;
          return sim_type_read_array (context, proto, code - 0xC0, name);
        case 5:
          if (sim_type_read_number (context, code, 0xDF, 0x20, &len)) {
#if SIM_MAX_MEMORY_SIZE
            if ((simunsigned) len < SIM_MAX_MEMORY_SIZE * 32)
#endif
              if (proto->typ == SIMNIL || (simunsigned) len <= SIM_MAX_PACKET_SIZE)
                return sim_type_read_string (context, (unsigned) len, proto->typ);
            LOG_DEBUG_SIMTYPE_ (*name, 0, "new %s:%u length = %llu ", context->file, context->line, len);
          }
          return nil ();
        case 6:
          if (sim_type_read_number (context, code, 0xE7, 0x10, &len)) {
#if SIM_MAX_MEMORY_SIZE
            if ((simunsigned) len < SIM_MAX_MEMORY_SIZE / sizeof (*proto->tbl))
#endif
              return sim_type_read_table (context, proto, (unsigned) len, name);
            LOG_DEBUG_SIMTYPE_ (*name, 0, "table %s:%u new length = %llu ", context->file, context->line, len);
          }
          return nil ();
        case 7:
          if (sim_type_read_number (context, code, 0xEB, 0x10, &len)) {
#if SIM_MAX_MEMORY_SIZE
            if ((simunsigned) len < SIM_MAX_MEMORY_SIZE / sizeof (*proto->arr))
#endif
              return sim_type_read_array (context, proto, (unsigned) len, name);
            LOG_DEBUG_SIMTYPE_ (*name, 0, "array %s:%u new length = %llu ", context->file, context->line, len);
          }
          return nil ();
        case 8:
          return sim_type_read_numbers (0, proto->typ);
        case 9:
          if (sim_type_read_number (context, code, 0xF0, 0x01, &len)) {
            if (SIM_NUMBER_CHECK (len))
              return sim_type_read_numbers (len, proto->typ);
            LOG_DEBUG_SIMTYPE_ (*name, 0, "invalid %s:%u number %lld ", context->file, context->line, len);
          }
          return nil ();
        case 10:
          if (sim_type_read_number (context, code ^ 1, 0xFD, 0x100 - code, &len))
            return sim_type_read_numbers (-len, proto->typ);
          return nil ();
      }
    LOG_DEBUG_SIMTYPE_ (*name, 0, "invalid %s:%u code %02X%s (wanted %d%s) ", context->file, context->line,
                        code, sim_type_get_name (type_code_types[type_code_bytes[code]]),
                        proto ? proto->typ : SIMNIL, sim_type_get_name (proto ? proto->typ : SIMNIL));
  }
  return nil ();
}

static simbool sim_type_write_number (const struct type_context *context,
                                      simunsigned number, int code, int base, int codes) {
  unsigned len = 0;
  simbyte buffer[sizeof (number) + 1], *buf = buffer + sizeof (buffer) - 1;
  if ((simnumber) number < 0 || (simnumber) number >= codes) {
    do {
      *buf-- = (simbyte) number;
      len++;
    } while (number >>= 8);
    *buf = (simbyte) (base + len);
  } else
    *buf = (simbyte) (code + number);
  return context->streamer (context->context, pointer_new_len (buf, len + 1));
}

static simbool sim_type_write (const struct type_context *context, const simtype value, const simtype name,
                               const char *file, unsigned line) {
  unsigned len;
  simtype key, val;
  simwalker ctx;
  switch (value.typ) {
    case SIMNUMBER:
      if (value.num < 0) {
        if (value.num >= -255)
          return sim_type_write_number (context, -value.num, 0xFF, 0xFE, 0x00);
        if (value.num >= -65535 && context->version) {
          if (sim_type_write_number (context, -value.num >> 8, 0xFE, 0xFD, 0x00))
            return sim_type_write_number (context, -value.num, 0x00, 0x00, 0x10000);
          return false;
        }
      }
      if (SIM_NUMBER_CHECK (value.num))
        return sim_type_write_number (context, value.num, 0xF0, 0xF0, 0x01);
      LOG_ERROR_SIMTYPE_ (name, 0, "%s:%u write %s:%u invalid number = %lld ",
                          file, line, context->file, context->line, value.num);
      return false;
    case SIMPOINTER:
    case SIMSTRING:
      if (! sim_type_write_number (context, value.len, 0x40, 0xDF, 0x20))
        return false;
      return value.len ? context->streamer (context->context, *(simtype *) &value) : true;
    case SIMTABLE:
      if (! _sim_table_walk_first (&ctx, value, context->file, context->line))
        return false;
      if (! sim_type_write_number (context, _sim_table_count (value, context->file, context->line), 0x80, 0xE7, 0x20))
        return false;
      while ((val = table_walk_next (&ctx, &key)).typ != SIMNIL) {
        if (! sim_type_write (context, key, nil (),
                              table_get_name_elements (ctx.first), table_get_line_elements (ctx.first)))
          return false;
        if (! sim_type_write (context, val, pointer_new_len (key.str, key.len),
                              table_get_name_elements (ctx.first), table_get_line_elements (ctx.first)))
          return false;
      }
      return true;
    case SIMARRAY_NUMBER:
    case SIMARRAY_STRING:
    case SIMARRAY_TABLE:
    case SIMARRAY_ARRAY:
      if (! sim_type_write_number (context, value.len, 0xC0, 0xEB, 0x10))
        break;
      if ((len = value.len) == 0) {
        simbyte type = (simbyte) value.typ;
        return context->streamer (context->context, pointer_new_len (&type, 1));
      }
      key.len = 0;
      for (key.str = value.ptr; key.str += sizeof (*value.arr), len; len--)
        if (! sim_type_write (context, *(simtype *) key.ptr, number_new (++key.len),
                              array_get_name (value), array_get_line (value)))
          return false;
      return true;
    default:
      LOG_ERROR_SIMTYPE_ (name, 0, "%s:%u write %s:%u invalid type %d%s ",
                          file, line, context->file, context->line, value.typ, sim_type_get_name (value.typ));
  }
  return false;
}

void _sim_type_free (simtype value, const char *file, unsigned line) {
  switch (value.typ) {
    default:
      LOG_ERROR_ ("free %s:%u nil\n", file, line);
    case SIMNIL:
    case SIMNUMBER:
    case SIMPOINTER:
      break;
    case SIMSTRING:
      _sim_string_free (value, file, line);
      break;
    case SIMTABLE:
      _sim_table_free (value, file, line);
      break;
    case SIMARRAY_NUMBER:
    case SIMARRAY_STRING:
    case SIMARRAY_TABLE:
    case SIMARRAY_ARRAY:
      _sim_array_free (value, file, line);
  }
}

static simbool sim_type_callback_size (void *ctxt, simtype input) {
  *(unsigned *) ctxt += input.len;
  return true;
}

unsigned _sim_type_size (const simtype value, int version, const char *file, unsigned line) {
  unsigned len = 0;
  struct type_context ctx;
  ctx.streamer = sim_type_callback_size;
  ctx.context = &len;
  ctx.file = file, ctx.line = line;
  ctx.version = version;
  switch (value.typ) {
    case SIMTABLE:
      file = table_get_name (value), line = table_get_line (value);
      break;
    case SIMARRAY_NUMBER:
    case SIMARRAY_STRING:
    case SIMARRAY_TABLE:
    case SIMARRAY_ARRAY:
      file = array_get_name (value), line = array_get_line (value);
      break;
    default:
      file = NULL, line = 0;
  }
  return sim_type_write (&ctx, value, number_new (0), file, line) ? len : 0;
}

simtype _sim_table_read (const simtype proto, const simtype proto2, simstreamer *reader, void *context,
                         const char *file, unsigned line) {
  simtype key = number_new (0), val = nil ();
  struct type_context ctx;
  ctx.streamer = reader;
  ctx.context = context;
  ctx.proto = proto.typ != SIMNIL ? &proto2 : NULL;
  ctx.file = file, ctx.line = line;
  if (proto.typ == SIMNIL || (proto.typ == SIMTABLE && (proto2.typ == SIMNIL || proto2.typ == SIMARRAY_ARRAY)))
    val = sim_type_read (&ctx, &proto, &key);
  if (val.typ == SIMTABLE || (proto.typ != SIMNIL && val.typ == proto2.typ))
    return val;
  LOG_DEBUG_ ("table %s:%u type %d%s (got %d%s)\n", file, line,
              proto.typ, sim_type_get_name (proto.typ), val.typ, sim_type_get_name (val.typ));
  _sim_type_free (val, file, line);
  return nil ();
}

simbool _sim_table_write (const simtype table, simstreamer *writer, void *context, int version,
                          const char *file, unsigned line) {
  struct type_context ctx;
  ctx.streamer = writer;
  ctx.context = context;
  ctx.file = file, ctx.line = line;
  ctx.version = version;
#if SIM_TYPE_CHECKS
  if (table.typ != SIMTABLE && table.typ != SIMARRAY_ARRAY) {
    LOG_ERROR_ ("table %s:%u type %d%s\n", file, line, table.typ, sim_type_get_name (table.typ));
    return false;
  }
#endif
  return sim_type_write (&ctx, table, number_new (0), file, line);
}
